20739272 Nova driver should support evacuation s11u3-sru
authorSean Wilcox <sean.wilcox@oracle.com>
Tue, 09 Feb 2016 17:07:37 -0800
branchs11u3-sru
changeset 5433 48cd54da97b5
parent 5432 e6a5784e62c3
child 5440 7d56dc1439e4
20739272 Nova driver should support evacuation 21660635 root-password functionality required for defcore coverage 22464594 Nova driver should support rebuild
components/openstack/horizon/files/overrides.py
components/openstack/nova/files/solariszones/driver.py
--- a/components/openstack/horizon/files/overrides.py	Tue Feb 09 16:26:27 2016 -0800
+++ b/components/openstack/horizon/files/overrides.py	Tue Feb 09 17:07:37 2016 -0800
@@ -85,8 +85,8 @@
     project_tables.TerminateInstance
 )
 
-# Remove 'EditInstanceSecurityGroups', 'TogglePause', 'RebuildInstance'
-# actions from Project/Instances/Actions
+# Remove 'EditInstanceSecurityGroups', 'TogglePause' actions from
+# Project/Instances/Actions
 project_tables.InstancesTable._meta.row_actions = (
     project_tables.StartInstance,
     project_tables.ConfirmResize,
@@ -102,6 +102,7 @@
     project_tables.SoftRebootInstance,
     project_tables.RebootInstance,
     project_tables.StopInstance,
+    project_tables.RebuildInstance,
     project_tables.TerminateInstance
 )
 
--- a/components/openstack/nova/files/solariszones/driver.py	Tue Feb 09 16:26:27 2016 -0800
+++ b/components/openstack/nova/files/solariszones/driver.py	Tue Feb 09 17:07:37 2016 -0800
@@ -40,6 +40,7 @@
 from eventlet import greenthread
 from lxml import etree
 from oslo_config import cfg
+from passlib.hash import sha256_crypt
 
 from nova.compute import power_state
 from nova.compute import task_states
@@ -148,6 +149,8 @@
 
 ROOTZPOOL_RESOURCE = 'rootzpool'
 
+shared_storage = ['iscsi', 'fibre_channel']
+
 def lookup_resource_property(zone, resource, prop, filter=None):
     """Lookup specified property from specified Solaris Zone resource."""
     try:
@@ -429,7 +432,7 @@
 
     capabilities = {
         "has_imagecache": False,
-        "supports_recreate": False,
+        "supports_recreate": True,
         }
 
     def __init__(self, virtapi):
@@ -789,6 +792,28 @@
         """
         raise NotImplementedError()
 
+    def _rebuild_block_devices(self, context, instance, bdms, recreate):
+        root_ci = None
+        rootmp = instance['root_device_name']
+        for entry in bdms:
+            if entry['connection_info'] is None:
+                continue
+
+            if entry['device_name'] == rootmp:
+                root_ci = jsonutils.loads(entry['connection_info'])
+                continue
+
+            if not recreate:
+                ci = jsonutils.loads(entry['connection_info'])
+                self.detach_volume(ci, instance, entry['device_name'])
+
+        if root_ci is None and recreate:
+            msg = (_("Unable to find the root device for instance '%s'.")
+                   % instance['name'])
+            raise exception.NovaException(msg)
+
+        return root_ci
+
     def rebuild(self, context, instance, image_meta, injected_files,
                 admin_password, bdms, detach_block_devices,
                 attach_block_devices, network_info=None,
@@ -829,7 +854,109 @@
         :param preserve_ephemeral: True if the default ephemeral storage
                                    partition must be preserved on rebuild
         """
