Merge "Migrates Apex to Python"
[apex.git] / apex / virtual / configure_vm.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 math
13 import os
14 import random
15
16 MAX_NUM_MACS = math.trunc(0xff / 2)
17
18
19 def generate_baremetal_macs(count=1):
20     """Generate an Ethernet MAC address suitable for baremetal testing."""
21     # NOTE(dprince): We generate our own bare metal MAC address's here
22     # instead of relying on libvirt so that we can ensure the
23     # locally administered bit is set low. (The libvirt default is
24     # to set the 2nd MSB high.) This effectively allows our
25     # fake baremetal VMs to more accurately behave like real hardware
26     # and fixes issues with bridge/DHCP configurations which rely
27     # on the fact that bridges assume the MAC address of the lowest
28     # attached NIC.
29     # MACs generated for a given machine will also be in sequential
30     # order, which matches how most BM machines are laid out as well.
31     # Additionally we increment each MAC by two places.
32     macs = []
33
34     if count > MAX_NUM_MACS:
35         raise ValueError("The MAX num of MACS supported is %i." % MAX_NUM_MACS)
36
37     base_nums = [0x00,
38                  random.randint(0x00, 0xff),
39                  random.randint(0x00, 0xff),
40                  random.randint(0x00, 0xff),
41                  random.randint(0x00, 0xff)]
42     base_mac = ':'.join(map(lambda x: "%02x" % x, base_nums))
43
44     start = random.randint(0x00, 0xff)
45     if (start + (count * 2)) > 0xff:
46         # leave room to generate macs in sequence
47         start = 0xff - count * 2
48     for num in range(0, count * 2, 2):
49         mac = start + num
50         macs.append(base_mac + ":" + ("%02x" % mac))
51     return macs
52
53
54 def create_vm_storage(domain, vol_path='/var/lib/libvirt/images'):
55     volume_name = domain + '.qcow2'
56     stgvol_xml = """
57     <volume>
58       <name>{}</name>
59       <allocation>0</allocation>
60       <capacity unit="G">41</capacity>
61       <target>
62         <format type='qcow2'/>
63         <path>{}</path>
64         <permissions>
65           <owner>107</owner>
66           <group>107</group>
67           <mode>0744</mode>
68           <label>virt_image_t</label>
69         </permissions>
70       </target>
71     </volume>""".format(volume_name, os.path.join(vol_path, volume_name))
72
73     conn = libvirt.open('qemu:///system')
74     pool = conn.storagePoolLookupByName('default')
75     if pool is None:
76         raise Exception("Default libvirt storage pool missing")
77         # TODO(trozet) create default storage pool
78
79     if pool.isActive() == 0:
80         pool.create()
81     try:
82         vol = pool.storageVolLookupByName(volume_name)
83         vol.wipe(0)
84         vol.delete(0)
85     except libvirt.libvirtError as e:
86         if e.get_error_code() != libvirt.VIR_ERR_NO_STORAGE_VOL:
87             raise
88     new_vol = pool.createXML(stgvol_xml)
89     if new_vol is None:
90         raise Exception("Unable to create new volume")
91     logging.debug("Created new storage volume: {}".format(volume_name))
92
93
94 def create_vm(name, image, diskbus='sata', baremetal_interfaces=['admin'],
95               arch='x86_64', engine='kvm', memory=8192, bootdev='network',
96               cpus=4, nic_driver='virtio', macs=[], direct_boot=None,
97               kernel_args=None, default_network=False,
98               template_dir='/usr/share/opnfv-apex'):
99     # TODO(trozet): fix name here to be image since it is full path of qcow2
100     create_vm_storage(name)
101     with open(os.path.join(template_dir, 'domain.xml'), 'r') as f:
102         source_template = f.read()
103     imagefile = os.path.realpath(image)
104     memory = int(memory) * 1024
105     params = {
106         'name': name,
107         'imagefile': imagefile,
108         'engine': engine,
109         'arch': arch,
110         'memory': str(memory),
111         'cpus': str(cpus),
112         'bootdev': bootdev,
113         'network': '',
114         'enable_serial_console': '',
115         'direct_boot': '',
116         'kernel_args': '',
117         'user_interface': '',
118     }
119
120     # Configure the bus type for the target disk device
121     params['diskbus'] = diskbus
122     nicparams = {
123         'nicdriver': nic_driver,
124     }
125     if default_network:
126         params['network'] = """
127       <!-- regular natted network, for access to the vm -->
128       <interface type='network'>
129         <source network='default'/>
130         <model type='%(nicdriver)s'/>
131       </interface>""" % nicparams
132     else:
133         params['network'] = ''
134     while len(macs) < len(baremetal_interfaces):
135         macs += generate_baremetal_macs(1)
136
137     params['bm_network'] = ""
138     for bm_interface, mac in zip(baremetal_interfaces, macs):
139         bm_interface_params = {
140             'bminterface': bm_interface,
141             'bmmacaddress': mac,
142             'nicdriver': nic_driver,
143         }
144         params['bm_network'] += """
145           <!-- bridged 'bare metal' network on %(bminterface)s -->
146           <interface type='network'>
147             <mac address='%(bmmacaddress)s'/>
148             <source network='%(bminterface)s'/>
149             <model type='%(nicdriver)s'/>
150           </interface>""" % bm_interface_params
151
152     params['enable_serial_console'] = """
153         <serial type='pty'>
154           <target port='0'/>
155         </serial>
156         <console type='pty'>
157           <target type='serial' port='0'/>
158         </console>
159         """
160     if direct_boot:
161         params['direct_boot'] = """
162         <kernel>/var/lib/libvirt/images/%(direct_boot)s.vmlinuz</kernel>
163         <initrd>/var/lib/libvirt/images/%(direct_boot)s.initrd</initrd>
164         """ % {'direct_boot': direct_boot}
165     if kernel_args:
166         params['kernel_args'] = """
167         <cmdline>%s</cmdline>
168         """ % ' '.join(kernel_args)
169
170     if arch == 'aarch64':
171
172         params['direct_boot'] += """
173         <loader readonly='yes' \
174         type='pflash'>/usr/share/AAVMF/AAVMF_CODE.fd</loader>
175         <nvram>/var/lib/libvirt/qemu/nvram/centos7.0_VARS.fd</nvram>
176         """
177         params['user_interface'] = """
178         <controller type='virtio-serial' index='0'>
179           <address type='virtio-mmio'/>
180         </controller>
181         <serial type='pty'>
182           <target port='0'/>
183         </serial>
184         <console type='pty'>
185           <target type='serial' port='0'/>
186         </console>
187         <channel type='unix'>
188           <target type='virtio' name='org.qemu.guest_agent.0'/>
189           <address type='virtio-serial' controller='0' bus='0' port='1'/>
190         </channel>
191         """
192     else:
193         params['user_interface'] = """
194         <input type='mouse' bus='ps2'/>
195         <graphics type='vnc' port='-1' autoport='yes'/>
196         <video>
197           <model type='cirrus' vram='9216' heads='1'/>
198         </video>
199         """
200
201     libvirt_template = source_template % params
202     logging.debug("libvirt template is {}".format(libvirt_template))
203     conn = libvirt.open('qemu:///system')
204     vm = conn.defineXML(libvirt_template)
205     logging.info("Created machine %s with UUID %s" % (name, vm.UUIDString()))
206     return vm