PSARC 2017/039 cloudbase-init: Portable cloud image initialization with ConfigDrive
authorSean Wilcox <sean.wilcox@oracle.com>
Fri, 24 Mar 2017 14:28:46 -0600
changeset 7799 e35d3ee6d1b8
parent 7798 fd5b3d046269
child 7800 03f2671fb364
PSARC 2017/039 cloudbase-init: Portable cloud image initialization with ConfigDrive 25615046 cloudbase-init should support configdrive on solaris 25027429 Enable cloudbase-init debug in config file
components/openstack/cloudbase-init/Makefile
components/openstack/cloudbase-init/cloudbase-init.p5m
components/openstack/cloudbase-init/files/cloudbase-init
components/openstack/cloudbase-init/files/cloudbase-init.conf
components/openstack/cloudbase-init/files/cloudbase-init.stencil
components/openstack/cloudbase-init/files/cloudbase-init.xml
components/openstack/cloudbase-init/files/solaris.py
components/openstack/cloudbase-init/patches/osconfigdrive.patch
components/openstack/nova/Makefile
components/openstack/nova/files/nova-compute
components/openstack/nova/files/nova.conf
components/openstack/nova/files/solariszones/driver.py
components/openstack/nova/files/solariszones/sysconfig.py
components/openstack/nova/nova.p5m
--- a/components/openstack/cloudbase-init/Makefile	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/cloudbase-init/Makefile	Fri Mar 24 14:28:46 2017 -0600
@@ -65,6 +65,12 @@
 COMPONENT_PRE_BUILD_ACTION = \
     $(CP) $(COMPONENT_DIR)/PKG-INFO $(SOURCE_DIR)
 
+COMPONENT_POST_INSTALL_ACTION += \
+    $(CP) \
+        files/solaris.py \
+        $(PROTO_DIR)$(PYTHON_LIB)/cloudbaseinit/metadata/services/osconfigdrive; \
+    $(PYTHON) -m compileall $(PROTO_DIR)/$(PYTHON_VENDOR_PACKAGES)
+
 # common targets
 build:		$(BUILD_NO_ARCH)
 
--- a/components/openstack/cloudbase-init/cloudbase-init.p5m	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/cloudbase-init/cloudbase-init.p5m	Fri Mar 24 14:28:46 2017 -0600
@@ -20,7 +20,7 @@
 #
 
 #
-# Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
 #
 
 set name=pkg.fmri \
@@ -39,9 +39,9 @@
 set name=org.opensolaris.arc-caseid value=PSARC/2016/010
 set name=org.opensolaris.consolidation value=$(CONSOLIDATION)
 #
-file cloudbase-init.conf path=etc/cloudbase-init.conf
 file cloudbase-init.xml path=lib/svc/manifest/application/cloudbase-init.xml
 file cloudbase-init path=lib/svc/method/cloudbase-init
+file cloudbase-init.stencil path=lib/svc/stencils/cloudbase-init.stencil
 file path=usr/bin/cloudbase-init
 file path=usr/lib/python$(PYVER)/vendor-packages/cloudbase_init-$(COMPONENT_VERSION)-py$(PYVER).egg-info/PKG-INFO
 file path=usr/lib/python$(PYVER)/vendor-packages/cloudbase_init-$(COMPONENT_VERSION)-py$(PYVER).egg-info/SOURCES.txt
@@ -67,6 +67,8 @@
 file path=usr/lib/python$(PYVER)/vendor-packages/cloudbaseinit/metadata/services/osconfigdrive/__init__.py
 file path=usr/lib/python$(PYVER)/vendor-packages/cloudbaseinit/metadata/services/osconfigdrive/base.py
 file path=usr/lib/python$(PYVER)/vendor-packages/cloudbaseinit/metadata/services/osconfigdrive/factory.py
+file files/solaris.py \
+    path=usr/lib/python$(PYVER)/vendor-packages/cloudbaseinit/metadata/services/osconfigdrive/solaris.py
 file path=usr/lib/python$(PYVER)/vendor-packages/cloudbaseinit/metadata/services/osconfigdrive/windows.py
 file path=usr/lib/python$(PYVER)/vendor-packages/cloudbaseinit/osutils/__init__.py
 file path=usr/lib/python$(PYVER)/vendor-packages/cloudbaseinit/osutils/base.py
--- a/components/openstack/cloudbase-init/files/cloudbase-init	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/cloudbase-init/files/cloudbase-init	Fri Mar 24 14:28:46 2017 -0600
@@ -1,6 +1,6 @@
 #!/usr/bin/python2.7
 
-# Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
@@ -16,6 +16,7 @@
 
 import ConfigParser
 import os
+import subprocess
 import sys
 import urllib2
 
@@ -25,21 +26,22 @@
 def start():
 
     # verify metadata service is reachable
-    try:
-        parser = ConfigParser.ConfigParser()
-        parser.read('/etc/cloudbase-init.conf')
-        if parser.has_option('DEFAULT', 'metadata_base_url'):
-            url = parser.get('DEFAULT', 'metadata_base_url')
-        else:
-            url = 'http://169.254.169.254'
-        open_url = urllib2.urlopen(url, timeout=20)
-    except Exception as err:
-        print >> sys.stderr, 'No response from %s: %s' % (url, err)
-        return smf_include.SMF_EXIT_ERR_FATAL
+    parser = ConfigParser.ConfigParser()
+    parser.read('/etc/cloudbase-init.conf')
+    if not parser.has_option('DEFAULT', 'config_drive'):
+        try:
+            if parser.has_option('DEFAULT', 'metadata_base_url'):
+                url = parser.get('DEFAULT', 'metadata_base_url')
+            else:
+                url = 'http://169.254.169.254'
+            open_url = urllib2.urlopen(url, timeout=20)
+        except Exception as err:
+            print >> sys.stderr, 'No response from %s: %s' % (url, err)
+            return smf_include.SMF_EXIT_ERR_FATAL
 
     # Initiate cloudbase-init service
-    cmd_str = "/usr/bin/cloudbase-init --debug"
-    smf_include.smf_subprocess(cmd_str)
+    cmd_str = "/usr/bin/cloudbase-init"
+    subprocess.call(cmd_str, stdout=sys.stdout, stderr=sys.stderr)
 
 if __name__ == "__main__":
     os.putenv("LC_ALL", "C")
--- a/components/openstack/cloudbase-init/files/cloudbase-init.conf	Thu Mar 23 23:22:55 2017 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-[DEFAULT]
-plugins=cloudbaseinit.plugins.common.userdata.UserDataPlugin
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openstack/cloudbase-init/files/cloudbase-init.stencil	Fri Mar 24 14:28:46 2017 -0600
@@ -0,0 +1,14 @@
+# This file is generated via SMF, and configuration should be changed by
+# setting or updating properties in the cloudbase-init:default service:instance.
+# Any modifications to this file will be overwritten on the next start of the
+# cloudbase-init.
+#
+# For example to turn on debugging do the following :
+#	svccfg -s cloudbase-init:default setprop config/debug = boolean: true
+#	svcadm refresh cloudbase-init
+
+[DEFAULT]
+; add any properties and their values that are in the config property group.
+$%/config/(.*)/ {
+$%1 = $%{config/$%1}
+}
--- a/components/openstack/cloudbase-init/files/cloudbase-init.xml	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/cloudbase-init/files/cloudbase-init.xml	Fri Mar 24 14:28:46 2017 -0600
@@ -1,7 +1,7 @@
 <?xml version="1.0" ?>
 <!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
 <!--
- Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
 
  Licensed under the Apache License, Version 2.0 (the "License"); you may
  not use this file except in compliance with the License. You may obtain
@@ -30,19 +30,21 @@
       <service_fmri value='svc:/milestone/network:default' />
     </dependency>
 
-    <logfile_attributes permissions='600'/>
+    <instance name='default' enabled='true'>
+      <logfile_attributes permissions='600'/>
+
+      <exec_method timeout_seconds="0" type="method" name="start"
+        exec="/lib/svc/method/cloudbase-init %m">
+      </exec_method>
 
-    <exec_method timeout_seconds="0" type="method" name="start"
-      exec="/lib/svc/method/cloudbase-init %m">
-    </exec_method>
-    <exec_method timeout_seconds="30" type="method" name="stop"
-      exec=":kill"/>
+      <!-- This is a transient service, so nothing to stop -->
+      <exec_method timeout_seconds="0" type="method" name="stop"
+        exec=":true"/>
 
-    <property_group name='startd' type='framework'>
-      <propval name='duration' type='astring' value='transient' />
-    </property_group>
+      <property_group name='startd' type='framework'>
+        <propval name='duration' type='astring' value='transient' />
+      </property_group>
     
-    <instance name='default' enabled='true'>
       <!-- to start/stop/refresh the service -->
       <property_group name='general' type='framework'>
         <propval name='action_authorization' type='astring'