-        raise NotImplementedError()
+        if recreate:
+            instance.system_metadata['evac_from'] = instance['launched_on']
+            instance.save()
+            inst_type = flavor_obj.Flavor.get_by_id(
+                nova_context.get_admin_context(read_deleted='yes'),
+                instance['instance_type_id'])
+            extra_specs = inst_type['extra_specs'].copy()
+            brand = extra_specs.get('zonecfg:brand', ZONE_BRAND_SOLARIS)
+            if brand == ZONE_BRAND_SOLARIS:
+                msg = (_("'%s' branded zones do not currently support "
+                         "evacuation.") % brand)
+                raise exception.NovaException(msg)
+
+        instance.task_state = task_states.REBUILD_BLOCK_DEVICE_MAPPING
+        instance.save(expected_task_state=[task_states.REBUILDING])
+
+        root_ci = self._rebuild_block_devices(context, instance, bdms,
+                                              recreate)
+
+        if root_ci is not None:
+            driver_type = root_ci['driver_volume_type']
+        else:
+            driver_type = 'local'
+
+        # If image_meta is provided then the --on-shared-storage option
+        # was not used.
+        if image_meta:
+            # If not then raise an exception.  But if this is a rebuild then
+            # the local storage is ok.
+            if driver_type in shared_storage and recreate:
+                msg = (_("Root device is on shared storage for instance '%s'.")
+                       % instance['name'])
+                raise exception.NovaException(msg)
+
+        else:
+            # So the root device is not expected to be local so we can move
+            # forward with building the zone.
+            if driver_type not in shared_storage:
+                msg = (_("Root device is not on shared storage for instance "
+                         "'%s'.") % instance['name'])
+                raise exception.NovaException(msg)
+
+        if not recreate:
+            self.destroy(context, instance, network_info, block_device_info)
+            if root_ci is not None:
+                self._volume_api.detach(context, root_ci['serial'])
+                self._volume_api.delete(context, root_ci['serial'])
+
+                # We need to clear the block device mapping for the root device
+                bdmobj = objects.BlockDeviceMapping()
+                bdm = bdmobj.get_by_volume_id(context, root_ci['serial'])
+                bdm.destroy(context)
+
+        instance.task_state = task_states.REBUILD_SPAWNING
+        instance.save(
+            expected_task_state=[task_states.REBUILD_BLOCK_DEVICE_MAPPING])
+
+        if recreate:
+            inst_type = flavor_obj.Flavor.get_by_id(
+                nova_context.get_admin_context(read_deleted='yes'),
+                instance['instance_type_id'])
+            extra_specs = inst_type['extra_specs'].copy()
+
+            instance.system_metadata['rebuilding'] = False
+            self._create_config(context, instance, network_info,
+                                root_ci, None)
+            del instance.system_metadata['evac_from']
+            instance.save()
+        else:
+            instance.system_metadata['rebuilding'] = True
+            self.spawn(context, instance, image_meta, injected_files,
+                       admin_password, network_info, block_device_info)
+            greenthread.sleep(15)
+            self.power_off(instance)
+
+        del instance.system_metadata['rebuilding']
+        name = instance['name']
+        zone = self._get_zone_by_name(name)
+        if zone is None:
+            raise exception.InstanceNotFound(instance_id=name)
+
+        if recreate:
+            zone.attach(['-x', 'initialize-hostdata'])
+
+        instance_uuid = instance.uuid
+        rootmp = instance['root_device_name']
+        for entry in bdms:
+            if (entry['connection_info'] is None or
+                    rootmp == entry['device_name']):
+                continue
+
+            connection_info = jsonutils.loads(entry['connection_info'])
+            mount = entry['device_name']
+            self.attach_volume(context, connection_info, instance, mount)
+
+        self._power_on(instance)
+
+        if admin_password is not None:
+            # Because there is no way to make sure a zone is ready upon
+            # returning from a boot request. We must give the zone a few
+            # seconds to boot before attempting to set the admin password.
+            greenthread.sleep(15)
+            self.set_admin_password(instance, admin_password)
 
     def _get_extra_specs(self, instance):
         """Retrieve extra_specs of an instance."""
@@ -1210,7 +1337,8 @@
             tstate = instance['task_state']
             if tstate not in [task_states.RESIZE_FINISH,
                               task_states.RESIZE_REVERTING,
-                              task_states.RESIZE_MIGRATING]:
+                              task_states.RESIZE_MIGRATING,
+                              task_states.REBUILD_SPAWNING]:
                 subnet_uuid = port['fixed_ips'][0]['subnet_id']
                 subnet = network_plugin.show_subnet(subnet_uuid)['subnet']
 
@@ -1311,7 +1439,10 @@
         tstate = instance['task_state']
         if tstate not in [task_states.RESIZE_FINISH,
                            task_states.RESIZE_REVERTING,
-                           task_states.RESIZE_MIGRATING]:
+                           task_states.RESIZE_MIGRATING,
+                           task_states.REBUILD_SPAWNING] or \
+            (tstate == task_states.REBUILD_SPAWNING and
+             instance.system_metadata['rebuilding']):
             sc_profile = extra_specs.get('install:sc_profile')
             if sc_profile is not None:
                 if os.path.isfile(sc_profile):
@@ -1772,6 +1903,18 @@
                                          block_device_info)
             return
 
+        # A destroy is issued for the original zone for an evac case.  If
+        # the evac fails we need to protect the zone from deletion when
+        # power comes back on.
+        evac_from = instance.system_metadata.get('evac_from')
+        if evac_from is not None and instance['task_state'] is None:
+            instance.host = evac_from
+            instance.node = evac_from
+            del instance.system_metadata['evac_from']
+            instance.save()
+
+            return
+
         try:
             # These methods log if problems occur so no need to double log
             # here. Just catch any stray exceptions and allow destroy to
@@ -3275,7 +3418,17 @@
         :param instance: nova.objects.instance.Instance
         :param new_password: the new password
         """
-        raise NotImplementedError()
+        name = instance['name']
+        zone = self._get_zone_by_name(name)
+        if zone is None:
+            raise exception.InstanceNotFound(instance_id=name)
+
+        if zone.state == ZONE_STATE_RUNNING:
+            out, err = utils.execute('/usr/sbin/zlogin', '-S', name,
+                                     '/usr/bin/passwd', '-p', "'%s'" %
+                                     sha256_crypt.encrypt(new_pass))
+        else:
+            raise exception.InstanceNotRunning(instance_id=name)
 
     def inject_file(self, instance, b64_path, b64_contents):
         """Writes a file on the specified instance.
@@ -3604,7 +3757,28 @@
             Used in rebuild for HA implementation and required for validation
             of access to instance shared disk files
         """
-        return False
+        bdmobj = objects.BlockDeviceMappingList
+        bdms = bdmobj.get_by_instance_uuid(
+                nova_context.get_admin_context(),
+                instance['uuid'])
+
+        root_ci = None
+        rootmp = instance['root_device_name']
+        for entry in bdms:
+            if entry['connection_info'] is None:
+                continue
+
+            if entry['device_name'] == rootmp:
+                root_ci = jsonutils.loads(entry['connection_info'])
+                break
+
+        if root_ci is None:
+            msg = (_("Unable to find the root device for instance '%s'.")
+                   % instance['name'])
+            raise exception.NovaException(msg)
+
+        driver_type = root_ci['driver_volume_type']
+        return driver_type in shared_storage
 
     def register_event_listener(self, callback):
         """Register a callback to receive events.