Example #1
0
class AttachVolume(forms.SelfHandlingForm):
    volume = forms.ChoiceField(label=_("Volume ID"),
                               widget=forms.ThemableSelectWidget(),
                               help_text=_("Select a volume to attach "
                                           "to this instance."))
    device = forms.CharField(label=_("Device Name"),
                             widget=forms.HiddenInput(),
                             required=False,
                             help_text=_("Actual device name may differ due "
                                         "to hypervisor settings. If not "
                                         "specified, then hypervisor will "
                                         "select a device name."))
    instance_id = forms.CharField(widget=forms.HiddenInput())

    def __init__(self, *args, **kwargs):
        super(AttachVolume, self).__init__(*args, **kwargs)

        # Populate volume choices
        volume_list = kwargs.get('initial', {}).get("volume_list", [])
        volumes = []
        for volume in volume_list:
            # Only show volumes that aren't attached to an instance already
            if not volume.attachments:
                volumes.append((volume.id, '%(name)s (%(id)s)' % {
                    "name": volume.name,
                    "id": volume.id
                }))
        if volumes:
            volumes.insert(0, ("", _("Select a volume")))
        else:
            volumes.insert(0, ("", _("No volumes available")))
        self.fields['volume'].choices = volumes

    def handle(self, request, data):
        instance_id = self.initial.get("instance_id", None)
        volume_choices = dict(self.fields['volume'].choices)
        volume = volume_choices.get(data['volume'], _("Unknown volume (None)"))
        volume_id = data.get('volume')

        device = data.get('device') or None

        try:
            attach = api.nova.instance_volume_attach(request, volume_id,
                                                     instance_id, device)

            message = _('Attaching volume %(vol)s to instance '
                        '%(inst)s on %(dev)s.') % {
                            "vol": volume,
                            "inst": instance_id,
                            "dev": attach.device
                        }
            messages.info(request, message)
        except Exception as ex:
            redirect = reverse('horizon:project:tasks:index')
            if isinstance(ex, api.nova.VolumeMultiattachNotSupported):
                # Use the specific error from the specific message.
                msg = six.text_type(ex)
            else:
                # Use a generic error message.
                msg = _('Unable to attach volume.')
            exceptions.handle(request, msg, redirect=redirect)
        return True
Example #2
0
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.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'source',
        }))

    subnetpool = forms.ChoiceField(
        label=_("Address pool"),
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched switchable',
                'data-required-when-shown': 'true',
                '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.ThemableSelectWidget(attrs={
            'class': 'switched',
            'data-switch-on': 'subnetpool',
        }),
        label=_('Network Mask'),
        required=False)

    cidr = forms.IPField(label=_("Network Address"),
                         required=False,
                         initial="",
                         error_messages={
                             'required':
                             _('Specify "Network Address" or '
                               'clear "Create Subnet" checkbox '
                               'in previous step.')
                         },
                         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.ThemableSelectWidget(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().__init__(request, context, *args, **kwargs)
        if not setting_utils.get_dict_config('OPENSTACK_NEUTRON_NETWORK',
                                             'enable_ipv6'):
            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 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 = 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):
        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 address_source == 'subnetpool' and 'cidr' in self._errors:
            del self._errors['cidr']
        elif 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 no_gateway and 'gateway_ip' in self._errors:
            del self._errors['gateway_ip']

    def _remove_fields_errors(self):
        self._errors = {}

    def clean(self):
        with_subnet = self.initial.get('with_subnet')
        if not with_subnet:
            self._remove_fields_errors()
            return None
        cleaned_data = super().clean()
        self._check_subnet_data(cleaned_data)
        return cleaned_data
Example #3
0
class ImportPackageForm(forms.Form):
    import_type = forms.ChoiceField(
        label=_("Package Source"),
        choices=IMPORT_TYPE_CHOICES,
        widget=horizon_forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'source'
        }))
    url = PackageURLField(
        label=_("Package URL"),
        required=False,
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-by_url': _('Package URL')
            }),
        help_text=_('An external http/https URL to load the package from.'))
    repo_name = horizon_forms.CharField(
        label=_("Package Name"),
        required=False,
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-by_name': _('Package Name')
            }),
        help_text=_(
            'Package name in the repository, usually a fully qualified name'),
    )
    package = forms.FileField(
        label=_('Application Package'),
        required=False,
        widget=forms.FileInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-upload': _('Application Package')
            }),
        help_text=_('A local zip file to upload'))
    repo_version = horizon_forms.CharField(
        label=_("Package version"),
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-by_name': _('Package version')
            }),
        required=False)

    def __init__(self, *args, **kwargs):
        super(ImportPackageForm, self).__init__(*args, **kwargs)
        self.fields['repo_version'].widget.attrs['placeholder'] = \
            _('Optional')

    def clean_package(self):
        package = self.cleaned_data.get('package')
        if package:
            max_size_in_bytes = consts.MAX_FILE_SIZE_MB << 20
            if package.size > max_size_in_bytes:
                msg = _('It is forbidden to upload files larger than '
                        '{0} MB.').format(consts.MAX_FILE_SIZE_MB)
                LOG.error(msg)
                raise forms.ValidationError(msg)
        return package

    def clean(self):
        cleaned_data = super(ImportPackageForm, self).clean()
        import_type = cleaned_data.get('import_type')
        if import_type == 'upload' and not cleaned_data.get('package'):
            msg = _('Please supply a package file')
            LOG.error(msg)
            raise forms.ValidationError(msg)
        elif import_type == 'by_name' and not cleaned_data.get('repo_name'):
            msg = _('Please supply a package name')
            LOG.error(msg)
            raise forms.ValidationError(msg)
        elif import_type == 'by_url' and not cleaned_data.get('url'):
            msg = _('Please supply a package url')
            LOG.error(msg)
            raise forms.ValidationError(msg)
        return cleaned_data
Example #4
0
class RetypeForm(forms.SelfHandlingForm):
    name = forms.CharField(label=_('Volume Name'),
                           widget=forms.TextInput(
                               attrs={'readonly': 'readonly'}))
    volume_type = forms.ThemableChoiceField(label=_('Type'))
    MIGRATION_POLICY_CHOICES = [('never', _('Never')),
                                ('on-demand', _('On Demand'))]
    migration_policy = forms.ChoiceField(label=_('Migration Policy'),
                                         widget=forms.ThemableSelectWidget(),
                                         choices=MIGRATION_POLICY_CHOICES,
                                         initial='never',
                                         required=False)

    def __init__(self, request, *args, **kwargs):
        super().__init__(request, *args, **kwargs)

        try:
            volume_types = cinder.volume_type_list(request)
        except Exception:
            redirect_url = reverse("horizon:project:volumes:index")
            error_message = _('Unable to retrieve the volume type list.')
            exceptions.handle(request, error_message, redirect=redirect_url)

        origin_type = self.initial['volume_type']
        type_list = [(t.name,
                      _("%s (current)") % t.name
                      if origin_type == t.name else t.name)
                     for t in volume_types]

        if not type_list:
            type_list.insert(0, ("", _("No other volume types available")))
        self.fields['volume_type'].choices = sorted(type_list)

    def clean_volume_type(self):
        volume_type = self.cleaned_data.get("volume_type")
        if self.initial['volume_type'] == volume_type:
            msg = _('The new type must be different from the '
                    'current volume type.')
            raise forms.ValidationError(msg)
        return volume_type

    def handle(self, request, data):
        volume_id = self.initial['id']

        try:
            cinder.volume_retype(request,
                                 volume_id,
                                 data['volume_type'],
                                 data['migration_policy'])

            message = _(
                'Successfully sent the request to change the volume '
                'type to "%(vtype)s" for volume: "%(name)s"')
            params = {'name': data['name'],
                      'vtype': data['volume_type']}
            messages.info(request, message % params)

            return True
        except Exception:
            redirect = reverse("horizon:project:volumes:index")
            error_message = _(
                'Unable to change the volume type for volume: "%s"') \
                % data['name']
            exceptions.handle(request, error_message, redirect=redirect)
Example #5
0
class CreateNetwork(forms.SelfHandlingForm):
    name = forms.CharField(max_length=255, label=_("Name"), required=False)
    tenant_id = forms.ThemableChoiceField(label=_("Project"))
    if api.neutron.is_port_profiles_supported():
        widget = None
    else:
        widget = forms.HiddenInput()
    net_profile_id = forms.ChoiceField(label=_("Network Profile"),
                                       required=False,
                                       widget=widget)
    network_type = forms.ChoiceField(
        label=_("Provider Network Type"),
        help_text=_("The physical mechanism by which the virtual "
                    "network is implemented."),
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'network_type'
        }))
    physical_network = forms.CharField(
        max_length=255,
        label=_("Physical Network"),
        help_text=_("The name of the physical network over which the "
                    "virtual network is implemented."),
        initial='default',
        widget=forms.TextInput(attrs={
            'class': 'switched',
            'data-switch-on': 'network_type',
        }))
    segmentation_id = forms.IntegerField(
        label=_("Segmentation ID"),
        widget=forms.TextInput(attrs={
            'class': 'switched',
            'data-switch-on': 'network_type',
        }))
    admin_state = forms.BooleanField(label=_("Enable Admin State"),
                                     initial=True,
                                     required=False)
    shared = forms.BooleanField(label=_("Shared"),
                                initial=False,
                                required=False)
    external = forms.BooleanField(label=_("External Network"),
                                  initial=False,
                                  required=False)

    @classmethod
    def _instantiate(cls, request, *args, **kwargs):
        return cls(request, *args, **kwargs)

    def __init__(self, request, *args, **kwargs):
        super(CreateNetwork, self).__init__(request, *args, **kwargs)
        tenant_choices = [('', _("Select a project"))]
        tenants, has_more = api.keystone.tenant_list(request)
        for tenant in tenants:
            if tenant.enabled:
                tenant_choices.append((tenant.id, tenant.name))
        self.fields['tenant_id'].choices = tenant_choices

        if api.neutron.is_port_profiles_supported():
            self.fields['net_profile_id'].choices = (
                self.get_network_profile_choices(request))
        try:
            is_extension_supported = \
                api.neutron.is_extension_supported(request, 'provider')
        except Exception:
            msg = _("Unable to verify Neutron service providers")
            exceptions.handle(self.request, msg)
            self._hide_provider_network_type()
            is_extension_supported = False

        if is_extension_supported:
            neutron_settings = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
                                       {})
            self.seg_id_range = SEGMENTATION_ID_RANGE.copy()
            seg_id_range = neutron_settings.get('segmentation_id_range')
            if seg_id_range:
                self.seg_id_range.update(seg_id_range)

            self.provider_types = PROVIDER_TYPES.copy()
            extra_provider_types = neutron_settings.get('extra_provider_types')
            if extra_provider_types:
                self.provider_types.update(extra_provider_types)

            self.nettypes_with_seg_id = [
                net_type for net_type in self.provider_types
                if self.provider_types[net_type]['require_segmentation_id']
            ]
            self.nettypes_with_physnet = [
                net_type for net_type in self.provider_types
                if self.provider_types[net_type]['require_physical_network']
            ]

            supported_provider_types = neutron_settings.get(
                'supported_provider_types', DEFAULT_PROVIDER_TYPES)
            if supported_provider_types == ['*']:
                supported_provider_types = DEFAULT_PROVIDER_TYPES

            undefined_provider_types = [
                net_type for net_type in supported_provider_types
                if net_type not in self.provider_types
            ]
            if undefined_provider_types:
                LOG.error('Undefined provider network types are found: %s',
                          undefined_provider_types)

            seg_id_help = [
                _("For %(type)s networks, valid IDs are %(min)s to %(max)s.") %
                {
                    'type': net_type,
                    'min': self.seg_id_range[net_type][0],
                    'max': self.seg_id_range[net_type][1]
                } for net_type in self.nettypes_with_seg_id
            ]
            self.fields['segmentation_id'].help_text = ' '.join(seg_id_help)

            # Register network types which require segmentation ID
            attrs = dict(
                ('data-network_type-%s' % network_type, _('Segmentation ID'))
                for network_type in self.nettypes_with_seg_id)
            self.fields['segmentation_id'].widget.attrs.update(attrs)

            # Register network types which require physical network
            attrs = dict(
                ('data-network_type-%s' % network_type, _('Physical Network'))
                for network_type in self.nettypes_with_physnet)
            self.fields['physical_network'].widget.attrs.update(attrs)

            network_type_choices = [
                (net_type, self.provider_types[net_type]['display_name'])
                for net_type in supported_provider_types
            ]
            if len(network_type_choices) == 0:
                self._hide_provider_network_type()
            else:
                self.fields['network_type'].choices = network_type_choices

    def get_network_profile_choices(self, request):
        profile_choices = [('', _("Select a profile"))]
        for profile in self._get_profiles(request, 'network'):
            profile_choices.append((profile.id, profile.name))
        return profile_choices

    def _get_profiles(self, request, type_p):
        profiles = []
        try:
            profiles = api.neutron.profile_list(request, type_p)
        except Exception:
            msg = _('Network Profiles could not be retrieved.')
            exceptions.handle(request, msg)
        return profiles

    def _hide_provider_network_type(self):
        self.fields['network_type'].widget = forms.HiddenInput()
        self.fields['physical_network'].widget = forms.HiddenInput()
        self.fields['segmentation_id'].widget = forms.HiddenInput()
        self.fields['network_type'].required = False
        self.fields['physical_network'].required = False
        self.fields['segmentation_id'].required = False

    def handle(self, request, data):
        try:
            params = {
                'name': data['name'],
                'tenant_id': data['tenant_id'],
                'admin_state_up': data['admin_state'],
                'shared': data['shared'],
                'router:external': data['external']
            }
            if api.neutron.is_port_profiles_supported():
                params['net_profile_id'] = data['net_profile_id']
            if api.neutron.is_extension_supported(request, 'provider'):
                network_type = data['network_type']
                params['provider:network_type'] = network_type
                if network_type in self.nettypes_with_physnet:
                    params['provider:physical_network'] = (
                        data['physical_network'])
                if network_type in self.nettypes_with_seg_id:
                    params['provider:segmentation_id'] = (
                        data['segmentation_id'])
            network = api.neutron.network_create(request, **params)
            msg = _('Network %s was successfully created.') % data['name']
            LOG.debug(msg)
            messages.success(request, msg)
            return network
        except Exception:
            redirect = reverse('horizon:network:networks:index')
            msg = _('Failed to create network %s') % data['name']
            exceptions.handle(request, msg, redirect=redirect)

    def clean(self):
        cleaned_data = super(CreateNetwork, self).clean()
        if api.neutron.is_extension_supported(self.request, 'provider'):
            self._clean_physical_network(cleaned_data)
            self._clean_segmentation_id(cleaned_data)
        return cleaned_data

    def _clean_physical_network(self, data):
        network_type = data.get('network_type')
        if ('physical_network' in self._errors
                and network_type not in self.nettypes_with_physnet):
            # In this case the physical network is not required, so we can
            # ignore any errors.
            del self._errors['physical_network']

    def _clean_segmentation_id(self, data):
        network_type = data.get('network_type')
        if 'segmentation_id' in self._errors:
            if (network_type not in self.nettypes_with_seg_id
                    and not self.data.get("segmentation_id")):
                # In this case the segmentation ID is not required, so we can
                # ignore the field is required error.
                del self._errors['segmentation_id']
        elif network_type in self.nettypes_with_seg_id:
            seg_id = data.get('segmentation_id')
            seg_id_range = {
                'min': self.seg_id_range[network_type][0],
                'max': self.seg_id_range[network_type][1]
            }
            if seg_id < seg_id_range['min'] or seg_id > seg_id_range['max']:
                msg = (_('For a %(network_type)s network, valid segmentation '
                         'IDs are %(min)s through %(max)s.') % {
                             'network_type': network_type,
                             'min': seg_id_range['min'],
                             'max': seg_id_range['max']
                         })
                self._errors['segmentation_id'] = self.error_class([msg])
