20370821 Users should be able to select zone type at flavour creation
authorMatt Keenan <matt.keenan@oracle.com>
Thu, 24 Nov 2016 00:59:36 +0000
changeset 7390 ed91ff551438
parent 7389 80b2f482f59f
child 7391 df75a8cb8663
20370821 Users should be able to select zone type at flavour creation PSARC/2016/609 Solaris zone brand support for flavors in OpenStack Horizon
components/openstack/horizon/files/local_settings.py
components/openstack/horizon/files/overrides.py
--- a/components/openstack/horizon/files/local_settings.py	Tue Nov 22 15:31:31 2016 -0800
+++ b/components/openstack/horizon/files/local_settings.py	Thu Nov 24 00:59:36 2016 +0000
@@ -752,3 +752,8 @@
 # to edit boot options post instance creation. If you want this disabled set
 # to False.
 SOLARIS_BOOTARGS = True
+
+# Flavor brand types exposed in Create/Edit flavors dialogs.
+# Allows a user to set preferred Solaris brand for a flavor
+# Set to False to disallow setting of brand type in flvaors
+SOLARIS_BRANDTYPE = True
--- a/components/openstack/horizon/files/overrides.py	Tue Nov 22 15:31:31 2016 -0800
+++ b/components/openstack/horizon/files/overrides.py	Thu Nov 24 00:59:36 2016 +0000
@@ -17,6 +17,9 @@
 """
 
 from django.conf import settings
+from django.conf.urls import patterns
+from django.conf.urls import url
+from django.core.urlresolvers import reverse_lazy
 from django.utils.translation import ugettext_lazy as _
 
 from horizon import exceptions
@@ -24,6 +27,9 @@
 from horizon import workflows
 
 from openstack_dashboard import api
+from openstack_dashboard.dashboards.admin.flavors import \
+    urls as flavor_urls, views as flavor_views, workflows as flavor_workflows
+from openstack_dashboard.dashboards.admin.flavors.views import INDEX_URL
 from openstack_dashboard.dashboards.admin.instances.forms import \
     LiveMigrateForm
 from openstack_dashboard.dashboards.admin.instances.tables import \
@@ -37,6 +43,10 @@
 from openstack_dashboard.dashboards.project.instances.workflows import \
     create_instance, update_instance
 
+SOLARIS_ZONE_TYPE_CHOICES = (('solaris-kz', 'Solaris Kernel Zone'),
+                             ('solaris', 'Solaris Non-Global Zone'))
+SOLARIS_ZONE_TYPE_DEFAULT = SOLARIS_ZONE_TYPE_CHOICES[0][0]
+
 
 # Bootargs feature:
 # Add bootargs feature to 'SetAdvanced' workflow action.
@@ -178,3 +188,178 @@
 temp = list(ImagesTable._meta.row_actions)
 temp.remove(CreateVolumeFromImage)
 ImagesTable._meta.row_actions = tuple(temp)
+
+
+# Solaris brand type feature.
+# Override CreateFlavorInfoAction adding Solaris Zone Brand drop down
+# if SOLARIS_BRANDTYPE is set to True
+class SolarisCreateFlavorInfoAction(flavor_workflows.CreateFlavorInfoAction):
+    if getattr(settings, 'SOLARIS_BRANDTYPE', True):
+        zone_brand = forms.ChoiceField(label=_("Solaris Zone Brand"),
+                                       required=True,
+                                       choices=SOLARIS_ZONE_TYPE_CHOICES)
+
+    class Meta(object):
+        name = _("Flavor Information")
+        help_text = _("Flavors define the sizes for RAM, disk, number of "
+                      "cores, and other resources and can be selected when "
+                      "users deploy instances.")
+
+
+# Solaris brand type feature.
+# Override UpdateFlavorInfoAction adding Solaris Zone Brand drop down
+# if SOLARIS_BRANDTYPE is set to True
+class SolarisUpdateFlavorInfoAction(flavor_workflows.UpdateFlavorInfoAction):
+    if getattr(settings, 'SOLARIS_BRANDTYPE', True):
+        zone_brand = forms.ChoiceField(label=_("Solaris Zone Brand"),
+                                       required=True,
+                                       choices=SOLARIS_ZONE_TYPE_CHOICES)
+
+    class Meta(object):
+        name = _("Flavor Information")
+        slug = 'update_info'
+        help_text = _("Edit the flavor details. Flavors define the sizes for "
+                      "RAM, disk, number of cores, and other resources. "
+                      "Flavors are selected when users deploy instances.")
+
+
+# Solaris brand type feature.
+# Override CreateFlavor ensuring brand type metadata is saved to flavor
+class SolarisCreateFlavor(flavor_workflows.CreateFlavor):
+    def handle(self, request, data):
+        flavor_id = data.get('flavor_id') or 'auto'
+        swap = data.get('swap_mb') or 0
+        ephemeral = data.get('eph_gb') or 0
+        flavor_access = data['flavor_access']
+        is_public = not flavor_access
+        rxtx_factor = data.get('rxtx_factor') or 1
+        zone_brand = data.get('zone_brand')
+        metadata = {'zonecfg:brand': zone_brand}
+
+        # Create the flavor
+        try:
+            self.object = api.nova.flavor_create(request,
+                                                 name=data['name'],
+                                                 memory=data['memory_mb'],
+                                                 vcpu=data['vcpus'],
+                                                 disk=data['disk_gb'],
+                                                 ephemeral=ephemeral,
+                                                 swap=swap,
+                                                 flavorid=flavor_id,
+                                                 is_public=is_public,
+                                                 rxtx_factor=rxtx_factor,
+                                                 metadata=metadata)
+        except Exception:
+            exceptions.handle(request, _('Unable to create flavor.'))
+            return False
+
+        # Update flavor access if the new flavor is not public
+        flavor_id = self.object.id
+        for project in flavor_access:
+            try:
+                api.nova.add_tenant_to_flavor(
+                    request, flavor_id, project)
+            except Exception:
+                exceptions.handle(
+                    request,
+                    _('Unable to set flavor access for project %s.') % project)
+        return True
+
+
+# Solaris brand type feature.
+# Override UpdateFlavor ensuring brand type metadata is saved to flavor
+class SolarisUpdateFlavor(flavor_workflows.UpdateFlavor):
+    def handle(self, request, data):
+        flavor_projects = data["flavor_access"]
+        is_public = not flavor_projects
+
+        # Update flavor information
+        try:
+            flavor_id = data['flavor_id']
+            # Grab any existing extra specs, because flavor edit is currently
+            # implemented as a delete followed by a create.
+            extras_dict = api.nova.flavor_get_extras(self.request,
+                                                     flavor_id,
+                                                     raw=True)
+            extras_dict['zonecfg:brand'] = data['zone_brand']
+            # Mark the existing flavor as deleted.
+            api.nova.flavor_delete(request, flavor_id)
+            # Then create a new flavor with the same name but a new ID.
+            # This is in the same try/except block as the delete call
+            # because if the delete fails the API will error out because
+            # active flavors can't have the same name.
+            flavor = api.nova.flavor_create(request,
+                                            data['name'],
+                                            data['memory_mb'],
+                                            data['vcpus'],
+                                            data['disk_gb'],
+                                            ephemeral=data['eph_gb'],
+                                            swap=data['swap_mb'],
+                                            is_public=is_public,
+                                            rxtx_factor=data['rxtx_factor'])
+            if (extras_dict):
+                api.nova.flavor_extra_set(request, flavor.id, extras_dict)
+        except Exception:
+            exceptions.handle(request, ignore=True)
+            return False
+
+        # Add flavor access if the flavor is not public.
+        for project in flavor_projects:
+            try:
+                api.nova.add_tenant_to_flavor(request, flavor.id, project)
+            except Exception:
+                exceptions.handle(request, _('Modified flavor information, '
+                                             'but unable to modify flavor '
+                                             'access.'))
+        return True
+
+
+# Solaris brand type feature.
+# Override UpdateView ensuring brand type metadata is retrieved from flavor
+class SolarisUpdateView(flavor_views.UpdateView):
+    def get_initial(self):
+        flavor_id = self.kwargs['id']
+
+        try:
+            # Get initial flavor information
+            flavor = api.nova.flavor_get(self.request, flavor_id,
+                                         get_extras=True)
+        except Exception:
+            exceptions.handle(self.request,
+                              _('Unable to retrieve flavor details.'),
+                              redirect=reverse_lazy(INDEX_URL))
+
+        if "zonecfg:brand" not in flavor.extras:
+            zone_brand = SOLARIS_ZONE_TYPE_DEFAULT
+        else:
+            zone_brand = flavor.extras['zonecfg:brand']
+
+        return {'flavor_id': flavor.id,
+                'name': flavor.name,
+                'vcpus': flavor.vcpus,
+                'memory_mb': flavor.ram,
+                'disk_gb': flavor.disk,
+                'swap_mb': flavor.swap or 0,
+                'rxtx_factor': flavor.rxtx_factor or 1,
+                'eph_gb': getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral', None),
+                'zone_brand': zone_brand}
+
+
+# Solaris brand type feature.
+# Various overrides only peformed if SOLARIS_BRANDTYPE is set to True
+if getattr(settings, 'SOLARIS_BRANDTYPE', True):
+    flavor_workflows.CreateFlavorInfo.action_class = \
+        SolarisCreateFlavorInfoAction
+    flavor_workflows.CreateFlavorInfo.contributes += ('zone_brand',)
+    flavor_workflows.UpdateFlavorInfo.action_class = \
+        SolarisUpdateFlavorInfoAction
+    flavor_workflows.UpdateFlavorInfo.contributes += ('zone_brand',)
+    flavor_views.CreateView.workflow_class = SolarisCreateFlavor
+    flavor_views.UpdateView.workflow_class = SolarisUpdateFlavor
+    flavor_urls.urlpatterns = patterns(
+        'openstack_dashboard.dashboards.admin.flavors.views',
+        url(r'^$', flavor_views.IndexView.as_view(), name='index'),
+        url(r'^create/$', flavor_views.CreateView.as_view(), name='create'),
+        url(r'^(?P<id>[^/]+)/update/$', SolarisUpdateView.as_view(),
+            name='update'),
+    )