class UpdateProviderNetworkRange(forms.SelfHandlingForm): failure_url = 'horizon:admin:datanets:datanets:detail' providernet_id = forms.CharField(widget=forms.HiddenInput()) providernet_range_id = forms.CharField(widget=forms.HiddenInput()) name = forms.CharField( max_length=255, label=_("Name"), required=False, widget=forms.TextInput(attrs={'readonly': 'readonly'})) description = forms.CharField(max_length=255, label=_("Description"), required=False) minimum = forms.IntegerField(label=_("Minimum"), min_value=1) maximum = forms.IntegerField(label=_("Maximum"), min_value=1) shared = forms.BooleanField(widget=forms.HiddenInput(), required=False) tenant_id = forms.CharField(widget=forms.HiddenInput(), required=False) # VXLAN specific fields mode_widget = forms.TextInput(attrs={'readonly': 'readonly'}) mode = forms.CharField(label=_("mode"), required=False, widget=mode_widget) group_widget = forms.TextInput(attrs={'readonly': 'readonly'}) group = forms.CharField(max_length=255, label=_("Multicast Group Address"), required=False, widget=group_widget) port_widget = forms.RadioSelect(attrs={'disabled': 'disabled'}) port_choices = [('4789', _('IANA Assigned VXLAN UDP port (4789)')), ('4790', _('IANA Assigned VXLAN-GPE UDP port (4790)')), ('8472', _('Legacy VXLAN UDP port (8472)'))] port = forms.ChoiceField(label=_("UDP Port"), required=False, widget=port_widget, choices=port_choices) ttl_widget = forms.TextInput(attrs={'readonly': 'readonly'}) ttl = forms.IntegerField(label=_("TTL"), required=False, widget=ttl_widget) def __init__(self, request, *args, **kwargs): super(UpdateProviderNetworkRange, self).__init__(request, *args, **kwargs) initial = kwargs['initial'] if 'mode' not in initial: del self.fields["mode"] if 'group' not in initial or initial.get('mode') == 'static': del self.fields["group"] if 'port' not in initial: del self.fields["port"] if 'ttl' not in initial: del self.fields["ttl"] def handle(self, request, data): try: params = { 'description': data['description'], 'minimum': data['minimum'], 'maximum': data['maximum'] } providernet_range = stx_api.neutron.provider_network_range_modify( request, data['providernet_range_id'], **params) msg = (_('Provider network range %s was successfully updated.') % data['providernet_range_id']) LOG.debug(msg) messages.success(request, msg) return providernet_range except neutron_exceptions.NeutronClientException as e: LOG.info(str(e)) redirect = reverse('horizon:admin:datanets:datanets:' 'detail', args=(data['providernet_id'], )) exceptions.handle(request, str(e), redirect=redirect) except Exception: msg = (_('Failed to update provider network range %s') % data['providernet_range_id']) LOG.info(msg) redirect = reverse(self.failure_url, args=[data['providernet_id']]) exceptions.handle(request, msg, redirect=redirect)
class CreateImageForm(forms.SelfHandlingForm): name = forms.CharField(max_length="255", label=_("Name"), required=True) copy_from = forms.CharField(max_length="255", label=_("Image Location"), help_text=_("An external (HTTP) URL to load " "the image from."), required=True) disk_format = forms.ChoiceField( label=_('Format'), required=True, choices=[('', ''), ('aki', _('AKI - Amazon Kernel ' 'Image')), ('ami', _('AMI - Amazon Machine ' 'Image')), ('ari', _('ARI - Amazon Ramdisk ' 'Image')), ('iso', _('ISO - Optical Disk Image')), ('qcow2', _('QCOW2 - QEMU Emulator')), ('raw', 'Raw'), ('vdi', 'VDI'), ('vhd', 'VHD'), ('vmdk', 'VMDK')], widget=forms.Select(attrs={'class': 'switchable'})) minimum_disk = forms.IntegerField(label=_("Minimum Disk (GB)"), help_text=_( 'The minimum disk size' ' required to boot the' ' image. If unspecified, this' ' value defaults to 0' ' (no minimum).'), required=False) minimum_ram = forms.IntegerField(label=_("Minimum Ram (MB)"), help_text=_('The minimum disk size' ' required to boot the' ' image. If unspecified, this' ' value defaults to 0 (no' ' minimum).'), required=False) is_public = forms.BooleanField(label=_("Public"), required=False) def handle(self, request, data): # Glance does not really do anything with container_format at the # moment. It requires it is set to the same disk_format for the three # Amazon image types, otherwise it just treats them as 'bare.' As such # we will just set that to be that here instead of bothering the user # with asking them for information we can already determine. if data['disk_format'] in ( 'ami', 'aki', 'ari', ): container_format = data['disk_format'] else: container_format = 'bare' meta = { 'is_public': data['is_public'], 'disk_format': data['disk_format'], 'container_format': container_format, 'copy_from': data['copy_from'], 'min_disk': (data['minimum_disk'] or 0), 'min_ram': (data['minimum_ram'] or 0), 'name': data['name'] } try: image = api.glance.image_create(request, **meta) messages.success( request, _('Your image %s has been queued for creation.' % data['name'])) return image except: exceptions.handle(request, _('Unable to create new image.'))
class GeneralConfigAction(workflows.Action): nodegroup_name = forms.CharField(label=_("Template Name")) description = forms.CharField(label=_("Description"), required=False, widget=forms.Textarea(attrs={'rows': 4})) flavor = forms.ChoiceField(label=_("OpenStack Flavor")) availability_zone = forms.ChoiceField( label=_("Availability Zone"), help_text=_("Launch instances in this availability zone."), required=False, widget=forms.Select(attrs={"class": "availability_zone_field"})) storage = forms.ChoiceField( label=_("Storage location"), help_text=_("Choose a storage location"), choices=[], widget=forms.Select(attrs={ "class": "storage_field switchable", 'data-slug': 'storage_loc' })) volumes_per_node = forms.IntegerField( label=_("Volumes per node"), required=False, initial=1, widget=forms.TextInput( attrs={ "class": "volume_per_node_field switched", "data-switch-on": "storage_loc", "data-storage_loc-cinder_volume": _('Volumes per node') })) volumes_size = forms.IntegerField( label=_("Volumes size (GB)"), required=False, initial=10, widget=forms.TextInput( attrs={ "class": "volume_size_field switched", "data-switch-on": "storage_loc", "data-storage_loc-cinder_volume": _('Volumes size (GB)') })) volume_type = forms.ChoiceField( label=_("Volumes type"), required=False, widget=forms.Select( attrs={ "class": "volume_type_field switched", "data-switch-on": "storage_loc", "data-storage_loc-cinder_volume": _('Volumes type') })) volume_local_to_instance = forms.BooleanField( label=_("Volume local to instance"), required=False, help_text=_("Instance and attached volumes will be created on the " "same physical host"), widget=forms.CheckboxInput( attrs={ "class": "volume_local_to_instance_field switched", "data-switch-on": "storage_loc", "data-storage_loc-cinder_volume": _('Volume local to instance') })) volumes_availability_zone = forms.ChoiceField( label=_("Volumes Availability Zone"), help_text=_("Create volumes in this availability zone."), required=False, widget=forms.Select( attrs={ "class": "volumes_availability_zone_field switched", "data-switch-on": "storage_loc", "data-storage_loc-cinder_volume": _( 'Volumes Availability Zone') })) image = forms.DynamicChoiceField(label=_("Base Image"), required=False, add_item_link=BASE_IMAGE_URL) hidden_configure_field = forms.CharField( required=False, widget=forms.HiddenInput(attrs={"class": "hidden_configure_field"})) def __init__(self, request, *args, **kwargs): super(GeneralConfigAction, self).__init__(request, *args, **kwargs) hlps = helpers.Helpers(request) plugin, hadoop_version = ( workflow_helpers.get_plugin_and_hadoop_version(request)) if not saharaclient.SAHARA_AUTO_IP_ALLOCATION_ENABLED: pools = neutron.floating_ip_pools_list(request) pool_choices = [(pool.id, pool.name) for pool in pools] pool_choices.insert(0, (None, "Do not assign floating IPs")) self.fields['floating_ip_pool'] = forms.ChoiceField( label=_("Floating IP Pool"), choices=pool_choices, required=False) self.fields["use_autoconfig"] = forms.BooleanField( label=_("Auto-configure"), help_text=_("If selected, instances of a node group will be " "automatically configured during cluster " "creation. Otherwise you should manually specify " "configuration values."), required=False, widget=forms.CheckboxInput(), initial=True, ) self.fields["proxygateway"] = forms.BooleanField( label=_("Proxy Gateway"), widget=forms.CheckboxInput(), help_text=_("Sahara will use instances of this node group to " "access other cluster instances."), required=False) self.fields['is_public'] = acl_utils.get_is_public_form( _("node group template")) self.fields['is_protected'] = acl_utils.get_is_protected_form( _("node group template")) self.fields["plugin_name"] = forms.CharField( widget=forms.HiddenInput(), initial=plugin) self.fields["hadoop_version"] = forms.CharField( widget=forms.HiddenInput(), initial=hadoop_version) self.fields["storage"].choices = storage_choices(request) node_parameters = hlps.get_general_node_group_configs( plugin, hadoop_version) for param in node_parameters: self.fields[param.name] = workflow_helpers.build_control(param) # when we copy or edit a node group template then # request contains valuable info in both GET and POST methods req = request.GET.copy() req.update(request.POST) if req.get("guide_template_type"): self.fields["guide_template_type"] = forms.CharField( required=False, widget=forms.HiddenInput(), initial=req.get("guide_template_type")) if is_cinder_enabled(request): volume_types = cinder.volume_type_list(request) else: volume_types = [] self.fields['volume_type'].choices = [(None, _("No volume type"))] + \ [(type.name, type.name) for type in volume_types] def populate_flavor_choices(self, request, context): flavors = nova_utils.flavor_list(request) if flavors: return nova_utils.sort_flavor_list(request, flavors) return [] def populate_availability_zone_choices(self, request, context): # The default is None, i.e. not specifying any availability zone az_list = [(None, _('No availability zone specified'))] az_list.extend([(az.zoneName, az.zoneName) for az in nova_utils.availability_zone_list(request) if az.zoneState['available']]) return az_list def populate_volumes_availability_zone_choices(self, request, context): az_list = [(None, _('No availability zone specified'))] if is_cinder_enabled(request): az_list.extend([ (az.zoneName, az.zoneName) for az in cinder_utils.availability_zone_list(request) if az.zoneState['available'] ]) return az_list def populate_image_choices(self, request, context): return workflow_helpers.populate_image_choices(self, request, context, empty_choice=True) def get_help_text(self): extra = dict() plugin_name, hadoop_version = ( workflow_helpers.get_plugin_and_hadoop_version(self.request)) extra["plugin_name"] = plugin_name extra["hadoop_version"] = hadoop_version plugin = saharaclient.plugin_get_version_details( self.request, plugin_name, hadoop_version) extra["deprecated"] = workflow_helpers.is_version_of_plugin_deprecated( plugin, hadoop_version) return super(GeneralConfigAction, self).get_help_text(extra) class Meta(object): name = _("Configure Node Group Template") help_text_template = "nodegroup_templates/_configure_general_help.html"
class CreateStackForm(forms.SelfHandlingForm): param_prefix = '__param_' class Meta: name = _('Create Stack') template_data = forms.CharField( widget=forms.widgets.HiddenInput, required=False) template_url = forms.CharField( widget=forms.widgets.HiddenInput, required=False) environment_data = forms.CharField( widget=forms.widgets.HiddenInput, required=False) parameters = forms.CharField( widget=forms.widgets.HiddenInput) stack_name = forms.RegexField( max_length='255', label=_('Stack Name'), help_text=_('Name of the stack to create.'), regex=r"^[a-zA-Z][a-zA-Z0-9_.-]*$", error_messages={'invalid': _('Name must start with a letter and may ' 'only contain letters, numbers, underscores, ' 'periods and hyphens.')}) timeout_mins = forms.IntegerField( initial=60, label=_('Creation Timeout (minutes)'), help_text=_('Stack creation timeout in minutes.')) enable_rollback = forms.BooleanField( label=_('Rollback On Failure'), help_text=_('Enable rollback on create/update failure.'), required=False) def __init__(self, *args, **kwargs): parameters = kwargs.pop('parameters') # special case: load template data from API, not passed in params if(kwargs.get('validate_me')): parameters = kwargs.pop('validate_me') super(CreateStackForm, self).__init__(*args, **kwargs) self._build_parameter_fields(parameters) def _build_parameter_fields(self, template_validate): self.fields['password'] = forms.CharField( label=_('Password for user "%s"') % self.request.user.username, help_text=_('This is required for operations to be performed ' 'throughout the lifecycle of the stack'), widget=forms.PasswordInput()) self.help_text = template_validate['Description'] params = template_validate.get('Parameters', {}) for param_key, param in params.items(): field_key = self.param_prefix + param_key field_args = { 'initial': param.get('Default', None), 'label': param.get('Label', param_key), 'help_text': param.get('Description', ''), 'required': param.get('Default', None) is None } param_type = param.get('Type', None) hidden = strutils.bool_from_string(param.get('NoEcho', 'false')) if 'AllowedValues' in param: choices = map(lambda x: (x, x), param['AllowedValues']) field_args['choices'] = choices field = forms.ChoiceField(**field_args) elif param_type in ('CommaDelimitedList', 'String'): if 'MinLength' in param: field_args['min_length'] = int(param['MinLength']) field_args['required'] = param.get('MinLength', 0) > 0 if 'MaxLength' in param: field_args['max_length'] = int(param['MaxLength']) if hidden: field_args['widget'] = forms.PasswordInput() field = forms.CharField(**field_args) elif param_type == 'Number': if 'MinValue' in param: field_args['min_value'] = int(param['MinValue']) if 'MaxValue' in param: field_args['max_value'] = int(param['MaxValue']) field = forms.IntegerField(**field_args) self.fields[field_key] = field @sensitive_variables('password') def handle(self, request, data): prefix_length = len(self.param_prefix) params_list = [(k[prefix_length:], v) for (k, v) in six.iteritems(data) if k.startswith(self.param_prefix)] fields = { 'stack_name': data.get('stack_name'), 'timeout_mins': data.get('timeout_mins'), 'disable_rollback': not(data.get('enable_rollback')), 'parameters': dict(params_list), 'password': data.get('password') } if data.get('template_data'): fields['template'] = data.get('template_data') else: fields['template_url'] = data.get('template_url') if data.get('environment_data'): fields['environment'] = data.get('environment_data') try: api.heat.stack_create(self.request, **fields) messages.success(request, _("Stack creation started.")) return True except Exception: exceptions.handle(request)
class SetInstanceDetailsAction(workflows.Action): availability_zone = forms.ThemableChoiceField(label=_("Availability Zone"), required=False) name = forms.CharField(label=_("Instance Name"), max_length=255) flavor = forms.ThemableChoiceField(label=_("Flavor"), help_text=_("Size of image to launch.")) count = forms.IntegerField(label=_("Number of Instances"), min_value=1, initial=1) source_type = forms.ThemableChoiceField(label=_("Instance Boot Source"), help_text=_( "Choose Your Boot Source " "Type.")) instance_snapshot_id = forms.ThemableChoiceField( label=_("Instance Snapshot"), required=False) volume_id = forms.ThemableChoiceField(label=_("Volume"), required=False) volume_snapshot_id = forms.ThemableChoiceField(label=_("Volume Snapshot"), required=False) image_id = forms.ChoiceField( label=_("Image Name"), required=False, widget=forms.ThemableSelectWidget(data_attrs=('volume_size', ), transform=lambda x: ("%s (%s)" % (x.name, filesizeformat(x.bytes))))) volume_size = forms.IntegerField(label=_("Device size (GB)"), initial=1, min_value=0, required=False, help_text=_("Volume size in gigabytes " "(integer value).")) device_name = forms.CharField(label=_("Device Name"), required=False, initial="vda", help_text=_("Volume mount point (e.g. 'vda' " "mounts at '/dev/vda'). Leave " "this field blank to let the " "system choose a device name " "for you.")) vol_delete_on_instance_delete = forms.BooleanField( label=_("Delete Volume on Instance Delete"), initial=False, required=False, help_text=_("Delete volume when the instance is deleted")) class Meta(object): name = _("Details") help_text_template = ("project/instances/" "_launch_details_help.html") def __init__(self, request, context, *args, **kwargs): self._init_images_cache() self.request = request self.context = context super(SetInstanceDetailsAction, self).__init__(request, context, *args, **kwargs) # Hide the device field if the hypervisor doesn't support it. if not nova.can_set_mount_point(): self.fields['device_name'].widget = forms.widgets.HiddenInput() source_type_choices = [ ('', _("Select source")), ("image_id", _("Boot from image")), ("instance_snapshot_id", _("Boot from snapshot")), ] if cinder.is_volume_service_enabled(request): source_type_choices.append(("volume_id", _("Boot from volume"))) try: if api.nova.extension_supported("BlockDeviceMappingV2Boot", request): source_type_choices.append( ("volume_image_id", _("Boot from image (creates a new volume)"))) except Exception: exceptions.handle( request, _('Unable to retrieve extensions ' 'information.')) source_type_choices.append( ("volume_snapshot_id", _("Boot from volume snapshot (creates a new volume)"))) self.fields['source_type'].choices = source_type_choices @memoized.memoized_method def _get_flavor(self, flavor_id): try: # We want to retrieve details for a given flavor, # however flavor_list uses a memoized decorator # so it is used instead of flavor_get to reduce the number # of API calls. flavors = instance_utils.flavor_list(self.request) flavor = [x for x in flavors if x.id == flavor_id][0] except IndexError: flavor = None return flavor @memoized.memoized_method def _get_image(self, image_id): try: # We want to retrieve details for a given image, # however get_available_images uses a cache of image list, # so it is used instead of image_get to reduce the number # of API calls. images = image_utils.get_available_images( self.request, self.context.get('project_id'), self._images_cache) image = [x for x in images if x.id == image_id][0] except IndexError: image = None return image def _check_quotas(self, cleaned_data): count = cleaned_data.get('count', 1) # Prevent launching more instances than the quota allows usages = quotas.tenant_quota_usages(self.request) available_count = usages['instances']['available'] if available_count < count: error_message = ungettext_lazy( 'The requested instance cannot be launched as you only ' 'have %(avail)i of your quota available. ', 'The requested %(req)i instances cannot be launched as you ' 'only have %(avail)i of your quota available.', count) params = {'req': count, 'avail': available_count} raise forms.ValidationError(error_message % params) source_type = cleaned_data.get('source_type') if source_type in ('volume_image_id', 'volume_snapshot_id'): available_volume = usages['volumes']['available'] if available_volume < count: msg = (_('The requested instance cannot be launched. ' 'Requested volume exceeds quota: Available: ' '%(avail)s, Requested: %(req)s.') % { 'avail': available_volume, 'req': count }) raise forms.ValidationError(msg) flavor_id = cleaned_data.get('flavor') flavor = self._get_flavor(flavor_id) count_error = [] # Validate cores and ram. available_cores = usages['cores']['available'] if flavor and available_cores < count * flavor.vcpus: count_error.append( _("Cores(Available: %(avail)s, " "Requested: %(req)s)") % { 'avail': available_cores, 'req': count * flavor.vcpus }) available_ram = usages['ram']['available'] if flavor and available_ram < count * flavor.ram: count_error.append( _("RAM(Available: %(avail)s, " "Requested: %(req)s)") % { 'avail': available_ram, 'req': count * flavor.ram }) if count_error: value_str = ", ".join(count_error) msg = (_('The requested instance cannot be launched. ' 'The following requested resource(s) exceed ' 'quota(s): %s.') % value_str) if count == 1: self._errors['flavor'] = self.error_class([msg]) else: self._errors['count'] = self.error_class([msg]) def _check_flavor_for_image(self, cleaned_data): # Prevents trying to launch an image needing more resources. image_id = cleaned_data.get('image_id') image = self._get_image(image_id) flavor_id = cleaned_data.get('flavor') flavor = self._get_flavor(flavor_id) if not image or not flavor: return props_mapping = (("min_ram", "ram"), ("min_disk", "disk")) for iprop, fprop in props_mapping: if getattr(image, iprop) > 0 and \ getattr(image, iprop) > getattr(flavor, fprop): msg = (_("The flavor '%(flavor)s' is too small " "for requested image.\n" "Minimum requirements: " "%(min_ram)s MB of RAM and " "%(min_disk)s GB of Root Disk.") % { 'flavor': flavor.name, 'min_ram': image.min_ram, 'min_disk': image.min_disk }) self._errors['image_id'] = self.error_class([msg]) break # Not necessary to continue the tests. def _check_volume_for_image(self, cleaned_data): image_id = cleaned_data.get('image_id') image = self._get_image(image_id) volume_size = cleaned_data.get('volume_size') if not image or not volume_size: return volume_size = int(volume_size) img_gigs = functions.bytes_to_gigabytes(image.size) smallest_size = max(img_gigs, image.min_disk) if volume_size < smallest_size: msg = (_("The Volume size is too small for the" " '%(image_name)s' image and has to be" " greater than or equal to " "'%(smallest_size)d' GB.") % { 'image_name': image.name, 'smallest_size': smallest_size }) self._errors['volume_size'] = self.error_class([msg]) def _check_source_image(self, cleaned_data): if not cleaned_data.get('image_id'): msg = _("You must select an image.") self._errors['image_id'] = self.error_class([msg]) else: self._check_flavor_for_image(cleaned_data) def _check_source_volume_image(self, cleaned_data): volume_size = self.data.get('volume_size', None) if not volume_size: msg = _("You must set volume size") self._errors['volume_size'] = self.error_class([msg]) if float(volume_size) <= 0: msg = _("Volume size must be greater than 0") self._errors['volume_size'] = self.error_class([msg]) if not cleaned_data.get('image_id'): msg = _("You must select an image.") self._errors['image_id'] = self.error_class([msg]) return else: self._check_flavor_for_image(cleaned_data) self._check_volume_for_image(cleaned_data) def _check_source_instance_snapshot(self, cleaned_data): # using the array form of get blows up with KeyError # if instance_snapshot_id is nil if not cleaned_data.get('instance_snapshot_id'): msg = _("You must select a snapshot.") self._errors['instance_snapshot_id'] = self.error_class([msg]) def _check_source_volume(self, cleaned_data): if not cleaned_data.get('volume_id'): msg = _("You must select a volume.") self._errors['volume_id'] = self.error_class([msg]) # Prevent launching multiple instances with the same volume. # TODO(gabriel): is it safe to launch multiple instances with # a snapshot since it should be cloned to new volumes? count = cleaned_data.get('count', 1) if count > 1: msg = _('Launching multiple instances is only supported for ' 'images and instance snapshots.') raise forms.ValidationError(msg) def _check_source_volume_snapshot(self, cleaned_data): if not cleaned_data.get('volume_snapshot_id'): msg = _("You must select a snapshot.") self._errors['volume_snapshot_id'] = self.error_class([msg]) def _check_source(self, cleaned_data): # Validate our instance source. source_type = self.data.get('source_type', None) source_check_methods = { 'image_id': self._check_source_image, 'volume_image_id': self._check_source_volume_image, 'instance_snapshot_id': self._check_source_instance_snapshot, 'volume_id': self._check_source_volume, 'volume_snapshot_id': self._check_source_volume_snapshot } check_method = source_check_methods.get(source_type) if check_method: check_method(cleaned_data) def clean(self): cleaned_data = super(SetInstanceDetailsAction, self).clean() self._check_quotas(cleaned_data) self._check_source(cleaned_data) return cleaned_data def populate_flavor_choices(self, request, context): return instance_utils.flavor_field_data(request, False) def populate_availability_zone_choices(self, request, context): try: zones = api.nova.availability_zone_list(request) except Exception: zones = [] exceptions.handle(request, _('Unable to retrieve availability zones.')) zone_list = [(zone.zoneName, zone.zoneName) for zone in zones if zone.zoneState['available']] zone_list.sort() if not zone_list: zone_list.insert(0, ("", _("No availability zones found"))) elif len(zone_list) > 1: zone_list.insert(0, ("", _("Any Availability Zone"))) return zone_list def get_help_text(self, extra_context=None): extra = {} if extra_context is None else dict(extra_context) try: extra['usages'] = api.nova.tenant_absolute_limits(self.request, reserved=True) extra['usages_json'] = json.dumps(extra['usages']) flavors = json.dumps( [f._info for f in instance_utils.flavor_list(self.request)]) extra['flavors'] = flavors images = image_utils.get_available_images( self.request, self.initial['project_id'], self._images_cache) if images is not None: attrs = [{ 'id': i.id, 'min_disk': getattr(i, 'min_disk', 0), 'min_ram': getattr(i, 'min_ram', 0), 'size': functions.bytes_to_gigabytes(i.size) } for i in images] extra['images'] = json.dumps(attrs) except Exception: exceptions.handle(self.request, _("Unable to retrieve quota information.")) return super(SetInstanceDetailsAction, self).get_help_text(extra) def _init_images_cache(self): if not hasattr(self, '_images_cache'): self._images_cache = {} def _get_volume_display_name(self, volume): if hasattr(volume, "volume_id"): vol_type = "snap" visible_label = _("Snapshot") else: vol_type = "vol" visible_label = _("Volume") return (("%s:%s" % (volume.id, vol_type)), (_("%(name)s - %(size)s GB (%(label)s)") % { 'name': volume.name, 'size': volume.size, 'label': visible_label })) def populate_image_id_choices(self, request, context): choices = [] images = image_utils.get_available_images(request, context.get('project_id'), self._images_cache) for image in images: image.bytes = getattr(image, 'virtual_size', None) or image.size image.volume_size = max(image.min_disk, functions.bytes_to_gigabytes(image.bytes)) choices.append((image.id, image)) if context.get('image_id') == image.id and \ 'volume_size' not in context: context['volume_size'] = image.volume_size if choices: choices.sort(key=lambda c: c[1].name or '') choices.insert(0, ("", _("Select Image"))) else: choices.insert(0, ("", _("No images available"))) return choices def populate_instance_snapshot_id_choices(self, request, context): images = image_utils.get_available_images(request, context.get('project_id'), self._images_cache) choices = [(image.id, image.name) for image in images if image.properties.get("image_type", '') == "snapshot"] if choices: choices.sort(key=operator.itemgetter(1)) choices.insert(0, ("", _("Select Instance Snapshot"))) else: choices.insert(0, ("", _("No snapshots available"))) return choices def populate_volume_id_choices(self, request, context): volumes = [] try: if cinder.is_volume_service_enabled(request): available = api.cinder.VOLUME_STATE_AVAILABLE volumes = [ self._get_volume_display_name(v) for v in cinder.volume_list( self.request, search_opts=dict(status=available, bootable=True)) ] except Exception: exceptions.handle(self.request, _('Unable to retrieve list of volumes.')) if volumes: volumes.insert(0, ("", _("Select Volume"))) else: volumes.insert(0, ("", _("No volumes available"))) return volumes def populate_volume_snapshot_id_choices(self, request, context): snapshots = [] try: if cinder.is_volume_service_enabled(request): available = api.cinder.VOLUME_STATE_AVAILABLE snapshots = [ self._get_volume_display_name(s) for s in cinder.volume_snapshot_list( self.request, search_opts=dict(status=available)) ] except Exception: exceptions.handle( self.request, _('Unable to retrieve list of volume ' 'snapshots.')) if snapshots: snapshots.insert(0, ("", _("Select Volume Snapshot"))) else: snapshots.insert(0, ("", _("No volume snapshots available"))) return snapshots
class CreateForm(forms.SelfHandlingForm): name = forms.CharField(max_length="255", label=_("Volume Name")) description = forms.CharField(widget=forms.Textarea, label=_("Description"), required=False) size = forms.IntegerField(min_value=1, label=_("Size (GB)")) snapshot_source = forms.ChoiceField( label=_("Use snapshot as a source"), widget=SelectWidget(attrs={'class': 'snapshot-selector'}, data_attrs=('size', 'display_name'), transform=lambda x: ("%s (%sGB)" % (x.display_name, x.size))), required=False) def __init__(self, request, *args, **kwargs): super(CreateForm, self).__init__(request, *args, **kwargs) if ("snapshot_id" in request.GET): try: snapshot = self.get_snapshot(request, request.GET["snapshot_id"]) self.fields['name'].initial = snapshot.display_name self.fields['size'].initial = snapshot.size self.fields['snapshot_source'].choices = ((snapshot.id, snapshot), ) self.fields['size'].help_text = _( 'Volume size must be equal ' 'to or greater than the snapshot size (%sGB)' % snapshot.size) except: exceptions.handle(request, _('Unable to load the specified snapshot.')) else: try: snapshots = api.volume_snapshot_list(request) if snapshots: choices = [('', _("Choose a snapshot"))] + \ [(s.id, s) for s in snapshots] self.fields['snapshot_source'].choices = choices else: del self.fields['snapshot_source'] except: exceptions.handle(request, _("Unable to retrieve " "volume snapshots.")) def handle(self, request, data): try: # FIXME(johnp): cinderclient currently returns a useless # error message when the quota is exceeded when trying to create # a volume, so we need to check for that scenario here before we # send it off to try and create. usages = api.tenant_quota_usages(request) snapshot_id = None if (data.get("snapshot_source", None)): # Create from Snapshot snapshot = self.get_snapshot(request, data["snapshot_source"]) snapshot_id = snapshot.id if (data['size'] < snapshot.size): error_message = _('The volume size cannot be less than ' 'the snapshot size (%sGB)' % snapshot.size) raise ValidationError(error_message) else: if type(data['size']) is str: data['size'] = int(data['size']) if usages['gigabytes']['available'] < data['size']: error_message = _('A volume of %(req)iGB cannot be created as ' 'you only have %(avail)iGB of your quota ' 'available.') params = { 'req': data['size'], 'avail': usages['gigabytes']['available'] } raise ValidationError(error_message % params) elif usages['volumes']['available'] <= 0: error_message = _('You are already using all of your available' ' volumes.') raise ValidationError(error_message) volume = api.volume_create(request, data['size'], data['name'], data['description'], snapshot_id=snapshot_id) message = 'Creating volume "%s"' % data['name'] messages.info(request, message) return volume except ValidationError, e: return self.api_error(e.messages[0]) except:
class UpdateIPSecSiteConnection(forms.SelfHandlingForm): name = forms.CharField(max_length=80, label=_("Name"), required=False) ipsecsiteconnection_id = forms.CharField(label=_("ID"), widget=forms.TextInput(attrs={'readonly': 'readonly'})) description = forms.CharField( required=False, max_length=80, label=_("Description")) peer_address = forms.IPField( label=_("Peer gateway public IPv4/IPv6 Address or FQDN"), help_text=_("Peer gateway public IPv4/IPv6 address or FQDN for " "the VPN Connection"), version=forms.IPv4 | forms.IPv6, mask=False) peer_id = forms.IPField( label=_("Peer router identity for authentication (Peer ID)"), help_text=_("Peer router identity for authentication. " "Can be IPv4/IPv6 address, e-mail, key ID, or FQDN"), version=forms.IPv4 | forms.IPv6, mask=False) peer_cidrs = forms.MultiIPField( label=_("Remote peer subnet(s)"), help_text=_("Remote peer subnet(s) address(es) " "with mask(s) in CIDR format " "separated with commas if needed " "(e.g. 20.1.0.0/24, 21.1.0.0/24)"), version=forms.IPv4 | forms.IPv6, mask=True) psk = forms.CharField( max_length=80, label=_("Pre-Shared Key (PSK) string")) mtu = forms.IntegerField( min_value=68, label=_("Maximum Transmission Unit size for the connection"), help_text=_("Equal to or greater than 68 if the local subnet is IPv4. " "Equal to or greater than 1280 if the local subnet " "is IPv6.")) dpd_action = forms.ChoiceField( label=_("Dead peer detection actions"), choices=[('hold', _('hold')), ('clear', _('clear')), ('disabled', _('disabled')), ('restart', _('restart')), ('restart-by-peer', _('restart-by-peer'))]) dpd_interval = forms.IntegerField( min_value=1, label=_("Dead peer detection interval"), help_text=_("Valid integer")) dpd_timeout = forms.IntegerField( min_value=1, label=_("Dead peer detection timeout"), help_text=_("Valid integer greater than the DPD interval")) initiator = forms.ChoiceField( label=_("Initiator state"), choices=[('bi-directional', _('bi-directional')), ('response-only', _('response-only'))]) admin_state_up = forms.BooleanField(label=_("Admin State"), required=False) failure_url = 'horizon:project:vpn:index' def handle(self, request, context): try: data = {'ipsec_site_connection': {'name': context['name'], 'description': context['description'], 'peer_address': context['peer_address'], 'peer_id': context['peer_id'], 'peer_cidrs': context[ 'peer_cidrs'].replace(" ", "").split(","), 'psk': context['psk'], 'mtu': context['mtu'], 'dpd': {'action': context['dpd_action'], 'interval': context['dpd_interval'], 'timeout': context['dpd_timeout']}, 'initiator': context['initiator'], 'admin_state_up': context['admin_state_up'], }} ipsecsiteconnection = api.vpn.ipsecsiteconnection_update( request, context['ipsecsiteconnection_id'], **data) msg = (_('IPSec Site Connection %s was successfully updated.') % context['name']) LOG.debug(msg) messages.success(request, msg) return ipsecsiteconnection except Exception as e: msg = (_('Failed to update IPSec Site Connection %s') % context['name']) LOG.info('%s: %s' % (msg, e)) redirect = reverse(self.failure_url) exceptions.handle(request, msg, redirect=redirect)
class UpdateIPSecPolicy(forms.SelfHandlingForm): name = forms.CharField(max_length=80, label=_("Name"), required=False) ipsecpolicy_id = forms.CharField( label=_("ID"), widget=forms.TextInput(attrs={'readonly': 'readonly'})) description = forms.CharField(required=False, max_length=80, label=_("Description")) # Currently this field has only one choice, so mark it as readonly. auth_algorithm = forms.ChoiceField( label=_("Authorization algorithm"), choices=[('sha1', _('sha1'))], widget=forms.TextInput(attrs={'readonly': 'readonly'}), required=False) encapsulation_mode = forms.ChoiceField(label=_("Encapsulation mode"), choices=[('tunnel', _('tunnel')), ('transport', _('transport'))], required=False) encryption_algorithm = forms.ChoiceField(label=_("Encryption algorithm"), choices=[ ('3des', _('3des')), ('aes-128', _('aes-128')), ('aes-192', _('aes-192')), ('aes-256', _('aes-256')) ], required=False) # Currently this field has only one choice, so mark it as readonly. lifetime_units = forms.ChoiceField( label=_("Lifetime units"), choices=[('seconds', _('seconds'))], widget=forms.ThemableSelectWidget(attrs={'readonly': 'readonly'}), required=False) lifetime_value = forms.IntegerField( min_value=60, label=_("Lifetime value"), help_text=_("Equal to or greater than 60"), required=False) pfs = forms.ChoiceField(label=_("Perfect Forward Secrecy"), choices=[('group2', _('group2')), ('group5', _('group5')), ('group14', _('group14'))], required=False) transform_protocol = forms.ChoiceField(label=_("Transform Protocol"), choices=[('esp', _('esp')), ('ah', _('ah')), ('ah-esp', _('ah-esp'))], required=False) failure_url = 'horizon:project:vpn:index' def handle(self, request, context): try: data = { 'ipsecpolicy': { 'name': context['name'], 'description': context['description'], 'auth_algorithm': context['auth_algorithm'], 'encapsulation_mode': context['encapsulation_mode'], 'encryption_algorithm': context['encryption_algorithm'], 'lifetime': { 'units': context['lifetime_units'], 'value': context['lifetime_value'] }, 'pfs': context['pfs'], 'transform_protocol': context['transform_protocol'], } } ipsecpolicy = api.vpn.ipsecpolicy_update(request, context['ipsecpolicy_id'], **data) msg = (_('IPSec Policy %s was successfully updated.') % context['name']) messages.success(request, msg) return ipsecpolicy except Exception as e: LOG.info('Failed to update IPSec Policy %(id)s: %(exc)s', { 'id': context['ipsecpolicy_id'], 'exc': e }) msg = _('Failed to update IPSec Policy %s') % context['name'] redirect = reverse(self.failure_url) exceptions.handle(request, msg, redirect=redirect)
class CreateForm(forms.SelfHandlingForm): name = forms.CharField(max_length="255", label=_("Server Group Name")) policy = forms.ChoiceField(label=_("Policy"), required=False, widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'policy_ht' })) is_best_effort = forms.BooleanField(label=_("Best Effort"), required=False) group_size = forms.IntegerField( min_value=1, label=_("Max Group Size (Instances)"), required=False, widget=forms.TextInput( attrs={ 'class': 'switchable switched', 'data-switch-on': 'policy_ht', 'data-policy_ht-anti-affinity': 'Max Group Size (Instances)', 'data-policy_ht-affinity': 'Max Group Size (Instances)' })) group_size_ht = forms.IntegerField( label=_("Max Group Size (Instances)"), required=False, widget=forms.TextInput( attrs={ 'readonly': 'readonly', 'class': 'switchable switched', 'data-switch-on': 'policy_ht', 'data-policy_ht-affinity-hyperthread': 'Max Group Size (Instances)' })) def __init__(self, request, *args, **kwargs): super(CreateForm, self).__init__(request, *args, **kwargs) self.fields['policy'].choices = [("anti-affinity", "anti-affinity"), ("affinity", "affinity")] def handle(self, request, data): try: project_id = self.request.user.tenant_id policy = data['policy'] policies = [] if policy: policies.append(policy) metadata = {} if data['is_best_effort']: metadata['wrs-sg:best_effort'] = "true" group_size = data['group_size'] group_size_ht = data['group_size_ht'] if group_size: metadata['wrs-sg:group_size'] = str(group_size) elif group_size_ht: metadata['wrs-sg:group_size'] = str(group_size_ht) server_group = nova.server_group_create(request, data['name'], project_id, metadata, policies) return server_group except ValidationError as e: self.api_error(e.messages[0]) return False except Exception: exceptions.handle(request, ignore=True) self.api_error(_("Unable to create server group.")) return False
class UpdateIPsecSiteConnection(forms.SelfHandlingForm): name = forms.CharField(max_length=80, label=_("Name"), required=False) description = forms.CharField(required=False, max_length=80, label=_("Description")) peer_address = forms.CharField( label=_("Peer gateway public IPv4/IPv6 Address or FQDN"), help_text=_("Peer gateway public IPv4/IPv6 address or FQDN for " "the VPN Connection"), ) peer_id = forms.CharField( label=_("Peer router identity for authentication (Peer ID)"), help_text=_("Peer router identity for authentication. " "Can be IPv4/IPv6 address, e-mail, key ID, or FQDN"), ) peer_cidrs = forms.MultiIPField(required=False, label=_("Remote peer subnet(s)"), help_text=_( "Remote peer subnet(s) address(es) " "with mask(s) in CIDR format " "separated with commas if needed " "(e.g. 20.1.0.0/24, 21.1.0.0/24)"), version=forms.IPv4 | forms.IPv6, mask=True) local_ep_group_id = forms.CharField( required=False, label=_("Local Endpoint Group(s)"), help_text=_("IPsec connection validation requires " "that local endpoints are subnets")) peer_ep_group_id = forms.CharField( required=False, label=_("Peer Endpoint Group(s)"), help_text=_("IPsec connection validation requires " "that peer endpoints are CIDRs")) psk = forms.CharField(widget=forms.PasswordInput(render_value=True), max_length=80, label=_("Pre-Shared Key (PSK) string")) mtu = forms.IntegerField( min_value=68, required=False, label=_("Maximum Transmission Unit size for the connection"), help_text=_("Equal to or greater than 68 if the local subnet is IPv4. " "Equal to or greater than 1280 if the local subnet " "is IPv6.")) dpd_action = forms.ThemableChoiceField( label=_("Dead peer detection actions"), required=False, choices=[('hold', _('hold')), ('clear', _('clear')), ('disabled', _('disabled')), ('restart', _('restart')), ('restart-by-peer', _('restart-by-peer'))]) dpd_interval = forms.IntegerField( min_value=1, required=False, label=_("Dead peer detection interval"), help_text=_("Valid integer lesser than the DPD timeout")) dpd_timeout = forms.IntegerField( min_value=1, required=False, label=_("Dead peer detection timeout"), help_text=_("Valid integer greater than the DPD interval")) initiator = forms.ThemableChoiceField(label=_("Initiator state"), required=False, choices=[('bi-directional', _('bi-directional')), ('response-only', _('response-only'))]) admin_state_up = forms.BooleanField(label=_("Enable Admin State"), required=False) failure_url = 'horizon:project:vpn:index' def clean(self): cleaned_data = super(UpdateIPsecSiteConnection, self).clean() interval = cleaned_data.get('dpd_interval') timeout = cleaned_data.get('dpd_timeout') if not interval < timeout: msg = _("DPD Timeout must be greater than DPD Interval") self._errors['dpd_timeout'] = self.error_class([msg]) return cleaned_data def handle(self, request, context): try: data = { 'name': context['name'], 'description': context['description'], 'peer_address': context['peer_address'], 'peer_id': context['peer_id'], 'psk': context['psk'], 'mtu': context['mtu'], 'dpd': { 'action': context['dpd_action'], 'interval': context['dpd_interval'], 'timeout': context['dpd_timeout'] }, 'initiator': context['initiator'], 'admin_state_up': context['admin_state_up'] } if not context['peer_cidrs']: data['local_ep_group_id'] = context['local_ep_group_id'] data['peer_ep_group_id'] = context['peer_ep_group_id'] else: cidrs = context['peer_cidrs'] data['peer_cidrs'] = [ cidr.strip() for cidr in cidrs.split(',') if cidr.strip() ] ipsecsiteconnection = api_vpn.ipsecsiteconnection_update( request, self.initial['ipsecsiteconnection_id'], ipsec_site_connection=data) msg = (_('IPsec site connection %s was successfully updated.') % context['name']) messages.success(request, msg) return ipsecsiteconnection except Exception as e: LOG.info('Failed to update IPsec site connection %(id)s: %(exc)s', { 'id': self.initial['ipsecsiteconnection_id'], 'exc': e }) msg = (_('Failed to update IPsec site connection %s') % context['name']) redirect = reverse(self.failure_url) exceptions.handle(request, msg, redirect=redirect)
class CreateDatasource(forms.SelfHandlingForm): name = forms.CharField(max_length=255, label=_("Data Source Name"), help_text='Name of the data source') driver = forms.ThemableChoiceField(label=_("Driver"), help_text='Data Source driver') description = forms.CharField(label=_("Description"), required=False, help_text='Data Source Description') username = forms.CharField( max_length=255, label=_("UserName"), help_text='username to connect to the driver service') password = forms.CharField(widget=forms.PasswordInput(render_value=False), label=_('Password')) tenant_name = forms.CharField(max_length=255, label=_("Project Name")) # TODO(ramineni): support adding lazy tables # lazy_tables = forms.CharField(max_length=255, label=_("Lazy Tables")) auth_url = forms.URLField(max_length=255, label=_("Keystone Auth URL")) poll_time = forms.IntegerField( label=_("Poll Interval (in seconds)"), help_text='periodic interval congress needs to poll data') failure_url = 'horizon:admin:datasources:index' @classmethod def _instantiate(cls, request, *args, **kwargs): return cls(request, *args, **kwargs) def __init__(self, request, *args, **kwargs): super(CreateDatasource, self).__init__(request, *args, **kwargs) driver_choices = [('', _("Select a Driver"))] drivers = congress.supported_driver_list(request) driver_choices.extend(drivers) self.fields['driver'].choices = driver_choices def handle(self, request, data): datasource_name = data['name'] datasource_description = data.get('description') datasource_driver = data.pop('driver') config = { 'username': data['username'], 'password': data['password'], 'tenant_name': data['tenant_name'], 'auth_url': data['auth_url'], 'poll_time': data['poll_time'] } try: params = { 'name': datasource_name, 'driver': datasource_driver, 'description': datasource_description, 'config': config } datasource = congress.create_datasource(request, params) msg = _('Created Data Source "%s"') % datasource_name LOG.info(msg) messages.success(request, msg) except Exception as e: msg_args = {'datasource_name': datasource_name, 'error': str(e)} msg = _('Failed to create data source "%(datasource_name)s": ' '%(error)s') % msg_args LOG.error(msg) messages.error(self.request, msg) redirect = reverse(self.failure_url) raise exceptions.Http302(redirect) return datasource
class AddRule(forms.SelfHandlingForm): id = forms.CharField(widget=forms.HiddenInput()) rule_menu = forms.ChoiceField( label=_('Rule'), widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'rule_menu' })) # "direction" field is enabled only when custom mode. # It is because most common rules in local_settings.py is meaningful # when its direction is 'ingress'. direction = forms.ChoiceField( label=_('Direction'), required=False, widget=forms.Select( attrs={ 'class': 'switched', 'data-switch-on': 'rule_menu', 'data-rule_menu-tcp': _('Direction'), 'data-rule_menu-udp': _('Direction'), 'data-rule_menu-icmp': _('Direction'), 'data-rule_menu-custom': _('Direction'), 'data-rule_menu-all_tcp': _('Direction'), 'data-rule_menu-all_udp': _('Direction'), 'data-rule_menu-all_icmp': _('Direction'), })) ip_protocol = forms.IntegerField( label=_('IP Protocol'), required=False, help_text=_("Enter an integer value between 0 and 255 " "(or -1 which means wildcard)."), validators=[utils_validators.validate_ip_protocol], widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'rule_menu', 'data-rule_menu-custom': _('IP Protocol') })) port_or_range = forms.ChoiceField( label=_('Open Port'), choices=[('port', _('Port')), ('range', _('Port Range'))], widget=forms.Select( attrs={ 'class': 'switchable switched', 'data-slug': 'range', 'data-switch-on': 'rule_menu', 'data-rule_menu-tcp': _('Open Port'), 'data-rule_menu-udp': _('Open Port') })) port = forms.IntegerField( label=_("Port"), required=False, help_text=_("Enter an integer value " "between 1 and 65535."), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'range', 'data-range-port': _('Port') }), validators=[utils_validators.validate_port_range]) from_port = forms.IntegerField( label=_("From Port"), required=False, help_text=_("Enter an integer value " "between 1 and 65535."), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'range', 'data-range-range': _('From Port') }), validators=[utils_validators.validate_port_range]) to_port = forms.IntegerField( label=_("To Port"), required=False, help_text=_("Enter an integer value " "between 1 and 65535."), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'range', 'data-range-range': _('To Port') }), validators=[utils_validators.validate_port_range]) icmp_type = forms.IntegerField( label=_("Type"), required=False, help_text=_("Enter a value for ICMP type " "in the range (-1: 255)"), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'rule_menu', 'data-rule_menu-icmp': _('Type') }), validators=[utils_validators.validate_port_range]) icmp_code = forms.IntegerField( label=_("Code"), required=False, help_text=_("Enter a value for ICMP code " "in the range (-1: 255)"), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'rule_menu', 'data-rule_menu-icmp': _('Code') }), validators=[utils_validators.validate_port_range]) remote = forms.ChoiceField(label=_('Remote'), choices=[('cidr', _('CIDR')), ('sg', _('Security Group'))], help_text=_('To specify an allowed IP ' 'range, select "CIDR". ' 'To allow access from all ' 'members of another security ' 'group select "Security ' 'Group".'), widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'remote' })) cidr = forms.IPField(label=_("CIDR"), required=False, initial="0.0.0.0/0", help_text=_("Classless Inter-Domain Routing " "(e.g. 192.168.0.0/24)"), version=forms.IPv4 | forms.IPv6, mask=True, widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'remote', 'data-remote-cidr': _('CIDR') })) security_group = forms.ChoiceField( label=_('Security Group'), required=False, widget=forms.Select( attrs={ 'class': 'switched', 'data-switch-on': 'remote', 'data-remote-sg': _('Security ' 'Group') })) # When cidr is used ethertype is determined from IP version of cidr. # When source group, ethertype needs to be specified explicitly. ethertype = forms.ChoiceField(label=_('Ether Type'), required=False, choices=[('IPv4', _('IPv4')), ('IPv6', _('IPv6'))], widget=forms.Select( attrs={ 'class': 'switched', 'data-slug': 'ethertype', 'data-switch-on': 'remote', 'data-remote-sg': _('Ether Type') })) def __init__(self, *args, **kwargs): sg_list = kwargs.pop('sg_list', []) super(AddRule, self).__init__(*args, **kwargs) # Determine if there are security groups available for the # remote group option; add the choices and enable the option if so. if sg_list: security_groups_choices = sg_list else: security_groups_choices = [("", _("No security groups available"))] self.fields['security_group'].choices = security_groups_choices backend = api.network.security_group_backend(self.request) rules_dict = getattr(settings, 'SECURITY_GROUP_RULES', []) common_rules = [(k, rules_dict[k]['name']) for k in rules_dict if rules_dict[k].get('backend', backend) == backend] common_rules.sort() custom_rules = [('tcp', _('Custom TCP Rule')), ('udp', _('Custom UDP Rule')), ('icmp', _('Custom ICMP Rule'))] if backend == 'neutron': custom_rules.append(('custom', _('Other Protocol'))) self.fields['rule_menu'].choices = custom_rules + common_rules self.rules = rules_dict if backend == 'neutron': self.fields['direction'].choices = [('ingress', _('Ingress')), ('egress', _('Egress'))] else: # direction and ethertype are not supported in Nova secgroup. self.fields['direction'].widget = forms.HiddenInput() self.fields['ethertype'].widget = forms.HiddenInput() # ip_protocol field is to specify arbitrary protocol number # and it is available only for neutron security group. self.fields['ip_protocol'].widget = forms.HiddenInput() def _update_and_pop_error(self, cleaned_data, key, value): cleaned_data[key] = value self.errors.pop(key, None) def _clean_rule_icmp(self, cleaned_data, rule_menu): icmp_type = cleaned_data.get("icmp_type", None) icmp_code = cleaned_data.get("icmp_code", None) self._update_and_pop_error(cleaned_data, 'ip_protocol', rule_menu) if icmp_type is None: msg = _('The ICMP type is invalid.') raise ValidationError(msg) if icmp_code is None: msg = _('The ICMP code is invalid.') raise ValidationError(msg) if icmp_type not in range(-1, 256): msg = _('The ICMP type not in range (-1, 255)') raise ValidationError(msg) if icmp_code not in range(-1, 256): msg = _('The ICMP code not in range (-1, 255)') raise ValidationError(msg) self._update_and_pop_error(cleaned_data, 'from_port', icmp_type) self._update_and_pop_error(cleaned_data, 'to_port', icmp_code) self._update_and_pop_error(cleaned_data, 'port', None) def _clean_rule_tcp_udp(self, cleaned_data, rule_menu): port_or_range = cleaned_data.get("port_or_range") from_port = cleaned_data.get("from_port", None) to_port = cleaned_data.get("to_port", None) port = cleaned_data.get("port", None) self._update_and_pop_error(cleaned_data, 'ip_protocol', rule_menu) self._update_and_pop_error(cleaned_data, 'icmp_code', None) self._update_and_pop_error(cleaned_data, 'icmp_type', None) if port_or_range == "port": self._update_and_pop_error(cleaned_data, 'from_port', port) self._update_and_pop_error(cleaned_data, 'to_port', port) if port is None: msg = _('The specified port is invalid.') raise ValidationError(msg) else: self._update_and_pop_error(cleaned_data, 'port', None) if from_port is None: msg = _('The "from" port number is invalid.') raise ValidationError(msg) if to_port is None: msg = _('The "to" port number is invalid.') raise ValidationError(msg) if to_port < from_port: msg = _('The "to" port number must be greater than ' 'or equal to the "from" port number.') raise ValidationError(msg) def _apply_rule_menu(self, cleaned_data, rule_menu): cleaned_data['ip_protocol'] = self.rules[rule_menu]['ip_protocol'] cleaned_data['from_port'] = int(self.rules[rule_menu]['from_port']) cleaned_data['to_port'] = int(self.rules[rule_menu]['to_port']) if rule_menu not in ['all_tcp', 'all_udp', 'all_icmp']: direction = self.rules[rule_menu].get('direction') cleaned_data['direction'] = direction def _clean_rule_menu(self, cleaned_data): rule_menu = cleaned_data.get('rule_menu') if rule_menu == 'icmp': self._clean_rule_icmp(cleaned_data, rule_menu) elif rule_menu == 'tcp' or rule_menu == 'udp': self._clean_rule_tcp_udp(cleaned_data, rule_menu) elif rule_menu == 'custom': pass else: self._apply_rule_menu(cleaned_data, rule_menu) def clean(self): cleaned_data = super(AddRule, self).clean() self._clean_rule_menu(cleaned_data) # NOTE(amotoki): There are two cases where cleaned_data['direction'] # is empty: (1) Nova Security Group is used. Since "direction" is # HiddenInput, direction field exists but its value is ''. # (2) Template except all_* is used. In this case, the default value # is None. To make sure 'direction' field has 'ingress' or 'egress', # fill this field here if it is not specified. if not cleaned_data['direction']: cleaned_data['direction'] = 'ingress' remote = cleaned_data.get("remote") if remote == "cidr": self._update_and_pop_error(cleaned_data, 'security_group', None) else: self._update_and_pop_error(cleaned_data, 'cidr', None) # If cleaned_data does not contain a non-empty value, IPField already # has validated it, so skip the further validation for cidr. # In addition cleaned_data['cidr'] is None means source_group is used. if 'cidr' in cleaned_data and cleaned_data['cidr'] is not None: cidr = cleaned_data['cidr'] if not cidr: msg = _('CIDR must be specified.') self._errors['cidr'] = self.error_class([msg]) else: # If cidr is specified, ethertype is determined from IP address # version. It is used only when Neutron is enabled. ip_ver = netaddr.IPNetwork(cidr).version cleaned_data['ethertype'] = 'IPv6' if ip_ver == 6 else 'IPv4' return cleaned_data def handle(self, request, data): try: rule = api.network.security_group_rule_create( request, filters.get_int_or_uuid(data['id']), data['direction'], data['ethertype'], data['ip_protocol'], data['from_port'], data['to_port'], data['cidr'], data['security_group']) messages.success( request, _('Successfully added rule: %s') % six.text_type(rule)) return rule except Exception: redirect = reverse( "horizon:project:access_and_security:" "security_groups:detail", args=[data['id']]) exceptions.handle(request, _('Unable to add rule to security group.'), redirect=redirect)
class AddRule(forms.SelfHandlingForm): id = forms.CharField(widget=forms.HiddenInput()) rule_menu = forms.ChoiceField( label=_('Rule'), widget=forms.ThemableSelectWidget(attrs={ 'class': 'switchable', 'data-slug': 'rule_menu' })) description = forms.CharField( label=_('Description'), required=False, max_length=255, widget=forms.Textarea(attrs={'rows': 2}), help_text=_('A brief description of the security group rule ' 'you are adding')) # "direction" field is enabled only when custom mode. # It is because most common rules in local_settings.py is meaningful # when its direction is 'ingress'. direction = forms.ChoiceField( label=_('Direction'), required=False, widget=forms.ThemableSelectWidget( attrs={ 'class': 'switched', 'data-switch-on': 'rule_menu', 'data-rule_menu-tcp': _('Direction'), 'data-rule_menu-udp': _('Direction'), 'data-rule_menu-icmp': _('Direction'), 'data-rule_menu-custom': _('Direction'), 'data-rule_menu-all_tcp': _('Direction'), 'data-rule_menu-all_udp': _('Direction'), 'data-rule_menu-all_icmp': _('Direction'), })) ip_protocol = forms.IntegerField( label=_('IP Protocol'), required=False, help_text=_("Enter an integer value between 0 and 255."), validators=[utils_validators.validate_ip_protocol], widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'rule_menu', 'data-rule_menu-custom': _('IP Protocol') })) port_or_range = forms.ChoiceField( label=_('Open Port'), choices=[('port', _('Port')), ('range', _('Port Range'))], widget=forms.ThemableSelectWidget( attrs={ 'class': 'switchable switched', 'data-slug': 'range', 'data-switch-on': 'rule_menu', 'data-rule_menu-tcp': _('Open Port'), 'data-rule_menu-udp': _('Open Port') })) port = forms.IntegerField( label=_("Port"), required=False, help_text=_("Enter an integer value " "between 1 and 65535."), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-required-when-shown': 'true', 'data-switch-on': 'range', 'data-range-port': _('Port') }), validators=[utils_validators.validate_port_range]) from_port = forms.IntegerField( label=_("From Port"), required=False, help_text=_("Enter an integer value " "between 1 and 65535."), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-required-when-shown': 'true', 'data-switch-on': 'range', 'data-range-range': _('From Port') }), validators=[utils_validators.validate_port_range]) to_port = forms.IntegerField( label=_("To Port"), required=False, help_text=_("Enter an integer value " "between 1 and 65535."), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-required-when-shown': 'true', 'data-switch-on': 'range', 'data-range-range': _('To Port') }), validators=[utils_validators.validate_port_range]) icmp_type = forms.IntegerField( label=_("Type"), required=False, help_text=_("Enter a value for ICMP type " "in the range (-1: 255)"), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'rule_menu', 'data-rule_menu-icmp': _('Type') }), validators=[utils_validators.validate_icmp_type_range]) icmp_code = forms.IntegerField( label=_("Code"), required=False, help_text=_("Enter a value for ICMP code " "in the range (-1: 255)"), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'rule_menu', 'data-rule_menu-icmp': _('Code') }), validators=[utils_validators.validate_icmp_code_range]) remote = forms.ChoiceField( label=_('Remote'), choices=[('cidr', _('CIDR')), ('sg', _('Security Group'))], help_text=_('To specify an allowed IP ' 'range, select "CIDR". ' 'To allow access from all ' 'members of another security ' 'group select "Security ' 'Group".'), widget=forms.ThemableSelectWidget(attrs={ 'class': 'switchable', 'data-slug': 'remote' })) cidr = forms.IPField(label=_("CIDR"), required=False, initial="0.0.0.0/0", help_text=_("Classless Inter-Domain Routing " "(e.g. 192.168.0.0/24, or " "2001:db8::/128)"), version=forms.IPv4 | forms.IPv6, mask=True, widget=forms.TextInput( attrs={ 'class': 'switched', 'data-required-when-shown': 'true', 'data-switch-on': 'remote', 'data-remote-cidr': _('CIDR') })) security_group = forms.ChoiceField( label=_('Security Group'), required=False, widget=forms.ThemableSelectWidget( attrs={ 'class': 'switched', 'data-required-when-shown': 'true', 'data-switch-on': 'remote', 'data-remote-sg': _('Security Group') })) # When cidr is used ethertype is determined from IP version of cidr. # When source group, ethertype needs to be specified explicitly. ethertype = forms.ChoiceField(label=_('Ether Type'), required=False, choices=[('IPv4', _('IPv4')), ('IPv6', _('IPv6'))], widget=forms.ThemableSelectWidget( attrs={ 'class': 'switched', 'data-slug': 'ethertype', 'data-switch-on': 'remote', 'data-remote-sg': _('Ether Type') })) def __init__(self, *args, **kwargs): sg_list = kwargs.pop('sg_list', []) super(AddRule, self).__init__(*args, **kwargs) # Determine if there are security groups available for the # remote group option; add the choices and enable the option if so. if sg_list: security_groups_choices = sg_list else: security_groups_choices = [("", _("No security groups available"))] self.fields['security_group'].choices = security_groups_choices # TODO(amotoki): settings.SECURITY_GROUP_RULES may contains 'backend' # parameter. If 'backend' is used, error message should be emitted. backend = 'neutron' rules_dict = settings.SECURITY_GROUP_RULES common_rules = [(k, rules_dict[k]['name']) for k in rules_dict if rules_dict[k].get('backend', backend) == backend] common_rules.sort() custom_rules = [('tcp', _('Custom TCP Rule')), ('udp', _('Custom UDP Rule')), ('icmp', _('Custom ICMP Rule')), ('custom', _('Other Protocol'))] self.fields['rule_menu'].choices = custom_rules + common_rules self.rules = rules_dict self.fields['direction'].choices = [('ingress', _('Ingress')), ('egress', _('Egress'))] self.fields['ip_protocol'].help_text = _( "Enter an integer value between -1 and 255 " "(-1 means wild card).") self.fields['port_or_range'].choices = [ ('port', _('Port')), ('range', _('Port Range')), ('all', _('All ports')), ] if not setting_utils.get_dict_config('OPENSTACK_NEUTRON_NETWORK', 'enable_ipv6'): self.fields['cidr'].version = forms.IPv4 self.fields['ethertype'].widget = forms.TextInput( attrs={'readonly': 'readonly'}) self.fields['ethertype'].initial = 'IPv4' try: is_desc_supported = api.neutron.is_extension_supported( self.request, 'standard-attr-description') except Exception: exceptions.handle( self.request, _('Failed to check if description field is supported.')) is_desc_supported = False if not is_desc_supported: del self.fields['description'] def _update_and_pop_error(self, cleaned_data, key, value): cleaned_data[key] = value self.errors.pop(key, None) def _clean_rule_icmp(self, cleaned_data, rule_menu): icmp_type = cleaned_data.get("icmp_type", None) icmp_code = cleaned_data.get("icmp_code", None) self._update_and_pop_error(cleaned_data, 'ip_protocol', rule_menu) if icmp_type == -1 and icmp_code != -1: msg = _('ICMP code is provided but ICMP type is missing.') raise ValidationError(msg) if self.errors.get('icmp_type'): msg = _('The ICMP type not in range (-1, 255)') raise ValidationError(msg) if self.errors.get('icmp_code'): msg = _('The ICMP code not in range (-1, 255)') raise ValidationError(msg) self._update_and_pop_error(cleaned_data, 'from_port', icmp_type) self._update_and_pop_error(cleaned_data, 'to_port', icmp_code) self._update_and_pop_error(cleaned_data, 'port', None) def _clean_rule_tcp_udp(self, cleaned_data, rule_menu): port_or_range = cleaned_data.get("port_or_range") from_port = cleaned_data.get("from_port", None) to_port = cleaned_data.get("to_port", None) port = cleaned_data.get("port", None) self._update_and_pop_error(cleaned_data, 'ip_protocol', rule_menu) self._update_and_pop_error(cleaned_data, 'icmp_code', None) self._update_and_pop_error(cleaned_data, 'icmp_type', None) if port_or_range == 'all': self._update_and_pop_error(cleaned_data, 'port', None) self._update_and_pop_error(cleaned_data, 'from_port', None) self._update_and_pop_error(cleaned_data, 'to_port', None) elif port_or_range == "port": self._update_and_pop_error(cleaned_data, 'from_port', port) self._update_and_pop_error(cleaned_data, 'to_port', port) if port is None: msg = _('The specified port is invalid.') raise ValidationError(msg) else: self._update_and_pop_error(cleaned_data, 'port', None) if from_port is None: msg = _('The "from" port number is invalid.') raise ValidationError(msg) if to_port is None: msg = _('The "to" port number is invalid.') raise ValidationError(msg) if to_port < from_port: msg = _('The "to" port number must be greater than ' 'or equal to the "from" port number.') raise ValidationError(msg) def _clean_rule_custom(self, cleaned_data, rule_menu): # custom IP protocol rule so we need to fill unused fields so # the validation works unused_fields = [ 'icmp_code', 'icmp_type', 'from_port', 'to_port', 'port' ] for unused_field in unused_fields: self._update_and_pop_error(cleaned_data, unused_field, None) def _apply_rule_menu(self, cleaned_data, rule_menu): cleaned_data['ip_protocol'] = self.rules[rule_menu]['ip_protocol'] cleaned_data['from_port'] = int(self.rules[rule_menu]['from_port']) cleaned_data['to_port'] = int(self.rules[rule_menu]['to_port']) self._update_and_pop_error(cleaned_data, 'icmp_code', None) self._update_and_pop_error(cleaned_data, 'icmp_type', None) if rule_menu not in ['all_tcp', 'all_udp', 'all_icmp']: direction = self.rules[rule_menu].get('direction') cleaned_data['direction'] = direction def _clean_rule_menu(self, cleaned_data): rule_menu = cleaned_data.get('rule_menu') if rule_menu == 'icmp': self._clean_rule_icmp(cleaned_data, rule_menu) elif rule_menu in ('tcp', 'udp'): self._clean_rule_tcp_udp(cleaned_data, rule_menu) elif rule_menu == 'custom': self._clean_rule_custom(cleaned_data, rule_menu) else: self._apply_rule_menu(cleaned_data, rule_menu) def _adjust_ip_protocol_of_icmp(self, data): # Note that this needs to be called after IPv4/IPv6 is determined. try: ip_protocol = int(data['ip_protocol']) except ValueError: # string representation of IP protocol ip_protocol = data['ip_protocol'] is_ipv6 = data['ethertype'] == 'IPv6' if isinstance(ip_protocol, int): # When IP protocol number is specified, we assume a user # knows more detail on IP protocol number, # so a warning message on a mismatch between IP proto number # and IP version is displayed. if is_ipv6 and ip_protocol == 1: msg = _('58 (ipv6-icmp) should be specified for IPv6 ' 'instead of 1.') self._errors['ip_protocol'] = self.error_class([msg]) elif not is_ipv6 and ip_protocol == 58: msg = _('1 (icmp) should be specified for IPv4 ' 'instead of 58.') self._errors['ip_protocol'] = self.error_class([msg]) else: # ICMPv6 uses different an IP protocol name and number. # To allow 'icmp' for both IPv4 and IPv6 in the form, # we need to replace 'icmp' with 'ipv6-icmp' based on IP version. if is_ipv6 and ip_protocol == 'icmp': data['ip_protocol'] = 'ipv6-icmp' def clean(self): cleaned_data = super(AddRule, self).clean() self._clean_rule_menu(cleaned_data) # NOTE(amotoki): There are two cases where cleaned_data['direction'] # is empty: (1) Nova Security Group is used. Since "direction" is # HiddenInput, direction field exists but its value is ''. # (2) Template except all_* is used. In this case, the default value # is None. To make sure 'direction' field has 'ingress' or 'egress', # fill this field here if it is not specified. if not cleaned_data['direction']: cleaned_data['direction'] = 'ingress' remote = cleaned_data.get("remote") if remote == "cidr": self._update_and_pop_error(cleaned_data, 'security_group', None) else: self._update_and_pop_error(cleaned_data, 'cidr', None) # If cleaned_data does not contain a non-empty value, IPField already # has validated it, so skip the further validation for cidr. # In addition cleaned_data['cidr'] is None means source_group is used. if 'cidr' in cleaned_data and cleaned_data['cidr'] is not None: cidr = cleaned_data['cidr'] if not cidr: msg = _('CIDR must be specified.') self._errors['cidr'] = self.error_class([msg]) else: # If cidr is specified, ethertype is determined from IP address # version. It is used only when Neutron is enabled. ip_ver = netaddr.IPNetwork(cidr).version cleaned_data['ethertype'] = 'IPv6' if ip_ver == 6 else 'IPv4' self._adjust_ip_protocol_of_icmp(cleaned_data) return cleaned_data def handle(self, request, data): redirect = reverse("horizon:project:security_groups:detail", args=[data['id']]) params = {} if 'description' in data: params['description'] = data['description'] try: rule = api.neutron.security_group_rule_create( request, filters.get_int_or_uuid(data['id']), data['direction'], data['ethertype'], data['ip_protocol'], data['from_port'], data['to_port'], data['cidr'], data['security_group'], **params) messages.success(request, _('Successfully added rule: %s') % rule) return rule except exceptions.Conflict as error: exceptions.handle(request, error, redirect=redirect) except Exception: exceptions.handle(request, _('Unable to add rule to security group.'), redirect=redirect)
class CreateProviderNetworkRange(forms.SelfHandlingForm): providernet_id = forms.CharField(widget=forms.HiddenInput()) name = forms.CharField(max_length=255, label=_("Name"), required=False) description = forms.CharField(max_length=255, label=_("Description"), required=False) shared = forms.BooleanField(label=_("Shared"), initial=False, required=False, widget=forms.CheckboxInput( attrs={ 'class': 'switchable', 'data-hide-on-checked': 'true', 'data-slug': 'is_shared' })) tenant_id = forms.ChoiceField( label=_("Project"), required=False, widget=forms.Select(attrs={ 'class': 'switched', 'data-switch-on': 'is_shared' })) minimum = forms.IntegerField(label=_("Minimum"), min_value=1) maximum = forms.IntegerField(label=_("Maximum"), min_value=1) # VXLAN specific fields mode_choices = [('dynamic', _('Multicast VXLAN')), ('static', _('Static VXLAN'))] mode = forms.ChoiceField(label=_("Mode"), initial='dynamic', required=False, choices=mode_choices, widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'vxlan_mode' })) group_help = (_("Specify the IPv4 or IPv6 multicast address for these " "VXLAN instances")) group = forms.CharField( max_length=255, label=_("Multicast Group Address"), initial="239.0.0.1", required=False, help_text=group_help, widget=forms.TextInput( attrs={ 'class': 'switchable switched', 'data-slug': 'vxlan_group', 'data-switch-on': 'vxlan_mode', 'data-vxlan_mode-dynamic': 'Multicast Group Address' })) port_choices = [('4789', _('IANA Assigned VXLAN UDP port (4789)')), ('4790', _('IANA Assigned VXLAN-GPE UDP port (4790)')), ('8472', _('Legacy VXLAN UDP port (8472)'))] port = forms.ChoiceField(label=_("UDP Port"), required=True, widget=forms.RadioSelect(), choices=port_choices) ttl = forms.IntegerField( label=_("TTL"), required=False, initial=1, min_value=1, max_value=255, help_text=( _("Specify the time-to-live value for these VXLAN instances"))) def __init__(self, request, *args, **kwargs): super(CreateProviderNetworkRange, self).__init__(request, *args, **kwargs) tenant_choices = [('', _("Select a project"))] tenants, has_more = api.keystone.tenant_list(request) # noqa pylint: disable=unused-variable for tenant in tenants: if tenant.enabled: tenant_choices.append((tenant.id, tenant.name)) self.fields['tenant_id'].choices = tenant_choices initial = kwargs['initial'] if 'providernet_type' in initial: providernet_type = initial['providernet_type'] if providernet_type != "vxlan": del self.fields["mode"] del self.fields["group"] del self.fields["port"] del self.fields["ttl"] def handle(self, request, data): try: params = { 'providernet_id': data['providernet_id'], 'name': data['name'], 'description': data['description'], 'minimum': data['minimum'], 'maximum': data['maximum'], 'shared': data['shared'], 'tenant_id': data['tenant_id'] } if not data['tenant_id']: params['shared'] = True if self.initial['providernet_type'] == "vxlan": params['mode'] = data['mode'] if params['mode'] == 'dynamic': params['group'] = data['group'] params['port'] = int(data['port']) params['ttl'] = int(data['ttl']) providernet_range = stx_api.neutron.provider_network_range_create( request, **params) msg = (_('Provider network range %s was successfully created.') % providernet_range['id']) LOG.debug(msg) messages.success(request, msg) return providernet_range except neutron_exceptions.NeutronClientException as e: LOG.info(str(e)) redirect = reverse('horizon:admin:datanets:datanets:' 'detail', args=(data['providernet_id'], )) exceptions.handle(request, str(e), redirect=redirect) except Exception: msg = _('Failed to create a provider' ' network range for network %s') \ % data['providernet_id'] LOG.info(msg) redirect = reverse('horizon:admin:datanets:datanets:' 'detail', args=(data['providernet_id'], )) exceptions.handle(request, msg, redirect=redirect) def clean(self): cleaned_data = super(CreateProviderNetworkRange, self).clean() if not cleaned_data["shared"] and not cleaned_data["tenant_id"]: msg = "Project must be specified for non-shared Segmentation Range" raise forms.ValidationError(msg) if cleaned_data["shared"]: cleaned_data["tenant_id"] = ""
class LaunchForm(forms.SelfHandlingForm): name = forms.CharField(label=_("Cluster Name"), max_length=80) datastore = forms.ChoiceField( label=_("Datastore"), help_text=_("Type and version of datastore."), widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'datastore' })) network = forms.ChoiceField(label=_("Network"), help_text=_("Network attached to instance."), required=False) volume = forms.IntegerField(label=_("Volume Size"), min_value=0, initial=1, help_text=_("Size of the volume in GB.")) locality = forms.ChoiceField( label=_("Locality"), choices=[("", "None"), ("affinity", "affinity"), ("anti-affinity", "anti-affinity")], required=False, help_text=_("Specify whether instances in the cluster will " "be created on the same hypervisor (affinity) or on " "different hypervisors (anti-affinity).")) root_password = forms.CharField( label=_("Root Password"), required=False, help_text=_("Password for root user."), widget=forms.PasswordInput(attrs={ 'class': 'switched', 'data-switch-on': 'datastore', })) num_instances_vertica = forms.IntegerField( label=_("Number of Instances"), min_value=3, initial=3, required=False, help_text=_("Number of instances in the cluster. (Read only)"), widget=forms.TextInput( attrs={ 'readonly': 'readonly', 'class': 'switched', 'data-switch-on': 'datastore', })) num_shards = forms.IntegerField( label=_("Number of Shards"), min_value=1, initial=1, required=False, help_text=_("Number of shards. (Read only)"), widget=forms.TextInput( attrs={ 'readonly': 'readonly', 'class': 'switched', 'data-switch-on': 'datastore', })) num_instances = forms.IntegerField( label=_("Number of Instances"), initial=3, required=False, help_text=_("Number of instances in the cluster."), widget=forms.TextInput(attrs={ 'class': 'switched', 'data-switch-on': 'datastore', })) # (name of field variable, label) default_fields = [('num_instances', _('Number of Instances'))] mongodb_fields = default_fields + [ ('num_shards', _('Number of Shards')), ] vertica_fields = [ ('num_instances_vertica', ('Number of Instances')), ('root_password', _('Root Password')), ] def __init__(self, request, *args, **kwargs): super(LaunchForm, self).__init__(request, *args, **kwargs) self.fields['datastore'].choices = self.populate_datastore_choices( request) self.fields['network'].choices = self.populate_network_choices(request) def clean(self): datastore_field_value = self.data.get("datastore", None) if datastore_field_value: datastore, datastore_version = ( create_instance.parse_datastore_and_version_text( common_utils.unhexlify(datastore_field_value))) flavor_field_name = self._build_widget_field_name( datastore, datastore_version) if not self.data.get(flavor_field_name, None): msg = _("The flavor must be specified.") self._errors[flavor_field_name] = self.error_class([msg]) if db_capability.is_vertica_datastore(datastore): if not self.data.get("root_password", None): msg = _("Password for root user must be specified.") self._errors["root_password"] = self.error_class([msg]) else: if int(self.data.get("num_instances", 0)) < 1: msg = _("The number of instances must be greater than 1.") self._errors["num_instances"] = self.error_class([msg]) if db_capability.is_mongodb_datastore(datastore): if int(self.data.get("num_shards", 0)) < 1: msg = _("The number of shards must be greater than 1.") self._errors["num_shards"] = self.error_class([msg]) if not self.data.get("locality", None): self.cleaned_data["locality"] = None return self.cleaned_data @memoized.memoized_method def datastore_flavors(self, request, datastore_name, datastore_version): try: return trove_api.trove.datastore_flavors(request, datastore_name, datastore_version) except Exception: LOG.exception("Exception while obtaining flavors list") self._flavors = [] redirect = reverse('horizon:project:database_clusters:index') exceptions.handle(request, _('Unable to obtain flavors.'), redirect=redirect) @memoized.memoized_method def populate_network_choices(self, request): network_list = [] try: if api.base.is_service_enabled(request, 'network'): tenant_id = self.request.user.tenant_id networks = api.neutron.network_list_for_tenant( request, tenant_id) network_list = [(network.id, network.name_or_id) for network in networks] else: self.fields['network'].widget = forms.HiddenInput() except exceptions.ServiceCatalogException: network_list = [] redirect = reverse('horizon:project:database_clusters:index') exceptions.handle(request, _('Unable to retrieve networks.'), redirect=redirect) return network_list @memoized.memoized_method def datastores(self, request): try: return trove_api.trove.datastore_list(request) except Exception: LOG.exception("Exception while obtaining datastores list") self._datastores = [] redirect = reverse('horizon:project:database_clusters:index') exceptions.handle(request, _('Unable to obtain datastores.'), redirect=redirect) def filter_cluster_datastores(self, request): datastores = [] for ds in self.datastores(request): # TODO(michayu): until capabilities lands if db_capability.is_cluster_capable_datastore(ds.name): datastores.append(ds) return datastores @memoized.memoized_method def datastore_versions(self, request, datastore): try: return trove_api.trove.datastore_version_list(request, datastore) except Exception: LOG.exception("Exception while obtaining datastore version list") self._datastore_versions = [] redirect = reverse('horizon:project:database_clusters:index') exceptions.handle(request, _('Unable to obtain datastore versions.'), redirect=redirect) def populate_datastore_choices(self, request): choices = () datastores = self.filter_cluster_datastores(request) if datastores is not None: datastore_flavor_fields = {} for ds in datastores: versions = self.datastore_versions(request, ds.name) if versions: # only add to choices if datastore has at least one version version_choices = () for v in versions: # NOTE(zhaochao): troveclient API resources are lazy # loading objects. When an attribute is not found, the # get() method of the Manager object will be called # with the ID of the resource. However for # datastore_versions, the get() method is expecting two # arguments: datastore and datastore_version(name), so # TypeError will be raised as not enough arguments are # passed. In Python 2.x, hasattr() won't reraise the # exception(which is not correct), but reraise under # Python 3(which should be correct). # Use v.to_dict() to verify the 'active' info instead. if not v.to_dict().get('active', True): continue selection_text = self._build_datastore_display_text( ds.name, v.name) widget_text = self._build_widget_field_name( ds.name, v.name) version_choices = (version_choices + ((widget_text, selection_text), )) k, v = self._add_datastore_flavor_field( request, ds.name, v.name) datastore_flavor_fields[k] = v self._add_attr_to_optional_fields(ds.name, widget_text) choices = choices + version_choices self._insert_datastore_version_fields(datastore_flavor_fields) return choices def _add_datastore_flavor_field(self, request, datastore, datastore_version): name = self._build_widget_field_name(datastore, datastore_version) attr_key = 'data-datastore-' + name field = forms.ChoiceField(label=_("Flavor"), help_text=_("Size of image to launch."), required=False, widget=forms.Select( attrs={ 'class': 'switched', 'data-switch-on': 'datastore', attr_key: _("Flavor") })) valid_flavors = self.datastore_flavors(request, datastore, datastore_version) if valid_flavors: field.choices = instance_utils.sort_flavor_list( request, valid_flavors) return name, field def _build_datastore_display_text(self, datastore, datastore_version): return datastore + ' - ' + datastore_version def _build_widget_field_name(self, datastore, datastore_version): # Since the fieldnames cannot contain an uppercase character # we generate a hex encoded string representation of the # datastore and version as the fieldname return common_utils.hexlify( self._build_datastore_display_text(datastore, datastore_version)) def _insert_datastore_version_fields(self, datastore_flavor_fields): fields_to_restore_at_the_end = collections.OrderedDict() while True: k, v = self.fields.popitem() if k == 'datastore': self.fields[k] = v break else: fields_to_restore_at_the_end[k] = v for k, v in datastore_flavor_fields.items(): self.fields[k] = v for k in reversed(list(fields_to_restore_at_the_end.keys())): self.fields[k] = fields_to_restore_at_the_end[k] def _add_attr_to_optional_fields(self, datastore, selection_text): if db_capability.is_mongodb_datastore(datastore): fields = self.mongodb_fields elif db_capability.is_vertica_datastore(datastore): fields = self.vertica_fields else: fields = self.default_fields for field in fields: attr_key = 'data-datastore-' + selection_text widget = self.fields[field[0]].widget if attr_key not in widget.attrs: widget.attrs[attr_key] = field[1] def _get_locality(self, data): locality = None if data.get('locality'): locality = data['locality'] return locality @sensitive_variables('data') def handle(self, request, data): try: datastore, datastore_version = ( create_instance.parse_datastore_and_version_text( common_utils.unhexlify(data['datastore']))) flavor_field_name = self._build_widget_field_name( datastore, datastore_version) flavor = data[flavor_field_name] num_instances = data['num_instances'] root_password = None if db_capability.is_vertica_datastore(datastore): root_password = data['root_password'] num_instances = data['num_instances_vertica'] LOG.info( "Launching cluster with parameters " "{name=%s, volume=%s, flavor=%s, " "datastore=%s, datastore_version=%s", "locality=%s", data['name'], data['volume'], flavor, datastore, datastore_version, self._get_locality(data)) trove_api.trove.cluster_create(request, data['name'], data['volume'], flavor, num_instances, datastore=datastore, datastore_version=datastore_version, nics=data['network'], root_password=root_password, locality=self._get_locality(data)) messages.success(request, _('Launched cluster "%s"') % data['name']) return True except Exception: redirect = reverse("horizon:project:database_clusters:index") exceptions.handle(request, _('Unable to launch cluster.'), redirect=redirect)
class CreateNetwork(forms.SelfHandlingForm): name = forms.CharField(max_length=255, label=_("Name"), required=False) tenant_id = forms.ChoiceField(label=_("Project")) if api.neutron.is_port_profiles_supported(): widget = None else: widget = forms.HiddenInput() net_profile_id = forms.ChoiceField(label=_("Network Profile"), required=False, widget=widget) network_type = forms.ChoiceField( label=_("Provider Network Type"), help_text=_("The physical mechanism by which the virtual " "network is implemented."), widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'network_type' })) physical_network = forms.CharField( max_length=255, label=_("Physical Network"), help_text=_("The name of the physical network over which the " "virtual network is implemented."), initial='default', widget=forms.TextInput(attrs={ 'class': 'switched', 'data-switch-on': 'network_type', })) segmentation_id = forms.IntegerField( label=_("Segmentation ID"), widget=forms.TextInput(attrs={ 'class': 'switched', 'data-switch-on': 'network_type', })) admin_state = forms.ChoiceField(choices=[(True, _('UP')), (False, _('DOWN'))], label=_("Admin State")) shared = forms.BooleanField(label=_("Shared"), initial=False, required=False) external = forms.BooleanField(label=_("External Network"), initial=False, required=False) @classmethod def _instantiate(cls, request, *args, **kwargs): return cls(request, *args, **kwargs) def __init__(self, request, *args, **kwargs): super(CreateNetwork, self).__init__(request, *args, **kwargs) tenant_choices = [('', _("Select a project"))] tenants, has_more = api.keystone.tenant_list(request) for tenant in tenants: if tenant.enabled: tenant_choices.append((tenant.id, tenant.name)) self.fields['tenant_id'].choices = tenant_choices if api.neutron.is_port_profiles_supported(): self.fields['net_profile_id'].choices = ( self.get_network_profile_choices(request)) if api.neutron.is_extension_supported(request, 'provider'): neutron_settings = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}) self.seg_id_range = SEGMENTATION_ID_RANGE.copy() seg_id_range = neutron_settings.get('segmentation_id_range') if seg_id_range: self.seg_id_range.update(seg_id_range) self.provider_types = PROVIDER_TYPES.copy() extra_provider_types = neutron_settings.get('extra_provider_types') if extra_provider_types: self.provider_types.update(extra_provider_types) self.nettypes_with_seg_id = [ net_type for net_type in self.provider_types if self.provider_types[net_type]['require_segmentation_id']] self.nettypes_with_physnet = [ net_type for net_type in self.provider_types if self.provider_types[net_type]['require_physical_network']] supported_provider_types = neutron_settings.get( 'supported_provider_types', DEFAULT_PROVIDER_TYPES) if supported_provider_types == ['*']: supported_provider_types = DEFAULT_PROVIDER_TYPES undefined_provider_types = [ net_type for net_type in supported_provider_types if net_type not in self.provider_types] if undefined_provider_types: LOG.error('Undefined provider network types are found: %s', undefined_provider_types) seg_id_help = [ _("For %(type)s networks, valid IDs are %(min)s to %(max)s.") % {'type': net_type, 'min': self.seg_id_range[net_type][0], 'max': self.seg_id_range[net_type][1]} for net_type in self.nettypes_with_seg_id] self.fields['segmentation_id'].help_text = ' '.join(seg_id_help) # Register network types which require segmentation ID attrs = dict(('data-network_type-%s' % network_type, _('Segmentation ID')) for network_type in self.nettypes_with_seg_id) self.fields['segmentation_id'].widget.attrs.update(attrs) # Register network types which require physical network attrs = dict(('data-network_type-%s' % network_type, _('Physical Network')) for network_type in self.nettypes_with_physnet) self.fields['physical_network'].widget.attrs.update(attrs) network_type_choices = [ (net_type, self.provider_types[net_type]['display_name']) for net_type in supported_provider_types] if len(network_type_choices) == 0: self._hide_provider_network_type() else: self.fields['network_type'].choices = network_type_choices else: self._hide_provider_network_type() def get_network_profile_choices(self, request): profile_choices = [('', _("Select a profile"))] for profile in self._get_profiles(request, 'network'): profile_choices.append((profile.id, profile.name)) return profile_choices def _get_profiles(self, request, type_p): profiles = [] try: profiles = api.neutron.profile_list(request, type_p) except Exception: msg = _('Network Profiles could not be retrieved.') exceptions.handle(request, msg) return profiles def _hide_provider_network_type(self): self.fields['network_type'].widget = forms.HiddenInput() self.fields['physical_network'].widget = forms.HiddenInput() self.fields['segmentation_id'].widget = forms.HiddenInput() self.fields['network_type'].required = False self.fields['physical_network'].required = False self.fields['segmentation_id'].required = False def handle(self, request, data): try: params = {'name': data['name'], 'tenant_id': data['tenant_id'], 'admin_state_up': (data['admin_state'] == 'True'), 'shared': data['shared'], 'router:external': data['external']} if api.neutron.is_port_profiles_supported(): params['net_profile_id'] = data['net_profile_id'] if api.neutron.is_extension_supported(request, 'provider'): network_type = data['network_type'] params['provider:network_type'] = network_type if network_type in self.nettypes_with_physnet: params['provider:physical_network'] = ( data['physical_network']) if network_type in self.nettypes_with_seg_id: params['provider:segmentation_id'] = ( data['segmentation_id']) network = api.neutron.network_create(request, **params) msg = _('Network %s was successfully created.') % data['name'] LOG.debug(msg) messages.success(request, msg) return network except Exception: redirect = reverse('horizon:admin:networks:index') msg = _('Failed to create network %s') % data['name'] exceptions.handle(request, msg, redirect=redirect) def clean(self): cleaned_data = super(CreateNetwork, self).clean() if api.neutron.is_extension_supported(self.request, 'provider'): self._clean_physical_network(cleaned_data) self._clean_segmentation_id(cleaned_data) return cleaned_data def _clean_physical_network(self, data): network_type = data.get('network_type') if ('physical_network' in self._errors and network_type not in self.nettypes_with_physnet): # In this case the physical network is not required, so we can # ignore any errors. del self._errors['physical_network'] def _clean_segmentation_id(self, data): network_type = data.get('network_type') if 'segmentation_id' in self._errors: if network_type not in self.nettypes_with_seg_id: # In this case the segmentation ID is not required, so we can # ignore any errors. del self._errors['segmentation_id'] elif network_type in self.nettypes_with_seg_id: seg_id = data.get('segmentation_id') seg_id_range = {'min': self.seg_id_range[network_type][0], 'max': self.seg_id_range[network_type][1]} if seg_id < seg_id_range['min'] or seg_id > seg_id_range['max']: msg = (_('For a %(network_type)s network, valid segmentation ' 'IDs are %(min)s through %(max)s.') % {'network_type': network_type, 'min': seg_id_range['min'], 'max': seg_id_range['max']}) self._errors['segmentation_id'] = self.error_class([msg])
class StopPluginForm(forms.SelfHandlingForm): uuid = forms.CharField(label=_("Plugin ID"), widget=forms.HiddenInput) name = forms.CharField( label=_('Plugin Name'), widget=forms.TextInput(attrs={'readonly': 'readonly'}) ) delay = forms.IntegerField( label=_("Delay in secs"), required=False, help_text=_("OPTIONAL: seconds to wait before stopping the plugin") ) board_list = forms.MultipleChoiceField( label=_("Boards List"), widget=forms.SelectMultiple( attrs={'class': 'switchable', 'data-slug': 'slug-stop-boards'}), help_text=_("Select boards in this pool ") ) def __init__(self, *args, **kwargs): super(StopPluginForm, self).__init__(*args, **kwargs) # input=kwargs.get('initial',{}) boardslist_length = len(kwargs["initial"]["board_list"]) self.fields["board_list"].choices = kwargs["initial"]["board_list"] self.fields["board_list"].max_length = boardslist_length def handle(self, request, data): counter = 0 if not data["delay"]: data["delay"] = {} else: data["delay"] = {"delay": data["delay"]} for board in data["board_list"]: for key, value in self.fields["board_list"].choices: if key == board: try: plugin = None plugin = iotronic.plugin_action(request, key, data["uuid"], "PluginStop", data["delay"]) # LOG.debug("API: %s %s", plugin, request) message_text = "Plugin stopped successfully on board "\ + str(value) + "." messages.success(request, _(message_text)) if counter != len(data["board_list"]) - 1: counter += 1 else: return plugin except Exception: message_text = "Unable to stop plugin on board " \ + str(value) + "." exceptions.handle(request, _(message_text)) break
class SetInstanceDetailsAction(workflows.Action): SOURCE_TYPE_CHOICES = ( ("image_id", _("Image")), ("instance_snapshot_id", _("Snapshot")), ) source_type = forms.ChoiceField(label=_("Instance Source"), choices=SOURCE_TYPE_CHOICES) image_id = forms.ChoiceField(label=_("Image"), required=False) instance_snapshot_id = forms.ChoiceField(label=_("Instance Snapshot"), required=False) name = forms.CharField(max_length=80, label=_("Instance Name")) flavor = forms.ChoiceField(label=_("Flavor"), help_text=_("Size of image to launch.")) count = forms.IntegerField(label=_("Instance Count"), min_value=1, initial=1, help_text=_("Number of instances to launch.")) class Meta: name = _("Details") help_text_template = ("nova/instances/" "_launch_details_help.html") def clean(self): cleaned_data = super(SetInstanceDetailsAction, self).clean() # Validate our instance source. source = cleaned_data['source_type'] # There should always be at least one image_id choice, telling the user # that there are "No Images Available" so we check for 2 here... if source == 'image_id' and not \ filter(lambda x: x[0] != '', self.fields['image_id'].choices): raise forms.ValidationError(_("There are no image sources " "available; you must first create " "an image before attempting to " "launch an instance.")) if not cleaned_data[source]: raise forms.ValidationError(_("Please select an option for the " "instance source.")) # Prevent launching multiple instances with the same volume. # TODO(gabriel): is it safe to launch multiple instances with # a snapshot since it should be cloned to new volumes? count = cleaned_data.get('count', 1) volume_type = self.data.get('volume_type', None) if volume_type and count > 1: msg = _('Launching multiple instances is only supported for ' 'images and instance snapshots.') raise forms.ValidationError(msg) return cleaned_data def _get_available_images(self, request, context): project_id = context.get('project_id', None) if not hasattr(self, "_public_images"): public = {"is_public": True, "status": "active"} try: public_images, _more = api.glance.image_list_detailed(request, filters=public) except: public_images = [] exceptions.handle(request, _("Unable to retrieve public images.")) self._public_images = public_images # Preempt if we don't have a project_id yet. if project_id is None: setattr(self, "_images_for_%s" % project_id, []) if not hasattr(self, "_images_for_%s" % project_id): owner = {"property-owner_id": project_id, "status": "active"} try: owned_images, _more = api.glance.image_list_detailed(request, filters=owner) except: exceptions.handle(request, _("Unable to retrieve images for " "the current project.")) setattr(self, "_images_for_%s" % project_id, owned_images) owned_images = getattr(self, "_images_for_%s" % project_id) images = owned_images + self._public_images # Remove duplicate images image_ids = [] final_images = [] for image in images: if image.id not in image_ids: image_ids.append(image.id) final_images.append(image) return [image for image in final_images if image.container_format not in ('aki', 'ari')] def populate_image_id_choices(self, request, context): images = self._get_available_images(request, context) choices = [(image.id, image.name) for image in images if image.properties.get("image_type", '') != "snapshot"] if choices: choices.insert(0, ("", _("Select Image"))) else: choices.insert(0, ("", _("No images available."))) return choices def populate_instance_snapshot_id_choices(self, request, context): images = self._get_available_images(request, context) choices = [(image.id, image.name) for image in images if image.properties.get("image_type", '') == "snapshot"] if choices: choices.insert(0, ("", _("Select Instance Snapshot"))) else: choices.insert(0, ("", _("No snapshots available."))) return choices def populate_flavor_choices(self, request, context): try: flavors = api.nova.flavor_list(request) flavor_list = [(flavor.id, "%s" % flavor.name) for flavor in flavors] except: flavor_list = [] exceptions.handle(request, _('Unable to retrieve instance flavors.')) return sorted(flavor_list) def get_help_text(self): extra = {} try: extra['usages'] = api.nova.tenant_quota_usages(self.request) extra['usages_json'] = jsonutils.dumps(extra['usages']) flavors = jsonutils.dumps([f._info for f in api.nova.flavor_list(self.request)]) extra['flavors'] = flavors except: exceptions.handle(self.request, _("Unable to retrieve quota information.")) return super(SetInstanceDetailsAction, self).get_help_text(extra)
class ProjectQuotaAction(workflows.Action): ifcb_label = _("Injected File Content (Bytes)") metadata_items = forms.IntegerField(min_value=-1, label=_("Metadata Items")) cores = forms.IntegerField(min_value=-1, label=_("VCPUs")) instances = forms.IntegerField(min_value=-1, label=_("Instances")) injected_files = forms.IntegerField(min_value=-1, label=_("Injected Files")) injected_file_content_bytes = forms.IntegerField(min_value=-1, label=ifcb_label) volumes = forms.IntegerField(min_value=-1, label=_("Volumes")) snapshots = forms.IntegerField(min_value=-1, label=_("Volume Snapshots")) gigabytes = forms.IntegerField( min_value=-1, label=_("Total Size of Volumes and Snapshots (GB)")) ram = forms.IntegerField(min_value=-1, label=_("RAM (MB)")) floating_ips = forms.IntegerField(min_value=-1, label=_("Floating IPs")) fixed_ips = forms.IntegerField(min_value=-1, label=_("Fixed IPs")) security_groups = forms.IntegerField(min_value=-1, label=_("Security Groups")) security_group_rules = forms.IntegerField(min_value=-1, label=_("Security Group Rules")) # Neutron security_group = forms.IntegerField(min_value=-1, label=_("Security Groups")) security_group_rule = forms.IntegerField(min_value=-1, label=_("Security Group Rules")) floatingip = forms.IntegerField(min_value=-1, label=_("Floating IPs")) network = forms.IntegerField(min_value=-1, label=_("Networks")) port = forms.IntegerField(min_value=-1, label=_("Ports")) router = forms.IntegerField(min_value=-1, label=_("Routers")) subnet = forms.IntegerField(min_value=-1, label=_("Subnets")) def __init__(self, request, *args, **kwargs): super(ProjectQuotaAction, self).__init__(request, *args, **kwargs) disabled_quotas = quotas.get_disabled_quotas(request) for field in disabled_quotas: if field in self.fields: self.fields[field].required = False self.fields[field].widget = forms.HiddenInput()
class CreateFlavorInfoAction(workflows.Action): _flavor_id_regex = (r'^[a-zA-Z0-9. _-]+$') _flavor_id_help_text = _("flavor id can only contain alphanumeric " "characters, underscores, periods, hyphens, " "spaces.") name = forms.CharField(label=_("Name"), max_length=255) flavor_id = forms.RegexField(label=_("ID"), regex=_flavor_id_regex, required=False, initial='auto', max_length=255, help_text=_flavor_id_help_text) vcpus = forms.IntegerField(label=_("VCPUs"), min_value=1, max_value=2147483647) memory_mb = forms.IntegerField(label=_("RAM (MB)"), min_value=1, max_value=2147483647) disk_gb = forms.IntegerField(label=_("Root Disk (GB)"), min_value=0, max_value=2147483647) eph_gb = forms.IntegerField(label=_("Ephemeral Disk (GB)"), required=False, initial=0, min_value=0) swap_mb = forms.IntegerField(label=_("Swap Disk (MB)"), required=False, initial=0, min_value=0) rxtx_factor = forms.FloatField(label=_("RX/TX Factor"), required=False, initial=1, min_value=1) 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.") def clean_name(self): name = self.cleaned_data.get('name').strip() if not name: msg = _('Flavor name cannot be empty.') self._errors['name'] = self.error_class([msg]) return name def clean(self): cleaned_data = super().clean() name = cleaned_data.get('name') flavor_id = cleaned_data.get('flavor_id') try: flavors = api.nova.flavor_list(self.request, None) except Exception: flavors = [] msg = _('Unable to get flavor list') exceptions.handle(self.request, msg) raise if flavors is not None and name is not None: for flavor in flavors: if flavor.name.lower() == name.lower(): error_msg = _('The name "%s" is already used by ' 'another flavor.') % name self._errors['name'] = self.error_class([error_msg]) if flavor.id == flavor_id: error_msg = _('The ID "%s" is already used by ' 'another flavor.') % flavor_id self._errors['flavor_id'] = self.error_class([error_msg]) return cleaned_data
class UpdateIKEPolicy(forms.SelfHandlingForm): name = forms.CharField(max_length=80, label=_("Name"), required=False) ikepolicy_id = forms.CharField( label=_("ID"), widget=forms.TextInput(attrs={'readonly': 'readonly'})) description = forms.CharField( required=False, max_length=80, label=_("Description")) # Currently this field has only one choice, so mark it as readonly. auth_algorithm = forms.ChoiceField( label=_("Authorization algorithm"), choices=[('sha1', _('sha1'))], widget=forms.Select(attrs={'readonly': 'readonly'})) encryption_algorithm = forms.ChoiceField( label=_("Encryption algorithm"), choices=[('3des', _('3des')), ('aes-128', _('aes-128')), ('aes-192', _('aes-192')), ('aes-256', _('aes-256'))]) ike_version = forms.ChoiceField( label=_("IKE version"), choices=[('v1', _('v1')), ('v2', _('v2'))]) # Currently this field has only one choice, so mark it as readonly. lifetime_units = forms.ChoiceField( label=_("Lifetime units for IKE keys"), choices=[('seconds', _('seconds'))], widget=forms.Select(attrs={'readonly': 'readonly'})) lifetime_value = forms.IntegerField( min_value=60, label=_("Lifetime value for IKE keys"), help_text=_("Equal to or greater than 60")) pfs = forms.ChoiceField( label=_("Perfect Forward Secrecy"), choices=[('group2', _('group2')), ('group5', _('group5')), ('group14', _('group14'))]) # Currently this field has only one choice, so mark it as readonly. phase1_negotiation_mode = forms.ChoiceField( label=_("IKE Phase1 negotiation mode"), choices=[('main', 'main')], widget=forms.Select(attrs={'readonly': 'readonly'})) failure_url = 'horizon:project:vpn:index' def handle(self, request, context): try: data = {'ikepolicy': {'name': context['name'], 'description': context['description'], 'auth_algorithm': context['auth_algorithm'], 'encryption_algorithm': context['encryption_algorithm'], 'ike_version': context['ike_version'], 'lifetime': {'units': context['lifetime_units'], 'value': context['lifetime_value']}, 'pfs': context['pfs'], 'phase1_negotiation_mode': context['phase1_negotiation_mode'], }} ikepolicy = api.vpn.ikepolicy_update( request, context['ikepolicy_id'], **data) msg = (_('IKE Policy %s was successfully updated.') % context['name']) LOG.debug(msg) messages.success(request, msg) return ikepolicy except Exception as e: msg = _('Failed to update IKE Policy %s') % context['name'] LOG.info('%s: %s' % (msg, e)) redirect = reverse(self.failure_url) exceptions.handle(request, msg, redirect=redirect)
class CreateNetwork(forms.SelfHandlingForm): name = forms.CharField(max_length=255, label=_("Name"), required=False) tenant_id = forms.ThemableChoiceField(label=_("Project")) network_type = forms.ChoiceField( label=_("Provider Network Type"), help_text=_("The physical mechanism by which the virtual " "network is implemented."), widget=forms.ThemableSelectWidget(attrs={ 'class': 'switchable', 'data-slug': 'network_type' })) physical_network = forms.CharField( max_length=255, label=_("Physical Network"), help_text=_("The name of the physical network over which the " "virtual network is implemented. Specify one of the " "physical networks defined in your neutron deployment."), widget=forms.TextInput(attrs={ 'class': 'switched', 'data-switch-on': 'network_type', })) segmentation_id = forms.IntegerField( label=_("Segmentation ID"), widget=forms.TextInput(attrs={ 'class': 'switched', 'data-switch-on': 'network_type', })) admin_state = forms.BooleanField(label=_("Enable Admin State"), initial=True, required=False) shared = forms.BooleanField(label=_("Shared"), initial=False, required=False) external = forms.BooleanField(label=_("External Network"), initial=False, required=False) with_subnet = forms.BooleanField(label=_("Create Subnet"), widget=forms.CheckboxInput( attrs={ 'class': 'switchable', 'data-slug': 'with_subnet', 'data-hide-tab': 'create_network__' 'createsubnetinfo' 'action,' 'create_network__' 'createsubnetdetail' 'action', 'data-hide-on-checked': 'false' }), initial=True, required=False) az_hints = forms.MultipleChoiceField( label=_("Availability Zone Hints"), required=False, help_text=_("Availability zones where the DHCP agents may be " "scheduled. Leaving this unset is equivalent to " "selecting all availability zones")) @classmethod def _instantiate(cls, request, *args, **kwargs): return cls(request, *args, **kwargs) def __init__(self, request, *args, **kwargs): super(CreateNetwork, self).__init__(request, *args, **kwargs) tenant_choices = [('', _("Select a project"))] tenants, has_more = api.keystone.tenant_list(request) for tenant in tenants: if tenant.enabled: tenant_choices.append((tenant.id, tenant.name)) self.fields['tenant_id'].choices = tenant_choices try: is_extension_supported = \ api.neutron.is_extension_supported(request, 'provider') except Exception: msg = _("Unable to verify Neutron service providers") exceptions.handle(self.request, msg) self._hide_provider_network_type() is_extension_supported = False if is_extension_supported: neutron_settings = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}) self.seg_id_range = SEGMENTATION_ID_RANGE.copy() seg_id_range = neutron_settings.get('segmentation_id_range') if seg_id_range: self.seg_id_range.update(seg_id_range) self.provider_types = PROVIDER_TYPES.copy() extra_provider_types = neutron_settings.get('extra_provider_types') if extra_provider_types: self.provider_types.update(extra_provider_types) self.nettypes_with_seg_id = [ net_type for net_type in self.provider_types if self.provider_types[net_type]['require_segmentation_id'] ] self.nettypes_with_physnet = [ net_type for net_type in self.provider_types if self.provider_types[net_type]['require_physical_network'] ] supported_provider_types = neutron_settings.get( 'supported_provider_types', DEFAULT_PROVIDER_TYPES) if supported_provider_types == ['*']: supported_provider_types = DEFAULT_PROVIDER_TYPES undefined_provider_types = [ net_type for net_type in supported_provider_types if net_type not in self.provider_types ] if undefined_provider_types: LOG.error('Undefined provider network types are found: %s', undefined_provider_types) seg_id_help = [ _("For %(type)s networks, valid IDs are %(min)s to %(max)s.") % { 'type': net_type, 'min': self.seg_id_range[net_type][0], 'max': self.seg_id_range[net_type][1] } for net_type in self.nettypes_with_seg_id ] self.fields['segmentation_id'].help_text = ' '.join(seg_id_help) # Register network types which require segmentation ID attrs = dict( ('data-network_type-%s' % network_type, _('Segmentation ID')) for network_type in self.nettypes_with_seg_id) self.fields['segmentation_id'].widget.attrs.update(attrs) physical_networks = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}).get('physical_networks', []) if physical_networks: self.fields['physical_network'] = forms.ThemableChoiceField( label=_("Physical Network"), choices=[(net, net) for net in physical_networks], widget=forms.ThemableSelectWidget( attrs={ 'class': 'switched', 'data-switch-on': 'network_type', }), help_text=_("The name of the physical network over " "which the virtual network is implemented."), ) # Register network types which require physical network attrs = dict( ('data-network_type-%s' % network_type, _('Physical Network')) for network_type in self.nettypes_with_physnet) self.fields['physical_network'].widget.attrs.update(attrs) network_type_choices = [ (net_type, self.provider_types[net_type]['display_name']) for net_type in supported_provider_types ] if not network_type_choices: self._hide_provider_network_type() else: self.fields['network_type'].choices = network_type_choices try: if api.neutron.is_extension_supported(request, 'network_availability_zone'): zones = api.neutron.list_availability_zones( self.request, 'network', 'available') self.fields['az_hints'].choices = [(zone['name'], zone['name']) for zone in zones] else: del self.fields['az_hints'] except Exception: msg = _('Failed to get availability zone list.') messages.warning(request, msg) del self.fields['az_hints'] def _hide_provider_network_type(self): self.fields['network_type'].widget = forms.HiddenInput() self.fields['physical_network'].widget = forms.HiddenInput() self.fields['segmentation_id'].widget = forms.HiddenInput() self.fields['network_type'].required = False self.fields['physical_network'].required = False self.fields['segmentation_id'].required = False def handle(self, request, data): try: params = { 'name': data['name'], 'tenant_id': data['tenant_id'], 'admin_state_up': data['admin_state'], 'shared': data['shared'], 'router:external': data['external'] } if api.neutron.is_extension_supported(request, 'provider'): network_type = data['network_type'] params['provider:network_type'] = network_type if network_type in self.nettypes_with_physnet: params['provider:physical_network'] = ( data['physical_network']) if network_type in self.nettypes_with_seg_id: params['provider:segmentation_id'] = ( data['segmentation_id']) if 'az_hints' in data and data['az_hints']: params['availability_zone_hints'] = data['az_hints'] network = api.neutron.network_create(request, **params) LOG.debug('Network %s was successfully created.', data['name']) return network except Exception: redirect = reverse('horizon:admin:networks:index') msg = _('Failed to create network %s') % data['name'] exceptions.handle(request, msg, redirect=redirect) def clean(self): cleaned_data = super(CreateNetwork, self).clean() if api.neutron.is_extension_supported(self.request, 'provider'): self._clean_physical_network(cleaned_data) self._clean_segmentation_id(cleaned_data) return cleaned_data def _clean_physical_network(self, data): network_type = data.get('network_type') if ('physical_network' in self._errors and network_type not in self.nettypes_with_physnet): # In this case the physical network is not required, so we can # ignore any errors. del self._errors['physical_network'] def _clean_segmentation_id(self, data): network_type = data.get('network_type') if 'segmentation_id' in self._errors: if (network_type not in self.nettypes_with_seg_id and not self.data.get("segmentation_id")): # In this case the segmentation ID is not required, so we can # ignore the field is required error. del self._errors['segmentation_id'] elif network_type in self.nettypes_with_seg_id: seg_id = data.get('segmentation_id') seg_id_range = { 'min': self.seg_id_range[network_type][0], 'max': self.seg_id_range[network_type][1] } if seg_id < seg_id_range['min'] or seg_id > seg_id_range['max']: msg = (_('For a %(network_type)s network, valid segmentation ' 'IDs are %(min)s through %(max)s.') % { 'network_type': network_type, 'min': seg_id_range['min'], 'max': seg_id_range['max'] }) self._errors['segmentation_id'] = self.error_class([msg])
class CreateForm(forms.SelfHandlingForm): name = forms.CharField(max_length=255, label=_("Volume Name"), required=False) description = forms.CharField(max_length=255, widget=forms.Textarea( attrs={'rows': 4}), label=_("Description"), required=False) volume_source_type = forms.ChoiceField( label=_("Volume Source"), required=False, widget=forms.ThemableSelectWidget(attrs={ 'class': 'switchable', 'data-slug': 'source'})) snapshot_source = forms.ChoiceField( label=_("Use snapshot as a source"), widget=forms.ThemableSelectWidget( attrs={'class': 'snapshot-selector'}, data_attrs=('size', 'name'), transform=lambda x: "%s (%s GiB)" % (x.name, x.size)), required=False) image_source = forms.ChoiceField( label=_("Use image as a source"), widget=forms.ThemableSelectWidget( attrs={'class': 'image-selector'}, data_attrs=('size', 'name', 'min_disk'), transform=lambda x: "%s (%s)" % (x.name, filesizeformat(x.bytes))), required=False) volume_source = forms.ChoiceField( label=_("Use a volume as source"), widget=forms.ThemableSelectWidget( attrs={'class': 'image-selector'}, data_attrs=('size', 'name'), transform=lambda x: "%s (%s GiB)" % (x.name, x.size)), required=False) type = forms.ChoiceField( label=_("Type"), required=False, widget=forms.ThemableSelectWidget( attrs={'class': 'switched', 'data-switch-on': 'source', 'data-source-no_source_type': _('Type'), 'data-source-image_source': _('Type')})) size = forms.IntegerField(min_value=1, initial=1, label=_("Size (GiB)")) availability_zone = forms.ChoiceField( label=_("Availability Zone"), required=False, widget=forms.ThemableSelectWidget( attrs={'class': 'switched', 'data-switch-on': 'source', 'data-source-no_source_type': _('Availability Zone'), 'data-source-image_source': _('Availability Zone')})) def prepare_source_fields_if_snapshot_specified(self, request): try: snapshot = self.get_snapshot(request, request.GET["snapshot_id"]) self.fields['name'].initial = snapshot.name self.fields['size'].initial = snapshot.size self.fields['snapshot_source'].choices = ((snapshot.id, snapshot),) try: # Set the volume type from the original volume orig_volume = cinder.volume_get(request, snapshot.volume_id) self.fields['type'].initial = orig_volume.volume_type except Exception: pass self.fields['size'].help_text = ( _('Volume size must be equal to or greater than the ' 'snapshot size (%sGiB)') % snapshot.size) del self.fields['image_source'] del self.fields['volume_source'] del self.fields['volume_source_type'] del self.fields['availability_zone'] except Exception: exceptions.handle(request, _('Unable to load the specified snapshot.')) def prepare_source_fields_if_image_specified(self, request): self.fields['availability_zone'].choices = \ availability_zones(request) try: image = self.get_image(request, request.GET["image_id"]) image.bytes = image.size self.fields['name'].initial = image.name min_vol_size = functions.bytes_to_gigabytes( image.size) size_help_text = (_('Volume size must be equal to or greater ' 'than the image size (%s)') % filesizeformat(image.size)) properties = getattr(image, 'properties', {}) min_disk_size = (getattr(image, 'min_disk', 0) or properties.get('min_disk', 0)) if (min_disk_size > min_vol_size): min_vol_size = min_disk_size size_help_text = (_('Volume size must be equal to or ' 'greater than the image minimum ' 'disk size (%sGiB)') % min_disk_size) self.fields['size'].initial = min_vol_size self.fields['size'].help_text = size_help_text self.fields['image_source'].choices = ((image.id, image),) del self.fields['snapshot_source'] del self.fields['volume_source'] del self.fields['volume_source_type'] except Exception: msg = _('Unable to load the specified image. %s') exceptions.handle(request, msg % request.GET['image_id']) def prepare_source_fields_if_volume_specified(self, request): self.fields['availability_zone'].choices = \ availability_zones(request) volume = None try: volume = self.get_volume(request, request.GET["volume_id"]) except Exception: msg = _('Unable to load the specified volume. %s') exceptions.handle(request, msg % request.GET['volume_id']) if volume is not None: self.fields['name'].initial = volume.name self.fields['description'].initial = volume.description min_vol_size = volume.size size_help_text = (_('Volume size must be equal to or greater ' 'than the origin volume size (%sGiB)') % volume.size) self.fields['size'].initial = min_vol_size self.fields['size'].help_text = size_help_text self.fields['volume_source'].choices = ((volume.id, volume),) self.fields['type'].initial = volume.type del self.fields['snapshot_source'] del self.fields['image_source'] del self.fields['volume_source_type'] def prepare_source_fields_default(self, request): source_type_choices = [] self.fields['availability_zone'].choices = \ availability_zones(request) try: available = api.cinder.VOLUME_STATE_AVAILABLE snapshots = cinder.volume_snapshot_list( request, search_opts=dict(status=available)) if snapshots: source_type_choices.append(("snapshot_source", _("Snapshot"))) choices = [('', _("Choose a snapshot"))] + \ [(s.id, s) for s in snapshots] self.fields['snapshot_source'].choices = choices else: del self.fields['snapshot_source'] except Exception: exceptions.handle(request, _("Unable to retrieve volume snapshots.")) images = utils.get_available_images(request, request.user.tenant_id) if images: source_type_choices.append(("image_source", _("Image"))) choices = [('', _("Choose an image"))] for image in images: image.bytes = image.size image.size = functions.bytes_to_gigabytes(image.bytes) choices.append((image.id, image)) self.fields['image_source'].choices = choices else: del self.fields['image_source'] volumes = self.get_volumes(request) if volumes: source_type_choices.append(("volume_source", _("Volume"))) choices = [('', _("Choose a volume"))] for volume in volumes: choices.append((volume.id, volume)) self.fields['volume_source'].choices = choices else: del self.fields['volume_source'] if source_type_choices: choices = ([('no_source_type', _("No source, empty volume"))] + source_type_choices) self.fields['volume_source_type'].choices = choices else: del self.fields['volume_source_type'] def __init__(self, request, *args, **kwargs): super(CreateForm, self).__init__(request, *args, **kwargs) volume_types = cinder.volume_type_list(request) self.fields['type'].choices = [("no_type", _("No volume type"))] + \ [(type.name, type.name) for type in volume_types] if 'initial' in kwargs and 'type' in kwargs['initial']: # if there is a default volume type to select, then remove # the first ""No volume type" entry self.fields['type'].choices.pop(0) if "snapshot_id" in request.GET: self.prepare_source_fields_if_snapshot_specified(request) elif 'image_id' in request.GET: self.prepare_source_fields_if_image_specified(request) elif 'volume_id' in request.GET: self.prepare_source_fields_if_volume_specified(request) else: self.prepare_source_fields_default(request) def clean(self): cleaned_data = super(CreateForm, self).clean() source_type = self.cleaned_data.get('volume_source_type') if (source_type == 'image_source' and not cleaned_data.get('image_source')): msg = _('Image source must be specified') self._errors['image_source'] = self.error_class([msg]) elif (source_type == 'snapshot_source' and not cleaned_data.get('snapshot_source')): msg = _('Snapshot source must be specified') self._errors['snapshot_source'] = self.error_class([msg]) elif (source_type == 'volume_source' and not cleaned_data.get('volume_source')): msg = _('Volume source must be specified') self._errors['volume_source'] = self.error_class([msg]) return cleaned_data def get_volumes(self, request): volumes = [] try: available = api.cinder.VOLUME_STATE_AVAILABLE volumes = cinder.volume_list(self.request, search_opts=dict(status=available)) except Exception: exceptions.handle(request, _('Unable to retrieve list of volumes.')) return volumes def handle(self, request, data): try: usages = quotas.tenant_limit_usages(self.request) availableGB = usages['maxTotalVolumeGigabytes'] - \ usages['gigabytesUsed'] availableVol = usages['maxTotalVolumes'] - usages['volumesUsed'] snapshot_id = None image_id = None volume_id = None source_type = data.get('volume_source_type', None) az = data.get('availability_zone', None) or None if (data.get("snapshot_source", None) and source_type in ['', None, 'snapshot_source']): # Create from Snapshot snapshot = self.get_snapshot(request, data["snapshot_source"]) snapshot_id = snapshot.id if (data['size'] < snapshot.size): error_message = (_('The volume size cannot be less than ' 'the snapshot size (%sGiB)') % snapshot.size) raise ValidationError(error_message) az = None elif (data.get("image_source", None) and source_type in ['', None, 'image_source']): # Create from Snapshot image = self.get_image(request, data["image_source"]) image_id = image.id image_size = functions.bytes_to_gigabytes(image.size) if (data['size'] < image_size): error_message = (_('The volume size cannot be less than ' 'the image size (%s)') % filesizeformat(image.size)) raise ValidationError(error_message) properties = getattr(image, 'properties', {}) min_disk_size = (getattr(image, 'min_disk', 0) or properties.get('min_disk', 0)) if (min_disk_size > 0 and data['size'] < min_disk_size): error_message = (_('The volume size cannot be less than ' 'the image minimum disk size (%sGiB)') % min_disk_size) raise ValidationError(error_message) elif (data.get("volume_source", None) and source_type in ['', None, 'volume_source']): # Create from volume volume = self.get_volume(request, data["volume_source"]) volume_id = volume.id if data['size'] < volume.size: error_message = (_('The volume size cannot be less than ' 'the source volume size (%sGiB)') % volume.size) raise ValidationError(error_message) else: if type(data['size']) is str: data['size'] = int(data['size']) if availableGB < data['size']: error_message = _('A volume of %(req)iGiB cannot be created ' 'as you only have %(avail)iGiB of your ' 'quota available.') params = {'req': data['size'], 'avail': availableGB} raise ValidationError(error_message % params) elif availableVol <= 0: error_message = _('You are already using all of your available' ' volumes.') raise ValidationError(error_message) metadata = {} if data['type'] == 'no_type': data['type'] = '' volume = cinder.volume_create(request, data['size'], data['name'], data['description'], data['type'], snapshot_id=snapshot_id, image_id=image_id, metadata=metadata, availability_zone=az, source_volid=volume_id) message = _('Creating volume "%s"') % data['name'] messages.info(request, message) return volume except ValidationError as e: self.api_error(e.messages[0]) return False except Exception: redirect = reverse("horizon:project:volumes:index") exceptions.handle(request, _("Unable to create volume."), redirect=redirect) @memoized def get_snapshot(self, request, id): return cinder.volume_snapshot_get(request, id) @memoized def get_image(self, request, id): return glance.image_get(request, id) @memoized def get_volume(self, request, id): return cinder.volume_get(request, id)
class CreateStackForm(forms.SelfHandlingForm): param_prefix = '__param_' class Meta(object): name = _('Create Stack') environment_data = forms.CharField(widget=forms.widgets.HiddenInput, required=False) if django.VERSION >= (1, 9): # Note(Itxaka): On django>=1.9 Charfield has an strip option that # we need to set to False as to not hit # https://bugs.launchpad.net/python-heatclient/+bug/1546166 environment_data.strip = False parameters = forms.CharField(widget=forms.widgets.HiddenInput) stack_name = forms.RegexField( max_length=255, label=_('Stack Name'), help_text=_('Name of the stack to create.'), regex=r"^[a-zA-Z][a-zA-Z0-9_.-]*$", error_messages={ 'invalid': _('Name must start with a letter and may ' 'only contain letters, numbers, underscores, ' 'periods and hyphens.') }) timeout_mins = forms.IntegerField( initial=60, label=_('Creation Timeout (minutes)'), help_text=_('Stack creation timeout in minutes.')) enable_rollback = forms.BooleanField( label=_('Rollback On Failure'), help_text=_('Enable rollback on create/update failure.'), required=False) def __init__(self, *args, **kwargs): parameters = kwargs.pop('parameters') # special case: load template data from API, not passed in params if kwargs.get('validate_me'): parameters = kwargs.pop('validate_me') super(CreateStackForm, self).__init__(*args, **kwargs) if self._stack_password_enabled(): self.fields['password'] = forms.CharField( label=_('Password for user "%s"') % self.request.user.username, help_text=_('This is required for operations to be performed ' 'throughout the lifecycle of the stack'), widget=forms.PasswordInput()) self._build_parameter_fields(parameters) def _stack_password_enabled(self): stack_settings = getattr(settings, 'OPENSTACK_HEAT_STACK', {}) return stack_settings.get('enable_user_pass', True) def _build_parameter_fields(self, template_validate): self.help_text = template_validate['Description'] params = template_validate.get('Parameters', {}) if template_validate.get('ParameterGroups'): params_in_order = [] for group in template_validate['ParameterGroups']: for param in group.get('parameters', []): if param in params: params_in_order.append((param, params[param])) else: # no parameter groups, simply sorted to make the order fixed params_in_order = sorted(params.items()) for param_key, param in params_in_order: field = None field_key = self.param_prefix + param_key initial = param.get( 'Value', param.get('DefaultValue', param.get('Default'))) field_args = { 'initial': initial, 'label': param.get('Label', param_key), 'help_text': html.escape(param.get('Description', '')), 'required': initial is None, } param_type = param.get('Type', None) hidden = strutils.bool_from_string(param.get('NoEcho', 'false')) if 'CustomConstraint' in param: choices = self._populate_custom_choices( param['CustomConstraint']) field_args['choices'] = choices field = forms.ChoiceField(**field_args) elif 'AllowedValues' in param: choices = map(lambda x: (x, x), param['AllowedValues']) field_args['choices'] = choices field = forms.ChoiceField(**field_args) elif param_type == 'Json' and 'Default' in param: field_args['initial'] = json.dumps(param['Default']) field = forms.CharField(**field_args) elif param_type in ('CommaDelimitedList', 'String', 'Json'): if 'MinLength' in param: field_args['min_length'] = int(param['MinLength']) field_args['required'] = field_args['min_length'] > 0 if 'MaxLength' in param: field_args['max_length'] = int(param['MaxLength']) if hidden: field_args['widget'] = forms.PasswordInput( render_value=True) field = forms.CharField(**field_args) elif param_type == 'Number': if 'MinValue' in param: field_args['min_value'] = int(param['MinValue']) if 'MaxValue' in param: field_args['max_value'] = int(param['MaxValue']) field = forms.IntegerField(**field_args) elif param_type == 'Boolean': field_args['required'] = False field = forms.BooleanField(**field_args) if field: self.fields[field_key] = field @sensitive_variables('password') def handle(self, request, data): prefix_length = len(self.param_prefix) params_list = [(k[prefix_length:], v) for (k, v) in data.items() if k.startswith(self.param_prefix)] fields = { 'stack_name': data.get('stack_name'), 'timeout_mins': data.get('timeout_mins'), 'disable_rollback': not (data.get('enable_rollback')), 'parameters': dict(params_list), 'files': json.loads(data.get('parameters')).get('files'), 'template': json.loads(data.get('parameters')).get('template') } if data.get('password'): fields['password'] = data.get('password') if data.get('environment_data'): fields['environment'] = data.get('environment_data') try: api.heat.stack_create(self.request, **fields) messages.info(request, _("Stack creation started.")) return True except Exception: exceptions.handle(request) def _populate_custom_choices(self, custom_type): if custom_type == 'neutron.network': return instance_utils.network_field_data(self.request, True) if custom_type == 'nova.keypair': return instance_utils.keypair_field_data(self.request, True) if custom_type == 'glance.image': return image_utils.image_field_data(self.request, True) if custom_type == 'nova.flavor': return instance_utils.flavor_field_data(self.request, True) return []
class AddRule(forms.SelfHandlingForm): ip_protocol = forms.ChoiceField( label=_('IP Protocol'), choices=[('tcp', 'TCP'), ('udp', 'UDP'), ('icmp', 'ICMP')], help_text=_("The protocol which this " "rule should be applied to."), widget=forms.Select(attrs={'class': 'switchable'})) from_port = forms.IntegerField( label=_("From Port"), help_text=_("TCP/UDP: Enter integer value " "between 1 and 65535. ICMP: " "enter a value for ICMP type " "in the range (-1: 255)"), widget=forms.TextInput(attrs={ 'data': _('From Port'), 'data-icmp': _('Type') }), validators=[validate_port_range]) to_port = forms.IntegerField(label=_("To Port"), help_text=_("TCP/UDP: Enter integer value " "between 1 and 65535. ICMP: " "enter a value for ICMP code " "in the range (-1: 255)"), widget=forms.TextInput(attrs={ 'data': _('To Port'), 'data-icmp': _('Code') }), validators=[validate_port_range]) source_group = forms.ChoiceField(label=_('Source Group'), required=False, help_text=_("To specify an allowed IP " "range, select CIDR. To " "allow access from all " "members of another security " "group select Source Group.")) cidr = fields.IPField(label=_("CIDR"), required=False, initial="0.0.0.0/0", help_text=_("Classless Inter-Domain Routing " "(e.g. 192.168.0.0/24)"), version=fields.IPv4 | fields.IPv6, mask=True) security_group_id = forms.IntegerField(widget=forms.HiddenInput()) def __init__(self, *args, **kwargs): sg_list = kwargs.pop('sg_list', []) super(AddRule, self).__init__(*args, **kwargs) # Determine if there are security groups available for the # source group option; add the choices and enable the option if so. security_groups_choices = [("", "CIDR")] if sg_list: security_groups_choices.append(('Security Group', sg_list)) self.fields['source_group'].choices = security_groups_choices def clean(self): cleaned_data = super(AddRule, self).clean() from_port = cleaned_data.get("from_port", None) to_port = cleaned_data.get("to_port", None) cidr = cleaned_data.get("cidr", None) ip_proto = cleaned_data.get('ip_protocol', None) source_group = cleaned_data.get("source_group", None) if ip_proto == 'icmp': if from_port is None: msg = _('The ICMP type is invalid.') raise ValidationError(msg) if to_port is None: msg = _('The ICMP code is invalid.') raise ValidationError(msg) if from_port not in xrange(-1, 256): msg = _('The ICMP type not in range (-1, 255)') raise ValidationError(msg) if to_port not in xrange(-1, 256): msg = _('The ICMP code not in range (-1, 255)') raise ValidationError(msg) else: if from_port is None: msg = _('The "from" port number is invalid.') raise ValidationError(msg) if to_port is None: msg = _('The "to" port number is invalid.') raise ValidationError(msg) if to_port < from_port: msg = _('The "to" port number must be greater than ' 'or equal to the "from" port number.') raise ValidationError(msg) if source_group and cidr != self.fields['cidr'].initial: # Specifying a source group *and* a custom CIDR is invalid. msg = _('Either CIDR or Source Group may be specified, ' 'but not both.') raise ValidationError(msg) elif source_group: # If a source group is specified, clear the CIDR from its default cleaned_data['cidr'] = None else: # If only cidr is specified, clear the source_group entirely cleaned_data['source_group'] = None return cleaned_data def handle(self, request, data): try: rule = api.security_group_rule_create( request, data['security_group_id'], data['ip_protocol'], data['from_port'], data['to_port'], data['cidr'], data['source_group']) messages.success(request, _('Successfully added rule: %s') % unicode(rule)) return rule except: redirect = reverse("horizon:project:access_and_security:index") exceptions.handle(request, _('Unable to add rule to security group.'), redirect=redirect)
def _build_parameter_fields(self, template_validate): self.help_text = template_validate['Description'] params = template_validate.get('Parameters', {}) if template_validate.get('ParameterGroups'): params_in_order = [] for group in template_validate['ParameterGroups']: for param in group.get('parameters', []): if param in params: params_in_order.append((param, params[param])) else: # no parameter groups, simply sorted to make the order fixed params_in_order = sorted(params.items()) for param_key, param in params_in_order: field = None field_key = self.param_prefix + param_key initial = param.get( 'Value', param.get('DefaultValue', param.get('Default'))) field_args = { 'initial': initial, 'label': param.get('Label', param_key), 'help_text': html.escape(param.get('Description', '')), 'required': initial is None, } param_type = param.get('Type', None) hidden = strutils.bool_from_string(param.get('NoEcho', 'false')) if 'CustomConstraint' in param: choices = self._populate_custom_choices( param['CustomConstraint']) field_args['choices'] = choices field = forms.ChoiceField(**field_args) elif 'AllowedValues' in param: choices = map(lambda x: (x, x), param['AllowedValues']) field_args['choices'] = choices field = forms.ChoiceField(**field_args) elif param_type == 'Json' and 'Default' in param: field_args['initial'] = json.dumps(param['Default']) field = forms.CharField(**field_args) elif param_type in ('CommaDelimitedList', 'String', 'Json'): if 'MinLength' in param: field_args['min_length'] = int(param['MinLength']) field_args['required'] = field_args['min_length'] > 0 if 'MaxLength' in param: field_args['max_length'] = int(param['MaxLength']) if hidden: field_args['widget'] = forms.PasswordInput( render_value=True) field = forms.CharField(**field_args) elif param_type == 'Number': if 'MinValue' in param: field_args['min_value'] = int(param['MinValue']) if 'MaxValue' in param: field_args['max_value'] = int(param['MaxValue']) field = forms.IntegerField(**field_args) elif param_type == 'Boolean': field_args['required'] = False field = forms.BooleanField(**field_args) if field: self.fields[field_key] = field
class CreateImageForm(forms.SelfHandlingForm): name = forms.CharField(max_length="255", label=_("Name"), required=True) description = forms.CharField(widget=forms.widgets.Textarea(), label=_("Description"), required=False) source_type = forms.ChoiceField( label=_('Image Source'), choices=[('url', _('Image Location')), ('file', _('Image File'))], widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'source' })) copy_from = forms.CharField(max_length="255", label=_("Image Location"), help_text=_("An external (HTTP) URL to load " "the image from."), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'source', 'data-source-url': _('Image Location') }), required=False) image_file = forms.FileField(label=_("Image File"), help_text=_("A local image to upload."), widget=forms.FileInput( attrs={ 'class': 'switched', 'data-switch-on': 'source', 'data-source-file': _('Image File') }), required=False) disk_format = forms.ChoiceField( label=_('Format'), required=True, choices=[('', ''), ('aki', _('AKI - Amazon Kernel ' 'Image')), ('ami', _('AMI - Amazon Machine ' 'Image')), ('ari', _('ARI - Amazon Ramdisk ' 'Image')), ('iso', _('ISO - Optical Disk Image')), ('qcow2', _('QCOW2 - QEMU Emulator')), ('raw', 'Raw'), ('vdi', 'VDI'), ('vhd', 'VHD'), ('vmdk', 'VMDK')], widget=forms.Select(attrs={'class': 'switchable'})) minimum_disk = forms.IntegerField(label=_("Minimum Disk (GB)"), help_text=_( 'The minimum disk size' ' required to boot the' ' image. If unspecified, this' ' value defaults to 0' ' (no minimum).'), required=False) minimum_ram = forms.IntegerField(label=_("Minimum Ram (MB)"), help_text=_('The minimum disk size' ' required to boot the' ' image. If unspecified, this' ' value defaults to 0 (no' ' minimum).'), required=False) is_public = forms.BooleanField(label=_("Public"), required=False) protected = forms.BooleanField(label=_("Protected"), required=False) def __init__(self, *args, **kwargs): super(CreateImageForm, self).__init__(*args, **kwargs) if not settings.HORIZON_IMAGES_ALLOW_UPLOAD: self.fields['image_file'].widget = HiddenInput() def clean(self): data = super(CreateImageForm, self).clean() if not data['copy_from'] and not data['image_file']: raise ValidationError( _("A image or external image location must be specified.")) elif data['copy_from'] and data['image_file']: raise ValidationError( _("Can not specify both image and external image location.")) else: return data def handle(self, request, data): # Glance does not really do anything with container_format at the # moment. It requires it is set to the same disk_format for the three # Amazon image types, otherwise it just treats them as 'bare.' As such # we will just set that to be that here instead of bothering the user # with asking them for information we can already determine. if data['disk_format'] in ( 'ami', 'aki', 'ari', ): container_format = data['disk_format'] else: container_format = 'bare' meta = { 'is_public': data['is_public'], 'protected': data['protected'], 'disk_format': data['disk_format'], 'container_format': container_format, 'min_disk': (data['minimum_disk'] or 0), 'min_ram': (data['minimum_ram'] or 0), 'name': data['name'], 'properties': {} } if data['description']: meta['properties']['description'] = data['description'] if settings.HORIZON_IMAGES_ALLOW_UPLOAD and data['image_file']: meta['data'] = self.files['image_file'] else: meta['copy_from'] = data['copy_from'] try: image = api.glance.image_create(request, **meta) messages.success( request, _('Your image %s has been queued for creation.') % data['name']) return image except Exception: exceptions.handle(request, _('Unable to create new image.'))
class ClusterAddInstanceForm(forms.SelfHandlingForm): cluster_id = forms.CharField(required=False, widget=forms.HiddenInput()) flavor = forms.ChoiceField(label=_("Flavor"), help_text=_("Size of image to launch.")) volume = forms.IntegerField(label=_("Volume Size"), min_value=0, initial=1, help_text=_("Size of the volume in GB.")) name = forms.CharField(label=_("Name"), required=False, help_text=_("Optional name of the instance.")) type = forms.CharField( label=_("Instance Type"), required=False, help_text=_("Optional datastore specific type of the instance.")) related_to = forms.CharField( label=_("Related To"), required=False, help_text=_("Optional datastore specific value that defines the " "relationship from one instance in the cluster to " "another.")) network = forms.ChoiceField(label=_("Network"), help_text=_("Network attached to instance."), required=False) def __init__(self, request, *args, **kwargs): super(ClusterAddInstanceForm, self).__init__(request, *args, **kwargs) self.fields['cluster_id'].initial = kwargs['initial']['cluster_id'] self.fields['flavor'].choices = self.populate_flavor_choices(request) self.fields['network'].choices = self.populate_network_choices(request) @memoized.memoized_method def flavors(self, request): try: datastore = None datastore_version = None datastore_dict = self.initial.get('datastore', None) if datastore_dict: datastore = datastore_dict.get('type', None) datastore_version = datastore_dict.get('version', None) return trove_api.trove.datastore_flavors( request, datastore_name=datastore, datastore_version=datastore_version) except Exception: LOG.exception("Exception while obtaining flavors list") self._flavors = [] redirect = reverse('horizon:project:database_clusters:index') exceptions.handle(request, _('Unable to obtain flavors.'), redirect=redirect) def populate_flavor_choices(self, request): flavor_list = [(f.id, "%s" % f.name) for f in self.flavors(request)] return sorted(flavor_list) @memoized.memoized_method def populate_network_choices(self, request): network_list = [] try: if api.base.is_service_enabled(request, 'network'): tenant_id = self.request.user.tenant_id networks = api.neutron.network_list_for_tenant( request, tenant_id) network_list = [(network.id, network.name_or_id) for network in networks] else: self.fields['network'].widget = forms.HiddenInput() except exceptions.ServiceCatalogException: network_list = [] redirect = reverse('horizon:project:database_clusters:index') exceptions.handle(request, _('Unable to retrieve networks.'), redirect=redirect) return network_list def handle(self, request, data): try: flavor = trove_api.trove.flavor_get(request, data['flavor']) manager = cluster_manager.get(data['cluster_id']) manager.add_instance(str(uuid.uuid4()), data.get('name', None), data['flavor'], flavor.name, data['volume'], data.get('type', None), data.get('related_to', None), data.get('network', None)) except Exception: redirect = reverse("horizon:project:database_clusters:index") exceptions.handle(request, _('Unable to grow cluster.'), redirect=redirect) return True
class AddVipAction(workflows.Action): name = forms.CharField(max_length=80, label=_("Name")) description = forms.CharField(initial="", required=False, max_length=80, label=_("Description")) subnet_id = forms.ChoiceField(label=_("VIP Subnet"), initial="", required=False) address = forms.IPField(label=_("Specify a free IP address " "from the selected subnet"), version=forms.IPv4, mask=False, required=False) protocol_port = forms.IntegerField( label=_("Protocol Port"), min_value=1, help_text=_("Enter an integer value " "between 1 and 65535."), validators=[validators.validate_port_range]) protocol = forms.ChoiceField(label=_("Protocol")) session_persistence = forms.ChoiceField( required=False, initial={}, label=_("Session Persistence"), widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'persistence' })) cookie_name = forms.CharField( initial="", required=False, max_length=80, label=_("Cookie Name"), help_text=_("Required for APP_COOKIE persistence;" " Ignored otherwise."), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'persistence', 'data-persistence-app_cookie': 'APP_COOKIE', })) connection_limit = forms.IntegerField( required=False, min_value=-1, label=_("Connection Limit"), help_text=_("Maximum number of connections allowed " "for the VIP or '-1' if the limit is not set")) admin_state_up = forms.ChoiceField(choices=[(True, _('UP')), (False, _('DOWN'))], label=_("Admin State")) def __init__(self, request, *args, **kwargs): super(AddVipAction, self).__init__(request, *args, **kwargs) tenant_id = request.user.tenant_id subnet_id_choices = [('', _("Select a Subnet"))] try: networks = api.neutron.network_list_for_tenant(request, tenant_id) except Exception: exceptions.handle(request, _('Unable to retrieve networks list.')) networks = [] for n in networks: for s in n['subnets']: subnet_id_choices.append((s.id, s.cidr)) self.fields['subnet_id'].choices = subnet_id_choices protocol_choices = [('', _("Select a Protocol"))] [protocol_choices.append((p, p)) for p in AVAILABLE_PROTOCOLS] self.fields['protocol'].choices = protocol_choices session_persistence_choices = [('', _("No Session Persistence"))] for mode in ('SOURCE_IP', 'HTTP_COOKIE', 'APP_COOKIE'): session_persistence_choices.append((mode.lower(), mode)) self.fields[ 'session_persistence'].choices = session_persistence_choices def clean(self): cleaned_data = super(AddVipAction, self).clean() persistence = cleaned_data.get('session_persistence') if persistence: cleaned_data['session_persistence'] = persistence.upper() if (cleaned_data.get('session_persistence') == 'APP_COOKIE' and not cleaned_data.get('cookie_name')): msg = _('Cookie name is required for APP_COOKIE persistence.') self._errors['cookie_name'] = self.error_class([msg]) return cleaned_data class Meta: name = _("Specify VIP") permissions = ('openstack.services.network', ) help_text = _("Create a VIP for this pool. " "Assign a name, description, IP address, port, " "and maximum connections allowed for the VIP. " "Choose the protocol and session persistence " "method for the VIP. " "Admin State is UP (checked) by default.")
class AddTaskDetailsAction(workflows.Action): scheduleId = forms.IntegerField(label=_("ScheduleId)"), required=True, min_value=1, max_value=9999999, help_text=_("ScheduleId")) name = forms.CharField(label=_("Name"), required=True, max_length=80, help_text=_("Name")) description = forms.CharField(label=_("Description"), required=True, max_length=120, help_text=_("Description")) hour = forms.IntegerField(label=_("Hour)"), required=True, min_value=1, max_value=12, help_text=_("1-12")) min = forms.IntegerField(label=_("Minute"), required=True, min_value=0, max_value=59, help_text=_("0 - 59")) period = forms.ChoiceField(label=_("Period"), choices=[('AM', 'AM'), ('PM', 'PM')], required=True, help_text=_("AM or PM")) start = forms.DateField( label=_("Start date"), required=False, input_formats=("%Y-%m-%d", ), help_text=_("YYYY-MM-DD"), widget=forms.DateInput(attrs={'data-date-format': 'yyyy-mm-dd'})) end = forms.DateField( label=_("End date"), required=True, input_formats=("%Y-%m-%d", ), help_text=_("YYYY-MM-DD"), widget=forms.DateInput(attrs={'data-date-format': 'yyyy-mm-dd'})) enabled = forms.BooleanField(label=_("Enabled"), required=False, help_text=_("Enabled")) class Meta: name = _("Details") def __init__(self, request, context, *args, **kwargs): self.request = request self.context = context super(AddTaskDetailsAction, self).__init__(request, context, *args, **kwargs)