Example #6
0
    def __init__(self, request, *args, **kwargs):
        super(CreateNetwork, self).__init__(request, *args, **kwargs)
        tenant_choices = [('', _("Select a project"))]
        tenants, has_more = api.keystone.tenant_list(request)
        for tenant in tenants:
            if tenant.enabled:
                tenant_choices.append((tenant.id, tenant.name))
        self.fields['tenant_id'].choices = tenant_choices

        try:
            is_extension_supported = \
                api.neutron.is_extension_supported(request, 'provider')
        except Exception:
            msg = _("Unable to verify Neutron service providers")
            exceptions.handle(self.request, msg)
            self._hide_provider_network_type()
            is_extension_supported = False

        if is_extension_supported:
            neutron_settings = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
                                       {})
            self.seg_id_range = SEGMENTATION_ID_RANGE.copy()
            seg_id_range = neutron_settings.get('segmentation_id_range')
            if seg_id_range:
                self.seg_id_range.update(seg_id_range)

            self.provider_types = PROVIDER_TYPES.copy()
            extra_provider_types = neutron_settings.get('extra_provider_types')
            if extra_provider_types:
                self.provider_types.update(extra_provider_types)

            self.nettypes_with_seg_id = [
                net_type for net_type in self.provider_types
                if self.provider_types[net_type]['require_segmentation_id']
            ]
            self.nettypes_with_physnet = [
                net_type for net_type in self.provider_types
                if self.provider_types[net_type]['require_physical_network']
            ]

            supported_provider_types = neutron_settings.get(
                'supported_provider_types', DEFAULT_PROVIDER_TYPES)
            if supported_provider_types == ['*']:
                supported_provider_types = DEFAULT_PROVIDER_TYPES

            undefined_provider_types = [
                net_type for net_type in supported_provider_types
                if net_type not in self.provider_types
            ]
            if undefined_provider_types:
                LOG.error('Undefined provider network types are found: %s',
                          undefined_provider_types)

            seg_id_help = [
                _("For %(type)s networks, valid IDs are %(min)s to %(max)s.") %
                {
                    'type': net_type,
                    'min': self.seg_id_range[net_type][0],
                    'max': self.seg_id_range[net_type][1]
                } for net_type in self.nettypes_with_seg_id
            ]
            self.fields['segmentation_id'].help_text = ' '.join(seg_id_help)

            # Register network types which require segmentation ID
            attrs = dict(
                ('data-network_type-%s' % network_type, _('Segmentation ID'))
                for network_type in self.nettypes_with_seg_id)
            self.fields['segmentation_id'].widget.attrs.update(attrs)

            physical_networks = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
                                        {}).get('physical_networks', [])

            if physical_networks:
                self.fields['physical_network'] = forms.ThemableChoiceField(
                    label=_("Physical Network"),
                    choices=[(net, net) for net in physical_networks],
                    widget=forms.ThemableSelectWidget(
                        attrs={
                            'class': 'switched',
                            'data-switch-on': 'network_type',
                        }),
                    help_text=_("The name of the physical network over "
                                "which the virtual network is implemented."),
                )

            # Register network types which require physical network
            attrs = dict(
                ('data-network_type-%s' % network_type, _('Physical Network'))
                for network_type in self.nettypes_with_physnet)
            self.fields['physical_network'].widget.attrs.update(attrs)

            network_type_choices = [
                (net_type, self.provider_types[net_type]['display_name'])
                for net_type in supported_provider_types
            ]
            if len(network_type_choices) == 0:
                self._hide_provider_network_type()
            else:
                self.fields['network_type'].choices = network_type_choices
Example #7
0
class AddVipAction(workflows.Action):
    name = forms.CharField(max_length=80, label=_("Name"))
    description = forms.CharField(initial="",
                                  required=False,
                                  max_length=80,
                                  label=_("Description"))
    subnet_id = forms.ThemableChoiceField(label=_("VIP Subnet"),
                                          initial="",
                                          required=False)
    address = forms.IPField(label=_("IP address"),
                            version=forms.IPv4 | forms.IPv6,
                            mask=False,
                            required=False)
    protocol_port = forms.IntegerField(
        label=_("Protocol Port"),
        min_value=1,
        help_text=_("Enter an integer value "
                    "between 1 and 65535."),
        validators=[validators.validate_port_range])
    protocol = forms.ThemableChoiceField(label=_("Protocol"))
    session_persistence = forms.ChoiceField(
        required=False,
        initial={},
        label=_("Session Persistence"),
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'persistence'
        }))
    cookie_name = forms.CharField(
        initial="",
        required=False,
        max_length=80,
        label=_("Cookie Name"),
        help_text=_("Required for APP_COOKIE persistence;"
                    " Ignored otherwise."),
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'persistence',
                'data-persistence-app_cookie': 'APP_COOKIE',
            }))
    connection_limit = forms.IntegerField(
        required=False,
        min_value=-1,
        label=_("Connection Limit"),
        help_text=_("Maximum number of connections allowed "
                    "for the VIP or '-1' if the limit is not set"))
    admin_state_up = forms.ThemableChoiceField(choices=[(True, _('UP')),
                                                        (False, _('DOWN'))],
                                               label=_("Admin State"))

    def __init__(self, request, *args, **kwargs):
        super(AddVipAction, self).__init__(request, *args, **kwargs)
        tenant_id = request.user.tenant_id
        subnet_id_choices = [('', _("Select a Subnet"))]
        try:
            networks = api.neutron.network_list_for_tenant(request, tenant_id)
        except Exception:
            exceptions.handle(request, _('Unable to retrieve networks list.'))
            networks = []
        for n in networks:
            for s in n['subnets']:
                name = "%s (%s)" % (s.name, s.cidr)
                subnet_id_choices.append((s.id, name))
        self.fields['subnet_id'].choices = subnet_id_choices
        protocol_choices = [('', _("Select a Protocol"))]
        [protocol_choices.append((p, p)) for p in AVAILABLE_PROTOCOLS]
        self.fields['protocol'].choices = protocol_choices

        session_persistence_choices = [('', _("No Session Persistence"))]
        for mode in ('SOURCE_IP', 'HTTP_COOKIE', 'APP_COOKIE'):
            session_persistence_choices.append((mode.lower(), mode))
        self.fields[
            'session_persistence'].choices = session_persistence_choices

    def clean(self):
        cleaned_data = super(AddVipAction, self).clean()
        persistence = cleaned_data.get('session_persistence')
        if persistence:
            cleaned_data['session_persistence'] = persistence.upper()
        if (cleaned_data.get('session_persistence') == 'APP_COOKIE'
                and not cleaned_data.get('cookie_name')):
            msg = _('Cookie name is required for APP_COOKIE persistence.')
            self._errors['cookie_name'] = self.error_class([msg])
        return cleaned_data

    class Meta(object):
        name = _("Specify VIP")
        permissions = ('openstack.services.network', )
        help_text_template = 'project/loadbalancers/_create_vip_help.html'
Example #8
0
class AddGroupInfoAction(workflows.Action):
    name = forms.CharField(label=_("Name"), max_length=255)
    description = forms.CharField(
        widget=forms.widgets.Textarea(attrs={'rows': 4}),
        label=_("Description"),
        required=False)
    group_type = forms.ChoiceField(label=_("Group Type"),
                                   widget=forms.ThemableSelectWidget())
    availability_zone = forms.ChoiceField(
        label=_("Availability Zone"),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-no_source_type': _('Availability Zone'),
                'data-source-image_source': _('Availability Zone')
            }))

    def __init__(self, request, *args, **kwargs):
        super(AddGroupInfoAction, self).__init__(request, *args, **kwargs)
        self.fields['availability_zone'].choices = \
            availability_zones(request)
        try:
            # Group type name 'default_cgsnapshot_type' is reserved for
            # consistency group and it cannot be used for a group type.
            # Let's exclude it.
            group_types = [(t.id, t.name)
                           for t in api.cinder.group_type_list(request)
                           if t.name != 'default_cgsnapshot_type']
        except Exception:
            exceptions.handle(request, _('Unable to retrieve group types.'))
        if group_types:
            group_types.insert(0, ("", _("Select group type")))
        else:
            group_types.insert(0, ("", _("No valid group type")))
        self.fields['group_type'].choices = group_types

    class Meta(object):
        name = _("Group Information")
        help_text = _("Volume groups provide a mechanism for "
                      "creating snapshots of multiple volumes at the same "
                      "point-in-time to ensure data consistency\n\n"
                      "A volume group can support more than one volume "
                      "type, but it can only contain volumes hosted by the "
                      "same back end.")
        slug = "set_group_info"

    def clean(self):
        cleaned_data = super(AddGroupInfoAction, self).clean()
        name = cleaned_data.get('name')

        try:
            groups = cinder.group_list(self.request)
        except Exception:
            msg = _('Unable to get group list')
            exceptions.check_message(["Connection", "refused"], msg)
            raise

        if groups is not None and name is not None:
            for group in groups:
                if group.name.lower() == name.lower():
                    # ensure new name has reasonable length
                    formatted_name = name
                    if len(name) > 20:
                        formatted_name = name[:14] + "..." + name[-3:]
                    raise forms.ValidationError(
                        _('The name "%s" is already used by '
                          'another group.') % formatted_name)

        return cleaned_data
Example #9
0
class AddRuleAction(workflows.Action):
    name = forms.CharField(max_length=80, label=_("Name"), required=False)
    description = forms.CharField(max_length=80,
                                  label=_("Description"),
                                  required=False)
    protocol = forms.ThemableChoiceField(
        label=_("Protocol"),
        choices=[('tcp', _('TCP')), ('udp', _('UDP')), ('icmp', _('ICMP')),
                 ('any', _('ANY'))],
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'protocol',
        }))
    action = forms.ThemableChoiceField(
        label=_("Action"),
        choices=[('allow', _('ALLOW')), ('deny', _('DENY')),
                 ('reject', _('REJECT'))],
    )
    source_ip_address = forms.IPField(label=_("Source IP Address/Subnet"),
                                      version=forms.IPv4 | forms.IPv6,
                                      required=False,
                                      mask=True)
    destination_ip_address = forms.IPField(
        label=_("Destination IP Address/Subnet"),
        version=forms.IPv4 | forms.IPv6,
        required=False,
        mask=True)
    source_port = forms.CharField(
        max_length=80,
        label=_("Source Port/Port Range"),
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'protocol',
                'data-protocol-tcp': _("Source Port/Port Range"),
                'data-protocol-udp': _("Source Port/Port Range"),
            }),
        required=False,
        validators=[port_validator])
    destination_port = forms.CharField(
        max_length=80,
        label=_("Destination Port/Port Range"),
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'protocol',
                'data-protocol-tcp': _("Destination Port/Port Range"),
                'data-protocol-udp': _("Destination Port/Port Range"),
            }),
        required=False,
        validators=[port_validator])
    ip_version = forms.ThemableChoiceField(label=_("IP Version"),
                                           required=False,
                                           choices=[('4', '4'), ('6', '6')])
    shared = forms.BooleanField(label=_("Shared"),
                                initial=False,
                                required=False)
    enabled = forms.BooleanField(label=_("Enabled"),
                                 initial=True,
                                 required=False)

    def __init__(self, request, *args, **kwargs):
        super(AddRuleAction, self).__init__(request, *args, **kwargs)
        # Only admin user can update the 'shared' attribute
        self.ignore_shared = False
        if not policy.check(
            (("neutron-fwaas", "create_firewall_rule:shared"), ), request):
            self.fields['shared'].widget = forms.CheckboxInput(
                attrs={
                    'readonly': 'readonly',
                    'disabled': 'disabled'
                })
            self.fields['shared'].help_text = _(
                'Non admin users are not allowed to set the shared property '
                'of the rule.')
            self.ignore_shared = True

    def _check_ip_addr_and_ip_version(self, cleaned_data):
        ip_version = int(str(cleaned_data.get('ip_version')))
        src_ip = cleaned_data.get('source_ip_address')
        dst_ip = cleaned_data.get('destination_ip_address')
        msg = _('Source/Destination Network Address and IP version '
                'are inconsistent. Please make them consistent.')
        if (src_ip and netaddr.IPNetwork(src_ip).version != ip_version):
            self._errors['ip_version'] = self.error_class([msg])

        elif (dst_ip and netaddr.IPNetwork(dst_ip).version != ip_version):
            self._errors['ip_version'] = self.error_class([msg])

    def clean(self):
        cleaned_data = super(AddRuleAction, self).clean()
        self._check_ip_addr_and_ip_version(cleaned_data)

    class Meta(object):
        name = _("Rule")
        permissions = ('openstack.services.network', )
        help_text = _("Create a firewall rule.\n\n"
                      "A firewall rule is an association of the following "
                      "attributes:\n\n"
                      "<li>IP Addresses: The addresses from/to which the "
                      "traffic filtration needs to be applied.</li>"
                      "<li>IP Version: The type of IP packets (IP V4/V6) "
                      "that needs to be filtered.</li>"
                      "<li>Protocol: Type of packets (UDP, ICMP, TCP, Any) "
                      "that needs to be checked.</li>"
                      "<li>Action: Action is the type of filtration "
                      "required, it can be Reject/Deny/Allow data "
                      "packets.</li>\n"
                      "The protocol and action fields are required, all "
                      "others are optional.")
Example #10
0
class SetInstanceDetailsAction(workflows.Action):
    availability_zone = forms.ThemableChoiceField(label=_("Availability Zone"),
                                                  required=False)

    name = forms.CharField(label=_("Instance Name"), max_length=255)

    flavor = forms.ThemableChoiceField(label=_("Flavor"),
                                       help_text=_("Size of image to launch."))

    count = forms.IntegerField(label=_("Number of Instances"),
                               min_value=1,
                               initial=1)

    source_type = forms.ThemableChoiceField(label=_("Instance Boot Source"),
                                            initial='image_id',
                                            help_text=_(
                                                "Choose Your Boot Source "
                                                "Type."),
                                            disabled=True)

    image_id = forms.ChoiceField(
        label=_("Image Name"),
        required=False,
        widget=forms.ThemableSelectWidget(data_attrs=('volume_size', ),
                                          transform=lambda x:
                                          ("%s (%s)" %
                                           (x.name, filesizeformat(x.bytes)))))

    class Meta(object):
        name = _("Details")
        help_text_template = ("project/order/" "_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")),
        ]
        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,
                                            targets=(
                                                'instances',
                                                'cores',
                                                'ram',
                                            ))
        # available_count = usages['instances']['available']
        # if available_count < count:
        #     msg = (_('The requested instance(s) cannot be launched '
        #              'as your quota will be exceeded: Available: '
        #              '%(avail)s, Requested: %(req)s.')
        #            % {'avail': available_count, 'req': count})
        #     raise forms.ValidationError(msg)

        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(flavor, fprop) > 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_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(self, cleaned_data):
        # Validate our instance source.
        source_type = self.data.get('source_type', None)
        source_check_methods = {
            'image_id': self._check_source_image,
        }
        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:
            data = api.order.order_hypervisor(self.request,
                                              self.context['start_time'],
                                              self.context['stop_time'])
            total_restouce = data.get('total_resource', None)
            free_resource = data.get('free_resource', None)
            extra['usages'] = {
                'disk': {
                    'available':
                    free_resource.get('disk', None),
                    'used':
                    total_restouce.get('disk', None) -
                    free_resource.get('disk', None),
                    'quota':
                    total_restouce.get('disk', None),
                },
                'cores': {
                    'available':
                    free_resource.get('vcpus', None),
                    'used':
                    total_restouce.get('vcpus', None) -
                    free_resource.get('vcpus', None),
                    'quota':
                    total_restouce.get('vcpus', None),
                },
                'ram': {
                    'available':
                    free_resource.get('ram', None),
                    'used':
                    total_restouce.get('ram', None) -
                    free_resource.get('ram', None),
                    'quota':
                    total_restouce.get('ram', None),
                }
            }
            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 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:
            if image_tables.get_image_type(image) != "snapshot":
                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
Example #11
0
class AttachInterface(forms.SelfHandlingForm):
    instance_id = forms.CharField(widget=forms.HiddenInput())
    specification_method = forms.ThemableChoiceField(
        label=_("The way to specify an interface"),
        initial=False,
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'specification_method',
        }))
    port = forms.ThemableChoiceField(
        label=_("Port"),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-switch-on': 'specification_method',
                'data-specification_method-port': _('Port'),
            }))
    network = forms.ThemableChoiceField(
        label=_("Network"),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-switch-on': 'specification_method',
                'data-specification_method-network': _('Network'),
            }))
    fixed_ip = forms.IPField(
        label=_("Fixed IP Address"),
        required=False,
        help_text=_("IP address for the new port"),
        version=forms.IPv4 | forms.IPv6,
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'specification_method',
                'data-specification_method-network': _('Fixed IP Address'),
            }))

    def __init__(self, request, *args, **kwargs):
        super(AttachInterface, self).__init__(request, *args, **kwargs)
        networks = instance_utils.network_field_data(request,
                                                     include_empty_option=True,
                                                     with_cidr=True)
        self.fields['network'].choices = networks

        choices = [('network', _("by Network (and IP address)"))]
        ports = instance_utils.port_field_data(request, with_network=True)
        if len(ports) > 0:
            self.fields['port'].choices = ports
            choices.append(('port', _("by Port")))

        self.fields['specification_method'].choices = choices

    def clean_network(self):
        specification_method = self.cleaned_data.get('specification_method')
        network = self.cleaned_data.get('network')
        if specification_method == 'network' and not network:
            msg = _('This field is required.')
            self._errors['network'] = self.error_class([msg])
        return network

    def handle(self, request, data):
        instance_id = data['instance_id']
        try:
            net_id = port_id = fixed_ip = None
            if data['specification_method'] == 'port':
                port_id = data.get('port')
            else:
                net_id = data.get('network')
                if data.get('fixed_ip'):
                    fixed_ip = data.get('fixed_ip')
            api.nova.interface_attach(request,
                                      instance_id,
                                      net_id=net_id,
                                      fixed_ip=fixed_ip,
                                      port_id=port_id)
            msg = _('Attaching interface for instance %s.') % instance_id
            messages.success(request, msg)
        except Exception:
            redirect = reverse('horizon:project:instances:index')
            exceptions.handle(request,
                              _("Unable to attach interface."),
                              redirect=redirect)
        return True
