control.c revision 213004
1/*- 2 * Copyright (c) 2009-2010 The FreeBSD Foundation 3 * All rights reserved. 4 * 5 * This software was developed by Pawel Jakub Dawidek under sponsorship from 6 * the FreeBSD Foundation. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30#include <sys/cdefs.h> 31__FBSDID("$FreeBSD: head/sbin/hastd/control.c 213004 2010-09-22 18:39:43Z pjd $"); 32 33#include <sys/types.h> 34#include <sys/wait.h> 35 36#include <assert.h> 37#include <errno.h> 38#include <pthread.h> 39#include <signal.h> 40#include <stdio.h> 41#include <string.h> 42 43#include "hast.h" 44#include "hastd.h" 45#include "hast_proto.h" 46#include "hooks.h" 47#include "nv.h" 48#include "pjdlog.h" 49#include "proto.h" 50#include "subr.h" 51 52#include "control.h" 53 54static void 55control_set_role_common(struct hastd_config *cfg, struct nv *nvout, 56 uint8_t role, struct hast_resource *res, const char *name, unsigned int no) 57{ 58 int oldrole; 59 60 /* Name is always needed. */ 61 if (name != NULL) 62 nv_add_string(nvout, name, "resource%u", no); 63 64 if (res == NULL) { 65 assert(cfg != NULL); 66 assert(name != NULL); 67 68 TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { 69 if (strcmp(res->hr_name, name) == 0) 70 break; 71 } 72 if (res == NULL) { 73 nv_add_int16(nvout, EHAST_NOENTRY, "error%u", no); 74 return; 75 } 76 } 77 assert(res != NULL); 78 79 /* Send previous role back. */ 80 nv_add_string(nvout, role2str(res->hr_role), "role%u", no); 81 82 /* Nothing changed, return here. */ 83 if (role == res->hr_role) 84 return; 85 86 pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role)); 87 pjdlog_info("Role changed to %s.", role2str(role)); 88 89 /* Change role to the new one. */ 90 oldrole = res->hr_role; 91 res->hr_role = role; 92 pjdlog_prefix_set("[%s] (%s) ", res->hr_name, role2str(res->hr_role)); 93 94 /* 95 * If previous role was primary or secondary we have to kill process 96 * doing that work. 97 */ 98 if (res->hr_workerpid != 0) { 99 if (kill(res->hr_workerpid, SIGTERM) < 0) { 100 pjdlog_errno(LOG_WARNING, 101 "Unable to kill worker process %u", 102 (unsigned int)res->hr_workerpid); 103 } else if (waitpid(res->hr_workerpid, NULL, 0) != 104 res->hr_workerpid) { 105 pjdlog_errno(LOG_WARNING, 106 "Error while waiting for worker process %u", 107 (unsigned int)res->hr_workerpid); 108 } else { 109 pjdlog_debug(1, "Worker process %u stopped.", 110 (unsigned int)res->hr_workerpid); 111 } 112 res->hr_workerpid = 0; 113 } 114 115 /* Start worker process if we are changing to primary. */ 116 if (role == HAST_ROLE_PRIMARY) 117 hastd_primary(res); 118 pjdlog_prefix_set("%s", ""); 119 hook_exec(res->hr_exec, "role", res->hr_name, role2str(oldrole), 120 role2str(res->hr_role), NULL); 121} 122 123void 124control_set_role(struct hast_resource *res, uint8_t role) 125{ 126 127 control_set_role_common(NULL, NULL, role, res, NULL, 0); 128} 129 130static void 131control_status_worker(struct hast_resource *res, struct nv *nvout, 132 unsigned int no) 133{ 134 struct nv *cnvin, *cnvout; 135 const char *str; 136 int error; 137 138 cnvin = cnvout = NULL; 139 error = 0; 140 141 /* 142 * Prepare and send command to worker process. 143 */ 144 cnvout = nv_alloc(); 145 nv_add_uint8(cnvout, HASTCTL_STATUS, "cmd"); 146 error = nv_error(cnvout); 147 if (error != 0) { 148 /* LOG */ 149 goto end; 150 } 151 if (hast_proto_send(res, res->hr_ctrl, cnvout, NULL, 0) < 0) { 152 error = errno; 153 /* LOG */ 154 goto end; 155 } 156 157 /* 158 * Receive response. 159 */ 160 if (hast_proto_recv_hdr(res->hr_ctrl, &cnvin) < 0) { 161 error = errno; 162 /* LOG */ 163 goto end; 164 } 165 166 error = nv_get_int64(cnvin, "error"); 167 if (error != 0) 168 goto end; 169 170 if ((str = nv_get_string(cnvin, "status")) == NULL) { 171 error = ENOENT; 172 /* LOG */ 173 goto end; 174 } 175 nv_add_string(nvout, str, "status%u", no); 176 nv_add_uint64(nvout, nv_get_uint64(cnvin, "dirty"), "dirty%u", no); 177 nv_add_uint32(nvout, nv_get_uint32(cnvin, "extentsize"), 178 "extentsize%u", no); 179 nv_add_uint32(nvout, nv_get_uint32(cnvin, "keepdirty"), 180 "keepdirty%u", no); 181end: 182 if (cnvin != NULL) 183 nv_free(cnvin); 184 if (cnvout != NULL) 185 nv_free(cnvout); 186 if (error != 0) 187 nv_add_int16(nvout, error, "error"); 188} 189 190static void 191control_status(struct hastd_config *cfg, struct nv *nvout, 192 struct hast_resource *res, const char *name, unsigned int no) 193{ 194 195 assert(cfg != NULL); 196 assert(nvout != NULL); 197 assert(name != NULL); 198 199 /* Name is always needed. */ 200 nv_add_string(nvout, name, "resource%u", no); 201 202 if (res == NULL) { 203 TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { 204 if (strcmp(res->hr_name, name) == 0) 205 break; 206 } 207 if (res == NULL) { 208 nv_add_int16(nvout, EHAST_NOENTRY, "error%u", no); 209 return; 210 } 211 } 212 assert(res != NULL); 213 nv_add_string(nvout, res->hr_provname, "provname%u", no); 214 nv_add_string(nvout, res->hr_localpath, "localpath%u", no); 215 nv_add_string(nvout, res->hr_remoteaddr, "remoteaddr%u", no); 216 switch (res->hr_replication) { 217 case HAST_REPLICATION_FULLSYNC: 218 nv_add_string(nvout, "fullsync", "replication%u", no); 219 break; 220 case HAST_REPLICATION_MEMSYNC: 221 nv_add_string(nvout, "memsync", "replication%u", no); 222 break; 223 case HAST_REPLICATION_ASYNC: 224 nv_add_string(nvout, "async", "replication%u", no); 225 break; 226 default: 227 nv_add_string(nvout, "unknown", "replication%u", no); 228 break; 229 } 230 nv_add_string(nvout, role2str(res->hr_role), "role%u", no); 231 232 switch (res->hr_role) { 233 case HAST_ROLE_PRIMARY: 234 assert(res->hr_workerpid != 0); 235 /* FALLTHROUGH */ 236 case HAST_ROLE_SECONDARY: 237 if (res->hr_workerpid != 0) 238 break; 239 /* FALLTHROUGH */ 240 default: 241 return; 242 } 243 244 /* 245 * If we are here, it means that we have a worker process, which we 246 * want to ask some questions. 247 */ 248 control_status_worker(res, nvout, no); 249} 250 251void 252control_handle(struct hastd_config *cfg) 253{ 254 struct proto_conn *conn; 255 struct nv *nvin, *nvout; 256 unsigned int ii; 257 const char *str; 258 uint8_t cmd, role; 259 int error; 260 261 if (proto_accept(cfg->hc_controlconn, &conn) < 0) { 262 pjdlog_errno(LOG_ERR, "Unable to accept control connection"); 263 return; 264 } 265 266 nvin = nvout = NULL; 267 role = HAST_ROLE_UNDEF; 268 269 if (hast_proto_recv_hdr(conn, &nvin) < 0) { 270 pjdlog_errno(LOG_ERR, "Unable to receive control header"); 271 nvin = NULL; 272 goto close; 273 } 274 275 /* Obtain command code. 0 means that nv_get_uint8() failed. */ 276 cmd = nv_get_uint8(nvin, "cmd"); 277 if (cmd == 0) { 278 pjdlog_error("Control header is missing 'cmd' field."); 279 error = EHAST_INVALID; 280 goto close; 281 } 282 283 /* Allocate outgoing nv structure. */ 284 nvout = nv_alloc(); 285 if (nvout == NULL) { 286 pjdlog_error("Unable to allocate header for control response."); 287 error = EHAST_NOMEMORY; 288 goto close; 289 } 290 291 error = 0; 292 293 str = nv_get_string(nvin, "resource0"); 294 if (str == NULL) { 295 pjdlog_error("Control header is missing 'resource0' field."); 296 error = EHAST_INVALID; 297 goto fail; 298 } 299 if (cmd == HASTCTL_SET_ROLE) { 300 role = nv_get_uint8(nvin, "role"); 301 switch (role) { 302 case HAST_ROLE_INIT: /* Is that valid to set, hmm? */ 303 case HAST_ROLE_PRIMARY: 304 case HAST_ROLE_SECONDARY: 305 break; 306 default: 307 pjdlog_error("Invalid role received (%hhu).", role); 308 error = EHAST_INVALID; 309 goto fail; 310 } 311 } 312 if (strcmp(str, "all") == 0) { 313 struct hast_resource *res; 314 315 /* All configured resources. */ 316 317 ii = 0; 318 TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { 319 switch (cmd) { 320 case HASTCTL_SET_ROLE: 321 control_set_role_common(cfg, nvout, role, res, 322 res->hr_name, ii++); 323 break; 324 case HASTCTL_STATUS: 325 control_status(cfg, nvout, res, res->hr_name, 326 ii++); 327 break; 328 default: 329 pjdlog_error("Invalid command received (%hhu).", 330 cmd); 331 error = EHAST_UNIMPLEMENTED; 332 goto fail; 333 } 334 } 335 } else { 336 /* Only selected resources. */ 337 338 for (ii = 0; ; ii++) { 339 str = nv_get_string(nvin, "resource%u", ii); 340 if (str == NULL) 341 break; 342 switch (cmd) { 343 case HASTCTL_SET_ROLE: 344 control_set_role_common(cfg, nvout, role, NULL, 345 str, ii); 346 break; 347 case HASTCTL_STATUS: 348 control_status(cfg, nvout, NULL, str, ii); 349 break; 350 default: 351 pjdlog_error("Invalid command received (%hhu).", 352 cmd); 353 error = EHAST_UNIMPLEMENTED; 354 goto fail; 355 } 356 } 357 } 358 if (nv_error(nvout) != 0) 359 goto close; 360fail: 361 if (error != 0) 362 nv_add_int16(nvout, error, "error"); 363 364 if (hast_proto_send(NULL, conn, nvout, NULL, 0) < 0) 365 pjdlog_errno(LOG_ERR, "Unable to send control response"); 366close: 367 if (nvin != NULL) 368 nv_free(nvin); 369 if (nvout != NULL) 370 nv_free(nvout); 371 proto_close(conn); 372} 373 374/* 375 * Thread handles control requests from the parent. 376 */ 377void * 378ctrl_thread(void *arg) 379{ 380 struct hast_resource *res = arg; 381 struct nv *nvin, *nvout; 382 uint8_t cmd; 383 384 for (;;) { 385 if (hast_proto_recv_hdr(res->hr_ctrl, &nvin) < 0) { 386 if (sigexit_received) 387 pthread_exit(NULL); 388 pjdlog_errno(LOG_ERR, 389 "Unable to receive control message"); 390 kill(getpid(), SIGTERM); 391 pthread_exit(NULL); 392 } 393 cmd = nv_get_uint8(nvin, "cmd"); 394 if (cmd == 0) { 395 pjdlog_error("Control message is missing 'cmd' field."); 396 nv_free(nvin); 397 continue; 398 } 399 nv_free(nvin); 400 nvout = nv_alloc(); 401 switch (cmd) { 402 case HASTCTL_STATUS: 403 if (res->hr_remotein != NULL && 404 res->hr_remoteout != NULL) { 405 nv_add_string(nvout, "complete", "status"); 406 } else { 407 nv_add_string(nvout, "degraded", "status"); 408 } 409 nv_add_uint32(nvout, (uint32_t)res->hr_extentsize, 410 "extentsize"); 411 if (res->hr_role == HAST_ROLE_PRIMARY) { 412 nv_add_uint32(nvout, 413 (uint32_t)res->hr_keepdirty, "keepdirty"); 414 nv_add_uint64(nvout, 415 (uint64_t)(activemap_ndirty(res->hr_amp) * 416 res->hr_extentsize), "dirty"); 417 } else { 418 nv_add_uint32(nvout, (uint32_t)0, "keepdirty"); 419 nv_add_uint64(nvout, (uint64_t)0, "dirty"); 420 } 421 break; 422 default: 423 nv_add_int16(nvout, EINVAL, "error"); 424 break; 425 } 426 if (nv_error(nvout) != 0) { 427 pjdlog_error("Unable to create answer on control message."); 428 nv_free(nvout); 429 continue; 430 } 431 if (hast_proto_send(NULL, res->hr_ctrl, nvout, NULL, 0) < 0) { 432 pjdlog_errno(LOG_ERR, 433 "Unable to send reply to control message"); 434 } 435 nv_free(nvout); 436 } 437 /* NOTREACHED */ 438 return (NULL); 439} 440