@@ -50,6 +52,22 @@
         <propval name='value_authorization' type='astring'
                  value='solaris.smf.value.cloudbase-init' />
       </property_group>
+
+      <property_group name='cloudbase_stencil' type='configfile'>
+        <propval name='path' type='astring'
+	  value='/etc/cloudbase-init.conf' />
+        <propval name='stencil' type='astring'
+	  value='cloudbase-init.stencil' />
+        <propval name='mode' type='astring' value='0444' />
+        <propval name='owner' type='astring' value='root' />
+        <propval name='group' type='astring' value='bin' />
+      </property_group>
+
+      <property_group name='config' type='application' >
+        <propval name='plugins' type='astring'
+          value='cloudbaseinit.plugins.common.userdata.UserDataPlugin' />
+        <propval name='debug' type='boolean' value='false' />
+      </property_group>
     </instance>
 
     <template>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openstack/cloudbase-init/files/solaris.py	Fri Mar 24 14:28:46 2017 -0600
@@ -0,0 +1,138 @@
+# Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import itertools
+import os
+import shutil
+import subprocess
+
+from cloudbaseinit.metadata.services.osconfigdrive import base
+from oslo_config import cfg
+from oslo_log import log as oslo_logging
+
+opts = [
+    cfg.StrOpt('tar', default='/usr/bin/tar',
+               help='path to "tar", used to extract ISO ConfigDrive '
+                    'files'),
+]
+CONF = cfg.CONF
+CONF.register_opts(opts)
+
+LOG = oslo_logging.getLogger(__name__)
+
+
+class SolarisConfigDriveManager(base.BaseConfigDriveManager):
+
+    def __init__(self):
+        super(SolarisConfigDriveManager, self).__init__()
+
+    def _config_drive_iso_cdrom(self):
+        """ Currently we support iso_cdrom because this is the closest "set" to
+        what we can currently get into the zone.  We cannot push a file into
+        the zone without several complicated steps that would be prone to
+        breakage. So we are bringing in the ISO as a device in the zone. This
+        device can then be mounted. The way we present the device to the zone
+        is a read only cdrom like device so we will mount it as a "cdrom".
+        """
+        LOG.debug("self.target_path = %s" % self.target_path)
+        mountdir = '/root/cbi_cdrom'
+        try:
+            # The device is currently hard coded to c1d1p0
+            mounted = False
+            if not os.path.exists(mountdir):
+                os.mkdir(mountdir, 0500)
+
+            subprocess.check_call(['/usr/sbin/mount', '-F', 'hsfs',
+                                   '/dev/dsk/c1d1p0', mountdir])
+
+            mounted = True
+            chkfile = os.path.join(mountdir, 'openstack/latest/meta_data.json')
+            if os.path.exists(chkfile):
+                os.rmdir(self.target_path)
+                os.mkdir(self.target_path, 0500)
+                copycmd = ['/usr/bin/cp', '-rp']
+                for d in os.listdir(mountdir):
+                    copycmd.append(mountdir + '/' + d)
+
+                copycmd.append(self.target_path)
+                subprocess.check_call(copycmd)
+            else:
+                return False
+        except Exception as ex:
+            LOG.error("Get config drive data failed: %s" % ex)
+            return False
+        finally:
+            if mounted:
+                subprocess.call(['/usr/sbin/umount', mountdir])
+                os.rmdir(mountdir)
+            # Set the property to complete the mount/copy/umount process and
+            # release the BUILD status in the controller.
+            subprocess.call(['/usr/sbin/svccfg', '-s',
+                             'cloudbase-init:default', 'setprop',
+                             'configdrive/copydone', '=', 'true'])
+
+        return True
+
+    def _config_drive_iso_hdd(self):
+        """ Currently not supported
+        """
+        LOG.error("iso/hdd is currently not supported")
+        return False
+
+    def _config_drive_iso_partition(self):
+        """ Currently not supported
+        """
+        LOG.error("iso/partition is currently not supported")
+        return False
+
+    def _config_drive_vfat_cdrom(self):
+        """ Currently not supported
+        """
+        LOG.error("vfat/cdrom is currently not supported")
+        return False
+
+    def _config_drive_vfat_hdd(self):
+        """ Currently not supported
+        """
+        LOG.error("vfat/hdd is currently not supported")
+        return False
+
+    def _config_drive_vfat_partition(self):
+        """ Currently not supported
+        """
+        LOG.error("vfat/partition is currently not supported")
+        return False
+
+    def get_config_drive_files(self, searched_types=None,
+                               searched_locations=None):
+        # The problem we face is that the type and locations are not really
+        # relevant because we do not literally inject the config drive file
+        # into the zone.  We provide an iso as a device to the zone which can
+        # then be mounted as a cdrom (readonly device) and then the data
+        # extracted from there.  So looping through like this isn't really
+        # going to buy much, but we get simply write those methods as not
+        # supported yet.
+        #
+        # A lot of this will change once we can truly inject files into the
+        # zone.
+        for cd_type, cd_location in itertools.product(searched_types,
+                                                      searched_locations):
+            LOG.info('Looking for Config Drive %(type)s in %(location)s',
+                     {"type": cd_type, "location": cd_location})
+            find_drive = getattr(self, "_config_drive_" + cd_type + '_' +
+                                 cd_location)
+            if find_drive():
+                return True
+
+        return False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openstack/cloudbase-init/patches/osconfigdrive.patch	Fri Mar 24 14:28:46 2017 -0600
@@ -0,0 +1,17 @@
+In house patch to add the sunos5 platform to the confidrive portion of
+cloudbase-init.
+
+This patch is suitable for upstream submission.
+
+--- cloudbase-init-0.9.9/cloudbaseinit/metadata/services/osconfigdrive/factory.py.~1~	2016-11-08 10:35:08.131876634 +0000
++++ cloudbase-init-0.9.9/cloudbaseinit/metadata/services/osconfigdrive/factory.py	2016-11-08 10:35:41.632217784 +0000
+@@ -21,6 +21,8 @@ def get_config_drive_manager():
+     class_paths = {
+         'win32': 'cloudbaseinit.metadata.services.osconfigdrive.windows.'
+         'WindowsConfigDriveManager',
++        'sunos5': 'cloudbaseinit.metadata.services.osconfigdrive.solaris.'
++        'SolarisConfigDriveManager',
+     }
+ 
+     class_path = class_paths.get(sys.platform)
+
--- a/components/openstack/nova/Makefile	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/nova/Makefile	Fri Mar 24 14:28:46 2017 -0600
@@ -20,7 +20,7 @@
 #
 
 #
-# Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
 #
 
 include ../../../make-rules/shared-macros.mk
@@ -114,6 +114,7 @@
 REQUIRED_PACKAGES += library/python/sqlalchemy-27
 REQUIRED_PACKAGES += library/python/sqlalchemy-migrate-27
 REQUIRED_PACKAGES += library/python/webob-27
+REQUIRED_PACKAGES += media/cdrtools
 REQUIRED_PACKAGES += system/core-os
 REQUIRED_PACKAGES += system/file-system/zfs
 REQUIRED_PACKAGES += system/library/storage/suri
--- a/components/openstack/nova/files/nova-compute	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/nova/files/nova-compute	Fri Mar 24 14:28:46 2017 -0600
@@ -1,6 +1,6 @@
 #!/usr/bin/python2.7
 
-# Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
@@ -86,6 +86,15 @@
             check_call(['/usr/bin/pfexec', '/usr/bin/chown', 'nova:nova',
                         imagecache_path])
 
+    configdrive_path = '/var/share/nova/configdrives'
+    if not os.path.exists(configdrive_path):
+        ret = _create_dataset(configdrive_path, "VARSHARE/nova/configdrives")
+        if ret != smf_include.SMF_EXIT_OK:
+            return ret
+
+        check_call(['/usr/bin/pfexec', '/usr/bin/chown', 'nova:nova',
+                    configdrive_path])
+
     smf_include.smf_subprocess("/usr/bin/pfexec /usr/lib/nova/nova-compute")
 
 if __name__ == "__main__":
--- a/components/openstack/nova/files/nova.conf	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/nova/files/nova.conf	Fri Mar 24 14:28:46 2017 -0600
@@ -1925,7 +1925,7 @@
 
 # Name and optionally path of the tool used for ISO image creation (string
 # value)
-#mkisofs_cmd = genisoimage
+mkisofs_cmd = /usr/bin/mkisofs
 
 # Number of seconds to wait between runs of the image cache manager. Set to -1
 # to disable. Setting this to 0 will run at the default rate. (integer value)
--- a/components/openstack/nova/files/solariszones/driver.py	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/nova/files/solariszones/driver.py	Fri Mar 24 14:28:46 2017 -0600
@@ -51,6 +51,7 @@
 from oslo_utils import versionutils
 from passlib.hash import sha256_crypt
 