Example #12
0
class ImportX509Certificate(forms.SelfHandlingForm):
    name = forms.RegexField(required=False,
                            max_length=255,
                            label=_("Certificate Name"),
                            regex=NAME_REGEX,
                            error_messages=ERROR_MESSAGES)
    source_type = forms.ChoiceField(
        label=_('Source'),
        required=False,
        choices=[('file', _('Import File')), ('raw', _('Direct Input'))],
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'source'
        }))
    cert_file = forms.FileField(
        label=_("Choose file"),
        widget=forms.FileInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-file': _('PEM Certificate File')
            }),
        required=False)
    direct_input = forms.CharField(
        label=_('PEM Certificate'),
        widget=forms.widgets.Textarea(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-raw': _('PEM Certificate')
            }),
        required=False)

    def clean(self):
        data = super(ImportX509Certificate, self).clean()

        # The cert can be missing based on particular upload
        # conditions. Code defensively for it here...
        cert_file = data.get('cert_file', None)
        cert_raw = data.get('direct_input', None)

        if cert_raw and cert_file:
            raise forms.ValidationError(
                _("Cannot specify both file and direct input."))
        if not cert_raw and not cert_file:
            raise forms.ValidationError(
                _("No input was provided for the certificate value."))
        try:
            if cert_file:
                cert_pem = self.files['cert_file'].read()
            else:
                cert_pem = str(data['direct_input'])
            cert_obj = load_pem_x509_certificate(cert_pem.encode('utf-8'),
                                                 default_backend())
            cert_der = cert_obj.public_bytes(Encoding.DER)
        except Exception as e:
            msg = _('There was a problem loading the certificate: %s. '
                    'Is the certificate valid and in PEM format?') % e
            raise forms.ValidationError(msg)

        data['cert_data'] = base64.b64encode(cert_der).decode('utf-8')

        return data

    def handle(self, request, data):
        try:
            cert_pem = data.get('cert_data')
            cert_uuid = client.import_object(request,
                                             data=cert_pem,
                                             name=data['name'],
                                             object_type=x_509.X509)

            if data['name']:
                identifier = data['name']
            else:
                identifier = cert_uuid
            messages.success(
                request,
                _('Successfully imported certificate: %s') % identifier)
            return cert_uuid
        except Exception as e:
            msg = _('Unable to import certificate: %s')
            messages.error(request, msg % e)
            exceptions.handle(request, ignore=True)
            self.api_error(_('Unable to import certificate.'))
            return False
Example #13
0
class CreatePortInfoAction(workflows.Action):
    name = forms.CharField(max_length=255, label=_("Name"), required=False)
    admin_state = forms.BooleanField(label=_("Enable Admin State"),
                                     initial=True,
                                     required=False)
    device_id = forms.CharField(max_length=100,
                                label=_("Device ID"),
                                help_text=_("Device ID attached to the port"),
                                required=False)
    device_owner = forms.CharField(
        max_length=100,
        label=_("Device Owner"),
        help_text=_("Owner of the device attached to the port"),
        required=False)
    specify_ip = forms.ThemableChoiceField(
        label=_("Specify IP address or subnet"),
        help_text=_("To specify a subnet or a fixed IP, select any options."),
        required=False,
        choices=[('', _("Unspecified")), ('subnet_id', _("Subnet")),
                 ('fixed_ip', _("Fixed IP Address"))],
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'specify_ip',
        }))
    subnet_id = forms.ThemableChoiceField(
        label=_("Subnet"),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-required-when-shown': 'true',
                'data-switch-on': 'specify_ip',
                'data-specify_ip-subnet_id': _('Subnet'),
            }))
    fixed_ip = forms.IPField(
        label=_("Fixed IP Address"),
        required=False,
        help_text=_("Specify the subnet IP address for the new port"),
        version=forms.IPv4 | forms.IPv6,
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-required-when-shown': 'true',
                'data-switch-on': 'specify_ip',
                'data-specify_ip-fixed_ip': _('Fixed IP Address'),
            }))
    mac_address = forms.MACAddressField(
        label=_("MAC Address"),
        required=False,
        help_text=_("Specify the MAC address for the new port"))
    mac_state = forms.BooleanField(label=_("MAC Learning State"),
                                   initial=False,
                                   required=False)
    port_security_enabled = forms.BooleanField(
        label=_("Port Security"),
        help_text=_("Enable anti-spoofing rules for the port"),
        initial=True,
        required=False,
        widget=forms.CheckboxInput(
            attrs={
                'class': 'switchable',
                'data-slug': 'port_security_enabled',
                'data-hide-tab': 'create_port__create_security_groups',
                'data-hide-on-checked': 'false'
            }))
    binding__vnic_type = forms.ThemableChoiceField(
        label=_("VNIC Type"),
        help_text=_("The VNIC type that is bound to the network port"),
        required=False)

    def __init__(self, request, context, *args, **kwargs):
        super(CreatePortInfoAction, self).__init__(request, context, *args,
                                                   **kwargs)

        # prepare subnet choices and input area for each subnet
        subnet_choices = self._get_subnet_choices(context)
        if subnet_choices:
            subnet_choices.insert(0, ('', _("Select a subnet")))
            self.fields['subnet_id'].choices = subnet_choices
        else:
            self.fields['specify_ip'].widget = forms.HiddenInput()
            self.fields['subnet_id'].widget = forms.HiddenInput()
            self.fields['fixed_ip'].widget = forms.HiddenInput()

        self._hide_field_if_not_supported(
            request, 'mac_state', 'mac-learning',
            _("Unable to retrieve MAC learning state"))
        self._hide_field_if_not_supported(
            request, 'port_security_enabled', 'port-security',
            _("Unable to retrieve port security state"))

        self._populate_vnic_type_choices(request)

    def _hide_field_if_not_supported(self, request, field, extension_alias,
                                     failure_message):
        is_supproted = False
        try:
            is_supproted = api.neutron.is_extension_supported(
                request, extension_alias)
        except Exception:
            exceptions.handle(self.request, failure_message)
        if not is_supproted:
            del self.fields[field]
        return is_supproted

    def _populate_vnic_type_choices(self, request):
        neutron_settings = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
        supported_vnic_types = neutron_settings.get('supported_vnic_types',
                                                    ['*'])
        # When a list of VNIC types is empty, hide the corresponding field.
        if not supported_vnic_types:
            del self.fields['binding__vnic_type']
            return

        binding_supported = self._hide_field_if_not_supported(
            request, 'binding__vnic_type', 'binding',
            _("Unable to verify the VNIC types extension in Neutron"))
        if not binding_supported:
            # binding__vnic_type field is already deleted, so return here
            return

        if supported_vnic_types == ['*']:
            vnic_type_choices = api.neutron.VNIC_TYPES
        else:
            vnic_type_choices = [
                vnic_type for vnic_type in api.neutron.VNIC_TYPES
                if vnic_type[0] in supported_vnic_types
            ]
        self.fields['binding__vnic_type'].choices = vnic_type_choices

    def _get_subnet_choices(self, context):
        try:
            network_id = context['network_id']
            network = api.neutron.network_get(self.request, network_id)
        except Exception:
            return []

        # NOTE(amotoki): When a user cannot retrieve a subnet info,
        # subnet ID is stored in network.subnets field.
        # If so, we skip such subnet as subnet choices.
        # This happens usually for external networks.
        # TODO(amotoki): Ideally it is better to disable/hide
        # Create Port button in the port table, but as of Pike
        # the default neutron policy.json for "create_port" is empty
        # and there seems no appropriate policy. This is a dirty hack.
        return [(subnet.id, '%s %s' % (subnet.name_or_id, subnet.cidr))
                for subnet in network.subnets
                if isinstance(subnet, api.neutron.Subnet)]

    class Meta(object):
        name = _("Info")
        slug = 'create_info'
        help_text_template = 'project/networks/ports/_create_port_help.html'
Example #14
0
class UpdateIPSecPolicy(forms.SelfHandlingForm):
    name = forms.CharField(max_length=80, label=_("Name"), required=False)
    ipsecpolicy_id = forms.CharField(
        label=_("ID"), widget=forms.TextInput(attrs={'readonly': 'readonly'}))
    description = forms.CharField(required=False,
                                  max_length=80,
                                  label=_("Description"))
    # Currently this field has only one choice, so mark it as readonly.
    auth_algorithm = forms.ChoiceField(
        label=_("Authorization algorithm"),
        choices=[('sha1', _('sha1'))],
        widget=forms.TextInput(attrs={'readonly': 'readonly'}),
        required=False)
    encapsulation_mode = forms.ChoiceField(label=_("Encapsulation mode"),
                                           choices=[('tunnel', _('tunnel')),
                                                    ('transport',
                                                     _('transport'))],
                                           required=False)
    encryption_algorithm = forms.ChoiceField(label=_("Encryption algorithm"),
                                             choices=[
                                                 ('3des', _('3des')),
                                                 ('aes-128', _('aes-128')),
                                                 ('aes-192', _('aes-192')),
                                                 ('aes-256', _('aes-256'))
                                             ],
                                             required=False)
    # Currently this field has only one choice, so mark it as readonly.
    lifetime_units = forms.ChoiceField(
        label=_("Lifetime units"),
        choices=[('seconds', _('seconds'))],
        widget=forms.ThemableSelectWidget(attrs={'readonly': 'readonly'}),
        required=False)
    lifetime_value = forms.IntegerField(
        min_value=60,
        label=_("Lifetime value"),
        help_text=_("Equal to or greater than 60"),
        required=False)
    pfs = forms.ChoiceField(label=_("Perfect Forward Secrecy"),
                            choices=[('group2', _('group2')),
                                     ('group5', _('group5')),
                                     ('group14', _('group14'))],
                            required=False)
    transform_protocol = forms.ChoiceField(label=_("Transform Protocol"),
                                           choices=[('esp', _('esp')),
                                                    ('ah', _('ah')),
                                                    ('ah-esp', _('ah-esp'))],
                                           required=False)

    failure_url = 'horizon:project:vpn:index'

    def handle(self, request, context):
        try:
            data = {
                'ipsecpolicy': {
                    'name': context['name'],
                    'description': context['description'],
                    'auth_algorithm': context['auth_algorithm'],
                    'encapsulation_mode': context['encapsulation_mode'],
                    'encryption_algorithm': context['encryption_algorithm'],
                    'lifetime': {
                        'units': context['lifetime_units'],
                        'value': context['lifetime_value']
                    },
                    'pfs': context['pfs'],
                    'transform_protocol': context['transform_protocol'],
                }
            }
            ipsecpolicy = api.vpn.ipsecpolicy_update(request,
                                                     context['ipsecpolicy_id'],
                                                     **data)
            msg = (_('IPSec Policy %s was successfully updated.') %
                   context['name'])
            LOG.debug(msg)
            messages.success(request, msg)
            return ipsecpolicy
        except Exception as e:
            msg = _('Failed to update IPSec Policy %s') % context['name']
            LOG.info('%s: %s' % (msg, e))
            redirect = reverse(self.failure_url)
            exceptions.handle(request, msg, redirect=redirect)
Example #15
0
class SetResumeDetailAction(workflows.Action):
    image_id = forms.ChoiceField(
        label=_("Image Name"),
        required=True,
        widget=forms.ThemableSelectWidget(data_attrs=('volume_size', ),
                                          transform=lambda x:
                                          ("%s (%s)" %
                                           (x.name, filesizeformat(x.bytes)))))

    name = forms.CharField(max_length=80,
                           label=_("Instance Name"),
                           initial="resumed_vm")

    flavor = forms.ChoiceField(label=_("Flavor"),
                               required=True,
                               help_text=_("Size of image to launch."))

    class Meta:
        name = _("Base VM Info")
        help_text_template = ("project/cloudlet/instance/"
                              "_resume_details_help.html")

    def clean(self):
        cleaned_data = super(SetResumeDetailAction, self).clean()
        return cleaned_data

    def _get_available_images(self, request, context):
        if not hasattr(self, '_images_cache'):
            images_cache = {}

        if images_cache is None:
            images_cache = {}

        public_images = images_cache.get('public_images', [])
        images_by_project = images_cache.get('images_by_project', {})
        if 'public_images' not in images_cache:
            public = {"is_public": True, "status": "active"}

            try:
                images, _more, _prev = glance.image_list_detailed(
                    request, filters=public)
                [public_images.append(image) for image in images]
                images_cache['public_images'] = public_images
            except Exception:
                exceptions.handle(request,
                                  _("Unable to retrieve public images."))

        # Preempt if we don't have a project_id yet.
        project_id = context.get('project_id', None)
        if project_id is None:
            images_by_project[project_id] = []

        if project_id not in images_by_project:
            owner = {"property-owner_id": project_id, "status": "active"}
            try:
                owned_images, _more, _prev = glance.image_list_detailed(
                    request, filters=owner)
                images_by_project[project_id] = owned_images
            except Exception:
                owned_images = []
                exceptions.handle(
                    request,
                    _("Unable to retrieve images for "
                      "the current project."))
        else:
            owned_images = images_by_project[project_id]

        if 'images_by_project' not in images_cache:
            images_cache['images_by_project'] = images_by_project

        images = owned_images + public_images
        base_vms = list()
        for image in images:
            if hasattr(image, 'properties') == True:
                properties = getattr(image, 'properties')
                cloudlet_type = properties.get('cloudlet_type', None)
                if cloudlet_type == 'cloudlet_base_disk':
                    base_vms.append(image)

        image_ids = []
        final_images = []
        for image in base_vms:
            if image.id not in image_ids:
                image_ids.append(image.id)
                final_images.append(image)
        return [
            image for image in final_images
            if image.container_format not in ('aki', 'ari')
        ]

    def populate_image_id_choices(self, request, context):
        images = self._get_available_images(request, context)
        choices = [(image.id, image.name) for image in images
                   if image.properties.get("image_type", '') == "snapshot"]
        if choices:
            choices.insert(0, ("", _("Select Base VM")))
        else:
            choices.insert(0, ("", _("No Base VM is available.")))
        return choices

    def populate_flavor_choices(self, request, context):
        # return all flavors of Base VM image
        try:
            matching_flavors = set()
            flavors = api.nova.flavor_list(request)
            basevm_images = self._get_available_images(request, context)
            for basevm_image in basevm_images:
                if basevm_image.properties is None or \
                                len(basevm_image.properties) == 0:
                    continue
                libvirt_xml_str = basevm_image.properties.get(
                    'base_resource_xml_str', None)
                if libvirt_xml_str is None:
                    continue
                qemu_mem = utils.QemuMemory()
                cpu_count, memory_mb = qemu_mem.get_resource_size(
                    libvirt_xml_str)
                disk_gb = basevm_image.min_disk
                ret_flavors = utils.find_matching_flavor(
                    flavors, cpu_count, memory_mb, disk_gb)
                matching_flavors.update(ret_flavors)
            if len(matching_flavors) > 0:
                self.fields['flavor'].initial = list(matching_flavors)[0]
            else:
                self.fields['flavor'].initial = (0, "No valid flavor")
        except Exception as e:
            matching_flavors = set()
            exceptions.handle(request,
                              _('Unable to retrieve instance flavors.'))
        return sorted(list(matching_flavors))

    def get_help_text(self):
        extra = {}
        try:
            extra['usages'] = quotas.tenant_quota_usages(self.request)
            extra['usages_json'] = json.dumps(extra['usages'])
            flavors = json.dumps(
                [f._info for f in api.nova.flavor_list(self.request)])
            extra['flavors'] = flavors
        except Exception:
            exceptions.handle(self.request,
                              _("Unable to retrieve quota information."))
        return super(SetResumeDetailAction, self).get_help_text(extra)
