aboutsummaryrefslogtreecommitdiffstats
path: root/patches/opnfv-fuel
diff options
context:
space:
mode:
Diffstat (limited to 'patches/opnfv-fuel')
-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 00000000..584413ec
--- /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 00000000..918a1192
--- /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 00000000..f515aab6
--- /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 00000000..83d6e292
--- /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 00000000..4e1f583b
--- /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 00000000..87266ef8
--- /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 00000000..4c24bb0e
--- /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 00000000..db602029
--- /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 00000000..ebaad984
--- /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 00000000..b6a351e4
--- /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 00000000..241f3078
--- /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 00000000..d799723e
--- /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 00000000..c1617f04
--- /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 00000000..376a7221
--- /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 00000000..b10effee
--- /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 00000000..fbcd11d1
--- /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>