+from nova.api.metadata import base as instance_metadata
 from nova.api.metadata import password
 from nova.compute import arch
 from nova.compute import hv_type
@@ -72,6 +73,7 @@
 from nova.objects import flavor as flavor_obj
 from nova.objects import migrate_data as migrate_data_obj
 from nova import utils
+from nova.virt import configdrive
 from nova.virt import driver
 from nova.virt import event as virtevent
 from nova.virt import hardware
@@ -1478,6 +1480,46 @@
                                [zonemgr.Property("storage", listvalue=[suri])],
                                ignore_exists=True)
 
+    def _get_configdrive_path(self, instance):
+        cd_name = "config_drive-" + instance['name']
+
+        return os.path.join("/var/share/nova/configdrives", cd_name)
+
+    def _set_configdrive(self, name, instance, sc_dir):
+        """Set the configdrive device"""
+        zone = self._get_zone_by_name(name)
+        if zone is None:
+            raise exception.InstanceNotFound(instance_id=name)
+
+        cd_path = self._get_configdrive_path(instance)
+
+        with ZoneConfig(zone) as zc:
+            storagepath = "file://root:root@" + cd_path
+            zc.addresource("device", [zonemgr.Property("storage", storagepath),
+                                      zonemgr.Property("id", "1")])
+
+        fp = os.path.join(sc_dir, "config_drive.xml")
+        tree = sysconfig.create_config_drive()
+        sysconfig.create_sc_profile(fp, tree)
+
+    def _unset_configdrive(self, name, instance):
+        """ Remove the configdrive device from the zone"""
+        zone = self._get_zone_by_name(name)
+        if zone is None:
+            raise exception.InstanceNotFound(instance_id=name)
+
+        cd_path = self._get_configdrive_path(instance)
+
+        with ZoneConfig(zone) as zc:
+            storagepath = "file://root:root@" + cd_path
+            zc.removeresources("device",
+                               [zonemgr.Property("storage", storagepath)])
+
+        if zone.state == ZONE_STATE_RUNNING:
+            zone.apply()
+
+        os.remove(cd_path)
+
     def _set_num_cpu(self, name, vcpus, brand):
         """Set number of VCPUs in a Solaris Zone configuration."""
         zone = self._get_zone_by_name(name)
@@ -1895,6 +1937,8 @@
             self._set_memory_cap(name, instance['memory_mb'], brand)
             self._set_network(context, name, instance, network_info, brand,
                               sc_dir)
+            if configdrive.required_by(instance):
+                self._set_configdrive(name, instance, sc_dir)
         except Exception as ex:
             reason = zonemgr_strerror(ex)
             LOG.exception(_("Unable to create configuration for instance '%s' "
@@ -2199,6 +2243,47 @@
                             "via zonemgr(3RAD): %s") % (name, reason))
             raise
 