Example #16
0
class RebuildInstanceForm(forms.SelfHandlingForm):
    instance_id = forms.CharField(widget=forms.HiddenInput())

    image = forms.ChoiceField(label=_("Select Image"),
                              widget=forms.ThemableSelectWidget(
                                  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.ThemableChoiceField(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.info(request, _('Rebuilding instance %s.') % instance)
        except Exception:
            redirect = reverse('horizon:project:instances:index')
            exceptions.handle(request,
                              _("Unable to rebuild instance."),
                              redirect=redirect)
        return True
Example #17
0
class CreateNetwork(forms.SelfHandlingForm):
    name = forms.CharField(max_length=255, label=_("Name"), required=False)
    tenant_id = forms.ThemableChoiceField(label=_("Project"))
    network_type = forms.ChoiceField(
        label=_("Provider Network Type"),
        help_text=_("The physical mechanism by which the virtual "
                    "network is implemented."),
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'network_type'
        }))
    physical_network = forms.CharField(
        max_length=255,
        label=_("Physical Network"),
        help_text=_("The name of the physical network over which the "
                    "virtual network is implemented. Specify one of the "
                    "physical networks defined in your neutron deployment."),
        widget=forms.TextInput(attrs={
            'class': 'switched',
            'data-switch-on': 'network_type',
        }))
    segmentation_id = forms.IntegerField(
        label=_("Segmentation ID"),
        widget=forms.TextInput(attrs={
            'class': 'switched',
            'data-switch-on': 'network_type',
        }))
    admin_state = forms.BooleanField(label=_("Enable Admin State"),
                                     initial=True,
                                     required=False)
    shared = forms.BooleanField(label=_("Shared"),
                                initial=False,
                                required=False)
    external = forms.BooleanField(label=_("External Network"),
                                  initial=False,
                                  required=False)
    with_subnet = forms.BooleanField(label=_("Create Subnet"),
                                     widget=forms.CheckboxInput(
                                         attrs={
                                             'class':
                                             'switchable',
                                             'data-slug':
                                             'with_subnet',
                                             'data-hide-tab':
                                             'create_network__'
                                             'createsubnetinfo'
                                             'action,'
                                             'create_network__'
                                             'createsubnetdetail'
                                             'action',
                                             'data-hide-on-checked':
                                             'false'
                                         }),
                                     initial=True,
                                     required=False)
    az_hints = forms.MultipleChoiceField(
        label=_("Availability Zone Hints"),
        required=False,
        help_text=_("Availability zones where the DHCP agents may be "
                    "scheduled. Leaving this unset is equivalent to "
                    "selecting all availability zones"))

    @classmethod
    def _instantiate(cls, request, *args, **kwargs):
        return cls(request, *args, **kwargs)

    def __init__(self, request, *args, **kwargs):
        super(CreateNetwork, self).__init__(request, *args, **kwargs)
        tenant_choices = [('', _("Select a project"))]
        tenants, has_more = api.keystone.tenant_list(request)
        for tenant in tenants:
            if tenant.enabled:
                tenant_choices.append((tenant.id, tenant.name))
        self.fields['tenant_id'].choices = tenant_choices

        try:
            is_extension_supported = \
                api.neutron.is_extension_supported(request, 'provider')
        except Exception:
            msg = _("Unable to verify Neutron service providers")
            exceptions.handle(self.request, msg)
            self._hide_provider_network_type()
            is_extension_supported = False

        if is_extension_supported:
            neutron_settings = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
                                       {})
            self.seg_id_range = SEGMENTATION_ID_RANGE.copy()
            seg_id_range = neutron_settings.get('segmentation_id_range')
            if seg_id_range:
                self.seg_id_range.update(seg_id_range)

            self.provider_types = PROVIDER_TYPES.copy()
            extra_provider_types = neutron_settings.get('extra_provider_types')
            if extra_provider_types:
                self.provider_types.update(extra_provider_types)

            self.nettypes_with_seg_id = [
                net_type for net_type in self.provider_types
                if self.provider_types[net_type]['require_segmentation_id']
            ]
            self.nettypes_with_physnet = [
                net_type for net_type in self.provider_types
                if self.provider_types[net_type]['require_physical_network']
            ]

            supported_provider_types = neutron_settings.get(
                'supported_provider_types', DEFAULT_PROVIDER_TYPES)
            if supported_provider_types == ['*']:
                supported_provider_types = DEFAULT_PROVIDER_TYPES

            undefined_provider_types = [
                net_type for net_type in supported_provider_types
                if net_type not in self.provider_types
            ]
            if undefined_provider_types:
                LOG.error('Undefined provider network types are found: %s',
                          undefined_provider_types)

            seg_id_help = [
                _("For %(type)s networks, valid IDs are %(min)s to %(max)s.") %
                {
                    'type': net_type,
                    'min': self.seg_id_range[net_type][0],
                    'max': self.seg_id_range[net_type][1]
                } for net_type in self.nettypes_with_seg_id
            ]
            self.fields['segmentation_id'].help_text = ' '.join(seg_id_help)

            # Register network types which require segmentation ID
            attrs = dict(
                ('data-network_type-%s' % network_type, _('Segmentation ID'))
                for network_type in self.nettypes_with_seg_id)
            self.fields['segmentation_id'].widget.attrs.update(attrs)

            physical_networks = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK',
                                        {}).get('physical_networks', [])

            if physical_networks:
                self.fields['physical_network'] = forms.ThemableChoiceField(
                    label=_("Physical Network"),
                    choices=[(net, net) for net in physical_networks],
                    widget=forms.ThemableSelectWidget(
                        attrs={
                            'class': 'switched',
                            'data-switch-on': 'network_type',
                        }),
                    help_text=_("The name of the physical network over "
                                "which the virtual network is implemented."),
                )

            # Register network types which require physical network
            attrs = dict(
                ('data-network_type-%s' % network_type, _('Physical Network'))
                for network_type in self.nettypes_with_physnet)
            self.fields['physical_network'].widget.attrs.update(attrs)

            network_type_choices = [
                (net_type, self.provider_types[net_type]['display_name'])
                for net_type in supported_provider_types
            ]
            if len(network_type_choices) == 0:
                self._hide_provider_network_type()
            else:
                self.fields['network_type'].choices = network_type_choices

        try:
            if api.neutron.is_extension_supported(request,
                                                  'network_availability_zone'):
                zones = api.neutron.list_availability_zones(
                    self.request, 'network', 'available')
                self.fields['az_hints'].choices = [(zone['name'], zone['name'])
                                                   for zone in zones]
            else:
                del self.fields['az_hints']
        except Exception:
            msg = _('Failed to get availability zone list.')
            messages.warning(request, msg)
            del self.fields['az_hints']

    def _hide_provider_network_type(self):
        self.fields['network_type'].widget = forms.HiddenInput()
        self.fields['physical_network'].widget = forms.HiddenInput()
        self.fields['segmentation_id'].widget = forms.HiddenInput()
        self.fields['network_type'].required = False
        self.fields['physical_network'].required = False
        self.fields['segmentation_id'].required = False

    def handle(self, request, data):
        try:
            params = {
                'name': data['name'],
                'tenant_id': data['tenant_id'],
                'admin_state_up': data['admin_state'],
                'shared': data['shared'],
                'router:external': data['external']
            }
            if api.neutron.is_extension_supported(request, 'provider'):
                network_type = data['network_type']
                params['provider:network_type'] = network_type
                if network_type in self.nettypes_with_physnet:
                    params['provider:physical_network'] = (
                        data['physical_network'])
                if network_type in self.nettypes_with_seg_id:
                    params['provider:segmentation_id'] = (
                        data['segmentation_id'])
            if 'az_hints' in data and data['az_hints']:
                params['availability_zone_hints'] = data['az_hints']
            network = api.neutron.network_create(request, **params)
            LOG.debug('Network %s was successfully created.', data['name'])
            return network
        except Exception:
            redirect = reverse('horizon:admin:networks:index')
            msg = _('Failed to create network %s') % data['name']
            exceptions.handle(request, msg, redirect=redirect)

    def clean(self):
        cleaned_data = super(CreateNetwork, self).clean()
        if api.neutron.is_extension_supported(self.request, 'provider'):
            self._clean_physical_network(cleaned_data)
            self._clean_segmentation_id(cleaned_data)
        return cleaned_data

    def _clean_physical_network(self, data):
        network_type = data.get('network_type')
        if ('physical_network' in self._errors
                and network_type not in self.nettypes_with_physnet):
            # In this case the physical network is not required, so we can
            # ignore any errors.
            del self._errors['physical_network']

    def _clean_segmentation_id(self, data):
        network_type = data.get('network_type')
        if 'segmentation_id' in self._errors:
            if (network_type not in self.nettypes_with_seg_id
                    and not self.data.get("segmentation_id")):
                # In this case the segmentation ID is not required, so we can
                # ignore the field is required error.
                del self._errors['segmentation_id']
        elif network_type in self.nettypes_with_seg_id:
            seg_id = data.get('segmentation_id')
            seg_id_range = {
                'min': self.seg_id_range[network_type][0],
                'max': self.seg_id_range[network_type][1]
            }
            if seg_id < seg_id_range['min'] or seg_id > seg_id_range['max']:
                msg = (_('For a %(network_type)s network, valid segmentation '
                         'IDs are %(min)s through %(max)s.') % {
                             'network_type': network_type,
                             'min': seg_id_range['min'],
                             'max': seg_id_range['max']
                         })
                self._errors['segmentation_id'] = self.error_class([msg])
Example #18
0
class ImportOpaqueData(forms.SelfHandlingForm):
    name = forms.RegexField(required=False,
                            max_length=255,
                            label=_("Data Name"),
                            regex=shared_forms.NAME_REGEX,
                            error_messages=shared_forms.ERROR_MESSAGES)
    source_type = forms.ChoiceField(
        label=_('Source'),
        required=False,
        choices=[('file', _('File')), ('raw', _('Direct Input'))],
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'source'
        }))
    object_file = forms.FileField(label=_("Choose file"),
                                  help_text=_("A local file to upload."),
                                  widget=forms.FileInput(
                                      attrs={
                                          'class': 'switched',
                                          'data-switch-on': 'source',
                                          'data-source-file': _('File')
                                      }),
                                  required=False)
    direct_input = forms.CharField(
        label=_('Object Bytes'),
        help_text=_('The bytes of the object, represented in hex.'),
        widget=forms.widgets.Textarea(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-raw': _('Bytes')
            }),
        required=False)

    def __init__(self, request, *args, **kwargs):
        super(ImportOpaqueData, self).__init__(request, *args, **kwargs)

    def clean(self):
        data = super(ImportOpaqueData, self).clean()

        # The data can be missing based on particular upload
        # conditions. Code defensively for it here...
        data_file = data.get('object_file', None)
        data_raw = data.get('direct_input', None)

        if data_raw and data_file:
            raise forms.ValidationError(
                _("Cannot specify both file and direct input."))
        if not data_raw and not data_file:
            raise forms.ValidationError(
                _("No input was provided for the object value."))
        try:
            if data_file:
                data_bytes = self.files['object_file'].read()
            else:
                data_str = data['direct_input']
                data_bytes = binascii.unhexlify(data_str)
            data['object_bytes'] = base64.b64encode(data_bytes)
        except Exception as e:
            msg = _('There was a problem loading the object: %s. '
                    'Is the object valid and in the correct format?') % e
            raise forms.ValidationError(msg)

        return data

    def handle(self, request, data):
        try:
            data_bytes = data.get('object_bytes')
            data_uuid = client.import_object(
                request,
                data=data_bytes,
                name=data['name'],
                object_type=opaque_data.OpaqueData)

            if data['name']:
                data_identifier = data['name']
            else:
                data_identifier = data_uuid
            messages.success(
                request,
                _('Successfully imported object: %s') % data_identifier)
            return data_uuid
        except Exception as e:
            msg = _('Unable to import object: %s')
            messages.error(msg % e)
            exceptions.handle(request, ignore=True)
            self.api_error(_('Unable to import object.'))
            return False
Example #19
0
class CreateNamespaceForm(forms.SelfHandlingForm):
    source_type = forms.ChoiceField(
        label=_('Namespace Definition Source'),
        choices=[('file', _('Metadata Definition File')),
                 ('raw', _('Direct Input'))],
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'source'
        }))

    metadef_file = forms.FileField(
        label=_("Metadata Definition File"),
        help_text=_("A local metadata definition file to upload."),
        widget=forms.FileInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-required-when-shown': 'true',
                'data-source-file': _('Metadata Definition File')
            }),
        required=False)

    direct_input = forms.CharField(
        label=_('Namespace JSON'),
        help_text=_('The JSON formatted contents of a namespace.'),
        widget=forms.widgets.Textarea(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-required-when-shown': 'true',
                'data-source-raw': _('Namespace JSON')
            }),
        required=False)

    public = forms.BooleanField(label=_("Public"), required=False)
    protected = forms.BooleanField(label=_("Protected"), required=False)

    def clean(self):
        data = super().clean()

        # The key can be missing based on particular upload
        # conditions. Code defensively for it here...
        metadef_file = data.get('metadef_file', None)
        metadata_raw = data.get('direct_input', None)

        if metadata_raw and metadef_file:
            raise ValidationError(
                _("Cannot specify both file and direct input."))
        if not metadata_raw and not metadef_file:
            raise ValidationError(
                _("No input was provided for the namespace content."))
        try:
            if metadef_file:
                ns_str = self.files['metadef_file'].read()
            else:
                ns_str = data['direct_input']
            namespace = json.loads(ns_str)

            if data['public']:
                namespace['visibility'] = 'public'
            else:
                namespace['visibility'] = 'private'

            namespace['protected'] = data['protected']

            for protected_prop in constants.METADEFS_PROTECTED_PROPS:
                namespace.pop(protected_prop, None)

            data['namespace'] = namespace
        except Exception as e:
            msg = _('There was a problem loading the namespace: %s.') % e
            raise forms.ValidationError(msg)

        return data

    def handle(self, request, data):
        try:
            namespace = glance.metadefs_namespace_create(
                request, data['namespace'])
            messages.success(
                request,
                _('Namespace %s has been created.') % namespace['namespace'])
            return namespace
        except Exception as e:
            msg = _('Unable to create new namespace. %s')
            msg %= e.message.split('Failed validating', 1)[0]
            exceptions.handle(request, message=msg)
            return False
Example #20
0
class UpdateIKEPolicy(forms.SelfHandlingForm):
    name = forms.CharField(max_length=80, label=_("Name"), required=False)
    ikepolicy_id = forms.CharField(
        label=_("ID"), widget=forms.TextInput(attrs={'readonly': 'readonly'}))
    description = forms.CharField(required=False,
                                  max_length=80,
                                  label=_("Description"))
    # Currently this field has only one choice, so mark it as readonly.
    auth_algorithm = forms.ChoiceField(
        label=_("Authorization algorithm"),
        choices=[('sha1', _('sha1'))],
        widget=forms.ThemableSelectWidget(attrs={'readonly': 'readonly'}),
        required=False)
    encryption_algorithm = forms.ChoiceField(label=_("Encryption algorithm"),
                                             choices=[
                                                 ('3des', _('3des')),
                                                 ('aes-128', _('aes-128')),
                                                 ('aes-192', _('aes-192')),
                                                 ('aes-256', _('aes-256'))
                                             ],
                                             required=False)
    ike_version = forms.ChoiceField(label=_("IKE version"),
                                    choices=[('v1', _('v1')), ('v2', _('v2'))],
                                    required=False)
    # Currently this field has only one choice, so mark it as readonly.
    lifetime_units = forms.ChoiceField(
        label=_("Lifetime units for IKE keys"),
        choices=[('seconds', _('seconds'))],
        widget=forms.ThemableSelectWidget(attrs={'readonly': 'readonly'}),
        required=False)
    lifetime_value = forms.IntegerField(
        min_value=60,
        label=_("Lifetime value for IKE keys"),
        help_text=_("Equal to or greater than 60"),
        required=False)
    pfs = forms.ChoiceField(label=_("Perfect Forward Secrecy"),
                            choices=[('group2', _('group2')),
                                     ('group5', _('group5')),
                                     ('group14', _('group14'))],
                            required=False)
    # Currently this field has only one choice, so mark it as readonly.
    phase1_negotiation_mode = forms.ChoiceField(
        label=_("IKE Phase1 negotiation mode"),
        choices=[('main', 'main')],
        widget=forms.ThemableSelectWidget(attrs={'readonly': 'readonly'}),
        required=False)

    failure_url = 'horizon:project:vpn:index'

    def handle(self, request, context):
        try:
            data = {
                'ikepolicy': {
                    'name': context['name'],
                    'description': context['description'],
                    'auth_algorithm': context['auth_algorithm'],
                    'encryption_algorithm': context['encryption_algorithm'],
                    'ike_version': context['ike_version'],
                    'lifetime': {
                        'units': context['lifetime_units'],
                        'value': context['lifetime_value']
                    },
                    'pfs': context['pfs'],
                    'phase1_negotiation_mode':
                    context['phase1_negotiation_mode'],
                }
            }
            ikepolicy = api.vpn.ikepolicy_update(request,
                                                 context['ikepolicy_id'],
                                                 **data)
            msg = (_('IKE Policy %s was successfully updated.') %
                   context['name'])
            messages.success(request, msg)
            return ikepolicy
        except Exception as e:
            LOG.info('Failed to update IKE Policy %(id)s: %(exc)s', {
                'id': context['ikepolicy_id'],
                'exc': e
            })
            msg = _('Failed to update IKE Policy %s') % context['name']
            redirect = reverse(self.failure_url)
            exceptions.handle(request, msg, redirect=redirect)
