aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosep Puigdemont <josep.puigdemont@enea.com>2016-05-08 13:04:07 +0200
committerAlexandru Avadanii <Alexandru.Avadanii@enea.com>2016-05-08 17:34:50 +0000
commita31faaebf959dca2793edd984f6776ae5c272f4f (patch)
treed09bf3d730b109339363c326dc96dfd187b037f9
parent1158f729444aa16313e263b468e92555f8eb7eb4 (diff)
ARMband patches for the fuel@opnfv deploy scripts
These are a collection of patches that adapt the current Fuel deploy scripts for mainly two purposes: - Make it possible to create a Fuel VM on a remote libvirt server. We use the LIBVIRT_DEFAULT_URI environment variable to detect that. Local deploys are possible by setting this variable to 'quemu:///system', or leaving it empty. See: https://libvirt.org/remote.html for more details. - Make it possible to add additional network interfaces. For this we allow the user to pass the "-b bridge" paramter several times, and creating a new virtual NIC for each of them, in the same order they were given. This required a bit of refactoring of the code. None of the changes above should break backwards compatibility, except when indicated in the commit (search for CHANGE in the log) In addition there are some updates to the code that were deemed necessary, like the ability to retry when executing shell commands instead of directly failing, and a simplification of the DHA IPMI adapter. Change-Id: I8a0cd5b8672383decd861309328137971eaed14b Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com> (cherry picked from commit bedeb36ac9ad42fb1ead2449ed8e75f0171808a2)
-rw-r--r--patches/opnfv-fuel/0010-deployment.py-stdout-not-consumed-when-deploying-cha.patch66
-rw-r--r--patches/opnfv-fuel/0011-common.py-catch-stderr-in-exec_cmd.patch40
-rw-r--r--patches/opnfv-fuel/0012-deploy.sh-do-not-expect-a-parameter-for-h.patch47
-rw-r--r--patches/opnfv-fuel/0013-VirtualFuel-Add-temp_dir-and-vm_name-attributes.patch57
-rw-r--r--patches/opnfv-fuel/0014-virtual_fuel-factor-out-image-creation-into-a-method.patch35
-rw-r--r--patches/opnfv-fuel/0015-virtual_fuel-initial-support-for-libvirt-volumes.patch209
-rw-r--r--patches/opnfv-fuel/0016-Remove-check-for-root.patch79
-rw-r--r--patches/opnfv-fuel/0017-virtual_fuel-make-vm_template-an-attibute.patch33
-rw-r--r--patches/opnfv-fuel/0018-virtual_fuel-add-XML-tree-as-attribute-of-VirtualFue.patch102
-rw-r--r--patches/opnfv-fuel/0019-transplant-Generate-extra-interfaces-config-file.patch107
-rw-r--r--patches/opnfv-fuel/0020-deploy.sh-no-need-to-set-umask-0000.patch33
-rw-r--r--patches/opnfv-fuel/0021-common.py-allow-specifying-number-of-attempts-in-exe.patch70
-rw-r--r--patches/opnfv-fuel/0022-ipmi_adapter-simplify-retry-if-command-fails.patch171
-rw-r--r--patches/opnfv-fuel/0023-deploy.py-add-multiple-bridges-support.patch69
-rw-r--r--patches/opnfv-fuel/0024-deploy.sh-allow-specifying-several-bridges.patch47
-rw-r--r--patches/opnfv-fuel/0025-Fuel-VM-for-the-Enea-Armband-lab.patch106
16 files changed, 1271 insertions, 0 deletions
diff --git a/patches/opnfv-fuel/0010-deployment.py-stdout-not-consumed-when-deploying-cha.patch b/patches/opnfv-fuel/0010-deployment.py-stdout-not-consumed-when-deploying-cha.patch
new file mode 100644
index 0000000..584413e
--- /dev/null
+++ b/patches/opnfv-fuel/0010-deployment.py-stdout-not-consumed-when-deploying-cha.patch
@@ -0,0 +1,66 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] deployment.py: stdout not consumed when deploying changes
+
+During the automatic deployment, when the environment is ready to be
+deployed, the deploy.py script will spawn a shell process that will
+perform the command "fuel deploy-changes". The standard output of this
+process is then piped to a "tee" process, which redirects the output
+to the standard output of the shell process, and to a file named
+cloud.log. The file is monitored by the deploy script to find out the
+status of the deployment, and print it to the log file of the automatic
+deployment script, including percentages for each node being
+provisioned. However, the deploy script never consumes the standard
+output of the shell process. If the shell process produces enough
+output, its standard output buffer will fill up, thus making the tee
+process block trying to write to its standard output, and the cloud.log
+file will not be updated. At this point, the deploy process, which is
+monitoring cloud.log, will not detect any progress in the deployment,
+and eventually it will time out and assume the deployment failed,
+although it might have finished fine after that.
+
+The solution here is to remove the "tee" process from the shell command,
+and instead redirect standard output to the cloud.log file.
+Another solution would be to actually parse the standard output of the
+shell command from the deploy script itself, but that would require a
+bit more work, as reading a line at a time might block the script.
+
+Finally, with this patch the cloud.log file won't be deleted unless the
+shell process has already finished.
+
+Change-Id: I03a77be42d220b1606e48fc4ca35e22d73a6e583
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/cloud/deployment.py | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/deploy/cloud/deployment.py b/deploy/cloud/deployment.py
+index 306abf0..0127d2a 100644
+--- a/deploy/cloud/deployment.py
++++ b/deploy/cloud/deployment.py
+@@ -101,8 +101,8 @@ class Deployment(object):
+ LOG_FILE = 'cloud.log'
+
+ log('Starting deployment of environment %s' % self.env_id)
+- run_proc('fuel --env %s deploy-changes | strings | tee %s'
+- % (self.env_id, LOG_FILE))
++ p = run_proc('fuel --env %s deploy-changes | strings > %s'
++ % (self.env_id, LOG_FILE))
+
+ ready = False
+ for i in range(int(self.deploy_timeout)):
+@@ -119,7 +119,13 @@ class Deployment(object):
+ break
+ else:
+ time.sleep(SLEEP_TIME)
+- delete(LOG_FILE)
++
++ p.poll()
++ if p.returncode == None:
++ log('The process deploying the changes has not yet finished.')
++ log('''The file %s won't be deleted''' % LOG_FILE)
++ else:
++ delete(LOG_FILE)
+
+ if ready:
+ log('Environment %s successfully deployed' % self.env_id)
diff --git a/patches/opnfv-fuel/0011-common.py-catch-stderr-in-exec_cmd.patch b/patches/opnfv-fuel/0011-common.py-catch-stderr-in-exec_cmd.patch
new file mode 100644
index 0000000..918a119
--- /dev/null
+++ b/patches/opnfv-fuel/0011-common.py-catch-stderr-in-exec_cmd.patch
@@ -0,0 +1,40 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] common.py: catch stderr in exec_cmd
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/common.py | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/deploy/common.py b/deploy/common.py
+index 787a21a..41b4e27 100644
+--- a/deploy/common.py
++++ b/deploy/common.py
+@@ -38,20 +38,20 @@ LOG.addHandler(out_handler)
+ os.chmod(LOGFILE, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+ def exec_cmd(cmd, check=True):
+- nul_f = open(os.devnull, 'w')
+ process = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+- stderr=nul_f,
++ stderr=subprocess.PIPE,
+ shell=True)
+- nul_f.close()
+- response = process.communicate()[0].strip()
++ (response, stderr) = process.communicate()
+ return_code = process.returncode
++ response = response.strip()
+ if check:
+ if return_code > 0:
++ stderr = stderr.strip()
+ print "Failed command: " + str(cmd)
+- print "Command returned response: " + str(response)
++ print "Command returned response: " + str(stderr)
+ print "Command return code: " + str(return_code)
+- raise Exception(response)
++ raise Exception(stderr)
+ else:
+ print "Command: " + str(cmd)
+ print str(response)
diff --git a/patches/opnfv-fuel/0012-deploy.sh-do-not-expect-a-parameter-for-h.patch b/patches/opnfv-fuel/0012-deploy.sh-do-not-expect-a-parameter-for-h.patch
new file mode 100644
index 0000000..f515aab
--- /dev/null
+++ b/patches/opnfv-fuel/0012-deploy.sh-do-not-expect-a-parameter-for-h.patch
@@ -0,0 +1,47 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] deploy.sh: do not expect a parameter for -h
+
+If -h was given as a parameter to the script, it would report an error
+as it expected a parameter, and if it was called as the only parameter,
+it would run deploy.py as if "old style" parameters had been given, thus
+showing the usage for the python script, instead of the expected usage
+message for this script.
+
+Update the usage message to include -h.
+
+Change-Id: I0930936962c1cb479ec4409ff114cd60a386b276
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ ci/deploy.sh | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/ci/deploy.sh b/ci/deploy.sh
+index d83bba2..dc13f1c 100755
+--- a/ci/deploy.sh
++++ b/ci/deploy.sh
+@@ -40,6 +40,7 @@ OPTIONS:
+ -f Deploy on existing Fuel master
+ -e Do not launch environment deployment
+ -F Do only create a Fuel master
++ -h Print this message and exit
+ -H No health check
+ -l Lab-name
+ -p Pod-name
+@@ -62,6 +63,7 @@ Input parameters to the build script is:
+ -f Deploy on existing Fuel master
+ -e Do not launch environment deployment
+ -F Do only create a Fuel master
++-h Print this message and exit
+ -H Do not run fuel built in health-check after successfull deployment
+ -l Lab name as defined in the configuration directory, e.g. lf
+ -p POD name as defined in the configuration directory, e.g. pod-1
+@@ -116,7 +118,7 @@ DRY_RUN=0
+ ############################################################################
+ # BEGIN of main
+ #
+-while getopts "b:B:dfFHl:p:s:S:i:h:e" OPTION
++while getopts "b:B:dfFHl:p:s:S:i:he" OPTION
+ do
+ case $OPTION in
+ b)
diff --git a/patches/opnfv-fuel/0013-VirtualFuel-Add-temp_dir-and-vm_name-attributes.patch b/patches/opnfv-fuel/0013-VirtualFuel-Add-temp_dir-and-vm_name-attributes.patch
new file mode 100644
index 0000000..83d6e29
--- /dev/null
+++ b/patches/opnfv-fuel/0013-VirtualFuel-Add-temp_dir-and-vm_name-attributes.patch
@@ -0,0 +1,57 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] VirtualFuel: Add temp_dir and vm_name attributes
+
+These two variables are defined in one of the methods right now. They
+will be useful to other methods too, so we add them as attributes to the
+object here.
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/environments/virtual_fuel.py | 15 +++++++++------
+ 1 file changed, 9 insertions(+), 6 deletions(-)
+
+diff --git a/deploy/environments/virtual_fuel.py b/deploy/environments/virtual_fuel.py
+index cb3bc6c..966bb91 100644
+--- a/deploy/environments/virtual_fuel.py
++++ b/deploy/environments/virtual_fuel.py
+@@ -25,6 +25,12 @@ class VirtualFuel(ExecutionEnvironment):
+ def __init__(self, storage_dir, pxe_bridge, dha_file, root_dir):
+ super(VirtualFuel, self).__init__(storage_dir, dha_file, root_dir)
+ self.pxe_bridge = pxe_bridge
++ self.temp_dir = tempfile.mkdtemp()
++ self.vm_name = self.dha.get_node_property(self.fuel_node_id,
++ 'libvirtName')
++
++ def __del__(self):
++ delete(self.temp_dir)
+
+ def set_vm_nic(self, temp_vm_file):
+ with open(temp_vm_file) as f:
+@@ -46,23 +52,20 @@ class VirtualFuel(ExecutionEnvironment):
+ vm_xml.write(f, pretty_print=True, xml_declaration=True)
+
+ def create_vm(self):
+- temp_dir = tempfile.mkdtemp()
+- vm_name = self.dha.get_node_property(self.fuel_node_id, 'libvirtName')
+ vm_template = '%s/%s' % (self.root_dir,
+ self.dha.get_node_property(
+ self.fuel_node_id, 'libvirtTemplate'))
+ check_file_exists(vm_template)
+- disk_path = '%s/%s.raw' % (self.storage_dir, vm_name)
++ disk_path = '%s/%s.raw' % (self.storage_dir, self.vm_name)
+ disk_sizes = self.dha.get_disks()
+ disk_size = disk_sizes['fuel']
+ exec_cmd('qemu-img create -f qcow2 %s %s' % (disk_path, disk_size))
+- temp_vm_file = '%s/%s' % (temp_dir, vm_name)
++ temp_vm_file = '%s/%s' % (self.temp_dir, self.vm_name)
+ exec_cmd('cp %s %s' % (vm_template, temp_vm_file))
+ self.set_vm_nic(temp_vm_file)
+ vm_definition_overwrite = self.dha.get_vm_definition('fuel')
+- self.define_vm(vm_name, temp_vm_file, disk_path,
++ self.define_vm(self.vm_name, temp_vm_file, disk_path,
+ vm_definition_overwrite)
+- delete(temp_dir)
+
+ def setup_environment(self):
+ check_if_root()
diff --git a/patches/opnfv-fuel/0014-virtual_fuel-factor-out-image-creation-into-a-method.patch b/patches/opnfv-fuel/0014-virtual_fuel-factor-out-image-creation-into-a-method.patch
new file mode 100644
index 0000000..4e1f583
--- /dev/null
+++ b/patches/opnfv-fuel/0014-virtual_fuel-factor-out-image-creation-into-a-method.patch
@@ -0,0 +1,35 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] virtual_fuel: factor out image creation into a method
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/environments/virtual_fuel.py | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/deploy/environments/virtual_fuel.py b/deploy/environments/virtual_fuel.py
+index 966bb91..82c4e47 100644
+--- a/deploy/environments/virtual_fuel.py
++++ b/deploy/environments/virtual_fuel.py
+@@ -51,15 +51,20 @@ class VirtualFuel(ExecutionEnvironment):
+ with open(temp_vm_file, 'w') as f:
+ vm_xml.write(f, pretty_print=True, xml_declaration=True)
+
++ def create_image(self, disk_path, disk_size):
++ exec_cmd('qemu-img create -f qcow2 %s %s' % (disk_path, disk_size))
++
+ def create_vm(self):
+ vm_template = '%s/%s' % (self.root_dir,
+ self.dha.get_node_property(
+ self.fuel_node_id, 'libvirtTemplate'))
+ check_file_exists(vm_template)
++
+ disk_path = '%s/%s.raw' % (self.storage_dir, self.vm_name)
+ disk_sizes = self.dha.get_disks()
+ disk_size = disk_sizes['fuel']
+- exec_cmd('qemu-img create -f qcow2 %s %s' % (disk_path, disk_size))
++ self.create_image(disk_path, disk_size)
++
+ temp_vm_file = '%s/%s' % (self.temp_dir, self.vm_name)
+ exec_cmd('cp %s %s' % (vm_template, temp_vm_file))
+ self.set_vm_nic(temp_vm_file)
diff --git a/patches/opnfv-fuel/0015-virtual_fuel-initial-support-for-libvirt-volumes.patch b/patches/opnfv-fuel/0015-virtual_fuel-initial-support-for-libvirt-volumes.patch
new file mode 100644
index 0000000..87266ef
--- /dev/null
+++ b/patches/opnfv-fuel/0015-virtual_fuel-initial-support-for-libvirt-volumes.patch
@@ -0,0 +1,209 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] virtual_fuel: initial support for libvirt volumes
+
+This patch introduces the ability to create volumes on the libvirt host
+where the Fuel VM is being deployed. For now a default pool is used,
+but the idea is to allow this to be configured.
+
+Since all virsh commands honor LIBVIRT_DEFAULT_URI, we use this
+environment variable to detect wheter we should create a volume or not.
+The rationale being that this environment variable will only be set if
+the user wants to do the VM deployment on a remote libvirt host.
+
+All this could also be done using scp and a user directory on the host
+machine, but using pools allows us to take advantage of libvirt's
+policies and file permissions.
+
+CHANGE: before this patch, the file system image was named like the VM:
+vm_name.raw. This patch introduces a change and adds a timestamp suffix
+to the image: vm_name-timestamp.raw. This is so to avoid collisions with
+an image with the same name on the remote pool. It may also be useful to
+keep around old images for later testing, while the VM definition can
+likely be the same.
+
+FIXME: This patch will use a pool called "jenkins" in the libvirt
+server, and it will fail if it is not present. This is a requirement
+that should be amended in the future, and properly documented.
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/deploy.py | 5 +++
+ deploy/dha_adapters/libvirt_adapter.py | 28 +++++++++++++++++
+ deploy/environments/virtual_fuel.py | 57 +++++++++++++++++++++++++++++-----
+ deploy/install_fuel_master.py | 8 +++--
+ 4 files changed, 88 insertions(+), 10 deletions(-)
+
+diff --git a/deploy/deploy.py b/deploy/deploy.py
+index f86f2be..265e888 100755
+--- a/deploy/deploy.py
++++ b/deploy/deploy.py
+@@ -243,6 +243,11 @@ class AutoDeploy(object):
+
+
+ def check_bridge(pxe_bridge, dha_path):
++ # Assume that bridges on remote nodes exists, we could ssh but
++ # the remote user might not have a login shell.
++ if os.environ.get('LIBVIRT_DEFAULT_URI'):
++ return
++
+ with io.open(dha_path) as yaml_file:
+ dha_struct = yaml.load(yaml_file)
+ if dha_struct['adapter'] != 'libvirt':
+diff --git a/deploy/dha_adapters/libvirt_adapter.py b/deploy/dha_adapters/libvirt_adapter.py
+index 85913ac..8f3042c 100644
+--- a/deploy/dha_adapters/libvirt_adapter.py
++++ b/deploy/dha_adapters/libvirt_adapter.py
+@@ -11,6 +11,7 @@
+ from lxml import etree
+ from hardware_adapter import HardwareAdapter
+ import tempfile
++import os
+
+ from common import (
+ log,
+@@ -23,6 +24,13 @@ DEV = {'pxe': 'network',
+ 'disk': 'hd',
+ 'iso': 'cdrom'}
+
++vol_xml_template = '''<volume type='file'>
++ <name>%s</name>
++ <capacity unit='%s'>%s</capacity>
++ <target>
++ <format type='%s'/>
++ </target>
++</volume>'''
+
+ class LibvirtAdapter(HardwareAdapter):
+
+@@ -140,3 +148,23 @@ class LibvirtAdapter(HardwareAdapter):
+
+ def get_virt_net_conf_dir(self):
+ return self.dha_struct['virtNetConfDir']
++
++ def upload_iso(self, iso_file):
++ size = os.path.getsize(iso_file)
++ vol_name = os.path.basename(iso_file)
++ vol_xml = vol_xml_template % (vol_name, 'bytes', str(size), 'raw')
++ fd, fname = tempfile.mkstemp(text=True, suffix='deploy')
++ os.write(fd, vol_xml)
++ os.close(fd)
++
++ log(vol_xml)
++ pool = 'jenkins' # FIXME
++ exec_cmd('virsh vol-create --pool %s %s' % (pool, fname))
++ vol_path = exec_cmd('virsh vol-path --pool %s %s' % (pool, vol_name))
++
++ exec_cmd('virsh vol-upload %s %s' % (vol_path, iso_file),
++ attempts=5, delay=10, verbose=True)
++
++ delete(fname)
++
++ return vol_path
+diff --git a/deploy/environments/virtual_fuel.py b/deploy/environments/virtual_fuel.py
+index 82c4e47..56d6f98 100644
+--- a/deploy/environments/virtual_fuel.py
++++ b/deploy/environments/virtual_fuel.py
+@@ -11,14 +11,33 @@
+ from lxml import etree
+ from execution_environment import ExecutionEnvironment
+ import tempfile
++import os
++import re
+
+ from common import (
+ exec_cmd,
+ check_file_exists,
+ check_if_root,
+ delete,
++ log,
+ )
+
++vol_xml_template = '''<volume type='file'>
++ <name>%s</name>
++ <capacity unit='%s'>%s</capacity>
++ <target>
++ <format type='%s'/>
++ </target>
++</volume>'''
++
++def get_size_and_unit(s):
++ p = re.compile('^(\d+)\s*(\D+)')
++ m = p.match(s)
++ if m == None:
++ return None, None
++ size = m.groups()[0]
++ unit = m.groups()[1]
++ return size, unit
+
+ class VirtualFuel(ExecutionEnvironment):
+
+@@ -51,19 +70,41 @@ class VirtualFuel(ExecutionEnvironment):
+ with open(temp_vm_file, 'w') as f:
+ vm_xml.write(f, pretty_print=True, xml_declaration=True)
+
++ def create_volume(self, pool, name, su, img_type='qcow2'):
++ log('Creating image using Libvirt volumes in pool %s, name: %s' %
++ (pool, name))
++ size, unit = get_size_and_unit(su)
++ if size == None:
++ err('Could not determine size and unit of %s' % s)
++
++ vol_xml = vol_xml_template % (name, unit, str(size), img_type)
++ fname = os.path.join(self.temp_dir, '%s_vol.xml' % name)
++ with file(fname, 'w') as f:
++ f.write(vol_xml)
++
++ exec_cmd('virsh vol-create --pool %s %s' % (pool, fname))
++ vol_path = exec_cmd('virsh vol-path --pool %s %s' % (pool, name))
++
++ delete(fname)
++
++ return vol_path
++
+ def create_image(self, disk_path, disk_size):
+- exec_cmd('qemu-img create -f qcow2 %s %s' % (disk_path, disk_size))
++ if os.environ.get('LIBVIRT_DEFAULT_URI') == None:
++ exec_cmd('qemu-img create -f qcow2 %s %s' % (disk_path, disk_size))
++ else:
++ pool = 'jenkins' # FIXME
++ name = os.path.basename(disk_path)
++ disk_path = self.create_volume(pool, name, disk_size)
+
+- def create_vm(self):
+- vm_template = '%s/%s' % (self.root_dir,
+- self.dha.get_node_property(
+- self.fuel_node_id, 'libvirtTemplate'))
+- check_file_exists(vm_template)
++ return disk_path
+
+- disk_path = '%s/%s.raw' % (self.storage_dir, self.vm_name)
++ def create_vm(self):
++ stamp = time.strftime("%Y%m%d%H%M%S")
++ disk_path = '%s/%s-%s.raw' % (self.storage_dir, self.vm_name, stamp)
+ disk_sizes = self.dha.get_disks()
+ disk_size = disk_sizes['fuel']
+- self.create_image(disk_path, disk_size)
++ disk_path = self.create_image(disk_path, disk_size)
+
+ temp_vm_file = '%s/%s' % (self.temp_dir, self.vm_name)
+ exec_cmd('cp %s %s' % (vm_template, temp_vm_file))
+diff --git a/deploy/install_fuel_master.py b/deploy/install_fuel_master.py
+index 4f6a052..1c1bf05 100644
+--- a/deploy/install_fuel_master.py
++++ b/deploy/install_fuel_master.py
+@@ -54,8 +54,12 @@ class InstallFuelMaster(object):
+
+ self.dha.node_power_off(self.fuel_node_id)
+
+- log('Zero the MBR')
+- self.dha.node_zero_mbr(self.fuel_node_id)
++ if os.environ.get('LIBVIRT_DEFAULT_URI'):
++ log('Upload ISO to pool')
++ self.iso_file = self.dha.upload_iso(self.iso_file)
++ else:
++ log('Zero the MBR')
++ self.dha.node_zero_mbr(self.fuel_node_id)
+
+ self.dha.node_set_boot_order(self.fuel_node_id, ['disk', 'iso'])
+
diff --git a/patches/opnfv-fuel/0016-Remove-check-for-root.patch b/patches/opnfv-fuel/0016-Remove-check-for-root.patch
new file mode 100644
index 0000000..4c24bb0
--- /dev/null
+++ b/patches/opnfv-fuel/0016-Remove-check-for-root.patch
@@ -0,0 +1,79 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] Remove check for root
+
+---
+ ci/deploy.sh | 5 -----
+ deploy/deploy-config.py | 1 -
+ deploy/deploy.py | 2 --
+ deploy/environments/virtual_fuel.py | 2 --
+ 4 files changed, 10 deletions(-)
+
+diff --git a/ci/deploy.sh b/ci/deploy.sh
+index dc13f1c..343d499 100755
+--- a/ci/deploy.sh
++++ b/ci/deploy.sh
+@@ -193,11 +193,6 @@ do
+ esac
+ done
+
+-if [[ $EUID -ne 0 ]]; then
+- echo "This script must be run as root" 1>&2
+- exit 1
+-fi
+-
+ if [ -z $BASE_CONFIG_URI ] || [ -z $TARGET_LAB ] || \
+ [ -z $TARGET_POD ] || [ -z $DEPLOY_SCENARIO ] || \
+ [ -z $ISO ]; then
+diff --git a/deploy/deploy-config.py b/deploy/deploy-config.py
+index 65d51b2..88a1111 100644
+--- a/deploy/deploy-config.py
++++ b/deploy/deploy-config.py
+@@ -40,7 +40,6 @@ from common import (
+ check_file_exists,
+ create_dir_if_not_exists,
+ delete,
+- check_if_root,
+ ArgParser,
+ )
+
+diff --git a/deploy/deploy.py b/deploy/deploy.py
+index 265e888..ff4582a 100755
+--- a/deploy/deploy.py
++++ b/deploy/deploy.py
+@@ -32,7 +32,6 @@ from common import (
+ check_file_exists,
+ create_dir_if_not_exists,
+ delete,
+- check_if_root,
+ ArgParser,
+ )
+
+@@ -230,7 +229,6 @@ class AutoDeploy(object):
+ return 0
+
+ def run(self):
+- check_if_root()
+ if self.cleanup_only:
+ self.cleanup_execution_environment()
+ else:
+diff --git a/deploy/environments/virtual_fuel.py b/deploy/environments/virtual_fuel.py
+index 56d6f98..f07207f 100644
+--- a/deploy/environments/virtual_fuel.py
++++ b/deploy/environments/virtual_fuel.py
+@@ -17,7 +17,6 @@ import re
+ from common import (
+ exec_cmd,
+ check_file_exists,
+- check_if_root,
+ delete,
+ log,
+ )
+@@ -114,7 +113,6 @@ class VirtualFuel(ExecutionEnvironment):
+ vm_definition_overwrite)
+
+ def setup_environment(self):
+- check_if_root()
+ self.cleanup_environment()
+ self.create_vm()
+
diff --git a/patches/opnfv-fuel/0017-virtual_fuel-make-vm_template-an-attibute.patch b/patches/opnfv-fuel/0017-virtual_fuel-make-vm_template-an-attibute.patch
new file mode 100644
index 0000000..db60202
--- /dev/null
+++ b/patches/opnfv-fuel/0017-virtual_fuel-make-vm_template-an-attibute.patch
@@ -0,0 +1,33 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] virtual_fuel: make vm_template an attibute
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/environments/virtual_fuel.py | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/deploy/environments/virtual_fuel.py b/deploy/environments/virtual_fuel.py
+index f07207f..92a234c 100644
+--- a/deploy/environments/virtual_fuel.py
++++ b/deploy/environments/virtual_fuel.py
+@@ -46,6 +46,10 @@ class VirtualFuel(ExecutionEnvironment):
+ self.temp_dir = tempfile.mkdtemp()
+ self.vm_name = self.dha.get_node_property(self.fuel_node_id,
+ 'libvirtName')
++ self.vm_template = '%s/%s' % (self.root_dir,
++ self.dha.get_node_property(
++ self.fuel_node_id, 'libvirtTemplate'))
++ check_file_exists(self.vm_template)
+
+ def __del__(self):
+ delete(self.temp_dir)
+@@ -106,7 +110,7 @@ class VirtualFuel(ExecutionEnvironment):
+ disk_path = self.create_image(disk_path, disk_size)
+
+ temp_vm_file = '%s/%s' % (self.temp_dir, self.vm_name)
+- exec_cmd('cp %s %s' % (vm_template, temp_vm_file))
++ exec_cmd('cp %s %s' % (self.vm_template, temp_vm_file))
+ self.set_vm_nic(temp_vm_file)
+ vm_definition_overwrite = self.dha.get_vm_definition('fuel')
+ self.define_vm(self.vm_name, temp_vm_file, disk_path,
diff --git a/patches/opnfv-fuel/0018-virtual_fuel-add-XML-tree-as-attribute-of-VirtualFue.patch b/patches/opnfv-fuel/0018-virtual_fuel-add-XML-tree-as-attribute-of-VirtualFue.patch
new file mode 100644
index 0000000..ebaad98
--- /dev/null
+++ b/patches/opnfv-fuel/0018-virtual_fuel-add-XML-tree-as-attribute-of-VirtualFue.patch
@@ -0,0 +1,102 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] virtual_fuel: add XML tree as attribute of VirtualFuel
+
+Now the VM XML definition tree is an attribute of the object, this way
+it can be used by all methods without having to re-read the file from
+the file.
+
+Methods added:
+update_vm_template_file: Flushes the contents of the in-memory XML
+ representation of the VM to the backing file.
+
+del_vm_nics: Deletes all interfaces from the VM
+
+add_vm_nic: Adds a new NIC to the VM, it now takes the name of the
+ bridge as a parameter.
+
+Add a function to flush the contents of the in-memory XML representation
+to the file update_vm_template_file
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/environments/virtual_fuel.py | 37 +++++++++++++++++++++++++------------
+ 1 file changed, 25 insertions(+), 12 deletions(-)
+
+diff --git a/deploy/environments/virtual_fuel.py b/deploy/environments/virtual_fuel.py
+index 92a234c..b68577e 100644
+--- a/deploy/environments/virtual_fuel.py
++++ b/deploy/environments/virtual_fuel.py
+@@ -13,6 +13,7 @@ from execution_environment import ExecutionEnvironment
+ import tempfile
+ import os
+ import re
++import time
+
+ from common import (
+ exec_cmd,
+@@ -50,28 +51,38 @@ class VirtualFuel(ExecutionEnvironment):
+ self.dha.get_node_property(
+ self.fuel_node_id, 'libvirtTemplate'))
+ check_file_exists(self.vm_template)
++ with open(self.vm_template) as f:
++ self.vm_xml = etree.parse(f)
++
++ self.temp_vm_file = '%s/%s' % (self.temp_dir, self.vm_name)
++ self.update_vm_template_file()
+
+ def __del__(self):
+ delete(self.temp_dir)
+
+- def set_vm_nic(self, temp_vm_file):
+- with open(temp_vm_file) as f:
+- vm_xml = etree.parse(f)
+- interfaces = vm_xml.xpath('/domain/devices/interface')
++ def update_vm_template_file(self):
++ with open(self.temp_vm_file, "wc") as f:
++ self.vm_xml.write(f, pretty_print=True, xml_declaration=True)
++
++ def del_vm_nics(self):
++ interfaces = self.vm_xml.xpath('/domain/devices/interface')
+ for interface in interfaces:
+ interface.getparent().remove(interface)
++
++ def add_vm_nic(self, bridge):
+ interface = etree.Element('interface')
+ interface.set('type', 'bridge')
+ source = etree.SubElement(interface, 'source')
+- source.set('bridge', self.pxe_bridge)
++ source.set('bridge', bridge)
+ model = etree.SubElement(interface, 'model')
+ model.set('type', 'virtio')
+- devices = vm_xml.xpath('/domain/devices')
++
++ devices = self.vm_xml.xpath('/domain/devices')
+ if devices:
+ device = devices[0]
+ device.append(interface)
+- with open(temp_vm_file, 'w') as f:
+- vm_xml.write(f, pretty_print=True, xml_declaration=True)
++ else:
++ err('No devices!')
+
+ def create_volume(self, pool, name, su, img_type='qcow2'):
+ log('Creating image using Libvirt volumes in pool %s, name: %s' %
+@@ -109,11 +120,13 @@ class VirtualFuel(ExecutionEnvironment):
+ disk_size = disk_sizes['fuel']
+ disk_path = self.create_image(disk_path, disk_size)
+
+- temp_vm_file = '%s/%s' % (self.temp_dir, self.vm_name)
+- exec_cmd('cp %s %s' % (self.vm_template, temp_vm_file))
+- self.set_vm_nic(temp_vm_file)
++ self.del_vm_nics()
++ self.add_vm_nic(self.pxe_bridge)
++ self.update_vm_template_file()
++
+ vm_definition_overwrite = self.dha.get_vm_definition('fuel')
+- self.define_vm(self.vm_name, temp_vm_file, disk_path,
++
++ self.define_vm(self.vm_name, self.temp_vm_file, disk_path,
+ vm_definition_overwrite)
+
+ def setup_environment(self):
diff --git a/patches/opnfv-fuel/0019-transplant-Generate-extra-interfaces-config-file.patch b/patches/opnfv-fuel/0019-transplant-Generate-extra-interfaces-config-file.patch
new file mode 100644
index 0000000..b6a351e
--- /dev/null
+++ b/patches/opnfv-fuel/0019-transplant-Generate-extra-interfaces-config-file.patch
@@ -0,0 +1,107 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 17:58:56 +0200
+Subject: [PATCH] transplant: Generate extra interfaces config file
+
+The DEA override may contain a IFCGF_<interface> section in its 'fuel:'
+section, containing the necessary keys to produce a ifcfg-<interface>
+file, like in this example:
+
+fuel:
+ IFCFG_ETH1:
+ device: eth1
+ ipaddress: 10.0.1.10
+ netmask: 255.255.255.0
+ gateway: 10.0.1.254
+
+FIXME: In order for Network Manager to use the newly added interfaces
+for outgoing traffic and honor their GATEWAY setting (e.g. if we just
+added one public interface), the default route on admin iface (most of
+the time called eth0) should be disabled. For now, we assume the admin
+interface is always "eth0".
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+Signed-off-by: Alexandu Avadanii <alexandru.avadanii@enea.com>
+---
+ deploy/transplant_fuel_settings.py | 37 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 37 insertions(+)
+
+diff --git a/deploy/transplant_fuel_settings.py b/deploy/transplant_fuel_settings.py
+index e57a4fb..9a65cf6 100644
+--- a/deploy/transplant_fuel_settings.py
++++ b/deploy/transplant_fuel_settings.py
+@@ -11,10 +11,14 @@
+ import sys
+ import io
+ import yaml
++import re
++import os
+ from dea import DeploymentEnvironmentAdapter
+
+ from common import (
+ check_file_exists,
++ exec_cmd,
++ log,
+ )
+
+ ASTUTE_YAML = '/etc/fuel/astute.yaml'
+@@ -35,15 +39,45 @@ def parse_arguments():
+ check_file_exists(dea_file)
+ return dea_file
+
++def write_ifcfg_file(key, fuel_conf):
++ config = ('BOOTPROTO=none\n'
++ 'ONBOOT=yes\n'
++ 'TYPE=Ethernet\n'
++ 'NM_CONTROLLED=yes\n')
++ for skey in ('ipaddress', 'device', 'netmask', 'gateway'):
++ if not fuel_conf[key].get(skey):
++ log('Warning: missing key %s for %s' % (skey, key))
++ config += '%s=\n' % skey.upper()
++ elif skey == 'ipaddress':
++ config += 'IPADDR=%s\n' % fuel_conf[key][skey]
++ else:
++ config += '%s=%s\n' % (skey.upper(), fuel_conf[key][skey])
++
++ fname = os.path.join('/etc/sysconfig/network-scripts/',
++ key.lower().replace('_','-'))
++ with open(fname, 'wc') as f:
++ f.write(config)
+
+ def transplant(dea, astute):
+ fuel_conf = dea.get_fuel_config()
++ require_network_restart = False
+ for key in fuel_conf.iterkeys():
+ if key == 'ADMIN_NETWORK':
+ for skey in fuel_conf[key].iterkeys():
+ astute[key][skey] = fuel_conf[key][skey]
++ elif re.match('^IFCFG', key):
++ log('Adding interface configuration for: %s' % key.lower())
++ require_network_restart = True
++ write_ifcfg_file(key, fuel_conf)
++ if astute.has_key(key):
++ astute.pop(key, None)
+ else:
+ astute[key] = fuel_conf[key]
++ if require_network_restart:
++ admin_ifcfg = '/etc/sysconfig/network-scripts/ifcfg-eth0'
++ exec_cmd('echo "DEFROUTE=no" >> %s' % admin_ifcfg)
++ log('At least one interface was reconfigured, restart network manager')
++ exec_cmd('systemctl restart network')
+ return astute
+
+
+@@ -51,11 +85,14 @@ def main():
+ dea_file = parse_arguments()
+ check_file_exists(ASTUTE_YAML)
+ dea = DeploymentEnvironmentAdapter(dea_file)
++ log('Reading astute file %s' % ASTUTE_YAML)
+ with io.open(ASTUTE_YAML) as stream:
+ astute = yaml.load(stream)
++ log('Initiating transplant')
+ transplant(dea, astute)
+ with io.open(ASTUTE_YAML, 'w') as stream:
+ yaml.dump(astute, stream, default_flow_style=False)
++ log('Transplant done')
+
+
+ if __name__ == '__main__':
diff --git a/patches/opnfv-fuel/0020-deploy.sh-no-need-to-set-umask-0000.patch b/patches/opnfv-fuel/0020-deploy.sh-no-need-to-set-umask-0000.patch
new file mode 100644
index 0000000..241f307
--- /dev/null
+++ b/patches/opnfv-fuel/0020-deploy.sh-no-need-to-set-umask-0000.patch
@@ -0,0 +1,33 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Fri, 6 May 2016 03:07:40 +0200
+Subject: [PATCH] deploy.sh: no need to set umask 0000
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ ci/deploy.sh | 6 ------
+ 1 file changed, 6 deletions(-)
+
+diff --git a/ci/deploy.sh b/ci/deploy.sh
+index 343d499..34ecc57 100755
+--- a/ci/deploy.sh
++++ b/ci/deploy.sh
+@@ -76,9 +76,6 @@ Input parameters to the build script is:
+ -i .iso image to be deployed (needs to be provided in a URI
+ style, it can be a local resource: file:// or a remote resource http(s)://)
+
+-NOTE: Root priviledges are needed for this script to run
+-
+-
+ Examples:
+ sudo `basename $0` -b file:///home/jenkins/lab-config -l lf -p pod1 -s ha_odl-l3_heat_ceilometer -i file:///home/jenkins/myiso.iso
+ EOF
+@@ -207,9 +204,6 @@ fi
+ # Enable the automatic exit trap
+ trap do_exit SIGINT SIGTERM EXIT
+
+-# Set no restrictive umask so that Jenkins can removeeee any residuals
+-umask 0000
+-
+ clean
+
+ pushd ${DEPLOY_DIR} > /dev/null
diff --git a/patches/opnfv-fuel/0021-common.py-allow-specifying-number-of-attempts-in-exe.patch b/patches/opnfv-fuel/0021-common.py-allow-specifying-number-of-attempts-in-exe.patch
new file mode 100644
index 0000000..d799723
--- /dev/null
+++ b/patches/opnfv-fuel/0021-common.py-allow-specifying-number-of-attempts-in-exe.patch
@@ -0,0 +1,70 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Fri, 6 May 2016 03:28:26 +0200
+Subject: [PATCH] common.py: allow specifying number of attempts in exec_cmd
+
+Some commands executed by exec_cmd may fail because of a temporary
+cause, and it may be desirable to retry the same command several times
+until it succeeds. One example of this are the ipmitool commands, which
+may fail temorarily on some targets if they get too many requests
+simultaneously.
+
+In this patch two new optional parameters are introduced to the function
+signature, which do not break backward compatibility:
+ attempts: which indicates how many times the command should be run if
+ it returns a non-zero value*, and defaults to 1 (as today).
+ delay: which indicates the delay in seconds between attempts, and
+ defaults to 5 seconds.
+ verbose: It will print the remaining attempts left for the current
+ command if set to True.
+
+* It may be desirable to add yet another parameter to indicate what
+ return value should be considered an error, but zero for now seems a
+ reasonable default
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/common.py | 24 +++++++++++++++++-------
+ 1 file changed, 17 insertions(+), 7 deletions(-)
+
+diff --git a/deploy/common.py b/deploy/common.py
+index 41b4e27..3cd3e0e 100644
+--- a/deploy/common.py
++++ b/deploy/common.py
+@@ -16,6 +16,7 @@ import argparse
+ import shutil
+ import stat
+ import errno
++import time
+
+ N = {'id': 0, 'status': 1, 'name': 2, 'cluster': 3, 'ip': 4, 'mac': 5,
+ 'roles': 6, 'pending_roles': 7, 'online': 8, 'group_id': 9}
+@@ -37,13 +38,22 @@ out_handler.setFormatter(formatter)
+ LOG.addHandler(out_handler)
+ os.chmod(LOGFILE, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+
+-def exec_cmd(cmd, check=True):
+- process = subprocess.Popen(cmd,
+- stdout=subprocess.PIPE,
+- stderr=subprocess.PIPE,
+- shell=True)
+- (response, stderr) = process.communicate()
+- return_code = process.returncode
++def exec_cmd(cmd, check=True, attempts=1, delay=5, verbose=False):
++ # a negative value means forever
++ while attempts != 0:
++ attempts = attempts - 1
++ process = subprocess.Popen(cmd,
++ stdout=subprocess.PIPE,
++ stderr=subprocess.PIPE,
++ shell=True)
++ (response, stderr) = process.communicate()
++ return_code = process.returncode
++ if return_code == 0 or attempts == 0:
++ break
++ time.sleep(delay)
++ if verbose:
++ log('%d attempts left: %s' % (attempts, cmd))
++
+ response = response.strip()
+ if check:
+ if return_code > 0:
diff --git a/patches/opnfv-fuel/0022-ipmi_adapter-simplify-retry-if-command-fails.patch b/patches/opnfv-fuel/0022-ipmi_adapter-simplify-retry-if-command-fails.patch
new file mode 100644
index 0000000..c1617f0
--- /dev/null
+++ b/patches/opnfv-fuel/0022-ipmi_adapter-simplify-retry-if-command-fails.patch
@@ -0,0 +1,171 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Fri, 6 May 2016 12:09:58 +0200
+Subject: [PATCH] ipmi_adapter: simplify, retry if command fails
+
+The method get_node_state has been added to the The IpmiAdapter class.
+
+In addition, now the power on/off methods will try several times to
+perform their IPMI command before giving up, instead of bailing out at
+the first error.
+
+After the power on/off command is completed, the method will wait until
+the node is in the desired state.
+
+FIXME: a command could potentially take several minutes if the defaults
+are used; each IPMI command can take 1 minutes, and there can be three
+commands issued per operation, one of them may be retried 20 times with
+the current defaults. Ideally we would use eventlet or something alike
+to allow each command a limited time to execute:
+ with eventlet.timeout.Timeout(seconds) as t:
+ power_on/off_command
+
+FIXME: There is a potential dead-lock situation by issuing the command
+and then checking the status, as someone could have intervened in
+between the two commands.
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/dha_adapters/ipmi_adapter.py | 101 +++++++++++++++---------------------
+ 1 file changed, 42 insertions(+), 59 deletions(-)
+
+diff --git a/deploy/dha_adapters/ipmi_adapter.py b/deploy/dha_adapters/ipmi_adapter.py
+index 8fda4f9..283bd57 100644
+--- a/deploy/dha_adapters/ipmi_adapter.py
++++ b/deploy/dha_adapters/ipmi_adapter.py
+@@ -1,5 +1,6 @@
+ ###############################################################################
+ # Copyright (c) 2015 Ericsson AB and others.
++# (c) 2016 Enea Software AB
+ # szilard.cserey@ericsson.com
+ # All rights reserved. This program and the accompanying materials
+ # are made available under the terms of the Apache License, Version 2.0
+@@ -20,8 +21,10 @@ from common import (
+
+ class IpmiAdapter(HardwareAdapter):
+
+- def __init__(self, yaml_path):
++ def __init__(self, yaml_path, attempts=20, delay=3):
+ super(IpmiAdapter, self).__init__(yaml_path)
++ self.attempts = attempts
++ self.delay = delay
+
+ def get_access_info(self, node_id):
+ ip = self.get_node_property(node_id, 'ipmiIp')
+@@ -40,69 +43,46 @@ class IpmiAdapter(HardwareAdapter):
+ mac_list.append(self.get_node_property(node_id, 'pxeMac').lower())
+ return mac_list
+
++ def node_get_state(self, node_id):
++ state = exec_cmd('%s chassis power status' % self.ipmi_cmd(node_id),
++ attempts=self.attempts, delay=self.delay,
++ verbose=True)
++ return state
++
++ def __node_power_cmd__(self, node_id, cmd):
++ expected = 'Chassis Power is %s' % cmd
++ if self.node_get_state(node_id) == expected:
++ return
++
++ pow_cmd = '%s chassis power %s' % (self.ipmi_cmd(node_id), cmd)
++ exec_cmd(pow_cmd, attempts=self.attempts, delay=self.delay,
++ verbose=True)
++
++ attempts = self.attempts
++ while attempts:
++ state = self.node_get_state(node_id)
++ attempts -= 1
++ if state == expected:
++ return
++ elif attempts != 0:
++ # reinforce our will, but allow the command to fail,
++ # we know our message got across once already...
++ exec_cmd(pow_cmd, check=False)
++
++ err('Could not set chassis %s for node %s' % (cmd, node_id))
++
+ def node_power_on(self, node_id):
+- WAIT_LOOP = 200
+- SLEEP_TIME = 3
+ log('Power ON Node %s' % node_id)
+- cmd_prefix = self.ipmi_cmd(node_id)
+- state = exec_cmd('%s chassis power status' % cmd_prefix)
+- if state == 'Chassis Power is off':
+- exec_cmd('%s chassis power on' % cmd_prefix)
+- done = False
+- for i in range(WAIT_LOOP):
+- state, _ = exec_cmd('%s chassis power status' % cmd_prefix,
+- False)
+- if state == 'Chassis Power is on':
+- done = True
+- break
+- else:
+- time.sleep(SLEEP_TIME)
+- if not done:
+- err('Could Not Power ON Node %s' % node_id)
++ self.__node_power_cmd__(node_id, 'on')
+
+ def node_power_off(self, node_id):
+- WAIT_LOOP = 200
+- SLEEP_TIME = 3
+ log('Power OFF Node %s' % node_id)
+- cmd_prefix = self.ipmi_cmd(node_id)
+- state = exec_cmd('%s chassis power status' % cmd_prefix)
+- if state == 'Chassis Power is on':
+- done = False
+- exec_cmd('%s chassis power off' % cmd_prefix)
+- for i in range(WAIT_LOOP):
+- state, _ = exec_cmd('%s chassis power status' % cmd_prefix,
+- False)
+- if state == 'Chassis Power is off':
+- done = True
+- break
+- else:
+- time.sleep(SLEEP_TIME)
+- if not done:
+- err('Could Not Power OFF Node %s' % node_id)
++ self.__node_power_cmd__(node_id, 'off')
+
+ def node_reset(self, node_id):
+- WAIT_LOOP = 600
+ log('RESET Node %s' % node_id)
+- cmd_prefix = self.ipmi_cmd(node_id)
+- state = exec_cmd('%s chassis power status' % cmd_prefix)
+- if state == 'Chassis Power is on':
+- was_shut_off = False
+- done = False
+- exec_cmd('%s chassis power reset' % cmd_prefix)
+- for i in range(WAIT_LOOP):
+- state, _ = exec_cmd('%s chassis power status' % cmd_prefix,
+- False)
+- if state == 'Chassis Power is off':
+- was_shut_off = True
+- elif state == 'Chassis Power is on' and was_shut_off:
+- done = True
+- break
+- time.sleep(1)
+- if not done:
+- err('Could Not RESET Node %s' % node_id)
+- else:
+- err('Cannot RESET Node %s because it\'s not Active, state: %s'
+- % (node_id, state))
++ cmd = '%s chassis power reset' % self.ipmi_cmd(node_id)
++ exec_cmd(cmd, attempts=self.attempts, delay=self.delay, verbose=True)
+
+ def node_set_boot_order(self, node_id, boot_order_list):
+ log('Set boot order %s on Node %s' % (boot_order_list, node_id))
+@@ -111,9 +91,12 @@ class IpmiAdapter(HardwareAdapter):
+ for dev in boot_order_list:
+ if dev == 'pxe':
+ exec_cmd('%s chassis bootdev pxe options=persistent'
+- % cmd_prefix)
++ % cmd_prefix, attempts=self.attempts, delay=self.delay,
++ verbose=True)
+ elif dev == 'iso':
+- exec_cmd('%s chassis bootdev cdrom' % cmd_prefix)
++ exec_cmd('%s chassis bootdev cdrom' % cmd_prefix,
++ attempts=self.attempts, delay=self.delay, verbose=True)
+ elif dev == 'disk':
+ exec_cmd('%s chassis bootdev disk options=persistent'
+- % cmd_prefix)
++ % cmd_prefix, attempts=self.attempts, delay=self.delay,
++ verbose=True)
diff --git a/patches/opnfv-fuel/0023-deploy.py-add-multiple-bridges-support.patch b/patches/opnfv-fuel/0023-deploy.py-add-multiple-bridges-support.patch
new file mode 100644
index 0000000..376a722
--- /dev/null
+++ b/patches/opnfv-fuel/0023-deploy.py-add-multiple-bridges-support.patch
@@ -0,0 +1,69 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Fri, 6 May 2016 04:32:06 +0200
+Subject: [PATCH] deploy.py: add multiple bridges support
+
+Some Fuel VMs may need more than one network interface. To be able to do
+that, we now allow the user to specify the "-b" paramter (bridge)
+multiple times, creating a new NIC for each one of them.
+
+The NICs are created in the same order as they are given in the command
+line.
+
+There is no change in behavior from earlier versions, pxebr will still
+be the default bridge if none is specified.
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ deploy/deploy.py | 10 +++++++---
+ deploy/environments/virtual_fuel.py | 3 ++-
+ 2 files changed, 9 insertions(+), 4 deletions(-)
+
+diff --git a/deploy/deploy.py b/deploy/deploy.py
+index ff4582a..041ba2f 100755
+--- a/deploy/deploy.py
++++ b/deploy/deploy.py
+@@ -312,8 +312,8 @@ def parse_arguments():
+ parser.add_argument('-s', dest='storage_dir', action='store',
+ default='%s/images' % CWD,
+ help='Storage Directory [default: images]')
+- parser.add_argument('-b', dest='pxe_bridge', action='store',
+- default='pxebr',
++ parser.add_argument('-b', dest='pxe_bridge', action='append',
++ default=[],
+ help='Linux Bridge for booting up the Fuel Master VM '
+ '[default: pxebr]')
+ parser.add_argument('-p', dest='fuel_plugins_dir', action='store',
+@@ -332,6 +332,9 @@ def parse_arguments():
+ args = parser.parse_args()
+ log(args)
+
++ if not args.pxe_bridge:
++ args.pxe_bridge = ['pxebr']
++
+ check_file_exists(args.dha_file)
+
+ if not args.cleanup_only:
+@@ -343,7 +346,8 @@ def parse_arguments():
+ check_file_exists(args.iso_file)
+ log('Using image directory: %s' % args.storage_dir)
+ create_dir_if_not_exists(args.storage_dir)
+- check_bridge(args.pxe_bridge, args.dha_file)
++ for bridge in args.pxe_bridge:
++ check_bridge(bridge, args.dha_file)
+
+ kwargs = {'no_fuel': args.no_fuel, 'fuel_only': args.fuel_only,
+ 'no_health_check': args.no_health_check,
+diff --git a/deploy/environments/virtual_fuel.py b/deploy/environments/virtual_fuel.py
+index b68577e..6b673d0 100644
+--- a/deploy/environments/virtual_fuel.py
++++ b/deploy/environments/virtual_fuel.py
+@@ -121,7 +121,8 @@ class VirtualFuel(ExecutionEnvironment):
+ disk_path = self.create_image(disk_path, disk_size)
+
+ self.del_vm_nics()
+- self.add_vm_nic(self.pxe_bridge)
++ for bridge in self.pxe_bridge:
++ self.add_vm_nic(bridge)
+ self.update_vm_template_file()
+
+ vm_definition_overwrite = self.dha.get_vm_definition('fuel')
diff --git a/patches/opnfv-fuel/0024-deploy.sh-allow-specifying-several-bridges.patch b/patches/opnfv-fuel/0024-deploy.sh-allow-specifying-several-bridges.patch
new file mode 100644
index 0000000..b10effe
--- /dev/null
+++ b/patches/opnfv-fuel/0024-deploy.sh-allow-specifying-several-bridges.patch
@@ -0,0 +1,47 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Fri, 6 May 2016 04:39:44 +0200
+Subject: [PATCH] deploy.sh: allow specifying several bridges
+
+It might be desirable to add several bridges to the fuel VM, so we let
+the user specify -B more than once, and honor that when calling
+deploy.py. We also make it possible to specify a comma separated list of
+bridges, as in: -B br1,br2, for convenience for the Jenkins jobs.
+
+There is a change in behavior from the previous version, and that is
+that it may call the deploy.py python script with more than one instance
+of the "-b" parameter.
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ ci/deploy.sh | 11 +++++++----
+ 1 file changed, 7 insertions(+), 4 deletions(-)
+
+diff --git a/ci/deploy.sh b/ci/deploy.sh
+index 34ecc57..c9b836b 100755
+--- a/ci/deploy.sh
++++ b/ci/deploy.sh
+@@ -57,7 +57,10 @@ and provides a fairly simple mechanism to execute a deployment.
+ Input parameters to the build script is:
+ -b Base URI to the configuration directory (needs to be provided in a URI
+ style, it can be a local resource: file:// or a remote resource http(s)://)
+--B PXE Bridge for booting of Fuel master, default is pxebr
++-B PXE Bridge for booting of Fuel master. It can be specified several times,
++ or as a comma separated list of bridges, or both: -B br1 -B br2,br3
++ One NIC connected to each specified bridge will be created in the Fuel VM,
++ in the same order as provided in the command line. The default is pxebr.
+ -d Dry-run - Produces deploy config files (config/dea.yaml and
+ config/dha.yaml), but does not execute deploy
+ -f Deploy on existing Fuel master
+@@ -130,9 +133,9 @@ do
+ fi
+ ;;
+ B)
+- if [[ ${OPTARG} ]]; then
+- PXE_BRIDGE="-b ${OPTARG}"
+- fi
++ for bridge in ${OPTARG//,/ }; do
++ PXE_BRIDGE+=" -b $bridge"
++ done
+ ;;
+ d)
+ DRY_RUN=1
diff --git a/patches/opnfv-fuel/0025-Fuel-VM-for-the-Enea-Armband-lab.patch b/patches/opnfv-fuel/0025-Fuel-VM-for-the-Enea-Armband-lab.patch
new file mode 100644
index 0000000..fbcd11d
--- /dev/null
+++ b/patches/opnfv-fuel/0025-Fuel-VM-for-the-Enea-Armband-lab.patch
@@ -0,0 +1,106 @@
+From: Josep Puigdemont <josep.puigdemont@enea.com>
+Date: Wed, 4 May 2016 14:27:23 +0200
+Subject: [PATCH] Fuel VM for the Enea Armband lab
+
+This is the initial VM description fit for Enea's Armband lab.
+
+Signed-off-by: Josep Puigdemont <josep.puigdemont@enea.com>
+---
+ .../hardware_environment/vms/enea_lab/fuel.xml | 88 ++++++++++++++++++++++
+ 1 file changed, 88 insertions(+)
+ create mode 100644 deploy/templates/hardware_environment/vms/enea_lab/fuel.xml
+
+diff --git a/deploy/templates/hardware_environment/vms/enea_lab/fuel.xml b/deploy/templates/hardware_environment/vms/enea_lab/fuel.xml
+new file mode 100644
+index 0000000..8773ed4
+--- /dev/null
++++ b/deploy/templates/hardware_environment/vms/enea_lab/fuel.xml
+@@ -0,0 +1,88 @@
++<domain type='kvm' id='1'>
++ <name>fuel</name>
++ <memory unit='KiB'>8290304</memory>
++ <currentMemory unit='KiB'>8290304</currentMemory>
++ <vcpu placement='static'>8</vcpu>
++ <resource>
++ <partition>/machine</partition>
++ </resource>
++ <os>
++ <type arch='x86_64' machine='pc-i440fx-rhel7.0.0'>hvm</type>
++ <boot dev='cdrom'/>
++ <boot dev='hd'/>
++ <bootmenu enable='no'/>
++ </os>
++ <features>
++ <acpi/>
++ <apic/>
++ <pae/>
++ </features>
++ <cpu mode='host-model'>
++ <model fallback='allow'/>
++ </cpu>
++ <clock offset='utc'>
++ <timer name='rtc' tickpolicy='catchup'/>
++ <timer name='pit' tickpolicy='delay'/>
++ <timer name='hpet' present='no'/>
++ </clock>
++ <on_poweroff>destroy</on_poweroff>
++ <on_reboot>restart</on_reboot>
++ <on_crash>restart</on_crash>
++ <pm>
++ <suspend-to-mem enabled='no'/>
++ <suspend-to-disk enabled='no'/>
++ </pm>
++ <devices>
++ <emulator>/usr/libexec/qemu-kvm</emulator>
++ <disk type='file' device='disk'>
++ <driver name='qemu' type='qcow2'/>
++ <target dev='vda' bus='virtio'/>
++ </disk>
++ <disk type='block' device='cdrom'>
++ <driver name='qemu' type='raw'/>
++ <target dev='hdb' bus='ide'/>
++ <readonly/>
++ </disk>
++ <controller type='usb' index='0' model='ich9-ehci1'>
++ </controller>
++ <controller type='usb' index='0' model='ich9-uhci1'>
++ <master startport='0'/>
++ </controller>
++ <controller type='usb' index='0' model='ich9-uhci2'>
++ <master startport='2'/>
++ </controller>
++ <controller type='usb' index='0' model='ich9-uhci3'>
++ <master startport='4'/>
++ </controller>
++ <controller type='pci' index='0' model='pci-root'>
++ </controller>
++ <controller type='ide' index='0'>
++ </controller>
++ <controller type='virtio-serial' index='0'>
++ </controller>
++ <interface type='bridge'>
++ <model type='virtio'/>
++ </interface>
++ <interface type='bridge'>
++ <model type='virtio'/>
++ </interface>
++ <serial type='pty'>
++ <source path='/dev/pts/0'/>
++ <target port='0'/>
++ </serial>
++ <console type='pty' tty='/dev/pts/0'>
++ <source path='/dev/pts/0'/>
++ <target type='serial' port='0'/>
++ </console>
++ <input type='mouse' bus='ps2'/>
++ <input type='keyboard' bus='ps2'/>
++ <graphics type='vnc' port='5906' autoport='yes' listen='127.0.0.1'>
++ <listen type='address' address='127.0.0.1'/>
++ </graphics>
++ <video>
++ <model type='vga' vram='16384' heads='1'/>
++ </video>
++ <memballoon model='virtio'>
++ </memballoon>
++ </devices>
++</domain>