1 import sys
2 import xml.dom.minidom
3 from io import StringIO
4 from time import sleep
5 from unittest import TestCase, mock
6
7 from pcs import utils
8 from pcs.common import const
9
10 from pcs_test.tools.misc import get_test_resource as rc
11 from pcs_test.tools.xml import dom_get_child_elements
12
13 cib_with_nodes = rc("cib-empty-withnodes.xml")
14 empty_cib = rc("cib-empty.xml")
15
16 TestCase.maxDiff = None
17
18
19 class UtilsTest(TestCase):
20 @staticmethod
21 def get_cib_empty():
22 return xml.dom.minidom.parse(empty_cib)
23
24 @staticmethod
25 def get_cib_with_nodes_minidom():
26 return xml.dom.minidom.parse(cib_with_nodes)
27
28 def get_cib_resources(self):
29 cib_dom = self.get_cib_empty()
30 new_resources = xml.dom.minidom.parseString(
31 """
32 <resources>
33 <primitive id="myResource"
34 class="ocf" provider="heartbeat" type="Dummy">
35 </primitive>
36 <clone id="myClone">
37 <primitive id="myClonedResource"
38 class="ocf" provider="heartbeat" type="Dummy">
39 </primitive>
40 </clone>
41 <clone id="myUniqueClone">
42 <primitive id="myUniqueClonedResource"
43 class="ocf" provider="heartbeat" type="Dummy">
44 </primitive>
45 <meta-attributes>
46 <nvpair name="globally-unique" value="true" />
47 </meta-attributes>
48 </clone>
49 <master id="myMaster">
50 <primitive id="myMasteredResource"
51 class="ocf" provider="heartbeat" type="Dummy">
52 </primitive>
53 </master>
54 <group id="myGroup">
55 <primitive id="myGroupedResource"
56 class="ocf" provider="heartbeat" type="Dummy">
57 </primitive>
58 </group>
59 <clone id="myGroupClone">
60 <group id="myClonedGroup">
61 <primitive id="myClonedGroupedResource"
62 class="ocf" provider="heartbeat" type="Dummy">
63 </primitive>
64 </group>
65 </clone>
66 <master id="myGroupMaster">
67 <group id="myMasteredGroup">
68 <primitive id="myMasteredGroupedResource"
69 class="ocf" provider="heartbeat" type="Dummy">
70 </primitive>
71 </group>
72 </master>
73 <bundle id="myBundle">
74 <primitive id="myBundledResource"
75 class="ocf" provider="heartbeat" type="Dummy" />
76 </bundle>
77 <bundle id="myEmptyBundle"/>
78 </resources>
79 """
80 ).documentElement
81 resources = cib_dom.getElementsByTagName("resources")[0]
82 resources.parentNode.replaceChild(new_resources, resources)
83 return cib_dom
84
85 def test_dom_get_resources(self): # noqa: PLR0915
86 def test_dom_get(method, dom, ok_ids, bad_ids):
87 for element_id in ok_ids:
88 self.assert_element_id(method(dom, element_id), element_id)
89 for element_id in bad_ids:
90 self.assertFalse(method(dom, element_id))
91
92 cib_dom = self.get_cib_empty()
93 self.assertFalse(utils.dom_get_resource(cib_dom, "myResource"))
94 self.assertFalse(
95 utils.dom_get_resource_clone(cib_dom, "myClonedResource")
96 )
97 self.assertFalse(
98 utils.dom_get_resource_masterslave(cib_dom, "myMasteredResource")
99 )
100 self.assertFalse(utils.dom_get_group(cib_dom, "myGroup"))
101 self.assertFalse(utils.dom_get_group_clone(cib_dom, "myClonedGroup"))
102 self.assertFalse(
103 utils.dom_get_group_masterslave(cib_dom, "myMasteredGroup")
104 )
105 self.assertFalse(utils.dom_get_clone(cib_dom, "myClone"))
106 self.assertFalse(utils.dom_get_master(cib_dom, "myMaster"))
107 self.assertFalse(utils.dom_get_clone_ms_resource(cib_dom, "myClone"))
108 self.assertFalse(utils.dom_get_clone_ms_resource(cib_dom, "myMaster"))
109 self.assertFalse(
110 utils.dom_get_resource_clone_ms_parent(cib_dom, "myClonedResource")
111 )
112 self.assertFalse(
113 utils.dom_get_resource_clone_ms_parent(
114 cib_dom, "myMasteredResource"
115 )
116 )
117 self.assertIsNone(utils.dom_get_bundle(cib_dom, "myResource"))
118 self.assertIsNone(utils.dom_get_bundle(cib_dom, "notExisting"))
119 self.assertIsNone(
120 utils.dom_get_resource_bundle_parent(cib_dom, "myBundledResource")
121 )
122
123 cib_dom = self.get_cib_resources()
124 all_ids = {
125 "none",
126 "myResource",
127 "myClone",
128 "myClonedResource",
129 "myUniqueClone",
130 "myUniqueClonedResource",
131 "myMaster",
132 "myMasteredResource",
133 "myGroup",
134 "myGroupedResource",
135 "myGroupClone",
136 "myClonedGroup",
137 "myClonedGroupedResource",
138 "myGroupMaster",
139 "myMasteredGroup",
140 "myMasteredGroupedResource",
141 "myBundledResource",
142 "myBundle",
143 "myEmptyBundle",
144 }
145
146 resource_ids = {
147 "myResource",
148 "myClonedResource",
149 "myUniqueClonedResource",
150 "myGroupedResource",
151 "myMasteredResource",
152 "myClonedGroupedResource",
153 "myMasteredGroupedResource",
154 "myBundledResource",
155 }
156 test_dom_get(
157 utils.dom_get_resource,
158 cib_dom,
159 resource_ids,
160 all_ids - resource_ids,
161 )
162
163 cloned_ids = {
164 "myClonedResource",
165 "myUniqueClonedResource",
166 "myClonedGroupedResource",
167 }
168 test_dom_get(
169 utils.dom_get_resource_clone,
170 cib_dom,
171 cloned_ids,
172 all_ids - cloned_ids,
173 )
174
175 mastered_ids = {"myMasteredResource", "myMasteredGroupedResource"}
176 test_dom_get(
177 utils.dom_get_resource_masterslave,
178 cib_dom,
179 mastered_ids,
180 all_ids - mastered_ids,
181 )
182
183 group_ids = {"myGroup", "myClonedGroup", "myMasteredGroup"}
184 test_dom_get(
185 utils.dom_get_group, cib_dom, group_ids, all_ids - group_ids
186 )
187
188 cloned_group_ids = {"myClonedGroup"}
189 test_dom_get(
190 utils.dom_get_group_clone,
191 cib_dom,
192 cloned_group_ids,
193 all_ids - cloned_group_ids,
194 )
195
196 clone_ids = {"myClone", "myUniqueClone", "myGroupClone"}
197 test_dom_get(
198 utils.dom_get_clone, cib_dom, clone_ids, all_ids - clone_ids
199 )
200
201 mastered_group_ids = {"myMasteredGroup"}
202 test_dom_get(
203 utils.dom_get_group_masterslave,
204 cib_dom,
205 mastered_group_ids,
206 all_ids - mastered_group_ids,
207 )
208
209 master_ids = {"myMaster", "myGroupMaster"}
210 test_dom_get(
211 utils.dom_get_master, cib_dom, master_ids, all_ids - master_ids
212 )
213
214 bundle_ids = {"myBundle", "myEmptyBundle"}
215 test_dom_get(
216 utils.dom_get_bundle, cib_dom, bundle_ids, all_ids - bundle_ids
217 )
218
219 self.assert_element_id(
220 utils.dom_get_clone_ms_resource(cib_dom, "myClone"),
221 "myClonedResource",
222 )
223 self.assert_element_id(
224 utils.dom_get_clone_ms_resource(cib_dom, "myGroupClone"),
225 "myClonedGroup",
226 )
227 self.assert_element_id(
228 utils.dom_get_clone_ms_resource(cib_dom, "myMaster"),
229 "myMasteredResource",
230 )
231 self.assert_element_id(
232 utils.dom_get_clone_ms_resource(cib_dom, "myGroupMaster"),
233 "myMasteredGroup",
234 )
235
236 self.assert_element_id(
237 utils.dom_get_resource_clone_ms_parent(cib_dom, "myClonedResource"),
238 "myClone",
239 )
240 self.assert_element_id(
241 utils.dom_get_resource_clone_ms_parent(cib_dom, "myClonedGroup"),
242 "myGroupClone",
243 )
244 self.assert_element_id(
245 utils.dom_get_resource_clone_ms_parent(
246 cib_dom, "myClonedGroupedResource"
247 ),
248 "myGroupClone",
249 )
250 self.assert_element_id(
251 utils.dom_get_resource_clone_ms_parent(
252 cib_dom, "myMasteredResource"
253 ),
254 "myMaster",
255 )
256 self.assert_element_id(
257 utils.dom_get_resource_clone_ms_parent(cib_dom, "myMasteredGroup"),
258 "myGroupMaster",
259 )
260 self.assert_element_id(
261 utils.dom_get_resource_clone_ms_parent(
262 cib_dom, "myMasteredGroupedResource"
263 ),
264 "myGroupMaster",
265 )
266 self.assertEqual(
267 None, utils.dom_get_resource_clone_ms_parent(cib_dom, "myResource")
268 )
269 self.assertEqual(
270 None, utils.dom_get_resource_clone_ms_parent(cib_dom, "myGroup")
271 )
272 self.assertEqual(
273 None,
274 utils.dom_get_resource_clone_ms_parent(
275 cib_dom, "myGroupedResource"
276 ),
277 )
278
279 self.assertIsNone(
280 utils.dom_get_resource_bundle(
281 utils.dom_get_bundle(cib_dom, "myEmptyBundle")
282 )
283 )
284 self.assert_element_id(
285 utils.dom_get_resource_bundle(
286 utils.dom_get_bundle(cib_dom, "myBundle")
287 ),
288 "myBundledResource",
289 "primitive",
290 )
291
292 self.assert_element_id(
293 utils.dom_get_resource_bundle_parent(cib_dom, "myBundledResource"),
294 "myBundle",
295 )
296 self.assertIsNone(
297 utils.dom_get_resource_bundle_parent(cib_dom, "myResource")
298 )
299 self.assertIsNone(
300 utils.dom_get_resource_bundle_parent(cib_dom, "myClone")
301 )
302 self.assertIsNone(
303 utils.dom_get_resource_bundle_parent(cib_dom, "myClonedResource")
304 )
305 self.assertIsNone(
306 utils.dom_get_resource_bundle_parent(cib_dom, "myMaster")
307 )
308 self.assertIsNone(
309 utils.dom_get_resource_bundle_parent(cib_dom, "myMasteredGroup")
310 )
311 self.assertIsNone(
312 utils.dom_get_resource_bundle_parent(cib_dom, "myGroup")
313 )
314 self.assertIsNone(
315 utils.dom_get_resource_bundle_parent(cib_dom, "myGroupedResource")
316 )
317 self.assertIsNone(
318 utils.dom_get_resource_bundle_parent(cib_dom, "myGroupClone")
319 )
320 self.assertIsNone(
321 utils.dom_get_resource_bundle_parent(cib_dom, "myClonedGroup")
322 )
323 self.assertIsNone(
324 utils.dom_get_resource_bundle_parent(
325 cib_dom, "myClonedGroupedResource"
326 )
327 )
328
329 def test_dom_get_resource_remote_node_name(self):
330 dom = self.get_cib_empty()
331 new_resources = xml.dom.minidom.parseString(
332 """
333 <resources>
334 <primitive id="dummy1"
335 class="ocf" provider="heartbeat" type="Dummy">
336 </primitive>
337 <primitive class="ocf" id="vm-guest1" provider="heartbeat"
338 type="VirtualDomain">
339 <instance_attributes id="vm-guest1-instance_attributes">
340 <nvpair id="vm-guest1-instance_attributes-hypervisor"
341 name="hypervisor" value="qemu:///system"/>
342 <nvpair id="vm-guest1-instance_attributes-config"
343 name="config" value="/root/guest1.xml"/>
344 </instance_attributes>
345 <meta_attributes id="vm-guest1-meta_attributes">
346 <nvpair id="vm-guest1-meta_attributes-remote-node"
347 name="remote-node" value="guest1"/>
348 </meta_attributes>
349 </primitive>
350 <primitive id="dummy2"
351 class="ocf" provider="heartbeat" type="Dummy">
352 <instance_attributes id="vm-guest1-meta_attributes">
353 <nvpair id="dummy2-remote-node"
354 name="remote-node" value="guest2"/>
355 </instance_attributes>
356 </primitive>
357 <primitive id="dummy3"
358 class="ocf" provider="pacemaker" type="remote">
359 </primitive>
360 </resources>
361 """
362 ).documentElement
363 resources = dom.getElementsByTagName("resources")[0]
364 resources.parentNode.replaceChild(new_resources, resources)
365
366 self.assertEqual(
367 None,
368 utils.dom_get_resource_remote_node_name(
369 utils.dom_get_resource(dom, "dummy1")
370 ),
371 )
372 self.assertEqual(
373 None,
374 utils.dom_get_resource_remote_node_name(
375 utils.dom_get_resource(dom, "dummy2")
376 ),
377 )
378 self.assertEqual(
379 "guest1",
380 utils.dom_get_resource_remote_node_name(
381 utils.dom_get_resource(dom, "vm-guest1")
382 ),
383 )
384 self.assertEqual(
385 "dummy3",
386 utils.dom_get_resource_remote_node_name(
387 utils.dom_get_resource(dom, "dummy3")
388 ),
389 )
390
391 def test_dom_get_meta_attr_value(self):
392 dom = self.get_cib_empty()
393 new_resources = xml.dom.minidom.parseString(
394 """
395 <resources>
396 <primitive id="dummy1"
397 class="ocf" provider="heartbeat" type="Dummy">
398 </primitive>
399 <primitive class="ocf" id="vm-guest1" provider="heartbeat"
400 type="VirtualDomain">
401 <instance_attributes id="vm-guest1-instance_attributes">
402 <nvpair id="vm-guest1-instance_attributes-hypervisor"
403 name="hypervisor" value="qemu:///system"/>
404 <nvpair id="vm-guest1-instance_attributes-config"
405 name="config" value="/root/guest1.xml"/>
406 </instance_attributes>
407 <meta_attributes id="vm-guest1-meta_attributes">
408 <nvpair id="vm-guest1-meta_attributes-remote-node"
409 name="remote-node" value="guest1"/>
410 </meta_attributes>
411 </primitive>
412 <primitive id="dummy2"
413 class="ocf" provider="heartbeat" type="Dummy">
414 <instance_attributes id="vm-guest1-meta_attributes">
415 <nvpair id="dummy2-remote-node"
416 name="remote-node" value="guest2"/>
417 </instance_attributes>
418 </primitive>
419 </resources>
420 """
421 ).documentElement
422 resources = dom.getElementsByTagName("resources")[0]
423 resources.parentNode.replaceChild(new_resources, resources)
424
425 self.assertEqual(
426 None,
427 utils.dom_get_meta_attr_value(
428 utils.dom_get_resource(dom, "dummy1"), "foo"
429 ),
430 )
431 self.assertEqual(
432 None,
433 utils.dom_get_meta_attr_value(
434 utils.dom_get_resource(dom, "dummy2"), "remote-node"
435 ),
436 )
437 self.assertEqual(
438 "guest1",
439 utils.dom_get_meta_attr_value(
440 utils.dom_get_resource(dom, "vm-guest1"), "remote-node"
441 ),
442 )
443 self.assertEqual(
444 None,
445 utils.dom_get_meta_attr_value(
446 utils.dom_get_resource(dom, "vm-guest1"), "foo"
447 ),
448 )
449
450 def test_dom_get_parent_by_tag_name(self):
451 def dom_get_element_with_id(dom, tag_name, element_id):
452 for elem in dom.getElementsByTagName(tag_name):
453 if (
454 elem.hasAttribute("id")
455 and elem.getAttribute("id") == element_id
456 ):
457 return elem
458 return None
459
460 dom = xml.dom.minidom.parseString(
461 """
462 <aa id="aa1">
463 <bb id="bb1"/>
464 <bb id="bb2">
465 <cc id="cc1"/>
466 </bb>
467 <bb id="bb3">
468 <cc id="cc2"/>
469 </bb>
470 <dd id="dd1" />
471 </aa>
472 """
473 ).documentElement
474 bb1 = dom_get_element_with_id(dom, "bb", "bb1")
475 cc1 = dom_get_element_with_id(dom, "cc", "cc1")
476
477 self.assert_element_id(
478 utils.dom_get_parent_by_tag_names(bb1, ["aa"]), "aa1"
479 )
480 self.assert_element_id(
481 utils.dom_get_parent_by_tag_names(cc1, ["aa"]), "aa1"
482 )
483 self.assert_element_id(
484 utils.dom_get_parent_by_tag_names(cc1, ["bb"]), "bb2"
485 )
486
487 self.assertEqual(None, utils.dom_get_parent_by_tag_names(bb1, ["cc"]))
488 self.assertEqual(None, utils.dom_get_parent_by_tag_names(cc1, ["dd"]))
489 self.assertEqual(None, utils.dom_get_parent_by_tag_names(cc1, ["ee"]))
490
491 def test_validate_constraint_resource(self):
492 dom = self.get_cib_resources()
493 self.assertEqual(
494 (True, "", "myClone"),
495 utils.validate_constraint_resource(dom, "myClone"),
496 )
497 self.assertEqual(
498 (True, "", "myGroupClone"),
499 utils.validate_constraint_resource(dom, "myGroupClone"),
500 )
501 self.assertEqual(
502 (True, "", "myMaster"),
503 utils.validate_constraint_resource(dom, "myMaster"),
504 )
505 self.assertEqual(
506 (True, "", "myGroupMaster"),
507 utils.validate_constraint_resource(dom, "myGroupMaster"),
508 )
509 self.assertEqual(
510 (True, "", "myBundle"),
511 utils.validate_constraint_resource(dom, "myBundle"),
512 )
513 self.assertEqual(
514 (True, "", "myEmptyBundle"),
515 utils.validate_constraint_resource(dom, "myEmptyBundle"),
516 )
517 self.assertEqual(
518 (True, "", "myResource"),
519 utils.validate_constraint_resource(dom, "myResource"),
520 )
521 self.assertEqual(
522 (True, "", "myGroup"),
523 utils.validate_constraint_resource(dom, "myGroup"),
524 )
525 self.assertEqual(
526 (True, "", "myGroupedResource"),
527 utils.validate_constraint_resource(dom, "myGroupedResource"),
528 )
529
530 self.assertEqual(
531 (False, "Resource 'myNonexistent' does not exist", None),
532 utils.validate_constraint_resource(dom, "myNonexistent"),
533 )
534
535 message = (
536 "%s is a clone resource, you should use the clone id: "
537 "%s when adding constraints. Use --force to override."
538 )
539 self.assertEqual(
540 (False, message % ("myClonedResource", "myClone"), "myClone"),
541 utils.validate_constraint_resource(dom, "myClonedResource"),
542 )
543 self.assertEqual(
544 (
545 False,
546 message % ("myClonedGroup", "myGroupClone"),
547 "myGroupClone",
548 ),
549 utils.validate_constraint_resource(dom, "myClonedGroup"),
550 )
551 self.assertEqual(
552 (
553 False,
554 message % ("myClonedGroupedResource", "myGroupClone"),
555 "myGroupClone",
556 ),
557 utils.validate_constraint_resource(dom, "myClonedGroupedResource"),
558 )
559
560 message = (
561 "%s is a clone resource, you should use the clone id: "
562 "%s when adding constraints. Use --force to override."
563 )
564 self.assertEqual(
565 (False, message % ("myMasteredResource", "myMaster"), "myMaster"),
566 utils.validate_constraint_resource(dom, "myMasteredResource"),
567 )
568 self.assertEqual(
569 (
570 False,
571 message % ("myMasteredGroup", "myGroupMaster"),
572 "myGroupMaster",
573 ),
574 utils.validate_constraint_resource(dom, "myMasteredGroup"),
575 )
576 self.assertEqual(
577 (
578 False,
579 message % ("myMasteredGroupedResource", "myGroupMaster"),
580 "myGroupMaster",
581 ),
582 utils.validate_constraint_resource(
583 dom, "myMasteredGroupedResource"
584 ),
585 )
586
587 message = (
588 "%s is a bundle resource, you should use the bundle id: "
589 "%s when adding constraints. Use --force to override."
590 )
591 self.assertEqual(
592 (False, message % ("myBundledResource", "myBundle"), "myBundle"),
593 utils.validate_constraint_resource(dom, "myBundledResource"),
594 )
595
596 utils.pcs_options["--force"] = True
597 self.assertEqual(
598 (True, "", "myClone"),
599 utils.validate_constraint_resource(dom, "myClonedResource"),
600 )
601 self.assertEqual(
602 (True, "", "myGroupClone"),
603 utils.validate_constraint_resource(dom, "myClonedGroup"),
604 )
605 self.assertEqual(
606 (True, "", "myGroupClone"),
607 utils.validate_constraint_resource(dom, "myClonedGroupedResource"),
608 )
609 self.assertEqual(
610 (True, "", "myMaster"),
611 utils.validate_constraint_resource(dom, "myMasteredResource"),
612 )
613 self.assertEqual(
614 (True, "", "myGroupMaster"),
615 utils.validate_constraint_resource(dom, "myMasteredGroup"),
616 )
617 self.assertEqual(
618 (True, "", "myGroupMaster"),
619 utils.validate_constraint_resource(
620 dom, "myMasteredGroupedResource"
621 ),
622 )
623 self.assertEqual(
624 (True, "", "myBundle"),
625 utils.validate_constraint_resource(dom, "myBundledResource"),
626 )
627
628 def test_validate_xml_id(self):
629 self.assertEqual((True, ""), utils.validate_xml_id("dummy"))
630 self.assertEqual((True, ""), utils.validate_xml_id("DUMMY"))
631 self.assertEqual((True, ""), utils.validate_xml_id("dUmMy"))
632 self.assertEqual((True, ""), utils.validate_xml_id("dummy0"))
633 self.assertEqual((True, ""), utils.validate_xml_id("dum0my"))
634 self.assertEqual((True, ""), utils.validate_xml_id("dummy-"))
635 self.assertEqual((True, ""), utils.validate_xml_id("dum-my"))
636 self.assertEqual((True, ""), utils.validate_xml_id("dummy."))
637 self.assertEqual((True, ""), utils.validate_xml_id("dum.my"))
638 self.assertEqual((True, ""), utils.validate_xml_id("_dummy"))
639 self.assertEqual((True, ""), utils.validate_xml_id("dummy_"))
640 self.assertEqual((True, ""), utils.validate_xml_id("dum_my"))
641
642 self.assertEqual(
643 (False, "test id cannot be empty"),
644 utils.validate_xml_id("", "test id"),
645 )
646
647 msg = "invalid test id '%s', '%s' is not a valid first character for a test id"
648 self.assertEqual(
649 (False, msg % ("0", "0")), utils.validate_xml_id("0", "test id")
650 )
651 self.assertEqual(
652 (False, msg % ("-", "-")), utils.validate_xml_id("-", "test id")
653 )
654 self.assertEqual(
655 (False, msg % (".", ".")), utils.validate_xml_id(".", "test id")
656 )
657 self.assertEqual(
658 (False, msg % (":", ":")), utils.validate_xml_id(":", "test id")
659 )
660 self.assertEqual(
661 (False, msg % ("0dummy", "0")),
662 utils.validate_xml_id("0dummy", "test id"),
663 )
664 self.assertEqual(
665 (False, msg % ("-dummy", "-")),
666 utils.validate_xml_id("-dummy", "test id"),
667 )
668 self.assertEqual(
669 (False, msg % (".dummy", ".")),
670 utils.validate_xml_id(".dummy", "test id"),
671 )
672 self.assertEqual(
673 (False, msg % (":dummy", ":")),
674 utils.validate_xml_id(":dummy", "test id"),
675 )
676
677 msg = (
678 "invalid test id '%s', '%s' is not a valid character for a test id"
679 )
680 self.assertEqual(
681 (False, msg % ("dum:my", ":")),
682 utils.validate_xml_id("dum:my", "test id"),
683 )
684 self.assertEqual(
685 (False, msg % ("dummy:", ":")),
686 utils.validate_xml_id("dummy:", "test id"),
687 )
688 self.assertEqual(
689 (False, msg % ("dum?my", "?")),
690 utils.validate_xml_id("dum?my", "test id"),
691 )
692 self.assertEqual(
693 (False, msg % ("dummy?", "?")),
694 utils.validate_xml_id("dummy?", "test id"),
695 )
696
697 def test_is_score(self):
698 self.assertTrue(utils.is_score("INFINITY"))
699 self.assertTrue(utils.is_score("+INFINITY"))
700 self.assertTrue(utils.is_score("-INFINITY"))
701 self.assertTrue(utils.is_score("0"))
702 self.assertTrue(utils.is_score("+0"))
703 self.assertTrue(utils.is_score("-0"))
704 self.assertTrue(utils.is_score("123"))
705 self.assertTrue(utils.is_score("-123"))
706 self.assertTrue(utils.is_score("+123"))
707
708 self.assertFalse(utils.is_score(""))
709 self.assertFalse(utils.is_score("abc"))
710 self.assertFalse(utils.is_score("+abc"))
711 self.assertFalse(utils.is_score("-abc"))
712 self.assertFalse(utils.is_score("10a"))
713 self.assertFalse(utils.is_score("+10a"))
714 self.assertFalse(utils.is_score("-10a"))
715 self.assertFalse(utils.is_score("a10"))
716 self.assertFalse(utils.is_score("+a10"))
717 self.assertFalse(utils.is_score("a-10"))
718 self.assertFalse(utils.is_score("infinity"))
719 self.assertFalse(utils.is_score("+infinity"))
720 self.assertFalse(utils.is_score("-infinity"))
721 self.assertFalse(utils.is_score("+InFiNiTy"))
722 self.assertFalse(utils.is_score("INFINITY10"))
723 self.assertFalse(utils.is_score("INFINITY+10"))
724 self.assertFalse(utils.is_score("-INFINITY10"))
725 self.assertFalse(utils.is_score("+INFINITY+10"))
726 self.assertFalse(utils.is_score("10INFINITY"))
727 self.assertFalse(utils.is_score("+10+INFINITY"))
728
729 def get_cib_status_lrm(self):
730 cib_dom = self.get_cib_empty()
731 new_status = xml.dom.minidom.parseString(
732 """
733 <status>
734 <node_state id="1" uname="rh70-node1">
735 <lrm id="1">
736 <lrm_resources>
737 <lrm_resource id="dummy" type="Dummy" class="ocf" provider="heartbeat">
738 <lrm_rsc_op id="dummy_monitor_30000" operation="monitor" call-id="34"
739 rc-code="1" on_node="Xrh70-node1X" exit-reason="test" />
740 <lrm_rsc_op id="dummy_stop_0" operation="stop" call-id="32"
741 rc-code="0" />
742 <lrm_rsc_op id="dummy_start_0" operation="start" call-id="33"
743 rc-code="0" />
744 </lrm_resource>
745 </lrm_resources>
746 </lrm>
747 </node_state>
748 <node_state id="2" uname="rh70-node2">
749 <lrm id="2">
750 <lrm_resources>
751 <lrm_resource id="dummy" type="Dummy" class="ocf" provider="heartbeat">
752 <lrm_rsc_op id="dummy_monitor_0" operation="monitor" call-id="5"
753 rc-code="1" />
754 </lrm_resource>
755 <lrm_resource id="dummy1" type="Dummy" class="ocf" provider="heartbeat">
756 <lrm_rsc_op id="dummy1_monitor_0" operation="monitor" call-id="3"
757 rc-code="0" />
758 </lrm_resource>
759 </lrm_resources>
760 </lrm>
761 </node_state>
762 </status>
763 """
764 ).documentElement
765 status = cib_dom.getElementsByTagName("status")[0]
766 status.parentNode.replaceChild(new_status, status)
767 return cib_dom
768
769 def test_resource_running_on(self):
770 status = xml.dom.minidom.parseString(
771 f"""
772 <crm_mon>
773 <summary />
774 <nodes />
775 <resources>
776 <resource id="myResource" role="Started">
777 <node name="rh70-node1" />
778 </resource>
779 <clone id="myClone">
780 <resource id="myClonedResource" role="Started">
781 <node name="rh70-node1" />
782 </resource>
783 <resource id="myClonedResource" role="Started">
784 <node name="rh70-node2" />
785 </resource>
786 <resource id="myClonedResource" role="Started">
787 <node name="rh70-node3" />
788 </resource>
789 </clone>
790 <clone id="myMaster">
791 <resource id="myMasteredResource:1" role="{const.PCMK_ROLE_UNPROMOTED}">
792 <node name="rh70-node2" />
793 </resource>
794 <resource id="myMasteredResource" role="{const.PCMK_ROLE_UNPROMOTED}">
795 <node name="rh70-node3" />
796 </resource>
797 <resource id="myMasteredResource" role="{const.PCMK_ROLE_PROMOTED}">
798 <node name="rh70-node1" />
799 </resource>
800 </clone>
801 <group id="myGroup">
802 <resource id="myGroupedResource" role="Started">
803 <node name="rh70-node2" />
804 </resource>
805 </group>
806 <clone id="myGroupClone">
807 <group id="myClonedGroup:0">
808 <resource id="myClonedGroupedResource" role="Started">
809 <node name="rh70-node1" />
810 </resource>
811 </group>
812 <group id="myClonedGroup:1">
813 <resource id="myClonedGroupedResource" role="Started">
814 <node name="rh70-node2" />
815 </resource>
816 </group>
817 <group id="myClonedGroup:2">
818 <resource id="myClonedGroupedResource" role="Started">
819 <node name="rh70-node3" />
820 </resource>
821 </group>
822 <group id="myClonedGroup:3">
823 <resource id="myClonedGroupedResource" role="Started">
824 <node name="rh70-node3" />
825 </resource>
826 </group>
827 </clone>
828 <clone id="myGroupMaster">
829 <group id="myMasteredGroup:0">
830 <resource id="myMasteredGroupedResource" role="{const.PCMK_ROLE_UNPROMOTED}">
831 <node name="rh70-node1" />
832 </resource>
833 </group>
834 <group id="myMasteredGroup:1">
835 <resource id="myMasteredGroupedResource" role="{const.PCMK_ROLE_PROMOTED}">
836 <node name="rh70-node2" />
837 </resource>
838 </group>
839 <group id="myMasteredGroup:2">
840 <resource id="myMasteredGroupedResource" role="{const.PCMK_ROLE_UNPROMOTED}">
841 <node name="rh70-node3" />
842 </resource>
843 </group>
844 </clone>
845 <resource id="myStoppedResource" role="Stopped">
846 </resource>
847 </resources>
848 </crm_mon>
849 """
850 ).documentElement
851
852 self.assertEqual(
853 utils.resource_running_on("myResource", status),
854 {
855 "message": "Resource 'myResource' is running on node rh70-node1.",
856 "is_running": True,
857 },
858 )
859 self.assertEqual(
860 utils.resource_running_on("myClonedResource", status),
861 {
862 "message": "Resource 'myClonedResource' is running on nodes "
863 "rh70-node1, rh70-node2, rh70-node3.",
864 "is_running": True,
865 },
866 )
867 self.assertEqual(
868 utils.resource_running_on("myClone", status),
869 {
870 "message": "Resource 'myClone' is running on nodes "
871 "rh70-node1, rh70-node2, rh70-node3.",
872 "is_running": True,
873 },
874 )
875 self.assertEqual(
876 utils.resource_running_on("myMasteredResource", status),
877 {
878 "message": (
879 "Resource 'myMasteredResource' is {promoted} on node "
880 "rh70-node1; {unpromoted} on nodes rh70-node2, rh70-node3."
881 ).format(
882 promoted=str(const.PCMK_ROLE_PROMOTED).lower(),
883 unpromoted=str(const.PCMK_ROLE_UNPROMOTED).lower(),
884 ),
885 "is_running": True,
886 },
887 )
888 self.assertEqual(
889 utils.resource_running_on("myMaster", status),
890 {
891 "message": (
892 "Resource 'myMaster' is {promoted} on node "
893 "rh70-node1; {unpromoted} on nodes rh70-node2, rh70-node3."
894 ).format(
895 promoted=str(const.PCMK_ROLE_PROMOTED).lower(),
896 unpromoted=str(const.PCMK_ROLE_UNPROMOTED).lower(),
897 ),
898 "is_running": True,
899 },
900 )
901 self.assertEqual(
902 utils.resource_running_on("myGroupedResource", status),
903 {
904 "message": "Resource 'myGroupedResource' is running on node "
905 "rh70-node2.",
906 "is_running": True,
907 },
908 )
909 self.assertEqual(
910 utils.resource_running_on("myGroup", status),
911 {
912 "message": "Resource 'myGroup' is running on node rh70-node2.",
913 "is_running": True,
914 },
915 )
916 self.assertEqual(
917 utils.resource_running_on("myClonedGroupedResource", status),
918 {
919 "message": "Resource 'myClonedGroupedResource' is running on nodes "
920 "rh70-node1, rh70-node2, rh70-node3, rh70-node3.",
921 "is_running": True,
922 },
923 )
924 self.assertEqual(
925 utils.resource_running_on("myClonedGroup", status),
926 {
927 "message": "Resource 'myClonedGroup' is running on nodes "
928 "rh70-node1, rh70-node2, rh70-node3, rh70-node3.",
929 "is_running": True,
930 },
931 )
932 self.assertEqual(
933 utils.resource_running_on("myGroupClone", status),
934 {
935 "message": "Resource 'myGroupClone' is running on nodes "
936 "rh70-node1, rh70-node2, rh70-node3, rh70-node3.",
937 "is_running": True,
938 },
939 )
940 self.assertEqual(
941 utils.resource_running_on("myMasteredGroupedResource", status),
942 {
943 "message": (
944 "Resource 'myMasteredGroupedResource' is {promoted} on node "
945 "rh70-node2; {unpromoted} on nodes rh70-node1, rh70-node3."
946 ).format(
947 promoted=str(const.PCMK_ROLE_PROMOTED).lower(),
948 unpromoted=str(const.PCMK_ROLE_UNPROMOTED).lower(),
949 ),
950 "is_running": True,
951 },
952 )
953 self.assertEqual(
954 utils.resource_running_on("myMasteredGroup", status),
955 {
956 "message": (
957 "Resource 'myMasteredGroup' is {promoted} on node "
958 "rh70-node2; {unpromoted} on nodes rh70-node1, rh70-node3."
959 ).format(
960 promoted=str(const.PCMK_ROLE_PROMOTED).lower(),
961 unpromoted=str(const.PCMK_ROLE_UNPROMOTED).lower(),
962 ),
963 "is_running": True,
964 },
965 )
966 self.assertEqual(
967 utils.resource_running_on("myGroupMaster", status),
968 {
969 "message": (
970 "Resource 'myGroupMaster' is {promoted} on node "
971 "rh70-node2; {unpromoted} on nodes rh70-node1, rh70-node3."
972 ).format(
973 promoted=str(const.PCMK_ROLE_PROMOTED).lower(),
974 unpromoted=str(const.PCMK_ROLE_UNPROMOTED).lower(),
975 ),
976 "is_running": True,
977 },
978 )
979 self.assertEqual(
980 utils.resource_running_on("notMyResource", status),
981 {
982 "message": "Resource 'notMyResource' is not running on any node",
983 "is_running": False,
984 },
985 )
986 self.assertEqual(
987 utils.resource_running_on("myStoppedResource", status),
988 {
989 "message": "Resource 'myStoppedResource' is not running on any node",
990 "is_running": False,
991 },
992 )
993
994 def test_get_operations_from_transitions(self):
995 transitions = xml.dom.minidom.parse(rc("transitions01.xml"))
996 self.assertEqual(
997 [
998 {
999 "id": "dummy",
1000 "long_id": "dummy",
1001 "operation": "stop",
1002 "on_node": "rh7-3",
1003 },
1004 {
1005 "id": "dummy",
1006 "long_id": "dummy",
1007 "operation": "start",
1008 "on_node": "rh7-2",
1009 },
1010 {
1011 "id": "d0",
1012 "long_id": "d0:1",
1013 "operation": "stop",
1014 "on_node": "rh7-1",
1015 },
1016 {
1017 "id": "d0",
1018 "long_id": "d0:1",
1019 "operation": "start",
1020 "on_node": "rh7-2",
1021 },
1022 {
1023 "id": "state",
1024 "long_id": "state:0",
1025 "operation": "stop",
1026 "on_node": "rh7-3",
1027 },
1028 {
1029 "id": "state",
1030 "long_id": "state:0",
1031 "operation": "start",
1032 "on_node": "rh7-2",
1033 },
1034 ],
1035 utils.get_operations_from_transitions(transitions),
1036 )
1037
1038 transitions = xml.dom.minidom.parse(rc("transitions02.xml"))
1039 self.assertEqual(
1040 [
1041 {
1042 "id": "RemoteNode",
1043 "long_id": "RemoteNode",
1044 "operation": "stop",
1045 "on_node": "virt-143",
1046 },
1047 {
1048 "id": "RemoteNode",
1049 "long_id": "RemoteNode",
1050 "operation": "migrate_to",
1051 "on_node": "virt-143",
1052 },
1053 {
1054 "id": "RemoteNode",
1055 "long_id": "RemoteNode",
1056 "operation": "migrate_from",
1057 "on_node": "virt-142",
1058 },
1059 {
1060 "id": "dummy8",
1061 "long_id": "dummy8",
1062 "operation": "stop",
1063 "on_node": "virt-143",
1064 },
1065 {
1066 "id": "dummy8",
1067 "long_id": "dummy8",
1068 "operation": "start",
1069 "on_node": "virt-142",
1070 },
1071 ],
1072 utils.get_operations_from_transitions(transitions),
1073 )
1074
1075 def test_get_resources_location_from_operations(self):
1076 cib_dom = self.get_cib_resources()
1077
1078 operations = []
1079 self.assertEqual(
1080 {},
1081 utils.get_resources_location_from_operations(cib_dom, operations),
1082 )
1083
1084 operations = [
1085 {
1086 "id": "myResource",
1087 "long_id": "myResource",
1088 "operation": "start",
1089 "on_node": "rh7-1",
1090 },
1091 ]
1092 self.assertEqual(
1093 {
1094 "myResource": {
1095 "id": "myResource",
1096 "id_for_constraint": "myResource",
1097 "long_id": "myResource",
1098 "start_on_node": "rh7-1",
1099 },
1100 },
1101 utils.get_resources_location_from_operations(cib_dom, operations),
1102 )
1103
1104 operations = [
1105 {
1106 "id": "myResource",
1107 "long_id": "myResource",
1108 "operation": "start",
1109 "on_node": "rh7-1",
1110 },
1111 {
1112 "id": "myResource",
1113 "long_id": "myResource",
1114 "operation": "start",
1115 "on_node": "rh7-2",
1116 },
1117 {
1118 "id": "myResource",
1119 "long_id": "myResource",
1120 "operation": "monitor",
1121 "on_node": "rh7-3",
1122 },
1123 {
1124 "id": "myResource",
1125 "long_id": "myResource",
1126 "operation": "stop",
1127 "on_node": "rh7-3",
1128 },
1129 ]
1130 self.assertEqual(
1131 {
1132 "myResource": {
1133 "id": "myResource",
1134 "id_for_constraint": "myResource",
1135 "long_id": "myResource",
1136 "start_on_node": "rh7-2",
1137 },
1138 },
1139 utils.get_resources_location_from_operations(cib_dom, operations),
1140 )
1141
1142 operations = [
1143 {
1144 "id": "myResource",
1145 "long_id": "myResource",
1146 "operation": "start",
1147 "on_node": "rh7-1",
1148 },
1149 {
1150 "id": "myClonedResource",
1151 "long_id": "myClonedResource:0",
1152 "operation": "start",
1153 "on_node": "rh7-1",
1154 },
1155 {
1156 "id": "myClonedResource",
1157 "long_id": "myClonedResource:0",
1158 "operation": "start",
1159 "on_node": "rh7-2",
1160 },
1161 {
1162 "id": "myClonedResource",
1163 "long_id": "myClonedResource:1",
1164 "operation": "start",
1165 "on_node": "rh7-3",
1166 },
1167 ]
1168 self.assertEqual(
1169 {
1170 "myResource": {
1171 "id": "myResource",
1172 "id_for_constraint": "myResource",
1173 "long_id": "myResource",
1174 "start_on_node": "rh7-1",
1175 },
1176 "myClonedResource:0": {
1177 "id": "myClonedResource",
1178 "id_for_constraint": "myClone",
1179 "long_id": "myClonedResource:0",
1180 "start_on_node": "rh7-2",
1181 },
1182 "myClonedResource:1": {
1183 "id": "myClonedResource",
1184 "id_for_constraint": "myClone",
1185 "long_id": "myClonedResource:1",
1186 "start_on_node": "rh7-3",
1187 },
1188 },
1189 utils.get_resources_location_from_operations(cib_dom, operations),
1190 )
1191
1192 operations = [
1193 {
1194 "id": "myUniqueClonedResource:0",
1195 "long_id": "myUniqueClonedResource:0",
1196 "operation": "start",
1197 "on_node": "rh7-1",
1198 },
1199 {
1200 "id": "myUniqueClonedResource:1",
1201 "long_id": "myUniqueClonedResource:1",
1202 "operation": "monitor",
1203 "on_node": "rh7-2",
1204 },
1205 {
1206 "id": "myUniqueClonedResource:2",
1207 "long_id": "myUniqueClonedResource:2",
1208 "operation": "start",
1209 "on_node": "rh7-3",
1210 },
1211 ]
1212 self.assertEqual(
1213 {
1214 "myUniqueClonedResource:0": {
1215 "id": "myUniqueClonedResource:0",
1216 "id_for_constraint": "myUniqueClone",
1217 "long_id": "myUniqueClonedResource:0",
1218 "start_on_node": "rh7-1",
1219 },
1220 "myUniqueClonedResource:2": {
1221 "id": "myUniqueClonedResource:2",
1222 "id_for_constraint": "myUniqueClone",
1223 "long_id": "myUniqueClonedResource:2",
1224 "start_on_node": "rh7-3",
1225 },
1226 },
1227 utils.get_resources_location_from_operations(cib_dom, operations),
1228 )
1229
1230 operations = [
1231 {
1232 "id": "myMasteredGroupedResource",
1233 "long_id": "myMasteredGroupedResource:0",
1234 "operation": "start",
1235 "on_node": "rh7-1",
1236 },
1237 {
1238 "id": "myMasteredGroupedResource",
1239 "long_id": "myMasteredGroupedResource:1",
1240 "operation": "demote",
1241 "on_node": "rh7-2",
1242 },
1243 {
1244 "id": "myMasteredGroupedResource",
1245 "long_id": "myMasteredGroupedResource:1",
1246 "operation": "promote",
1247 "on_node": "rh7-3",
1248 },
1249 ]
1250 self.assertEqual(
1251 {
1252 "myMasteredGroupedResource:0": {
1253 "id": "myMasteredGroupedResource",
1254 "id_for_constraint": "myGroupMaster",
1255 "long_id": "myMasteredGroupedResource:0",
1256 "start_on_node": "rh7-1",
1257 },
1258 "myMasteredGroupedResource:1": {
1259 "id": "myMasteredGroupedResource",
1260 "id_for_constraint": "myGroupMaster",
1261 "long_id": "myMasteredGroupedResource:1",
1262 "promote_on_node": "rh7-3",
1263 },
1264 },
1265 utils.get_resources_location_from_operations(cib_dom, operations),
1266 )
1267
1268 operations = [
1269 {
1270 "id": "myResource",
1271 "long_id": "myResource",
1272 "operation": "stop",
1273 "on_node": "rh7-1",
1274 },
1275 {
1276 "id": "myResource",
1277 "long_id": "myResource",
1278 "operation": "migrate_to",
1279 "on_node": "rh7-1",
1280 },
1281 {
1282 "id": "myResource",
1283 "long_id": "myResource",
1284 "operation": "migrate_from",
1285 "on_node": "rh7-2",
1286 },
1287 ]
1288 self.assertEqual(
1289 {
1290 "myResource": {
1291 "id": "myResource",
1292 "id_for_constraint": "myResource",
1293 "long_id": "myResource",
1294 "start_on_node": "rh7-2",
1295 },
1296 },
1297 utils.get_resources_location_from_operations(cib_dom, operations),
1298 )
1299
1300 def test_is_int(self):
1301 self.assertTrue(utils.is_int("-999"))
1302 self.assertTrue(utils.is_int("-1"))
1303 self.assertTrue(utils.is_int("0"))
1304 self.assertTrue(utils.is_int("1"))
1305 self.assertTrue(utils.is_int("99999"))
1306 self.assertTrue(utils.is_int(" 99999 "))
1307 self.assertFalse(utils.is_int("0.0"))
1308 self.assertFalse(utils.is_int("-1.0"))
1309 self.assertFalse(utils.is_int("-0.1"))
1310 self.assertFalse(utils.is_int("0.001"))
1311 self.assertFalse(utils.is_int("-999999.1"))
1312 self.assertFalse(utils.is_int("0.0001"))
1313 self.assertFalse(utils.is_int(""))
1314 self.assertFalse(utils.is_int(" "))
1315 self.assertFalse(utils.is_int("A"))
1316 self.assertFalse(utils.is_int("random 15 47 text "))
1317
1318 def test_dom_get_node(self):
1319 cib = self.get_cib_with_nodes_minidom()
1320 self.assertIsNone(utils.dom_get_node(cib, "non-existing-node"))
1321 node = utils.dom_get_node(cib, "rh7-1")
1322 self.assertEqual(node.getAttribute("uname"), "rh7-1")
1323 self.assertEqual(node.getAttribute("id"), "1")
1324
1325 def test_dom_prepare_child_element(self):
1326 cib = self.get_cib_with_nodes_minidom()
1327 node = cib.getElementsByTagName("node")[0]
1328 self.assertEqual(len(dom_get_child_elements(node)), 0)
1329 child = utils.dom_prepare_child_element(
1330 node, "utilization", "rh7-1-utilization"
1331 )
1332 self.assertEqual(len(dom_get_child_elements(node)), 1)
1333 self.assertEqual(child, dom_get_child_elements(node)[0])
1334 self.assertEqual(dom_get_child_elements(node)[0].tagName, "utilization")
1335 self.assertEqual(
1336 dom_get_child_elements(node)[0].getAttribute("id"),
1337 "rh7-1-utilization",
1338 )
1339 child2 = utils.dom_prepare_child_element(
1340 node, "utilization", "rh7-1-utilization"
1341 )
1342 self.assertEqual(len(dom_get_child_elements(node)), 1)
1343 self.assertEqual(child, child2)
1344
1345 def test_dom_update_nv_pair_add(self):
1346 nv_set = xml.dom.minidom.parseString("<nvset/>").documentElement
1347 utils.dom_update_nv_pair(nv_set, "test_name", "test_val", "prefix-")
1348 self.assertEqual(len(dom_get_child_elements(nv_set)), 1)
1349 pair = dom_get_child_elements(nv_set)[0]
1350 self.assertEqual(pair.getAttribute("name"), "test_name")
1351 self.assertEqual(pair.getAttribute("value"), "test_val")
1352 self.assertEqual(pair.getAttribute("id"), "prefix-test_name")
1353 utils.dom_update_nv_pair(nv_set, "another_name", "value", "prefix2-")
1354 self.assertEqual(len(dom_get_child_elements(nv_set)), 2)
1355 self.assertEqual(pair, dom_get_child_elements(nv_set)[0])
1356 pair = dom_get_child_elements(nv_set)[1]
1357 self.assertEqual(pair.getAttribute("name"), "another_name")
1358 self.assertEqual(pair.getAttribute("value"), "value")
1359 self.assertEqual(pair.getAttribute("id"), "prefix2-another_name")
1360
1361 def test_dom_update_nv_pair_update(self):
1362 nv_set = xml.dom.minidom.parseString(
1363 """
1364 <nv_set>
1365 <nvpair id="prefix-test_name" name="test_name" value="test_val"/>
1366 <nvpair id="prefix2-another_name" name="another_name" value="value"/>
1367 </nv_set>
1368 """
1369 ).documentElement
1370 utils.dom_update_nv_pair(nv_set, "test_name", "new_value")
1371 self.assertEqual(len(dom_get_child_elements(nv_set)), 2)
1372 pair1 = dom_get_child_elements(nv_set)[0]
1373 pair2 = dom_get_child_elements(nv_set)[1]
1374 self.assertEqual(pair1.getAttribute("name"), "test_name")
1375 self.assertEqual(pair1.getAttribute("value"), "new_value")
1376 self.assertEqual(pair1.getAttribute("id"), "prefix-test_name")
1377 self.assertEqual(pair2.getAttribute("name"), "another_name")
1378 self.assertEqual(pair2.getAttribute("value"), "value")
1379 self.assertEqual(pair2.getAttribute("id"), "prefix2-another_name")
1380
1381 def test_dom_update_nv_pair_remove(self):
1382 nv_set = xml.dom.minidom.parseString(
1383 """
1384 <nv_set>
1385 <nvpair id="prefix-test_name" name="test_name" value="test_val"/>
1386 <nvpair id="prefix2-another_name" name="another_name" value="value"/>
1387 </nv_set>
1388 """
1389 ).documentElement
1390 utils.dom_update_nv_pair(nv_set, "non_existing_name", "")
1391 self.assertEqual(len(dom_get_child_elements(nv_set)), 2)
1392 utils.dom_update_nv_pair(nv_set, "another_name", "")
1393 self.assertEqual(len(dom_get_child_elements(nv_set)), 1)
1394 pair = dom_get_child_elements(nv_set)[0]
1395 self.assertEqual(pair.getAttribute("name"), "test_name")
1396 self.assertEqual(pair.getAttribute("value"), "test_val")
1397 self.assertEqual(pair.getAttribute("id"), "prefix-test_name")
1398 utils.dom_update_nv_pair(nv_set, "test_name", "")
1399 self.assertEqual(len(dom_get_child_elements(nv_set)), 0)
1400
1401 def test_convert_args_to_tuples(self):
1402 out = utils.convert_args_to_tuples(
1403 ["invalid_string", "key=value", "key2=val=ue", "k e y= v a l u e "]
1404 )
1405 self.assertEqual(
1406 out,
1407 [("key", "value"), ("key2", "val=ue"), ("k e y", " v a l u e ")],
1408 )
1409
1410 def test_dom_update_utilization_invalid(self):
1411 # commands writes to stderr
1412 # we want clean test output, so we capture it
1413 tmp_stderr = sys.stderr
1414 sys.stderr = StringIO()
1415
1416 el = xml.dom.minidom.parseString(
1417 """
1418 <resource id="test_id"/>
1419 """
1420 ).documentElement
1421 self.assertRaises(
1422 SystemExit,
1423 utils.dom_update_utilization,
1424 el,
1425 {"name": "invalid_val"},
1426 )
1427
1428 self.assertRaises(
1429 SystemExit, utils.dom_update_utilization, el, {"name": "0.01"}
1430 )
1431
1432 sys.stderr = tmp_stderr
1433
1434 def test_dom_update_utilization_add(self):
|
CID (unavailable; MK=c54de875e9258c0348bb3155c7f172c0) (#1 of 1): XML external entity processing enabled (SIGMA.xml_external_entity_enabled): |
|
(1) Event Sigma main event: |
The application uses Python's built in `xml` module which does not properly handle erroneous or maliciously constructed data, making the application vulnerable to one or more types of XML attacks. |
|
(2) Event remediation: |
Avoid using the `xml` module. Consider using the `defusedxml` module or similar which safely prevents all XML entity attacks. |
1435 el = xml.dom.minidom.parseString(
1436 """
1437 <resource id="test_id"/>
1438 """
1439 ).documentElement
1440 utils.dom_update_utilization(
1441 el,
1442 {
1443 "name": "",
1444 "key": "-1",
1445 "keys": "90",
1446 },
1447 )
1448
1449 self.assertEqual(len(dom_get_child_elements(el)), 1)
1450 u = dom_get_child_elements(el)[0]
1451 self.assertEqual(u.tagName, "utilization")
1452 self.assertEqual(u.getAttribute("id"), "test_id-utilization")
1453 self.assertEqual(len(dom_get_child_elements(u)), 2)
1454
1455 self.assertEqual(
1456 dom_get_child_elements(u)[0].getAttribute("id"),
1457 "test_id-utilization-key",
1458 )
1459 self.assertEqual(
1460 dom_get_child_elements(u)[0].getAttribute("name"), "key"
1461 )
1462 self.assertEqual(
1463 dom_get_child_elements(u)[0].getAttribute("value"), "-1"
1464 )
1465 self.assertEqual(
1466 dom_get_child_elements(u)[1].getAttribute("id"),
1467 "test_id-utilization-keys",
1468 )
1469 self.assertEqual(
1470 dom_get_child_elements(u)[1].getAttribute("name"), "keys"
1471 )
1472 self.assertEqual(
1473 dom_get_child_elements(u)[1].getAttribute("value"), "90"
1474 )
1475
1476 def test_dom_update_utilization_update_remove(self):
1477 el = xml.dom.minidom.parseString(
1478 """
1479 <resource id="test_id">
1480 <utilization id="test_id-utilization">
1481 <nvpair id="test_id-utilization-key" name="key" value="-1"/>
1482 <nvpair id="test_id-utilization-keys" name="keys" value="90"/>
1483 </utilization>
1484 </resource>
1485 """
1486 ).documentElement
1487 utils.dom_update_utilization(
1488 el,
1489 {
1490 "key": "100",
1491 "keys": "",
1492 },
1493 )
1494
1495 u = dom_get_child_elements(el)[0]
1496 self.assertEqual(len(dom_get_child_elements(u)), 1)
1497 self.assertEqual(
1498 dom_get_child_elements(u)[0].getAttribute("id"),
1499 "test_id-utilization-key",
1500 )
1501 self.assertEqual(
1502 dom_get_child_elements(u)[0].getAttribute("name"), "key"
1503 )
1504 self.assertEqual(
1505 dom_get_child_elements(u)[0].getAttribute("value"), "100"
1506 )
1507
1508 def test_dom_update_meta_attr_add(self):
1509 el = xml.dom.minidom.parseString(
1510 """
1511 <resource id="test_id"/>
1512 """
1513 ).documentElement
1514 utils.dom_update_meta_attr(
1515 el, [("name", ""), ("key", "test"), ("key2", "val")]
1516 )
1517
1518 self.assertEqual(len(dom_get_child_elements(el)), 1)
1519 u = dom_get_child_elements(el)[0]
1520 self.assertEqual(u.tagName, "meta_attributes")
1521 self.assertEqual(u.getAttribute("id"), "test_id-meta_attributes")
1522 self.assertEqual(len(dom_get_child_elements(u)), 2)
1523
1524 self.assertEqual(
1525 dom_get_child_elements(u)[0].getAttribute("id"),
1526 "test_id-meta_attributes-key",
1527 )
1528 self.assertEqual(
1529 dom_get_child_elements(u)[0].getAttribute("name"), "key"
1530 )
1531 self.assertEqual(
1532 dom_get_child_elements(u)[0].getAttribute("value"), "test"
1533 )
1534 self.assertEqual(
1535 dom_get_child_elements(u)[1].getAttribute("id"),
1536 "test_id-meta_attributes-key2",
1537 )
1538 self.assertEqual(
1539 dom_get_child_elements(u)[1].getAttribute("name"), "key2"
1540 )
1541 self.assertEqual(
1542 dom_get_child_elements(u)[1].getAttribute("value"), "val"
1543 )
1544
1545 def test_dom_update_meta_attr_update_remove(self):
1546 el = xml.dom.minidom.parseString(
1547 """
1548 <resource id="test_id">
1549 <meta_attributes id="test_id-utilization">
1550 <nvpair id="test_id-meta_attributes-key" name="key" value="test"/>
1551 <nvpair id="test_id-meta_attributes-key2" name="key2" value="val"/>
1552 </meta_attributes>
1553 </resource>
1554 """
1555 ).documentElement
1556 utils.dom_update_meta_attr(el, [("key", "another_val"), ("key2", "")])
1557
1558 u = dom_get_child_elements(el)[0]
1559 self.assertEqual(len(dom_get_child_elements(u)), 1)
1560 self.assertEqual(
1561 dom_get_child_elements(u)[0].getAttribute("id"),
1562 "test_id-meta_attributes-key",
1563 )
1564 self.assertEqual(
1565 dom_get_child_elements(u)[0].getAttribute("name"), "key"
1566 )
1567 self.assertEqual(
1568 dom_get_child_elements(u)[0].getAttribute("value"), "another_val"
1569 )
1570
1571 def test_get_utilization(self):
1572 el = xml.dom.minidom.parseString(
1573 """
1574 <resource id="test_id">
1575 <utilization id="test_id-utilization">
1576 <nvpair id="test_id-utilization-key" name="key" value="-1"/>
1577 <nvpair id="test_id-utilization-keys" name="keys" value="90"/>
1578 </utilization>
1579 </resource>
1580 """
1581 ).documentElement
1582 self.assertEqual({"key": "-1", "keys": "90"}, utils.get_utilization(el))
1583
1584 def test_get_utilization_str(self):
1585 el = xml.dom.minidom.parseString(
1586 """
1587 <resource id="test_id">
1588 <utilization id="test_id-utilization">
1589 <nvpair id="test_id-utilization-key" name="key" value="-1"/>
1590 <nvpair id="test_id-utilization-keys" name="keys" value="90"/>
1591 </utilization>
1592 </resource>
1593 """
1594 ).documentElement
1595 self.assertEqual("key=-1 keys=90", utils.get_utilization_str(el))
1596
1597 def assert_element_id(self, node, node_id, tag=None):
1598 self.assertTrue(
1599 isinstance(node, xml.dom.minidom.Element),
1600 "element with id '%s' not found" % node_id,
1601 )
1602 self.assertEqual(node.getAttribute("id"), node_id)
1603 if tag:
1604 self.assertEqual(node.tagName, tag)
1605
1606
1607 class RunParallelTest(TestCase):
1608 @staticmethod
1609 def fixture_create_worker(log, name, sleepSeconds=0):
1610 def worker():
1611 sleep(sleepSeconds)
1612 log.append(name)
1613
1614 return worker
1615
1616 def test_run_all_workers(self):
1617 log = []
1618 utils.run_parallel(
1619 [
1620 self.fixture_create_worker(log, "first"),
1621 self.fixture_create_worker(log, "second"),
1622 ],
1623 wait_seconds=0.1,
1624 )
1625
1626 self.assertEqual(sorted(log), sorted(["first", "second"]))
1627
1628
1629 class NodeActionTaskTest(TestCase):
1630 def test_can_run_action(self):
1631 def action(node, arg, kwarg=None):
1632 return (0, ":".join([node, arg, kwarg]))
1633
1634 report_list = []
1635
1636 def report(node, returncode, output):
1637 report_list.append("|".join([node, str(returncode), output]))
1638
1639 task = utils.create_task(report, action, "node", "arg", kwarg="kwarg")
1640 task()
1641
1642 self.assertEqual(["node|0|node:arg:kwarg"], report_list)
1643
1644
1645 class TouchCibFile(TestCase):
1646 @mock.patch("pcs.utils.os.path.isfile", mock.Mock(return_value=False))
1647 @mock.patch(
1648 "pcs.utils.write_empty_cib",
1649 mock.Mock(side_effect=OSError("some message")),
1650 )
1651 @mock.patch("pcs.utils.err")
1652 def test_exception_is_transformed_correctly(self, err):
1653 filename = "/fake/filename"
1654 utils.touch_cib_file(filename)
1655 err.assert_called_once_with(
1656 "Unable to write to file: '/fake/filename': 'some message'"
1657 )
1658
1659
1660 @mock.patch("pcs.utils.is_run_interactive", mock.Mock(return_value=False))
1661 class GetContinueConfirmation(TestCase):
1662 def setUp(self):
1663 self.text = "some warning text"
1664 patcher_output = mock.patch("pcs.cli.reports.output.print_to_stderr")
1665 self.addCleanup(patcher_output.stop)
1666 self.mock_output = patcher_output.start()
1667
1668 def test_yes_force(self):
1669 self.assertTrue(utils.get_continue_confirmation(self.text, True, True))
1670 self.mock_output.assert_called_once_with(f"Warning: {self.text}")
1671
1672 def test_yes(self):
1673 self.assertTrue(utils.get_continue_confirmation(self.text, True, False))
1674 self.mock_output.assert_called_once_with(f"Warning: {self.text}")
1675
1676 def test_force(self):
1677 self.assertTrue(utils.get_continue_confirmation(self.text, False, True))
1678 self.assertEqual(
1679 self.mock_output.mock_calls,
1680 [
1681 mock.call(
1682 "Deprecation Warning: Using --force to confirm this action "
1683 "is deprecated and might be removed in a future release, "
1684 "use --yes instead"
1685 ),
1686 mock.call(f"Warning: {self.text}"),
1687 ],
1688 )
1689
1690 def test_nothing(self):
1691 with self.assertRaises(SystemExit) as cm:
1692 self.assertFalse(
1693 utils.get_continue_confirmation(self.text, False, False)
1694 )
1695 self.assertEqual(cm.exception.code, 1)
1696 self.mock_output.assert_called_once_with(
1697 f"Error: {self.text}, use --yes to override"
1698 )
1699