A declarative host provisioning system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

oob.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. # Copyright 2018 AT&T Intellectual Property. All other rights reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Driver for controlling OOB interface via Redfish.
  15. Based on Redfish Rest API specification.
  16. """
  17. import time
  18. from oslo_config import cfg
  19. from drydock_provisioner.orchestrator.actions.orchestrator import BaseAction
  20. from drydock_provisioner.drivers.oob.redfish_driver.client import RedfishException
  21. from drydock_provisioner.drivers.oob.redfish_driver.client import RedfishSession
  22. import drydock_provisioner.error as errors
  23. import drydock_provisioner.objects.fields as hd_fields
  24. class RedfishBaseAction(BaseAction):
  25. """Base action for Redfish executed actions."""
  26. def get_redfish_session(self, node):
  27. """Initialize a Redfish session to the node.
  28. :param node: instance of objects.BaremetalNode
  29. :return: An instance of client.RedfishSession initialized to node's Redfish interface
  30. """
  31. if node.oob_type != 'redfish':
  32. raise errors.DriverError("Node OOB type is not Redfish")
  33. oob_network = node.oob_parameters['network']
  34. oob_address = node.get_network_address(oob_network)
  35. if oob_address is None:
  36. raise errors.DriverError(
  37. "Node %s has no OOB Redfish address" % (node.name))
  38. oob_account = node.oob_parameters['account']
  39. oob_credential = node.oob_parameters['credential']
  40. self.logger.debug("Starting Redfish session to %s with %s" %
  41. (oob_address, oob_account))
  42. try:
  43. redfish_obj = RedfishSession(host=oob_address,
  44. account=oob_account,
  45. password=oob_credential,
  46. use_ssl=cfg.CONF.redfish_driver.use_ssl,
  47. connection_retries=cfg.CONF.redfish_driver.max_retries)
  48. except (RedfishException, errors.DriverError) as iex:
  49. self.logger.error(
  50. "Error initializing Redfish session for node %s" % node.name)
  51. self.logger.error("Redfish Exception: %s" % str(iex))
  52. redfish_obj = None
  53. return redfish_obj
  54. def exec_redfish_command(self, node, session, func, *args):
  55. """Call a Redfish command after establishing a session.
  56. :param node: Instance of objects.BaremetalNode to execute against
  57. :param session: Redfish session
  58. :param func: The redfish Command method to call
  59. :param args: The args to pass the func
  60. """
  61. try:
  62. self.logger.debug("Calling Redfish command %s on %s" %
  63. (func.__name__, node.name))
  64. response = func(session, *args)
  65. return response
  66. except RedfishException as iex:
  67. self.logger.error(
  68. "Error executing Redfish command %s for node %s" % (func.__name__, node.name))
  69. self.logger.error("Redfish Exception: %s" % str(iex))
  70. raise errors.DriverError("Redfish command failed.")
  71. class ValidateOobServices(RedfishBaseAction):
  72. """Action to validate OOB services are available."""
  73. def start(self):
  74. self.task.add_status_msg(
  75. msg="OOB does not require services.",
  76. error=False,
  77. ctx='NA',
  78. ctx_type='NA')
  79. self.task.set_status(hd_fields.TaskStatus.Complete)
  80. self.task.success()
  81. self.task.save()
  82. return
  83. class ConfigNodePxe(RedfishBaseAction):
  84. """Action to configure PXE booting via OOB."""
  85. def start(self):
  86. self.task.set_status(hd_fields.TaskStatus.Running)
  87. self.task.save()
  88. node_list = self.orchestrator.get_target_nodes(self.task)
  89. for n in node_list:
  90. self.task.add_status_msg(
  91. msg="Redfish doesn't configure PXE options.",
  92. error=True,
  93. ctx=n.name,
  94. ctx_type='node')
  95. self.task.set_status(hd_fields.TaskStatus.Complete)
  96. self.task.failure()
  97. self.task.save()
  98. return
  99. class SetNodeBoot(RedfishBaseAction):
  100. """Action to configure a node to PXE boot."""
  101. def start(self):
  102. self.task.set_status(hd_fields.TaskStatus.Running)
  103. self.task.save()
  104. node_list = self.orchestrator.get_target_nodes(self.task)
  105. for n in node_list:
  106. self.logger.debug("Setting bootdev to PXE for %s" % n.name)
  107. self.task.add_status_msg(
  108. msg="Setting node to PXE boot.",
  109. error=False,
  110. ctx=n.name,
  111. ctx_type='node')
  112. bootdev = None
  113. try:
  114. session = self.get_redfish_session(n)
  115. bootdev = self.exec_redfish_command(n, session, RedfishSession.get_bootdev)
  116. if bootdev.get('bootdev', '') != 'Pxe':
  117. self.exec_redfish_command(n, session, RedfishSession.set_bootdev, 'Pxe')
  118. bootdev = self.exec_redfish_command(n, session, RedfishSession.get_bootdev)
  119. session.close_session()
  120. except errors.DriverError:
  121. pass
  122. if bootdev is not None and (bootdev.get('bootdev',
  123. '') == 'Pxe'):
  124. self.task.add_status_msg(
  125. msg="Set bootdev to PXE.",
  126. error=False,
  127. ctx=n.name,
  128. ctx_type='node')
  129. self.logger.debug("%s reports bootdev of network" % n.name)
  130. self.task.success(focus=n.name)
  131. else:
  132. self.task.add_status_msg(
  133. msg="Unable to set bootdev to PXE.",
  134. error=True,
  135. ctx=n.name,
  136. ctx_type='node')
  137. self.task.failure(focus=n.name)
  138. self.logger.warning(
  139. "Unable to set node %s to PXE boot." % (n.name))
  140. self.task.set_status(hd_fields.TaskStatus.Complete)
  141. self.task.save()
  142. return
  143. class PowerOffNode(RedfishBaseAction):
  144. """Action to power off a node via Redfish."""
  145. def start(self):
  146. self.task.set_status(hd_fields.TaskStatus.Running)
  147. self.task.save()
  148. node_list = self.orchestrator.get_target_nodes(self.task)
  149. for n in node_list:
  150. self.logger.debug("Sending set_power = off command to %s" % n.name)
  151. self.task.add_status_msg(
  152. msg="Sending set_power = off command.",
  153. error=False,
  154. ctx=n.name,
  155. ctx_type='node')
  156. session = self.get_redfish_session(n)
  157. # If power is already off, continue with the next node
  158. power_state = self.exec_redfish_command(n, RedfishSession.get_power)
  159. if power_state is not None and (power_state.get(
  160. 'powerstate', '') == 'Off'):
  161. self.task.add_status_msg(
  162. msg="Node reports power off.",
  163. error=False,
  164. ctx=n.name,
  165. ctx_type='node')
  166. self.logger.debug(
  167. "Node %s reports powerstate already off. No action required" % n.name)
  168. self.task.success(focus=n.name)
  169. continue
  170. self.exec_redfish_command(n, session, RedfishSession.set_power, 'ForceOff')
  171. attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
  172. while attempts > 0:
  173. self.logger.debug("Polling powerstate waiting for success.")
  174. power_state = self.exec_redfish_command(n, RedfishSession.get_power)
  175. if power_state is not None and (power_state.get(
  176. 'powerstate', '') == 'Off'):
  177. self.task.add_status_msg(
  178. msg="Node reports power off.",
  179. error=False,
  180. ctx=n.name,
  181. ctx_type='node')
  182. self.logger.debug(
  183. "Node %s reports powerstate of off" % n.name)
  184. self.task.success(focus=n.name)
  185. break
  186. time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
  187. attempts = attempts - 1
  188. if power_state is not None and (power_state.get('powerstate', '')
  189. != 'Off'):
  190. self.task.add_status_msg(
  191. msg="Node failed to power off.",
  192. error=True,
  193. ctx=n.name,
  194. ctx_type='node')
  195. self.logger.error("Giving up on Redfish command to %s" % n.name)
  196. self.task.failure(focus=n.name)
  197. session.close_session()
  198. self.task.set_status(hd_fields.TaskStatus.Complete)
  199. self.task.save()
  200. return
  201. class PowerOnNode(RedfishBaseAction):
  202. """Action to power on a node via Redfish."""
  203. def start(self):
  204. self.task.set_status(hd_fields.TaskStatus.Running)
  205. self.task.save()
  206. node_list = self.orchestrator.get_target_nodes(self.task)
  207. for n in node_list:
  208. self.logger.debug("Sending set_power = on command to %s" % n.name)
  209. self.task.add_status_msg(
  210. msg="Sending set_power = on command.",
  211. error=False,
  212. ctx=n.name,
  213. ctx_type='node')
  214. session = self.get_redfish_session(n)
  215. # If power is already on, continue with the next node
  216. power_state = self.exec_redfish_command(n, RedfishSession.get_power)
  217. if power_state is not None and (power_state.get(
  218. 'powerstate', '') == 'On'):
  219. self.task.add_status_msg(
  220. msg="Node reports power on.",
  221. error=False,
  222. ctx=n.name,
  223. ctx_type='node')
  224. self.logger.debug(
  225. "Node %s reports powerstate already on. No action required" % n.name)
  226. self.task.success(focus=n.name)
  227. continue
  228. self.exec_redfish_command(n, session, RedfishSession.set_power, 'On')
  229. attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
  230. while attempts > 0:
  231. self.logger.debug("Polling powerstate waiting for success.")
  232. power_state = self.exec_redfish_command(n, session, RedfishSession.get_power)
  233. if power_state is not None and (power_state.get(
  234. 'powerstate', '') == 'On'):
  235. self.logger.debug(
  236. "Node %s reports powerstate of on" % n.name)
  237. self.task.add_status_msg(
  238. msg="Node reports power on.",
  239. error=False,
  240. ctx=n.name,
  241. ctx_type='node')
  242. self.task.success(focus=n.name)
  243. break
  244. time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
  245. attempts = attempts - 1
  246. if power_state is not None and (power_state.get('powerstate', '')
  247. != 'On'):
  248. self.task.add_status_msg(
  249. msg="Node failed to power on.",
  250. error=True,
  251. ctx=n.name,
  252. ctx_type='node')
  253. self.logger.error("Giving up on Redfish command to %s" % n.name)
  254. self.task.failure(focus=n.name)
  255. session.close_session()
  256. self.task.set_status(hd_fields.TaskStatus.Complete)
  257. self.task.save()
  258. return
  259. class PowerCycleNode(RedfishBaseAction):
  260. """Action to hard powercycle a node via Redfish."""
  261. def start(self):
  262. self.task.set_status(hd_fields.TaskStatus.Running)
  263. self.task.save()
  264. node_list = self.orchestrator.get_target_nodes(self.task)
  265. for n in node_list:
  266. self.logger.debug("Sending set_power = off command to %s" % n.name)
  267. self.task.add_status_msg(
  268. msg="Power cycling node via Redfish.",
  269. error=False,
  270. ctx=n.name,
  271. ctx_type='node')
  272. session = self.get_redfish_session(n)
  273. self.exec_redfish_command(n, session, RedfishSession.set_power, 'ForceOff')
  274. # Wait for power state of off before booting back up
  275. attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
  276. while attempts > 0:
  277. power_state = self.exec_redfish_command(n, session, RedfishSession.get_power)
  278. if power_state is not None and power_state.get(
  279. 'powerstate', '') == 'Off':
  280. self.logger.debug("%s reports powerstate of off" % n.name)
  281. break
  282. elif power_state is None:
  283. self.logger.debug(
  284. "No response on Redfish power query to %s" % n.name)
  285. time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
  286. attempts = attempts - 1
  287. if power_state.get('powerstate', '') != 'Off':
  288. self.task.add_status_msg(
  289. msg="Failed to power down during power cycle.",
  290. error=True,
  291. ctx=n.name,
  292. ctx_type='node')
  293. self.logger.warning(
  294. "Failed powering down node %s during power cycle task" %
  295. n.name)
  296. self.task.failure(focus=n.name)
  297. break
  298. self.logger.debug("Sending set_power = on command to %s" % n.name)
  299. self.exec_redfish_command(n, session, RedfishSession.set_power, 'On')
  300. attempts = cfg.CONF.redfish_driver.power_state_change_max_retries
  301. while attempts > 0:
  302. power_state = self.exec_redfish_command(n, session, RedfishSession.get_power)
  303. if power_state is not None and power_state.get(
  304. 'powerstate', '') == 'On':
  305. self.logger.debug("%s reports powerstate of on" % n.name)
  306. break
  307. elif power_state is None:
  308. self.logger.debug(
  309. "No response on Redfish power query to %s" % n.name)
  310. time.sleep(cfg.CONF.redfish_driver.power_state_change_retry_interval)
  311. attempts = attempts - 1
  312. if power_state is not None and (power_state.get('powerstate',
  313. '') == 'On'):
  314. self.task.add_status_msg(
  315. msg="Node power cycle complete.",
  316. error=False,
  317. ctx=n.name,
  318. ctx_type='node')
  319. self.task.success(focus=n.name)
  320. else:
  321. self.task.add_status_msg(
  322. msg="Failed to power up during power cycle.",
  323. error=True,
  324. ctx=n.name,
  325. ctx_type='node')
  326. self.logger.warning(
  327. "Failed powering up node %s during power cycle task" %
  328. n.name)
  329. self.task.failure(focus=n.name)
  330. session.close_session()
  331. self.task.set_status(hd_fields.TaskStatus.Complete)
  332. self.task.save()
  333. return
  334. class InterrogateOob(RedfishBaseAction):
  335. """Action to complete a basic interrogation of the node Redfish interface."""
  336. def start(self):
  337. self.task.set_status(hd_fields.TaskStatus.Running)
  338. self.task.save()
  339. node_list = self.orchestrator.get_target_nodes(self.task)
  340. for n in node_list:
  341. try:
  342. self.logger.debug(
  343. "Interrogating node %s Redfish interface." % n.name)
  344. session = self.get_redfish_session(n)
  345. powerstate = self.exec_redfish_command(n, session, RedfishSession.get_power)
  346. session.close_session()
  347. if powerstate is None:
  348. raise errors.DriverError()
  349. self.task.add_status_msg(
  350. msg="Redfish interface interrogation yielded powerstate %s" %
  351. powerstate.get('powerstate'),
  352. error=False,
  353. ctx=n.name,
  354. ctx_type='node')
  355. self.task.success(focus=n.name)
  356. except errors.DriverError:
  357. self.logger.debug(
  358. "Interrogating node %s Redfish interface failed." % n.name)
  359. self.task.add_status_msg(
  360. msg="Redfish interface interrogation failed.",
  361. error=True,
  362. ctx=n.name,
  363. ctx_type='node')
  364. self.task.failure(focus=n.name)
  365. self.task.set_status(hd_fields.TaskStatus.Complete)
  366. self.task.save()
  367. return