Example #21
0
class CreateForm(forms.SelfHandlingForm):
    name = forms.CharField(max_length=255, label=_("Volume Name"),
                           required=False)
    description = forms.CharField(max_length=255, widget=forms.Textarea(
        attrs={'rows': 4}),
        label=_("Description"), required=False)
    volume_source_type = forms.ChoiceField(
        label=_("Volume Source"),
        required=False,
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'source'}))
    snapshot_source = forms.ChoiceField(
        label=_("Use snapshot as a source"),
        widget=forms.ThemableSelectWidget(
            attrs={'class': 'snapshot-selector switched',
                   'data-switch-on': 'source',
                   'data-source-snapshot_source':
                       _("Use snapshot as a source"),
                   'data-required-when-shown': 'true'},
            data_attrs=('size', 'name'),
            transform=lambda x: "%s (%s GiB)" % (x.name, x.size)),
        required=False)
    image_source = forms.ChoiceField(
        label=_("Use image as a source"),
        widget=forms.ThemableSelectWidget(
            attrs={'class': 'image-selector switched',
                   'data-switch-on': 'source',
                   'data-source-image_source':
                       _("Use image as a source"),
                   'data-required-when-shown': 'true'},
            data_attrs=('size', 'name', 'min_disk'),
            transform=lambda x: "%s (%s)" % (x.name, filesizeformat(x.bytes))),
        required=False)
    volume_source = forms.ChoiceField(
        label=_("Use a volume as source"),
        widget=forms.ThemableSelectWidget(
            attrs={'class': 'image-selector switched',
                   'data-switch-on': 'source',
                   'data-source-volume_source':
                       _("Use a volume as source"),
                   'data-required-when-shown': 'true'},
            data_attrs=('size', 'name'),
            transform=lambda x: "%s (%s GiB)" % (x.name, x.size)),
        required=False)
    type = forms.ChoiceField(
        label=_("Type"),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={'class': 'switched',
                   'data-switch-on': 'source',
                   'data-source-no_source_type': _('Type'),
                   'data-source-image_source': _('Type')}))
    size = forms.IntegerField(min_value=1, initial=1, label=_("Size (GiB)"))
    availability_zone = forms.ChoiceField(
        label=_("Availability Zone"),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={'class': 'switched',
                   'data-switch-on': 'source',
                   'data-source-no_source_type': _('Availability Zone'),
                   'data-source-image_source': _('Availability Zone')}))
    group = forms.ThemableChoiceField(
        label=_("Group"), required=False,
        help_text=_("Group which the new volume belongs to. Choose "
                    "'No group' if the new volume belongs to no group."))

    def prepare_source_fields_if_snapshot_specified(self, request):
        try:
            snapshot = self.get_snapshot(request,
                                         request.GET["snapshot_id"])
            self.fields['name'].initial = snapshot.name
            self.fields['size'].initial = snapshot.size
            self.fields['snapshot_source'].choices = ((snapshot.id,
                                                       snapshot),)
            try:
                # Set the volume type from the original volume
                orig_volume = cinder.volume_get(request,
                                                snapshot.volume_id)
                self.fields['type'].initial = orig_volume.volume_type
            except Exception:
                pass
            self.fields['size'].help_text = (
                _('Volume size must be equal to or greater than the '
                  'snapshot size (%sGiB)') % snapshot.size)
            self.fields['type'].widget = forms.widgets.HiddenInput()
            del self.fields['image_source']
            del self.fields['volume_source']
            del self.fields['volume_source_type']
            del self.fields['availability_zone']

        except Exception:
            exceptions.handle(request,
                              _('Unable to load the specified snapshot.'))

    def prepare_source_fields_if_image_specified(self, request):
        self.fields['availability_zone'].choices = \
            availability_zones(request)
        try:
            image = self.get_image(request,
                                   request.GET["image_id"])
            image.bytes = image.size
            self.fields['name'].initial = image.name
            min_vol_size = functions.bytes_to_gigabytes(
                image.size)
            size_help_text = (_('Volume size must be equal to or greater '
                                'than the image size (%s)')
                              % filesizeformat(image.size))
            properties = getattr(image, 'properties', {})
            min_disk_size = (getattr(image, 'min_disk', 0) or
                             properties.get('min_disk', 0))
            if min_disk_size > min_vol_size:
                min_vol_size = min_disk_size
                size_help_text = (_('Volume size must be equal to or '
                                    'greater than the image minimum '
                                    'disk size (%sGiB)')
                                  % min_disk_size)
            self.fields['size'].initial = min_vol_size
            self.fields['size'].help_text = size_help_text
            self.fields['image_source'].choices = ((image.id, image),)
            del self.fields['snapshot_source']
            del self.fields['volume_source']
            del self.fields['volume_source_type']
        except Exception:
            msg = _('Unable to load the specified image. %s')
            exceptions.handle(request, msg % request.GET['image_id'])

    def prepare_source_fields_if_volume_specified(self, request):
        self.fields['availability_zone'].choices = \
            availability_zones(request)
        volume = None
        try:
            volume = self.get_volume(request, request.GET["volume_id"])
        except Exception:
            msg = _('Unable to load the specified volume. %s')
            exceptions.handle(request, msg % request.GET['volume_id'])

        if volume is not None:
            self.fields['name'].initial = volume.name
            self.fields['description'].initial = volume.description
            min_vol_size = volume.size
            size_help_text = (_('Volume size must be equal to or greater '
                                'than the origin volume size (%sGiB)')
                              % volume.size)
            self.fields['size'].initial = min_vol_size
            self.fields['size'].help_text = size_help_text
            self.fields['volume_source'].choices = ((volume.id, volume),)
            self.fields['type'].initial = volume.type
            del self.fields['snapshot_source']
            del self.fields['image_source']
            del self.fields['volume_source_type']

    def prepare_source_fields_default(self, request):
        source_type_choices = []
        self.fields['availability_zone'].choices = \
            availability_zones(request)

        try:
            available = api.cinder.VOLUME_STATE_AVAILABLE
            snapshots = cinder.volume_snapshot_list(
                request, search_opts=dict(status=available))
            if snapshots:
                source_type_choices.append(("snapshot_source",
                                            _("Snapshot")))
                choices = [('', _("Choose a snapshot"))] + \
                          [(s.id, s) for s in snapshots]
                self.fields['snapshot_source'].choices = choices
            else:
                del self.fields['snapshot_source']
        except Exception:
            exceptions.handle(request,
                              _("Unable to retrieve volume snapshots."))

        images = utils.get_available_images(request,
                                            request.user.tenant_id)
        if images:
            source_type_choices.append(("image_source", _("Image")))
            choices = [('', _("Choose an image"))]
            for image in images:
                image.bytes = image.size
                image.size = functions.bytes_to_gigabytes(image.bytes)
                choices.append((image.id, image))
            self.fields['image_source'].choices = choices
        else:
            del self.fields['image_source']

        volumes = self.get_volumes(request)
        if volumes:
            source_type_choices.append(("volume_source", _("Volume")))
            choices = [('', _("Choose a volume"))]
            for volume in volumes:
                choices.append((volume.id, volume))
            self.fields['volume_source'].choices = choices
        else:
            del self.fields['volume_source']

        if source_type_choices:
            choices = ([('no_source_type',
                         _("No source, empty volume"))] +
                       source_type_choices)
            self.fields['volume_source_type'].choices = choices
        else:
            del self.fields['volume_source_type']

    def _populate_group_choices(self, request):
        try:
            groups = cinder.group_list(request)
        except cinder_exc.VersionNotFoundForAPIMethod:
            del self.fields['group']
            return
        except Exception:
            redirect = reverse("horizon:project:volumes:index")
            exceptions.handle(request,
                              _('Unable to retrieve the volume group list.'),
                              redirect=redirect)
        group_choices = [(g.id, g.name or g.id) for g in groups]
        group_choices.insert(0, ("", _("No group")))
        self.fields['group'].choices = group_choices

    def __init__(self, request, *args, **kwargs):
        super().__init__(request, *args, **kwargs)
        volume_types = []
        try:
            volume_types = cinder.volume_type_list(request)
        except Exception:
            redirect_url = reverse("horizon:project: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 '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)

        self._populate_group_choices(request)

    def clean(self):
        cleaned_data = super().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_quota_usages(
                self.request, targets=('volumes', 'gigabytes'))
            availableGB = usages['gigabytes']['available']
            availableVol = usages['volumes']['available']

            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
            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"])
                snapshot_id = snapshot.id
                if data['size'] < snapshot.size:
                    error_message = (_('The volume size cannot be less than '
                                       'the snapshot size (%sGiB)')
                                     % snapshot.size)
                    raise ValidationError(error_message)
                az = None
                volume_type = ""
            elif (data.get("image_source", None) and
                  source_type in ['', None, 'image_source']):
                # Create from Snapshot
                image = self.get_image(request,
                                       data["image_source"])
                image_id = image.id
                image_size = functions.bytes_to_gigabytes(image.size)
                if data['size'] < image_size:
                    error_message = (_('The volume size cannot be less than '
                                       'the image size (%s)')
                                     % filesizeformat(image.size))
                    raise ValidationError(error_message)
                properties = getattr(image, 'properties', {})
                min_disk_size = (getattr(image, 'min_disk', 0) or
                                 properties.get('min_disk', 0))
                if min_disk_size > 0 and data['size'] < min_disk_size:
                    error_message = (_('The volume size cannot be less than '
                                       'the image minimum disk size (%sGiB)')
                                     % min_disk_size)
                    raise ValidationError(error_message)
            elif (data.get("volume_source", None) and
                  source_type in ['', None, 'volume_source']):
                # Create from volume
                volume = self.get_volume(request, data["volume_source"])
                volume_id = volume.id
                volume_type = None

                if data['size'] < volume.size:
                    error_message = (_('The volume size cannot be less than '
                                       'the source volume size (%sGiB)')
                                     % volume.size)
                    raise ValidationError(error_message)
            else:
                if type(data['size']) is str:
                    data['size'] = int(data['size'])

            if availableGB < data['size']:
                error_message = _('A volume of %(req)iGiB cannot be created '
                                  'as you only have %(avail)iGiB of your '
                                  'quota available.')
                params = {'req': data['size'],
                          'avail': availableGB}
                raise ValidationError(error_message % params)
            if 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'],
                                          volume_type,
                                          snapshot_id=snapshot_id,
                                          image_id=image_id,
                                          metadata=metadata,
                                          availability_zone=az,
                                          source_volid=volume_id,
                                          group_id=data.get('group') or None)
            message = _('Creating volume "%s"') % volume.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)
Example #22
0
class CreatePort(forms.SelfHandlingForm):
    network_name = forms.CharField(
        label=_("Network Name"),
        widget=forms.TextInput(attrs={'readonly': 'readonly'}),
        required=False)
    network_id = forms.CharField(
        label=_("Network ID"),
        widget=forms.TextInput(attrs={'readonly': 'readonly'}))
    name = forms.CharField(max_length=255, label=_("Name"), required=False)
    admin_state = forms.BooleanField(label=_("Enable Admin State"),
                                     initial=True,
                                     required=False)
    device_id = forms.CharField(max_length=100,
                                label=_("Device ID"),
                                help_text=_("Device ID attached to the port"),
                                required=False)
    device_owner = forms.CharField(
        max_length=100,
        label=_("Device Owner"),
        help_text=_("Owner of the device attached to the port"),
        required=False)
    specify_ip = forms.ThemableChoiceField(
        label=_("Specify IP address or subnet"),
        help_text=_("To specify a subnet or a fixed IP, select any options."),
        initial=False,
        required=False,
        choices=[('', _("Unspecified")), ('subnet_id', _("Subnet")),
                 ('fixed_ip', _("Fixed IP Address"))],
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'specify_ip',
        }))
    subnet_id = forms.ThemableChoiceField(
        label=_("Subnet"),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-switch-on': 'specify_ip',
                'data-specify_ip-subnet_id': _('Subnet'),
            }))
    fixed_ip = forms.IPField(
        label=_("Fixed IP Address"),
        required=False,
        help_text=_("Specify the subnet IP address for the new port"),
        version=forms.IPv4 | forms.IPv6,
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'specify_ip',
                'data-specify_ip-fixed_ip': _('Fixed IP Address'),
            }))
    mac_address = forms.MACAddressField(
        label=_("MAC Address"),
        required=False,
        help_text=_("Specify the MAC address for the new port"))
    failure_url = 'horizon:project:networks:detail'

    def __init__(self, request, *args, **kwargs):
        super(CreatePort, self).__init__(request, *args, **kwargs)

        # prepare subnet choices and input area for each subnet
        subnet_choices = self._get_subnet_choices(kwargs['initial'])
        if subnet_choices:
            subnet_choices.insert(0, ('', _("Select a subnet")))
            self.fields['subnet_id'].choices = subnet_choices
        else:
            self.fields['specify_ip'].widget = forms.HiddenInput()
            self.fields['subnet_id'].widget = forms.HiddenInput()
            self.fields['fixed_ip'].widget = forms.HiddenInput()

        if api.neutron.is_extension_supported(request, 'mac-learning'):
            self.fields['mac_state'] = forms.BooleanField(
                label=_("MAC Learning State"), initial=False, required=False)

        try:
            if api.neutron.is_extension_supported(request, 'port-security'):
                self.fields['port_security_enabled'] = forms.BooleanField(
                    label=_("Port Security"),
                    help_text=_("Enable anti-spoofing rules for the port"),
                    initial=True,
                    required=False)
        except Exception:
            msg = _("Unable to retrieve port security state")
            exceptions.handle(self.request, msg)

    def _get_subnet_choices(self, kwargs):
        try:
            network_id = kwargs['network_id']
            network = api.neutron.network_get(self.request, network_id)
        except Exception:
            return []

        return [(subnet.id, '%s %s' % (subnet.name_or_id, subnet.cidr))
                for subnet in network.subnets]

    def handle(self, request, data):
        try:
            params = {
                'network_id': data['network_id'],
                'admin_state_up': data['admin_state'],
                'name': data['name'],
                'device_id': data['device_id'],
                'device_owner': data['device_owner']
            }

            if data.get('specify_ip') == 'subnet_id':
                if data.get('subnet_id'):
                    params['fixed_ips'] = [{"subnet_id": data['subnet_id']}]
            elif data.get('specify_ip') == 'fixed_ip':
                if data.get('fixed_ip'):
                    params['fixed_ips'] = [{"ip_address": data['fixed_ip']}]

            if data.get('mac_state'):
                params['mac_learning_enabled'] = data['mac_state']
            if 'port_security_enabled' in data:
                params['port_security_enabled'] = data['port_security_enabled']

            # Send mac_address only when it is specified.
            if data['mac_address']:
                params['mac_address'] = data['mac_address']

            port = api.neutron.port_create(request, **params)
            if port['name']:
                msg = _('Port %s was successfully created.') % port['name']
            else:
                msg = _('Port %s was successfully created.') % port['id']
            messages.success(request, msg)
            return port
        except Exception as e:
            LOG.info('Failed to create a port for network %(id)s: %(exc)s', {
                'id': data['network_id'],
                'exc': e
            })
            msg = (_('Failed to create a port for network %s') %
                   data['network_id'])
            redirect = reverse(self.failure_url, args=(data['network_id'], ))
            exceptions.handle(request, msg, redirect=redirect)
Example #23
0
class CreatePort(forms.SelfHandlingForm):
    name = forms.CharField(max_length=255, label=_("Name"), required=False)
    admin_state = forms.BooleanField(label=_("Enable Admin State"),
                                     initial=True,
                                     required=False)
    device_id = forms.CharField(max_length=100,
                                label=_("Device ID"),
                                help_text=_("Device ID attached to the port"),
                                required=False)
    device_owner = forms.CharField(
        max_length=100,
        label=_("Device Owner"),
        help_text=_("Owner of the device attached to the port"),
        required=False)
    specify_ip = forms.ThemableChoiceField(
        label=_("Specify IP address or subnet"),
        help_text=_("To specify a subnet or a fixed IP, select any options."),
        required=False,
        choices=[('', _("Unspecified")), ('subnet_id', _("Subnet")),
                 ('fixed_ip', _("Fixed IP Address"))],
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'specify_ip',
        }))
    subnet_id = forms.ThemableChoiceField(
        label=_("Subnet"),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-switch-on': 'specify_ip',
                'data-specify_ip-subnet_id': _('Subnet'),
            }))
    fixed_ip = forms.IPField(
        label=_("Fixed IP Address"),
        required=False,
        help_text=_("Specify the subnet IP address for the new port"),
        version=forms.IPv4 | forms.IPv6,
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'specify_ip',
                'data-specify_ip-fixed_ip': _('Fixed IP Address'),
            }))
    mac_address = forms.MACAddressField(
        label=_("MAC Address"),
        required=False,
        help_text=_("Specify the MAC address for the new port"))
    failure_url = 'horizon:project:networks:detail'

    def __init__(self, request, *args, **kwargs):
        super(CreatePort, self).__init__(request, *args, **kwargs)

        # prepare subnet choices and input area for each subnet
        subnet_choices = self._get_subnet_choices(kwargs['initial'])
        if subnet_choices:
            subnet_choices.insert(0, ('', _("Select a subnet")))
            self.fields['subnet_id'].choices = subnet_choices
        else:
            self.fields['specify_ip'].widget = forms.HiddenInput()
            self.fields['subnet_id'].widget = forms.HiddenInput()
            self.fields['fixed_ip'].widget = forms.HiddenInput()

        if api.neutron.is_extension_supported(request, 'mac-learning'):
            self.fields['mac_state'] = forms.BooleanField(
                label=_("MAC Learning State"), initial=False, required=False)

        try:
            if api.neutron.is_extension_supported(request, 'port-security'):
                self.fields['port_security_enabled'] = forms.BooleanField(
                    label=_("Port Security"),
                    help_text=_("Enable anti-spoofing rules for the port"),
                    initial=True,
                    required=False)
        except Exception:
            msg = _("Unable to retrieve port security state")
            exceptions.handle(self.request, msg)

    def _get_subnet_choices(self, kwargs):
        try:
            network_id = kwargs['network_id']
            network = api.neutron.network_get(self.request, network_id)
        except Exception:
            return []

        # NOTE(amotoki): When a user cannot retrieve a subnet info,
        # subnet ID is stored in network.subnets field.
        # If so, we skip such subnet as subnet choices.
        # This happens usually for external networks.
        # TODO(amotoki): Ideally it is better to disable/hide
        # Create Port button in the port table, but as of Pike
        # the default neutron policy.json for "create_port" is empty
        # and there seems no appropriate policy. This is a dirty hack.
        return [(subnet.id, '%s %s' % (subnet.name_or_id, subnet.cidr))
                for subnet in network.subnets
                if isinstance(subnet, api.neutron.Subnet)]

    def handle(self, request, data):
        try:
            params = {
                'network_id': self.initial['network_id'],
                'admin_state_up': data['admin_state'],
                'name': data['name'],
                'device_id': data['device_id'],
                'device_owner': data['device_owner']
            }

            if data.get('specify_ip') == 'subnet_id':
                if data.get('subnet_id'):
                    params['fixed_ips'] = [{"subnet_id": data['subnet_id']}]
            elif data.get('specify_ip') == 'fixed_ip':
                if data.get('fixed_ip'):
                    params['fixed_ips'] = [{"ip_address": data['fixed_ip']}]

            if data.get('mac_state'):
                params['mac_learning_enabled'] = data['mac_state']
            if 'port_security_enabled' in data:
                params['port_security_enabled'] = data['port_security_enabled']

            # Send mac_address only when it is specified.
            if data['mac_address']:
                params['mac_address'] = data['mac_address']

            port = api.neutron.port_create(request, **params)
            if port['name']:
                msg = _('Port %s was successfully created.') % port['name']
            else:
                msg = _('Port %s was successfully created.') % port['id']
            messages.success(request, msg)
            return port
        except Exception as e:
            LOG.info('Failed to create a port for network %(id)s: %(exc)s', {
                'id': self.initial['network_id'],
                'exc': e
            })
            if isinstance(e, neutron_exc.Forbidden):
                msg = (_('You are not allowed to create a port '
                         'for network %s.') % self.initial['network_id'])
            else:
                msg = (_('Failed to create a port for network %s') %
                       self.initial['network_id'])
            redirect = reverse(self.failure_url,
                               args=(self.initial['network_id'], ))
            exceptions.handle(request, msg, redirect=redirect)
