]> git.proxmox.com Git - pve-cluster.git/blob - src/pmxcfs/status.c
c8094ac8befe5f30f17b8dea7a40b5c59edaa831
[pve-cluster.git] / src / pmxcfs / status.c
1 /*
2 Copyright (C) 2010 - 2020 Proxmox Server Solutions GmbH
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Affero General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Affero General Public License for more details.
13
14 You should have received a copy of the GNU Affero General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 Author: Dietmar Maurer <dietmar@proxmox.com>
18
19 */
20
21 #define G_LOG_DOMAIN "status"
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif /* HAVE_CONFIG_H */
26
27 #include <stdio.h>
28 #include <stdint.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <glib.h>
32 #include <sys/syslog.h>
33 #include <rrd.h>
34 #include <rrd_client.h>
35 #include <time.h>
36 #include <ctype.h>
37
38 #include "cfs-utils.h"
39 #include "status.h"
40 #include "memdb.h"
41 #include "logger.h"
42
43 #define KVSTORE_CPG_GROUP_NAME "pve_kvstore_v1"
44
45 typedef enum {
46 KVSTORE_MESSAGE_UPDATE = 1,
47 KVSTORE_MESSAGE_UPDATE_COMPLETE = 2,
48 KVSTORE_MESSAGE_LOG = 3,
49 } kvstore_message_t;
50
51 static uint32_t vminfo_version_counter;
52
53 typedef struct {
54 uint32_t vmid;
55 char *nodename;
56 int vmtype;
57 uint32_t version;
58 } vminfo_t;
59
60 typedef struct {
61 char *key;
62 gpointer data;
63 size_t len;
64 uint32_t version;
65 } kventry_t;
66
67 typedef struct {
68 char *key;
69 gpointer data;
70 size_t len;
71 uint32_t time;
72 } rrdentry_t;
73
74 typedef struct {
75 char *path;
76 uint32_t version;
77 } memdb_change_t;
78
79 static memdb_change_t memdb_change_array[] = {
80 { .path = "corosync.conf" },
81 { .path = "corosync.conf.new" },
82 { .path = "storage.cfg" },
83 { .path = "user.cfg" },
84 { .path = "domains.cfg" },
85 { .path = "notifications.cfg"},
86 { .path = "priv/notifications.cfg"},
87 { .path = "priv/shadow.cfg" },
88 { .path = "priv/acme/plugins.cfg" },
89 { .path = "priv/tfa.cfg" },
90 { .path = "priv/token.cfg" },
91 { .path = "priv/ipam.db" },
92 { .path = "datacenter.cfg" },
93 { .path = "vzdump.cron" },
94 { .path = "vzdump.conf" },
95 { .path = "jobs.cfg" },
96 { .path = "ha/crm_commands" },
97 { .path = "ha/manager_status" },
98 { .path = "ha/resources.cfg" },
99 { .path = "ha/groups.cfg" },
100 { .path = "ha/fence.cfg" },
101 { .path = "status.cfg" },
102 { .path = "replication.cfg" },
103 { .path = "ceph.conf" },
104 { .path = "sdn/vnets.cfg" },
105 { .path = "sdn/zones.cfg" },
106 { .path = "sdn/controllers.cfg" },
107 { .path = "sdn/subnets.cfg" },
108 { .path = "sdn/ipams.cfg" },
109 { .path = "sdn/dns.cfg" },
110 { .path = "sdn/.running-config" },
111 { .path = "virtual-guest/cpu-models.conf" },
112 { .path = "firewall/cluster.fw" },
113 { .path = "mapping/pci.cfg" },
114 { .path = "mapping/usb.cfg" },
115 };
116
117 static GMutex mutex;
118
119 typedef struct {
120 time_t start_time;
121
122 uint32_t quorate;
123
124 cfs_clinfo_t *clinfo;
125 uint32_t clinfo_version;
126
127 GHashTable *vmlist;
128 uint32_t vmlist_version;
129
130 dfsm_t *kvstore;
131 GHashTable *kvhash;
132 GHashTable *rrdhash;
133 GHashTable *iphash;
134
135 GHashTable *memdb_changes;
136
137 clusterlog_t *clusterlog;
138 } cfs_status_t;
139
140 static cfs_status_t cfs_status;
141
142 struct cfs_clnode {
143 char *name;
144 uint32_t nodeid;
145 uint32_t votes;
146 gboolean online;
147 GHashTable *kvhash;
148 };
149
150 struct cfs_clinfo {
151 char *cluster_name;
152 uint32_t cman_version;
153
154 GHashTable *nodes_byid;
155 GHashTable *nodes_byname;
156 };
157
158 static guint
159 g_int32_hash (gconstpointer v)
160 {
161 return *(const uint32_t *) v;
162 }
163
164 static gboolean
165 g_int32_equal (gconstpointer v1,
166 gconstpointer v2)
167 {
168 return *((const uint32_t*) v1) == *((const uint32_t*) v2);
169 }
170
171 static void vminfo_free(vminfo_t *vminfo)
172 {
173 g_return_if_fail(vminfo != NULL);
174
175 if (vminfo->nodename)
176 g_free(vminfo->nodename);
177
178
179 g_free(vminfo);
180 }
181
182 static const char *vminfo_type_to_string(vminfo_t *vminfo)
183 {
184 if (vminfo->vmtype == VMTYPE_QEMU) {
185 return "qemu";
186 } else if (vminfo->vmtype == VMTYPE_OPENVZ) {
187 // FIXME: remove openvz stuff for 7.x
188 return "openvz";
189 } else if (vminfo->vmtype == VMTYPE_LXC) {
190 return "lxc";
191 } else {
192 return "unknown";
193 }
194 }
195
196 static const char *vminfo_type_to_path_type(vminfo_t *vminfo)
197 {
198 if (vminfo->vmtype == VMTYPE_QEMU) {
199 return "qemu-server"; // special case..
200 } else {
201 return vminfo_type_to_string(vminfo);
202 }
203 }
204
205 int vminfo_to_path(vminfo_t *vminfo, GString *path)
206 {
207 g_return_val_if_fail(vminfo != NULL, -1);
208 g_return_val_if_fail(path != NULL, -1);
209
210 if (!vminfo->nodename)
211 return 0;
212
213 const char *type = vminfo_type_to_path_type(vminfo);
214 g_string_printf(path, "/nodes/%s/%s/%u.conf", vminfo->nodename, type, vminfo->vmid);
215
216 return 1;
217 }
218
219 void cfs_clnode_destroy(
220 cfs_clnode_t *clnode)
221 {
222 g_return_if_fail(clnode != NULL);
223
224 if (clnode->kvhash)
225 g_hash_table_destroy(clnode->kvhash);
226
227 if (clnode->name)
228 g_free(clnode->name);
229
230 g_free(clnode);
231 }
232
233 cfs_clnode_t *cfs_clnode_new(
234 const char *name,
235 uint32_t nodeid,
236 uint32_t votes)
237 {
238 g_return_val_if_fail(name != NULL, NULL);
239
240 cfs_clnode_t *clnode = g_new0(cfs_clnode_t, 1);
241 if (!clnode)
242 return NULL;
243
244 clnode->name = g_strdup(name);
245 clnode->nodeid = nodeid;
246 clnode->votes = votes;
247
248 return clnode;
249 }
250
251 gboolean cfs_clinfo_destroy(
252 cfs_clinfo_t *clinfo)
253 {
254 g_return_val_if_fail(clinfo != NULL, FALSE);
255
256 if (clinfo->cluster_name)
257 g_free(clinfo->cluster_name);
258
259 if (clinfo->nodes_byname)
260 g_hash_table_destroy(clinfo->nodes_byname);
261
262 if (clinfo->nodes_byid)
263 g_hash_table_destroy(clinfo->nodes_byid);
264
265 g_free(clinfo);
266
267 return TRUE;
268 }
269
270 cfs_clinfo_t *cfs_clinfo_new(
271 const char *cluster_name,
272 uint32_t cman_version)
273 {
274 g_return_val_if_fail(cluster_name != NULL, NULL);
275
276 cfs_clinfo_t *clinfo = g_new0(cfs_clinfo_t, 1);
277 if (!clinfo)
278 return NULL;
279
280 clinfo->cluster_name = g_strdup(cluster_name);
281 clinfo->cman_version = cman_version;
282
283 if (!(clinfo->nodes_byid = g_hash_table_new_full(
284 g_int32_hash, g_int32_equal, NULL,
285 (GDestroyNotify)cfs_clnode_destroy)))
286 goto fail;
287
288 if (!(clinfo->nodes_byname = g_hash_table_new(g_str_hash, g_str_equal)))
289 goto fail;
290
291 return clinfo;
292
293 fail:
294 cfs_clinfo_destroy(clinfo);
295
296 return NULL;
297 }
298
299 gboolean cfs_clinfo_add_node(
300 cfs_clinfo_t *clinfo,
301 cfs_clnode_t *clnode)
302 {
303 g_return_val_if_fail(clinfo != NULL, FALSE);
304 g_return_val_if_fail(clnode != NULL, FALSE);
305
306 g_hash_table_replace(clinfo->nodes_byid, &clnode->nodeid, clnode);
307 g_hash_table_replace(clinfo->nodes_byname, clnode->name, clnode);
308
309 return TRUE;
310 }
311
312 int
313 cfs_create_memberlist_msg(
314 GString *str)
315 {
316 g_return_val_if_fail(str != NULL, -EINVAL);
317
318 g_mutex_lock (&mutex);
319
320 g_string_append_printf(str,"{\n");
321
322 guint nodecount = 0;
323
324 cfs_clinfo_t *clinfo = cfs_status.clinfo;
325
326 if (clinfo && clinfo->nodes_byid)
327 nodecount = g_hash_table_size(clinfo->nodes_byid);
328
329 if (nodecount) {
330 g_string_append_printf(str, "\"nodename\": \"%s\",\n", cfs.nodename);
331 g_string_append_printf(str, "\"version\": %u,\n", cfs_status.clinfo_version);
332
333 g_string_append_printf(str, "\"cluster\": { ");
334 g_string_append_printf(str, "\"name\": \"%s\", \"version\": %d, "
335 "\"nodes\": %d, \"quorate\": %d ",
336 clinfo->cluster_name, clinfo->cman_version,
337 nodecount, cfs_status.quorate);
338
339 g_string_append_printf(str,"},\n");
340 g_string_append_printf(str,"\"nodelist\": {\n");
341
342 GHashTable *ht = clinfo->nodes_byid;
343 GHashTableIter iter;
344 gpointer key, value;
345
346 g_hash_table_iter_init (&iter, ht);
347
348 int i = 0;
349 while (g_hash_table_iter_next (&iter, &key, &value)) {
350 cfs_clnode_t *node = (cfs_clnode_t *)value;
351 if (i) g_string_append_printf(str, ",\n");
352 i++;
353
354 g_string_append_printf(str, " \"%s\": { \"id\": %d, \"online\": %d",
355 node->name, node->nodeid, node->online);
356
357
358 char *ip = (char *)g_hash_table_lookup(cfs_status.iphash, node->name);
359 if (ip) {
360 g_string_append_printf(str, ", \"ip\": \"%s\"", ip);
361 }
362
363 g_string_append_printf(str, "}");
364
365 }
366 g_string_append_printf(str,"\n }\n");
367 } else {
368 g_string_append_printf(str, "\"nodename\": \"%s\",\n", cfs.nodename);
369 g_string_append_printf(str, "\"version\": %u\n", cfs_status.clinfo_version);
370 }
371
372 g_string_append_printf(str,"}\n");
373
374 g_mutex_unlock (&mutex);
375
376 return 0;
377 }
378
379 static void
380 kventry_free(kventry_t *entry)
381 {
382 g_return_if_fail(entry != NULL);
383
384 g_free(entry->key);
385 g_free(entry->data);
386 g_free(entry);
387 }
388
389 static GHashTable *
390 kventry_hash_new(void)
391 {
392 return g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
393 (GDestroyNotify)kventry_free);
394 }
395
396 static void
397 rrdentry_free(rrdentry_t *entry)
398 {
399 g_return_if_fail(entry != NULL);
400
401 g_free(entry->key);
402 g_free(entry->data);
403 g_free(entry);
404 }
405
406 static GHashTable *
407 rrdentry_hash_new(void)
408 {
409 return g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
410 (GDestroyNotify)rrdentry_free);
411 }
412
413 void
414 cfs_cluster_log_dump(GString *str, const char *user, guint max_entries)
415 {
416 clusterlog_dump(cfs_status.clusterlog, str, user, max_entries);
417 }
418
419 void
420 cfs_cluster_log(clog_entry_t *entry)
421 {
422 g_return_if_fail(entry != NULL);
423
424 clusterlog_insert(cfs_status.clusterlog, entry);
425
426 if (cfs_status.kvstore) {
427 struct iovec iov[1];
428 iov[0].iov_base = (char *)entry;
429 iov[0].iov_len = clog_entry_size(entry);
430
431 if (dfsm_is_initialized(cfs_status.kvstore))
432 dfsm_send_message(cfs_status.kvstore, KVSTORE_MESSAGE_LOG, iov, 1);
433 }
434 }
435
436 void cfs_status_init(void)
437 {
438 g_mutex_lock (&mutex);
439
440 cfs_status.start_time = time(NULL);
441
442 cfs_status.vmlist = vmlist_hash_new();
443
444 cfs_status.kvhash = kventry_hash_new();
445
446 cfs_status.rrdhash = rrdentry_hash_new();
447
448 cfs_status.iphash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
449
450 cfs_status.memdb_changes = g_hash_table_new(g_str_hash, g_str_equal);
451
452 for (int i = 0; i < G_N_ELEMENTS(memdb_change_array); i++) {
453 g_hash_table_replace(cfs_status.memdb_changes,
454 memdb_change_array[i].path,
455 &memdb_change_array[i]);
456 }
457
458 cfs_status.clusterlog = clusterlog_new();
459
460 // fixme:
461 clusterlog_add(cfs_status.clusterlog, "root", "cluster", getpid(),
462 LOG_INFO, "starting cluster log");
463
464 g_mutex_unlock (&mutex);
465 }
466
467 void cfs_status_cleanup(void)
468 {
469 g_mutex_lock (&mutex);
470
471 cfs_status.clinfo_version++;
472
473 if (cfs_status.clinfo) {
474 cfs_clinfo_destroy(cfs_status.clinfo);
475 cfs_status.clinfo = NULL;
476 }
477
478 if (cfs_status.vmlist) {
479 g_hash_table_destroy(cfs_status.vmlist);
480 cfs_status.vmlist = NULL;
481 }
482
483 if (cfs_status.kvhash) {
484 g_hash_table_destroy(cfs_status.kvhash);
485 cfs_status.kvhash = NULL;
486 }
487
488 if (cfs_status.rrdhash) {
489 g_hash_table_destroy(cfs_status.rrdhash);
490 cfs_status.rrdhash = NULL;
491 }
492
493 if (cfs_status.iphash) {
494 g_hash_table_destroy(cfs_status.iphash);
495 cfs_status.iphash = NULL;
496 }
497
498 if (cfs_status.clusterlog)
499 clusterlog_destroy(cfs_status.clusterlog);
500
501 g_mutex_unlock (&mutex);
502 }
503
504 void cfs_status_set_clinfo(
505 cfs_clinfo_t *clinfo)
506 {
507 g_return_if_fail(clinfo != NULL);
508
509 g_mutex_lock (&mutex);
510
511 cfs_status.clinfo_version++;
512
513 cfs_clinfo_t *old = cfs_status.clinfo;
514
515 cfs_status.clinfo = clinfo;
516
517 cfs_message("update cluster info (cluster name %s, version = %d)",
518 clinfo->cluster_name, clinfo->cman_version);
519
520
521 if (old && old->nodes_byid && clinfo->nodes_byid) {
522 /* copy kvstore */
523 GHashTable *ht = clinfo->nodes_byid;
524 GHashTableIter iter;
525 gpointer key, value;
526
527 g_hash_table_iter_init (&iter, ht);
528
529 while (g_hash_table_iter_next (&iter, &key, &value)) {
530 cfs_clnode_t *node = (cfs_clnode_t *)value;
531 cfs_clnode_t *oldnode;
532 if ((oldnode = g_hash_table_lookup(old->nodes_byid, key))) {
533 node->online = oldnode->online;
534 node->kvhash = oldnode->kvhash;
535 oldnode->kvhash = NULL;
536 }
537 }
538
539 }
540
541 if (old)
542 cfs_clinfo_destroy(old);
543
544
545 g_mutex_unlock (&mutex);
546 }
547
548 static void
549 dump_kvstore_versions(
550 GString *str,
551 GHashTable *kvhash,
552 const char *nodename)
553 {
554 g_return_if_fail(kvhash != NULL);
555 g_return_if_fail(str != NULL);
556 g_return_if_fail(nodename != NULL);
557
558 GHashTable *ht = kvhash;
559 GHashTableIter iter;
560 gpointer key, value;
561
562 g_string_append_printf(str, "\"%s\": {\n", nodename);
563
564 g_hash_table_iter_init (&iter, ht);
565
566 int i = 0;
567 while (g_hash_table_iter_next (&iter, &key, &value)) {
568 kventry_t *entry = (kventry_t *)value;
569 if (i) g_string_append_printf(str, ",\n");
570 i++;
571 g_string_append_printf(str,"\"%s\": %u", entry->key, entry->version);
572 }
573
574 g_string_append_printf(str, "}\n");
575 }
576
577 int
578 cfs_create_version_msg(GString *str)
579 {
580 g_return_val_if_fail(str != NULL, -EINVAL);
581
582 g_mutex_lock (&mutex);
583
584 g_string_append_printf(str,"{\n");
585
586 g_string_append_printf(str, "\"starttime\": %lu,\n", (unsigned long)cfs_status.start_time);
587
588 g_string_append_printf(str, "\"clinfo\": %u,\n", cfs_status.clinfo_version);
589
590 g_string_append_printf(str, "\"vmlist\": %u,\n", cfs_status.vmlist_version);
591
592 for (int i = 0; i < G_N_ELEMENTS(memdb_change_array); i++) {
593 g_string_append_printf(str, "\"%s\": %u,\n",
594 memdb_change_array[i].path,
595 memdb_change_array[i].version);
596 }
597
598 g_string_append_printf(str, "\"kvstore\": {\n");
599
600 dump_kvstore_versions(str, cfs_status.kvhash, cfs.nodename);
601
602 cfs_clinfo_t *clinfo = cfs_status.clinfo;
603
604 if (clinfo && clinfo->nodes_byid) {
605 GHashTable *ht = clinfo->nodes_byid;
606 GHashTableIter iter;
607 gpointer key, value;
608
609 g_hash_table_iter_init (&iter, ht);
610
611 while (g_hash_table_iter_next (&iter, &key, &value)) {
612 cfs_clnode_t *node = (cfs_clnode_t *)value;
613 if (!node->kvhash)
614 continue;
615 g_string_append_printf(str, ",\n");
616 dump_kvstore_versions(str, node->kvhash, node->name);
617 }
618 }
619
620 g_string_append_printf(str,"}\n");
621
622 g_string_append_printf(str,"}\n");
623
624 g_mutex_unlock (&mutex);
625
626 return 0;
627 }
628
629 GHashTable *
630 vmlist_hash_new(void)
631 {
632 return g_hash_table_new_full(g_int_hash, g_int_equal, NULL,
633 (GDestroyNotify)vminfo_free);
634 }
635
636 gboolean
637 vmlist_hash_insert_vm(
638 GHashTable *vmlist,
639 int vmtype,
640 guint32 vmid,
641 const char *nodename,
642 gboolean replace)
643 {
644 g_return_val_if_fail(vmlist != NULL, FALSE);
645 g_return_val_if_fail(nodename != NULL, FALSE);
646 g_return_val_if_fail(vmid != 0, FALSE);
647 // FIXME: remove openvz stuff for 7.x
648 g_return_val_if_fail(vmtype == VMTYPE_QEMU || vmtype == VMTYPE_OPENVZ ||
649 vmtype == VMTYPE_LXC, FALSE);
650
651 if (!replace && g_hash_table_lookup(vmlist, &vmid)) {
652 cfs_critical("detected duplicate VMID %d", vmid);
653 return FALSE;
654 }
655
656 vminfo_t *vminfo = g_new0(vminfo_t, 1);
657
658 vminfo->vmid = vmid;
659 vminfo->vmtype = vmtype;
660 vminfo->nodename = g_strdup(nodename);
661
662 vminfo->version = ++vminfo_version_counter;
663
664 g_hash_table_replace(vmlist, &vminfo->vmid, vminfo);
665
666 return TRUE;
667 }
668
669 void
670 vmlist_register_vm(
671 int vmtype,
672 guint32 vmid,
673 const char *nodename)
674 {
675 g_return_if_fail(cfs_status.vmlist != NULL);
676 g_return_if_fail(nodename != NULL);
677 g_return_if_fail(vmid != 0);
678 // FIXME: remove openvz stuff for 7.x
679 g_return_if_fail(vmtype == VMTYPE_QEMU || vmtype == VMTYPE_OPENVZ ||
680 vmtype == VMTYPE_LXC);
681
682 cfs_debug("vmlist_register_vm: %s/%u %d", nodename, vmid, vmtype);
683
684 g_mutex_lock (&mutex);
685
686 cfs_status.vmlist_version++;
687
688 vmlist_hash_insert_vm(cfs_status.vmlist, vmtype, vmid, nodename, TRUE);
689
690 g_mutex_unlock (&mutex);
691 }
692
693 gboolean
694 vmlist_different_vm_exists(
695 int vmtype,
696 guint32 vmid,
697 const char *nodename)
698 {
699 g_return_val_if_fail(cfs_status.vmlist != NULL, FALSE);
700 g_return_val_if_fail(vmid != 0, FALSE);
701
702 gboolean res = FALSE;
703
704 g_mutex_lock (&mutex);
705
706 vminfo_t *vminfo;
707 if ((vminfo = (vminfo_t *)g_hash_table_lookup(cfs_status.vmlist, &vmid))) {
708 if (!(vminfo->vmtype == vmtype && strcmp(vminfo->nodename, nodename) == 0))
709 res = TRUE;
710 }
711 g_mutex_unlock (&mutex);
712
713 return res;
714 }
715
716 gboolean
717 vmlist_vm_exists(
718 guint32 vmid)
719 {
720 g_return_val_if_fail(cfs_status.vmlist != NULL, FALSE);
721 g_return_val_if_fail(vmid != 0, FALSE);
722
723 g_mutex_lock (&mutex);
724
725 gpointer res = g_hash_table_lookup(cfs_status.vmlist, &vmid);
726
727 g_mutex_unlock (&mutex);
728
729 return res != NULL;
730 }
731
732 void
733 vmlist_delete_vm(
734 guint32 vmid)
735 {
736 g_return_if_fail(cfs_status.vmlist != NULL);
737 g_return_if_fail(vmid != 0);
738
739 g_mutex_lock (&mutex);
740
741 cfs_status.vmlist_version++;
742
743 g_hash_table_remove(cfs_status.vmlist, &vmid);
744
745 g_mutex_unlock (&mutex);
746 }
747
748 void cfs_status_set_vmlist(
749 GHashTable *vmlist)
750 {
751 g_return_if_fail(vmlist != NULL);
752
753 g_mutex_lock (&mutex);
754
755 cfs_status.vmlist_version++;
756
757 if (cfs_status.vmlist)
758 g_hash_table_destroy(cfs_status.vmlist);
759
760 cfs_status.vmlist = vmlist;
761
762 g_mutex_unlock (&mutex);
763 }
764
765 int
766 cfs_create_vmlist_msg(GString *str)
767 {
768 g_return_val_if_fail(cfs_status.vmlist != NULL, -EINVAL);
769 g_return_val_if_fail(str != NULL, -EINVAL);
770
771 g_mutex_lock (&mutex);
772
773 g_string_append_printf(str,"{\n");
774
775 GHashTable *ht = cfs_status.vmlist;
776
777 guint count = g_hash_table_size(ht);
778
779 if (!count) {
780 g_string_append_printf(str,"\"version\": %u\n", cfs_status.vmlist_version);
781 } else {
782 g_string_append_printf(str,"\"version\": %u,\n", cfs_status.vmlist_version);
783
784 g_string_append_printf(str,"\"ids\": {\n");
785
786 GHashTableIter iter;
787 gpointer key, value;
788
789 g_hash_table_iter_init (&iter, ht);
790
791 int first = 1;
792 while (g_hash_table_iter_next (&iter, &key, &value)) {
793 vminfo_t *vminfo = (vminfo_t *)value;
794 const char *type = vminfo_type_to_string(vminfo);
795
796 if (!first)
797 g_string_append_printf(str, ",\n");
798 first = 0;
799
800 g_string_append_printf(str,"\"%u\": { \"node\": \"%s\", \"type\": \"%s\", \"version\": %u }",
801 vminfo->vmid, vminfo->nodename, type, vminfo->version);
802 }
803
804 g_string_append_printf(str,"}\n");
805 }
806 g_string_append_printf(str,"\n}\n");
807
808 g_mutex_unlock (&mutex);
809
810 return 0;
811 }
812
813 // checks if a config line starts with the given prop. if yes, writes a '\0'
814 // at the end of the value, and returns the pointer where the value starts
815 // note: line[line_end] needs to be guaranteed a null byte
816 char *
817 _get_property_value_from_line(char *line, size_t line_len, const char *prop, size_t prop_len)
818 {
819 if (line_len <= prop_len + 1) return NULL;
820
821 if (line[prop_len] == ':' && memcmp(line, prop, prop_len) == 0) { // found
822 char *v_start = &line[prop_len + 1];
823 char *v_end = &line[line_len - 1];
824
825 // drop initial value whitespaces here already
826 while (v_start < v_end && *v_start && isspace(*v_start)) v_start++;
827
828 if (!*v_start) return NULL;
829
830 while (v_end > v_start && isspace(*v_end)) v_end--;
831 if (v_end < &line[line_len - 1]) {
832 v_end[1] = '\0';
833 }
834
835 return v_start;
836 }
837
838 return NULL;
839 }
840
841 // checks the conf for lines starting with the given props and
842 // writes the pointers into the correct positions into the 'found' array
843 // afterwards, without initial whitespace(s), we only deal with the format
844 // restriction imposed by our perl VM config parser, main reference is
845 // PVE::QemuServer::parse_vm_config this allows to be very fast and still
846 // relatively simple
847 // main restrictions used for our advantage is the properties match regex:
848 // ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) from parse_vm_config
849 // currently we only look at the current configuration in place, i.e., *no*
850 // snapshot and *no* pending changes
851 //
852 // min..max is the char range of the first character of the given props,
853 // so that we can return early when checking the line
854 // note: conf must end with a newline
855 void
856 _get_property_values(char **found, char *conf, int conf_size, const char **props, uint8_t num_props, char min, char max)
857 {
858 const char *const conf_end = conf + conf_size;
859 char *line = conf;
860 size_t remaining_size = conf_size;
861 uint8_t count = 0;
862
863 if (conf_size == 0) {
864 return;
865 }
866
867 char *next_newline = memchr(conf, '\n', conf_size);
868 if (next_newline == NULL) {
869 return; // valid property lines end with \n, but none in the config
870 }
871 *next_newline = '\0';
872
873 while (line != NULL) {
874 if (!line[0]) goto next;
875
876 // snapshot or pending section start, but nothing found yet -> not found
877 if (line[0] == '[') return;
878 // continue early if line does not begin with the min/max char of the properties
879 if (line[0] < min || line[0] > max) goto next;
880
881 size_t line_len = next_newline - line;
882 for (uint8_t i = 0; i < num_props; i++) {
883 char * value = _get_property_value_from_line(line, line_len, props[i], strlen(props[i]));
884 if (value != NULL) {
885 count += (found[i] != NULL) & 0x1; // count newly found lines
886 found[i] = value;
887 }
888 }
889 if (count == num_props) {
890 return; // found all
891 }
892 next:
893 line = next_newline + 1;
894 remaining_size = conf_end - line;
895 next_newline = memchr(line, '\n', remaining_size);
896 if (next_newline == NULL) {
897 return; // valid property lines end with \n, but none in the config
898 }
899 *next_newline = '\0';
900 }
901
902 return;
903 }
904
905 static void
906 _g_str_append_kv_jsonescaped(GString *str, const char *k, const char *v)
907 {
908 g_string_append_printf(str, "\"%s\": \"", k);
909
910 for (; *v; v++) {
911 if (*v == '\\' || *v == '"') {
912 g_string_append_c(str, '\\');
913 }
914 g_string_append_c(str, *v);
915 }
916
917 g_string_append_c(str, '"');
918 }
919
920 int
921 _print_found_properties(
922 GString *str,
923 gpointer conf,
924 int size,
925 const char **props,
926 uint8_t num_props,
927 uint32_t vmid,
928 char **values,
929 char min,
930 char max,
931 int first)
932 {
933 _get_property_values(values, conf, size, props, num_props, min, max);
934
935 uint8_t found = 0;
936 for (uint8_t i = 0; i < num_props; i++) {
937 if (values[i] == NULL) {
938 continue;
939 }
940 if (found) {
941 g_string_append_c(str, ',');
942 } else {
943 if (!first) {
944 g_string_append_printf(str, ",\n");
945 } else {
946 first = 0;
947 }
948 g_string_append_printf(str, "\"%u\":{", vmid);
949 found = 1;
950 }
951 _g_str_append_kv_jsonescaped(str, props[i], values[i]);
952 }
953
954 if (found) {
955 g_string_append_c(str, '}');
956 }
957
958 return first;
959 }
960
961 int
962 cfs_create_guest_conf_properties_msg(GString *str, memdb_t *memdb, const char **props, uint8_t num_props, uint32_t vmid)
963 {
964 g_return_val_if_fail(cfs_status.vmlist != NULL, -EINVAL);
965 g_return_val_if_fail(str != NULL, -EINVAL);
966
967 // Prelock &memdb->mutex in order to enable the usage of memdb_read_nolock
968 // to prevent Deadlocks as in #2553
969 g_mutex_lock (&memdb->mutex);
970 g_mutex_lock (&mutex);
971
972 g_string_printf(str, "{\n");
973
974 GHashTable *ht = cfs_status.vmlist;
975
976 int res = 0;
977 GString *path = NULL;
978 gpointer tmp = NULL;
979 char **values = calloc(num_props, sizeof(char*));
980 char min = 'z', max = 'a';
981
982 for (uint8_t i = 0; i < num_props; i++) {
983 if (props[i][0] > max) {
984 max = props[i][0];
985 }
986
987 if (props[i][0] < min) {
988 min = props[i][0];
989 }
990 }
991
992 if (!g_hash_table_size(ht)) {
993 goto ret;
994 }
995
996 if ((path = g_string_sized_new(256)) == NULL) {
997 res = -ENOMEM;
998 goto ret;
999 }
1000
1001 if (vmid >= 100) {
1002 vminfo_t *vminfo = (vminfo_t *) g_hash_table_lookup(cfs_status.vmlist, &vmid);
1003 if (vminfo == NULL) goto enoent;
1004
1005 if (!vminfo_to_path(vminfo, path)) goto err;
1006
1007 // use memdb_read_nolock because lock is handled here
1008 int size = memdb_read_nolock(memdb, path->str, &tmp);
1009 if (tmp == NULL) goto err;
1010
1011 // conf needs to be newline terminated
1012 if (((char *)tmp)[size - 1] != '\n') {
1013 gpointer new = realloc(tmp, size + 1);
1014 if (new == NULL) goto err;
1015 tmp = new;
1016 ((char *)tmp)[size++] = '\n';
1017 }
1018 _print_found_properties(str, tmp, size, props, num_props, vmid, values, min, max, 1);
1019 } else {
1020 GHashTableIter iter;
1021 g_hash_table_iter_init (&iter, ht);
1022
1023 gpointer key, value;
1024 int first = 1;
1025 while (g_hash_table_iter_next (&iter, &key, &value)) {
1026 vminfo_t *vminfo = (vminfo_t *)value;
1027
1028 if (!vminfo_to_path(vminfo, path)) goto err;
1029
1030 g_free(tmp); // no-op if already null
1031 tmp = NULL;
1032 // use memdb_read_nolock because lock is handled here
1033 int size = memdb_read_nolock(memdb, path->str, &tmp);
1034 if (tmp == NULL) continue;
1035
1036 // conf needs to be newline terminated
1037 if (((char *)tmp)[size - 1] != '\n') {
1038 gpointer new = realloc(tmp, size + 1);
1039 if (new == NULL) continue;
1040 tmp = new;
1041 ((char *)tmp)[size++] = '\n';
1042 }
1043
1044 memset(values, 0, sizeof(char*)*num_props); // reset array
1045 first = _print_found_properties(str, tmp, size, props, num_props,
1046 vminfo->vmid, values, min, max, first);
1047 }
1048 }
1049 ret:
1050 g_free(tmp);
1051 free(values);
1052 if (path != NULL) {
1053 g_string_free(path, TRUE);
1054 }
1055 g_string_append_printf(str,"\n}\n");
1056 g_mutex_unlock (&mutex);
1057 g_mutex_unlock (&memdb->mutex);
1058 return res;
1059 err:
1060 res = -EIO;
1061 goto ret;
1062 enoent:
1063 res = -ENOENT;
1064 goto ret;
1065 }
1066
1067 int
1068 cfs_create_guest_conf_property_msg(GString *str, memdb_t *memdb, const char *prop, uint32_t vmid)
1069 {
1070 return cfs_create_guest_conf_properties_msg(str, memdb, &prop, 1, vmid);
1071 }
1072
1073 void
1074 record_memdb_change(const char *path)
1075 {
1076 g_return_if_fail(cfs_status.memdb_changes != 0);
1077
1078 memdb_change_t *ce;
1079
1080 if ((ce = (memdb_change_t *)g_hash_table_lookup(cfs_status.memdb_changes, path))) {
1081 ce->version++;
1082 }
1083 }
1084
1085 void
1086 record_memdb_reload(void)
1087 {
1088 for (int i = 0; i < G_N_ELEMENTS(memdb_change_array); i++) {
1089 memdb_change_array[i].version++;
1090 }
1091 }
1092
1093 static gboolean
1094 kventry_hash_set(
1095 GHashTable *kvhash,
1096 const char *key,
1097 gconstpointer data,
1098 size_t len)
1099 {
1100 g_return_val_if_fail(kvhash != NULL, FALSE);
1101 g_return_val_if_fail(key != NULL, FALSE);
1102 g_return_val_if_fail(data != NULL, FALSE);
1103
1104 kventry_t *entry;
1105 if (!len) {
1106 g_hash_table_remove(kvhash, key);
1107 } else if ((entry = (kventry_t *)g_hash_table_lookup(kvhash, key))) {
1108 g_free(entry->data);
1109 entry->data = g_memdup2(data, len);
1110 entry->len = len;
1111 entry->version++;
1112 } else {
1113 kventry_t *entry = g_new0(kventry_t, 1);
1114
1115 entry->key = g_strdup(key);
1116 entry->data = g_memdup2(data, len);
1117 entry->len = len;
1118
1119 g_hash_table_replace(kvhash, entry->key, entry);
1120 }
1121
1122 return TRUE;
1123 }
1124
1125 static const char *rrd_def_node[] = {
1126 "DS:loadavg:GAUGE:120:0:U",
1127 "DS:maxcpu:GAUGE:120:0:U",
1128 "DS:cpu:GAUGE:120:0:U",
1129 "DS:iowait:GAUGE:120:0:U",
1130 "DS:memtotal:GAUGE:120:0:U",
1131 "DS:memused:GAUGE:120:0:U",
1132 "DS:swaptotal:GAUGE:120:0:U",
1133 "DS:swapused:GAUGE:120:0:U",
1134 "DS:roottotal:GAUGE:120:0:U",
1135 "DS:rootused:GAUGE:120:0:U",
1136 "DS:netin:DERIVE:120:0:U",
1137 "DS:netout:DERIVE:120:0:U",
1138
1139 "RRA:AVERAGE:0.5:1:70", // 1 min avg - one hour
1140 "RRA:AVERAGE:0.5:30:70", // 30 min avg - one day
1141 "RRA:AVERAGE:0.5:180:70", // 3 hour avg - one week
1142 "RRA:AVERAGE:0.5:720:70", // 12 hour avg - one month
1143 "RRA:AVERAGE:0.5:10080:70", // 7 day avg - ony year
1144
1145 "RRA:MAX:0.5:1:70", // 1 min max - one hour
1146 "RRA:MAX:0.5:30:70", // 30 min max - one day
1147 "RRA:MAX:0.5:180:70", // 3 hour max - one week
1148 "RRA:MAX:0.5:720:70", // 12 hour max - one month
1149 "RRA:MAX:0.5:10080:70", // 7 day max - ony year
1150 NULL,
1151 };
1152
1153 static const char *rrd_def_vm[] = {
1154 "DS:maxcpu:GAUGE:120:0:U",
1155 "DS:cpu:GAUGE:120:0:U",
1156 "DS:maxmem:GAUGE:120:0:U",
1157 "DS:mem:GAUGE:120:0:U",
1158 "DS:maxdisk:GAUGE:120:0:U",
1159 "DS:disk:GAUGE:120:0:U",
1160 "DS:netin:DERIVE:120:0:U",
1161 "DS:netout:DERIVE:120:0:U",
1162 "DS:diskread:DERIVE:120:0:U",
1163 "DS:diskwrite:DERIVE:120:0:U",
1164
1165 "RRA:AVERAGE:0.5:1:70", // 1 min avg - one hour
1166 "RRA:AVERAGE:0.5:30:70", // 30 min avg - one day
1167 "RRA:AVERAGE:0.5:180:70", // 3 hour avg - one week
1168 "RRA:AVERAGE:0.5:720:70", // 12 hour avg - one month
1169 "RRA:AVERAGE:0.5:10080:70", // 7 day avg - ony year
1170
1171 "RRA:MAX:0.5:1:70", // 1 min max - one hour
1172 "RRA:MAX:0.5:30:70", // 30 min max - one day
1173 "RRA:MAX:0.5:180:70", // 3 hour max - one week
1174 "RRA:MAX:0.5:720:70", // 12 hour max - one month
1175 "RRA:MAX:0.5:10080:70", // 7 day max - ony year
1176 NULL,
1177 };
1178
1179 static const char *rrd_def_storage[] = {
1180 "DS:total:GAUGE:120:0:U",
1181 "DS:used:GAUGE:120:0:U",
1182
1183 "RRA:AVERAGE:0.5:1:70", // 1 min avg - one hour
1184 "RRA:AVERAGE:0.5:30:70", // 30 min avg - one day
1185 "RRA:AVERAGE:0.5:180:70", // 3 hour avg - one week
1186 "RRA:AVERAGE:0.5:720:70", // 12 hour avg - one month
1187 "RRA:AVERAGE:0.5:10080:70", // 7 day avg - ony year
1188
1189 "RRA:MAX:0.5:1:70", // 1 min max - one hour
1190 "RRA:MAX:0.5:30:70", // 30 min max - one day
1191 "RRA:MAX:0.5:180:70", // 3 hour max - one week
1192 "RRA:MAX:0.5:720:70", // 12 hour max - one month
1193 "RRA:MAX:0.5:10080:70", // 7 day max - ony year
1194 NULL,
1195 };
1196
1197 #define RRDDIR "/var/lib/rrdcached/db"
1198
1199 static void
1200 create_rrd_file(
1201 const char *filename,
1202 int argcount,
1203 const char *rrddef[])
1204 {
1205 /* start at day boundary */
1206 time_t ctime;
1207 time(&ctime);
1208 struct tm *ltm = localtime(&ctime);
1209 ltm->tm_sec = 0;
1210 ltm->tm_min = 0;
1211 ltm->tm_hour = 0;
1212
1213 rrd_clear_error();
1214 if (rrd_create_r(filename, 60, timelocal(ltm), argcount, rrddef)) {
1215 cfs_message("RRD create error %s: %s", filename, rrd_get_error());
1216 }
1217 }
1218
1219 static inline const char *
1220 rrd_skip_data(
1221 const char *data,
1222 int count)
1223 {
1224 int found = 0;
1225 while (*data && found < count) {
1226 if (*data++ == ':')
1227 found++;
1228 }
1229 return data;
1230 }
1231
1232 static void
1233 update_rrd_data(
1234 const char *key,
1235 gconstpointer data,
1236 size_t len)
1237 {
1238 g_return_if_fail(key != NULL);
1239 g_return_if_fail(data != NULL);
1240 g_return_if_fail(len > 0);
1241 g_return_if_fail(len < 4096);
1242
1243 static const char *rrdcsock = "unix:/var/run/rrdcached.sock";
1244
1245 int use_daemon = 1;
1246 if (rrdc_connect(rrdcsock) != 0)
1247 use_daemon = 0;
1248
1249 char *filename = NULL;
1250
1251 int skip = 0;
1252
1253 if (strncmp(key, "pve2-node/", 10) == 0) {
1254 const char *node = key + 10;
1255
1256 skip = 2;
1257
1258 if (strchr(node, '/') != NULL)
1259 goto keyerror;
1260
1261 if (strlen(node) < 1)
1262 goto keyerror;
1263
1264 filename = g_strdup_printf(RRDDIR "/%s", key);
1265
1266 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
1267
1268 mkdir(RRDDIR "/pve2-node", 0755);
1269 int argcount = sizeof(rrd_def_node)/sizeof(void*) - 1;
1270 create_rrd_file(filename, argcount, rrd_def_node);
1271 }
1272
1273 } else if ((strncmp(key, "pve2-vm/", 8) == 0) ||
1274 (strncmp(key, "pve2.3-vm/", 10) == 0)) {
1275 const char *vmid;
1276
1277 if (strncmp(key, "pve2-vm/", 8) == 0) {
1278 vmid = key + 8;
1279 skip = 2;
1280 } else {
1281 vmid = key + 10;
1282 skip = 4;
1283 }
1284
1285 if (strchr(vmid, '/') != NULL)
1286 goto keyerror;
1287
1288 if (strlen(vmid) < 1)
1289 goto keyerror;
1290
1291 filename = g_strdup_printf(RRDDIR "/%s/%s", "pve2-vm", vmid);
1292
1293 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
1294
1295 mkdir(RRDDIR "/pve2-vm", 0755);
1296 int argcount = sizeof(rrd_def_vm)/sizeof(void*) - 1;
1297 create_rrd_file(filename, argcount, rrd_def_vm);
1298 }
1299
1300 } else if (strncmp(key, "pve2-storage/", 13) == 0) {
1301 const char *node = key + 13;
1302
1303 const char *storage = node;
1304 while (*storage && *storage != '/')
1305 storage++;
1306
1307 if (*storage != '/' || ((storage - node) < 1))
1308 goto keyerror;
1309
1310 storage++;
1311
1312 if (strchr(storage, '/') != NULL)
1313 goto keyerror;
1314
1315 if (strlen(storage) < 1)
1316 goto keyerror;
1317
1318 filename = g_strdup_printf(RRDDIR "/%s", key);
1319
1320 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
1321
1322 mkdir(RRDDIR "/pve2-storage", 0755);
1323
1324 char *dir = g_path_get_dirname(filename);
1325 mkdir(dir, 0755);
1326 g_free(dir);
1327
1328 int argcount = sizeof(rrd_def_storage)/sizeof(void*) - 1;
1329 create_rrd_file(filename, argcount, rrd_def_storage);
1330 }
1331
1332 } else {
1333 goto keyerror;
1334 }
1335
1336 const char *dp = skip ? rrd_skip_data(data, skip) : data;
1337
1338 const char *update_args[] = { dp, NULL };
1339
1340 if (use_daemon) {
1341 int status;
1342 if ((status = rrdc_update(filename, 1, update_args)) != 0) {
1343 cfs_message("RRDC update error %s: %d", filename, status);
1344 rrdc_disconnect();
1345 rrd_clear_error();
1346 if (rrd_update_r(filename, NULL, 1, update_args) != 0) {
1347 cfs_message("RRD update error %s: %s", filename, rrd_get_error());
1348 }
1349 }
1350
1351 } else {
1352 rrd_clear_error();
1353 if (rrd_update_r(filename, NULL, 1, update_args) != 0) {
1354 cfs_message("RRD update error %s: %s", filename, rrd_get_error());
1355 }
1356 }
1357
1358 ret:
1359 if (filename)
1360 g_free(filename);
1361
1362 return;
1363
1364 keyerror:
1365 cfs_critical("RRD update error: unknown/wrong key %s", key);
1366 goto ret;
1367 }
1368
1369 static gboolean
1370 rrd_entry_is_old(
1371 gpointer key,
1372 gpointer value,
1373 gpointer user_data)
1374 {
1375 rrdentry_t *entry = (rrdentry_t *)value;
1376 uint32_t ctime = GPOINTER_TO_UINT(user_data);
1377
1378 int diff = ctime - entry->time;
1379
1380 /* remove everything older than 5 minutes */
1381 int expire = 60*5;
1382
1383 return (diff > expire) ? TRUE : FALSE;
1384 }
1385
1386 static char *rrd_dump_buf = NULL;
1387 static time_t rrd_dump_last = 0;
1388
1389 void
1390 cfs_rrd_dump(GString *str)
1391 {
1392 time_t ctime;
1393
1394 g_mutex_lock (&mutex);
1395
1396 time(&ctime);
1397 if (rrd_dump_buf && (ctime - rrd_dump_last) < 2) {
1398 g_string_assign(str, rrd_dump_buf);
1399 g_mutex_unlock (&mutex);
1400 return;
1401 }
1402
1403 /* remove old data */
1404 g_hash_table_foreach_remove(cfs_status.rrdhash, rrd_entry_is_old,
1405 GUINT_TO_POINTER(ctime));
1406
1407 g_string_set_size(str, 0);
1408
1409 GHashTableIter iter;
1410 gpointer key, value;
1411
1412 g_hash_table_iter_init (&iter, cfs_status.rrdhash);
1413
1414 while (g_hash_table_iter_next (&iter, &key, &value)) {
1415 rrdentry_t *entry = (rrdentry_t *)value;
1416 g_string_append(str, key);
1417 g_string_append(str, ":");
1418 g_string_append(str, entry->data);
1419 g_string_append(str, "\n");
1420 }
1421
1422 g_string_append_c(str, 0); // never return undef
1423
1424 rrd_dump_last = ctime;
1425 if (rrd_dump_buf)
1426 g_free(rrd_dump_buf);
1427 rrd_dump_buf = g_strdup(str->str);
1428
1429 g_mutex_unlock (&mutex);
1430 }
1431
1432 static gboolean
1433 nodeip_hash_set(
1434 GHashTable *iphash,
1435 const char *nodename,
1436 const char *ip,
1437 size_t len)
1438 {
1439 g_return_val_if_fail(iphash != NULL, FALSE);
1440 g_return_val_if_fail(nodename != NULL, FALSE);
1441 g_return_val_if_fail(ip != NULL, FALSE);
1442 g_return_val_if_fail(len > 0, FALSE);
1443 g_return_val_if_fail(len < 256, FALSE);
1444 g_return_val_if_fail(ip[len-1] == 0, FALSE);
1445
1446 char *oldip = (char *)g_hash_table_lookup(iphash, nodename);
1447
1448 if (!oldip || (strcmp(oldip, ip) != 0)) {
1449 cfs_status.clinfo_version++;
1450 g_hash_table_replace(iphash, g_strdup(nodename), g_strdup(ip));
1451 }
1452
1453 return TRUE;
1454 }
1455
1456 static gboolean
1457 rrdentry_hash_set(
1458 GHashTable *rrdhash,
1459 const char *key,
1460 const char *data,
1461 size_t len)
1462 {
1463 g_return_val_if_fail(rrdhash != NULL, FALSE);
1464 g_return_val_if_fail(key != NULL, FALSE);
1465 g_return_val_if_fail(data != NULL, FALSE);
1466 g_return_val_if_fail(len > 0, FALSE);
1467 g_return_val_if_fail(len < 4096, FALSE);
1468 g_return_val_if_fail(data[len-1] == 0, FALSE);
1469
1470 rrdentry_t *entry;
1471 if ((entry = (rrdentry_t *)g_hash_table_lookup(rrdhash, key))) {
1472 g_free(entry->data);
1473 entry->data = g_memdup2(data, len);
1474 entry->len = len;
1475 entry->time = time(NULL);
1476 } else {
1477 rrdentry_t *entry = g_new0(rrdentry_t, 1);
1478
1479 entry->key = g_strdup(key);
1480 entry->data = g_memdup2(data, len);
1481 entry->len = len;
1482 entry->time = time(NULL);
1483
1484 g_hash_table_replace(rrdhash, entry->key, entry);
1485 }
1486
1487 update_rrd_data(key, data, len);
1488
1489 return TRUE;
1490 }
1491
1492 static int
1493 kvstore_send_update_message(
1494 dfsm_t *dfsm,
1495 const char *key,
1496 gpointer data,
1497 guint32 len)
1498 {
1499 if (!dfsm_is_initialized(dfsm))
1500 return -EACCES;
1501
1502 struct iovec iov[2];
1503
1504 char name[256];
1505 g_strlcpy(name, key, sizeof(name));
1506
1507 iov[0].iov_base = &name;
1508 iov[0].iov_len = sizeof(name);
1509
1510 iov[1].iov_base = (char *)data;
1511 iov[1].iov_len = len;
1512
1513 if (dfsm_send_message(dfsm, KVSTORE_MESSAGE_UPDATE, iov, 2) == CS_OK)
1514 return 0;
1515
1516 return -EACCES;
1517 }
1518
1519 static clog_entry_t *
1520 kvstore_parse_log_message(
1521 const void *msg,
1522 size_t msg_len)
1523 {
1524 g_return_val_if_fail(msg != NULL, NULL);
1525
1526 if (msg_len < sizeof(clog_entry_t)) {
1527 cfs_critical("received short log message (%zu < %zu)", msg_len, sizeof(clog_entry_t));
1528 return NULL;
1529 }
1530
1531 clog_entry_t *entry = (clog_entry_t *)msg;
1532
1533 uint32_t size = sizeof(clog_entry_t) + entry->node_len +
1534 entry->ident_len + entry->tag_len + entry->msg_len;
1535
1536 if (msg_len != size) {
1537 cfs_critical("received log message with wrong size (%zu != %u)", msg_len, size);
1538 return NULL;
1539 }
1540
1541 char *msgptr = entry->data;
1542
1543 if (*((char *)msgptr + entry->node_len - 1)) {
1544 cfs_critical("unterminated string in log message");
1545 return NULL;
1546 }
1547 msgptr += entry->node_len;
1548
1549 if (*((char *)msgptr + entry->ident_len - 1)) {
1550 cfs_critical("unterminated string in log message");
1551 return NULL;
1552 }
1553 msgptr += entry->ident_len;
1554
1555 if (*((char *)msgptr + entry->tag_len - 1)) {
1556 cfs_critical("unterminated string in log message");
1557 return NULL;
1558 }
1559 msgptr += entry->tag_len;
1560
1561 if (*((char *)msgptr + entry->msg_len - 1)) {
1562 cfs_critical("unterminated string in log message");
1563 return NULL;
1564 }
1565
1566 return entry;
1567 }
1568
1569 static gboolean
1570 kvstore_parse_update_message(
1571 const void *msg,
1572 size_t msg_len,
1573 const char **key,
1574 gconstpointer *data,
1575 guint32 *len)
1576 {
1577 g_return_val_if_fail(msg != NULL, FALSE);
1578 g_return_val_if_fail(key != NULL, FALSE);
1579 g_return_val_if_fail(data != NULL, FALSE);
1580 g_return_val_if_fail(len != NULL, FALSE);
1581
1582 if (msg_len < 256) {
1583 cfs_critical("received short kvstore message (%zu < 256)", msg_len);
1584 return FALSE;
1585 }
1586
1587 /* test if key is null terminated */
1588 int i = 0;
1589 for (i = 0; i < 256; i++)
1590 if (((char *)msg)[i] == 0)
1591 break;
1592
1593 if (i == 256)
1594 return FALSE;
1595
1596
1597 *len = msg_len - 256;
1598 *key = msg;
1599 *data = (char *) msg + 256;
1600
1601 return TRUE;
1602 }
1603
1604 int
1605 cfs_create_status_msg(
1606 GString *str,
1607 const char *nodename,
1608 const char *key)
1609 {
1610 g_return_val_if_fail(str != NULL, -EINVAL);
1611 g_return_val_if_fail(key != NULL, -EINVAL);
1612
1613 int res = -ENOENT;
1614
1615 GHashTable *kvhash = NULL;
1616
1617 g_mutex_lock (&mutex);
1618
1619 if (!nodename || !nodename[0] || !strcmp(nodename, cfs.nodename)) {
1620 kvhash = cfs_status.kvhash;
1621 } else if (cfs_status.clinfo && cfs_status.clinfo->nodes_byname) {
1622 cfs_clnode_t *clnode;
1623 if ((clnode = g_hash_table_lookup(cfs_status.clinfo->nodes_byname, nodename)))
1624 kvhash = clnode->kvhash;
1625 }
1626
1627 kventry_t *entry;
1628 if (kvhash && (entry = (kventry_t *)g_hash_table_lookup(kvhash, key))) {
1629 g_string_append_len(str, entry->data, entry->len);
1630 res = 0;
1631 }
1632
1633 g_mutex_unlock (&mutex);
1634
1635 return res;
1636 }
1637
1638 int
1639 cfs_status_set(
1640 const char *key,
1641 gpointer data,
1642 size_t len)
1643 {
1644 g_return_val_if_fail(key != NULL, FALSE);
1645 g_return_val_if_fail(data != NULL, FALSE);
1646 g_return_val_if_fail(cfs_status.kvhash != NULL, FALSE);
1647
1648 if (len > CFS_MAX_STATUS_SIZE)
1649 return -EFBIG;
1650
1651 g_mutex_lock (&mutex);
1652
1653 gboolean res;
1654
1655 if (strncmp(key, "rrd/", 4) == 0) {
1656 res = rrdentry_hash_set(cfs_status.rrdhash, key + 4, data, len);
1657 } else if (!strcmp(key, "nodeip")) {
1658 res = nodeip_hash_set(cfs_status.iphash, cfs.nodename, data, len);
1659 } else {
1660 res = kventry_hash_set(cfs_status.kvhash, key, data, len);
1661 }
1662 g_mutex_unlock (&mutex);
1663
1664 if (cfs_status.kvstore)
1665 kvstore_send_update_message(cfs_status.kvstore, key, data, len);
1666
1667 return res ? 0 : -ENOMEM;
1668 }
1669
1670 gboolean
1671 cfs_kvstore_node_set(
1672 uint32_t nodeid,
1673 const char *key,
1674 gconstpointer data,
1675 size_t len)
1676 {
1677 g_return_val_if_fail(nodeid != 0, FALSE);
1678 g_return_val_if_fail(key != NULL, FALSE);
1679 g_return_val_if_fail(data != NULL, FALSE);
1680
1681 g_mutex_lock (&mutex);
1682
1683 if (!cfs_status.clinfo || !cfs_status.clinfo->nodes_byid)
1684 goto ret; /* ignore */
1685
1686 cfs_clnode_t *clnode = g_hash_table_lookup(cfs_status.clinfo->nodes_byid, &nodeid);
1687 if (!clnode)
1688 goto ret; /* ignore */
1689
1690 cfs_debug("got node %d status update %s", nodeid, key);
1691
1692 if (strncmp(key, "rrd/", 4) == 0) {
1693 rrdentry_hash_set(cfs_status.rrdhash, key + 4, data, len);
1694 } else if (!strcmp(key, "nodeip")) {
1695 nodeip_hash_set(cfs_status.iphash, clnode->name, data, len);
1696 } else {
1697 if (!clnode->kvhash) {
1698 if (!(clnode->kvhash = kventry_hash_new())) {
1699 goto ret; /*ignore */
1700 }
1701 }
1702
1703 kventry_hash_set(clnode->kvhash, key, data, len);
1704
1705 }
1706 ret:
1707 g_mutex_unlock (&mutex);
1708
1709 return TRUE;
1710 }
1711
1712 static gboolean
1713 cfs_kvstore_sync(void)
1714 {
1715 g_return_val_if_fail(cfs_status.kvhash != NULL, FALSE);
1716 g_return_val_if_fail(cfs_status.kvstore != NULL, FALSE);
1717
1718 gboolean res = TRUE;
1719
1720 g_mutex_lock (&mutex);
1721
1722 GHashTable *ht = cfs_status.kvhash;
1723 GHashTableIter iter;
1724 gpointer key, value;
1725
1726 g_hash_table_iter_init (&iter, ht);
1727
1728 while (g_hash_table_iter_next (&iter, &key, &value)) {
1729 kventry_t *entry = (kventry_t *)value;
1730 kvstore_send_update_message(cfs_status.kvstore, entry->key, entry->data, entry->len);
1731 }
1732
1733 g_mutex_unlock (&mutex);
1734
1735 return res;
1736 }
1737
1738 static int
1739 dfsm_deliver(
1740 dfsm_t *dfsm,
1741 gpointer data,
1742 int *res_ptr,
1743 uint32_t nodeid,
1744 uint32_t pid,
1745 uint16_t msg_type,
1746 uint32_t msg_time,
1747 const void *msg,
1748 size_t msg_len)
1749 {
1750 g_return_val_if_fail(dfsm != NULL, -1);
1751 g_return_val_if_fail(msg != NULL, -1);
1752 g_return_val_if_fail(res_ptr != NULL, -1);
1753
1754 /* ignore message for ourself */
1755 if (dfsm_nodeid_is_local(dfsm, nodeid, pid))
1756 goto ret;
1757
1758 if (msg_type == KVSTORE_MESSAGE_UPDATE) {
1759 const char *key;
1760 gconstpointer data;
1761 guint32 len;
1762 if (kvstore_parse_update_message(msg, msg_len, &key, &data, &len)) {
1763 cfs_kvstore_node_set(nodeid, key, data, len);
1764 } else {
1765 cfs_critical("cant parse update message");
1766 }
1767 } else if (msg_type == KVSTORE_MESSAGE_LOG) {
1768 cfs_message("received log"); // fixme: remove
1769 const clog_entry_t *entry;
1770 if ((entry = kvstore_parse_log_message(msg, msg_len))) {
1771 clusterlog_insert(cfs_status.clusterlog, entry);
1772 } else {
1773 cfs_critical("cant parse log message");
1774 }
1775 } else {
1776 cfs_critical("received unknown message type %d\n", msg_type);
1777 goto fail;
1778 }
1779
1780 ret:
1781 *res_ptr = 0;
1782 return 1;
1783
1784 fail:
1785 *res_ptr = -EACCES;
1786 return 1;
1787 }
1788
1789 static void
1790 dfsm_confchg(
1791 dfsm_t *dfsm,
1792 gpointer data,
1793 const struct cpg_address *member_list,
1794 size_t member_list_entries)
1795 {
1796 g_return_if_fail(dfsm != NULL);
1797 g_return_if_fail(member_list != NULL);
1798
1799 cfs_debug("enter %s", __func__);
1800
1801 g_mutex_lock (&mutex);
1802
1803 cfs_clinfo_t *clinfo = cfs_status.clinfo;
1804
1805 if (clinfo && clinfo->nodes_byid) {
1806
1807 GHashTable *ht = clinfo->nodes_byid;
1808 GHashTableIter iter;
1809 gpointer key, value;
1810
1811 g_hash_table_iter_init (&iter, ht);
1812
1813 while (g_hash_table_iter_next (&iter, &key, &value)) {
1814 cfs_clnode_t *node = (cfs_clnode_t *)value;
1815 node->online = FALSE;
1816 }
1817
1818 for (int i = 0; i < member_list_entries; i++) {
1819 cfs_clnode_t *node;
1820 if ((node = g_hash_table_lookup(clinfo->nodes_byid, &member_list[i].nodeid))) {
1821 node->online = TRUE;
1822 }
1823 }
1824
1825 cfs_status.clinfo_version++;
1826 }
1827
1828 g_mutex_unlock (&mutex);
1829 }
1830
1831 static gpointer
1832 dfsm_get_state(
1833 dfsm_t *dfsm,
1834 gpointer data,
1835 unsigned int *res_len)
1836 {
1837 g_return_val_if_fail(dfsm != NULL, NULL);
1838
1839 gpointer msg = clusterlog_get_state(cfs_status.clusterlog, res_len);
1840
1841 return msg;
1842 }
1843
1844 static int
1845 dfsm_process_update(
1846 dfsm_t *dfsm,
1847 gpointer data,
1848 dfsm_sync_info_t *syncinfo,
1849 uint32_t nodeid,
1850 uint32_t pid,
1851 const void *msg,
1852 size_t msg_len)
1853 {
1854 cfs_critical("%s: received unexpected update message", __func__);
1855
1856 return -1;
1857 }
1858
1859 static int
1860 dfsm_process_state_update(
1861 dfsm_t *dfsm,
1862 gpointer data,
1863 dfsm_sync_info_t *syncinfo)
1864 {
1865 g_return_val_if_fail(dfsm != NULL, -1);
1866 g_return_val_if_fail(syncinfo != NULL, -1);
1867
1868 clog_base_t *clog[syncinfo->node_count];
1869
1870 int local_index = -1;
1871 for (int i = 0; i < syncinfo->node_count; i++) {
1872 dfsm_node_info_t *ni = &syncinfo->nodes[i];
1873 ni->synced = 1;
1874
1875 if (syncinfo->local == ni)
1876 local_index = i;
1877
1878 clog_base_t *base = (clog_base_t *)ni->state;
1879 if (ni->state_len > 8 && ni->state_len == clog_size(base)) {
1880 clog[i] = ni->state;
1881 } else {
1882 cfs_critical("received log with wrong size %u", ni->state_len);
1883 clog[i] = NULL;
1884 }
1885 }
1886
1887 if (!clusterlog_merge(cfs_status.clusterlog, clog, syncinfo->node_count, local_index)) {
1888 cfs_critical("unable to merge log files");
1889 }
1890
1891 cfs_kvstore_sync();
1892
1893 return 1;
1894 }
1895
1896 static int
1897 dfsm_commit(
1898 dfsm_t *dfsm,
1899 gpointer data,
1900 dfsm_sync_info_t *syncinfo)
1901 {
1902 g_return_val_if_fail(dfsm != NULL, -1);
1903 g_return_val_if_fail(syncinfo != NULL, -1);
1904
1905 return 1;
1906 }
1907
1908 static void
1909 dfsm_synced(dfsm_t *dfsm)
1910 {
1911 g_return_if_fail(dfsm != NULL);
1912
1913 char *ip = (char *)g_hash_table_lookup(cfs_status.iphash, cfs.nodename);
1914 if (!ip)
1915 ip = cfs.ip;
1916
1917 cfs_status_set("nodeip", ip, strlen(ip) + 1);
1918 }
1919
1920 static int
1921 dfsm_cleanup(
1922 dfsm_t *dfsm,
1923 gpointer data,
1924 dfsm_sync_info_t *syncinfo)
1925 {
1926 return 1;
1927 }
1928
1929 static dfsm_callbacks_t kvstore_dfsm_callbacks = {
1930 .dfsm_deliver_fn = dfsm_deliver,
1931 .dfsm_confchg_fn = dfsm_confchg,
1932
1933 .dfsm_get_state_fn = dfsm_get_state,
1934 .dfsm_process_state_update_fn = dfsm_process_state_update,
1935 .dfsm_process_update_fn = dfsm_process_update,
1936 .dfsm_commit_fn = dfsm_commit,
1937 .dfsm_cleanup_fn = dfsm_cleanup,
1938 .dfsm_synced_fn = dfsm_synced,
1939 };
1940
1941 dfsm_t *
1942 cfs_status_dfsm_new(void)
1943 {
1944 g_mutex_lock (&mutex);
1945
1946 cfs_status.kvstore = dfsm_new(NULL, KVSTORE_CPG_GROUP_NAME, G_LOG_DOMAIN,
1947 0, &kvstore_dfsm_callbacks);
1948 g_mutex_unlock (&mutex);
1949
1950 return cfs_status.kvstore;
1951 }
1952
1953 gboolean
1954 cfs_is_quorate(void)
1955 {
1956 g_mutex_lock (&mutex);
1957 gboolean res = cfs_status.quorate;
1958 g_mutex_unlock (&mutex);
1959
1960 return res;
1961 }
1962
1963 void
1964 cfs_set_quorate(
1965 uint32_t quorate,
1966 gboolean quiet)
1967 {
1968 g_mutex_lock (&mutex);
1969
1970 uint32_t prev_quorate = cfs_status.quorate;
1971 cfs_status.quorate = quorate;
1972
1973 if (!prev_quorate && cfs_status.quorate) {
1974 if (!quiet)
1975 cfs_message("node has quorum");
1976 }
1977
1978 if (prev_quorate && !cfs_status.quorate) {
1979 if (!quiet)
1980 cfs_message("node lost quorum");
1981 }
1982
1983 g_mutex_unlock (&mutex);
1984 }