class UpdateSubnetInfoAction(CreateSubnetInfoAction): cidr = fields.IPField( label=_("Network Address"), required=False, initial="", widget=forms.TextInput(attrs={'readonly': 'readonly'}), help_text=_("Network address in CIDR format " "(e.g. 192.168.0.0/24)"), version=fields.IPv4 | fields.IPv6, mask=True) ipam = forms.DynamicTypedChoiceField(label=_("IPAM"), required=False, empty_value=None, widget=forms.HiddenInput(), help_text=_( "Choose IPAM that will be " "associated with the IP Block")) # NOTE(amotoki): When 'disabled' attribute is set for the ChoiceField # and ValidationError is raised for POST request, the initial value of # the ip_version ChoiceField is not set in the re-displayed form # As a result, 'IPv4' is displayed even when IPv6 is used if # ValidationError is detected. In addition 'required=True' check complains # when re-POST since the value of the ChoiceField is not set. # Thus now I use HiddenInput for the ip_version ChoiceField as a work # around. ip_version = forms.ChoiceField( choices=[(4, 'IPv4'), (6, 'IPv6')], #widget=forms.Select( # attrs={'disabled': 'disabled'}), widget=forms.HiddenInput(), label=_("IP Version")) gateway_ip = fields.IPField( label=_("Gateway IP (optional)"), required=False, initial="", help_text=_("IP address of Gateway (e.g. 192.168.0.254). " "You need to specify an explicit address " "to set the gateway. " "If you want to use no gateway, " "check 'Disable Gateway' below."), version=fields.IPv4 | fields.IPv6, mask=False) no_gateway = forms.BooleanField(label=_("Disable Gateway"), initial=False, required=False) class Meta: name = ("Subnet") help_text = _('You can update a subnet associated with the ' 'network. Advanced configuration are available ' 'at "Subnet Detail" tab.') def clean(self): cleaned_data = workflows.Action.clean(self) self._check_subnet_data(cleaned_data, is_create=False) return cleaned_data
class AssociateIPAction(workflows.Action): ip_id = forms.DynamicTypedChoiceField(label=_("IP Address"), coerce=int, empty_value=None, add_item_link=ALLOCATE_URL) instance_id = forms.ChoiceField(label=_("Instance")) class Meta: name = _("IP Address") help_text = _("Select the IP address you wish to associate with " "the selected instance.") def populate_ip_id_choices(self, request, context): try: ips = api.nova.tenant_floating_ip_list(self.request) except: redirect = reverse('horizon:nova:access_and_security:index') exceptions.handle(self.request, _('Unable to retrieve floating IP addresses.'), redirect=redirect) options = sorted([(ip.id, ip.ip) for ip in ips if not ip.instance_id]) if options: options.insert(0, ("", _("Select an IP address"))) else: options = [("", _("No IP addresses available"))] return options def populate_instance_id_choices(self, request, context): try: servers = api.nova.server_list(self.request) except: redirect = reverse('horizon:nova:access_and_security:index') exceptions.handle(self.request, _('Unable to retrieve instance list.'), redirect=redirect) instances = [] for server in servers: server_name = "%s (%s)" % (server.name, server.id) instances.append((server.id, server_name)) # Sort instances for easy browsing instances = sorted(instances, key=lambda x: x[1]) if instances: instances.insert(0, ("", _("Select an instance"))) else: instances = (("", _("No instances available")), ) return instances
class AssociateIPAction(workflows.Action): ip_id = forms.DynamicTypedChoiceField(label=_("IP Address"), coerce=get_int_or_uuid, empty_value=None, add_item_link=ALLOCATE_URL) instance_id = forms.ChoiceField(label=_("Instance")) class Meta: name = _("IP Address") help_text = _("Select the IP address you wish to associate with " "the selected instance.") def __init__(self, *args, **kwargs): super(AssociateIPAction, self).__init__(*args, **kwargs) if api.base.is_service_enabled(self.request, 'network'): label = _("Port to be associated") else: label = _("Instance to be associated") self.fields['instance_id'].label = label # If AssociateIP is invoked from instance menu, instance_id parameter # is passed in URL. In Neutron based Floating IP implementation # an association target is not an instance but a port, so we need # to get an association target based on a received instance_id # and set the initial value of instance_id ChoiceField. q_instance_id = self.request.GET.get('instance_id') if q_instance_id: target_id = api.network.floating_ip_target_get_by_instance( self.request, q_instance_id) self.initial['instance_id'] = target_id def populate_ip_id_choices(self, request, context): try: ips = api.network.tenant_floating_ip_list(self.request) except: redirect = reverse('horizon:project:access_and_security:index') exceptions.handle(self.request, _('Unable to retrieve floating IP addresses.'), redirect=redirect) options = sorted([(ip.id, ip.ip) for ip in ips if not ip.port_id]) if options: options.insert(0, ("", _("Select an IP address"))) else: options = [("", _("No IP addresses available"))] return options def populate_instance_id_choices(self, request, context): try: targets = api.network.floating_ip_target_list(self.request) except: redirect = reverse('horizon:project:access_and_security:index') exceptions.handle(self.request, _('Unable to retrieve instance list.'), redirect=redirect) instances = [] for target in targets: instances.append((target.id, target.name)) # Sort instances for easy browsing instances = sorted(instances, key=lambda x: x[1]) neutron_enabled = api.base.is_service_enabled(request, 'network') if instances: if neutron_enabled: label = _("Select a port") else: label = _("Select an instance") instances.insert(0, ("", label)) else: if neutron_enabled: label = _("No ports available") else: label = _("No instances available") instances = (("", label),) return instances
class CreateSubnetInfoAction(workflows.Action): subnet_name = forms.CharField(max_length=255, widget=forms.TextInput(attrs={ }), label=_("Subnet Name"), required=False) ipam = forms.DynamicTypedChoiceField(label=_("IPAM"), required=False, empty_value=None, add_item_link=IPAM_CREATE_URL, help_text=_("Choose IPAM that will be " "associated with the IP Block")) 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-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="", 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(CreateSubnetInfoAction, self).__init__(request, context, *args, **kwargs) if 'with_subnet' in context: self.fields['with_subnet'] = forms.BooleanField( initial=context['with_subnet'], required=False, widget=forms.HiddenInput() ) if not getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {}).get('enable_ipv6', True): self.fields['ip_version'].widget = forms.HiddenInput() self.fields['ip_version'].initial = 4 try: if api.neutron.is_extension_supported(request, 'subnet_allocation'): self.fields['subnetpool'].choices = \ self.get_subnetpool_choices(request) else: self.hide_subnetpool_choices() except Exception: self.hide_subnetpool_choices() msg = _('Unable to initialize subnetpools') exceptions.handle(request, msg) if len(self.fields['subnetpool'].choices) > 1: # Pre-populate prefixlen choices to satisfy Django # ChoiceField Validation. This is overridden w/data from # subnetpool on select. self.fields['prefixlen'].choices = \ zip(list(range(0, 128 + 1)), list(range(0, 128 + 1))) # Populate data-fields for switching the prefixlen field # when user selects a subnetpool other than # "Provider default pool" for (id, name) in self.fields['subnetpool'].choices: if not len(id): continue key = 'data-subnetpool-' + id self.fields['prefixlen'].widget.attrs[key] = \ _('Network Mask') else: self.hide_subnetpool_choices() # Create IPAM choices tenant_id = self.request.user.tenant_id try: ipams = ipam_summary(self.request) if ipams: ipam_choices = [(ipam.id, "{0} ({1})".format(ipam.fq_name[2], ipam.fq_name[1])) for ipam in ipams] ipam_choices.append(('None', 'None')) else: ipam_choices = [('None', 'Create a new IPAM')] except: ipam_choices = [('None', 'None')] exceptions.handle(self.request, _('Unable to retrieve ipam list')) self.fields['ipam'].choices = ipam_choices def get_subnetpool_choices(self, request): subnetpool_choices = [('', _('Select a pool'))] for subnetpool in api.neutron.subnetpool_list(request): subnetpool_choices.append((subnetpool.id, subnetpool)) return subnetpool_choices def hide_subnetpool_choices(self): self.fields['address_source'].widget = forms.HiddenInput() self.fields['subnetpool'].choices = [] self.fields['subnetpool'].widget = forms.HiddenInput() self.fields['prefixlen'].widget = forms.HiddenInput() def _check_subnet_range(self, subnet, allow_cidr): allowed_net = netaddr.IPNetwork(allow_cidr) return subnet in allowed_net def _check_cidr_allowed(self, ip_version, subnet): if not self.check_subnet_range: return allowed_cidr = getattr(settings, "ALLOWED_PRIVATE_SUBNET_CIDR", {}) version_str = 'ipv%s' % ip_version allowed_ranges = allowed_cidr.get(version_str, []) if allowed_ranges: under_range = any(self._check_subnet_range(subnet, allowed_range) for allowed_range in allowed_ranges) if not under_range: range_str = ', '.join(allowed_ranges) msg = (_("CIDRs allowed for user private %(ip_ver)s " "networks are %(allowed)s.") % {'ip_ver': '%s' % version_str, 'allowed': range_str}) raise forms.ValidationError(msg) def _check_subnet_data(self, cleaned_data, is_create=True): cidr = cleaned_data.get('cidr') ipam = cleaned_data.get('ipam') ip_version = int(cleaned_data.get('ip_version')) gateway_ip = cleaned_data.get('gateway_ip') no_gateway = cleaned_data.get('no_gateway') address_source = cleaned_data.get('address_source') subnetpool = cleaned_data.get('subnetpool') if not subnetpool and address_source == 'subnetpool': msg = _('Specify "Address pool" or select ' '"Enter Network Address manually" and specify ' '"Network Address".') raise forms.ValidationError(msg) if not cidr and address_source != 'subnetpool': msg = _('Specify "Network Address" or ' 'clear "Create Subnet" checkbox in previous step.') raise forms.ValidationError(msg) if cidr: subnet = netaddr.IPNetwork(cidr) if subnet.version != ip_version: msg = _('Network Address and IP version are inconsistent.') raise forms.ValidationError(msg) if (ip_version == 4 and subnet.prefixlen == 32) or \ (ip_version == 6 and subnet.prefixlen == 128): msg = _("The subnet in the Network Address is " "too small (/%s).") % subnet.prefixlen self._errors['cidr'] = self.error_class([msg]) self._check_cidr_allowed(ip_version, subnet) if not no_gateway and gateway_ip: if netaddr.IPAddress(gateway_ip).version is not ip_version: msg = _('Gateway IP and IP version are inconsistent.') raise forms.ValidationError(msg) if not is_create and not no_gateway and not gateway_ip: msg = _('Specify IP address of gateway or ' 'check "Disable Gateway" checkbox.') raise forms.ValidationError(msg) def clean(self): cleaned_data = super(CreateSubnetInfoAction, self).clean() with_subnet = cleaned_data.get('with_subnet') if not with_subnet: return cleaned_data self._check_subnet_data(cleaned_data) return cleaned_data
class CreateSubnetInfoAction(workflows.Action): with_subnet = forms.BooleanField(label=_("Create Subnet"), initial=True, required=False) subnet_name = forms.CharField(max_length=255, label=_("Subnet Name"), required=False) ipam = forms.DynamicTypedChoiceField(label=_("IPAM"), required=False, empty_value=None, add_item_link=IPAM_CREATE_URL, help_text=_( "Choose IPAM that will be " "associated with the IP Block")) cidr = fields.IPField(label=_("Network Address"), required=False, initial="", help_text=_("Network address in CIDR format " "(e.g. 192.168.0.0/24)"), version=fields.IPv4 | fields.IPv6, mask=True) ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')], label=_("IP Version")) gateway_ip = fields.IPField( label=_("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). " "If you use the default, leave blank. " "If you want to use no gateway, " "check 'Disable Gateway' below."), version=fields.IPv4, mask=False) no_gateway = forms.BooleanField(label=_("Disable Gateway"), initial=False, required=False) def __init__(self, request, *args, **kwargs): super(CreateSubnetInfoAction, self).__init__(request, *args, **kwargs) tenant_id = self.request.user.tenant_id try: ipams = ipam_summary(self.request) if ipams: ipam_choices = [(ipam.id, "{0} ({1})".format(ipam.fq_name[2], ipam.fq_name[1])) for ipam in ipams] ipam_choices.append(('None', 'None')) else: ipam_choices = [('None', 'Create a new IPAM')] except: ipam_choices = [('None', 'None')] exceptions.handle(self.request, _('Unable to retrieve ipam list')) self.fields['ipam'].choices = ipam_choices class Meta: name = _("Subnet") help_text = _('You can create a subnet associated with the new ' 'network, in which case "Network Address" must be ' 'specified. If you wish to create a network WITHOUT a ' 'subnet, uncheck the "Create Subnet" checkbox.') def _check_subnet_data(self, cleaned_data, is_create=True): cidr = cleaned_data.get('cidr') ipam = cleaned_data.get('ipam') ip_version = int(cleaned_data.get('ip_version')) gateway_ip = cleaned_data.get('gateway_ip') no_gateway = cleaned_data.get('no_gateway') if not cidr: msg = _('Specify "Network Address" or ' 'clear "Create Subnet" checkbox.') raise forms.ValidationError(msg) if cidr: subnet = netaddr.IPNetwork(cidr) if subnet.version != ip_version: msg = _('Network Address and IP version are inconsistent.') raise forms.ValidationError(msg) if (ip_version == 4 and subnet.prefixlen == 32) or \ (ip_version == 6 and subnet.prefixlen == 128): msg = _( "The subnet in the Network Address is too small (/%s)." % subnet.prefixlen) raise forms.ValidationError(msg) if not no_gateway and gateway_ip: if netaddr.IPAddress(gateway_ip).version is not ip_version: msg = _('Gateway IP and IP version are inconsistent.') raise forms.ValidationError(msg) if not is_create and not no_gateway and not gateway_ip: msg = _('Specify IP address of gateway or ' 'check "Disable Gateway".') raise forms.ValidationError(msg) def clean(self): cleaned_data = super(CreateSubnetInfoAction, self).clean() with_subnet = cleaned_data.get('with_subnet') if not with_subnet: return cleaned_data self._check_subnet_data(cleaned_data) return cleaned_data