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