Line data Source code
1 : /*
2 : * Copyright 2024 the Pacemaker project contributors
3 : *
4 : * The version control history for this file may have further details.
5 : *
6 : * This source code is licensed under the GNU General Public License version 2
7 : * or later (GPLv2+) WITHOUT ANY WARRANTY.
8 : */
9 :
10 : #include <crm_internal.h>
11 :
12 : #include <crm/cib/internal.h>
13 : #include <crm/pengine/internal.h>
14 :
15 : #include <pacemaker.h>
16 : #include <pacemaker-internal.h>
17 :
18 : #include "libpacemaker_private.h"
19 :
20 : static int
21 10 : build_ticket_modify_xml(cib_t *cib, const char *ticket_id, xmlNode **ticket_state_xml,
22 : xmlNode **xml_top)
23 : {
24 10 : int rc = pcmk__get_ticket_state(cib, ticket_id, ticket_state_xml);
25 :
26 10 : if (rc == pcmk_rc_ok || rc == pcmk_rc_duplicate_id) {
27 : /* Ticket(s) found - return their state */
28 9 : *xml_top = *ticket_state_xml;
29 :
30 1 : } else if (rc == ENXIO) {
31 : /* No ticket found - build the XML needed to create it */
32 1 : xmlNode *xml_obj = NULL;
33 :
34 1 : *xml_top = pcmk__xe_create(NULL, PCMK_XE_STATUS);
35 1 : xml_obj = pcmk__xe_create(*xml_top, PCMK_XE_TICKETS);
36 1 : *ticket_state_xml = pcmk__xe_create(xml_obj, PCMK__XE_TICKET_STATE);
37 1 : crm_xml_add(*ticket_state_xml, PCMK_XA_ID, ticket_id);
38 :
39 1 : rc = pcmk_rc_ok;
40 :
41 : } else {
42 : /* Some other error occurred - clean up and return */
43 0 : free_xml(*ticket_state_xml);
44 : }
45 :
46 10 : return rc;
47 : }
48 :
49 : static void
50 4 : add_attribute_xml(pcmk_scheduler_t *scheduler, const char *ticket_id,
51 : GHashTable *attr_set, xmlNode **ticket_state_xml)
52 : {
53 : GHashTableIter hash_iter;
54 4 : char *key = NULL;
55 4 : char *value = NULL;
56 :
57 4 : pcmk_ticket_t *ticket = g_hash_table_lookup(scheduler->tickets, ticket_id);
58 :
59 4 : g_hash_table_iter_init(&hash_iter, attr_set);
60 9 : while (g_hash_table_iter_next(&hash_iter, (gpointer *) & key, (gpointer *) & value)) {
61 5 : crm_xml_add(*ticket_state_xml, key, value);
62 :
63 5 : if (pcmk__str_eq(key, PCMK__XA_GRANTED, pcmk__str_none)
64 2 : && (ticket == NULL || ticket->granted == FALSE)
65 1 : && crm_is_true(value)) {
66 :
67 1 : char *now = pcmk__ttoa(time(NULL));
68 :
69 1 : crm_xml_add(*ticket_state_xml, PCMK_XA_LAST_GRANTED, now);
70 1 : free(now);
71 : }
72 : }
73 4 : }
74 :
75 : int
76 27 : pcmk__get_ticket_state(cib_t *cib, const char *ticket_id, xmlNode **state)
77 : {
78 27 : int rc = pcmk_rc_ok;
79 27 : xmlNode *xml_search = NULL;
80 27 : char *xpath = NULL;
81 :
82 27 : CRM_ASSERT(cib!= NULL && state != NULL);
83 25 : *state = NULL;
84 :
85 25 : if (ticket_id != NULL) {
86 23 : xpath = crm_strdup_printf("/" PCMK_XE_CIB "/" PCMK_XE_STATUS "/" PCMK_XE_TICKETS
87 : "/" PCMK__XE_TICKET_STATE "[@" PCMK_XA_ID "=\"%s\"]",
88 : ticket_id);
89 : } else {
90 2 : xpath = crm_strdup_printf("/" PCMK_XE_CIB "/" PCMK_XE_STATUS "/" PCMK_XE_TICKETS);
91 : }
92 :
93 25 : rc = cib->cmds->query(cib, xpath, &xml_search,
94 : cib_sync_call | cib_scope_local | cib_xpath);
95 25 : rc = pcmk_legacy2rc(rc);
96 :
97 25 : if (rc == pcmk_rc_ok) {
98 18 : crm_log_xml_debug(xml_search, "Match");
99 :
100 18 : if (xml_search->children != NULL && ticket_id != NULL) {
101 3 : rc = pcmk_rc_duplicate_id;
102 : }
103 : }
104 :
105 25 : free(xpath);
106 :
107 25 : *state = xml_search;
108 25 : return rc;
109 : }
110 :
111 : int
112 3 : pcmk__ticket_constraints(pcmk__output_t *out, cib_t *cib, const char *ticket_id)
113 : {
114 3 : int rc = pcmk_rc_ok;
115 3 : xmlNode *result = NULL;
116 3 : const char *xpath_base = NULL;
117 3 : char *xpath = NULL;
118 :
119 3 : CRM_ASSERT(out != NULL && cib != NULL);
120 :
121 3 : xpath_base = pcmk_cib_xpath_for(PCMK_XE_CONSTRAINTS);
122 3 : CRM_ASSERT(xpath_base != NULL);
123 :
124 3 : if (ticket_id != NULL) {
125 2 : xpath = crm_strdup_printf("%s/" PCMK_XE_RSC_TICKET "[@" PCMK_XA_TICKET "=\"%s\"]",
126 : xpath_base, ticket_id);
127 : } else {
128 1 : xpath = crm_strdup_printf("%s/" PCMK_XE_RSC_TICKET, xpath_base);
129 : }
130 :
131 3 : rc = cib->cmds->query(cib, (const char *) xpath, &result,
132 : cib_sync_call | cib_scope_local | cib_xpath);
133 3 : rc = pcmk_legacy2rc(rc);
134 :
135 3 : if (result != NULL) {
136 2 : out->message(out, "ticket-constraints", result);
137 2 : free_xml(result);
138 : }
139 :
140 3 : free(xpath);
141 3 : return rc;
142 : }
143 :
144 : int
145 5 : pcmk_ticket_constraints(xmlNodePtr *xml, const char *ticket_id)
146 : {
147 5 : pcmk__output_t *out = NULL;
148 5 : int rc = pcmk_rc_ok;
149 5 : cib_t *cib = NULL;
150 :
151 5 : rc = pcmk__setup_output_cib_sched(&out, &cib, NULL, xml);
152 5 : if (rc != pcmk_rc_ok) {
153 2 : goto done;
154 : }
155 :
156 3 : rc = pcmk__ticket_constraints(out, cib, ticket_id);
157 :
158 5 : done:
159 5 : if (cib != NULL) {
160 3 : cib__clean_up_connection(&cib);
161 : }
162 :
163 5 : pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
164 5 : return rc;
165 : }
166 :
167 : static int
168 4 : delete_single_ticket(xmlNode *child, void *userdata)
169 : {
170 4 : int rc = pcmk_rc_ok;
171 4 : cib_t *cib = (cib_t *) userdata;
172 :
173 4 : rc = cib->cmds->remove(cib, PCMK_XE_STATUS, child, cib_sync_call);
174 4 : rc = pcmk_legacy2rc(rc);
175 :
176 4 : return rc;
177 : }
178 :
179 : int
180 7 : pcmk__ticket_delete(pcmk__output_t *out, cib_t *cib, pcmk_scheduler_t *scheduler,
181 : const char *ticket_id, bool force)
182 : {
183 7 : int rc = pcmk_rc_ok;
184 7 : xmlNode *state = NULL;
185 :
186 7 : CRM_ASSERT(cib != NULL && scheduler != NULL);
187 :
188 7 : if (ticket_id == NULL) {
189 1 : return EINVAL;
190 : }
191 :
192 6 : if (!force) {
193 3 : pcmk_ticket_t *ticket = g_hash_table_lookup(scheduler->tickets, ticket_id);
194 :
195 3 : if (ticket == NULL) {
196 1 : return ENXIO;
197 : }
198 :
199 2 : if (ticket->granted) {
200 1 : return EACCES;
201 : }
202 : }
203 :
204 4 : rc = pcmk__get_ticket_state(cib, ticket_id, &state);
205 :
206 4 : if (rc == pcmk_rc_duplicate_id) {
207 1 : out->info(out, "Multiple " PCMK__XE_TICKET_STATE "s match ticket=%s",
208 : ticket_id);
209 :
210 3 : } else if (rc == ENXIO) {
211 1 : return pcmk_rc_ok;
212 :
213 2 : } else if (rc != pcmk_rc_ok) {
214 0 : return rc;
215 : }
216 :
217 3 : crm_log_xml_debug(state, "Delete");
218 :
219 3 : if (rc == pcmk_rc_duplicate_id) {
220 1 : rc = pcmk__xe_foreach_child(state, NULL, delete_single_ticket, cib);
221 : } else {
222 2 : rc = delete_single_ticket(state, cib);
223 : }
224 :
225 3 : if (rc == pcmk_rc_ok) {
226 3 : out->info(out, "Cleaned up %s", ticket_id);
227 : }
228 :
229 3 : free_xml(state);
230 3 : return rc;
231 : }
232 :
233 : int
234 9 : pcmk_ticket_delete(xmlNodePtr *xml, const char *ticket_id, bool force)
235 : {
236 9 : pcmk_scheduler_t *scheduler = NULL;
237 9 : pcmk__output_t *out = NULL;
238 9 : cib_t *cib = NULL;
239 9 : int rc = pcmk_rc_ok;
240 :
241 9 : rc = pcmk__setup_output_cib_sched(&out, &cib, &scheduler, xml);
242 9 : if (rc != pcmk_rc_ok) {
243 2 : goto done;
244 : }
245 :
246 7 : rc = pcmk__ticket_delete(out, cib, scheduler, ticket_id, force);
247 :
248 9 : done:
249 9 : if (cib != NULL) {
250 7 : cib__clean_up_connection(&cib);
251 : }
252 :
253 9 : pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
254 9 : pe_free_working_set(scheduler);
255 9 : return rc;
256 : }
257 :
258 : int
259 7 : pcmk__ticket_get_attr(pcmk__output_t *out, pcmk_scheduler_t *scheduler,
260 : const char *ticket_id, const char *attr_name,
261 : const char *attr_default)
262 : {
263 7 : int rc = pcmk_rc_ok;
264 7 : const char *attr_value = NULL;
265 7 : pcmk_ticket_t *ticket = NULL;
266 :
267 7 : CRM_ASSERT(out != NULL && scheduler != NULL);
268 :
269 7 : if (ticket_id == NULL || attr_name == NULL) {
270 2 : return EINVAL;
271 : }
272 :
273 5 : ticket = g_hash_table_lookup(scheduler->tickets, ticket_id);
274 :
275 5 : if (ticket != NULL) {
276 3 : attr_value = g_hash_table_lookup(ticket->state, attr_name);
277 : }
278 :
279 5 : if (attr_value != NULL) {
280 1 : out->message(out, "ticket-attribute", ticket_id, attr_name, attr_value);
281 4 : } else if (attr_default != NULL) {
282 2 : out->message(out, "ticket-attribute", ticket_id, attr_name, attr_default);
283 : } else {
284 2 : rc = ENXIO;
285 : }
286 :
287 5 : return rc;
288 : }
289 :
290 : int
291 8 : pcmk_ticket_get_attr(xmlNodePtr *xml, const char *ticket_id,
292 : const char *attr_name, const char *attr_default)
293 : {
294 8 : pcmk_scheduler_t *scheduler = NULL;
295 8 : pcmk__output_t *out = NULL;
296 8 : int rc = pcmk_rc_ok;
297 :
298 8 : rc = pcmk__setup_output_cib_sched(&out, NULL, &scheduler, xml);
299 8 : if (rc != pcmk_rc_ok) {
300 1 : goto done;
301 : }
302 :
303 7 : rc = pcmk__ticket_get_attr(out, scheduler, ticket_id, attr_name, attr_default);
304 :
305 8 : done:
306 8 : pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
307 8 : pe_free_working_set(scheduler);
308 8 : return rc;
309 : }
310 :
311 : int
312 3 : pcmk__ticket_info(pcmk__output_t *out, pcmk_scheduler_t *scheduler,
313 : const char *ticket_id, bool details, bool raw)
314 : {
315 3 : int rc = pcmk_rc_ok;
316 :
317 3 : CRM_ASSERT(out != NULL && scheduler != NULL);
318 :
319 3 : if (ticket_id != NULL) {
320 2 : GHashTable *tickets = NULL;
321 2 : pcmk_ticket_t *ticket = g_hash_table_lookup(scheduler->tickets, ticket_id);
322 :
323 2 : if (ticket == NULL) {
324 1 : return ENXIO;
325 : }
326 :
327 : /* The ticket-list message expects a GHashTable, so we'll construct
328 : * one with just this single item.
329 : */
330 1 : tickets = pcmk__strkey_table(free, NULL);
331 1 : g_hash_table_insert(tickets, strdup(ticket->id), ticket);
332 1 : out->message(out, "ticket-list", tickets, false, raw, details);
333 1 : g_hash_table_destroy(tickets);
334 :
335 : } else {
336 1 : out->message(out, "ticket-list", scheduler->tickets, false, raw, details);
337 : }
338 :
339 2 : return rc;
340 : }
341 :
342 : int
343 4 : pcmk_ticket_info(xmlNodePtr *xml, const char *ticket_id)
344 : {
345 4 : pcmk_scheduler_t *scheduler = NULL;
346 4 : pcmk__output_t *out = NULL;
347 4 : int rc = pcmk_rc_ok;
348 :
349 4 : rc = pcmk__setup_output_cib_sched(&out, NULL, &scheduler, xml);
350 4 : if (rc != pcmk_rc_ok) {
351 1 : goto done;
352 : }
353 :
354 3 : pe__register_messages(out);
355 :
356 : /* XML output (which is the only format supported by public API functions
357 : * due to the use of pcmk__xml_output_new above) always prints all details,
358 : * so just pass false for the last two arguments.
359 : */
360 3 : rc = pcmk__ticket_info(out, scheduler, ticket_id, false, false);
361 :
362 4 : done:
363 4 : pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
364 4 : pe_free_working_set(scheduler);
365 4 : return rc;
366 : }
367 :
368 : int
369 8 : pcmk__ticket_remove_attr(pcmk__output_t *out, cib_t *cib, pcmk_scheduler_t *scheduler,
370 : const char *ticket_id, GList *attr_delete, bool force)
371 : {
372 8 : xmlNode *ticket_state_xml = NULL;
373 8 : xmlNode *xml_top = NULL;
374 8 : int rc = pcmk_rc_ok;
375 :
376 8 : CRM_ASSERT(out != NULL && cib != NULL && scheduler != NULL);
377 :
378 8 : if (ticket_id == NULL) {
379 1 : return EINVAL;
380 : }
381 :
382 : /* Nothing to do */
383 7 : if (attr_delete == NULL) {
384 3 : return pcmk_rc_ok;
385 : }
386 :
387 4 : rc = build_ticket_modify_xml(cib, ticket_id, &ticket_state_xml, &xml_top);
388 :
389 4 : if (rc == pcmk_rc_duplicate_id) {
390 0 : out->info(out, "Multiple " PCMK__XE_TICKET_STATE "s match ticket=%s", ticket_id);
391 4 : } else if (rc != pcmk_rc_ok) {
392 0 : free_xml(ticket_state_xml);
393 0 : return rc;
394 : }
395 :
396 7 : for (GList *list_iter = attr_delete; list_iter != NULL; list_iter = list_iter->next) {
397 4 : const char *key = list_iter->data;
398 :
399 4 : if (!force && pcmk__str_eq(key, PCMK__XA_GRANTED, pcmk__str_none)) {
400 1 : free_xml(ticket_state_xml);
401 1 : return EACCES;
402 : }
403 :
404 3 : pcmk__xe_remove_attr(ticket_state_xml, key);
405 : }
406 :
407 3 : crm_log_xml_debug(xml_top, "Replace");
408 3 : rc = cib->cmds->replace(cib, PCMK_XE_STATUS, ticket_state_xml, cib_sync_call);
409 3 : rc = pcmk_legacy2rc(rc);
410 :
411 3 : free_xml(xml_top);
412 3 : return rc;
413 : }
414 :
415 : int
416 10 : pcmk_ticket_remove_attr(xmlNodePtr *xml, const char *ticket_id, GList *attr_delete, bool force)
417 : {
418 10 : pcmk_scheduler_t *scheduler = NULL;
419 10 : pcmk__output_t *out = NULL;
420 10 : int rc = pcmk_rc_ok;
421 10 : cib_t *cib = NULL;
422 :
423 10 : rc = pcmk__setup_output_cib_sched(&out, &cib, &scheduler, xml);
424 10 : if (rc != pcmk_rc_ok) {
425 2 : goto done;
426 : }
427 :
428 8 : rc = pcmk__ticket_remove_attr(out, cib, scheduler, ticket_id, attr_delete, force);
429 :
430 10 : done:
431 10 : if (cib != NULL) {
432 8 : cib__clean_up_connection(&cib);
433 : }
434 :
435 10 : pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
436 10 : pe_free_working_set(scheduler);
437 10 : return rc;
438 : }
439 :
440 : int
441 9 : pcmk__ticket_set_attr(pcmk__output_t *out, cib_t *cib, pcmk_scheduler_t *scheduler,
442 : const char *ticket_id, GHashTable *attr_set, bool force)
443 : {
444 9 : xmlNode *ticket_state_xml = NULL;
445 9 : xmlNode *xml_top = NULL;
446 9 : int rc = pcmk_rc_ok;
447 :
448 9 : CRM_ASSERT(out != NULL && cib != NULL && scheduler != NULL);
449 :
450 9 : if (ticket_id == NULL) {
451 1 : return EINVAL;
452 : }
453 :
454 : /* Nothing to do */
455 8 : if (attr_set == NULL || g_hash_table_size(attr_set) == 0) {
456 2 : return pcmk_rc_ok;
457 : }
458 :
459 6 : rc = build_ticket_modify_xml(cib, ticket_id, &ticket_state_xml, &xml_top);
460 :
461 6 : if (rc == pcmk_rc_duplicate_id) {
462 0 : out->info(out, "Multiple " PCMK__XE_TICKET_STATE "s match ticket=%s", ticket_id);
463 6 : } else if (rc != pcmk_rc_ok) {
464 0 : free_xml(ticket_state_xml);
465 0 : return rc;
466 : }
467 :
468 6 : if (!force && g_hash_table_lookup(attr_set, PCMK__XA_GRANTED)) {
469 2 : free_xml(ticket_state_xml);
470 2 : return EACCES;
471 : }
472 :
473 4 : add_attribute_xml(scheduler, ticket_id, attr_set, &ticket_state_xml);
474 :
475 4 : crm_log_xml_debug(xml_top, "Update");
476 4 : rc = cib->cmds->modify(cib, PCMK_XE_STATUS, xml_top, cib_sync_call);
477 4 : rc = pcmk_legacy2rc(rc);
478 :
479 4 : free_xml(xml_top);
480 4 : return rc;
481 : }
482 :
483 : int
484 11 : pcmk_ticket_set_attr(xmlNodePtr *xml, const char *ticket_id, GHashTable *attr_set,
485 : bool force)
486 : {
487 11 : pcmk_scheduler_t *scheduler = NULL;
488 11 : pcmk__output_t *out = NULL;
489 11 : int rc = pcmk_rc_ok;
490 11 : cib_t *cib = NULL;
491 :
492 11 : rc = pcmk__setup_output_cib_sched(&out, &cib, &scheduler, xml);
493 11 : if (rc != pcmk_rc_ok) {
494 2 : goto done;
495 : }
496 :
497 9 : rc = pcmk__ticket_set_attr(out, cib, scheduler, ticket_id, attr_set, force);
498 :
499 11 : done:
500 11 : if (cib != NULL) {
501 9 : cib__clean_up_connection(&cib);
502 : }
503 :
504 11 : pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
505 11 : pe_free_working_set(scheduler);
506 11 : return rc;
507 : }
508 :
509 : int
510 4 : pcmk__ticket_state(pcmk__output_t *out, cib_t *cib, const char *ticket_id)
511 : {
512 4 : xmlNode *state_xml = NULL;
513 4 : int rc = pcmk_rc_ok;
514 :
515 4 : CRM_ASSERT(out != NULL && cib != NULL);
516 :
517 4 : rc = pcmk__get_ticket_state(cib, ticket_id, &state_xml);
518 :
519 4 : if (rc == pcmk_rc_duplicate_id) {
520 1 : out->info(out, "Multiple " PCMK__XE_TICKET_STATE "s match ticket=%s",
521 : ticket_id);
522 : }
523 :
524 4 : if (state_xml != NULL) {
525 3 : out->message(out, "ticket-state", state_xml);
526 3 : free_xml(state_xml);
527 : }
528 :
529 4 : return rc;
530 : }
531 :
532 : int
533 6 : pcmk_ticket_state(xmlNodePtr *xml, const char *ticket_id)
534 : {
535 6 : pcmk__output_t *out = NULL;
536 6 : int rc = pcmk_rc_ok;
537 6 : cib_t *cib = NULL;
538 :
539 6 : rc = pcmk__setup_output_cib_sched(&out, &cib, NULL, xml);
540 6 : if (rc != pcmk_rc_ok) {
541 2 : goto done;
542 : }
543 :
544 4 : rc = pcmk__ticket_state(out, cib, ticket_id);
545 :
546 6 : done:
547 6 : if (cib != NULL) {
548 4 : cib__clean_up_connection(&cib);
549 : }
550 :
551 6 : pcmk__xml_output_finish(out, pcmk_rc2exitc(rc), xml);
552 6 : return rc;
553 : }
|