# HG changeset patch # User Matt Keenan # Date 1479949176 0 # Node ID ed91ff55143811c72fc8f25c802eeca40a748df5 # Parent 80b2f482f59f5a78e60c16ffaa96249ec7b2115a 20370821 Users should be able to select zone type at flavour creation PSARC/2016/609 Solaris zone brand support for flavors in OpenStack Horizon diff -r 80b2f482f59f -r ed91ff551438 components/openstack/horizon/files/local_settings.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 diff -r 80b2f482f59f -r ed91ff551438 components/openstack/horizon/files/overrides.py --- 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[^/]+)/update/$', SolarisUpdateView.as_view(), + name='update'), + )