/pcs/lib/cib/resource/validations.py

https://github.com/feist/pcs · Python · 306 lines · 229 code · 33 blank · 44 comment · 37 complexity · fa80e6e12e462e6d1167113ae1c2e826 MD5 · raw file

  1. from collections import namedtuple
  2. from typing import (
  3. List,
  4. Optional,
  5. )
  6. from lxml.etree import _Element
  7. from pcs.common import reports
  8. from pcs.lib.validate import validate_add_remove_items
  9. from . import group
  10. from .bundle import is_bundle
  11. from .clone import (
  12. get_parent_any_clone,
  13. is_any_clone,
  14. is_master,
  15. is_promotable_clone,
  16. )
  17. from .common import (
  18. get_inner_resources,
  19. get_parent_resource,
  20. is_resource,
  21. is_wrapper_resource,
  22. )
  23. from .stonith import is_stonith
  24. def validate_move_resources_to_group(
  25. group_element: _Element,
  26. resource_element_list: List[_Element],
  27. adjacent_resource_element: Optional[_Element],
  28. ) -> reports.ReportItemList:
  29. """
  30. Validates that existing resources can be moved into a group,
  31. optionally beside an adjacent_resource_element
  32. group_element -- the group to put resources into
  33. resource_element_list -- resources that are being moved into the group
  34. adjacent_resource_element -- put resources beside this one
  35. """
  36. report_list: reports.ReportItemList = []
  37. # Validate types of resources and their parents
  38. for resource_element in resource_element_list:
  39. # Only primitive resources can be moved
  40. if not is_resource(resource_element):
  41. report_list.append(
  42. reports.ReportItem.error(
  43. reports.messages.IdBelongsToUnexpectedType(
  44. str(resource_element.attrib["id"]),
  45. ["primitive"],
  46. resource_element.tag,
  47. )
  48. )
  49. )
  50. elif is_wrapper_resource(resource_element):
  51. report_list.append(
  52. reports.ReportItem.error(
  53. reports.messages.CannotGroupResourceWrongType(
  54. str(resource_element.attrib["id"]),
  55. resource_element.tag,
  56. parent_id=None,
  57. parent_type=None,
  58. )
  59. )
  60. )
  61. else:
  62. parent = get_parent_resource(resource_element)
  63. if parent is not None and not group.is_group(parent):
  64. # At the moment, moving resources out of bundles and clones
  65. # (or masters) is not possible
  66. report_list.append(
  67. reports.ReportItem.error(
  68. reports.messages.CannotGroupResourceWrongType(
  69. str(resource_element.attrib["id"]),
  70. resource_element.tag,
  71. parent_id=str(parent.attrib["id"]),
  72. parent_type=parent.tag,
  73. )
  74. )
  75. )
  76. # Validate that elements can be added
  77. # Check if the group element is a group
  78. if group.is_group(group_element):
  79. report_list += validate_add_remove_items(
  80. [str(resource.attrib["id"]) for resource in resource_element_list],
  81. [],
  82. [
  83. str(resource.attrib["id"])
  84. for resource in get_inner_resources(group_element)
  85. ],
  86. reports.const.ADD_REMOVE_CONTAINER_TYPE_GROUP,
  87. reports.const.ADD_REMOVE_ITEM_TYPE_RESOURCE,
  88. str(group_element.attrib["id"]),
  89. str(adjacent_resource_element.attrib["id"])
  90. if adjacent_resource_element is not None
  91. else None,
  92. True,
  93. )
  94. else:
  95. report_list.append(
  96. reports.ReportItem.error(
  97. reports.messages.IdBelongsToUnexpectedType(
  98. str(group_element.attrib["id"]),
  99. expected_types=[group.TAG],
  100. current_type=group_element.tag,
  101. )
  102. )
  103. )
  104. # Elements can always be removed from their old groups, except when the last
  105. # resource is removed but that is handled in resource.group_add for now, no
  106. # need to run the validation for removing elements
  107. return report_list
  108. def validate_move(resource_element, master):
  109. """
  110. Validate moving a resource to a node
  111. etree resource_element -- the resource to be moved
  112. bool master -- limit moving to the master role
  113. """
  114. report_list = []
  115. if is_stonith(resource_element):
  116. report_list.append(
  117. reports.ReportItem.deprecation(
  118. reports.messages.ResourceStonithCommandsMismatch(
  119. "stonith device"
  120. )
  121. )
  122. )
  123. analysis = _validate_move_ban_clear_analyzer(resource_element)
  124. if analysis.is_bundle:
  125. report_list.append(
  126. reports.ReportItem.error(
  127. reports.messages.CannotMoveResourceBundle(
  128. resource_element.get("id")
  129. )
  130. )
  131. )
  132. return report_list
  133. if (analysis.is_clone or analysis.is_in_clone) and not (
  134. analysis.is_promotable_clone or analysis.is_in_promotable_clone
  135. ):
  136. report_list.append(
  137. reports.ReportItem.error(
  138. reports.messages.CannotMoveResourceClone(
  139. resource_element.get("id")
  140. )
  141. )
  142. )
  143. return report_list
  144. # Allow moving both promoted and demoted instances of promotable clones
  145. # (analysis.is_promotable_clone). Do not allow to move promoted instances
  146. # of promotables' inner resources (analysis.is_in_promotable_clone) as that
  147. # would create a constraint for the promoted role of a group or a primitive
  148. # which would not make sense if the group or primitive is later moved out
  149. # of its promotable clone. To be consistent, we do not allow to move
  150. # demoted instances of promotables' inner resources either. See
  151. # rhbz#1875301 for details.
  152. if not master and analysis.is_in_promotable_clone:
  153. report_list.append(
  154. reports.ReportItem.error(
  155. reports.messages.CannotMoveResourcePromotableInner(
  156. resource_element.get("id"),
  157. analysis.promotable_clone_id,
  158. )
  159. )
  160. )
  161. elif master and not analysis.is_promotable_clone:
  162. report_list.append(
  163. reports.ReportItem.error(
  164. reports.messages.CannotMoveResourceMasterResourceNotPromotable(
  165. resource_element.get("id"),
  166. promotable_id=analysis.promotable_clone_id,
  167. )
  168. )
  169. )
  170. return report_list
  171. def validate_ban(resource_element, master):
  172. """
  173. Validate banning a resource on a node
  174. etree resource_element -- the resource to be banned
  175. bool master -- limit banning to the master role
  176. """
  177. report_list = []
  178. if is_stonith(resource_element):
  179. report_list.append(
  180. reports.ReportItem.deprecation(
  181. reports.messages.ResourceStonithCommandsMismatch(
  182. "stonith device"
  183. )
  184. )
  185. )
  186. analysis = _validate_move_ban_clear_analyzer(resource_element)
  187. if master and not analysis.is_promotable_clone:
  188. report_list.append(
  189. reports.ReportItem.error(
  190. reports.messages.CannotBanResourceMasterResourceNotPromotable(
  191. resource_element.get("id"),
  192. promotable_id=analysis.promotable_clone_id,
  193. )
  194. )
  195. )
  196. return report_list
  197. def validate_unmove_unban(resource_element, master):
  198. """
  199. Validate unmoving/unbanning a resource to/on nodes
  200. etree resource_element -- the resource to be unmoved/unbanned
  201. bool master -- limit unmoving/unbanning to the master role
  202. """
  203. report_list = []
  204. if is_stonith(resource_element):
  205. report_list.append(
  206. reports.ReportItem.deprecation(
  207. reports.messages.ResourceStonithCommandsMismatch(
  208. "stonith device"
  209. )
  210. )
  211. )
  212. analysis = _validate_move_ban_clear_analyzer(resource_element)
  213. if master and not analysis.is_promotable_clone:
  214. # pylint: disable=line-too-long
  215. report_list.append(
  216. reports.ReportItem.error(
  217. reports.messages.CannotUnmoveUnbanResourceMasterResourceNotPromotable(
  218. resource_element.get("id"),
  219. promotable_id=analysis.promotable_clone_id,
  220. )
  221. )
  222. )
  223. return report_list
  224. class _MoveBanClearAnalysis(
  225. namedtuple(
  226. "_MoveBanClearAnalysis",
  227. [
  228. "is_bundle",
  229. "is_clone",
  230. "is_in_clone",
  231. "is_promotable_clone",
  232. "is_in_promotable_clone",
  233. "promotable_clone_id",
  234. ],
  235. )
  236. ):
  237. pass
  238. def _validate_move_ban_clear_analyzer(resource_element):
  239. resource_is_bundle = False
  240. resource_is_clone = False
  241. resource_is_in_clone = False
  242. resource_is_promotable_clone = False
  243. resource_is_in_promotable_clone = False
  244. promotable_clone_element = None
  245. if is_bundle(resource_element):
  246. resource_is_bundle = True
  247. elif is_any_clone(resource_element):
  248. resource_is_clone = True
  249. if is_master(resource_element) or is_promotable_clone(resource_element):
  250. resource_is_promotable_clone = True
  251. promotable_clone_element = resource_element
  252. elif get_parent_any_clone(resource_element) is not None:
  253. parent_clone = get_parent_any_clone(resource_element)
  254. resource_is_in_clone = True
  255. if is_master(parent_clone) or is_promotable_clone(parent_clone):
  256. resource_is_in_promotable_clone = True
  257. promotable_clone_element = parent_clone
  258. return _MoveBanClearAnalysis(
  259. resource_is_bundle,
  260. resource_is_clone,
  261. resource_is_in_clone,
  262. resource_is_promotable_clone,
  263. resource_is_in_promotable_clone,
  264. (
  265. promotable_clone_element.get("id")
  266. if promotable_clone_element is not None
  267. else None
  268. ),
  269. )