Index: head/sbin/hastctl/hastctl.8 =================================================================== --- head/sbin/hastctl/hastctl.8 (revision 248290) +++ head/sbin/hastctl/hastctl.8 (revision 248291) @@ -1,220 +1,228 @@ .\" Copyright (c) 2010 The FreeBSD Foundation .\" All rights reserved. .\" .\" This software was developed by Pawel Jakub Dawidek under sponsorship from .\" the FreeBSD Foundation. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" -.Dd April 10, 2011 +.Dd March 14, 2013 .Dt HASTCTL 8 .Os .Sh NAME .Nm hastctl .Nd "Highly Available Storage control utility" .Sh SYNOPSIS .Nm .Cm create .Op Fl d .Op Fl c Ar config .Op Fl e Ar extentsize .Op Fl k Ar keepdirty .Op Fl m Ar mediasize .Ar name ... .Nm .Cm role .Op Fl d .Op Fl c Ar config .Aq init | primary | secondary .Ar all | name ... .Nm +.Cm list +.Op Fl d +.Op Fl c Ar config +.Op Ar all | name ... +.Nm .Cm status .Op Fl d .Op Fl c Ar config .Op Ar all | name ... .Nm .Cm dump .Op Fl d .Op Fl c Ar config .Op Ar all | name ... .Sh DESCRIPTION The .Nm utility is used to control the behaviour of the .Xr hastd 8 daemon. .Pp This utility should be used by HA software like .Nm heartbeat or .Nm ucarp to setup HAST resources role when changing from primary mode to secondary or vice versa. Be aware that if a file system like UFS exists on HAST provider and primary node dies, file system has to be checked for inconsistencies with the .Xr fsck 8 utility after switching secondary node to primary role. .Pp The first argument to .Nm indicates an action to be performed: .Bl -tag -width ".Cm create" .It Cm create Initialize local provider configured for the given resource. Additional options include: .Bl -tag -width ".Fl e Ar extentsize" .It Fl e Ar extentsize Size of an extent. Extent is a block which is used for synchronization. .Xr hastd 8 maintains a map of dirty extents and extent is the smallest region that can be marked as dirty. If any part of an extent is modified, entire extent will be synchronized when nodes connect. If extent size is too small, there will be too much disk activity related to dirty map updates, which will degrade performance of the given resource. If extent size is too large, synchronization, even in case of short outage, can take a long time increasing the risk of losing up-to-date node before synchronization process is completed. The default extent size is .Va 2MB . .It Fl k Ar keepdirty Maximum number of dirty extents to keep dirty all the time. Most recently used extents are kept dirty to reduce number of metadata updates. The default number of most recently used extents which will be kept dirty is .Va 64 . .It Fl m Ar mediasize Size of the smaller provider used as backend storage on both nodes. This option can be omitted if node providers have the same size on both sides. .El .Pp If size is suffixed with a k, M, G or T, it is taken as a kilobyte, megabyte, gigabyte or terabyte measurement respectively. .It Cm role Change role of the given resource. The role can be one of: .Bl -tag -width ".Cm secondary" .It Cm init Resource is turned off. .It Cm primary Local .Xr hastd 8 daemon will act as primary node for the given resource. System on which resource role is set to primary can use .Pa /dev/hast/ GEOM provider. .It Cm secondary Local .Xr hastd 8 daemon will act as secondary node for the given resource - it will wait for connection from the primary node and will handle I/O requests received from it. GEOM provider .Pa /dev/hast/ will not be created on secondary node. .El +.It Cm list +Present verbose status of the configured resources. .It Cm status -Present status of the configured resources. +Present terse (and more easy machine-parseable) status of the configured +resources. .It Cm dump Dump metadata stored on local component for the configured resources. .El .Pp In addition, every subcommand can be followed by the following options: .Bl -tag -width ".Fl c Ar config" .It Fl c Ar config Specify alternative location of the configuration file. The default location is .Pa /etc/hast.conf . .It Fl d Print debugging information. This option can be specified multiple times to raise the verbosity level. .El .Sh FILES .Bl -tag -width ".Pa /var/run/hastctl" -compact .It Pa /etc/hast.conf Configuration file for .Nm and .Xr hastd 8 . .It Pa /var/run/hastctl Control socket used by .Nm to communicate with the .Xr hastd 8 daemon. .El .Sh EXIT STATUS Exit status is 0 on success, or one of the values described in .Xr sysexits 3 on failure. .Sh EXAMPLES Initialize HAST provider, create file system on it and mount it. .Bd -literal -offset indent nodeB# hastctl create shared nodeB# hastd nodeB# hastctl role secondary shared nodeA# hastctl create shared nodeA# hastd nodeA# hastctl role primary shared nodeA# newfs -U /dev/hast/shared nodeA# mount -o noatime /dev/hast/shared /shared nodeA# application_start .Ed .Pp Switch roles for the .Nm shared HAST resource. .Bd -literal -offset indent nodeA# application_stop nodeA# umount -f /shared nodeA# hastctl role secondary shared nodeB# hastctl role primary shared nodeB# fsck -t ufs /dev/hast/shared nodeB# mount -o noatime /dev/hast/shared /shared nodeB# application_start .Ed .Sh SEE ALSO .Xr sysexits 3 , .Xr geom 4 , .Xr hast.conf 5 , .Xr fsck 8 , .Xr ggatec 8 , .Xr ggatel 8 , .Xr hastd 8 , .Xr mount 8 , .Xr newfs 8 .Sh AUTHORS The .Nm was developed by .An Pawel Jakub Dawidek Aq pjd@FreeBSD.org under sponsorship of the FreeBSD Foundation. Index: head/sbin/hastctl/hastctl.c =================================================================== --- head/sbin/hastctl/hastctl.c (revision 248290) +++ head/sbin/hastctl/hastctl.c (revision 248291) @@ -1,525 +1,583 @@ /*- * Copyright (c) 2009-2010 The FreeBSD Foundation * All rights reserved. * * This software was developed by Pawel Jakub Dawidek under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include "hast.h" #include "hast_proto.h" #include "metadata.h" #include "nv.h" #include "pjdlog.h" #include "proto.h" #include "subr.h" /* Path to configuration file. */ static const char *cfgpath = HAST_CONFIG; /* Hastd configuration. */ static struct hastd_config *cfg; /* Control connection. */ static struct proto_conn *controlconn; enum { CMD_INVALID, CMD_CREATE, CMD_ROLE, CMD_STATUS, - CMD_DUMP + CMD_DUMP, + CMD_LIST }; static __dead2 void usage(void) { fprintf(stderr, "usage: %s create [-d] [-c config] [-e extentsize] [-k keepdirty]\n" "\t\t[-m mediasize] name ...\n", getprogname()); fprintf(stderr, " %s role [-d] [-c config] all | name ...\n", getprogname()); fprintf(stderr, + " %s list [-d] [-c config] [all | name ...]\n", + getprogname()); + fprintf(stderr, " %s status [-d] [-c config] [all | name ...]\n", getprogname()); fprintf(stderr, " %s dump [-d] [-c config] [all | name ...]\n", getprogname()); exit(EX_USAGE); } static int create_one(struct hast_resource *res, intmax_t mediasize, intmax_t extentsize, intmax_t keepdirty) { unsigned char *buf; size_t mapsize; int ec; ec = 0; pjdlog_prefix_set("[%s] ", res->hr_name); if (provinfo(res, true) == -1) { ec = EX_NOINPUT; goto end; } if (mediasize == 0) mediasize = res->hr_local_mediasize; else if (mediasize > res->hr_local_mediasize) { pjdlog_error("Provided mediasize is larger than provider %s size.", res->hr_localpath); ec = EX_DATAERR; goto end; } if (!powerof2(res->hr_local_sectorsize)) { pjdlog_error("Sector size of provider %s is not power of 2 (%u).", res->hr_localpath, res->hr_local_sectorsize); ec = EX_DATAERR; goto end; } if (extentsize == 0) extentsize = HAST_EXTENTSIZE; if (extentsize < res->hr_local_sectorsize) { pjdlog_error("Extent size (%jd) is less than sector size (%u).", (intmax_t)extentsize, res->hr_local_sectorsize); ec = EX_DATAERR; goto end; } if ((extentsize % res->hr_local_sectorsize) != 0) { pjdlog_error("Extent size (%jd) is not multiple of sector size (%u).", (intmax_t)extentsize, res->hr_local_sectorsize); ec = EX_DATAERR; goto end; } mapsize = activemap_calc_ondisk_size(mediasize - METADATA_SIZE, extentsize, res->hr_local_sectorsize); if (keepdirty == 0) keepdirty = HAST_KEEPDIRTY; res->hr_datasize = mediasize - METADATA_SIZE - mapsize; res->hr_extentsize = extentsize; res->hr_keepdirty = keepdirty; res->hr_localoff = METADATA_SIZE + mapsize; if (metadata_write(res) == -1) { ec = EX_IOERR; goto end; } buf = calloc(1, mapsize); if (buf == NULL) { pjdlog_error("Unable to allocate %zu bytes of memory for initial bitmap.", mapsize); ec = EX_TEMPFAIL; goto end; } if (pwrite(res->hr_localfd, buf, mapsize, METADATA_SIZE) != (ssize_t)mapsize) { pjdlog_errno(LOG_ERR, "Unable to store initial bitmap on %s", res->hr_localpath); free(buf); ec = EX_IOERR; goto end; } free(buf); end: if (res->hr_localfd >= 0) close(res->hr_localfd); pjdlog_prefix_set("%s", ""); return (ec); } static void control_create(int argc, char *argv[], intmax_t mediasize, intmax_t extentsize, intmax_t keepdirty) { struct hast_resource *res; int ec, ii, ret; /* Initialize the given resources. */ if (argc < 1) usage(); ec = 0; for (ii = 0; ii < argc; ii++) { TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { if (strcmp(argv[ii], res->hr_name) == 0) break; } if (res == NULL) { pjdlog_error("Unknown resource %s.", argv[ii]); if (ec == 0) ec = EX_DATAERR; continue; } ret = create_one(res, mediasize, extentsize, keepdirty); if (ret != 0 && ec == 0) ec = ret; } exit(ec); } static int dump_one(struct hast_resource *res) { int ret; ret = metadata_read(res, false); if (ret != 0) return (ret); printf("resource: %s\n", res->hr_name); printf(" datasize: %ju (%NB)\n", (uintmax_t)res->hr_datasize, (intmax_t)res->hr_datasize); printf(" extentsize: %d (%NB)\n", res->hr_extentsize, (intmax_t)res->hr_extentsize); printf(" keepdirty: %d\n", res->hr_keepdirty); printf(" localoff: %ju\n", (uintmax_t)res->hr_localoff); printf(" resuid: %ju\n", (uintmax_t)res->hr_resuid); printf(" localcnt: %ju\n", (uintmax_t)res->hr_primary_localcnt); printf(" remotecnt: %ju\n", (uintmax_t)res->hr_primary_remotecnt); printf(" prevrole: %s\n", role2str(res->hr_previous_role)); return (0); } static void control_dump(int argc, char *argv[]) { struct hast_resource *res; int ec, ret; /* Dump metadata of the given resource(s). */ ec = 0; if (argc == 0 || (argc == 1 && strcmp(argv[0], "all") == 0)) { TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { ret = dump_one(res); if (ret != 0 && ec == 0) ec = ret; } } else { int ii; for (ii = 0; ii < argc; ii++) { TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { if (strcmp(argv[ii], res->hr_name) == 0) break; } if (res == NULL) { pjdlog_error("Unknown resource %s.", argv[ii]); if (ec == 0) ec = EX_DATAERR; continue; } ret = dump_one(res); if (ret != 0 && ec == 0) ec = ret; } } exit(ec); } static int control_set_role(struct nv *nv, const char *newrole) { const char *res, *oldrole; unsigned int ii; int error, ret; ret = 0; for (ii = 0; ; ii++) { res = nv_get_string(nv, "resource%u", ii); if (res == NULL) break; pjdlog_prefix_set("[%s] ", res); error = nv_get_int16(nv, "error%u", ii); if (error != 0) { if (ret == 0) ret = error; pjdlog_warning("Received error %d from hastd.", error); continue; } oldrole = nv_get_string(nv, "role%u", ii); if (strcmp(oldrole, newrole) == 0) pjdlog_debug(2, "Role unchanged (%s).", oldrole); else { pjdlog_debug(1, "Role changed from %s to %s.", oldrole, newrole); } } pjdlog_prefix_set("%s", ""); return (ret); } static int -control_status(struct nv *nv) +control_list(struct nv *nv) { unsigned int ii; const char *str; int error, ret; ret = 0; for (ii = 0; ; ii++) { str = nv_get_string(nv, "resource%u", ii); if (str == NULL) break; printf("%s:\n", str); error = nv_get_int16(nv, "error%u", ii); if (error != 0) { if (ret == 0) ret = error; printf(" error: %d\n", error); continue; } printf(" role: %s\n", nv_get_string(nv, "role%u", ii)); printf(" provname: %s\n", nv_get_string(nv, "provname%u", ii)); printf(" localpath: %s\n", nv_get_string(nv, "localpath%u", ii)); printf(" extentsize: %u (%NB)\n", (unsigned int)nv_get_uint32(nv, "extentsize%u", ii), (intmax_t)nv_get_uint32(nv, "extentsize%u", ii)); printf(" keepdirty: %u\n", (unsigned int)nv_get_uint32(nv, "keepdirty%u", ii)); printf(" remoteaddr: %s\n", nv_get_string(nv, "remoteaddr%u", ii)); str = nv_get_string(nv, "sourceaddr%u", ii); if (str != NULL) printf(" sourceaddr: %s\n", str); printf(" replication: %s\n", nv_get_string(nv, "replication%u", ii)); str = nv_get_string(nv, "status%u", ii); if (str != NULL) printf(" status: %s\n", str); printf(" dirty: %ju (%NB)\n", (uintmax_t)nv_get_uint64(nv, "dirty%u", ii), (intmax_t)nv_get_uint64(nv, "dirty%u", ii)); printf(" statistics:\n"); printf(" reads: %ju\n", (uintmax_t)nv_get_uint64(nv, "stat_read%u", ii)); printf(" writes: %ju\n", (uintmax_t)nv_get_uint64(nv, "stat_write%u", ii)); printf(" deletes: %ju\n", (uintmax_t)nv_get_uint64(nv, "stat_delete%u", ii)); printf(" flushes: %ju\n", (uintmax_t)nv_get_uint64(nv, "stat_flush%u", ii)); printf(" activemap updates: %ju\n", (uintmax_t)nv_get_uint64(nv, "stat_activemap_update%u", ii)); printf(" local errors: " "read: %ju, write: %ju, delete: %ju, flush: %ju\n", (uintmax_t)nv_get_uint64(nv, "stat_read_error%u", ii), (uintmax_t)nv_get_uint64(nv, "stat_write_error%u", ii), (uintmax_t)nv_get_uint64(nv, "stat_delete_error%u", ii), (uintmax_t)nv_get_uint64(nv, "stat_flush_error%u", ii)); } return (ret); } +static int +control_status(struct nv *nv) +{ + unsigned int ii; + const char *str; + int error, hprinted, ret; + + hprinted = 0; + ret = 0; + + for (ii = 0; ; ii++) { + str = nv_get_string(nv, "resource%u", ii); + if (str == NULL) + break; + if (!hprinted) { + printf("Name\tStatus\t Role\t\tComponents\n"); + hprinted = 1; + } + printf("%s\t", str); + error = nv_get_int16(nv, "error%u", ii); + if (error != 0) { + if (ret == 0) + ret = error; + printf("ERR%d\n", error); + continue; + } + str = nv_get_string(nv, "status%u", ii); + printf("%-9s", (str != NULL) ? str : "-"); + printf("%-15s", nv_get_string(nv, "role%u", ii)); + printf("%s\t", + nv_get_string(nv, "localpath%u", ii)); + printf("%s\n", + nv_get_string(nv, "remoteaddr%u", ii)); + } + return (ret); +} + int main(int argc, char *argv[]) { struct nv *nv; int64_t mediasize, extentsize, keepdirty; int cmd, debug, error, ii; const char *optstr; debug = 0; mediasize = extentsize = keepdirty = 0; if (argc == 1) usage(); if (strcmp(argv[1], "create") == 0) { cmd = CMD_CREATE; optstr = "c:de:k:m:h"; } else if (strcmp(argv[1], "role") == 0) { cmd = CMD_ROLE; optstr = "c:dh"; + } else if (strcmp(argv[1], "list") == 0) { + cmd = CMD_LIST; + optstr = "c:dh"; } else if (strcmp(argv[1], "status") == 0) { cmd = CMD_STATUS; optstr = "c:dh"; } else if (strcmp(argv[1], "dump") == 0) { cmd = CMD_DUMP; optstr = "c:dh"; } else usage(); argc--; argv++; for (;;) { int ch; ch = getopt(argc, argv, optstr); if (ch == -1) break; switch (ch) { case 'c': cfgpath = optarg; break; case 'd': debug++; break; case 'e': if (expand_number(optarg, &extentsize) == -1) errx(EX_USAGE, "Invalid extentsize"); break; case 'k': if (expand_number(optarg, &keepdirty) == -1) errx(EX_USAGE, "Invalid keepdirty"); break; case 'm': if (expand_number(optarg, &mediasize) == -1) errx(EX_USAGE, "Invalid mediasize"); break; case 'h': default: usage(); } } argc -= optind; argv += optind; switch (cmd) { case CMD_CREATE: case CMD_ROLE: if (argc == 0) usage(); break; } pjdlog_init(PJDLOG_MODE_STD); pjdlog_debug_set(debug); cfg = yy_config_parse(cfgpath, true); PJDLOG_ASSERT(cfg != NULL); switch (cmd) { case CMD_CREATE: control_create(argc, argv, mediasize, extentsize, keepdirty); /* NOTREACHED */ PJDLOG_ABORT("What are we doing here?!"); break; case CMD_DUMP: /* Dump metadata from local component of the given resource. */ control_dump(argc, argv); /* NOTREACHED */ PJDLOG_ABORT("What are we doing here?!"); break; case CMD_ROLE: /* Change role for the given resources. */ if (argc < 2) usage(); nv = nv_alloc(); nv_add_uint8(nv, HASTCTL_CMD_SETROLE, "cmd"); if (strcmp(argv[0], "init") == 0) nv_add_uint8(nv, HAST_ROLE_INIT, "role"); else if (strcmp(argv[0], "primary") == 0) nv_add_uint8(nv, HAST_ROLE_PRIMARY, "role"); else if (strcmp(argv[0], "secondary") == 0) nv_add_uint8(nv, HAST_ROLE_SECONDARY, "role"); else usage(); for (ii = 0; ii < argc - 1; ii++) nv_add_string(nv, argv[ii + 1], "resource%d", ii); break; + case CMD_LIST: + /* Obtain verbose status of the given resources. */ + nv = nv_alloc(); + nv_add_uint8(nv, HASTCTL_CMD_STATUS, "cmd"); + if (argc == 0) + nv_add_string(nv, "all", "resource%d", 0); + else { + for (ii = 0; ii < argc; ii++) + nv_add_string(nv, argv[ii], "resource%d", ii); + } + break; case CMD_STATUS: - /* Obtain status of the given resources. */ + /* Obtain brief status of the given resources. */ nv = nv_alloc(); nv_add_uint8(nv, HASTCTL_CMD_STATUS, "cmd"); if (argc == 0) nv_add_string(nv, "all", "resource%d", 0); else { for (ii = 0; ii < argc; ii++) nv_add_string(nv, argv[ii], "resource%d", ii); } break; default: PJDLOG_ABORT("Impossible command!"); } /* Setup control connection... */ if (proto_client(NULL, cfg->hc_controladdr, &controlconn) == -1) { pjdlog_exit(EX_OSERR, "Unable to setup control connection to %s", cfg->hc_controladdr); } /* ...and connect to hastd. */ if (proto_connect(controlconn, HAST_TIMEOUT) == -1) { pjdlog_exit(EX_OSERR, "Unable to connect to hastd via %s", cfg->hc_controladdr); } if (drop_privs(NULL) != 0) exit(EX_CONFIG); /* Send the command to the server... */ if (hast_proto_send(NULL, controlconn, nv, NULL, 0) == -1) { pjdlog_exit(EX_UNAVAILABLE, "Unable to send command to hastd via %s", cfg->hc_controladdr); } nv_free(nv); /* ...and receive reply. */ if (hast_proto_recv_hdr(controlconn, &nv) == -1) { pjdlog_exit(EX_UNAVAILABLE, "cannot receive reply from hastd via %s", cfg->hc_controladdr); } error = nv_get_int16(nv, "error"); if (error != 0) { pjdlog_exitx(EX_SOFTWARE, "Error %d received from hastd.", error); } nv_set_error(nv, 0); switch (cmd) { case CMD_ROLE: error = control_set_role(nv, argv[0]); + break; + case CMD_LIST: + error = control_list(nv); break; case CMD_STATUS: error = control_status(nv); break; default: PJDLOG_ABORT("Impossible command!"); } exit(error); }