1 ##############################################################################
2 # Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others.
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
18 from apex.virtual import utils as virt_utils
19 from apex.virtual import configure_vm as vm_lib
20 from apex.common import constants
21 from apex.common import utils
24 class ApexUndercloudException(Exception):
30 This class represents an Apex Undercloud VM
32 def __init__(self, image_path, template_path,
33 root_pw=None, external_network=False,
34 image_name='undercloud.qcow2',
35 os_version=constants.DEFAULT_OS_VERSION):
37 self.os_version = os_version
38 self.root_pw = root_pw
39 self.external_net = external_network
40 self.volume = os.path.join(constants.LIBVIRT_VOLUME_PATH,
42 self.image_path = image_path
43 self.image_name = image_name
44 self.template_path = template_path
46 if Undercloud._get_vm():
47 logging.error("Undercloud VM already exists. Please clean "
49 raise ApexUndercloudException("Undercloud VM already exists!")
54 conn = libvirt.open('qemu:///system')
56 vm = conn.lookupByName('undercloud')
58 except libvirt.libvirtError:
59 logging.debug("No undercloud VM exists")
64 networks.append('external')
65 console = 'ttyAMA0' if platform.machine() == 'aarch64' else 'ttyS0'
66 root = 'vda' if platform.machine() == 'aarch64' else 'sda'
68 self.vm = vm_lib.create_vm(name='undercloud',
70 baremetal_interfaces=networks,
71 direct_boot='overcloud-full',
72 kernel_args=['console={}'.format(console),
73 'root=/dev/{}'.format(root)],
75 template_dir=self.template_path)
78 self._update_delorean_repo()
81 ip_out = self.vm.interfaceAddresses(
82 libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE, 0)
84 for (name, val) in ip_out.items():
85 for ipaddr in val['addrs']:
86 if ipaddr['type'] == libvirt.VIR_IP_ADDR_TYPE_IPV4:
87 self.ip = ipaddr['addr']
95 if self.vm.isActive():
96 logging.info("Undercloud already started")
98 logging.info("Starting undercloud")
100 # give 10 seconds to come up
105 logging.info("Undercloud started. IP Address: {}".format(
108 logging.debug("Did not find undercloud IP in {} "
109 "attempts...".format(x))
112 logging.error("Cannot find IP for Undercloud")
113 raise ApexUndercloudException(
114 "Unable to find IP for undercloud. Check if VM booted "
117 def configure(self, net_settings, deploy_settings,
118 playbook, apex_temp_dir, virtual_oc=False):
120 Configures undercloud VM
121 :param net_settings: Network settings for deployment
122 :param deploy_settings: Deployment settings for deployment
123 :param playbook: playbook to use to configure undercloud
124 :param apex_temp_dir: temporary apex directory to hold configs/logs
125 :param virtual_oc: Boolean to determine if overcloud is virt
129 logging.info("Configuring Undercloud...")
131 ansible_vars = Undercloud.generate_config(net_settings,
133 ansible_vars['apex_temp_dir'] = apex_temp_dir
134 ansible_vars['virtual_overcloud'] = virtual_oc
136 utils.run_ansible(ansible_vars, playbook, host=self.ip,
138 except subprocess.CalledProcessError:
140 "Failed to install undercloud..."
141 "please check log: {}".format(os.path.join(
142 apex_temp_dir, 'apex-undercloud-install.log')))
143 raise ApexUndercloudException('Failed to install undercloud')
144 logging.info("Undercloud installed!")
146 def setup_volumes(self):
147 for img_file in ('overcloud-full.vmlinuz', 'overcloud-full.initrd',
149 src_img = os.path.join(self.image_path, img_file)
150 if img_file == self.image_name:
151 dest_img = os.path.join(constants.LIBVIRT_VOLUME_PATH,
154 dest_img = os.path.join(constants.LIBVIRT_VOLUME_PATH,
156 if not os.path.isfile(src_img):
157 raise ApexUndercloudException(
158 "Required source file does not exist:{}".format(src_img))
159 if os.path.exists(dest_img):
161 shutil.copyfile(src_img, dest_img)
162 shutil.chown(dest_img, user='qemu', group='qemu')
163 os.chmod(dest_img, 0o0744)
164 # TODO(trozet):check if resize needed right now size is 50gb
165 # there is a lib called vminspect which has some dependencies and is
166 # not yet available in pip. Consider switching to this lib later.
168 def inject_auth(self):
170 # virt-customize keys/pws
172 pw_op = "password:{}".format(self.root_pw)
173 virt_ops.append({constants.VIRT_PW: pw_op})
175 virt_ops.append({constants.VIRT_RUN_CMD:
176 'mkdir -p /root/.ssh'})
177 virt_ops.append({constants.VIRT_UPLOAD:
178 '/root/.ssh/id_rsa.pub:/root/.ssh/authorized_keys'})
180 'chmod 600 /root/.ssh/authorized_keys',
181 'restorecon /root/.ssh/authorized_keys',
182 'cp /root/.ssh/authorized_keys /home/stack/.ssh/',
183 'chown stack:stack /home/stack/.ssh/authorized_keys',
184 'chmod 600 /home/stack/.ssh/authorized_keys'
187 virt_ops.append({constants.VIRT_RUN_CMD: cmd})
188 virt_utils.virt_customize(virt_ops, self.volume)
191 def generate_config(ns, ds):
193 Generates a dictionary of settings for configuring undercloud
194 :param ns: network settings to derive undercloud settings
195 :param ds: deploy settings to derive undercloud settings
196 :return: dictionary of settings
199 ns_admin = ns['networks']['admin']
200 intro_range = ns['apex']['networks']['admin']['introspection_range']
202 # Check if this is an ARM deployment
203 config['aarch64'] = platform.machine() == 'aarch64'
204 # Configuration for undercloud.conf
205 config['undercloud_config'] = [
207 "undercloud_update_packages false",
208 "undercloud_debug false",
209 "inspection_extras false",
210 "ipxe_enabled {}".format(
211 str(ds['global_params'].get('ipxe', True) and
212 not config['aarch64'])),
213 "undercloud_hostname undercloud.{}".format(ns['dns-domain']),
214 "local_ip {}/{}".format(str(ns_admin['installer_vm']['ip']),
215 str(ns_admin['cidr']).split('/')[1]),
216 "network_gateway {}".format(str(ns_admin['installer_vm']['ip'])),
217 "network_cidr {}".format(str(ns_admin['cidr'])),
218 "dhcp_start {}".format(str(ns_admin['dhcp_range'][0])),
219 "dhcp_end {}".format(str(ns_admin['dhcp_range'][1])),
220 "inspection_iprange {}".format(','.join(intro_range))
223 config['ironic_config'] = [
224 "disk_utils iscsi_verify_attempts 30",
225 "disk_partitioner check_device_max_retries 40"
228 config['nova_config'] = [
229 "dns_domain {}".format(ns['dns-domain']),
230 "dhcp_domain {}".format(ns['dns-domain'])
233 config['neutron_config'] = [
234 "dns_domain {}".format(ns['dns-domain']),
236 # FIXME(trozet): possible bug here with not using external network
237 ns_external = ns['networks']['external'][0]
238 config['external_network'] = {
239 "vlan": ns_external['installer_vm']['vlan'],
240 "ip": ns_external['installer_vm']['ip'],
241 "prefix": str(ns_external['cidr']).split('/')[1],
242 "enabled": ns_external['enabled']
244 # TODO(trozet): clean this logic up and merge with above
245 if 'external' in ns.enabled_network_list:
246 nat_cidr = ns_external['cidr']
248 nat_cidr = ns['networks']['admin']['cidr']
249 config['nat_cidr'] = str(nat_cidr)
250 if nat_cidr.version == 6:
251 config['nat_network_ipv6'] = True
253 config['nat_network_ipv6'] = False
254 config['http_proxy'] = ns.get('http_proxy', '')
255 config['https_proxy'] = ns.get('https_proxy', '')
259 def _update_delorean_repo(self):
260 if utils.internet_connectivity():
261 logging.info('Updating delorean repo on Undercloud')
263 "https://trunk.rdoproject.org/centos7-{}"
264 "/current-tripleo/delorean.repo".format(self.os_version))
265 cmd = ("curl -L -f -o "
266 "/etc/yum.repos.d/deloran.repo {}".format(delorean_repo))
268 virt_utils.virt_customize({constants.VIRT_RUN_CMD: cmd},
271 logging.warning("Failed to download and update delorean repo "