Extend ironic formula
This patch updates ironicng salt module to be able of:
* list vifs for a given node
* attach vif for a given node
* detach vif from a given node
* deploy user image to node
* generate configdrive
Fix some default values when enrolling nodes automatically.
Allow to download images from http to conductor http_root.
Change-Id: Id99ad955c8c7256ae10ece7a173242044692e713
diff --git a/_modules/configdrive.py b/_modules/configdrive.py
new file mode 100644
index 0000000..0a45b02
--- /dev/null
+++ b/_modules/configdrive.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+
+
+import gzip
+import json
+import logging
+import os
+import StringIO
+import shutil
+import six
+import tempfile
+import yaml
+
+
+HAS_LIBS = False
+try:
+ from oslo_utils import uuidutils
+ from oslo_utils import fileutils
+ from oslo_concurrency import processutils
+ from oslo_serialization import base64
+ HAS_LIBS = True
+except ImportError:
+ pass
+
+LOG = logging.getLogger(__name__)
+
+
+def __virtual__():
+ '''
+ Only load this module if mkisofs is installed on this minion.
+ '''
+ if not HAS_LIBS:
+ return False
+
+ for path in os.environ["PATH"].split(os.pathsep):
+ if os.access(os.path.join(path, 'mkisofs'), os.X_OK):
+ return True
+
+ return False
+
+class ConfigDriveBuilder(object):
+ """Build config drives, optionally as a context manager."""
+
+ def __init__(self, image_file):
+ self.image_file = image_file
+ self.mdfiles=[] # List with (path, data)
+
+ def __enter__(self):
+ fileutils.delete_if_exists(self.image_file)
+ return self
+
+ def __exit__(self, exctype, excval, exctb):
+ self.make_drive()
+
+ def add_file(self, path, data):
+ self.mdfiles.append((path, data))
+
+ def _add_file(self, basedir, path, data):
+ filepath = os.path.join(basedir, path)
+ dirname = os.path.dirname(filepath)
+ fileutils.ensure_tree(dirname)
+ with open(filepath, 'wb') as f:
+ # the given data can be either text or bytes. we can only write
+ # bytes into files.
+ if isinstance(data, six.text_type):
+ data = data.encode('utf-8')
+ f.write(data)
+
+ def _write_md_files(self, basedir):
+ for data in self.mdfiles:
+ self._add_file(basedir, data[0], data[1])
+
+ def _make_iso9660(self, path, tmpdir):
+
+ processutils.execute('mkisofs',
+ '-o', path,
+ '-ldots',
+ '-allow-lowercase',
+ '-allow-multidot',
+ '-l',
+ '-V', 'config-2',
+ '-r',
+ '-J',
+ '-quiet',
+ tmpdir,
+ attempts=1,
+ run_as_root=False)
+
+ def make_drive(self):
+ """Make the config drive.
+ :raises ProcessExecuteError if a helper process has failed.
+ """
+ try:
+ tmpdir = tempfile.mkdtemp()
+ self._write_md_files(tmpdir)
+ self._make_iso9660(self.image_file, tmpdir)
+ finally:
+ shutil.rmtree(tmpdir)
+
+
+def generate(dst, hostname, domainname, instance_id=None, public_keys=None,
+ user_data=None, network_data=None, ironic_format=False):
+ ''' Generate config drive
+
+ :param dst: destination file to place config drive.
+ :param hostname: hostname of Instance.
+ :param domainname: instance domain.
+ :param instance_id: UUID of the instance.
+ :param public_keys: dict of public keys.
+ :param user_data: custom user data dictionary.
+ :param network_data: custom network info dictionary.
+ :param ironic_format: create base64 of gzipped ISO format
+
+ CLI Example:
+ .. code-block:: bash
+ salt '*' configdrive.generate dst=/tmp/my_cfgdrive.iso hostname=host1
+ '''
+ instance_md = {}
+ public_keys = public_keys or {}
+
+ instance_md['uuid'] = instance_id or uuidutils.generate_uuid()
+ instance_md['hostname'] = '%s.%s' % (hostname, domainname)
+ instance_md['name'] = hostname
+ instance_md['public_keys'] = public_keys
+
+ data = json.dumps(instance_md)
+
+ if user_data:
+ user_data = '#cloud-config\n\n' + yaml.dump(user_data, default_flow_style=False)
+
+ LOG.debug('Generating config drive for %s' % hostname)
+
+ with ConfigDriveBuilder(dst) as cfgdrive:
+ cfgdrive.add_file('openstack/latest/meta_data.json', data)
+ if user_data:
+ cfgdrive.add_file('openstack/latest/user_data', user_data)
+ if network_data:
+ cfgdrive.add_file('openstack/latest/network_data.json',
+ json.dumps(network_data))
+ cfgdrive.add_file('openstack/latest/vendor_data.json', '{}')
+ cfgdrive.add_file('openstack/latest/vendor_data2.json', '{}')
+
+ b64_gzip = None
+ if ironic_format:
+ with open(dst) as f:
+ with tempfile.NamedTemporaryFile() as tmpzipfile:
+ g = gzip.GzipFile(fileobj=tmpzipfile, mode='wb')
+ shutil.copyfileobj(f, g)
+ g.close()
+ tmpzipfile.seek(0)
+ b64_gzip = base64.encode_as_bytes(tmpzipfile.read())
+ with open(dst, 'w') as f:
+ f.write(b64_gzip)
+
+ LOG.debug('Config drive was built %s' % dst)
+ res = {}
+ res['meta-data'] = data
+ if user_data:
+ res['user-data'] = user_data
+ if b64_gzip:
+ res['base64_gzip'] = b64_gzip
+ return res