Example #24
0
class TemplateForm(forms.SelfHandlingForm):
    class Meta(object):
        name = _('Select Template')
        help_text = _('Select a template to launch a stack.')

    # TODO(jomara) - update URL choice for template & environment files
    # w/ client side download when applicable
    base_choices = [('file', _('File')), ('raw', _('Direct Input'))]
    url_choice = [('url', _('URL'))]
    attributes = {'class': 'switchable', 'data-slug': 'templatesource'}
    template_source = forms.ChoiceField(
        label=_('Template Source'),
        choices=base_choices + url_choice,
        widget=forms.ThemableSelectWidget(attrs=attributes))

    attributes = create_upload_form_attributes('template', 'file',
                                               _('Template File'))
    template_upload = forms.FileField(
        label=_('Template File'),
        help_text=_('A local template to upload.'),
        widget=forms.FileInput(attrs=attributes),
        required=False)

    attributes = create_upload_form_attributes('template', 'url',
                                               _('Template URL'))
    template_url = forms.URLField(
        label=_('Template URL'),
        help_text=_('An external (HTTP) URL to load the template from.'),
        widget=forms.TextInput(attrs=attributes),
        required=False)

    attributes = create_upload_form_attributes('template', 'raw',
                                               _('Template Data'))
    template_data = forms.CharField(
        label=_('Template Data'),
        help_text=_('The raw contents of the template.'),
        widget=forms.widgets.Textarea(attrs=attributes),
        required=False)

    attributes = {'data-slug': 'envsource', 'class': 'switchable'}
    environment_source = forms.ChoiceField(
        label=_('Environment Source'),
        choices=base_choices,
        widget=forms.ThemableSelectWidget(attrs=attributes),
        required=False)

    attributes = create_upload_form_attributes('env', 'file',
                                               _('Environment File'))
    environment_upload = forms.FileField(
        label=_('Environment File'),
        help_text=_('A local environment to upload.'),
        widget=forms.FileInput(attrs=attributes),
        required=False)

    attributes = create_upload_form_attributes('env', 'raw',
                                               _('Environment Data'))
    environment_data = forms.CharField(
        label=_('Environment Data'),
        help_text=_('The raw contents of the environment file.'),
        widget=forms.widgets.Textarea(attrs=attributes),
        required=False)

    if django.VERSION >= (1, 9):
        # Note(Itxaka): On django>=1.9 Charfield has an strip option that
        # we need to set to False as to not hit
        # https://bugs.launchpad.net/python-heatclient/+bug/1546166
        environment_data.strip = False
        template_data.strip = False

    def __init__(self, *args, **kwargs):
        self.next_view = kwargs.pop('next_view')
        super(TemplateForm, self).__init__(*args, **kwargs)

    def clean(self):
        cleaned = super(TemplateForm, self).clean()

        files = self.request.FILES
        self.clean_uploaded_files('template', _('template'), cleaned, files)
        self.clean_uploaded_files('environment', _('environment'), cleaned,
                                  files)

        # Validate the template and get back the params.
        kwargs = {}
        if cleaned['environment_data']:
            kwargs['environment'] = cleaned['environment_data']
        try:
            files, tpl =\
                api.heat.get_template_files(cleaned.get('template_data'),
                                            cleaned.get('template_url'))
            kwargs['files'] = files
            kwargs['template'] = tpl
            validated = api.heat.template_validate(self.request, **kwargs)
            cleaned['template_validate'] = validated
            cleaned['template_validate']['files'] = files
            cleaned['template_validate']['template'] = tpl
        except Exception as e:
            raise forms.ValidationError(six.text_type(e))

        return cleaned

    def clean_uploaded_files(self, prefix, field_label, cleaned, files):
        """Cleans Template & Environment data from form upload.

        Does some of the crunchy bits for processing uploads vs raw
        data depending on what the user specified. Identical process
        for environment data & template data.

        :type prefix: str
        :param prefix: prefix (environment, template) of field
        :type field_label: str
        :param field_label: translated prefix str for messages
        :type input_type: dict
        :param prefix: existing cleaned fields from form
        :rtype: dict
        :return: cleaned dict including environment & template data
        """

        upload_str = prefix + "_upload"
        data_str = prefix + "_data"
        url = cleaned.get(prefix + '_url')
        data = cleaned.get(prefix + '_data')

        has_upload = upload_str in files
        # Uploaded file handler
        if has_upload and not url:
            log_template_name = files[upload_str].name
            LOG.info('got upload %s', log_template_name)

            tpl = files[upload_str].read()
            if tpl.startswith('{'):
                try:
                    json.loads(tpl)
                except Exception as e:
                    msg = _('There was a problem parsing the'
                            ' %(prefix)s: %(error)s')
                    msg = msg % {'prefix': prefix, 'error': six.text_type(e)}
                    raise forms.ValidationError(msg)
            cleaned[data_str] = tpl

        # URL handler
        elif url and (has_upload or data):
            msg = _('Please specify a %s using only one source method.')
            msg = msg % field_label
            raise forms.ValidationError(msg)

        elif prefix == 'template':
            # Check for raw template input - blank environment allowed
            if not url and not data:
                msg = _('You must specify a template via one of the '
                        'available sources.')
                raise forms.ValidationError(msg)

    def create_kwargs(self, data):
        kwargs = {
            'parameters': data['template_validate'],
            'environment_data': data['environment_data']
        }
        if data.get('stack_id'):
            kwargs['stack_id'] = data['stack_id']
        return kwargs

    def handle(self, request, data):
        kwargs = self.create_kwargs(data)
        # NOTE (gabriel): This is a bit of a hack, essentially rewriting this
        # request so that we can chain it as an input to the next view...
        # but hey, it totally works.
        request.method = 'GET'

        return self.next_view.as_view()(request, **kwargs)
Example #25
0
class AddRule(forms.SelfHandlingForm):
    id = forms.CharField(widget=forms.HiddenInput())
    rule_menu = forms.ChoiceField(
        label=_('Rule'),
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'rule_menu'
        }))
    description = forms.CharField(
        label=_('Description'),
        required=False,
        max_length=255,
        widget=forms.Textarea(attrs={'rows': 2}),
        help_text=_('A brief description of the security group rule '
                    'you are adding'))

    # "direction" field is enabled only when custom mode.
    # It is because most common rules in local_settings.py is meaningful
    # when its direction is 'ingress'.
    direction = forms.ChoiceField(
        label=_('Direction'),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-switch-on': 'rule_menu',
                'data-rule_menu-tcp': _('Direction'),
                'data-rule_menu-udp': _('Direction'),
                'data-rule_menu-icmp': _('Direction'),
                'data-rule_menu-custom': _('Direction'),
                'data-rule_menu-all_tcp': _('Direction'),
                'data-rule_menu-all_udp': _('Direction'),
                'data-rule_menu-all_icmp': _('Direction'),
            }))

    ip_protocol = forms.IntegerField(
        label=_('IP Protocol'),
        required=False,
        help_text=_("Enter an integer value between 0 and 255."),
        validators=[utils_validators.validate_ip_protocol],
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'rule_menu',
                'data-rule_menu-custom': _('IP Protocol')
            }))

    port_or_range = forms.ChoiceField(
        label=_('Open Port'),
        choices=[('port', _('Port')), ('range', _('Port Range'))],
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switchable switched',
                'data-slug': 'range',
                'data-switch-on': 'rule_menu',
                'data-rule_menu-tcp': _('Open Port'),
                'data-rule_menu-udp': _('Open Port')
            }))

    port = forms.IntegerField(
        label=_("Port"),
        required=False,
        help_text=_("Enter an integer value "
                    "between 1 and 65535."),
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-required-when-shown': 'true',
                'data-switch-on': 'range',
                'data-range-port': _('Port')
            }),
        validators=[utils_validators.validate_port_range])

    from_port = forms.IntegerField(
        label=_("From Port"),
        required=False,
        help_text=_("Enter an integer value "
                    "between 1 and 65535."),
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-required-when-shown': 'true',
                'data-switch-on': 'range',
                'data-range-range': _('From Port')
            }),
        validators=[utils_validators.validate_port_range])

    to_port = forms.IntegerField(
        label=_("To Port"),
        required=False,
        help_text=_("Enter an integer value "
                    "between 1 and 65535."),
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-required-when-shown': 'true',
                'data-switch-on': 'range',
                'data-range-range': _('To Port')
            }),
        validators=[utils_validators.validate_port_range])

    icmp_type = forms.IntegerField(
        label=_("Type"),
        required=False,
        help_text=_("Enter a value for ICMP type "
                    "in the range (-1: 255)"),
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'rule_menu',
                'data-rule_menu-icmp': _('Type')
            }),
        validators=[utils_validators.validate_icmp_type_range])

    icmp_code = forms.IntegerField(
        label=_("Code"),
        required=False,
        help_text=_("Enter a value for ICMP code "
                    "in the range (-1: 255)"),
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'rule_menu',
                'data-rule_menu-icmp': _('Code')
            }),
        validators=[utils_validators.validate_icmp_code_range])

    remote = forms.ChoiceField(
        label=_('Remote'),
        choices=[('cidr', _('CIDR')), ('sg', _('Security Group'))],
        help_text=_('To specify an allowed IP '
                    'range, select &quot;CIDR&quot;. '
                    'To allow access from all '
                    'members of another security '
                    'group select &quot;Security '
                    'Group&quot;.'),
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'remote'
        }))

    cidr = forms.IPField(label=_("CIDR"),
                         required=False,
                         initial="0.0.0.0/0",
                         help_text=_("Classless Inter-Domain Routing "
                                     "(e.g. 192.168.0.0/24, or "
                                     "2001:db8::/128)"),
                         version=forms.IPv4 | forms.IPv6,
                         mask=True,
                         widget=forms.TextInput(
                             attrs={
                                 'class': 'switched',
                                 'data-switch-on': 'remote',
                                 'data-remote-cidr': _('CIDR')
                             }))

    security_group = forms.ChoiceField(
        label=_('Security Group'),
        required=False,
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-switch-on': 'remote',
                'data-remote-sg': _('Security '
                                    'Group')
            }))
    # When cidr is used ethertype is determined from IP version of cidr.
    # When source group, ethertype needs to be specified explicitly.
    ethertype = forms.ChoiceField(label=_('Ether Type'),
                                  required=False,
                                  choices=[('IPv4', _('IPv4')),
                                           ('IPv6', _('IPv6'))],
                                  widget=forms.ThemableSelectWidget(
                                      attrs={
                                          'class': 'switched',
                                          'data-slug': 'ethertype',
                                          'data-switch-on': 'remote',
                                          'data-remote-sg': _('Ether Type')
                                      }))

    def __init__(self, *args, **kwargs):
        sg_list = kwargs.pop('sg_list', [])
        super(AddRule, self).__init__(*args, **kwargs)
        # Determine if there are security groups available for the
        # remote group option; add the choices and enable the option if so.
        if sg_list:
            security_groups_choices = sg_list
        else:
            security_groups_choices = [("", _("No security groups available"))]
        self.fields['security_group'].choices = security_groups_choices

        # TODO(amotoki): settings.SECURITY_GROUP_RULES may contains 'backend'
        # parameter. If 'backend' is used, error message should be emitted.
        backend = 'neutron'

        rules_dict = getattr(settings, 'SECURITY_GROUP_RULES', [])
        common_rules = [(k, rules_dict[k]['name']) for k in rules_dict
                        if rules_dict[k].get('backend', backend) == backend]
        common_rules.sort()
        custom_rules = [('tcp', _('Custom TCP Rule')),
                        ('udp', _('Custom UDP Rule')),
                        ('icmp', _('Custom ICMP Rule')),
                        ('custom', _('Other Protocol'))]
        self.fields['rule_menu'].choices = custom_rules + common_rules
        self.rules = rules_dict

        self.fields['direction'].choices = [('ingress', _('Ingress')),
                                            ('egress', _('Egress'))]
        self.fields['ip_protocol'].help_text = _(
            "Enter an integer value between -1 and 255 "
            "(-1 means wild card).")

        self.fields['port_or_range'].choices = [
            ('port', _('Port')),
            ('range', _('Port Range')),
            ('all', _('All ports')),
        ]

        if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}).get(
                'enable_ipv6', True):
            self.fields['cidr'].version = forms.IPv4
            self.fields['ethertype'].widget = forms.TextInput(
                attrs={'readonly': 'readonly'})
            self.fields['ethertype'].initial = 'IPv4'

        try:
            is_desc_supported = api.neutron.is_extension_supported(
                self.request, 'standard-attr-description')
        except Exception:
            exceptions.handle(
                self.request,
                _('Failed to check if description field is supported.'))
            is_desc_supported = False
        if not is_desc_supported:
            del self.fields['description']

    def _update_and_pop_error(self, cleaned_data, key, value):
        cleaned_data[key] = value
        self.errors.pop(key, None)

    def _clean_rule_icmp(self, cleaned_data, rule_menu):
        icmp_type = cleaned_data.get("icmp_type", None)
        icmp_code = cleaned_data.get("icmp_code", None)

        self._update_and_pop_error(cleaned_data, 'ip_protocol', rule_menu)
        if icmp_type == -1 and icmp_code != -1:
            msg = _('ICMP code is provided but ICMP type is missing.')
            raise ValidationError(msg)
        if self.errors.get('icmp_type'):
            msg = _('The ICMP type not in range (-1, 255)')
            raise ValidationError(msg)
        if self.errors.get('icmp_code'):
            msg = _('The ICMP code not in range (-1, 255)')
            raise ValidationError(msg)

        self._update_and_pop_error(cleaned_data, 'from_port', icmp_type)
        self._update_and_pop_error(cleaned_data, 'to_port', icmp_code)
        self._update_and_pop_error(cleaned_data, 'port', None)

    def _clean_rule_tcp_udp(self, cleaned_data, rule_menu):
        port_or_range = cleaned_data.get("port_or_range")
        from_port = cleaned_data.get("from_port", None)
        to_port = cleaned_data.get("to_port", None)
        port = cleaned_data.get("port", None)

        self._update_and_pop_error(cleaned_data, 'ip_protocol', rule_menu)
        self._update_and_pop_error(cleaned_data, 'icmp_code', None)
        self._update_and_pop_error(cleaned_data, 'icmp_type', None)
        if port_or_range == 'all':
            self._update_and_pop_error(cleaned_data, 'port', None)
            self._update_and_pop_error(cleaned_data, 'from_port', None)
            self._update_and_pop_error(cleaned_data, 'to_port', None)
        elif port_or_range == "port":
            self._update_and_pop_error(cleaned_data, 'from_port', port)
            self._update_and_pop_error(cleaned_data, 'to_port', port)
            if port is None:
                msg = _('The specified port is invalid.')
                raise ValidationError(msg)
        else:
            self._update_and_pop_error(cleaned_data, 'port', None)
            if from_port is None:
                msg = _('The "from" port number is invalid.')
                raise ValidationError(msg)
            if to_port is None:
                msg = _('The "to" port number is invalid.')
                raise ValidationError(msg)
            if to_port < from_port:
                msg = _('The "to" port number must be greater than '
                        'or equal to the "from" port number.')
                raise ValidationError(msg)

    def _clean_rule_custom(self, cleaned_data, rule_menu):
        # custom IP protocol rule so we need to fill unused fields so
        # the validation works
        unused_fields = [
            'icmp_code', 'icmp_type', 'from_port', 'to_port', 'port'
        ]
        for unused_field in unused_fields:
            self._update_and_pop_error(cleaned_data, unused_field, None)

    def _apply_rule_menu(self, cleaned_data, rule_menu):
        cleaned_data['ip_protocol'] = self.rules[rule_menu]['ip_protocol']
        cleaned_data['from_port'] = int(self.rules[rule_menu]['from_port'])
        cleaned_data['to_port'] = int(self.rules[rule_menu]['to_port'])
        self._update_and_pop_error(cleaned_data, 'icmp_code', None)
        self._update_and_pop_error(cleaned_data, 'icmp_type', None)
        if rule_menu not in ['all_tcp', 'all_udp', 'all_icmp']:
            direction = self.rules[rule_menu].get('direction')
            cleaned_data['direction'] = direction

    def _clean_rule_menu(self, cleaned_data):
        rule_menu = cleaned_data.get('rule_menu')
        if rule_menu == 'icmp':
            self._clean_rule_icmp(cleaned_data, rule_menu)
        elif rule_menu in ('tcp', 'udp'):
            self._clean_rule_tcp_udp(cleaned_data, rule_menu)
        elif rule_menu == 'custom':
            self._clean_rule_custom(cleaned_data, rule_menu)
        else:
            self._apply_rule_menu(cleaned_data, rule_menu)

    def _adjust_ip_protocol_of_icmp(self, data):
        # Note that this needs to be called after IPv4/IPv6 is determined.
        try:
            ip_protocol = int(data['ip_protocol'])
        except ValueError:
            # string representation of IP protocol
            ip_protocol = data['ip_protocol']
        is_ipv6 = data['ethertype'] == 'IPv6'

        if isinstance(ip_protocol, int):
            # When IP protocol number is specified, we assume a user
            # knows more detail on IP protocol number,
            # so a warning message on a mismatch between IP proto number
            # and IP version is displayed.
            if is_ipv6 and ip_protocol == 1:
                msg = _('58 (ipv6-icmp) should be specified for IPv6 '
                        'instead of 1.')
                self._errors['ip_protocol'] = self.error_class([msg])
            elif not is_ipv6 and ip_protocol == 58:
                msg = _('1 (icmp) should be specified for IPv4 '
                        'instead of 58.')
                self._errors['ip_protocol'] = self.error_class([msg])
        else:
            # ICMPv6 uses different an IP protocol name and number.
            # To allow 'icmp' for both IPv4 and IPv6 in the form,
            # we need to replace 'icmp' with 'ipv6-icmp' based on IP version.
            if is_ipv6 and ip_protocol == 'icmp':
                data['ip_protocol'] = 'ipv6-icmp'

    def clean(self):
        cleaned_data = super(AddRule, self).clean()

        self._clean_rule_menu(cleaned_data)

        # NOTE(amotoki): There are two cases where cleaned_data['direction']
        # is empty: (1) Nova Security Group is used. Since "direction" is
        # HiddenInput, direction field exists but its value is ''.
        # (2) Template except all_* is used. In this case, the default value
        # is None. To make sure 'direction' field has 'ingress' or 'egress',
        # fill this field here if it is not specified.
        if not cleaned_data['direction']:
            cleaned_data['direction'] = 'ingress'

        remote = cleaned_data.get("remote")
        if remote == "cidr":
            self._update_and_pop_error(cleaned_data, 'security_group', None)
        else:
            self._update_and_pop_error(cleaned_data, 'cidr', None)

        # If cleaned_data does not contain a non-empty value, IPField already
        # has validated it, so skip the further validation for cidr.
        # In addition cleaned_data['cidr'] is None means source_group is used.
        if 'cidr' in cleaned_data and cleaned_data['cidr'] is not None:
            cidr = cleaned_data['cidr']
            if not cidr:
                msg = _('CIDR must be specified.')
                self._errors['cidr'] = self.error_class([msg])
            else:
                # If cidr is specified, ethertype is determined from IP address
                # version. It is used only when Neutron is enabled.
                ip_ver = netaddr.IPNetwork(cidr).version
                cleaned_data['ethertype'] = 'IPv6' if ip_ver == 6 else 'IPv4'

        self._adjust_ip_protocol_of_icmp(cleaned_data)

        return cleaned_data

    def handle(self, request, data):
        redirect = reverse("horizon:project:security_groups:detail",
                           args=[data['id']])
        params = {}
        if 'description' in data:
            params['description'] = data['description']
        try:
            rule = api.neutron.security_group_rule_create(
                request, filters.get_int_or_uuid(data['id']),
                data['direction'], data['ethertype'], data['ip_protocol'],
                data['from_port'], data['to_port'], data['cidr'],
                data['security_group'], **params)
            messages.success(
                request,
                _('Successfully added rule: %s') % six.text_type(rule))
            return rule
        except exceptions.Conflict as error:
            exceptions.handle(request, error, redirect=redirect)
        except Exception:
            exceptions.handle(request,
                              _('Unable to add rule to security group.'),
                              redirect=redirect)
