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