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 ##############################################################################
17 MAX_NUM_MACS = math.trunc(0xff / 2)
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
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.
35 if count > MAX_NUM_MACS:
36 raise ValueError("The MAX num of MACS supported is %i." % MAX_NUM_MACS)
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))
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):
51 macs.append(base_mac + ":" + ("%02x" % mac))
55 def create_vm_storage(domain, vol_path='/var/lib/libvirt/images'):
56 volume_name = domain + '.qcow2'
60 <allocation>0</allocation>
61 <capacity unit="G">41</capacity>
63 <format type='qcow2'/>
69 <label>virt_image_t</label>
72 </volume>""".format(volume_name, os.path.join(vol_path, volume_name))
74 conn = libvirt.open('qemu:///system')
75 pool = conn.storagePoolLookupByName('default')
77 raise Exception("Default libvirt storage pool missing")
78 # TODO(trozet) create default storage pool
80 if pool.isActive() == 0:
83 vol = pool.storageVolLookupByName(volume_name)
86 except libvirt.libvirtError as e:
87 if e.get_error_code() != libvirt.VIR_ERR_NO_STORAGE_VOL:
89 new_vol = pool.createXML(stgvol_xml)
91 raise Exception("Unable to create new volume")
92 logging.debug("Created new storage volume: {}".format(volume_name))
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 memory = int(memory) * 1024
108 'imagefile': imagefile,
111 'memory': str(memory),
115 'enable_serial_console': '',
118 'user_interface': '',
121 # assign scsi as default for aarch64
122 if arch == 'aarch64' and diskbus == 'sata':
124 # Configure the bus type for the target disk device
125 params['diskbus'] = diskbus
127 'nicdriver': nic_driver,
130 params['network'] = """
131 <!-- regular natted network, for access to the vm -->
132 <interface type='network'>
133 <source network='default'/>
134 <model type='%(nicdriver)s'/>
135 </interface>""" % nicparams
137 params['network'] = ''
138 while len(macs) < len(baremetal_interfaces):
139 macs += generate_baremetal_macs(1)
141 params['bm_network'] = ""
142 for bm_interface, mac in zip(baremetal_interfaces, macs):
143 bm_interface_params = {
144 'bminterface': bm_interface,
146 'nicdriver': nic_driver,
148 params['bm_network'] += """
149 <!-- bridged 'bare metal' network on %(bminterface)s -->
150 <interface type='network'>
151 <mac address='%(bmmacaddress)s'/>
152 <source network='%(bminterface)s'/>
153 <model type='%(nicdriver)s'/>
154 </interface>""" % bm_interface_params
157 params['direct_boot'] = """
158 <kernel>/var/lib/libvirt/images/%(direct_boot)s.vmlinuz</kernel>
159 <initrd>/var/lib/libvirt/images/%(direct_boot)s.initrd</initrd>
160 """ % {'direct_boot': direct_boot}
162 params['kernel_args'] = """
163 <cmdline>%s</cmdline>
164 """ % ' '.join(kernel_args)
166 if arch == 'aarch64':
167 params['direct_boot'] += """
168 <loader readonly='yes' \
169 type='pflash'>/usr/share/AAVMF/AAVMF_CODE.fd</loader>
170 <nvram>/var/lib/libvirt/qemu/nvram/centos7.0_VARS.fd</nvram>
172 params['user_interface'] = """
173 <controller type='virtio-serial' index='0'>
174 <address type='virtio-mmio'/>
180 <target type='serial' port='0'/>
182 <channel type='unix'>
183 <target type='virtio' name='org.qemu.guest_agent.0'/>
184 <address type='virtio-serial' controller='0' bus='0' port='1'/>
188 params['enable_serial_console'] = """
193 <target type='serial' port='0'/>
196 params['user_interface'] = """
197 <input type='mouse' bus='ps2'/>
198 <graphics type='vnc' port='-1' autoport='yes'/>
200 <model type='cirrus' vram='9216' heads='1'/>
204 libvirt_template = source_template % params
205 logging.debug("libvirt template is {}".format(libvirt_template))
206 conn = libvirt.open('qemu:///system')
207 vm = conn.defineXML(libvirt_template)
208 logging.info("Created machine %s with UUID %s" % (name, vm.UUIDString()))