1 /*-
2  * Copyright (c) 2013 Mikolaj Golub <trociny@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <sys/param.h>
31 #include <sys/queue.h>
32 
33 #include <bsnmp/snmpmod.h>
34 
35 #include <string.h>
36 
37 #include "hast.h"
38 #include "hast_oid.h"
39 #include "hast_proto.h"
40 #include "hast_tree.h"
41 #include "nv.h"
42 #include "pjdlog.h"
43 #include "proto.h"
44 
45 #define UPDATE_INTERVAL	500	/* update interval in ticks */
46 
47 static struct lmodule *module;
48 
49 static const struct asn_oid oid_hast = OIDX_begemotHast;
50 
51 /* the Object Resource registration index */
52 static u_int hast_index = 0;
53 
54 /*
55  * Structure that describes single resource.
56  */
57 struct hast_snmp_resource {
58 	TAILQ_ENTRY(hast_snmp_resource) link;
59 	int32_t		index;
60 	char		name[NAME_MAX];
61 	int		error;
62 	int		role;
63 	char		provname[NAME_MAX];
64 	char		localpath[PATH_MAX];
65 	int32_t		extentsize;
66 	int32_t		keepdirty;
67 	char		remoteaddr[HAST_ADDRSIZE];
68 	char		sourceaddr[HAST_ADDRSIZE];
69 	int		replication;
70 	int		status;
71 	uint64_t	dirty;
72 	uint64_t	reads;
73 	uint64_t	writes;
74 	uint64_t	deletes;
75 	uint64_t	flushes;
76 	uint64_t	activemap_updates;
77 	uint64_t	read_errors;
78 	uint64_t	write_errors;
79 	uint64_t	delete_errors;
80 	uint64_t	flush_errors;
81 	pid_t		workerpid;
82 };
83 
84 static TAILQ_HEAD(, hast_snmp_resource) resources =
85     TAILQ_HEAD_INITIALIZER(resources);
86 
87 /* Path to configuration file. */
88 static u_char *cfgpath;
89 /* Ticks of the last hast resources update. */
90 static uint64_t last_resources_update;
91 
92 static void free_resources(void);
93 static int hastctl(struct nv *nvin, struct nv **nvout);
94 static int hast_fini(void);
95 static int hast_init(struct lmodule *mod, int argc, char *argv[]);
96 static void hast_start(void);
97 static int set_role(const char *resource, int role);
98 static int str2role(const char *str);
99 static int str2replication(const char *str);
100 static int str2status(const char *str);
101 static int update_resources(void);
102 
103 const struct snmp_module config = {
104     .comment   = "This module implements the BEGEMOT MIB for HAST.",
105     .init      = hast_init,
106     .start     = hast_start,
107     .fini      = hast_fini,
108     .tree      = hast_ctree,
109     .tree_size = hast_CTREE_SIZE,
110 };
111 
112 static int
113 hast_init(struct lmodule *mod, int argc __unused, char *argv[] __unused)
114 {
115 
116 	module = mod;
117 
118 	pjdlog_init(PJDLOG_MODE_SYSLOG);
119 	pjdlog_debug_set(0);
120 
121 	cfgpath = malloc(sizeof(HAST_CONFIG));
122 	if (cfgpath == NULL) {
123 		pjdlog_error("Unable to allocate %zu bytes for cfgpath",
124 		    sizeof(HAST_CONFIG));
125 		return (-1);
126 	}
127 	strcpy(cfgpath, HAST_CONFIG);
128 	return(0);
129 }
130 
131 static void
132 hast_start(void)
133 {
134 	hast_index = or_register(&oid_hast,
135 	    "The MIB module for BEGEMOT-HAST-MIB.", module);
136 }
137 
138 static int
139 hast_fini(void)
140 {
141 
142 	or_unregister(hast_index);
143 	free_resources();
144 	free(cfgpath);
145 	return (0);
146 }
147 
148 static void
149 free_resources(void)
150 {
151 	struct hast_snmp_resource *res;
152 
153 	while ((res = TAILQ_FIRST(&resources)) != NULL) {
154 		TAILQ_REMOVE(&resources, res, link);
155 		free(res);
156 	}
157 }
158 
159 static int
160 str2role(const char *str)
161 {
162 
163 	if (strcmp(str, "init") == 0)
164 		return (HAST_ROLE_INIT);
165 	if (strcmp(str, "primary") == 0)
166 		return (HAST_ROLE_PRIMARY);
167 	if (strcmp(str, "secondary") == 0)
168 		return (HAST_ROLE_SECONDARY);
169 	return (HAST_ROLE_UNDEF);
170 }
171 
172 static int
173 str2replication(const char *str)
174 {
175 
176 	if (strcmp(str, "fullsync") == 0)
177 		return (HAST_REPLICATION_FULLSYNC);
178 	if (strcmp(str, "memsync") == 0)
179 		return (HAST_REPLICATION_MEMSYNC);
180 	if (strcmp(str, "async") == 0)
181 		return (HAST_REPLICATION_ASYNC);
182 	return (-1);
183 }
184 
185 static int
186 str2status(const char *str)
187 {
188 
189 	if (strcmp(str, "complete") == 0)
190 		return (0);
191 	if (strcmp(str, "degraded") == 0)
192 		return (1);
193 	return (-1);
194 }
195 
196 static int
197 hastctl(struct nv *nvin, struct nv **nvout)
198 {
199 	struct hastd_config *cfg;
200 	struct proto_conn *conn;
201 	struct nv *nv;
202 	int error;
203 
204 	cfg = yy_config_parse(cfgpath, true);
205 	if (cfg == NULL)
206 		return (-1);
207 
208 	/* Setup control connection... */
209 	if (proto_client(NULL, cfg->hc_controladdr, &conn) == -1) {
210 		pjdlog_error("Unable to setup control connection to %s",
211 		    cfg->hc_controladdr);
212 		return (-1);
213 	}
214 	/* ...and connect to hastd. */
215 	if (proto_connect(conn, HAST_TIMEOUT) == -1) {
216 		pjdlog_error("Unable to connect to hastd via %s",
217 		    cfg->hc_controladdr);
218 		proto_close(conn);
219 		return (-1);
220 	}
221 	/* Send the command to the server... */
222 	if (hast_proto_send(NULL, conn, nvin, NULL, 0) == -1) {
223 		pjdlog_error("Unable to send command to hastd via %s",
224 		    cfg->hc_controladdr);
225 		proto_close(conn);
226 		return (-1);
227 	}
228 	/* ...and receive reply. */
229 	if (hast_proto_recv_hdr(conn, &nv) == -1) {
230 		pjdlog_error("cannot receive reply from hastd via %s",
231 		    cfg->hc_controladdr);
232 		proto_close(conn);
233 		return (-1);
234 	}
235 	proto_close(conn);
236 	error = nv_get_int16(nv, "error");
237 	if (error != 0) {
238 		pjdlog_error("Error %d received from hastd.", error);
239 		nv_free(nv);
240 		return (-1);
241 	}
242 	nv_set_error(nv, 0);
243 	*nvout = nv;
244 	return (0);
245 }
246 
247 static int
248 set_role(const char *resource, int role)
249 {
250 	struct nv *nvin, *nvout;
251 	int error;
252 
253 	nvin = nv_alloc();
254 	nv_add_string(nvin, resource, "resource%d", 0);
255 	nv_add_uint8(nvin, HASTCTL_CMD_SETROLE, "cmd");
256 	nv_add_uint8(nvin, role, "role");
257 	error = hastctl(nvin, &nvout);
258 	nv_free(nvin);
259 	if (error != 0)
260 		return (-1);
261 	nv_free(nvout);
262 	return (SNMP_ERR_NOERROR);
263 }
264 
265 static int
266 update_resources(void)
267 {
268 	struct hast_snmp_resource *res;
269 	struct nv *nvin, *nvout;
270 	static uint64_t now;
271 	unsigned int i;
272 	const char *str;
273 	int error;
274 
275 	now = get_ticks();
276 	if (now - last_resources_update < UPDATE_INTERVAL)
277 		return (0);
278 
279 	last_resources_update = now;
280 
281 	free_resources();
282 
283 	nvin = nv_alloc();
284 	nv_add_uint8(nvin, HASTCTL_CMD_STATUS, "cmd");
285 	nv_add_string(nvin, "all", "resource%d", 0);
286 	error = hastctl(nvin, &nvout);
287 	nv_free(nvin);
288 	if (error != 0)
289 		return (-1);
290 
291 	for (i = 0; ; i++) {
292 		str = nv_get_string(nvout, "resource%u", i);
293 		if (str == NULL)
294 			break;
295 		res = calloc(1, sizeof(*res));
296 		if (res == NULL) {
297 			pjdlog_error("Unable to allocate %zu bytes for "
298 			    "resource", sizeof(*res));
299 			return (-1);
300 		}
301 		res->index = i + 1;
302 		strncpy(res->name, str, sizeof(res->name) - 1);
303 		error = nv_get_int16(nvout, "error%u", i);
304 		if (error != 0)
305 			continue;
306 		str = nv_get_string(nvout, "role%u", i);
307 		res->role = str != NULL ? str2role(str) : HAST_ROLE_UNDEF;
308 		str = nv_get_string(nvout, "provname%u", i);
309 		if (str != NULL)
310 			strncpy(res->provname, str, sizeof(res->provname) - 1);
311 		str = nv_get_string(nvout, "localpath%u", i);
312 		if (str != NULL) {
313 			strncpy(res->localpath, str,
314 			    sizeof(res->localpath) - 1);
315 		}
316 		res->extentsize = nv_get_uint32(nvout, "extentsize%u", i);
317 		res->keepdirty = nv_get_uint32(nvout, "keepdirty%u", i);
318 		str = nv_get_string(nvout, "remoteaddr%u", i);
319 		if (str != NULL) {
320 			strncpy(res->remoteaddr, str,
321 			    sizeof(res->remoteaddr) - 1);
322 		}
323 		str = nv_get_string(nvout, "sourceaddr%u", i);
324 		if (str != NULL) {
325 			strncpy(res->sourceaddr, str,
326 			    sizeof(res->sourceaddr) - 1);
327 		}
328 		str = nv_get_string(nvout, "replication%u", i);
329 		res->replication = str != NULL ? str2replication(str) : -1;
330 		str = nv_get_string(nvout, "status%u", i);
331 		res->status = str != NULL ? str2status(str) : -1;
332 		res->dirty = nv_get_uint64(nvout, "dirty%u", i);
333 		res->reads = nv_get_uint64(nvout, "stat_read%u", i);
334 		res->writes = nv_get_uint64(nvout, "stat_write%u", i);
335 		res->deletes = nv_get_uint64(nvout, "stat_delete%u", i);
336 		res->flushes = nv_get_uint64(nvout, "stat_flush%u", i);
337 		res->activemap_updates =
338 		    nv_get_uint64(nvout, "stat_activemap_update%u", i);
339 		res->read_errors =
340 		    nv_get_uint64(nvout, "stat_read_error%u", i);
341 		res->write_errors =
342 		    nv_get_uint64(nvout, "stat_write_error%u", i);
343 		res->delete_errors =
344 		    nv_get_uint64(nvout, "stat_delete_error%u", i);
345 		res->flush_errors =
346 		    nv_get_uint64(nvout, "stat_flush_error%u", i);
347 		res->workerpid = nv_get_int32(nvout, "workerpid%u", i);
348 		TAILQ_INSERT_TAIL(&resources, res, link);
349 	}
350 	nv_free(nvout);
351 	return (0);
352 }
353 
354 int
355 op_hastConfig(struct snmp_context *context, struct snmp_value *value,
356     u_int sub, u_int iidx __unused, enum snmp_op op)
357 {
358 	asn_subid_t which;
359 
360 	which = value->var.subs[sub - 1];
361 
362 	switch (op) {
363 	case SNMP_OP_GET:
364 		switch (which) {
365 		case LEAF_hastConfigFile:
366 			return (string_get(value, cfgpath, -1));
367 		default:
368 			return (SNMP_ERR_RES_UNAVAIL);
369 		}
370 	case SNMP_OP_SET:
371 		switch (which) {
372 		case LEAF_hastConfigFile:
373 			return (string_save(value, context, -1,
374 			    (u_char **)&cfgpath));
375 		default:
376 			return (SNMP_ERR_RES_UNAVAIL);
377 		}
378 	case SNMP_OP_GETNEXT:
379 	case SNMP_OP_ROLLBACK:
380 	case SNMP_OP_COMMIT:
381 		return (SNMP_ERR_NOERROR);
382 	default:
383 		return (SNMP_ERR_RES_UNAVAIL);
384 	}
385 }
386 
387 int
388 op_hastResourceTable(struct snmp_context *context __unused,
389     struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op)
390 {
391 	struct hast_snmp_resource *res;
392 	asn_subid_t which;
393 	int ret;
394 
395 	if (update_resources() == -1)
396 		return (SNMP_ERR_RES_UNAVAIL);
397 
398 	which = value->var.subs[sub - 1];
399 
400 	switch (op) {
401 	case SNMP_OP_GETNEXT:
402 		res = NEXT_OBJECT_INT(&resources, &value->var, sub);
403 		if (res == NULL)
404 			return (SNMP_ERR_NOSUCHNAME);
405 		value->var.len = sub + 1;
406 		value->var.subs[sub] = res->index;
407 		break;
408 	case SNMP_OP_GET:
409 		if (value->var.len - sub != 1)
410 			return (SNMP_ERR_NOSUCHNAME);
411 		res = FIND_OBJECT_INT(&resources, &value->var, sub);
412 		if (res == NULL)
413 			return (SNMP_ERR_NOSUCHNAME);
414 		break;
415 	case SNMP_OP_SET:
416 		res = FIND_OBJECT_INT(&resources, &value->var, sub);
417 		if (res == NULL)
418 			return (SNMP_ERR_NOSUCHNAME);
419 		switch (which) {
420 		case LEAF_hastResourceRole:
421 			ret = set_role(res->name, value->v.integer);
422 			/* force update on next run */
423 			last_resources_update = 0;
424 			break;
425 		default:
426 			ret = SNMP_ERR_NOT_WRITEABLE;
427 			break;
428 		}
429 		return ret;
430 	case SNMP_OP_ROLLBACK:
431 	case SNMP_OP_COMMIT:
432 		return (SNMP_ERR_NOERROR);
433 	default:
434 		return (SNMP_ERR_RES_UNAVAIL);
435 	}
436 
437 	ret = SNMP_ERR_NOERROR;
438 
439 	switch (which) {
440 	case LEAF_hastResourceIndex:
441 		value->v.integer = res->index;
442 		break;
443 	case LEAF_hastResourceName:
444 		ret = string_get(value, res->name, -1);
445 		break;
446 	case LEAF_hastResourceRole:
447 		value->v.integer = res->role;
448 		break;
449 	case LEAF_hastResourceProvName:
450 		ret = string_get(value, res->provname, -1);
451 		break;
452 	case LEAF_hastResourceLocalPath:
453 		ret = string_get(value, res->localpath, -1);
454 		break;
455 	case LEAF_hastResourceExtentSize:
456 		value->v.integer = res->extentsize;
457 		break;
458 	case LEAF_hastResourceKeepDirty:
459 		value->v.integer = res->keepdirty;
460 		break;
461 	case LEAF_hastResourceRemoteAddr:
462 		ret = string_get(value, res->remoteaddr, -1);
463 		break;
464 	case LEAF_hastResourceSourceAddr:
465 		ret = string_get(value, res->sourceaddr, -1);
466 		break;
467 	case LEAF_hastResourceReplication:
468 		value->v.integer = res->replication;
469 		break;
470 	case LEAF_hastResourceStatus:
471 		value->v.integer = res->status;
472 		break;
473 	case LEAF_hastResourceDirty:
474 		value->v.counter64 = res->dirty;
475 		break;
476 	case LEAF_hastResourceReads:
477 		value->v.counter64 = res->reads;
478 		break;
479 	case LEAF_hastResourceWrites:
480 		value->v.counter64 = res->writes;
481 		break;
482 	case LEAF_hastResourceDeletes:
483 		value->v.counter64 = res->deletes;
484 		break;
485 	case LEAF_hastResourceFlushes:
486 		value->v.counter64 = res->flushes;
487 		break;
488 	case LEAF_hastResourceActivemapUpdates:
489 		value->v.counter64 = res->activemap_updates;
490 		break;
491 	case LEAF_hastResourceReadErrors:
492 		value->v.counter64 = res->read_errors;
493 		break;
494 	case LEAF_hastResourceWriteErrors:
495 		value->v.counter64 = res->write_errors;
496 		break;
497 	case LEAF_hastResourceDeleteErrors:
498 		value->v.counter64 = res->delete_errors;
499 		break;
500 	case LEAF_hastResourceFlushErrors:
501 		value->v.counter64 = res->flush_errors;
502 		break;
503 	case LEAF_hastResourceWorkerPid:
504 		value->v.integer = res->workerpid;
505 		break;
506 	default:
507 		ret = SNMP_ERR_RES_UNAVAIL;
508 		break;
509 	}
510 	return (ret);
511 }
512