class CustomizeAction(workflows.Action):
    class Meta(object):
        name = _("Post-Creation")
        help_text_template = ("project/instances/"
                              "_launch_customize_help.html")

    source_choices = [('', _('Select Script Source')),
                      ('raw', _('Direct Input')), ('file', _('File'))]

    attributes = {'class': 'switchable', 'data-slug': 'scriptsource'}
    script_source = forms.ChoiceField(
        label=_('Customization Script Source'),
        choices=source_choices,
        widget=forms.ThemableSelectWidget(attrs=attributes),
        required=False)

    script_help = _("A script or set of commands to be executed after the "
                    "instance has been built (max 16kb).")

    script_upload = forms.FileField(
        label=_('Script File'),
        help_text=script_help,
        widget=forms.FileInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'scriptsource',
                'data-scriptsource-file': _('Script File')
            }),
        required=False)

    script_data = forms.CharField(
        label=_('Script Data'),
        help_text=script_help,
        widget=forms.widgets.Textarea(
            attrs={
                'class': 'switched',
                'data-switch-on': 'scriptsource',
                'data-scriptsource-raw': _('Script Data')
            }),
        required=False)

    def __init__(self, *args):
        super(CustomizeAction, self).__init__(*args)

    def clean(self):
        cleaned = super(CustomizeAction, self).clean()

        files = self.request.FILES
        script = self.clean_uploaded_files('script', files)

        if script is not None:
            cleaned['script_data'] = script

        return cleaned

    def clean_uploaded_files(self, prefix, files):
        upload_str = prefix + "_upload"

        has_upload = upload_str in files
        if has_upload:
            upload_file = files[upload_str]
            log_script_name = upload_file.name
            LOG.info('got upload %s' % log_script_name)

            if upload_file._size > 16 * units.Ki:  # 16kb
                msg = _('File exceeds maximum size (16kb)')
                raise forms.ValidationError(msg)
            else:
                script = upload_file.read()
                if script != "":
                    try:
                        normalize_newlines(script)
                    except Exception as e:
                        msg = _('There was a problem parsing the'
                                ' %(prefix)s: %(error)s')
                        msg = msg % {
                            'prefix': prefix,
                            'error': six.text_type(e)
                        }
                        raise forms.ValidationError(msg)
                return script
        else:
            return None
Example #27
0
class CreateSubnetDetailAction(workflows.Action):
    enable_dhcp = forms.BooleanField(label=_("Enable DHCP"),
                                     initial=True,
                                     required=False)
    ipv6_modes = forms.ChoiceField(
        label=_("IPv6 Address Configuration Mode"),
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-switch-on': 'ipversion',
                'data-ipversion-6': _("IPv6 Address Configuration Mode"),
            }),
        initial=utils.IPV6_DEFAULT_MODE,
        required=False,
        help_text=_("Specifies how IPv6 addresses and additional information "
                    "are configured. We can specify SLAAC/DHCPv6 stateful/"
                    "DHCPv6 stateless provided by OpenStack, "
                    "or specify no option. "
                    "'No options specified' means addresses are configured "
                    "manually or configured by a non-OpenStack system."))
    allocation_pools = forms.CharField(
        widget=forms.Textarea(attrs={'rows': 4}),
        label=_("Allocation Pools"),
        help_text=_("IP address allocation pools. Each entry is: "
                    "start_ip_address,end_ip_address "
                    "(e.g., 192.168.1.100,192.168.1.120) "
                    "and one entry per line."),
        required=False)
    dns_nameservers = forms.CharField(
        widget=forms.widgets.Textarea(attrs={'rows': 4}),
        label=_("DNS Name Servers"),
        help_text=_("IP address list of DNS name servers for this subnet. "
                    "One entry per line."),
        required=False)
    host_routes = forms.CharField(
        widget=forms.widgets.Textarea(attrs={'rows': 4}),
        label=_("Host Routes"),
        help_text=_("Additional routes announced to the hosts. "
                    "Each entry is: destination_cidr,nexthop "
                    "(e.g., 192.168.200.0/24,10.56.1.254) "
                    "and one entry per line."),
        required=False)

    class Meta(object):
        name = _("Subnet Details")
        help_text = _('Specify additional attributes for the subnet.')

    def __init__(self, request, context, *args, **kwargs):
        super().__init__(request, context, *args, **kwargs)
        if not setting_utils.get_dict_config('OPENSTACK_NEUTRON_NETWORK',
                                             'enable_ipv6'):
            self.fields['ipv6_modes'].widget = forms.HiddenInput()

    def populate_ipv6_modes_choices(self, request, context):
        return [(value, _("%s (Default)") %
                 label) if value == utils.IPV6_DEFAULT_MODE else (value, label)
                for value, label in utils.IPV6_MODE_CHOICES]

    def _convert_ip_address(self, ip, field_name):
        try:
            return netaddr.IPAddress(ip)
        except (netaddr.AddrFormatError, ValueError):
            msg = (_('%(field_name)s: Invalid IP address (value=%(ip)s)') % {
                'field_name': field_name,
                'ip': ip
            })
            raise forms.ValidationError(msg)

    def _convert_ip_network(self, network, field_name):
        try:
            return netaddr.IPNetwork(network)
        except (netaddr.AddrFormatError, ValueError):
            msg = (
                _('%(field_name)s: Invalid IP address (value=%(network)s)') % {
                    'field_name': field_name,
                    'network': network
                })
            raise forms.ValidationError(msg)

    def _check_allocation_pools(self, allocation_pools):
        for p in allocation_pools.splitlines():
            p = p.strip()
            if not p:
                continue
            pool = p.split(',')
            if len(pool) != 2:
                msg = _('Start and end addresses must be specified '
                        '(value=%s)') % p
                raise forms.ValidationError(msg)
            start, end = [
                self._convert_ip_address(ip, "allocation_pools") for ip in pool
            ]
            if start > end:
                msg = _('Start address is larger than end address '
                        '(value=%s)') % p
                raise forms.ValidationError(msg)

    def _check_dns_nameservers(self, dns_nameservers):
        for ns in dns_nameservers.splitlines():
            ns = ns.strip()
            if not ns:
                continue
            self._convert_ip_address(ns, "dns_nameservers")

    def _check_host_routes(self, host_routes):
        for r in host_routes.splitlines():
            r = r.strip()
            if not r:
                continue
            route = r.split(',')
            if len(route) != 2:
                msg = _('Host Routes format error: '
                        'Destination CIDR and nexthop must be specified '
                        '(value=%s)') % r
                raise forms.ValidationError(msg)
            self._convert_ip_network(route[0], "host_routes")
            self._convert_ip_address(route[1], "host_routes")

    def clean(self):
        cleaned_data = super().clean()
        self._check_allocation_pools(cleaned_data.get('allocation_pools'))
        self._check_host_routes(cleaned_data.get('host_routes'))
        self._check_dns_nameservers(cleaned_data.get('dns_nameservers'))
        return cleaned_data
