python utility to manage a tripleo based cloud
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.

undercloud_config.py 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. # Copyright 2015 Red Hat, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # 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, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. #
  15. """Plugin action implementation"""
  16. import json
  17. import logging
  18. import netaddr
  19. import os
  20. import shutil
  21. import sys
  22. from cryptography import x509
  23. from cryptography.hazmat.backends import default_backend
  24. from cryptography.hazmat.primitives import serialization
  25. from jinja2 import Environment
  26. from jinja2 import FileSystemLoader
  27. from jinja2 import meta
  28. from osc_lib.i18n import _
  29. from oslo_config import cfg
  30. from tripleo_common.image import kolla_builder
  31. from tripleoclient.config.undercloud import load_global_config
  32. from tripleoclient.config.undercloud import UndercloudConfig
  33. from tripleoclient import constants
  34. from tripleoclient import exceptions
  35. from tripleoclient import utils
  36. from tripleoclient.v1 import undercloud_preflight
  37. # Provides mappings for some of the instack_env tags to undercloud heat
  38. # params or undercloud.conf opts known here (as a fallback), needed to maintain
  39. # feature parity with instack net config override templates.
  40. # TODO(bogdando): all of the needed mappings should be wired-in, eventually
  41. INSTACK_NETCONF_MAPPING = {
  42. 'LOCAL_INTERFACE': 'local_interface',
  43. 'LOCAL_IP': 'local_ip',
  44. 'LOCAL_MTU': 'UndercloudLocalMtu',
  45. 'PUBLIC_INTERFACE_IP': 'undercloud_public_host', # can't be 'CloudName'
  46. 'UNDERCLOUD_NAMESERVERS': 'undercloud_nameservers',
  47. 'SUBNETS_STATIC_ROUTES': 'ControlPlaneStaticRoutes',
  48. }
  49. MULTI_PARAMETER_MAPPING = {
  50. 'ipxe_enabled': ['IronicIPXEEnabled', 'IronicInspectorIPXEEnabled']
  51. }
  52. PARAMETER_MAPPING = {
  53. 'inspection_interface': 'IronicInspectorInterface',
  54. 'undercloud_debug': 'Debug',
  55. 'certificate_generation_ca': 'CertmongerCA',
  56. 'undercloud_public_host': 'CloudName',
  57. 'scheduler_max_attempts': 'NovaSchedulerMaxAttempts',
  58. 'local_mtu': 'UndercloudLocalMtu',
  59. 'clean_nodes': 'IronicAutomatedClean',
  60. 'upgrade_cleanup': 'UpgradeRemoveUnusedPackages',
  61. 'container_healthcheck_disabled': 'ContainerHealthcheckDisabled',
  62. 'local_subnet': 'UndercloudCtlplaneLocalSubnet',
  63. 'enable_routed_networks': 'UndercloudEnableRoutedNetworks',
  64. 'local_interface': 'NeutronPublicInterface',
  65. }
  66. SUBNET_PARAMETER_MAPPING = {
  67. 'cidr': 'NetworkCidr',
  68. 'gateway': 'NetworkGateway',
  69. 'host_routes': 'HostRoutes'
  70. }
  71. THT_HOME = os.environ.get('THT_HOME',
  72. "/usr/share/openstack-tripleo-heat-templates/")
  73. USER_HOME = os.environ.get('HOME', '')
  74. TELEMETRY_DOCKER_ENV_YAML = [
  75. 'environments/services/undercloud-gnocchi.yaml',
  76. 'environments/services/undercloud-aodh.yaml',
  77. 'environments/services/undercloud-panko.yaml',
  78. 'environments/services/undercloud-ceilometer.yaml']
  79. CONF = cfg.CONF
  80. # When adding new options to the lists below, make sure to regenerate the
  81. # sample config by running "tox -e genconfig" in the project root.
  82. ci_defaults = kolla_builder.container_images_prepare_defaults()
  83. config = UndercloudConfig()
  84. # Routed subnets
  85. _opts = config.get_opts()
  86. load_global_config()
  87. def _load_subnets_config_groups():
  88. for group in CONF.subnets:
  89. g = cfg.OptGroup(name=group, title=group)
  90. if group == CONF.local_subnet:
  91. CONF.register_opts(config.get_local_subnet_opts(), group=g)
  92. else:
  93. CONF.register_opts(config.get_remote_subnet_opts(), group=g)
  94. LOG = logging.getLogger(__name__ + ".undercloud_config")
  95. def _get_jinja_env_source(f):
  96. path, filename = os.path.split(f)
  97. env = Environment(loader=FileSystemLoader(path))
  98. src = env.loader.get_source(env, filename)[0]
  99. return (env, src)
  100. def _get_unknown_instack_tags(env, src):
  101. found_tags = set(meta.find_undeclared_variables(env.parse(src)))
  102. known_tags = set(INSTACK_NETCONF_MAPPING.keys())
  103. if found_tags <= known_tags:
  104. return (', ').join(found_tags - known_tags)
  105. else:
  106. return None
  107. def _process_drivers_and_hardware_types(conf, env):
  108. """Populate the environment with ironic driver information."""
  109. # Ensure correct rendering of the list and uniqueness of the items
  110. enabled_hardware_types = set(conf.enabled_hardware_types)
  111. if conf.enable_node_discovery:
  112. if conf.discovery_default_driver not in enabled_hardware_types:
  113. enabled_hardware_types.add(conf.discovery_default_driver)
  114. env['IronicInspectorEnableNodeDiscovery'] = True
  115. env['IronicInspectorDiscoveryDefaultDriver'] = (
  116. conf.discovery_default_driver)
  117. # In most cases power and management interfaces are called the same, so we
  118. # use one variable for them.
  119. mgmt_interfaces = {'fake', 'ipmitool'}
  120. # TODO(dtantsur): can we somehow avoid hardcoding hardware types here?
  121. for hw_type in ('redfish', 'idrac', 'ilo', 'irmc', 'staging-ovirt',
  122. 'xclarity'):
  123. if hw_type in enabled_hardware_types:
  124. mgmt_interfaces.add(hw_type)
  125. for (hw_type, iface) in [('cisco-ucs-managed', 'ucsm'),
  126. ('cisco-ucs-standalone', 'cimc')]:
  127. if hw_type in enabled_hardware_types:
  128. mgmt_interfaces.add(iface)
  129. bios_interfaces = {'no-bios'}
  130. for hw_type in ['ilo', 'irmc', 'redfish']:
  131. if hw_type in enabled_hardware_types:
  132. bios_interfaces.add(hw_type)
  133. # Two hardware types use non-default boot interfaces.
  134. boot_interfaces = {'ipxe', 'pxe'}
  135. for hw_type in ('ilo', 'irmc'):
  136. if hw_type in enabled_hardware_types:
  137. boot_interfaces.add('%s-pxe' % hw_type)
  138. inspect_interfaces = {'inspector', 'no-inspect'}
  139. for hw_type in ('redfish', 'idrac', 'ilo', 'irmc'):
  140. if hw_type in enabled_hardware_types:
  141. inspect_interfaces.add(hw_type)
  142. raid_interfaces = {'no-raid'}
  143. if 'idrac' in enabled_hardware_types:
  144. raid_interfaces.add('idrac')
  145. vendor_interfaces = {'no-vendor'}
  146. for (hw_type, iface) in [('ipmi', 'ipmitool'),
  147. ('idrac', 'idrac')]:
  148. if hw_type in enabled_hardware_types:
  149. vendor_interfaces.add(iface)
  150. power_interfaces = mgmt_interfaces.copy()
  151. # The snmp hardware type uses noop management and snmp power; noop
  152. # management is also used by ipmi and staging hardware types.
  153. mgmt_interfaces.add('noop')
  154. if 'snmp' in enabled_hardware_types:
  155. power_interfaces.add('snmp')
  156. env['IronicEnabledHardwareTypes'] = sorted(enabled_hardware_types)
  157. env['IronicEnabledBiosInterfaces'] = sorted(bios_interfaces)
  158. env['IronicEnabledBootInterfaces'] = sorted(boot_interfaces)
  159. env['IronicEnabledInspectInterfaces'] = sorted(inspect_interfaces)
  160. env['IronicEnabledManagementInterfaces'] = sorted(mgmt_interfaces)
  161. env['IronicEnabledPowerInterfaces'] = sorted(power_interfaces)
  162. env['IronicEnabledRaidInterfaces'] = sorted(raid_interfaces)
  163. env['IronicEnabledVendorInterfaces'] = sorted(vendor_interfaces)
  164. def _process_ipa_args(conf, env):
  165. """Populate the environment with IPA kernal args ."""
  166. inspection_kernel_args = []
  167. if conf.undercloud_debug:
  168. inspection_kernel_args.append('ipa-debug=1')
  169. if conf.inspection_runbench:
  170. inspection_kernel_args.append('ipa-inspection-benchmarks=cpu,mem,disk')
  171. if conf.inspection_extras:
  172. inspection_kernel_args.append('ipa-inspection-dhcp-all-interfaces=1')
  173. inspection_kernel_args.append('ipa-collect-lldp=1')
  174. env['IronicInspectorCollectors'] = ('default,extra-hardware,'
  175. 'numa-topology,logs')
  176. else:
  177. env['IronicInspectorCollectors'] = 'default,logs'
  178. env['IronicInspectorKernelArgs'] = ' '.join(inspection_kernel_args)
  179. def _generate_inspection_subnets():
  180. env_list = []
  181. for subnet in CONF.subnets:
  182. env_dict = {}
  183. s = CONF.get(subnet)
  184. env_dict['tag'] = subnet
  185. env_dict['ip_range'] = s.inspection_iprange
  186. env_dict['netmask'] = str(netaddr.IPNetwork(s.cidr).netmask)
  187. env_dict['gateway'] = s.gateway
  188. env_dict['host_routes'] = s.host_routes
  189. env_list.append(env_dict)
  190. return env_list
  191. def _generate_subnets_static_routes():
  192. env_list = []
  193. local_router = CONF.get(CONF.local_subnet).gateway
  194. for subnet in CONF.subnets:
  195. if subnet == str(CONF.local_subnet):
  196. continue
  197. s = CONF.get(subnet)
  198. env_list.append({'ip_netmask': s.cidr, 'next_hop': local_router})
  199. for route in CONF.get(CONF.local_subnet).host_routes:
  200. env_list.append({'ip_netmask': route['destination'],
  201. 'next_hop': route['nexthop']})
  202. return env_list
  203. def _generate_masquerade_networks():
  204. """Create input for OS::TripleO::Services::MasqueradeNetworks
  205. The service use parameter MasqueradeNetworks with the following
  206. formating:
  207. {'source_cidr_A': ['destination_cidr_A', 'destination_cidr_B'],
  208. 'source_cidr_B': ['destination_cidr_A', 'destination_cidr_B']}
  209. """
  210. network_cidrs = []
  211. for subnet in CONF.subnets:
  212. s = CONF.get(subnet)
  213. network_cidrs.append(s.cidr)
  214. masqurade_networks = {}
  215. for subnet in CONF.subnets:
  216. s = CONF.get(subnet)
  217. if s.masquerade:
  218. masqurade_networks.update({s.cidr: network_cidrs})
  219. return masqurade_networks
  220. def _calculate_allocation_pools(subnet):
  221. """Calculate subnet allocation pools
  222. Remove the gateway address, the inspection IP range and the undercloud IP's
  223. from the subnets full IP range and return all remaining address ranges as
  224. allocation pools. If dhcp_start and/or dhcp_end is defined, also remove
  225. addresses before dhcp_start and addresses after dhcp_end.
  226. """
  227. ip_network = netaddr.IPNetwork(subnet.cidr)
  228. # NOTE(hjensas): Ignore the default dhcp_start and dhcp_end if cidr is not
  229. # the default as well. I.e allow not specifying dhcp_start and dhcp_end.
  230. if (subnet.cidr != constants.CTLPLANE_CIDR_DEFAULT
  231. and subnet.dhcp_start == constants.CTLPLANE_DHCP_START_DEFAULT
  232. and subnet.dhcp_end == constants.CTLPLANE_DHCP_END_DEFAULT):
  233. subnet.dhcp_start, subnet.dhcp_end = None, None
  234. if subnet.dhcp_start and subnet.dhcp_end:
  235. ip_set = netaddr.IPSet()
  236. for a, b in zip(subnet.dhcp_start, subnet.dhcp_end):
  237. ip_set.add(netaddr.IPRange(netaddr.IPAddress(a),
  238. netaddr.IPAddress(b)))
  239. else:
  240. ip_set = netaddr.IPSet(ip_network)
  241. # Remove addresses before dhcp_start if defined
  242. if subnet.dhcp_start:
  243. a = netaddr.IPAddress(ip_network.first)
  244. b = netaddr.IPAddress(subnet.dhcp_start[0]) - 1
  245. ip_set.remove(netaddr.IPRange(a, b))
  246. # Remove addresses after dhcp_end if defined
  247. if subnet.dhcp_end:
  248. a = netaddr.IPAddress(subnet.dhcp_end[0]) + 1
  249. b = netaddr.IPAddress(ip_network.last)
  250. ip_set.remove(netaddr.IPRange(a, b))
  251. # Remove network address and broadcast address
  252. ip_set.remove(ip_network.first)
  253. ip_set.remove(ip_network.last)
  254. # Remove gateway, local_ip, admin_host and public_host addresses
  255. ip_set.remove(netaddr.IPAddress(subnet.get('gateway')))
  256. ip_set.remove(netaddr.IPNetwork(CONF.local_ip).ip)
  257. ip_set.remove(netaddr.IPNetwork(utils.get_single_ip(
  258. CONF.undercloud_admin_host)))
  259. ip_set.remove(netaddr.IPNetwork(utils.get_single_ip(
  260. CONF.undercloud_public_host)))
  261. # Remove addresses in the inspection_iprange
  262. inspect_start, inspect_end = subnet.get('inspection_iprange').split(',')
  263. ip_set.remove(netaddr.IPRange(inspect_start, inspect_end))
  264. # Remove dhcp_exclude addresses and ip ranges
  265. for exclude in subnet.dhcp_exclude:
  266. if '-' in exclude:
  267. exclude_start, exclude_end = exclude.split('-')
  268. ip_set.remove(netaddr.IPRange(exclude_start, exclude_end))
  269. else:
  270. ip_set.remove(netaddr.IPAddress(exclude))
  271. return [{'start': netaddr.IPAddress(ip_range.first).format(),
  272. 'end': netaddr.IPAddress(ip_range.last).format()}
  273. for ip_range in list(ip_set.iter_ipranges())]
  274. def _process_network_args(env):
  275. """Populate the environment with network configuration."""
  276. env['IronicInspectorSubnets'] = _generate_inspection_subnets()
  277. env['ControlPlaneStaticRoutes'] = _generate_subnets_static_routes()
  278. env['UndercloudCtlplaneSubnets'] = {}
  279. for subnet in CONF.subnets:
  280. s = CONF.get(subnet)
  281. env['UndercloudCtlplaneSubnets'][subnet] = {
  282. 'AllocationPools': _calculate_allocation_pools(s)
  283. }
  284. for param_key, param_value in SUBNET_PARAMETER_MAPPING.items():
  285. if param_value:
  286. env['UndercloudCtlplaneSubnets'][subnet].update(
  287. {param_value: s[param_key]})
  288. env['MasqueradeNetworks'] = _generate_masquerade_networks()
  289. if len(CONF['undercloud_nameservers']) > 5:
  290. raise exceptions.InvalidConfiguration('Too many nameservers provided. '
  291. 'Please provide less than 6 '
  292. 'servers in undercloud_'
  293. 'nameservers.')
  294. env['DnsServers'] = ','.join(CONF['undercloud_nameservers'])
  295. def prepare_undercloud_deploy(upgrade=False, no_validations=False,
  296. verbose_level=1, yes=False,
  297. force_stack_update=False, dry_run=False):
  298. """Prepare Undercloud deploy command based on undercloud.conf"""
  299. env_data = {}
  300. registry_overwrites = {}
  301. deploy_args = []
  302. # Fetch configuration and use its log file param to add logging to a file
  303. utils.load_config(CONF, constants.UNDERCLOUD_CONF_PATH)
  304. utils.configure_logging(LOG, verbose_level, CONF['undercloud_log_file'])
  305. _load_subnets_config_groups()
  306. # NOTE(bogdando): the generated env files are stored another path then
  307. # picked up later.
  308. # NOTE(aschultz): We copy this into the tht root that we save because
  309. # we move any user provided environment files into this root later.
  310. tempdir = os.path.join(os.path.abspath(CONF['output_dir']),
  311. 'tripleo-config-generated-env-files')
  312. if not os.path.isdir(tempdir):
  313. os.mkdir(tempdir)
  314. # Set the undercloud home dir parameter so that stackrc is produced in
  315. # the users home directory.
  316. env_data['UndercloudHomeDir'] = USER_HOME
  317. env_data['PythonInterpreter'] = sys.executable
  318. env_data['ContainerImagePrepareDebug'] = CONF['undercloud_debug']
  319. for param_key, param_value in PARAMETER_MAPPING.items():
  320. if param_key in CONF.keys():
  321. env_data[param_value] = CONF[param_key]
  322. # Some undercloud config options need to tweak multiple template parameters
  323. for undercloud_key in MULTI_PARAMETER_MAPPING:
  324. for env_value in MULTI_PARAMETER_MAPPING[undercloud_key]:
  325. if undercloud_key in CONF.keys():
  326. env_data[env_value] = CONF[undercloud_key]
  327. # Set up parameters for undercloud networking
  328. _process_network_args(env_data)
  329. # Parse the undercloud.conf options to include necessary args and
  330. # yaml files for undercloud deploy command
  331. if CONF.get('undercloud_enable_selinux'):
  332. env_data['SELinuxMode'] = 'enforcing'
  333. else:
  334. env_data['SELinuxMode'] = 'permissive'
  335. if CONF.get('undercloud_ntp_servers', None):
  336. env_data['NtpServer'] = CONF['undercloud_ntp_servers']
  337. if CONF.get('undercloud_timezone', None):
  338. env_data['TimeZone'] = CONF['undercloud_timezone']
  339. else:
  340. env_data['TimeZone'] = utils.get_local_timezone()
  341. if CONF.get('enable_validations', False):
  342. env_data['UndercloudConfigFilePath'] = constants.UNDERCLOUD_CONF_PATH
  343. if not no_validations:
  344. env_data['EnableValidations'] = CONF['enable_validations']
  345. if CONF.get('overcloud_domain_name', None):
  346. env_data['NeutronDnsDomain'] = CONF['overcloud_domain_name']
  347. deploy_args.append('--local-domain=%s' % CONF['overcloud_domain_name'])
  348. env_data['DockerInsecureRegistryAddress'] = [
  349. '%s:8787' % CONF['local_ip'].split('/')[0]]
  350. env_data['DockerInsecureRegistryAddress'].append(
  351. '%s:8787' % CONF['undercloud_admin_host'])
  352. env_data['DockerInsecureRegistryAddress'].extend(
  353. CONF['container_insecure_registries'])
  354. env_data['ContainerCli'] = CONF['container_cli']
  355. # NOTE(aschultz): deprecated in Stein
  356. if CONF.get('docker_bip', None):
  357. env_data['DockerNetworkOptions'] = CONF['docker_bip']
  358. if CONF.get('container_registry_mirror', None):
  359. env_data['DockerRegistryMirror'] = CONF['container_registry_mirror']
  360. # This parameter the IP address used to bind the local container registry
  361. env_data['LocalContainerRegistry'] = CONF['local_ip'].split('/')[0]
  362. if CONF['additional_architectures']:
  363. # In queens (instack-undercloud) we used this to setup additional
  364. # architectures. For rocky+ we want to pass a list and be smarter in
  365. # THT. We can remove this in 'T' when we get there.
  366. for arch in CONF['additional_architectures']:
  367. env_data['EnableArchitecture%s' % arch.upper()] = True
  368. env_data['AdditionalArchitectures'] = \
  369. ','.join(CONF['additional_architectures'])
  370. if CONF.get('local_ip', None):
  371. deploy_args.append('--local-ip=%s' % CONF['local_ip'])
  372. if CONF.get('templates', None):
  373. tht_templates = CONF['templates']
  374. deploy_args.append('--templates=%s' % tht_templates)
  375. else:
  376. tht_templates = THT_HOME
  377. deploy_args.append('--templates=%s' % THT_HOME)
  378. if CONF.get('roles_file', constants.UNDERCLOUD_ROLES_FILE):
  379. deploy_args.append('--roles-file=%s' % CONF['roles_file'])
  380. if CONF.get('networks_file'):
  381. deploy_args.append('--networks-file=%s' % CONF['networks_file'])
  382. else:
  383. deploy_args.append('--networks-file=%s' %
  384. constants.UNDERCLOUD_NETWORKS_FILE)
  385. if yes:
  386. deploy_args += ['-y']
  387. if upgrade:
  388. deploy_args += [
  389. '--upgrade',
  390. '-e', os.path.join(
  391. tht_templates,
  392. "environments/lifecycle/undercloud-upgrade-prepare.yaml")]
  393. if not CONF.get('heat_native', False):
  394. deploy_args.append('--heat-native=False')
  395. else:
  396. deploy_args.append('--heat-native')
  397. if CONF.get('heat_container_image'):
  398. deploy_args.append('--heat-container-image=%s'
  399. % CONF['heat_container_image'])
  400. # These should be loaded first so we can override all the bits later
  401. deploy_args += [
  402. "-e", os.path.join(tht_templates, "environments/undercloud.yaml"),
  403. '-e', os.path.join(tht_templates, 'environments/use-dns-for-vips.yaml')
  404. ]
  405. # we want to load this environment after undercloud.yaml for precedence.
  406. if CONF.get('container_cli', 'podman') == 'podman':
  407. deploy_args += [
  408. '-e', os.path.join(tht_templates, 'environments/podman.yaml')
  409. ]
  410. # If a container images file is used, copy it into the tempdir to make it
  411. # later into other deployment artifacts and user-provided files.
  412. _container_images_config(CONF, deploy_args, env_data, tempdir)
  413. if env_data['MasqueradeNetworks']:
  414. deploy_args += ['-e', os.path.join(
  415. tht_templates, "environments/services/masquerade-networks.yaml")]
  416. if CONF.get('enable_ironic'):
  417. deploy_args += ['-e', os.path.join(
  418. tht_templates, "environments/services/ironic.yaml")]
  419. # ironic-inspector can only work if ironic is enabled
  420. if CONF.get('enable_ironic_inspector'):
  421. deploy_args += ['-e', os.path.join(
  422. tht_templates,
  423. "environments/services/ironic-inspector.yaml")]
  424. _process_drivers_and_hardware_types(CONF, env_data)
  425. _process_ipa_args(CONF, env_data)
  426. if CONF.get('enable_mistral'):
  427. deploy_args += ['-e', os.path.join(
  428. tht_templates, "environments/services/mistral.yaml")]
  429. if CONF.get('enable_novajoin'):
  430. deploy_args += ['-e', os.path.join(
  431. tht_templates, "environments/services/novajoin.yaml")]
  432. env_data['NovajoinIpaOtp'] = CONF['ipa_otp']
  433. if CONF.get('enable_zaqar'):
  434. deploy_args += ['-e', os.path.join(
  435. tht_templates, "environments/services/zaqar-swift-backend.yaml")]
  436. if CONF.get('enable_telemetry'):
  437. for env_file in TELEMETRY_DOCKER_ENV_YAML:
  438. deploy_args += ['-e', os.path.join(tht_templates, env_file)]
  439. if CONF.get('enable_ui'):
  440. deploy_args += ['-e', os.path.join(
  441. tht_templates, "environments/services/tripleo-ui.yaml")]
  442. if CONF.get('enable_cinder'):
  443. deploy_args += ['-e', os.path.join(
  444. tht_templates,
  445. "environments/services/undercloud-cinder.yaml")]
  446. if CONF.get('enable_tempest'):
  447. deploy_args += ['-e', os.path.join(
  448. tht_templates,
  449. "environments/services/tempest.yaml")]
  450. if CONF.get('enable_swift_encryption'):
  451. deploy_args += [
  452. '-e', os.path.join(tht_templates,
  453. "environments/services/barbican.yaml"),
  454. '-e', os.path.join(
  455. tht_templates,
  456. "environments/barbican-backend-simple-crypto.yaml")
  457. ]
  458. env_data['BarbicanSimpleCryptoGlobalDefault'] = True
  459. env_data['SwiftEncryptionEnabled'] = True
  460. if CONF.get('undercloud_service_certificate'):
  461. # We assume that the certificate is trusted
  462. env_data['InternalTLSCAFile'] = ''
  463. env_data.update(
  464. _get_public_tls_parameters(
  465. CONF.get('undercloud_service_certificate')))
  466. deploy_args += [
  467. '-e', os.path.join(tht_templates, 'environments/services/'
  468. 'undercloud-haproxy.yaml'),
  469. '-e', os.path.join(tht_templates, 'environments/services/'
  470. 'undercloud-keepalived.yaml')]
  471. elif CONF.get('generate_service_certificate'):
  472. deploy_args += ['-e', os.path.join(
  473. tht_templates,
  474. "environments/public-tls-undercloud.yaml")]
  475. else:
  476. deploy_args += ['-e', os.path.join(
  477. tht_templates,
  478. "environments/ssl/no-tls-endpoints-public-ip.yaml")]
  479. if (CONF.get('generate_service_certificate') or
  480. CONF.get('undercloud_service_certificate')):
  481. endpoint_environment = _get_tls_endpoint_environment(
  482. CONF.get('undercloud_public_host'), tht_templates)
  483. public_host = utils.get_single_ip(CONF.get('undercloud_public_host'))
  484. netaddr.IPAddress(public_host)
  485. deploy_args += ['--public-virtual-ip', public_host]
  486. # To make sure the resolved host is set to the right IP in /etc/hosts
  487. if not utils.is_valid_ip(CONF.get('undercloud_public_host')):
  488. extra_host = public_host + ' ' + CONF.get('undercloud_public_host')
  489. env_data['ExtraHostFileEntries'] = extra_host
  490. admin_host = utils.get_single_ip(CONF.get('undercloud_admin_host'))
  491. netaddr.IPAddress(admin_host)
  492. deploy_args += ['--control-virtual-ip', admin_host]
  493. deploy_args += [
  494. '-e', endpoint_environment,
  495. '-e', os.path.join(
  496. tht_templates,
  497. 'environments/services/undercloud-haproxy.yaml'),
  498. '-e', os.path.join(
  499. tht_templates,
  500. 'environments/services/undercloud-keepalived.yaml')]
  501. u = CONF.get('deployment_user') or utils.get_deployment_user()
  502. env_data['DeploymentUser'] = u
  503. # TODO(cjeanner) drop that once using oslo.privsep
  504. deploy_args += ['--deployment-user', u]
  505. deploy_args += ['--output-dir=%s' % CONF['output_dir']]
  506. if not os.path.isdir(CONF['output_dir']):
  507. os.mkdir(CONF['output_dir'])
  508. if CONF.get('cleanup'):
  509. deploy_args.append('--cleanup')
  510. if CONF.get('net_config_override', None):
  511. data_file = CONF['net_config_override']
  512. if os.path.abspath(data_file) != data_file:
  513. data_file = os.path.join(USER_HOME, data_file)
  514. if not os.path.exists(data_file):
  515. msg = _("Could not find net_config_override file '%s'") % data_file
  516. LOG.error(msg)
  517. raise RuntimeError(msg)
  518. # NOTE(bogdando): Process templated net config override data:
  519. # * get a list of used instack_env j2 tags (j2 vars, like {{foo}}),
  520. # * fetch values for the tags from the known mappins,
  521. # * raise, if there is unmatched tags left
  522. # * render the template into a JSON dict
  523. net_config_env, template_source = _get_jinja_env_source(data_file)
  524. unknown_tags = _get_unknown_instack_tags(net_config_env,
  525. template_source)
  526. if unknown_tags:
  527. msg = (_('Can not render net_config_override file {0} contains '
  528. 'unknown instack_env j2 tags: {1}').format(
  529. data_file, unknown_tags))
  530. LOG.error(msg)
  531. raise exceptions.DeploymentError(msg)
  532. # Create rendering context from the known to be present mappings for
  533. # identified instack_env tags to generated in env_data undercloud heat
  534. # params. Fall back to config opts, when env_data misses a param.
  535. context = {}
  536. for tag in INSTACK_NETCONF_MAPPING.keys():
  537. mapped_value = INSTACK_NETCONF_MAPPING[tag]
  538. if mapped_value in env_data.keys() or mapped_value in CONF.keys():
  539. try:
  540. context[tag] = CONF[mapped_value]
  541. except cfg.NoSuchOptError:
  542. context[tag] = env_data.get(mapped_value, None)
  543. # this returns a unicode string, convert it in into json
  544. net_config_str = net_config_env.get_template(
  545. os.path.split(data_file)[-1]).render(context).replace(
  546. "'", '"').replace('&quot;', '"')
  547. try:
  548. net_config_json = json.loads(net_config_str)
  549. except ValueError:
  550. net_config_json = json.loads("{%s}" % net_config_str)
  551. if 'network_config' not in net_config_json:
  552. msg = ('Unsupported data format in net_config_override '
  553. 'file %s: %s' % (data_file, net_config_str))
  554. LOG.error(msg)
  555. raise exceptions.DeploymentError(msg)
  556. env_data['UndercloudNetConfigOverride'] = net_config_json
  557. params_file = os.path.join(tempdir, 'undercloud_parameters.yaml')
  558. utils.write_env_file(env_data, params_file, registry_overwrites)
  559. deploy_args += ['-e', params_file]
  560. if CONF.get('hieradata_override', None):
  561. data_file = CONF['hieradata_override']
  562. if os.path.abspath(data_file) != data_file:
  563. data_file = os.path.join(USER_HOME, data_file)
  564. if not os.path.exists(data_file):
  565. msg = _("Could not find hieradata_override file '%s'") % data_file
  566. LOG.error(msg)
  567. raise RuntimeError(msg)
  568. deploy_args += ['--hieradata-override=%s' % data_file]
  569. if CONF.get('undercloud_hostname'):
  570. utils.set_hostname(CONF.get('undercloud_hostname'))
  571. if CONF.get('enable_validations') and not no_validations:
  572. utils.ansible_symlink()
  573. undercloud_preflight.check(verbose_level, upgrade)
  574. deploy_args += ['-e', os.path.join(
  575. tht_templates, "environments/tripleo-validations.yaml")]
  576. if CONF.get('custom_env_files'):
  577. for custom_file in CONF['custom_env_files']:
  578. deploy_args += ['-e', custom_file]
  579. if verbose_level > 1:
  580. deploy_args.append('--debug')
  581. deploy_args.append('--log-file=%s' % CONF['undercloud_log_file'])
  582. # Always add a drop-in for the ephemeral undercloud heat stack
  583. # virtual state tracking (the actual file will be created later)
  584. stack_vstate_dropin = os.path.join(
  585. tht_templates, 'undercloud-stack-vstate-dropin.yaml')
  586. deploy_args += ["-e", stack_vstate_dropin]
  587. if force_stack_update:
  588. deploy_args += ["--force-stack-update"]
  589. cmd = ["sudo", "--preserve-env", "openstack", "tripleo", "deploy",
  590. "--standalone", "--standalone-role", "Undercloud", "--stack",
  591. "undercloud"]
  592. cmd += deploy_args[:]
  593. # In dry-run, also report the expected heat stack virtual state/action
  594. if dry_run:
  595. stack_update_mark = os.path.join(
  596. constants.STANDALONE_EPHEMERAL_STACK_VSTATE,
  597. 'update_mark_undercloud')
  598. if os.path.isfile(stack_update_mark) or force_stack_update:
  599. LOG.warning(_('The heat stack undercloud virtual state/action '
  600. ' would be UPDATE'))
  601. return cmd
  602. def _get_tls_endpoint_environment(public_host, tht_templates):
  603. try:
  604. netaddr.IPAddress(public_host)
  605. return os.path.join(tht_templates,
  606. "environments/ssl/tls-endpoints-public-ip.yaml")
  607. except netaddr.core.AddrFormatError:
  608. return os.path.join(tht_templates,
  609. "environments/ssl/tls-endpoints-public-dns.yaml")
  610. def _get_public_tls_parameters(service_certificate_path):
  611. with open(service_certificate_path, "rb") as pem_file:
  612. pem_data = pem_file.read()
  613. cert = x509.load_pem_x509_certificate(pem_data, default_backend())
  614. private_key = serialization.load_pem_private_key(
  615. pem_data,
  616. password=None,
  617. backend=default_backend())
  618. key_pem = private_key.private_bytes(
  619. encoding=serialization.Encoding.PEM,
  620. format=serialization.PrivateFormat.TraditionalOpenSSL,
  621. encryption_algorithm=serialization.NoEncryption())
  622. cert_pem = cert.public_bytes(serialization.Encoding.PEM)
  623. return {
  624. 'SSLCertificate': cert_pem,
  625. 'SSLKey': key_pem
  626. }
  627. def _container_images_config(conf, deploy_args, env_data, tempdir):
  628. if conf.container_images_file:
  629. deploy_args += ['-e', conf.container_images_file]
  630. try:
  631. shutil.copy(os.path.abspath(conf.container_images_file), tempdir)
  632. except Exception:
  633. msg = _('Cannot copy a container images'
  634. 'file %s into a tempdir!') % conf.container_images_file
  635. LOG.error(msg)
  636. raise exceptions.DeploymentError(msg)
  637. else:
  638. # no images file was provided. Set a default ContainerImagePrepare
  639. # parameter to trigger the preparation of the required container list
  640. cip = kolla_builder.CONTAINER_IMAGE_PREPARE_PARAM
  641. env_data['ContainerImagePrepare'] = cip