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