class SetInstanceDetailsAction(workflows.Action):
    availability_zone = forms.ThemableChoiceField(label=_("Availability Zone"),
                                                  required=False)

    name = forms.CharField(label=_("Instance Name"), max_length=255)

    flavor = forms.ThemableChoiceField(label=_("Flavor"),
                                       help_text=_("Size of image to launch."))

    count = forms.IntegerField(label=_("Number of Instances"),
                               min_value=1,
                               initial=1)

    source_type = forms.ThemableChoiceField(label=_("Instance Boot Source"),
                                            help_text=_(
                                                "Choose Your Boot Source "
                                                "Type."))

    instance_snapshot_id = forms.ThemableChoiceField(
        label=_("Instance Snapshot"), required=False)

    volume_id = forms.ThemableChoiceField(label=_("Volume"), required=False)

    volume_snapshot_id = forms.ThemableChoiceField(label=_("Volume Snapshot"),
                                                   required=False)

    image_id = forms.ChoiceField(
        label=_("Image Name"),
        required=False,
        widget=forms.ThemableSelectWidget(data_attrs=('volume_size', ),
                                          transform=lambda x:
                                          ("%s (%s)" %
                                           (x.name, filesizeformat(x.bytes)))))

    volume_size = forms.IntegerField(label=_("Device size (GB)"),
                                     initial=1,
                                     min_value=0,
                                     required=False,
                                     help_text=_("Volume size in gigabytes "
                                                 "(integer value)."))

    device_name = forms.CharField(label=_("Device Name"),
                                  required=False,
                                  initial="vda",
                                  help_text=_("Volume mount point (e.g. 'vda' "
                                              "mounts at '/dev/vda'). Leave "
                                              "this field blank to let the "
                                              "system choose a device name "
                                              "for you."))

    vol_delete_on_instance_delete = forms.BooleanField(
        label=_("Delete Volume on Instance Delete"),
        initial=False,
        required=False,
        help_text=_("Delete volume when the instance is deleted"))

    class Meta(object):
        name = _("Details")
        help_text_template = ("project/instances/" "_launch_details_help.html")

    def __init__(self, request, context, *args, **kwargs):
        self._init_images_cache()
        self.request = request
        self.context = context
        super(SetInstanceDetailsAction, self).__init__(request, context, *args,
                                                       **kwargs)

        # Hide the device field if the hypervisor doesn't support it.
        if not nova.can_set_mount_point():
            self.fields['device_name'].widget = forms.widgets.HiddenInput()

        source_type_choices = [
            ('', _("Select source")),
            ("image_id", _("Boot from image")),
            ("instance_snapshot_id", _("Boot from snapshot")),
        ]
        if cinder.is_volume_service_enabled(request):
            source_type_choices.append(("volume_id", _("Boot from volume")))

            try:
                if api.nova.extension_supported("BlockDeviceMappingV2Boot",
                                                request):
                    source_type_choices.append(
                        ("volume_image_id",
                         _("Boot from image (creates a new volume)")))
            except Exception:
                exceptions.handle(
                    request, _('Unable to retrieve extensions '
                               'information.'))

            source_type_choices.append(
                ("volume_snapshot_id",
                 _("Boot from volume snapshot (creates a new volume)")))
        self.fields['source_type'].choices = source_type_choices

    @memoized.memoized_method
    def _get_flavor(self, flavor_id):
        try:
            # We want to retrieve details for a given flavor,
            # however flavor_list uses a memoized decorator
            # so it is used instead of flavor_get to reduce the number
            # of API calls.
            flavors = instance_utils.flavor_list(self.request)
            flavor = [x for x in flavors if x.id == flavor_id][0]
        except IndexError:
            flavor = None
        return flavor

    @memoized.memoized_method
    def _get_image(self, image_id):
        try:
            # We want to retrieve details for a given image,
            # however get_available_images uses a cache of image list,
            # so it is used instead of image_get to reduce the number
            # of API calls.
            images = image_utils.get_available_images(
                self.request, self.context.get('project_id'),
                self._images_cache)
            image = [x for x in images if x.id == image_id][0]
        except IndexError:
            image = None
        return image

    def _check_quotas(self, cleaned_data):
        count = cleaned_data.get('count', 1)

        # Prevent launching more instances than the quota allows
        usages = quotas.tenant_quota_usages(self.request)
        available_count = usages['instances']['available']
        if available_count < count:
            error_message = ungettext_lazy(
                'The requested instance cannot be launched as you only '
                'have %(avail)i of your quota available. ',
                'The requested %(req)i instances cannot be launched as you '
                'only have %(avail)i of your quota available.', count)
            params = {'req': count, 'avail': available_count}
            raise forms.ValidationError(error_message % params)

        source_type = cleaned_data.get('source_type')
        if source_type in ('volume_image_id', 'volume_snapshot_id'):
            available_volume = usages['volumes']['available']
            if available_volume < count:
                msg = (_('The requested instance cannot be launched. '
                         'Requested volume exceeds quota: Available: '
                         '%(avail)s, Requested: %(req)s.') % {
                             'avail': available_volume,
                             'req': count
                         })
                raise forms.ValidationError(msg)

        flavor_id = cleaned_data.get('flavor')
        flavor = self._get_flavor(flavor_id)

        count_error = []
        # Validate cores and ram.
        available_cores = usages['cores']['available']
        if flavor and available_cores < count * flavor.vcpus:
            count_error.append(
                _("Cores(Available: %(avail)s, "
                  "Requested: %(req)s)") % {
                      'avail': available_cores,
                      'req': count * flavor.vcpus
                  })

        available_ram = usages['ram']['available']
        if flavor and available_ram < count * flavor.ram:
            count_error.append(
                _("RAM(Available: %(avail)s, "
                  "Requested: %(req)s)") % {
                      'avail': available_ram,
                      'req': count * flavor.ram
                  })

        if count_error:
            value_str = ", ".join(count_error)
            msg = (_('The requested instance cannot be launched. '
                     'The following requested resource(s) exceed '
                     'quota(s): %s.') % value_str)
            if count == 1:
                self._errors['flavor'] = self.error_class([msg])
            else:
                self._errors['count'] = self.error_class([msg])

    def _check_flavor_for_image(self, cleaned_data):
        # Prevents trying to launch an image needing more resources.
        image_id = cleaned_data.get('image_id')
        image = self._get_image(image_id)
        flavor_id = cleaned_data.get('flavor')
        flavor = self._get_flavor(flavor_id)
        if not image or not flavor:
            return
        props_mapping = (("min_ram", "ram"), ("min_disk", "disk"))
        for iprop, fprop in props_mapping:
            if getattr(image, iprop) > 0 and \
                    getattr(image, iprop) > getattr(flavor, fprop):
                msg = (_("The flavor '%(flavor)s' is too small "
                         "for requested image.\n"
                         "Minimum requirements: "
                         "%(min_ram)s MB of RAM and "
                         "%(min_disk)s GB of Root Disk.") % {
                             'flavor': flavor.name,
                             'min_ram': image.min_ram,
                             'min_disk': image.min_disk
                         })
                self._errors['image_id'] = self.error_class([msg])
                break  # Not necessary to continue the tests.

    def _check_volume_for_image(self, cleaned_data):
        image_id = cleaned_data.get('image_id')
        image = self._get_image(image_id)
        volume_size = cleaned_data.get('volume_size')
        if not image or not volume_size:
            return
        volume_size = int(volume_size)
        img_gigs = functions.bytes_to_gigabytes(image.size)
        smallest_size = max(img_gigs, image.min_disk)
        if volume_size < smallest_size:
            msg = (_("The Volume size is too small for the"
                     " '%(image_name)s' image and has to be"
                     " greater than or equal to "
                     "'%(smallest_size)d' GB.") % {
                         'image_name': image.name,
                         'smallest_size': smallest_size
                     })
            self._errors['volume_size'] = self.error_class([msg])

    def _check_source_image(self, cleaned_data):
        if not cleaned_data.get('image_id'):
            msg = _("You must select an image.")
            self._errors['image_id'] = self.error_class([msg])
        else:
            self._check_flavor_for_image(cleaned_data)

    def _check_source_volume_image(self, cleaned_data):
        volume_size = self.data.get('volume_size', None)
        if not volume_size:
            msg = _("You must set volume size")
            self._errors['volume_size'] = self.error_class([msg])
        if float(volume_size) <= 0:
            msg = _("Volume size must be greater than 0")
            self._errors['volume_size'] = self.error_class([msg])
        if not cleaned_data.get('image_id'):
            msg = _("You must select an image.")
            self._errors['image_id'] = self.error_class([msg])
            return
        else:
            self._check_flavor_for_image(cleaned_data)
            self._check_volume_for_image(cleaned_data)

    def _check_source_instance_snapshot(self, cleaned_data):
        # using the array form of get blows up with KeyError
        # if instance_snapshot_id is nil
        if not cleaned_data.get('instance_snapshot_id'):
            msg = _("You must select a snapshot.")
            self._errors['instance_snapshot_id'] = self.error_class([msg])

    def _check_source_volume(self, cleaned_data):
        if not cleaned_data.get('volume_id'):
            msg = _("You must select a volume.")
            self._errors['volume_id'] = self.error_class([msg])
        # Prevent launching multiple instances with the same volume.
        # TODO(gabriel): is it safe to launch multiple instances with
        # a snapshot since it should be cloned to new volumes?
        count = cleaned_data.get('count', 1)
        if count > 1:
            msg = _('Launching multiple instances is only supported for '
                    'images and instance snapshots.')
            raise forms.ValidationError(msg)

    def _check_source_volume_snapshot(self, cleaned_data):
        if not cleaned_data.get('volume_snapshot_id'):
            msg = _("You must select a snapshot.")
            self._errors['volume_snapshot_id'] = self.error_class([msg])

    def _check_source(self, cleaned_data):
        # Validate our instance source.
        source_type = self.data.get('source_type', None)
        source_check_methods = {
            'image_id': self._check_source_image,
            'volume_image_id': self._check_source_volume_image,
            'instance_snapshot_id': self._check_source_instance_snapshot,
            'volume_id': self._check_source_volume,
            'volume_snapshot_id': self._check_source_volume_snapshot
        }
        check_method = source_check_methods.get(source_type)
        if check_method:
            check_method(cleaned_data)

    def clean(self):
        cleaned_data = super(SetInstanceDetailsAction, self).clean()

        self._check_quotas(cleaned_data)
        self._check_source(cleaned_data)

        return cleaned_data

    def populate_flavor_choices(self, request, context):
        return instance_utils.flavor_field_data(request, False)

    def populate_availability_zone_choices(self, request, context):
        try:
            zones = api.nova.availability_zone_list(request)
        except Exception:
            zones = []
            exceptions.handle(request,
                              _('Unable to retrieve availability zones.'))

        zone_list = [(zone.zoneName, zone.zoneName) for zone in zones
                     if zone.zoneState['available']]
        zone_list.sort()
        if not zone_list:
            zone_list.insert(0, ("", _("No availability zones found")))
        elif len(zone_list) > 1:
            zone_list.insert(0, ("", _("Any Availability Zone")))
        return zone_list

    def get_help_text(self, extra_context=None):
        extra = {} if extra_context is None else dict(extra_context)
        try:
            extra['usages'] = api.nova.tenant_absolute_limits(self.request,
                                                              reserved=True)
            extra['usages_json'] = json.dumps(extra['usages'])
            flavors = json.dumps(
                [f._info for f in instance_utils.flavor_list(self.request)])
            extra['flavors'] = flavors
            images = image_utils.get_available_images(
                self.request, self.initial['project_id'], self._images_cache)
            if images is not None:
                attrs = [{
                    'id': i.id,
                    'min_disk': getattr(i, 'min_disk', 0),
                    'min_ram': getattr(i, 'min_ram', 0),
                    'size': functions.bytes_to_gigabytes(i.size)
                } for i in images]
                extra['images'] = json.dumps(attrs)

        except Exception:
            exceptions.handle(self.request,
                              _("Unable to retrieve quota information."))
        return super(SetInstanceDetailsAction, self).get_help_text(extra)

    def _init_images_cache(self):
        if not hasattr(self, '_images_cache'):
            self._images_cache = {}

    def _get_volume_display_name(self, volume):
        if hasattr(volume, "volume_id"):
            vol_type = "snap"
            visible_label = _("Snapshot")
        else:
            vol_type = "vol"
            visible_label = _("Volume")
        return (("%s:%s" % (volume.id, vol_type)),
                (_("%(name)s - %(size)s GB (%(label)s)") % {
                    'name': volume.name,
                    'size': volume.size,
                    'label': visible_label
                }))

    def populate_image_id_choices(self, request, context):
        choices = []
        images = image_utils.get_available_images(request,
                                                  context.get('project_id'),
                                                  self._images_cache)
        for image in images:
            image.bytes = getattr(image, 'virtual_size', None) or image.size
            image.volume_size = max(image.min_disk,
                                    functions.bytes_to_gigabytes(image.bytes))
            choices.append((image.id, image))
            if context.get('image_id') == image.id and \
                    'volume_size' not in context:
                context['volume_size'] = image.volume_size
        if choices:
            choices.sort(key=lambda c: c[1].name or '')
            choices.insert(0, ("", _("Select Image")))
        else:
            choices.insert(0, ("", _("No images available")))
        return choices

    def populate_instance_snapshot_id_choices(self, request, context):
        images = image_utils.get_available_images(request,
                                                  context.get('project_id'),
                                                  self._images_cache)
        choices = [(image.id, image.name) for image in images
                   if image.properties.get("image_type", '') == "snapshot"]
        if choices:
            choices.sort(key=operator.itemgetter(1))
            choices.insert(0, ("", _("Select Instance Snapshot")))
        else:
            choices.insert(0, ("", _("No snapshots available")))
        return choices

    def populate_volume_id_choices(self, request, context):
        volumes = []
        try:
            if cinder.is_volume_service_enabled(request):
                available = api.cinder.VOLUME_STATE_AVAILABLE
                volumes = [
                    self._get_volume_display_name(v)
                    for v in cinder.volume_list(
                        self.request,
                        search_opts=dict(status=available, bootable=True))
                ]
        except Exception:
            exceptions.handle(self.request,
                              _('Unable to retrieve list of volumes.'))
        if volumes:
            volumes.insert(0, ("", _("Select Volume")))
        else:
            volumes.insert(0, ("", _("No volumes available")))
        return volumes

    def populate_volume_snapshot_id_choices(self, request, context):
        snapshots = []
        try:
            if cinder.is_volume_service_enabled(request):
                available = api.cinder.VOLUME_STATE_AVAILABLE
                snapshots = [
                    self._get_volume_display_name(s)
                    for s in cinder.volume_snapshot_list(
                        self.request, search_opts=dict(status=available))
                ]
        except Exception:
            exceptions.handle(
                self.request,
                _('Unable to retrieve list of volume '
                  'snapshots.'))
        if snapshots:
            snapshots.insert(0, ("", _("Select Volume Snapshot")))
        else:
            snapshots.insert(0, ("", _("No volume snapshots available")))
        return snapshots
Example #29
0
class CreateImageForm(CreateParent):
    name = forms.CharField(max_length=255, label=_("Name"))
    description = forms.CharField(max_length=255,
                                  widget=forms.Textarea(attrs={'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)',
        'placeholder': 'http://example.com/image.img'
    }
    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 = 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.ThemableSelectWidget(transform=lambda x: "%s (%s)" % (
            x.name, defaultfilters.filesizeformat(x.size))))
    ramdisk = forms.ChoiceField(
        label=_('Ramdisk'),
        required=False,
        widget=forms.ThemableSelectWidget(transform=lambda x: "%s (%s)" % (
            x.name, defaultfilters.filesizeformat(x.size))))
    disk_format = forms.ChoiceField(
        label=_('Format'),
        choices=[],
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'ng-model': 'ctrl.diskFormat'
        }))
    architecture = forms.CharField(
        max_length=255,
        label=_("Architecture"),
        help_text=_('CPU architecture of the image.'),
        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"),
        help_text=_('Make the image visible across projects.'),
        required=False)
    protected = forms.BooleanField(
        label=_("Protected"),
        help_text=_('Prevent the deletion of the image.'),
        required=False)

    def __init__(self, request, *args, **kwargs):
        super(CreateImageForm, self).__init__(request, *args, **kwargs)

        if (api.glance.get_image_upload_mode() == 'off' 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()

        # GlanceV2 feature removals
        if api.glance.VERSIONS.active >= 2:
            # NOTE: GlanceV2 doesn't support copy-from feature, sorry!
            self._hide_is_copying()
            if not getattr(settings, 'IMAGES_ALLOW_LOCATION', False):
                self._hide_url_source_type()
                if (api.glance.get_image_upload_mode() == 'off'
                        or not policy.check(
                            (("image", "upload_image"), ), request)):
                    # Neither setting a location nor uploading image data is
                    # allowed, so throw an error.
                    msg = _('The current Horizon settings indicate no valid '
                            'image creation methods are available. Providing '
                            'an image location and/or uploading from the '
                            'local file system must be allowed to support '
                            'image creation.')
                    messages.error(request, msg)
                    raise ValidationError(msg)
        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 _hide_is_copying(self):
        self.fields['is_copying'].widget = HiddenInput()
        self.fields['is_copying'].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...
        source_type = data.get('source_type', None)
        image_file = data.get('image_file', None)
        image_url = data.get('image_url', None)

        if not image_url and not image_file:
            msg = _("An image file or an external location must be specified.")
            if source_type == 'file':
                raise ValidationError({'image_file': [
                    msg,
                ]})
            else:
                raise ValidationError({'image_url': [
                    msg,
                ]})
        else:
            return data

    def handle(self, request, data):
        meta = create_image_metadata(data)

        # Add image source file or URL to metadata
        if (api.glance.get_image_upload_mode() != 'off' and policy.check(
            (("image", "upload_image"), ), request)
                and data.get('image_file', None)):
            meta['data'] = data['image_file']
        elif data.get('is_copying'):
            meta['copy_from'] = data['image_url']
        else:
            meta['location'] = data['image_url']

        try:
            image = api.glance.image_create(request, **meta)
            messages.info(
                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
Example #30
0
class CreateForm(forms.SelfHandlingForm):
    # General fields
    name = forms.CharField(label=_("Lease Name"), required=True, max_length=80)
    start_date = forms.DateTimeField(
        label=_("Start Date"),
        required=False,
        help_text=_('Enter YYYY-MM-DD HH:MM or blank for now'),
        input_formats=['%Y-%m-%d %H:%M'],
        widget=forms.DateTimeInput(
            attrs={'placeholder': 'YYYY-MM-DD HH:MM (blank for now)'}))
    end_date = forms.DateTimeField(
        label=_("End Date"),
        required=False,
        help_text=_('Enter YYYY-MM-DD HH:MM or blank for Start Date + 24h'),
        input_formats=['%Y-%m-%d %H:%M'],
        widget=forms.DateTimeInput(
            attrs={
                'placeholder': 'YYYY-MM-DD HH:MM (blank for Start Date + '
                '24h)'
            }))
    resource_type = forms.ChoiceField(
        label=_("Resource Type"),
        required=True,
        choices=(('host', _('Physical Host')), ('instance',
                                                _('Virtual Instance'))),
        widget=forms.ThemableSelectWidget(attrs={
            'class': 'switchable',
            'data-slug': 'source'
        }))

    # Fields for host reservation
    min_hosts = forms.IntegerField(
        label=_('Minimum Number of Hosts'),
        required=False,
        help_text=_('Enter the minimum number of hosts to reserve.'),
        min_value=1,
        initial=1,
        widget=forms.NumberInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-host': _('Minimum Number of Hosts')
            }))
    max_hosts = forms.IntegerField(
        label=_('Maximum Number of Hosts'),
        required=False,
        help_text=_('Enter the maximum number of hosts to reserve.'),
        min_value=1,
        initial=1,
        widget=forms.NumberInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-host': _('Maximum Number of Hosts')
            }))
    hypervisor_properties = forms.CharField(
        label=_("Hypervisor Properties"),
        required=False,
        help_text=_('Enter properties of a hypervisor to reserve.'),
        max_length=255,
        widget=forms.TextInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-host': _('Hypervisor Properties'),
                'placeholder': 'e.g. [">=", "$vcpus", "2"]'
            }))

    # Fields for instance reservation
    amount = forms.IntegerField(
        label=_('Instance Count'),
        required=False,
        help_text=_('Enter the number of instances to reserve.'),
        min_value=1,
        initial=1,
        widget=forms.NumberInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-instance': _('Instance Count')
            }))
    vcpus = forms.IntegerField(
        label=_('Number of VCPUs'),
        required=False,
        help_text=_('Enter the number of VCPUs per instance.'),
        min_value=1,
        initial=1,
        widget=forms.NumberInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-instance': _('Number of VCPUs')
            }))
    memory_mb = forms.IntegerField(
        label=_('RAM (MB)'),
        required=False,
        help_text=_('Enter the size of RAM (MB) per instance'),
        min_value=1,
        initial=1,
        widget=forms.NumberInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-instance': _('RAM (MB)')
            }))
    disk_gb = forms.IntegerField(
        label=_('Root Disk (GB)'),
        required=False,
        help_text=_('Enter the root disk size (GB) per instance'),
        min_value=0,
        initial=0,
        widget=forms.NumberInput(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-instance': _('Root Disk (GB)')
            }))
    affinity = forms.ChoiceField(
        label=_("Affinity Rule"),
        required=False,
        choices=(
            (None, _('None')),
            (True, _('Affinity')),
            (False, _('Anti-Affinity')),
        ),
        initial=None,
        widget=forms.ThemableSelectWidget(
            attrs={
                'class': 'switched',
                'data-switch-on': 'source',
                'data-source-instance': _('Affinity Rule')
            }))

    # Fields for both of host and instance reservations
    resource_properties = forms.CharField(
        label=_("Resource Properties"),
        required=False,
        help_text=_('Enter properties of a resource to reserve.'),
        max_length=255,
        widget=forms.TextInput(
            attrs={'placeholder': 'e.g. ["==", "$extra_key", "extra_value"]'}))

    def handle(self, request, data):
        if data['resource_type'] == 'host':
            reservations = [{
                'resource_type':
                'physical:host',
                'min':
                data['min_hosts'],
                'max':
                data['max_hosts'],
                'hypervisor_properties': (data['hypervisor_properties'] or ''),
                'resource_properties':
                data['resource_properties'] or ''
            }]
        elif data['resource_type'] == 'instance':
            reservations = [{
                'resource_type':
                'virtual:instance',
                'amount':
                data['amount'],
                'vcpus':
                data['vcpus'],
                'memory_mb':
                data['memory_mb'],
                'disk_gb':
                data['disk_gb'],
                'affinity':
                data['affinity'],
                'resource_properties':
                data['resource_properties'] or ''
            }]

        events = []

        try:
            api.client.lease_create(
                request, data['name'],
                data['start_date'].strftime('%Y-%m-%d %H:%M'),
                data['end_date'].strftime('%Y-%m-%d %H:%M'), reservations,
                events)
            messages.success(
                request,
                _('Lease %s was successfully '
                  'created.') % data['name'])
            return True
        except Exception as e:
            LOG.error('Error submitting lease: %s', e)
            exceptions.handle(request,
                              message='An error occurred while creating this '
                              'lease: %s. Please try again.' % e)

    def clean(self):
        cleaned_data = super(CreateForm, self).clean()
        local = timezone(
            self.request.session.get(
                'django_timezone',
                self.request.COOKIES.get('django_timezone', 'UTC')))

        if cleaned_data['start_date']:
            cleaned_data['start_date'] = local.localize(
                cleaned_data['start_date'].replace(tzinfo=None)).astimezone(
                    timezone('UTC'))
        else:
            cleaned_data['start_date'] = datetime.datetime.utcnow()
        if cleaned_data['end_date']:
            cleaned_data['end_date'] = local.localize(
                cleaned_data['end_date'].replace(tzinfo=None)).astimezone(
                    timezone('UTC'))
        else:
            cleaned_data['end_date'] = (cleaned_data['start_date'] +
                                        datetime.timedelta(days=1))