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