Merge "Rebase: deploy related patch series."
[armband.git] / patches / opnfv-fuel / 0006-virtual_fuel-initial-support-for-remote-libvirt.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 remote libvirt
4
5 With this patch it should be possible to create a fuel VM on a remote
6 libvirt server by properly defining the LIBVIRT_DEFAULT_URI [1]
7 environment variable. If the variable is not defined, then there should
8 be no percievable change in behaviour for the script.
9
10 This patch introduces the ability to create volumes (images) on a
11 remote libvirt host where the Fuel VM is to be deployed. For now
12 the volumes are created by default in a pool named jenkins, but
13 the idea is to allow this to be configured, probably in the POD's
14 DHA file.
15
16 Since all virsh commands honor LIBVIRT_DEFAULT_URI, we use this
17 environment variable to detect wheter we should create a volume or not.
18 The rationale being that the variable will only be set if the user wants
19 to to do the VM deployment on a remote libvirt host.
20
21 We need to create a volume because we can not rely on being able to
22 access the remote server's file system directly.
23
24 The images are then transferred to the libvirt host using virsh
25 commands. All this could also be done using scp and a user directory
26 on the host machine, but using pools allows us to take advantage of
27 libvirt's policies and file permissions.
28
29 CHANGE: when LIBVIRT_DEFAULT_URI is defined, the script will not check
30 for the presence of the required PXE bridge. This will still be checked
31 when the Fuel VM is started and the bridge not found, but this happens
32 at a later point than it does today.
33
34 CHANGE: before this patch, the file system image was named like the VM:
35 vm_name.raw. This patch introduces a change and adds a timestamp suffix
36 to the image: vm_name-timestamp.raw. This is so to avoid collisions with
37 an image with the same name on the remote pool (two PODs may be using
38 the same pool). It may also be useful to keep around old file system
39 images.
40
41 FIXME: This patch requires a pool named "jenkins" in the remote libvirt
42 server, and it will fail if it is not present. This should be
43 configurable.
44
45 Notice though that we can still define LIBVIRT_DEFAULT_URI as
46 "qemu:///system" to create the Fuel VM on the local host.
47
48 [1] https://libvirt.org/remote.html#Remote_URI_reference
49
50 Change-Id: I40925ed31337d3ad9cf505f284f5c3d14e9129a0
51 Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
52 ---
53  deploy/deploy.py                       |  5 +++
54  deploy/dha_adapters/libvirt_adapter.py | 31 +++++++++++++++++++
55  deploy/environments/virtual_fuel.py    | 56 ++++++++++++++++++++++++++++++++--
56  deploy/install_fuel_master.py          |  8 +++--
57  4 files changed, 95 insertions(+), 5 deletions(-)
58
59 diff --git a/deploy/deploy.py b/deploy/deploy.py
60 index 742e76b..179ee7b 100755
61 --- a/deploy/deploy.py
62 +++ b/deploy/deploy.py
63 @@ -245,6 +245,11 @@ class AutoDeploy(object):
64  
65  
66  def check_bridge(pxe_bridge, dha_path):
67 +    # Assume that bridges on remote nodes exists, we could ssh but
68 +    # the remote user might not have a login shell.
69 +    if os.environ.get('LIBVIRT_DEFAULT_URI'):
70 +        return
71 +
72      with io.open(dha_path) as yaml_file:
73          dha_struct = yaml.load(yaml_file)
74      if dha_struct['adapter'] != 'libvirt':
75 diff --git a/deploy/dha_adapters/libvirt_adapter.py b/deploy/dha_adapters/libvirt_adapter.py
76 index 85913ac..466f134 100644
77 --- a/deploy/dha_adapters/libvirt_adapter.py
78 +++ b/deploy/dha_adapters/libvirt_adapter.py
79 @@ -11,6 +11,7 @@
80  from lxml import etree
81  from hardware_adapter import HardwareAdapter
82  import tempfile
83 +import os
84  
85  from common import (
86      log,
87 @@ -23,6 +24,15 @@ DEV = {'pxe': 'network',
88         'disk': 'hd',
89         'iso': 'cdrom'}
90  
91 +VOL_XML_TEMPLATE = '''<volume type='file'>
92 +  <name>{name}</name>
93 +  <capacity unit='{unit}'>{size!s}</capacity>
94 +  <target>
95 +    <format type='{format_type}'/>
96 +  </target>
97 +</volume>'''
98 +
99 +DEFAULT_POOL = 'jenkins'
100  
101  class LibvirtAdapter(HardwareAdapter):
102  
103 @@ -140,3 +150,24 @@ class LibvirtAdapter(HardwareAdapter):
104  
105      def get_virt_net_conf_dir(self):
106          return self.dha_struct['virtNetConfDir']
107 +
108 +    def upload_iso(self, iso_file):
109 +        size = os.path.getsize(iso_file)
110 +        vol_name = os.path.basename(iso_file)
111 +        vol_xml = VOL_XML_TEMPLATE.format(name=vol_name, unit='bytes',
112 +                                          size=size, format_type='raw')
113 +        fd, fname = tempfile.mkstemp(text=True, suffix='deploy')
114 +        os.write(fd, vol_xml)
115 +        os.close(fd)
116 +
117 +        log(vol_xml)
118 +        pool = DEFAULT_POOL # FIXME
119 +        exec_cmd('virsh vol-create --pool %s %s' % (pool, fname))
120 +        vol_path = exec_cmd('virsh vol-path --pool %s %s' % (pool, vol_name))
121 +
122 +        exec_cmd('virsh vol-upload %s %s' % (vol_path, iso_file),
123 +                 attempts=5, delay=10, verbose=True)
124 +
125 +        delete(fname)
126 +
127 +        return vol_path
128 diff --git a/deploy/environments/virtual_fuel.py b/deploy/environments/virtual_fuel.py
129 index ac5fc53..f9f9f7a 100644
130 --- a/deploy/environments/virtual_fuel.py
131 +++ b/deploy/environments/virtual_fuel.py
132 @@ -11,14 +11,36 @@
133  from lxml import etree
134  from execution_environment import ExecutionEnvironment
135  import tempfile
136 +import os
137 +import re
138 +import time
139  
140  from common import (
141      exec_cmd,
142      check_file_exists,
143      check_if_root,
144      delete,
145 +    log,
146  )
147  
148 +VOL_XML_TEMPLATE = '''<volume type='file'>
149 +  <name>{name}</name>
150 +  <capacity unit='{unit}'>{size!s}</capacity>
151 +  <target>
152 +    <format type='{format_type}'/>
153 +  </target>
154 +</volume>'''
155 +
156 +DEFAULT_POOL = 'jenkins'
157 +
158 +def get_size_and_unit(s):
159 +    p = re.compile('^(\d+)\s*(\D+)')
160 +    m = p.match(s)
161 +    if m == None:
162 +        return None, None
163 +    size = m.groups()[0]
164 +    unit = m.groups()[1]
165 +    return size, unit
166  
167  class VirtualFuel(ExecutionEnvironment):
168  
169 @@ -55,14 +77,42 @@ class VirtualFuel(ExecutionEnvironment):
170          with open(temp_vm_file, 'w') as f:
171              vm_xml.write(f, pretty_print=True, xml_declaration=True)
172  
173 +    def create_volume(self, pool, name, su, img_type='qcow2'):
174 +        log('Creating image using Libvirt volumes in pool %s, name: %s' %
175 +            (pool, name))
176 +        size, unit = get_size_and_unit(su)
177 +        if size == None:
178 +            err('Could not determine size and unit of %s' % s)
179 +
180 +        vol_xml = VOL_XML_TEMPLATE.format(name=name, unit=unit, size=size,
181 +                                          format_type=img_type)
182 +        fname = os.path.join(self.temp_dir, '%s_vol.xml' % name)
183 +        with file(fname, 'w') as f:
184 +            f.write(vol_xml)
185 +
186 +        exec_cmd('virsh vol-create --pool %s %s' % (pool, fname))
187 +        vol_path = exec_cmd('virsh vol-path --pool %s %s' % (pool, name))
188 +
189 +        delete(fname)
190 +
191 +        return vol_path
192 +
193      def create_image(self, disk_path, disk_size):
194 -        exec_cmd('qemu-img create -f qcow2 %s %s' % (disk_path, disk_size))
195 +        if os.environ.get('LIBVIRT_DEFAULT_URI') == None:
196 +            exec_cmd('qemu-img create -f qcow2 %s %s' % (disk_path, disk_size))
197 +        else:
198 +            pool = DEFAULT_POOL # FIXME
199 +            name = os.path.basename(disk_path)
200 +            disk_path = self.create_volume(pool, name, disk_size)
201 +
202 +        return disk_path
203  
204      def create_vm(self):
205 -        disk_path = '%s/%s.raw' % (self.storage_dir, self.vm_name)
206 +        stamp = time.strftime("%Y%m%d%H%M%S")
207 +        disk_path = '%s/%s-%s.raw' % (self.storage_dir, self.vm_name, stamp)
208          disk_sizes = self.dha.get_disks()
209          disk_size = disk_sizes['fuel']
210 -        self.create_image(disk_path, disk_size)
211 +        disk_path = self.create_image(disk_path, disk_size)
212  
213          temp_vm_file = '%s/%s' % (self.temp_dir, self.vm_name)
214          exec_cmd('cp %s %s' % (self.vm_template, temp_vm_file))
215 diff --git a/deploy/install_fuel_master.py b/deploy/install_fuel_master.py
216 index 631bf99..5adccef 100644
217 --- a/deploy/install_fuel_master.py
218 +++ b/deploy/install_fuel_master.py
219 @@ -54,8 +54,12 @@ class InstallFuelMaster(object):
220  
221          self.dha.node_power_off(self.fuel_node_id)
222  
223 -        log('Zero the MBR')
224 -        self.dha.node_zero_mbr(self.fuel_node_id)
225 +        if os.environ.get('LIBVIRT_DEFAULT_URI'):
226 +            log('Upload ISO to pool')
227 +            self.iso_file = self.dha.upload_iso(self.iso_file)
228 +        else:
229 +            log('Zero the MBR')
230 +            self.dha.node_zero_mbr(self.fuel_node_id)
231  
232          self.dha.node_set_boot_order(self.fuel_node_id, ['disk', 'iso'])
233