Merge "bootstrap: Add lshw package (also in fuel-mirror)."
[armband.git] / patches / opnfv-fuel / 0015-virtual_fuel-initial-support-for-libvirt-volumes.patch
1 From: Josep Puigdemont <josep.puigdemont@enea.com>
2 Date: Wed, 4 May 2016 14:27:23 +0200
3 Subject: [PATCH] virtual_fuel: initial support for libvirt volumes
4
5 This patch introduces the ability to create volumes on the libvirt host
6 where the Fuel VM is being deployed. For now a default pool is used,
7 but the idea is to allow this to be configured.
8
9 Since all virsh commands honor LIBVIRT_DEFAULT_URI, we use this
10 environment variable to detect wheter we should create a volume or not.
11 The rationale being that this environment variable will only be set if
12 the user wants to do the VM deployment on a remote libvirt host.
13
14 All this could also be done using scp and a user directory on the host
15 machine, but using pools allows us to take advantage of libvirt's
16 policies and file permissions.
17
18 CHANGE: before this patch, the file system image was named like the VM:
19 vm_name.raw. This patch introduces a change and adds a timestamp suffix
20 to the image: vm_name-timestamp.raw. This is so to avoid collisions with
21 an image with the same name on the remote pool. It may also be useful to
22 keep around old images for later testing, while the VM definition can
23 likely be the same.
24
25 FIXME: This patch will use a pool called "jenkins" in the libvirt
26 server, and it will fail if it is not present. This is a requirement
27 that should be amended in the future, and properly documented.
28
29 Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
30 ---
31  deploy/deploy.py                       |  5 +++
32  deploy/dha_adapters/libvirt_adapter.py | 28 +++++++++++++++++
33  deploy/environments/virtual_fuel.py    | 57 +++++++++++++++++++++++++++++-----
34  deploy/install_fuel_master.py          |  8 +++--
35  4 files changed, 88 insertions(+), 10 deletions(-)
36
37 diff --git a/deploy/deploy.py b/deploy/deploy.py
38 index f86f2be..265e888 100755
39 --- a/deploy/deploy.py
40 +++ b/deploy/deploy.py
41 @@ -243,6 +243,11 @@ class AutoDeploy(object):
42  
43  
44  def check_bridge(pxe_bridge, dha_path):
45 +    # Assume that bridges on remote nodes exists, we could ssh but
46 +    # the remote user might not have a login shell.
47 +    if os.environ.get('LIBVIRT_DEFAULT_URI'):
48 +        return
49 +
50      with io.open(dha_path) as yaml_file:
51          dha_struct = yaml.load(yaml_file)
52      if dha_struct['adapter'] != 'libvirt':
53 diff --git a/deploy/dha_adapters/libvirt_adapter.py b/deploy/dha_adapters/libvirt_adapter.py
54 index 85913ac..8f3042c 100644
55 --- a/deploy/dha_adapters/libvirt_adapter.py
56 +++ b/deploy/dha_adapters/libvirt_adapter.py
57 @@ -11,6 +11,7 @@
58  from lxml import etree
59  from hardware_adapter import HardwareAdapter
60  import tempfile
61 +import os
62  
63  from common import (
64      log,
65 @@ -23,6 +24,13 @@ DEV = {'pxe': 'network',
66         'disk': 'hd',
67         'iso': 'cdrom'}
68  
69 +vol_xml_template = '''<volume type='file'>
70 +  <name>%s</name>
71 +  <capacity unit='%s'>%s</capacity>
72 +  <target>
73 +    <format type='%s'/>
74 +  </target>
75 +</volume>'''
76  
77  class LibvirtAdapter(HardwareAdapter):
78  
79 @@ -140,3 +148,23 @@ class LibvirtAdapter(HardwareAdapter):
80  
81      def get_virt_net_conf_dir(self):
82          return self.dha_struct['virtNetConfDir']
83 +
84 +    def upload_iso(self, iso_file):
85 +        size = os.path.getsize(iso_file)
86 +        vol_name = os.path.basename(iso_file)
87 +        vol_xml = vol_xml_template % (vol_name, 'bytes', str(size), 'raw')
88 +        fd, fname = tempfile.mkstemp(text=True, suffix='deploy')
89 +        os.write(fd, vol_xml)
90 +        os.close(fd)
91 +
92 +        log(vol_xml)
93 +        pool = 'jenkins' # FIXME
94 +        exec_cmd('virsh vol-create --pool %s %s' % (pool, fname))
95 +        vol_path = exec_cmd('virsh vol-path --pool %s %s' % (pool, vol_name))
96 +
97 +        exec_cmd('virsh vol-upload %s %s' % (vol_path, iso_file),
98 +                 attempts=5, delay=10, verbose=True)
99 +
100 +        delete(fname)
101 +
102 +        return vol_path
103 diff --git a/deploy/environments/virtual_fuel.py b/deploy/environments/virtual_fuel.py
104 index 82c4e47..56d6f98 100644
105 --- a/deploy/environments/virtual_fuel.py
106 +++ b/deploy/environments/virtual_fuel.py
107 @@ -11,14 +11,33 @@
108  from lxml import etree
109  from execution_environment import ExecutionEnvironment
110  import tempfile
111 +import os
112 +import re
113  
114  from common import (
115      exec_cmd,
116      check_file_exists,
117      check_if_root,
118      delete,
119 +    log,
120  )
121  
122 +vol_xml_template = '''<volume type='file'>
123 +  <name>%s</name>
124 +  <capacity unit='%s'>%s</capacity>
125 +  <target>
126 +    <format type='%s'/>
127 +  </target>
128 +</volume>'''
129 +
130 +def get_size_and_unit(s):
131 +    p = re.compile('^(\d+)\s*(\D+)')
132 +    m = p.match(s)
133 +    if m == None:
134 +        return None, None
135 +    size = m.groups()[0]
136 +    unit = m.groups()[1]
137 +    return size, unit
138  
139  class VirtualFuel(ExecutionEnvironment):
140  
141 @@ -51,19 +70,41 @@ class VirtualFuel(ExecutionEnvironment):
142          with open(temp_vm_file, 'w') as f:
143              vm_xml.write(f, pretty_print=True, xml_declaration=True)
144  
145 +    def create_volume(self, pool, name, su, img_type='qcow2'):
146 +        log('Creating image using Libvirt volumes in pool %s, name: %s' %
147 +            (pool, name))
148 +        size, unit = get_size_and_unit(su)
149 +        if size == None:
150 +            err('Could not determine size and unit of %s' % s)
151 +
152 +        vol_xml = vol_xml_template % (name, unit, str(size), img_type)
153 +        fname = os.path.join(self.temp_dir, '%s_vol.xml' % name)
154 +        with file(fname, 'w') as f:
155 +            f.write(vol_xml)
156 +
157 +        exec_cmd('virsh vol-create --pool %s %s' % (pool, fname))
158 +        vol_path = exec_cmd('virsh vol-path --pool %s %s' % (pool, name))
159 +
160 +        delete(fname)
161 +
162 +        return vol_path
163 +
164      def create_image(self, disk_path, disk_size):
165 -        exec_cmd('qemu-img create -f qcow2 %s %s' % (disk_path, disk_size))
166 +        if os.environ.get('LIBVIRT_DEFAULT_URI') == None:
167 +            exec_cmd('qemu-img create -f qcow2 %s %s' % (disk_path, disk_size))
168 +        else:
169 +            pool = 'jenkins' # FIXME
170 +            name = os.path.basename(disk_path)
171 +            disk_path = self.create_volume(pool, name, disk_size)
172  
173 -    def create_vm(self):
174 -        vm_template = '%s/%s' % (self.root_dir,
175 -                                 self.dha.get_node_property(
176 -                                     self.fuel_node_id, 'libvirtTemplate'))
177 -        check_file_exists(vm_template)
178 +        return disk_path
179  
180 -        disk_path = '%s/%s.raw' % (self.storage_dir, self.vm_name)
181 +    def create_vm(self):
182 +        stamp = time.strftime("%Y%m%d%H%M%S")
183 +        disk_path = '%s/%s-%s.raw' % (self.storage_dir, self.vm_name, stamp)
184          disk_sizes = self.dha.get_disks()
185          disk_size = disk_sizes['fuel']
186 -        self.create_image(disk_path, disk_size)
187 +        disk_path = self.create_image(disk_path, disk_size)
188  
189          temp_vm_file = '%s/%s' % (self.temp_dir, self.vm_name)
190          exec_cmd('cp %s %s' % (vm_template, temp_vm_file))
191 diff --git a/deploy/install_fuel_master.py b/deploy/install_fuel_master.py
192 index 4f6a052..1c1bf05 100644
193 --- a/deploy/install_fuel_master.py
194 +++ b/deploy/install_fuel_master.py
195 @@ -54,8 +54,12 @@ class InstallFuelMaster(object):
196  
197          self.dha.node_power_off(self.fuel_node_id)
198  
199 -        log('Zero the MBR')
200 -        self.dha.node_zero_mbr(self.fuel_node_id)
201 +        if os.environ.get('LIBVIRT_DEFAULT_URI'):
202 +            log('Upload ISO to pool')
203 +            self.iso_file = self.dha.upload_iso(self.iso_file)
204 +        else:
205 +            log('Zero the MBR')
206 +            self.dha.node_zero_mbr(self.fuel_node_id)
207  
208          self.dha.node_set_boot_order(self.fuel_node_id, ['disk', 'iso'])
209