+    def _waitfor_copydone(self, name):
+        failcount = 0
+        cbi_service = 'svc:/application/cloudbase-init:default'
+        cbi_state = None
+        end_states = ['online', 'degraded', 'maintenance', 'disabled']
+        while cbi_state not in end_states:
+            try:
+                cbi_state, err = utils.execute('/usr/sbin/zlogin', '-S', name,
+                                               '/usr/bin/svcs', '-H', '-o',
+                                               'state', cbi_service)
+                cbi_state = cbi_state.strip()
+            except processutils.ProcessExecutionError:
+                # If it has been two minutes and the zone is still not able to
+                # return any kind of state, and the zlogin is failing, then
+                # simply get out of the process of protecting the config-drive
+                # but leave it attached to the zone.
+                if failcount > 120:
+                    return False
+
+                failcount = failcount + 1
+                greenthread.sleep(1)
+                continue
+
+            if cbi_state == "disabled" and cbi_state in end_states:
+                out, err = utils.execute('/usr/sbin/zlogin', '-S', name,
+                                         '/usr/bin/svcprop', '-p',
+                                         'general/enabled', cbi_service)
+                out = out.strip()
+                if out == "true":
+                    end_states.remove('disabled')
+
+            if cbi_state == "offline*":
+                out, err = utils.execute('/usr/sbin/zlogin', '-S', name,
+                                         '/usr/bin/svcprop', '-C', '-p',
+                                         'configdrive/copydone', cbi_service)
+                out = out.strip()
+                if out == "true":
+                    break
+
+        return True
+
     def spawn(self, context, instance, image_meta, injected_files,
               admin_password, network_info=None, block_device_info=None):
         """Create a new instance/VM/domain on the virtualization platform.
@@ -2270,6 +2355,18 @@
                                   dir=CONF.state_path)
         os.chmod(sc_dir, 0755)
 
+        # Create the configdrive if required.
+        if configdrive.required_by(instance):
+            instance_md = instance_metadata.InstanceMetadata(
+                              instance,
+                              content=injected_files)
+            with configdrive.ConfigDriveBuilder(instance_md=instance_md) as cd:
+                cd_path = self._get_configdrive_path(instance)
+                try:
+                    cd.make_drive(cd_path)
+                except Exception as e:
+                    LOG.info(_("Failed to create config drive '%s'" % cd_path))
+
         try:
             self._create_config(context, instance, network_info,
                                 connection_info, sc_dir, admin_password)
@@ -2281,6 +2378,10 @@
                                        instance, entry['mount_device'])
 
             self._power_on(instance, network_info)
+            if configdrive.required_by(instance):
+                unset = self._waitfor_copydone(name)
+                if unset:
+                    self._unset_configdrive(name, instance)
         except Exception as ex:
             reason = zonemgr_strerror(ex)
             LOG.exception(_("Unable to spawn instance '%s' via zonemgr(3RAD): "
@@ -2439,6 +2540,14 @@
                 self._uninstall(instance)
             if self._get_state(zone) == power_state.NOSTATE:
                 self._delete_config(instance)
+            if configdrive.required_by(instance):
+                # Make sure that we don't leave any dirt around.
+                cd_path = self._get_configdrive_path(instance)
+                try:
+                    os.remove(cd_path)
+                except OSError:
+                    pass
+
         except Exception as ex:
             reason = zonemgr_strerror(ex)
             LOG.warning(_("Unable to destroy instance '%s' via zonemgr(3RAD): "
--- a/components/openstack/nova/files/solariszones/sysconfig.py	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/nova/files/solariszones/sysconfig.py	Fri Mar 24 14:28:46 2017 -0600
@@ -1,6 +1,6 @@
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
 
-# Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 #    not use this file except in compliance with the License. You may obtain
@@ -225,6 +225,41 @@
     return svcbundle
 
 
+def create_config_drive():
+    """ return an etree object representing the configdrive options for
+    cloudbase-init
+    """
+    svcbundle = etree.Element("service_bundle", type="profile",
+                              name="cloudbase-init")
+    service = etree.SubElement(svcbundle, "service", version="1",
+                               type="service",
+                               name="application/cloudbase-init")
+    instance = etree.SubElement(service, "instance", name="default",
+                                enabled="true")
+    pg = etree.SubElement(instance, "property_group", type="application",
+                          name="config")
+    etree.SubElement(pg, "propval", type="astring", name="config_drive_types",
+                     value="iso")
+
+    etree.SubElement(pg, "propval", type="boolean", name="config_drive",
+                     value="true")
+
+    etree.SubElement(pg, "propval", type="astring",
+                     name="config_drive_locations", value="cdrom, hdd")
+
+    v = "cloudbaseinit.metadata.services.configdrive.ConfigDriveService"
+    etree.SubElement(pg, "propval", type="astring", name="metadata_services",
+                     value=v)
+
+    mntpg = etree.SubElement(instance, "property_group", type="application",
+                             name="configdrive")
+
+    etree.SubElement(mntpg, "propval", type="boolean", name="copydone",
+                     value="false")
+
+    return svcbundle
+
+
 def create_sc_profile(path, tree):
     """ create a file containing the proper XML headers and encoding for a
     given etree object
--- a/components/openstack/nova/nova.p5m	Thu Mar 23 23:22:55 2017 -0700
+++ b/components/openstack/nova/nova.p5m	Fri Mar 24 14:28:46 2017 -0600
@@ -20,7 +20,7 @@
 #
 
 #
-# Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
 #
 
 set name=pkg.fmri \
@@ -1007,6 +1007,9 @@
 # force a dependency on package delivering chown(1)
 depend type=require fmri=__TBD pkg.debug.depend.file=usr/bin/chown
 
+# force a dependency on package delivering mkisofs(8)
+depend type=require fmri=__TBD pkg.debug.depend.file=usr/bin/mkisofs
+
 # force a dependency on package delivering archiveadm(1M)
 depend type=require fmri=__TBD pkg.debug.depend.file=usr/sbin/archiveadm \
     variant.debug.container=false