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