Passing deploy_dir through to create_vm
[apex.git] / apex / undercloud / undercloud.py
1 ##############################################################################
2 # Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others.
3 #
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 ##############################################################################
9
10 import libvirt
11 import logging
12 import os
13 import shutil
14 import time
15
16 from apex.virtual import virtual_utils as virt_utils
17 from apex.virtual import configure_vm as vm_lib
18 from apex.common import constants
19 from apex.common import utils
20
21
22 class ApexUndercloudException(Exception):
23     pass
24
25
26 class Undercloud:
27     """
28     This class represents an Apex Undercloud VM
29     """
30     def __init__(self, image_path, template_path,
31                  root_pw=None, external_network=False):
32         self.ip = None
33         self.root_pw = root_pw
34         self.external_net = external_network
35         self.volume = os.path.join(constants.LIBVIRT_VOLUME_PATH,
36                                    'undercloud.qcow2')
37         self.image_path = image_path
38         self.template_path = template_path
39         self.vm = None
40         if Undercloud._get_vm():
41             logging.error("Undercloud VM already exists.  Please clean "
42                           "before creating")
43             raise ApexUndercloudException("Undercloud VM already exists!")
44         self.create()
45
46     @staticmethod
47     def _get_vm():
48         conn = libvirt.open('qemu:///system')
49         try:
50             vm = conn.lookupByName('undercloud')
51             return vm
52         except libvirt.libvirtError:
53             logging.debug("No undercloud VM exists")
54
55     def create(self):
56         networks = ['admin']
57         if self.external_net:
58             networks.append('external')
59         self.vm = vm_lib.create_vm(name='undercloud',
60                                    image=self.volume,
61                                    baremetal_interfaces=networks,
62                                    direct_boot='overcloud-full',
63                                    kernel_args=['console=ttyS0',
64                                                 'root=/dev/sda'],
65                                    default_network=True,
66                                    template_dir=self.template_path)
67         self.setup_volumes()
68         self.inject_auth()
69
70     def _set_ip(self):
71         ip_out = self.vm.interfaceAddresses(
72             libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE, 0)
73         if ip_out:
74             for (name, val) in ip_out.items():
75                 for ipaddr in val['addrs']:
76                     if ipaddr['type'] == libvirt.VIR_IP_ADDR_TYPE_IPV4:
77                         self.ip = ipaddr['addr']
78                         return True
79
80     def start(self):
81         """
82         Start Undercloud VM
83         :return: None
84         """
85         if self.vm.isActive():
86             logging.info("Undercloud already started")
87         else:
88             logging.info("Starting undercloud")
89             self.vm.create()
90             # give 10 seconds to come up
91             time.sleep(10)
92         # set IP
93         for x in range(5):
94             if self._set_ip():
95                 logging.info("Undercloud started.  IP Address: {}".format(
96                     self.ip))
97                 break
98             logging.debug("Did not find undercloud IP in {} "
99                           "attempts...".format(x))
100             time.sleep(10)
101         else:
102             logging.error("Cannot find IP for Undercloud")
103             raise ApexUndercloudException(
104                 "Unable to find IP for undercloud.  Check if VM booted "
105                 "correctly")
106
107     def configure(self, net_settings, playbook, apex_temp_dir):
108         """
109         Configures undercloud VM
110         :return:
111         """
112         # TODO(trozet): If undercloud install fails we can add a retry
113         logging.info("Configuring Undercloud...")
114         # run ansible
115         ansible_vars = Undercloud.generate_config(net_settings)
116         ansible_vars['apex_temp_dir'] = apex_temp_dir
117         utils.run_ansible(ansible_vars, playbook, host=self.ip, user='stack')
118         logging.info("Undercloud installed!")
119
120     def setup_volumes(self):
121         for img_file in ('overcloud-full.vmlinuz', 'overcloud-full.initrd',
122                          'undercloud.qcow2'):
123             src_img = os.path.join(self.image_path, img_file)
124             dest_img = os.path.join(constants.LIBVIRT_VOLUME_PATH, img_file)
125             if not os.path.isfile(src_img):
126                 raise ApexUndercloudException(
127                     "Required source file does not exist:{}".format(src_img))
128             if os.path.exists(dest_img):
129                 os.remove(dest_img)
130             shutil.copyfile(src_img, dest_img)
131
132         # TODO(trozet):check if resize needed right now size is 50gb
133         # there is a lib called vminspect which has some dependencies and is
134         # not yet available in pip.  Consider switching to this lib later.
135         # execute ansible playbook
136
137     def inject_auth(self):
138         virt_ops = list()
139         # virt-customize keys/pws
140         if self.root_pw:
141             pw_op = "password:{}".format(self.root_pw)
142             virt_ops.append({constants.VIRT_PW: pw_op})
143         # ssh key setup
144         virt_ops.append({constants.VIRT_RUN_CMD:
145                         'mkdir -p /root/.ssh'})
146         virt_ops.append({constants.VIRT_UPLOAD:
147                          '/root/.ssh/id_rsa.pub:/root/.ssh/authorized_keys'})
148         run_cmds = [
149             'chmod 600 /root/.ssh/authorized_keys',
150             'restorecon /root/.ssh/authorized_keys',
151             'cp /root/.ssh/authorized_keys /home/stack/.ssh/',
152             'chown stack:stack /home/stack/.ssh/authorized_keys',
153             'chmod 600 /home/stack/.ssh/authorized_keys'
154         ]
155         for cmd in run_cmds:
156             virt_ops.append({constants.VIRT_RUN_CMD: cmd})
157         virt_utils.virt_customize(virt_ops, self.volume)
158
159     @staticmethod
160     def generate_config(ns):
161         """
162         Generates a dictionary of settings for configuring undercloud
163         :param ns: network settings to derive undercloud settings
164         :return: dictionary of settings
165         """
166
167         ns_admin = ns['networks']['admin']
168         intro_range = ns['apex']['networks']['admin']['introspection_range']
169         config = dict()
170         config['undercloud_config'] = [
171             "enable_ui false",
172             "undercloud_update_packages false",
173             "undercloud_debug false",
174             "undercloud_hostname undercloud.{}".format(ns['dns-domain']),
175             "local_ip {}/{}".format(str(ns_admin['installer_vm']['ip']),
176                                     str(ns_admin['cidr']).split('/')[1]),
177             "network_gateway {}".format(str(ns_admin['installer_vm']['ip'])),
178             "network_cidr {}".format(str(ns_admin['cidr'])),
179             "dhcp_start {}".format(str(ns_admin['dhcp_range'][0])),
180             "dhcp_end {}".format(str(ns_admin['dhcp_range'][1])),
181             "inspection_iprange {}".format(','.join(intro_range))
182         ]
183
184         config['ironic_config'] = [
185             "disk_utils iscsi_verify_attempts 30",
186             "disk_partitioner check_device_max_retries 40"
187         ]
188
189         config['nova_config'] = [
190             "dns_domain {}".format(ns['dns-domain']),
191             "dhcp_domain {}".format(ns['dns-domain'])
192         ]
193
194         config['neutron_config'] = [
195             "dns_domain {}".format(ns['dns-domain']),
196         ]
197         # FIXME(trozet): possible bug here with not using external network
198         ns_external = ns['networks']['external'][0]
199         config['external_network'] = {
200             "vlan": ns_external['installer_vm']['vlan'],
201             "ip": ns_external['installer_vm']['ip'],
202             "prefix": str(ns_external['cidr']).split('/')[1],
203             "enabled": ns_external['enabled']
204         }
205
206         # FIXME (trozet): for now hardcoding aarch64 to false
207         config['aarch64'] = False
208
209         return config