class ImportKeypair(forms.SelfHandlingForm): name = forms.RegexField(max_length=255, label=_("Key Pair Name"), regex=KEYPAIR_NAME_REGEX, error_messages=KEYPAIR_ERROR_MESSAGES) key_type = forms.ChoiceField(label=_("Key Type"), widget=forms.SelectWidget(), choices=[('ssh', _("SSH Key")), ('x509', _("X509 Certificate"))], initial='ssh') public_key = forms.CharField(label=_("Public Key"), widget=forms.Textarea()) def handle(self, request, data): try: # Remove any new lines in the ssh public key if data['key_type'] == 'ssh': data['public_key'] = NEW_LINES.sub("", data['public_key']) keypair = api.nova.keypair_import(request, data['name'], data['public_key'], data['key_type']) messages.success(request, _('Successfully imported public key: %s') % data['name']) return keypair except Exception: exceptions.handle(request, ignore=True) self.api_error(_('Unable to import key pair.')) return False
class EnableForm(forms.SelfHandlingForm): volume_id = forms.ChoiceField( label=_("Select a volume to enable"), widget=forms.SelectWidget(attrs={'class': 'image-selector'}, data_attrs=('size', 'name'), transform=lambda x: "%s (%s)" % (x.name, x.id)), required=True) name = forms.CharField(max_length=255, label=_("Name"), required=False) description = forms.CharField(max_length=255, widget=forms.Textarea(attrs={'rows': 4}), label=_("Description"), required=False) def __init__(self, request, *args, **kwargs): super(EnableForm, self).__init__(request, *args, **kwargs) cinder_volumes = self.get_cinder_volumes(request) sg_volumes = [] choices = [('', _("Choose a volume"))] for vol in sg_api.volume_list(request): sg_volumes.append(vol.id) if cinder_volumes: choices = [('', _("Choose a volume"))] for volume in cinder_volumes: if volume.status == "available": choices.append((volume.id, volume)) self.fields['volume_id'].choices = choices def handle(self, request, data): try: result = None volume = cinder.volume_get(request, data['volume_id']) if not volume: message = _('Volume not exist,id:"%s".') % data['volume_id'] else: message = _('Enabling volume "%s".') % data['name'] result = sg_api.volume_enable(request, data['volume_id'], data['name'], data['description']) messages.info(request, message) return result except Exception: redirect = reverse("horizon:storage-gateway:volumes:index") msg = _('Unable to enable volume:%s.') % data['volume_id'] exceptions.handle(request, msg, redirect=redirect) def get_cinder_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
class CloneCGroupForm(forms.SelfHandlingForm): name = forms.CharField(max_length=255, label=_("Consistency Group Name")) description = forms.CharField(max_length=255, widget=forms.Textarea(attrs={'rows': 4}), label=_("Description"), required=False) cgroup_source = forms.ChoiceField( label=_("Use a consistency group as source"), widget=forms.SelectWidget(attrs={'class': 'image-selector'}, data_attrs=('name'), transform=lambda x: "%s" % (x.name)), required=False) def prepare_cgroup_source_field(self, request, cgroup_id): try: cgroup = cinder.volume_cgroup_get(request, cgroup_id) self.fields['cgroup_source'].choices = ((cgroup_id, cgroup), ) except Exception: exceptions.handle( request, _('Unable to load the specified ' 'consistency group.')) def __init__(self, request, *args, **kwargs): super(CloneCGroupForm, self).__init__(request, *args, **kwargs) # populate cgroup_id cgroup_id = kwargs.get('initial', {}).get('cgroup_id', []) self.fields['cgroup_id'] = forms.CharField(widget=forms.HiddenInput(), initial=cgroup_id) self.prepare_cgroup_source_field(request, cgroup_id) def handle(self, request, data): try: message = _('Creating consistency group "%s".') % data['name'] cgroup = cinder.volume_cgroup_create_from_source( request, data['name'], source_cgroup_id=data['cgroup_id'], description=data['description']) messages.info(request, message) return cgroup except Exception: redirect = reverse("horizon:project:volumes:index") msg = _('Unable to clone consistency group.') search_opts = {'consistentcygroup_id': data['cgroup_id']} volumes = cinder.volume_list(request, search_opts=search_opts) if len(volumes) == 0: msg = _('Unable to clone empty consistency group.') exceptions.handle(request, msg, redirect=redirect)
class ImageChoiceField(ChoiceField): widget = hz_forms.SelectWidget(transform=_get_title, transform_html_attrs=_disable_non_ready) def __init__(self, *args, **kwargs): self.image_type = kwargs.pop('image_type', None) super(ImageChoiceField, self).__init__(*args, **kwargs) @with_request def update(self, request, form=None, **kwargs): image_map, image_choices = {}, [] murano_images = get_murano_images(request, getattr(form, 'region', None)) for image in murano_images: murano_data = image.murano_property title = murano_data.get('title', image.name) if image.status == 'active': title = Choice(title, enabled=True) else: title = Choice("{} ({})".format(title, image.status), enabled=False) if self.image_type is not None: itype = murano_data.get('type') if not self.image_type and itype is None: continue prefix = '{type}.'.format(type=self.image_type) if (not itype.startswith(prefix) and not self.image_type == itype): continue image_map[image.id] = title for id_, title in sorted(six.iteritems(image_map), key=lambda e: e[1].title): image_choices.append((id_, title)) if image_choices: image_choices.insert(0, ("", _("Select Image"))) else: image_choices.insert(0, ("", _("No images available"))) self.choices = image_choices
class SelectPortAction(workflows.Action): port = forms.ChoiceField( label=_("Port"), help_text=_("Create tap flow with" " this port"), widget=forms.SelectWidget(transform=lambda x: ("%s (%s) %s" % ( x.name, x.id, x["fixed_ips"][0]["ip_address"])))) def __init__(self, request, *args, **kwargs): super(SelectPortAction, self).__init__(request, *args, **kwargs) port_list = self.fields["port"].choices if len(port_list) == 1: self.fields['port'].initial = [port_list[0][0]] class Meta(object): name = _("Port") permissions = ('openstack.services.network', ) help_text = _("Select port for your tap flow.") def populate_port_choices(self, request, context): return ts_utils.port_field_data(request, )
class RebuildForm(forms.SelfHandlingForm): router_id = forms.CharField(label=_("ID"), widget=forms.HiddenInput(), required=True) router_name = forms.CharField(label=_("Router Name"), widget=forms.HiddenInput(), required=False) attrs = {'class': 'image-selector'} image = forms.ChoiceField(label=_("Select Image"), widget=forms.SelectWidget( attrs=attrs, data_attrs=('size', 'display-name'), transform=_image_choice_title), required=False) def __init__(self, request, *args, **kwargs): super(RebuildForm, self).__init__(request, *args, **kwargs) images = utils.get_available_images(request, request.user.tenant_id) choices = [(image.id, image) for image in images] if choices: choices.insert(0, ("", _("Select Image"))) else: choices.insert(0, ("", _("No images available"))) self.fields['image'].choices = choices def handle(self, request, data): try: if data['image']: rc.router_rebuild(request, data['router_id'], data['image']) else: rc.router_rebuild(request, data['router_id']) messages.success(request, _('Rebuilt Router: %s.') % data['router_name']) except Exception: exceptions.handle( request, _('Unable to rebuild router %s.' % data['router_name'])) return True
class CreateSubnetInfoAction(workflows.Action): subnet_name = forms.CharField(max_length=255, widget=forms.TextInput(attrs={}), label=_("Subnet Name"), required=False) address_source = forms.ChoiceField( required=False, label=_('Network Address Source'), choices=[('manual', _('Enter Network Address manually')), ('subnetpool', _('Allocate Network Address from a pool'))], widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'source', })) subnetpool = forms.ChoiceField( label=_("Address pool"), widget=forms.SelectWidget(attrs={ 'class': 'switched switchable', 'data-slug': 'subnetpool', 'data-switch-on': 'source', 'data-source-subnetpool': _('Address pool') }, data_attrs=('name', 'prefixes', 'ip_version', 'min_prefixlen', 'max_prefixlen', 'default_prefixlen'), transform=lambda x: "%s (%s)" % (x.name, ", ".join(x.prefixes)) if 'prefixes' in x else "%s" % (x.name)), required=False) prefixlen = forms.ChoiceField( widget=forms.Select(attrs={ 'class': 'switched', 'data-switch-on': 'subnetpool', }), label=_('Network Mask'), required=False) cidr = forms.IPField(label=_("Network Address"), required=False, initial="", widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'source', 'data-source-manual': _("Network Address"), }), help_text=_("Network address in CIDR format " "(e.g. 192.168.0.0/24, 2001:DB8::/48)"), version=forms.IPv4 | forms.IPv6, mask=True) ip_version = forms.ChoiceField( choices=[(4, 'IPv4'), (6, 'IPv6')], widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'ipversion', }), label=_("IP Version"), required=False) gateway_ip = forms.IPField( label=_("Gateway IP"), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'gateway_ip', 'data-source-manual': _("Gateway IP") }), required=False, initial="", help_text=_("IP address of Gateway (e.g. 192.168.0.254) " "The default value is the first IP of the " "network address " "(e.g. 192.168.0.1 for 192.168.0.0/24, " "2001:DB8::1 for 2001:DB8::/48). " "If you use the default, leave blank. " "If you do not want to use a gateway, " "check 'Disable Gateway' below."), version=forms.IPv4 | forms.IPv6, mask=False) no_gateway = forms.BooleanField(label=_("Disable Gateway"), widget=forms.CheckboxInput( attrs={ 'class': 'switchable', 'data-slug': 'gateway_ip', 'data-hide-on-checked': 'true' }), initial=False, required=False) check_subnet_range = True class Meta(object): name = _("Subnet") help_text = _('Creates a subnet associated with the network.' ' You need to enter a valid "Network Address"' ' and "Gateway IP". If you did not enter the' ' "Gateway IP", the first value of a network' ' will be assigned by default. If you do not want' ' gateway please check the "Disable Gateway" checkbox.' ' Advanced configuration is available by clicking on' ' the "Subnet Details" tab.') def __init__(self, request, context, *args, **kwargs): super(CreateSubnetInfoAction, self).__init__(request, context, *args, **kwargs) if 'with_subnet' in context: self.fields['with_subnet'] = forms.BooleanField( initial=context['with_subnet'], required=False, widget=forms.HiddenInput()) if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}).get( 'enable_ipv6', True): self.fields['ip_version'].widget = forms.HiddenInput() self.fields['ip_version'].initial = 4 try: if api.neutron.is_extension_supported(request, 'subnet_allocation'): self.fields['subnetpool'].choices = \ self.get_subnetpool_choices(request) else: self.hide_subnetpool_choices() except Exception: self.hide_subnetpool_choices() msg = _('Unable to initialize subnetpools') exceptions.handle(request, msg) if len(self.fields['subnetpool'].choices) > 1: # Pre-populate prefixlen choices to satisfy Django # ChoiceField Validation. This is overridden w/data from # subnetpool on select. self.fields['prefixlen'].choices = \ zip(list(range(0, 128 + 1)), list(range(0, 128 + 1))) # Populate data-fields for switching the prefixlen field # when user selects a subnetpool other than # "Provider default pool" for (id, name) in self.fields['subnetpool'].choices: if not len(id): continue key = 'data-subnetpool-' + id self.fields['prefixlen'].widget.attrs[key] = \ _('Network Mask') else: self.hide_subnetpool_choices() def get_subnetpool_choices(self, request): subnetpool_choices = [('', _('Select a pool'))] for subnetpool in api.neutron.subnetpool_list(request): subnetpool_choices.append((subnetpool.id, subnetpool)) return subnetpool_choices def hide_subnetpool_choices(self): self.fields['address_source'].widget = forms.HiddenInput() self.fields['subnetpool'].choices = [] self.fields['subnetpool'].widget = forms.HiddenInput() self.fields['prefixlen'].widget = forms.HiddenInput() def _check_subnet_range(self, subnet, allow_cidr): allowed_net = netaddr.IPNetwork(allow_cidr) return subnet in allowed_net def _check_cidr_allowed(self, ip_version, subnet): if not self.check_subnet_range: return allowed_cidr = getattr(settings, "ALLOWED_PRIVATE_SUBNET_CIDR", {}) version_str = 'ipv%s' % ip_version allowed_ranges = allowed_cidr.get(version_str, []) if allowed_ranges: under_range = any( self._check_subnet_range(subnet, allowed_range) for allowed_range in allowed_ranges) if not under_range: range_str = ', '.join(allowed_ranges) msg = (_("CIDRs allowed for user private %(ip_ver)s " "networks are %(allowed)s.") % { 'ip_ver': '%s' % version_str, 'allowed': range_str }) raise forms.ValidationError(msg) def _check_subnet_data(self, cleaned_data, is_create=True): cidr = cleaned_data.get('cidr') ip_version = int(cleaned_data.get('ip_version')) gateway_ip = cleaned_data.get('gateway_ip') no_gateway = cleaned_data.get('no_gateway') address_source = cleaned_data.get('address_source') subnetpool = cleaned_data.get('subnetpool') if not subnetpool and address_source == 'subnetpool': msg = _('Specify "Address pool" or select ' '"Enter Network Address manually" and specify ' '"Network Address".') raise forms.ValidationError(msg) if not cidr and address_source != 'subnetpool': msg = _('Specify "Network Address" or ' 'clear "Create Subnet" checkbox in previous step.') raise forms.ValidationError(msg) if cidr: subnet = netaddr.IPNetwork(cidr) if subnet.version != ip_version: msg = _('Network Address and IP version are inconsistent.') raise forms.ValidationError(msg) if (ip_version == 4 and subnet.prefixlen == 32) or \ (ip_version == 6 and subnet.prefixlen == 128): msg = _("The subnet in the Network Address is " "too small (/%s).") % subnet.prefixlen self._errors['cidr'] = self.error_class([msg]) self._check_cidr_allowed(ip_version, subnet) if not no_gateway and gateway_ip: if netaddr.IPAddress(gateway_ip).version is not ip_version: msg = _('Gateway IP and IP version are inconsistent.') raise forms.ValidationError(msg) if not is_create and not no_gateway and not gateway_ip: msg = _('Specify IP address of gateway or ' 'check "Disable Gateway" checkbox.') raise forms.ValidationError(msg) def clean(self): cleaned_data = super(CreateSubnetInfoAction, self).clean() with_subnet = cleaned_data.get('with_subnet') if not with_subnet: return cleaned_data self._check_subnet_data(cleaned_data) return cleaned_data
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.Select(attrs={ 'class': 'switchable', 'data-slug': 'source' })) snapshot_source = forms.ChoiceField( label=_("Use snapshot as a source"), widget=forms.SelectWidget(attrs={'class': 'snapshot-selector'}, data_attrs=('size', 'name'), transform=lambda x: "%s (%sGB)" % (x.name, x.size)), required=False) image_source = forms.ChoiceField( label=_("Use image as a source"), widget=forms.SelectWidget(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.SelectWidget(attrs={'class': 'image-selector'}, data_attrs=('size', 'name'), transform=lambda x: "%s (%s)" % (x.name, filesizeformat(x.size * units.Gi))), required=False) type = forms.ChoiceField(label=_("Type"), required=False, widget=forms.Select( 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 (GB)")) availability_zone = forms.ChoiceField( label=_("Availability Zone"), required=False, widget=forms.Select( 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 (%sGB)') % 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 (%sGB)') % 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 (%s)') % filesizeformat(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 "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 (%sGB)') % 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 (%sGB)') % 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 (%sGB)') % 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)iGB cannot be created as ' 'you only have %(avail)iGB 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 UpdateQuotaForm(forms.SelfHandlingForm): project_id = forms.CharField(label=_("Project ID"), widget=forms.HiddenInput, required=False) user_id = forms.CharField(label=_("User ID"), widget=forms.HiddenInput, required=False) # user_type = forms.ChoiceField( # label=_("User Type"), # required=True, # choices=[('normal', _("Normal User")), ('credit', _("Credit User"))], # widget=forms.Select( # attrs={ # 'class': 'switchable', # 'data-slug': 'user_type' # } # ) # ) # # credit_line = forms.FloatField( # label=_("Credit Line"), # required=False, # min_value=0.0, # widget=forms.TextInput( # attrs={ # 'class': 'switched', # 'data-switch-on': 'user_type', # 'data-user_type-credit': _('Credit'), # })) # adjust_quota = forms.BooleanField( # label=_("Adjust Quota"), # required=False, # widget=forms.CheckboxInput( # attrs={ # 'class': 'switchable', # 'data-slug': 'adjust_quota', # 'data-hide-on-checked': 'false' # } # ) # ) region_id = forms.ChoiceField(label=_("Regions"), required=True, widget=forms.SelectWidget(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true', 'style': 'width 10%' })) instances = forms.IntegerField(min_value=-1, label=_("Instances"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true', 'style': 'width 10%' })) cores = forms.IntegerField(min_value=2, label=_("VCPUs"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) ram = forms.IntegerField(min_value=-1, label=_("RAM (MB)"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) volumes = forms.IntegerField(min_value=-1, label=_("Volumes"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) snapshots = forms.IntegerField(min_value=-1, label=_("Volume Snapshots"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) volume_gigabytes = forms.IntegerField( min_value=-1, label=_("Size of Volumes(GB)"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) snapshot_gigabytes = forms.IntegerField( min_value=-1, label=_("Size of Snapshots (GB)"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) floatingip = forms.IntegerField(min_value=-1, label=_("Floating IPs"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) network = forms.IntegerField(min_value=-1, label=_("Networks"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) router = forms.IntegerField(min_value=-1, label=_("Routers"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) subnet = forms.IntegerField(min_value=-1, label=_("Subnets"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) pool = forms.IntegerField(min_value=-1, label=_("Loadbalancers"), required=True, widget=forms.TextInput(attrs={ # 'class': 'switched', # 'data-switch-on': 'adjust_quota', # 'data-is-required': 'true' })) bandwidth = forms.IntegerField(required=False, widget=forms.HiddenInput) def __init__(self, request, *args, **kwargs): super(UpdateQuotaForm, self).__init__(request, *args, **kwargs) region_choices = [] user_id = kwargs['initial'].get('user_id', None) regions = api.keystone.list_regions_for_user(self.request, user_id) for region in regions: region_choices.append((region['id'], region['description'])) self.fields['region_id'].choices = region_choices # if kwargs['initial']['user_type'] == 'credit': # self.fields['user_type'].choices = [('credit', _("Credit User"))] # # if policy.check((("identity", "project_admin_required"),), self.request): # self.fields['credit_line'].validators.append(MaxValueValidator(settings.UPPER_CREDIT_LINE_FOR_PROJECT_ADMIN)) # self.fields['credit_line'].help_text = _('credit line is between 0~%s') % settings.UPPER_CREDIT_LINE_FOR_PROJECT_ADMIN def clean(self): cleaned_data = super(UpdateQuotaForm, self).clean() usages = quotas.tenant_quota_usages( self.request, tenant_id=self.initial['project_id'], region=cleaned_data['region_id']) # Validate the quota values before updating quotas. # add key based on cleaned data, do not update tenant_quota_usages because # do not know if hava affect on others need_trans = {'network': 'networks', 'subnet': 'subnets', 'router': 'routers', 'floatingip': 'floating_ips'} bad_values = [] for key, value in cleaned_data.items(): if key in need_trans: key = need_trans[key] used = usages[key].get('used', 0) if value is not None and value >= 0 and used > value: bad_values.append(_('%(used)s %(key)s used') % {'used': used, 'key': quotas.QUOTA_NAMES.get(key, key)}) if bad_values: value_str = ", ".join(bad_values) msg = (_('Quota value(s) cannot be less than the current usage ' 'value(s): %s.') % value_str) raise forms.ValidationError(msg) return cleaned_data # def _update_billing_accout(self, request, project_id, data): # client = api.billing.RequestClient(request) # account = client.get_account(project_id) # if data['user_type'] == 'credit' and account['type'] != 'credit': # ret = client.api_request('/account/change2credit/' + account['account_id'] + '?credit_line=' + str(data['credit_line']), method='GET') # user = json.loads(ret.read()) # if user['success'] != 'success': # raise # return True # else: # # change credit line # if data['user_type'] == 'credit': # ret = client.api_request( # '/account/changecreditline/' + account['account_id'] + '?credit_line=' + str(data['credit_line']), # method='GET') # user = json.loads(ret.read()) # if user['success'] != 'success': # raise # return True # def _update_user(self, request, data): # try: # response = self._update_billing_accout(request, data['project_id'], data) # except Exception: # response = exceptions.handle(request, ignore=True) # messages.error(request, _('Unable to update user account.')) # # if isinstance(response, http.HttpResponse): # return response # else: # return response def _update_project_quota(self, request, data, project_id): # Update the project quota. nova_data = dict( [(key, data[key]) for key in NOVA_QUOTA_FIELDS]) try: api.nova.tenant_quota_update(request, project_id, region=data['region_id'], **nova_data) if api.base.is_service_enabled(request, 'volume'): cinder_data = dict([(key, data[key]) for key in quotas.CINDER_QUOTA_FIELDS]) api.cinder.tenant_quota_update(request, project_id, region=data['region_id'], **cinder_data) if api.base.is_service_enabled(request, 'network') and \ api.neutron.is_quotas_extension_supported(request): neutron_data = {} for key in NEUTRON_QUOTA_FIELDS: neutron_data[key] = data[key] api.neutron.tenant_quota_update(request, project_id, region=data['region_id'], **neutron_data) except Exception: exceptions.handle(request, _('Unable to set project quotas.')) raise def handle(self, request, data): # set primary project for user try: # user = self._update_user(request, data) # if not user: # raise project_id = data['project_id'] self._update_project_quota(request, data, project_id) messages.success(request, _('User has been updated successfully.')) except Exception: # operation log config = _('Project ID: %s') % data['project_id'] api.logger.Logger(request).create(resource_type='account', action_name='Modify Quotas', resource_name='Account', config=config, status='Error') return False # operation log config = _('Project ID: %s') % data['project_id'] api.logger.Logger(request).create(resource_type='account', action_name='Modify Quotas', resource_name='Account', config=config, status='Success') return True
class SetInstanceDetailsAction(workflows.Action): availability_zone = forms.ChoiceField(label=_("Availability Zone"), required=False) name = forms.CharField(label=_("Instance Name"), max_length=255) 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.")) source_type = forms.ChoiceField(label=_("Instance Boot Source"), required=True, help_text=_("Choose Your Boot Source " "Type.")) instance_snapshot_id = forms.ChoiceField(label=_("Instance Snapshot"), required=False) volume_id = forms.ChoiceField(label=_("Volume"), required=False) volume_snapshot_id = forms.ChoiceField(label=_("Volume Snapshot"), required=False) image_id = forms.ChoiceField( label=_("Image Name"), required=False, widget=forms.SelectWidget(data_attrs=('volume_size', ), transform=lambda x: ("%s (%s)" % (x.name, filesizeformat(x.bytes))))) volume_size = forms.IntegerField(label=_("Device size (GB)"), 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').")) delete_on_terminate = forms.BooleanField(label=_("Delete on Terminate"), initial=False, required=False, help_text=_("Delete volume on " "instance terminate")) class Meta: 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) source_type_choices = [ ('', _("Select source")), ("image_id", _("Boot from image")), ("instance_snapshot_id", _("Boot from snapshot")), ] if base.is_service_enabled(request, 'volume'): 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 def clean(self): cleaned_data = super(SetInstanceDetailsAction, self).clean() 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) # Validate our instance source. source_type = self.data.get('source_type', None) if source_type in ('image_id', 'volume_image_id'): if source_type == 'volume_image_id': if not self.data.get('volume_size', None): msg = _("You must set volume size") self._errors['volume_size'] = self.error_class([msg]) if not cleaned_data.get('device_name'): msg = _("You must set device name") self._errors['device_name'] = 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]) else: # Prevents trying to launch an image needing more resources. try: image_id = cleaned_data.get('image_id') # 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 try: flavor_id = cleaned_data.get('flavor') # 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 if image and flavor: 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. volume_size = cleaned_data.get('volume_size') if volume_size and source_type == 'volume_image_id': 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]) elif source_type == 'instance_snapshot_id': if not cleaned_data['instance_snapshot_id']: msg = _("You must select a snapshot.") self._errors['instance_snapshot_id'] = self.error_class([msg]) elif source_type == 'volume_id': 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? if count > 1: msg = _('Launching multiple instances is only supported for ' 'images and instance snapshots.') raise forms.ValidationError(msg) elif source_type == 'volume_snapshot_id': if not cleaned_data.get('volume_snapshot_id'): msg = _("You must select a snapshot.") self._errors['volume_snapshot_id'] = self.error_class([msg]) if not cleaned_data.get('device_name'): msg = _("You must set device name") self._errors['device_name'] = self.error_class([msg]) return cleaned_data def populate_flavor_choices(self, request, context): flavors = instance_utils.flavor_list(request) if flavors: return instance_utils.sort_flavor_list(request, flavors) return [] 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 = {} try: extra['usages'] = api.nova.tenant_absolute_limits(self.request) 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) } 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 = 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) 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.insert(0, ("", _("Select Instance Snapshot"))) else: choices.insert(0, ("", _("No snapshots available"))) return choices def populate_volume_id_choices(self, request, context): try: volumes = [ self._get_volume_display_name(v) for v in cinder.volume_list(self.request) if v.status == api.cinder.VOLUME_STATE_AVAILABLE and v.bootable == 'true' ] except Exception: volumes = [] 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): try: snapshots = cinder.volume_snapshot_list(self.request) snapshots = [ self._get_volume_display_name(s) for s in snapshots if s.status == api.cinder.VOLUME_STATE_AVAILABLE ] except Exception: snapshots = [] 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 LaunchInstance(workflows.Action): availability_zone = forms.ChoiceField( label=_("Availability Zone"), required=False) name = forms.CharField(label=_("Instance Name"), max_length=255) 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.")) image = forms.ChoiceField(label=_("Select Image"), widget=forms.SelectWidget( attrs={'class': 'image-selector'}, data_attrs=('size', 'display-name'), transform=_image_choice_title)) def __init__(self, request, *args, **kwargs): super(LaunchInstance, self).__init__(request, *args, **kwargs) images = imageutils.get_available_images( request, request.user.tenant_id) choices = [(image.id, image) for image in images] if choices: choices.insert(0, ("", _("Select Image"))) else: choices.insert(0, ("", _("No images available"))) zones = self._availability_zone_choices(request) self.fields['image'].choices = choices self.fields['availability_zone'].choices = zones self.fields['flavor'].choices = self._flavor_choices(request) def _flavor_choices(self, request): flavors = utils.flavor_list(request) if flavors: return utils.sort_flavor_list(request, flavors) return [] def _availability_zone_choices(self, request): 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 handle(self, request, context): policy_target_id = self.request.path.split("/")[-2] try: msg = _('Member was successfully created.') ep = client.pt_create( request, policy_target_group_id=policy_target_id) api.nova.server_create(request, context['name'], context['image'], context['flavor'], key_name=None, user_data=None, security_groups=None, instance_count=context['count'], nics=[{'port-id': ep.port_id}]) LOG.debug(msg) messages.success(request, msg) except Exception: msg = _('Failed to launch VM') LOG.error(msg) u = "horizon:project:policytargets:policy_targetdetails" redirect = reverse(u, kwargs={'policy_target_id': policy_target_id}) exceptions.handle(request, msg, redirect=redirect)
class CreateImageForm(forms.SelfHandlingForm): name = forms.CharField(max_length=255, label=_("Name")) description = forms.CharField(widget=forms.widgets.Textarea(attrs={ 'class': 'modal-body-fixed-width', 'rows': 4 }), label=_("Description"), required=False) source_type = forms.ChoiceField( label=_('Image Source'), required=False, choices=[('url', _('Image Location')), ('file', _('Image File'))], widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'source' })) image_url_attrs = { 'class': 'switched', 'data-switch-on': 'source', 'data-source-url': _('Image Location'), 'ng-model': 'ctrl.copyFrom', 'ng-change': 'ctrl.selectImageFormat(ctrl.copyFrom)' } image_url = ImageURLField(label=_("Image Location"), help_text=_("An external (HTTP/HTTPS) URL to " "load the image from."), widget=forms.TextInput(attrs=image_url_attrs), required=False) image_attrs = { 'class': 'switched', 'data-switch-on': 'source', 'data-source-file': _('Image File'), 'ng-model': 'ctrl.imageFile', 'ng-change': 'ctrl.selectImageFormat(ctrl.imageFile.name)', 'image-file-on-change': None } image_file = forms.FileField(label=_("Image File"), help_text=_("A local image to upload."), widget=forms.FileInput(attrs=image_attrs), required=False) kernel = forms.ChoiceField( label=_('Kernel'), required=False, widget=forms.SelectWidget(transform=lambda x: "%s (%s)" % ( x.name, defaultfilters.filesizeformat(x.size)))) ramdisk = forms.ChoiceField( label=_('Ramdisk'), required=False, widget=forms.SelectWidget(transform=lambda x: "%s (%s)" % ( x.name, defaultfilters.filesizeformat(x.size)))) disk_format = forms.ChoiceField( label=_('Format'), choices=[], widget=forms.Select(attrs={ 'class': 'switchable', 'ng-model': 'ctrl.diskFormat' })) architecture = forms.CharField(max_length=255, label=_("Architecture"), required=False) minimum_disk = forms.IntegerField( label=_("Minimum Disk (GB)"), min_value=0, 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)"), min_value=0, help_text=_('The minimum memory size required to boot the image. ' 'If unspecified, this value defaults to 0 (no minimum).'), required=False) is_copying = forms.BooleanField( label=_("Copy Data"), initial=True, required=False, help_text=_('Specify this option to copy image data to the image ' 'service. If unspecified, image data will be used in its ' 'current location.'), widget=forms.CheckboxInput( attrs={ 'class': 'switched', 'data-source-url': _('Image Location'), 'data-switch-on': 'source' })) is_public = forms.BooleanField(label=_("Public"), required=False) protected = forms.BooleanField(label=_("Protected"), required=False) def __init__(self, request, *args, **kwargs): super(CreateImageForm, self).__init__(request, *args, **kwargs) if (not settings.HORIZON_IMAGES_ALLOW_UPLOAD or not policy.check( (("image", "upload_image"), ), request)): self._hide_file_source_type() if not policy.check((("image", "set_image_location"), ), request): self._hide_url_source_type() if not policy.check((("image", "publicize_image"), ), request): self._hide_is_public() self.fields['disk_format'].choices = IMAGE_FORMAT_CHOICES try: kernel_images = api.glance.image_list_detailed( request, filters={'disk_format': 'aki'})[0] except Exception: kernel_images = [] msg = _('Unable to retrieve image list.') messages.error(request, msg) if kernel_images: choices = [('', _("Choose an image"))] for image in kernel_images: choices.append((image.id, image)) self.fields['kernel'].choices = choices else: del self.fields['kernel'] try: ramdisk_images = api.glance.image_list_detailed( request, filters={'disk_format': 'ari'})[0] except Exception: ramdisk_images = [] msg = _('Unable to retrieve image list.') messages.error(request, msg) if ramdisk_images: choices = [('', _("Choose an image"))] for image in ramdisk_images: choices.append((image.id, image)) self.fields['ramdisk'].choices = choices else: del self.fields['ramdisk'] def _hide_file_source_type(self): self.fields['image_file'].widget = HiddenInput() source_type = self.fields['source_type'] source_type.choices = [ choice for choice in source_type.choices if choice[0] != 'file' ] if len(source_type.choices) == 1: source_type.widget = HiddenInput() def _hide_url_source_type(self): self.fields['image_url'].widget = HiddenInput() source_type = self.fields['source_type'] source_type.choices = [ choice for choice in source_type.choices if choice[0] != 'url' ] if len(source_type.choices) == 1: source_type.widget = HiddenInput() def _hide_is_public(self): self.fields['is_public'].widget = HiddenInput() self.fields['is_public'].initial = False def clean(self): data = super(CreateImageForm, self).clean() # The image_file key can be missing based on particular upload # conditions. Code defensively for it here... image_file = data.get('image_file', None) image_url = data.get('image_url', None) if not image_url and not image_file: raise ValidationError( _("A image or external image location must be specified.")) elif image_url and image_file: raise ValidationError( _("Can not specify both image and external image location.")) else: return data def handle(self, request, data): meta = create_image_metadata(data, False) # Add image source file or URL to metadata if (settings.HORIZON_IMAGES_ALLOW_UPLOAD and policy.check( (("image", "upload_image"), ), request) and data.get('image_file', None)): meta['data'] = self.files['image_file'] elif data['is_copying']: meta['copy_from'] = data['image_url'] else: meta['location'] = data['image_url'] try: image = api.glance.image_create(request, **meta) messages.success( request, _('Your image %s has been queued for creation.') % meta['name']) return image except Exception as e: msg = _('Unable to create new image') # TODO(nikunj2512): Fix this once it is fixed in glance client if hasattr(e, 'code') and e.code == 400: if "Invalid disk format" in e.details: msg = _('Unable to create new image: Invalid disk format ' '%s for image.') % meta['disk_format'] elif "Image name too long" in e.details: msg = _('Unable to create new image: Image name too long.') elif "not supported" in e.details: msg = _('Unable to create new image: URL scheme not ' 'supported.') exceptions.handle(request, msg) return False
class CreateSiteDetailAction(workflows.Action): with_gw = forms.BooleanField(label=_("Add your Enterprise Branch Gateway"), initial=True, required=False) #LGCHOICES = (('Provider_AZ1_GW1', 'Cloud Provider Avail. Zone 1 - Gateway 1'), ('Provider_AZ2_GW1', 'Cloud Provider Avail. Zone 2 - Gateway 1'), ('Provider_AZ2_GW2', 'Cloud Provider Avail. Zone 2 - Gateway 2'), ('new', 'Add a New Gateway')) #lgwchoice = forms.ChoiceField(required=True, label='Add a Provider Gateway', choices=LGCHOICES) '''RGCHOICES = (('new', 'Add a New Gateway'), ('GW1_Data_Center_Berlin', 'Enterprise Data Center in Berlin - Gateway 1')) rgwchoice = forms.ChoiceField(required=True, label='New Gateway VM or Pre-Configured Enterprise Gateway', choices=RGCHOICES)''' name = forms.CharField(max_length=255, label=_("Name your Enterprise Branch Gateway"), help_text=_( "Enterprise Gateway Name. This field is " "optional."), required=False) '''gw_endpoint = fields.IPField(label=_("Enterprise Gateway Public Management IP address"), required=False, initial="", help_text=_("Enterprise Gateway Network address in IPv4 or IPv6 address format " "(e.g., 50.59.22.182)"), version=fields.IPv4 | fields.IPv6, mask=False) ''' tenant_name = forms.CharField( max_length=255, required=True, label='Tenant Name', help_text=_("Enter a Tenant Name - it takes a string")) branch_name = forms.CharField( max_length=255, required=True, label='Branch Name', help_text=_("Enter a Branch Name - it takes a string")) KEYPAIR_IMPORT_URL = "horizon:project:access_and_security:keypairs:import" keypair = forms.DynamicChoiceField( label=_("Key Pair"), required=False, help_text=_("Key pair to use for authentication."), add_item_link=KEYPAIR_IMPORT_URL) image_id = forms.ChoiceField( label=_("Image Name"), required=False, widget=forms.SelectWidget(data_attrs=('volume_size', ), transform=lambda x: ("%s (%s)" % (x.name, filesizeformat(x.bytes))))) flavor = forms.ChoiceField(label=_("Flavor"), help_text=_("Size of image to launch.")) '''groups = forms.MultipleChoiceField(label=_("Security Groups"), required=False, initial=["default"], widget=forms.CheckboxSelectMultiple(), #widget=forms.RadioSelect(), help_text=_("Launch instance in these ""security groups."))''' groups = forms.ChoiceField(label=_("Security Groups"), required=True, initial=["default"], widget=forms.RadioSelect, help_text=_("Launch instance in these " "security groups.")) volume_size = forms.IntegerField(label=_("Device size (GB)"), initial=1, min_value=0, required=False, help_text=_("Volume size in gigabytes " "(integer value).")) class Meta: name = ("Local and Remote Gateways") help_text = _( 'You can specify the Local (Provider) and Remote (Enterprise) Gateways in Local and Remote Sites resp. to add to this VPN.' ) def __init__(self, request, context, *args, **kwargs): self._init_images_cache() self.request = request self.context = context super(CreateSiteDetailAction, self).__init__(request, context, *args, **kwargs) def clean(self): cleaned_data = super(CreateSiteDetailAction, self).clean() return cleaned_data def _init_images_cache(self): if not hasattr(self, '_images_cache'): self._images_cache = {} @memoized.memoized_method def _get_keypair(self, keypair): try: # We want to retrieve details for a given keypair, # however keypair_list uses a memoized decorator # so it is used instead of keypair_get to reduce the number # of API calls. keypairs = instance_utils.keypair_list(self.request) keypair = [x for x in keypairs if x.id == keypair][0] except IndexError: keypair = None return keypair @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 populate_keypair_choices(self, request, context): keypairs = instance_utils.keypair_field_data(request, False) if len(keypairs) == 2: self.fields['keypair'].initial = keypairs[1][0] return keypairs def populate_groups_choices(self, request, context): try: groups = api.network.security_group_list(request) security_group_list = [(sg.name, sg.name) for sg in groups] except Exception: exceptions.handle(request, _('Unable to retrieve list of security groups')) security_group_list = [] return security_group_list def populate_flavor_choices(self, request, context): return instance_utils.flavor_field_data(request, False) 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 = image.virtual_size 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) choices.insert(0, ("", _("Select Image"))) else: choices.insert(0, ("", _("No images available"))) return choices 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) 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, images_cache=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(CreateSiteDetailAction, self).get_help_text(extra)
class CreateSubnetInfoAction(workflows.Action): subnet_name = forms.CharField(max_length=255, widget=forms.TextInput(attrs={}), label=_("Subnet Name"), required=False) address_source = forms.ChoiceField( required=False, label=_('Network Address Source'), choices=[('manual', _('Enter Network Address manually')), ('subnetpool', _('Allocate Network Address from a pool'))], widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'source', })) subnetpool = forms.ChoiceField( label=_("Address pool"), widget=forms.SelectWidget(attrs={ 'class': 'switched switchable', 'data-slug': 'subnetpool', 'data-switch-on': 'source', 'data-source-subnetpool': _('Address pool') }, data_attrs=('name', 'prefixes', 'ip_version', 'min_prefixlen', 'max_prefixlen', 'default_prefixlen'), transform=lambda x: "%s (%s)" % (x.name, ", ".join(x.prefixes)) if 'prefixes' in x else "%s" % (x.name)), required=False) prefixlen = forms.ChoiceField( widget=forms.Select(attrs={ 'class': 'switched', 'data-switch-on': 'subnetpool', }), label=_('Network Mask'), required=False) cidr = forms.IPField(label=_("Network Address"), required=False, initial="", widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'source', 'data-source-manual': _("Network Address"), }), help_text=_("Network address in CIDR format " "(e.g. 192.168.0.0/24, 2001:DB8::/48)"), version=forms.IPv4 | forms.IPv6, mask=True) ip_version = forms.ChoiceField( choices=[(4, 'IPv4'), (6, 'IPv6')], widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'ipversion', }), label=_("IP Version"), required=False) gateway_ip = forms.IPField( label=_("Gateway IP"), widget=forms.TextInput( attrs={ 'class': 'switched', 'data-switch-on': 'source gateway_ip', 'data-source-manual': _("Gateway IP") }), required=False, initial="", help_text=_("IP address of Gateway (e.g. 192.168.0.254) " "The default value is the first IP of the " "network address " "(e.g. 192.168.0.1 for 192.168.0.0/24, " "2001:DB8::1 for 2001:DB8::/48). " "If you use the default, leave blank. " "If you do not want to use a gateway, " "check 'Disable Gateway' below."), version=forms.IPv4 | forms.IPv6, mask=False) no_gateway = forms.BooleanField(label=_("Disable Gateway"), widget=forms.CheckboxInput( attrs={ 'class': 'switchable', 'data-slug': 'gateway_ip', 'data-hide-on-checked': 'true' }), initial=False, required=False) msg = _('Specify "Network Address", "Address pool" or ' 'clear "Create Subnet" checkbox.') class Meta(object): name = _("Subnet") help_text = _('Create a subnet associated with the network. ' 'Advanced configuration is available by clicking on the ' '"Subnet Details" tab.') def __init__(self, request, context, *args, **kwargs): super(CreateSubnetInfoAction, self).__init__(request, context, *args, **kwargs) if 'with_subnet' in context: self.fields['with_subnet'] = forms.BooleanField( initial=context['with_subnet'], required=False, widget=forms.HiddenInput()) if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}).get( 'enable_ipv6', True): self.fields['ip_version'].widget = forms.HiddenInput() self.fields['ip_version'].initial = 4 try: if api.neutron.is_extension_supported(request, 'subnet_allocation'): self.fields['subnetpool'].choices = \ self.get_subnetpool_choices(request) else: self.hide_subnetpool_choices() except Exception: self.hide_subnetpool_choices() msg = _('Unable to initialize subnetpools') exceptions.handle(request, msg) if len(self.fields['subnetpool'].choices) > 1: # Pre-populate prefixlen choices to satisfy Django # ChoiceField Validation. This is overridden w/data from # subnetpool on select. self.fields['prefixlen'].choices = \ zip(list(range(0, 128 + 1)), list(range(0, 128 + 1))) # Populate data-fields for switching the prefixlen field # when user selects a subnetpool other than # "Provider default pool" for (id, name) in self.fields['subnetpool'].choices: if not len(id): continue key = 'data-subnetpool-' + id self.fields['prefixlen'].widget.attrs[key] = \ _('Network Mask') else: self.hide_subnetpool_choices() def get_subnetpool_choices(self, request): subnetpool_choices = [('', _('Select a pool'))] default_ipv6_subnet_pool_label = \ getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}).get( 'default_ipv6_subnet_pool_label', None) default_ipv4_subnet_pool_label = \ getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}).get( 'default_ipv4_subnet_pool_label', None) if default_ipv6_subnet_pool_label: subnetpool_dict = { 'ip_version': 6, 'name': default_ipv6_subnet_pool_label } subnetpool = api.neutron.SubnetPool(subnetpool_dict) subnetpool_choices.append(('', subnetpool)) if default_ipv4_subnet_pool_label: subnetpool_dict = { 'ip_version': 4, 'name': default_ipv4_subnet_pool_label } subnetpool = api.neutron.SubnetPool(subnetpool_dict) subnetpool_choices.append(('', subnetpool)) for subnetpool in api.neutron.subnetpool_list(request): subnetpool_choices.append((subnetpool.id, subnetpool)) return subnetpool_choices def hide_subnetpool_choices(self): self.fields['address_source'].widget = forms.HiddenInput() self.fields['subnetpool'].choices = [] self.fields['subnetpool'].widget = forms.HiddenInput() self.fields['prefixlen'].widget = forms.HiddenInput() def _check_subnet_data(self, cleaned_data, is_create=True): cidr = cleaned_data.get('cidr') ip_version = int(cleaned_data.get('ip_version')) gateway_ip = cleaned_data.get('gateway_ip') no_gateway = cleaned_data.get('no_gateway') address_source = cleaned_data.get('address_source') # When creating network from a pool it is allowed to supply empty # subnetpool_id signaling that Neutron should choose the default # pool configured by the operator. This is also part of the IPv6 # Prefix Delegation Workflow. if not cidr and address_source != 'subnetpool': raise forms.ValidationError(self.msg) if cidr: subnet = netaddr.IPNetwork(cidr) if subnet.version != ip_version: msg = _('Network Address and IP version are inconsistent.') raise forms.ValidationError(msg) if (ip_version == 4 and subnet.prefixlen == 32) or \ (ip_version == 6 and subnet.prefixlen == 128): msg = _("The subnet in the Network Address is " "too small (/%s).") % subnet.prefixlen raise forms.ValidationError(msg) if not no_gateway and gateway_ip: if netaddr.IPAddress(gateway_ip).version is not ip_version: msg = _('Gateway IP and IP version are inconsistent.') raise forms.ValidationError(msg) if not is_create and not no_gateway and not gateway_ip: msg = _('Specify IP address of gateway or ' 'check "Disable Gateway".') raise forms.ValidationError(msg) def clean(self): cleaned_data = super(CreateSubnetInfoAction, self).clean() with_subnet = cleaned_data.get('with_subnet') if not with_subnet: return cleaned_data self._check_subnet_data(cleaned_data) return cleaned_data
class CreateForm(forms.SelfHandlingForm): name = forms.CharField(max_length="255", label=_("Volume Name")) description = forms.CharField( widget=forms.Textarea(attrs={'class': 'modal-body-fixed-width'}), label=_("Description"), required=False) type = forms.ChoiceField(label=_("Type"), required=False) size = forms.IntegerField(min_value=1, label=_("Size (GB)")) volume_source_type = forms.ChoiceField( label=_("Volume Source"), required=False, widget=forms.Select(attrs={ 'class': 'switchable', 'data-slug': 'source' })) snapshot_source = forms.ChoiceField( label=_("Use snapshot as a source"), widget=forms.SelectWidget(attrs={'class': 'snapshot-selector'}, data_attrs=('size', 'name'), transform=lambda x: "%s (%sGB)" % (x.name, x.size)), required=False) image_source = forms.ChoiceField( label=_("Use image as a source"), widget=forms.SelectWidget(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.SelectWidget( attrs={'class': 'image-selector'}, data_attrs=('size', 'name'), transform=lambda x: "%s (%s)" % (x.name, filesizeformat(x.size * 1024 * 1024 * 1024))), required=False) availability_zone = forms.ChoiceField( label=_("Availability Zone"), required=False, widget=forms.Select( attrs={ 'class': 'switched', 'data-switch-on': 'source', 'data-source-no_source_type': _('Availability Zone'), 'data-source-image_source': _('Availability Zone') })) def __init__(self, request, *args, **kwargs): super(CreateForm, self).__init__(request, *args, **kwargs) volume_types = cinder.volume_type_list(request) self.fields['type'].choices = [("", "")] + \ [(type.name, type.name) for type in volume_types] if "snapshot_id" in request.GET: 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 (%sGB)') \ % 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.')) elif 'image_id' in request.GET: self.fields['availability_zone'].choices = \ self.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) min_disk_size = getattr(image, '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 (%sGB)') \ % 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']) elif 'volume_id' in request.GET: self.fields['availability_zone'].choices = \ self.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 (%s)') \ % filesizeformat(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'] else: source_type_choices = [] self.fields['availability_zone'].choices = \ self.availability_zones(request) try: snapshot_list = cinder.volume_snapshot_list(request) snapshots = [ s for s in snapshot_list if s.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'] # Determine whether the extension for Cinder AZs is enabled def cinder_az_supported(self, request): try: return cinder.extension_supported(request, 'AvailabilityZones') except Exception: exceptions.handle( request, _('Unable to determine if ' 'availability zones extension ' 'is supported.')) return False def availability_zones(self, request): zone_list = [] if self.cinder_az_supported(request): try: zones = api.cinder.availability_zone_list(request) zone_list = [(zone.zoneName, zone.zoneName) for zone in zones if zone.zoneState['available']] zone_list.sort() except Exception: exceptions.handle( request, _('Unable to retrieve availability ' 'zones.')) if not zone_list: zone_list.insert(0, ("", _("No availability zones found"))) elif len(zone_list) > 0: zone_list.insert(0, ("", _("Any Availability Zone"))) return zone_list def get_volumes(self, request): volumes = [] try: volume_list = cinder.volume_list(self.request) if volume_list is not None: volumes = [ v for v in volume_list if v.status == api.cinder.VOLUME_STATE_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 (%sGB)') % 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) min_disk_size = getattr(image, 'min_disk', 0) if (min_disk_size > 0 and data['size'] < image.min_disk): error_message = _( 'The volume size cannot be less than ' 'the image minimum disk size (%sGB)') % 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 (%sGB)') % 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)iGB cannot be created as ' 'you only have %(avail)iGB 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 = {} 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: exceptions.handle(request, ignore=True) self.api_error(_("Unable to create volume.")) return False @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 RebuildInstanceForm(forms.SelfHandlingForm): instance_id = forms.CharField(widget=forms.HiddenInput()) image = forms.ChoiceField( label=_("New Image"), widget=forms.SelectWidget(attrs={'class': 'image-selector'}, data_attrs=('size', 'display-name'), transform=_image_choice_title)) username = forms.CharField(max_length=64, label=_("User Name")) sync_set_root = forms.BooleanField(label=_("Sync set root/Administrator password"), initial=True, required=False) password = forms.RegexField( label=_("Rebuild Password"), required=False, widget=forms.PasswordInput(render_value=False, attrs={'placeholder': _('begin letter,8-64 bits,with number,letter and symbol')}), regex=validators.password_validator(), error_messages={'invalid': validators.password_validator_msg()}) confirm_password = forms.CharField( label=_("Confirm Rebuild Password"), required=False, widget=forms.PasswordInput(render_value=False)) # disk_config = forms.ChoiceField(label=_("Disk Partition"), # required=False) def __init__(self, request, *args, **kwargs): super(RebuildInstanceForm, self).__init__(request, *args, **kwargs) instance_id = kwargs.get('initial', {}).get('instance_id') self.fields['instance_id'].initial = instance_id self.fields["username"].initial = 'syscloud' images = utils.get_available_images(request, request.user.tenant_id) choices = [(image.id, image) for image in images] if choices: choices.insert(0, ("", _("Select Image"))) else: choices.insert(0, ("", _("No images available"))) self.fields['image'].choices = choices if not api.nova.can_set_server_password(): del self.fields['password'] del self.fields['confirm_password'] # try: # if not api.nova.extension_supported("DiskConfig", request): # del self.fields['disk_config'] # else: # # Set our disk_config choices # config_choices = [("AUTO", _("Automatic")), # ("MANUAL", _("Manual"))] # self.fields['disk_config'].choices = config_choices # except Exception: # exceptions.handle(request, _('Unable to retrieve extensions ' # 'information.')) def clean(self): cleaned_data = super(RebuildInstanceForm, self).clean() is_allow_inject_passwd = False select_image_id = cleaned_data.get('image', None) if select_image_id != None: image = api.glance.image_get(self.request, select_image_id) is_allow_inject_passwd = getattr(image, 'is_allow_inject_passwd', False) cleaned_data['is_allow_inject_passwd'] = is_allow_inject_passwd cleaned_data['image_name'] = image.name if 'password' in cleaned_data: passwd = cleaned_data.get('password') confirm = cleaned_data.get('confirm_password') if passwd is not None and confirm is not None: if passwd != confirm: raise forms.ValidationError(_("Passwords do not match.")) if is_allow_inject_passwd: if not validators.validate_password(passwd): raise forms.ValidationError(_("The password must begin with a letter, " "and the length is between the 8-64 bits, " "and the number, the letter and the symbol are the same.")) return cleaned_data # We have to protect the entire "data" dict because it contains the # password and confirm_password strings. @sensitive_variables('data', 'password') def handle(self, request, data): instance = data.get('instance_id') image = data.get('image') image_name = data.get('image_name', None) is_allow_inject_passwd = data.get('is_allow_inject_passwd', None) username = data.get('username', 'syscloud') password = data.get('password') or None disk_config = data.get('disk_config', None) is_sync_set_root = data.get('sync_set_root', False) if is_allow_inject_passwd: user_data_script_helper = userdata.UserDataScriptHelper(image_name, is_sync_set_root=is_sync_set_root, is_rebuild=True) password = user_data_script_helper.get_user_data(username, password) try: api.nova.server_rebuild(request, instance, image, password, disk_config) messages.success(request, _('Rebuilding instance %s.') % instance) # operation log config = _('Instance ID: %s') %instance api.logger.Logger(request).create(resource_type='instance', action_name='Rebuild Instance', resource_name='Instance', config=config, status='Success') except Exception: redirect = reverse('horizon:instances:instances:index') exceptions.handle(request, _("Unable to rebuild instance."), redirect=redirect) # operation log config = _('Instance ID: %s') %instance api.logger.Logger(request).create(resource_type='instance', action_name='Rebuild Instance', resource_name='Instance', config=config, status='Error') return True
class FirewallForm(forms.SelfHandlingForm): #required_css_class = 'required form-float' required_css_class = 'form-horizontal' gateway_ip = forms.ChoiceField( label=_("Select Gateway"), widget=forms.SelectWidget(attrs={'style': 'width: 500px'})) gateway_port = forms.IntegerField( label=_("Gateway Port (10000-65000"), validators=[limit_port_range], widget=forms.TextInput(attrs={'style': 'width: 510px'})) service_port = forms.IntegerField( label=_("Service Port"), min_value=1, validators=[validators.validate_port_range], widget=forms.TextInput(attrs={'style': 'width: 510px'})) def __init__(self, request, *args, **kwargs): super(FirewallForm, self).__init__(request, *args, **kwargs) choices = [] tmp_choices = [] gateways = kwargs['initial']['gateways'] for gateway in gateways: item = (gateway.hostname, gateway.vext_ip) if gateway.int_dev != gateway.ext_dev or \ gateway.vext_ip != gateway.ext_ip: choices.append(item) else: tmp_choices.append(item) if not choices: choices = tmp_choices if choices: choices.insert(0, ("", _("Select Gateway"))) else: choices.insert(0, ("", _("No Gateway available"))) self.fields['gateway_ip'].choices = choices def clean(self): data = super(forms.Form, self).clean() hostname = data.get("gateway_ip") gateway_port = data.get("gateway_port") service_port = data.get("service_port") if hostname and gateway_port and service_port: instance = self.initial['instance'] try: api.daolicloud.firewall_exist(self.request, instance.id, hostname=hostname, gateway_port=gateway_port) except Exception as e: #msg = _("This gateway port already be used.") #raise forms.ValidationError(msg) raise forms.ValidationError(e.message) return data def handle(self, request, data): hostname = data['gateway_ip'] gport = data['gateway_port'] sport = data['service_port'] instance = self.initial['instance'] try: firewall = api.daolicloud.firewall_create(request, instance.id, hostname, gport, sport) message = _('Create gateway %(gport)s, service %(sport)s to ' ' instance %(inst)s.') % { "gport": gport, "sport": sport, "inst": instance.name } messages.success(request, message) return firewall except Exception: redirect = reverse("horizon:project:edges:index") exceptions.handle(request, _('Unable to add firewall.'), redirect=redirect)
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 Type"), required=False, widget=forms.SelectWidget(attrs={ 'class': 'switchable', 'data-slug': 'source' })) snapshot_source = forms.ChoiceField( label=_("Use snapshot as a source"), widget=forms.SelectWidget(attrs={'class': 'snapshot-selector'}, data_attrs=('name', 'id'), transform=lambda x: "%s (%s GiB)" % (x.name, x.id)), required=False) checkpoint_source = forms.ChoiceField( label=_("Use checkpoint as a source"), widget=forms.SelectWidget(attrs={'class': 'snapshot-selector'}, data_attrs=('name', 'id'), transform=lambda x: "%s (%s GiB)" % (x.name, x.id)), required=False) type = forms.ChoiceField(label=_("Type"), required=False, widget=forms.SelectWidget( 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.SelectWidget( 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_checkpoint_specified(self, request): try: checkpoint = self.get_checkpoint(request, request.GET["checkpoint_id"]) replication = self.get_replication(request, checkpoint.replication_id) master_volume = self.get_volume(request, replication.master_volume) master_az = master_volume.availability_zone slave_volume = self.get_volume(request, replication.slave_volume) slave_az = slave_volume.availability_zone self.fields['name'].initial = checkpoint.name self.fields['size'].initial = master_volume.size self.fields['checkpoint_source'].choices = ((checkpoint.id, checkpoint), ) self.fields['availability_zone'].choices = \ [(master_az, master_az), (slave_az, slave_az)] try: # Set the volume type from the original volume orig_volume = cinder.volume_get(request, replication.master_volume) 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 ' 'master_volume size (%sGiB)') % master_volume.size) del self.fields['volume_source_type'] del self.fields['snapshot_source'] except Exception: exceptions.handle(request, _('Unable to load the specified snapshot.')) 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['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 self.fields['size'].initial = orig_volume.size except Exception: pass self.fields['size'].help_text = ( _('Volume size must be equal to or greater than the ' 'snapshot size (%sGiB)') % orig_volume.size) self.fields['type'].widget = forms.widgets.HiddenInput() del self.fields['volume_source_type'] del self.fields['availability_zone'] del self.fields['checkpoint_source'] except Exception: exceptions.handle(request, _('Unable to load the specified snapshot.')) def prepare_source_fields_default(self, request): source_type_choices = [] self.fields['availability_zone'].choices = \ availability_zones(request) try: available = sg_api.VOLUME_STATE_AVAILABLE snapshots = sg_api.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.")) try: checkpoints = sg_api.volume_checkpoint_list( request, search_opts=dict(status=available)) if checkpoints: source_type_choices.append( ("checkpoint_source", _("Checkpoint"))) choices = [('', _("Choose a checkpoint"))] + \ [(s.id, s) for s in checkpoints] self.fields['checkpoint_source'].choices = choices else: del self.fields['checkpoint_source'] except Exception: exceptions.handle(request, _("Unable to retrieve volume checkpoints.")) 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 = [] try: volume_types = cinder.volume_type_list(request) except Exception: redirect_url = reverse("horizon:storage-gateway:volumes:index") error_message = _('Unable to retrieve the volume type list.') exceptions.handle(request, error_message, redirect=redirect_url) self.fields['type'].choices = [("", _("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 'checkpoint_id' in request.GET: self.prepare_source_fields_if_checkpoint_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 == 'checkpoint_source' and not cleaned_data.get('checkpoint_source')): msg = _('Checkpoint source must be specified') self._errors['checkpoint_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]) return cleaned_data def get_volumes(self, request): volumes = [] try: available = sg_api.VOLUME_STATE_ENABLED volumes = sg_api.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 checkpoint_id = None source_type = data.get('volume_source_type', None) az = data.get('availability_zone', None) or None volume_type = data.get('type') if (data.get("snapshot_source", None) and source_type in ['', None, 'snapshot_source']): # Create from Snapshot snapshot = self.get_snapshot(request, data["snapshot_source"]) orig_volume = cinder.volume_get(request, snapshot.volume_id) snapshot_id = snapshot.id if data['size'] < orig_volume.size: error_message = (_('The volume size cannot be less than ' 'the snapshot size (%sGiB)') % orig_volume.size) raise ValidationError(error_message) az = None volume_type = "" if (data.get("checkpoint_source", None) and source_type in ['', None, 'checkpoint_source']): # Create from Checkpoint checkpoint = self.get_checkpoint(request, data["checkpoint_source"]) checkpoint_id = checkpoint.id check_size = self.fields['size'].initial if data['size'] < check_size: error_message = (_('The volume size cannot be less than ' 'the master_volume size (%sGiB)') % check_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) volume = sg_api.volume_create(request, size=data['size'], name=data['name'], description=data['description'], volume_type=volume_type, snapshot_id=snapshot_id, availability_zone=az, checkpoint_id=checkpoint_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:storage-gateway:volumes:index") exceptions.handle(request, _("Unable to create volume."), redirect=redirect) @memoized def get_snapshot(self, request, id): return sg_api.volume_snapshot_get(request, id) @memoized def get_volume(self, request, id): return sg_api.volume_get(request, id) @memoized def get_checkpoint(self, request, id): return sg_api.volume_checkpoint_get(request, id) @memoized def get_replication(self, request, id): return sg_api.volume_replication_get(request, id)
def __init__(self, request, *args, **kwargs): super(CreateShareGroupForm, self).__init__(request, *args, **kwargs) self.st_field_name_prefix = "share-type-choices-" self.fields["source_type"] = forms.ChoiceField( label=_("Source Type"), widget=forms.Select(attrs={ "class": "switchable", "data-slug": "source", }), required=False) self.fields["snapshot"] = forms.ChoiceField( label=_("Use share group snapshot as a source"), widget=forms.SelectWidget( attrs={ "class": "switched", "data-switch-on": "source", "data-source-snapshot": _("Share Group Snapshot"), }), required=True) if ("snapshot_id" in request.GET or kwargs.get("data", {}).get("snapshot")): try: snapshot = self.get_share_group_snapshot( request, request.GET.get("snapshot_id", kwargs.get("data", {}).get("snapshot"))) self.fields["name"].initial = snapshot.name self.fields["snapshot"].choices = ((snapshot.id, snapshot.name or snapshot.id), ) try: # Set the share group type from the original share group orig_sg = manila.share_group_get(request, snapshot.share_group_id) self.fields["sgt"].initial = orig_sg.share_group_type_id except Exception: pass del self.fields["source_type"] except Exception: exceptions.handle( request, _("Unable to load the specified share group snapshot.")) else: source_type_choices = [] try: snapshot_list = manila.share_group_snapshot_list(request) snapshots = [ s for s in snapshot_list if s.status == "available" ] if snapshots: source_type_choices.append(("snapshot", _("Snapshot"))) self.fields["snapshot"].choices = ( [("", _("Choose a snapshot"))] + [(s.id, s.name or s.id) for s in snapshots]) else: del self.fields["snapshot"] except Exception: exceptions.handle( request, _("Unable to retrieve share group snapshots.")) if source_type_choices: choices = ([('none', _("No source, empty share group"))] + source_type_choices) self.fields["source_type"].choices = choices else: del self.fields["source_type"] self.fields["az"] = forms.ChoiceField( label=_("Availability Zone"), widget=forms.SelectWidget( attrs={ "class": "switched", "data-switch-on": "source", "data-source-none": _("Availability Zone"), }), required=False) availability_zones = manila.availability_zone_list(request) self.fields["az"].choices = ([("", "")] + [(az.name, az.name) for az in availability_zones]) share_group_types = manila.share_group_type_list(request) self.fields["sgt"] = forms.ChoiceField( label=_("Share Group Type"), widget=forms.fields.SelectWidget( attrs={ "class": "switched switchable", "data-switch-on": "source", "data-source-none": _("Share Group Type"), "data-slug": "sgt", }), required=True) self.fields["sgt"].choices = ( [("", "")] + [(utils.transform_dashed_name(sgt.id), sgt.name) for sgt in share_group_types]) # NOTE(vponomaryov): create separate set of available share types # for each of share group types. share_types = manila.share_type_list(request) for sgt in share_group_types: st_choices = ([(st.id, st.name) for st in share_types if st.id in sgt.share_types]) amount_of_choices = len(st_choices) st_field_name = (self.st_field_name_prefix + utils.transform_dashed_name(sgt.id)) if amount_of_choices < 2: st_field = forms.ChoiceField( label=_("Share Types"), choices=st_choices, widget=forms.fields.SelectWidget( attrs={ "class": "switched", "data-switch-on": "sgt", "data-sgt-%s" % utils.transform_dashed_name(sgt.id): _("Share Types (one available)"), }), required=True) else: height = min(30 * amount_of_choices, 155) st_field = forms.MultipleChoiceField( label=_("Share Types"), choices=st_choices, widget=forms.fields.widgets.SelectMultiple( attrs={ "style": "max-height: %spx;" % height, "class": "switched", "data-switch-on": "sgt", "data-sgt-%s" % utils.transform_dashed_name(sgt.id): _("Share Types (multiple available)"), }), required=False) st_field.initial = st_choices[0] self.fields[st_field_name] = st_field self.fields["share_network"] = forms.ChoiceField( label=_("Share Network"), widget=forms.fields.SelectWidget( attrs={ "class": "switched", "data-switch-on": "source", "data-source-none": _("Share Network"), }), required=False) share_networks = manila.share_network_list(request) self.fields["share_network"].choices = ([("", "")] + [(sn.id, sn.name or sn.id) for sn in share_networks])
class RebuildInstanceForm(forms.SelfHandlingForm): instance_id = forms.CharField(widget=forms.HiddenInput()) image = forms.ChoiceField(label=_("Select Image"), widget=forms.SelectWidget( attrs={'class': 'image-selector'}, data_attrs=('size', 'display-name'), transform=_image_choice_title)) password = forms.RegexField( label=_("Rebuild Password"), required=False, widget=forms.PasswordInput(render_value=False), regex=validators.password_validator(), error_messages={'invalid': validators.password_validator_msg()}) confirm_password = forms.CharField( label=_("Confirm Rebuild Password"), required=False, widget=forms.PasswordInput(render_value=False)) disk_config = forms.ChoiceField(label=_("Disk Partition"), required=False) def __init__(self, request, *args, **kwargs): super(RebuildInstanceForm, self).__init__(request, *args, **kwargs) instance_id = kwargs.get('initial', {}).get('instance_id') self.fields['instance_id'].initial = instance_id images = image_utils.get_available_images(request, request.user.tenant_id) choices = [(image.id, image) for image in images] if choices: choices.insert(0, ("", _("Select Image"))) else: choices.insert(0, ("", _("No images available"))) self.fields['image'].choices = choices if not api.nova.can_set_server_password(): del self.fields['password'] del self.fields['confirm_password'] try: if not api.nova.extension_supported("DiskConfig", request): del self.fields['disk_config'] else: # Set our disk_config choices config_choices = [("AUTO", _("Automatic")), ("MANUAL", _("Manual"))] self.fields['disk_config'].choices = config_choices except Exception: exceptions.handle( request, _('Unable to retrieve extensions ' 'information.')) def clean(self): cleaned_data = super(RebuildInstanceForm, self).clean() if 'password' in cleaned_data: passwd = cleaned_data.get('password') confirm = cleaned_data.get('confirm_password') if passwd is not None and confirm is not None: if passwd != confirm: raise forms.ValidationError(_("Passwords do not match.")) return cleaned_data # We have to protect the entire "data" dict because it contains the # password and confirm_password strings. @sensitive_variables('data', 'password') def handle(self, request, data): instance = data.get('instance_id') image = data.get('image') password = data.get('password') or None disk_config = data.get('disk_config', None) try: api.nova.server_rebuild(request, instance, image, password, disk_config) messages.success(request, _('Rebuilding instance %s.') % instance) except Exception: redirect = reverse('horizon:project:instances:index') exceptions.handle(request, _("Unable to rebuild instance."), redirect=redirect) return True
class SetInstanceDetailsAction(workflows.Action): flavor_vcpus = forms.CharField(widget=forms.HiddenInput, required=False) flavor_ram = forms.CharField(widget=forms.HiddenInput, required=False) availability_zone = forms.ChoiceField(label=_("Availability Zone"), required=False) availability_zone_info = forms.CharField(widget=forms.HiddenInput, required=False) cluster_id = forms.ChoiceField(label=_("Cluster"), required=False) template_id = forms.ChoiceField(label=_("Template"), required=False) name = forms.CharField(label=_("Instance Name"), max_length=255) 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.")) source_type = forms.ChoiceField(label=_("Instance Boot Source"), help_text=_("Choose Your Boot Source " "Type.")) instance_snapshot_id = forms.ChoiceField(label=_("Instance Snapshot"), required=False) volume_id = forms.ChoiceField(label=_("Volume"), required=False) volume_snapshot_id = forms.ChoiceField(label=_("Volume Snapshot"), required=False) image_id = forms.ChoiceField( label=_("Image Name"), required=False, widget=forms.SelectWidget(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').")) delete_on_terminate = forms.BooleanField(label=_("Delete on Terminate"), initial=False, required=False, help_text=_("Delete volume on " "instance terminate")) class Meta: name = _("Details") help_text_template = ("project/applyhost/" "_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) source_type_choices = [ ('', _("Select source")), ("image_id", _("Boot from image")), ("instance_snapshot_id", _("Boot from snapshot")), ] if base.is_service_enabled(request, 'volume'): 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 availability_zone_info = self.get_availability_zone_info() if availability_zone_info and type(availability_zone_info) == type({}): self.fields[ 'availability_zone_info'].initial = availability_zone_info.get( 'res') self.fields['template_id'].choices = availability_zone_info.get( 'all_templates') self.fields['cluster_id'].choices = availability_zone_info.get( 'all_clusters') def get_availability_zone_info(self): res = {} def_res = {} try: source_domains = api.nova.availability_zone_list(self.request) if not source_domains: return def_res plats = [] try: request_api = RequestApi() plats = request_api.getRequestInfo( 'api/heterogeneous/platforms') except Exception: plats = [] if not plats: return def_res all_clusters = [] all_templates = [] for sd in source_domains: for pl in plats: if pl.get('domain_name') == sd.zoneName: vtype = pl.get('virtualplatformtype') templates = request_api.getRequestInfo( 'api/heterogeneous/platforms/' + vtype + '/templates', {'name': pl.get('name')}) or [] templates = [ t for t in templates if t.get('name') not in CSERVER_TEMPLATES_BLACK_LIST ] _clusters = request_api.getRequestInfo( 'api/heterogeneous/platforms/' + vtype + '/clusters', {'name': pl.get('name')}) or [] all_clusters += [(c.get('id'), c.get('name')) for c in _clusters] all_templates += [(c.get('id'), c.get('name')) for c in templates] clusters = [{ 'id': c.get('id'), 'name': c.get('name') } for c in _clusters] res[sd.zoneName] = { 'vtype': vtype, 'clusters': clusters, 'templates': templates } break return { 'res': json.dumps(res), 'all_clusters': all_clusters, 'all_templates': all_templates } except Exception: res = _( 'Unable to get vtype|clusters|templates info of source domain.' ) exceptions.handle(self.request, ignore=True) return def_res def get_image_id(self, image_name, request, context): images_list = self.get_images_list(request, context) if images_list: for image in images_list: if image_name == getattr(image[1], 'name', ''): return image[0] return '' def get_exists_name(self): try: instances, _more = api.nova.server_list(self.request, all_tenants=True) exists_name = [getattr(i, 'name', '') for i in instances] except Exception: self._more = False exceptions.handle(self.request, _('Unable to retrieve instance list.')) exists_name = [] return exists_name def set_meta_data(self, cleaned_data): availability_zone_info_str = cleaned_data.get('availability_zone_info') if availability_zone_info_str: cleaned_data['metadata'] = {} availability_zone = cleaned_data.get('availability_zone') availability_zone_info = json.loads(availability_zone_info_str) platformtype = 'Openstack' if availability_zone_info and availability_zone_info.get(availability_zone) \ and availability_zone_info.get(availability_zone).get('vtype'): platformtype = availability_zone_info.get( availability_zone).get('vtype') cleaned_data['metadata']['platformtype'] = platformtype return cleaned_data def clean(self): cleaned_data = super(SetInstanceDetailsAction, self).clean() cleaned_data = self.set_meta_data(cleaned_data) exists_name = self.get_exists_name() if cleaned_data.get('name') in exists_name: raise forms.ValidationError( _('The name is already used by another vm.')) flavor_vcpus = cleaned_data.get('flavor_vcpus') flavor_ram = cleaned_data.get('flavor_ram') if cleaned_data.get( 'cluster_id') and not cleaned_data.get('template_id'): raise forms.ValidationError(_('Please select one template.')) if cleaned_data.get('template_id') and cleaned_data.get('cluster_id') \ and cleaned_data.get('flavor_vcpus') and cleaned_data.get('flavor_ram'): _flavors = [] try: _flavors = api.nova.flavor_list(self.request, None) or [] except Exception as e: pass finded = False for f in _flavors: if getattr(f, 'vcpus', '0') and getattr(f, 'ram', '0'): if int(flavor_vcpus) == int( f.vcpus) and int(flavor_ram) == int(f.ram): cleaned_data['flavor'] = getattr(f, 'id', '') finded = True if not finded: new_flavor = None try: new_flavor = api.nova.flavor_create(self.request, name='vflavor-' + str(uuid.uuid4()), memory=flavor_ram, vcpu=flavor_vcpus, disk='10', ephemeral='0', swap='0', is_public=True) except Exception: exceptions.handle(self.request, _('Unable to create flavor.')) new_flavor_id = getattr(new_flavor, 'id', '') if new_flavor else '' cleaned_data['flavor'] = new_flavor_id cleaned_data['source_type'] = 'image_id' cleaned_data['image_id'] = self.get_image_id( 'cserver_template_image', self.request, self.context) count = cleaned_data.get('count', 1) 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) try: flavor_id = cleaned_data.get('flavor') # 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 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]) # Validate our instance source. source_type = self.data.get('source_type', None) if source_type in ('image_id', 'volume_image_id'): if source_type == 'volume_image_id': 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('device_name'): msg = _("You must set device name") self._errors['device_name'] = 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]) else: # Prevents trying to launch an image needing more resources. try: image_id = cleaned_data.get('image_id') # 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 if image and flavor: 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. volume_size = cleaned_data.get('volume_size') if volume_size and source_type == 'volume_image_id': 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]) elif source_type == 'instance_snapshot_id': if not cleaned_data['instance_snapshot_id']: msg = _("You must select a snapshot.") self._errors['instance_snapshot_id'] = self.error_class([msg]) elif source_type == 'volume_id': 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? if count > 1: msg = _('Launching multiple instances is only supported for ' 'images and instance snapshots.') raise forms.ValidationError(msg) elif source_type == 'volume_snapshot_id': if not cleaned_data.get('volume_snapshot_id'): msg = _("You must select a snapshot.") self._errors['volume_snapshot_id'] = self.error_class([msg]) if not cleaned_data.get('device_name'): msg = _("You must set device name") self._errors['device_name'] = self.error_class([msg]) return cleaned_data def populate_flavor_choices(self, request, context): flavors = instance_utils.flavor_list(request) res_flavors = [] for i in flavors: if getattr(i, 'name', '') not in ('m1.cserver_flavor', 'm1.vcenter_flavor'): res_flavors.append(i) if res_flavors: return instance_utils.sort_flavor_list(request, res_flavors) return [] 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 = extra_context or {} try: extra['usages'] = api.nova.tenant_absolute_limits(self.request) 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) } 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 get_images_list(self, request, context): choices = [] images = image_utils.get_available_images(request, context.get('project_id'), self._images_cache) for image in images: image.bytes = 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) choices.insert(0, ("", _("Select Image"))) else: choices.insert(0, ("", _("No images available"))) return choices def populate_image_id_choices(self, request, context): images = self.get_images_list(request, context) res_images = [] for image in images: if getattr(image[1], 'name', '') not in ('cserver_template_image', 'vcenter_template_image'): res_images.append(image) return res_images 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): try: volumes = [ self._get_volume_display_name(v) for v in cinder.volume_list(self.request) if v.status == api.cinder.VOLUME_STATE_AVAILABLE and v.bootable == 'true' ] except Exception: volumes = [] 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): try: snapshots = cinder.volume_snapshot_list(self.request) snapshots = [ self._get_volume_display_name(s) for s in snapshots if s.status == api.cinder.VOLUME_STATE_AVAILABLE ] except Exception: snapshots = [] 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 SetInstanceDetailsAction(workflows.Action): availability_zone = forms.ChoiceField(label=_("Availability Zone"), required=False) name = forms.CharField(label=_("Instance Name"), max_length=255) 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.")) source_type = forms.ChoiceField(label=_("Instance Boot Source"), help_text=_("Choose Your Boot Source " "Type.")) instance_snapshot_id = forms.ChoiceField(label=_("Instance Snapshot"), required=False) volume_id = forms.ChoiceField(label=_("Volume"), required=False) volume_snapshot_id = forms.ChoiceField(label=_("Volume Snapshot"), required=False) image_id = forms.ChoiceField( label=_("Image Name"), required=False, widget=forms.SelectWidget(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) 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=_("Replication Name"), required=False) description = forms.CharField(max_length=255, widget=forms.Textarea(attrs={'rows': 4}), label=_("Description"), required=False) master_volume = forms.ChoiceField( label=_("Master Volume"), widget=forms.SelectWidget(attrs={'class': 'image-selector'}, data_attrs=('id', 'name'), transform=lambda x: "%s (%s)" % (x.name, x.id))) slave_volume = forms.ChoiceField( label=_("Slave Volume"), widget=forms.SelectWidget(attrs={'class': 'image-selector'}, data_attrs=('id', 'name'), transform=lambda x: "%s (%s)" % (x.name, x.id))) def prepare_fields_default(self, request): try: volumes = self.get_volumes(request) choices = [(s.id, s) for s in volumes] self.fields['master_volume'].choices = choices self.fields['slave_volume'].choices = choices except Exception: exceptions.handle(request, _("Unable to retrieve volumes.")) def __init__(self, request, *args, **kwargs): super(CreateForm, self).__init__(request, *args, **kwargs) self.prepare_fields_default(request) def clean(self): cleaned_data = super(CreateForm, self).clean() if not cleaned_data.get('master_volume'): msg = _('Replication master_volume must be specified') self._errors['master_volume'] = self.error_class([msg]) if not cleaned_data.get('slave_volume'): msg = _('Replication slave_volume must be specified') self._errors['slave_volume'] = self.error_class([msg]) return cleaned_data def get_volumes(self, request): volumes = [] try: enabled = sg_api.VOLUME_STATE_ENABLED for vol in sg_api.volume_list(self.request, search_opts=dict(status=enabled)): if vol.replicate_status in ['deleted', 'disabled', None]: volumes.append(vol) except Exception: exceptions.handle(request, _('Unable to retrieve list of volumes.')) return volumes def handle(self, request, data): try: name = data.get("name", None) description = data.get("description", None) master_id = data.get('master_volume', None) slave_id = data.get('slave_volume', None) master_vol = sg_api.volume_get(request, master_id) slave_vol = sg_api.volume_get(request, slave_id) if master_id == slave_id: error_message = (_('The slave volume and master volume can not' ' be the same')) raise ValidationError(error_message) if master_vol.availability_zone == slave_vol.availability_zone: error_message = (_('The slave volume and master volume can not' ' be the same availability_zone')) raise ValidationError(error_message) replication = sg_api.volume_replication_create( request, master_id, slave_id, name, description) message = _('Creating replication "%s"') % data['name'] messages.info(request, message) return replication except ValidationError as e: self.api_error(e.messages[0]) return False except Exception: redirect = reverse("horizon:storage-gateway:replications:index") exceptions.handle(request, _("Unable to create replication."), redirect=redirect) @memoized def get_volume(self, request, id): return sg_api.volume_get(request, id)