A Python library for code common to TripleO CLI and TripleO UI.
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.

parameters.py 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. # Copyright 2016 Red Hat, Inc.
  2. # All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. import copy
  16. import json
  17. import logging
  18. import uuid
  19. from heatclient import exc as heat_exc
  20. from mistral_lib import actions
  21. from swiftclient import exceptions as swiftexceptions
  22. from tripleo_common.actions import base
  23. from tripleo_common.actions import templates
  24. from tripleo_common import constants
  25. from tripleo_common import exception
  26. from tripleo_common.utils import nodes
  27. from tripleo_common.utils import parameters as parameter_utils
  28. from tripleo_common.utils import passwords as password_utils
  29. from tripleo_common.utils import plan as plan_utils
  30. LOG = logging.getLogger(__name__)
  31. class GetParametersAction(base.TripleOAction):
  32. """Gets list of available heat parameters."""
  33. def __init__(self, container=constants.DEFAULT_CONTAINER_NAME):
  34. super(GetParametersAction, self).__init__()
  35. self.container = container
  36. def run(self, context):
  37. cached = self.cache_get(context,
  38. self.container,
  39. "tripleo.parameters.get")
  40. if cached is not None:
  41. return cached
  42. process_templates_action = templates.ProcessTemplatesAction(
  43. container=self.container
  44. )
  45. processed_data = process_templates_action.run(context)
  46. # If we receive a 'Result' instance it is because the parent action
  47. # had an error.
  48. if isinstance(processed_data, actions.Result):
  49. return processed_data
  50. processed_data['show_nested'] = True
  51. # respect previously user set param values
  52. swift = self.get_object_client(context)
  53. heat = self.get_orchestration_client(context)
  54. try:
  55. env = plan_utils.get_env(swift, self.container)
  56. except swiftexceptions.ClientException as err:
  57. err_msg = ("Error retrieving environment for plan %s: %s" % (
  58. self.container, err))
  59. LOG.exception(err_msg)
  60. return actions.Result(error=err_msg)
  61. params = env.get('parameter_defaults')
  62. fields = {
  63. 'template': processed_data['template'],
  64. 'files': processed_data['files'],
  65. 'environment': processed_data['environment'],
  66. 'show_nested': True
  67. }
  68. result = {
  69. 'heat_resource_tree': heat.stacks.validate(**fields),
  70. 'environment_parameters': params,
  71. }
  72. self.cache_set(context,
  73. self.container,
  74. "tripleo.parameters.get",
  75. result)
  76. return result
  77. class ResetParametersAction(base.TripleOAction):
  78. """Provides method to delete user set parameters."""
  79. def __init__(self, container=constants.DEFAULT_CONTAINER_NAME,
  80. key=constants.DEFAULT_PLAN_ENV_KEY):
  81. super(ResetParametersAction, self).__init__()
  82. self.container = container
  83. self.key = key
  84. def run(self, context):
  85. swift = self.get_object_client(context)
  86. try:
  87. env = plan_utils.get_env(swift, self.container)
  88. except swiftexceptions.ClientException as err:
  89. err_msg = ("Error retrieving environment for plan %s: %s" % (
  90. self.container, err))
  91. LOG.exception(err_msg)
  92. return actions.Result(error=err_msg)
  93. try:
  94. plan_utils.update_in_env(swift, env, self.key,
  95. delete_key=True)
  96. except swiftexceptions.ClientException as err:
  97. err_msg = ("Error updating environment for plan %s: %s" % (
  98. self.container, err))
  99. LOG.exception(err_msg)
  100. return actions.Result(error=err_msg)
  101. self.cache_delete(context,
  102. self.container,
  103. "tripleo.parameters.get")
  104. return env
  105. class UpdateParametersAction(base.TripleOAction):
  106. """Updates plan environment with parameters."""
  107. def __init__(self, parameters,
  108. container=constants.DEFAULT_CONTAINER_NAME,
  109. key=constants.DEFAULT_PLAN_ENV_KEY,
  110. validate=True):
  111. super(UpdateParametersAction, self).__init__()
  112. self.container = container
  113. self.parameters = parameters
  114. self.key = key
  115. self.validate = validate
  116. def run(self, context):
  117. swift = self.get_object_client(context)
  118. heat = self.get_orchestration_client(context)
  119. try:
  120. env = plan_utils.get_env(swift, self.container)
  121. except swiftexceptions.ClientException as err:
  122. err_msg = ("Error retrieving environment for plan %s: %s" % (
  123. self.container, err))
  124. LOG.exception(err_msg)
  125. return actions.Result(error=err_msg)
  126. saved_env = copy.deepcopy(env)
  127. try:
  128. plan_utils.update_in_env(swift, env, self.key,
  129. self.parameters)
  130. except swiftexceptions.ClientException as err:
  131. err_msg = ("Error updating environment for plan %s: %s" % (
  132. self.container, err))
  133. LOG.exception(err_msg)
  134. return actions.Result(error=err_msg)
  135. process_templates_action = templates.ProcessTemplatesAction(
  136. container=self.container
  137. )
  138. processed_data = process_templates_action.run(context)
  139. # If we receive a 'Result' instance it is because the parent action
  140. # had an error.
  141. if isinstance(processed_data, actions.Result):
  142. return processed_data
  143. env = plan_utils.get_env(swift, self.container)
  144. if not self.validate:
  145. return env
  146. params = env.get('parameter_defaults')
  147. fields = {
  148. 'template': processed_data['template'],
  149. 'files': processed_data['files'],
  150. 'environment': processed_data['environment'],
  151. 'show_nested': True
  152. }
  153. try:
  154. result = {
  155. 'heat_resource_tree': heat.stacks.validate(**fields),
  156. 'environment_parameters': params,
  157. }
  158. # Validation passes so the old cache gets replaced.
  159. self.cache_set(context,
  160. self.container,
  161. "tripleo.parameters.get",
  162. result)
  163. if result['heat_resource_tree']:
  164. flattened = {'resources': {}, 'parameters': {}}
  165. _flat_it(flattened, 'Root',
  166. result['heat_resource_tree'])
  167. result['heat_resource_tree'] = flattened
  168. except heat_exc.HTTPException as err:
  169. LOG.debug("Validation failed rebuilding saved env")
  170. # There has been an error validating we must reprocess the
  171. # templates with the saved working env
  172. plan_utils.put_env(swift, saved_env)
  173. process_templates_action._process_custom_roles(context)
  174. err_msg = ("Error validating environment for plan %s: %s" % (
  175. self.container, err))
  176. LOG.exception(err_msg)
  177. return actions.Result(error=err_msg)
  178. LOG.debug("Validation worked new env is saved")
  179. return result
  180. class UpdateRoleParametersAction(UpdateParametersAction):
  181. """Updates role related parameters in plan environment ."""
  182. def __init__(self, role, container=constants.DEFAULT_CONTAINER_NAME):
  183. super(UpdateRoleParametersAction, self).__init__(parameters=None,
  184. container=container)
  185. self.role = role
  186. def run(self, context):
  187. baremetal_client = self.get_baremetal_client(context)
  188. compute_client = self.get_compute_client(context)
  189. self.parameters = parameter_utils.set_count_and_flavor_params(
  190. self.role, baremetal_client, compute_client)
  191. return super(UpdateRoleParametersAction, self).run(context)
  192. class GeneratePasswordsAction(base.TripleOAction):
  193. """Generates passwords needed for Overcloud deployment
  194. This method generates passwords and ensures they are stored in the
  195. plan environment. By default, this method respects previously
  196. generated passwords and adds new passwords as necessary.
  197. If rotate_passwords is set to True, then passwords will be replaced as
  198. follows:
  199. - if password names are specified in the rotate_pw_list, then only those
  200. passwords will be replaced.
  201. - otherwise, all passwords not in the DO_NOT_ROTATE list (as they require
  202. special handling, like KEKs and Fernet keys) will be replaced.
  203. """
  204. def __init__(self, container=constants.DEFAULT_CONTAINER_NAME,
  205. rotate_passwords=False,
  206. rotate_pw_list=[]):
  207. super(GeneratePasswordsAction, self).__init__()
  208. self.container = container
  209. self.rotate_passwords = rotate_passwords
  210. self.rotate_pw_list = rotate_pw_list
  211. def run(self, context):
  212. heat = self.get_orchestration_client(context)
  213. swift = self.get_object_client(context)
  214. mistral = self.get_workflow_client(context)
  215. try:
  216. env = plan_utils.get_env(swift, self.container)
  217. except swiftexceptions.ClientException as err:
  218. err_msg = ("Error retrieving environment for plan %s: %s" % (
  219. self.container, err))
  220. LOG.exception(err_msg)
  221. return actions.Result(error=err_msg)
  222. try:
  223. stack_env = heat.stacks.environment(
  224. stack_id=self.container)
  225. # legacy heat resource names from overcloud.yaml
  226. # We don't modify these to avoid changing defaults
  227. for pw_res in constants.LEGACY_HEAT_PASSWORD_RESOURCE_NAMES:
  228. try:
  229. res = heat.resources.get(self.container, pw_res)
  230. param_defaults = stack_env.get('parameter_defaults', {})
  231. param_defaults[pw_res] = res.attributes['value']
  232. except heat_exc.HTTPNotFound:
  233. LOG.debug('Heat resouce not found: %s' % pw_res)
  234. pass
  235. except heat_exc.HTTPNotFound:
  236. stack_env = None
  237. passwords = password_utils.generate_passwords(
  238. mistralclient=mistral,
  239. stack_env=stack_env,
  240. rotate_passwords=self.rotate_passwords
  241. )
  242. # if passwords don't yet exist in plan environment
  243. if 'passwords' not in env:
  244. env['passwords'] = {}
  245. # NOTE(ansmith): if rabbit password previously generated and
  246. # stored, facilitate upgrade and use for oslo messaging in plan env
  247. if 'RabbitPassword' in env['passwords']:
  248. for i in ('RpcPassword', 'NotifyPassword'):
  249. if i not in env['passwords']:
  250. env['passwords'][i] = env['passwords']['RabbitPassword']
  251. # ensure all generated passwords are present in plan env,
  252. # but respect any values previously generated and stored
  253. for name, password in passwords.items():
  254. if name not in env['passwords']:
  255. env['passwords'][name] = password
  256. if self.rotate_passwords:
  257. if len(self.rotate_pw_list) > 0:
  258. for name in self.rotate_pw_list:
  259. env['passwords'][name] = passwords[name]
  260. else:
  261. for name, password in passwords.items():
  262. if name not in constants.DO_NOT_ROTATE_LIST:
  263. env['passwords'][name] = password
  264. try:
  265. plan_utils.put_env(swift, env)
  266. except swiftexceptions.ClientException as err:
  267. err_msg = "Error uploading to container: %s" % err
  268. LOG.exception(err_msg)
  269. return actions.Result(error=err_msg)
  270. self.cache_delete(context,
  271. self.container,
  272. "tripleo.parameters.get")
  273. return env['passwords']
  274. class GetPasswordsAction(base.TripleOAction):
  275. """Get passwords from the environment
  276. This method returns the list passwords which are used for the deployment.
  277. It will return a merged list of user provided passwords and generated
  278. passwords, giving priority to the user provided passwords.
  279. """
  280. def __init__(self, container=constants.DEFAULT_CONTAINER_NAME):
  281. super(GetPasswordsAction, self).__init__()
  282. self.container = container
  283. def run(self, context):
  284. swift = self.get_object_client(context)
  285. try:
  286. env = plan_utils.get_env(swift, self.container)
  287. except swiftexceptions.ClientException as err:
  288. err_msg = ("Error retrieving environment for plan %s: %s" % (
  289. self.container, err))
  290. LOG.exception(err_msg)
  291. return actions.Result(error=err_msg)
  292. parameter_defaults = env.get('parameter_defaults', {})
  293. passwords = env.get('passwords', {})
  294. return self._get_overriden_passwords(passwords, parameter_defaults)
  295. def _get_overriden_passwords(self, env_passwords, parameter_defaults):
  296. for name in constants.PASSWORD_PARAMETER_NAMES:
  297. if name in parameter_defaults:
  298. env_passwords[name] = parameter_defaults[name]
  299. return env_passwords
  300. class GenerateFencingParametersAction(base.TripleOAction):
  301. """Generates fencing configuration for a deployment.
  302. :param nodes_json: list of nodes & attributes in json format
  303. :param delay: time to wait before taking fencing action
  304. :param ipmi_level: IPMI user level to use
  305. :param ipmi_cipher: IPMI cipher suite to use
  306. :param ipmi_lanplus: whether to use IPMIv2.0
  307. """
  308. def __init__(self, nodes_json, delay,
  309. ipmi_level, ipmi_cipher, ipmi_lanplus):
  310. super(GenerateFencingParametersAction, self).__init__()
  311. self.nodes_json = nodes.convert_nodes_json_mac_to_ports(nodes_json)
  312. self.delay = delay
  313. self.ipmi_level = ipmi_level
  314. self.ipmi_cipher = ipmi_cipher
  315. self.ipmi_lanplus = ipmi_lanplus
  316. def run(self, context):
  317. """Returns the parameters for fencing controller nodes"""
  318. hostmap = nodes.generate_hostmap(self.get_baremetal_client(context),
  319. self.get_compute_client(context))
  320. fence_params = {"EnableFencing": True, "FencingConfig": {}}
  321. devices = []
  322. for node in self.nodes_json:
  323. node_data = {}
  324. params = {}
  325. if "ports" in node:
  326. # Not all Ironic drivers present a MAC address, so we only
  327. # capture it if it's present
  328. mac_addr = node['ports'][0]['address'].lower()
  329. node_data["host_mac"] = mac_addr
  330. # If the MAC isn't in the hostmap, this node hasn't been
  331. # provisioned, so no fencing parameters are necessary
  332. if hostmap and mac_addr not in hostmap:
  333. continue
  334. # Build up fencing parameters based on which Ironic driver this
  335. # node is using
  336. try:
  337. # Deprecated classic drivers (pxe_ipmitool, etc)
  338. driver_proto = node['pm_type'].split('_')[1]
  339. except IndexError:
  340. # New-style hardware types (ipmi, etc)
  341. driver_proto = node['pm_type']
  342. if driver_proto in {'ipmi', 'ipmitool', 'drac', 'idrac', 'ilo',
  343. 'redfish'}:
  344. # IPMI fencing driver
  345. if driver_proto == "redfish":
  346. node_data["agent"] = "fence_redfish"
  347. params["systems_uri"] = node["pm_system_id"]
  348. else:
  349. node_data["agent"] = "fence_ipmilan"
  350. params["ipaddr"] = node["pm_addr"]
  351. params["passwd"] = node["pm_password"]
  352. params["login"] = node["pm_user"]
  353. if hostmap:
  354. params["pcmk_host_list"] = \
  355. hostmap[mac_addr]["compute_name"]
  356. if "pm_port" in node:
  357. params["ipport"] = node["pm_port"]
  358. if "redfish_verify_ca" in node:
  359. if node["redfish_verify_ca"] == "false":
  360. params["ssl_insecure"] = "true"
  361. else:
  362. params["ssl_insecure"] = "false"
  363. if self.ipmi_lanplus:
  364. params["lanplus"] = self.ipmi_lanplus
  365. if self.delay:
  366. params["delay"] = self.delay
  367. if self.ipmi_cipher:
  368. params["cipher"] = self.ipmi_cipher
  369. if self.ipmi_level:
  370. params["privlvl"] = self.ipmi_level
  371. elif driver_proto in {'staging-ovirt'}:
  372. # fence_rhevm
  373. node_data["agent"] = "fence_rhevm"
  374. params["ipaddr"] = node["pm_addr"]
  375. params["passwd"] = node["pm_password"]
  376. params["login"] = node["pm_user"]
  377. params["port"] = node["pm_vm_name"]
  378. params["ssl"] = 1
  379. params["ssl_insecure"] = 1
  380. if hostmap:
  381. params["pcmk_host_list"] = \
  382. hostmap[mac_addr]["compute_name"]
  383. if self.delay:
  384. params["delay"] = self.delay
  385. else:
  386. error = ("Unable to generate fencing parameters for %s" %
  387. node["pm_type"])
  388. raise ValueError(error)
  389. node_data["params"] = params
  390. devices.append(node_data)
  391. fence_params["FencingConfig"]["devices"] = devices
  392. return {"parameter_defaults": fence_params}
  393. class GetFlattenedParametersAction(GetParametersAction):
  394. """Get the heat stack tree and parameters in flattened structure.
  395. This method validates the stack of the container and returns the
  396. parameters and the heat stack tree. The heat stack tree is flattened
  397. for easy consumption.
  398. """
  399. def __init__(self, container=constants.DEFAULT_CONTAINER_NAME):
  400. super(GetFlattenedParametersAction, self).__init__(container)
  401. def run(self, context):
  402. # process all plan files and create or update a stack
  403. processed_data = super(GetFlattenedParametersAction, self).run(context)
  404. # If we receive a 'Result' instance it is because the parent action
  405. # had an error.
  406. if isinstance(processed_data, actions.Result):
  407. return processed_data
  408. if processed_data['heat_resource_tree']:
  409. flattened = {'resources': {}, 'parameters': {}}
  410. _flat_it(flattened, 'Root',
  411. processed_data['heat_resource_tree'])
  412. processed_data['heat_resource_tree'] = flattened
  413. return processed_data
  414. def _process_params(flattened, params):
  415. for item in params:
  416. if item not in flattened['parameters']:
  417. param_obj = {}
  418. for key, value in params.get(item).items():
  419. camel_case_key = key[0].lower() + key[1:]
  420. param_obj[camel_case_key] = value
  421. param_obj['name'] = item
  422. flattened['parameters'][item] = param_obj
  423. return list(params)
  424. def _flat_it(flattened, name, data):
  425. key = str(uuid.uuid4())
  426. value = {}
  427. value.update({
  428. 'name': name,
  429. 'id': key
  430. })
  431. if 'Type' in data:
  432. value['type'] = data['Type']
  433. if 'Description' in data:
  434. value['description'] = data['Description']
  435. if 'Parameters' in data:
  436. value['parameters'] = _process_params(flattened,
  437. data['Parameters'])
  438. if 'ParameterGroups' in data:
  439. value['parameter_groups'] = data['ParameterGroups']
  440. if 'NestedParameters' in data:
  441. nested = data['NestedParameters']
  442. nested_ids = []
  443. for nested_key in nested.keys():
  444. nested_data = _flat_it(flattened, nested_key,
  445. nested.get(nested_key))
  446. # nested_data will always have one key (and only one)
  447. nested_ids.append(list(nested_data)[0])
  448. value['resources'] = nested_ids
  449. flattened['resources'][key] = value
  450. return {key: value}
  451. class GetProfileOfFlavorAction(base.TripleOAction):
  452. """Gets the profile name for a given flavor name.
  453. Need flavor object to get profile name since get_keys method is
  454. not available for external access. so we have created an action
  455. to get profile name from flavor name.
  456. :param flavor_name: Flavor name
  457. :return: profile name
  458. """
  459. def __init__(self, flavor_name):
  460. super(GetProfileOfFlavorAction, self).__init__()
  461. self.flavor_name = flavor_name
  462. def run(self, context):
  463. compute_client = self.get_compute_client(context)
  464. try:
  465. return parameter_utils.get_profile_of_flavor(self.flavor_name,
  466. compute_client)
  467. except exception.DeriveParamsError as err:
  468. LOG.error('Derive Params Error: %s', err)
  469. return actions.Result(error=str(err))
  470. class RotateFernetKeysAction(GetPasswordsAction):
  471. """Rotate fernet keys from the environment
  472. This method rotates the fernet keys that are saved in the environment, in
  473. the passwords parameter.
  474. """
  475. def __init__(self, container=constants.DEFAULT_CONTAINER_NAME):
  476. super(RotateFernetKeysAction, self).__init__()
  477. self.container = container
  478. def run(self, context):
  479. swift = self.get_object_client(context)
  480. try:
  481. env = plan_utils.get_env(swift, self.container)
  482. except swiftexceptions.ClientException as err:
  483. err_msg = ("Error retrieving environment for plan %s: %s" % (
  484. self.container, err))
  485. LOG.exception(err_msg)
  486. return actions.Result(error=err_msg)
  487. parameter_defaults = env.get('parameter_defaults', {})
  488. passwords = self._get_overriden_passwords(env.get('passwords', {}),
  489. parameter_defaults)
  490. next_index = self.get_next_index(passwords['KeystoneFernetKeys'])
  491. keys_map = self.rotate_keys(passwords['KeystoneFernetKeys'],
  492. next_index)
  493. max_keys = self.get_max_keys_value(parameter_defaults)
  494. keys_map = self.purge_excess_keys(max_keys, keys_map)
  495. env['passwords']['KeystoneFernetKeys'] = keys_map
  496. try:
  497. plan_utils.put_env(swift, env)
  498. except swiftexceptions.ClientException as err:
  499. err_msg = "Error uploading to container: %s" % err
  500. LOG.exception(err_msg)
  501. return actions.Result(error=err_msg)
  502. self.cache_delete(context,
  503. self.container,
  504. "tripleo.parameters.get")
  505. return keys_map
  506. @staticmethod
  507. def get_key_index_from_path(path):
  508. return int(path[path.rfind('/') + 1:])
  509. def get_next_index(self, keys_map):
  510. return self.get_key_index_from_path(
  511. max(keys_map, key=self.get_key_index_from_path)) + 1
  512. def get_key_path(self, index):
  513. return password_utils.KEYSTONE_FERNET_REPO + str(index)
  514. def rotate_keys(self, keys_map, next_index):
  515. next_index_path = self.get_key_path(next_index)
  516. zero_index_path = self.get_key_path(0)
  517. # promote staged key to be new primary
  518. keys_map[next_index_path] = keys_map[zero_index_path]
  519. # Set new staged key
  520. keys_map[zero_index_path] = {
  521. 'content': password_utils.create_keystone_credential()}
  522. return keys_map
  523. def get_max_keys_value(self, parameter_defaults):
  524. # The number of max keys should always be positive. The minimum amount
  525. # of keys is 3.
  526. return max(parameter_defaults.get('KeystoneFernetMaxActiveKeys', 5), 3)
  527. def purge_excess_keys(self, max_keys, keys_map):
  528. current_repo_size = len(keys_map)
  529. if current_repo_size <= max_keys:
  530. return keys_map
  531. key_paths = sorted(keys_map.keys(), key=self.get_key_index_from_path)
  532. keys_to_be_purged = current_repo_size - max_keys
  533. for key_path in key_paths[1:keys_to_be_purged + 1]:
  534. del keys_map[key_path]
  535. return keys_map
  536. class GetNetworkConfigAction(base.TripleOAction):
  537. """Gets network configuration details from available heat parameters."""
  538. def __init__(self, role_name, container=constants.DEFAULT_CONTAINER_NAME):
  539. super(GetNetworkConfigAction, self).__init__()
  540. self.container = container
  541. self.role_name = role_name
  542. def run(self, context):
  543. process_templates_action = templates.ProcessTemplatesAction(
  544. container=self.container
  545. )
  546. processed_data = process_templates_action.run(context)
  547. # If we receive a 'Result' instance it is because the parent action
  548. # had an error.
  549. if isinstance(processed_data, actions.Result):
  550. return processed_data
  551. # stacks.preview method raises validation message if stack is
  552. # already deployed. here renaming container to get preview data.
  553. container_temp = self.container + "-TEMP"
  554. fields = {
  555. 'template': processed_data['template'],
  556. 'files': processed_data['files'],
  557. 'environment': processed_data['environment'],
  558. 'stack_name': container_temp,
  559. }
  560. orc = self.get_orchestration_client(context)
  561. preview_data = orc.stacks.preview(**fields)
  562. try:
  563. result = self.get_network_config(preview_data, container_temp,
  564. self.role_name)
  565. return result
  566. except exception.DeriveParamsError as err:
  567. LOG.exception('Derive Params Error: %s' % err)
  568. return actions.Result(error=str(err))
  569. def get_network_config(self, preview_data, stack_name, role_name):
  570. result = None
  571. if preview_data:
  572. for res in preview_data.resources:
  573. net_script = self.process_preview_list(res,
  574. stack_name,
  575. role_name)
  576. if net_script:
  577. ns_len = len(net_script)
  578. start_index = (net_script.find(
  579. "echo '{\"network_config\"", 0, ns_len) + 6)
  580. end_index = net_script.find("'", start_index, ns_len)
  581. if (end_index > start_index):
  582. net_config = net_script[start_index:end_index]
  583. if net_config:
  584. result = json.loads(net_config)
  585. break
  586. if not result:
  587. err_msg = ("Unable to determine network config for role '%s'."
  588. % self.role_name)
  589. raise exception.DeriveParamsError(err_msg)
  590. return result
  591. def process_preview_list(self, res, stack_name, role_name):
  592. if type(res) == list:
  593. for item in res:
  594. out = self.process_preview_list(item, stack_name, role_name)
  595. if out:
  596. return out
  597. elif type(res) == dict:
  598. res_stack_name = stack_name + '-' + role_name
  599. if res['resource_name'] == "OsNetConfigImpl" and \
  600. res['resource_identity'] and \
  601. res_stack_name in res['resource_identity']['stack_name']:
  602. return res['properties']['config']
  603. return None