Add Port-Create in Project Dashboard
Gives end-users the ability to create and delete ports in their networks. The functionality will be implemented into the project network details table. Following the discussions in the bug discussion. This functionality will be enabled/disabled via policy. Change-Id: I560b42b94acb6a2424fbc9b574b6e376c34ac9ee Implements Blueprint: network-ports-tenant Closes-Bug: #1399252 Co-Authored-By: kenji-i<ken-ishii@sx.jp.nec.com>
This commit is contained in:
parent
f716d559ad
commit
b327515253
@ -32,78 +32,18 @@ VNIC_TYPES = [('normal', _('Normal')), ('direct', _('Direct')),
|
||||
('macvtap', _('MacVTap'))]
|
||||
|
||||
|
||||
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.ThemableChoiceField(choices=[('True', _('UP')),
|
||||
('False', _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
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=_("Device owner attached to the "
|
||||
"port"),
|
||||
required=False)
|
||||
class CreatePort(project_forms.CreatePort):
|
||||
binding__host_id = forms.CharField(
|
||||
label=_("Binding: Host"),
|
||||
help_text=_("The ID of the host where the port is allocated. In some "
|
||||
"cases, different implementations can run on different "
|
||||
"hosts."),
|
||||
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.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'specify_ip',
|
||||
}))
|
||||
subnet_id = forms.ThemableChoiceField(
|
||||
label=_("Subnet"),
|
||||
required=False,
|
||||
widget=forms.Select(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'),
|
||||
}))
|
||||
failure_url = 'horizon:admin: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()
|
||||
|
||||
try:
|
||||
if api.neutron.is_extension_supported(request, 'binding'):
|
||||
neutron_settings = getattr(settings,
|
||||
@ -140,16 +80,6 @@ class CreatePort(forms.SelfHandlingForm):
|
||||
msg = _("Unable to retrieve MAC learning 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:
|
||||
# We must specify tenant_id of the network which a subnet is
|
||||
|
@ -14,65 +14,24 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.project.networks.ports import \
|
||||
tables as project_tables
|
||||
from openstack_dashboard.dashboards.project.networks.ports.tabs \
|
||||
import PortsTab as project_port_tab
|
||||
from openstack_dashboard import policy
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeletePort(policy.PolicyTargetMixin, tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Port",
|
||||
u"Delete Ports",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Port",
|
||||
u"Deleted Ports",
|
||||
count
|
||||
)
|
||||
|
||||
policy_rules = (("network", "delete_port"),)
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.neutron.port_delete(request, obj_id)
|
||||
except Exception as e:
|
||||
msg = _('Failed to delete port: %s') % e
|
||||
LOG.info(msg)
|
||||
network_id = self.table.kwargs['network_id']
|
||||
redirect = reverse('horizon:admin:networks:detail',
|
||||
args=[network_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
class DeletePort(project_tables.DeletePort):
|
||||
failure_url = "horizon:admin:networks:detail"
|
||||
|
||||
|
||||
class CreatePort(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Port")
|
||||
class CreatePort(project_tables.CreatePort):
|
||||
url = "horizon:admin:networks:addport"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("network", "create_port"),)
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id,))
|
||||
|
||||
|
||||
class UpdatePort(project_tables.UpdatePort):
|
||||
|
@ -89,12 +89,13 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'binding')\
|
||||
.AndReturn(binding)
|
||||
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning')\
|
||||
.AndReturn(mac_learning)
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'binding')\
|
||||
.AndReturn(binding)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:admin:networks:addport',
|
||||
@ -127,12 +128,12 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'binding')\
|
||||
.AndReturn(binding)
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning')\
|
||||
.AndReturn(mac_learning)
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'binding') \
|
||||
.AndReturn(binding)
|
||||
extension_kwargs = {}
|
||||
if binding:
|
||||
extension_kwargs['binding__vnic_type'] = \
|
||||
@ -185,9 +186,6 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'binding')\
|
||||
.AndReturn(True)
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning')\
|
||||
.AndReturn(True)
|
||||
@ -252,12 +250,12 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'binding')\
|
||||
.AndReturn(binding)
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning')\
|
||||
.AndReturn(mac_learning)
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'binding') \
|
||||
.AndReturn(binding)
|
||||
extension_kwargs = {}
|
||||
if binding:
|
||||
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
||||
@ -311,7 +309,7 @@ class NetworkPortTests(test.BaseAdminViewTests):
|
||||
port.id)\
|
||||
.AndReturn(port)
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'binding')\
|
||||
'binding') \
|
||||
.AndReturn(binding)
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning')\
|
||||
|
@ -30,6 +30,123 @@ VNIC_TYPES = [('normal', _('Normal')), ('direct', _('Direct')),
|
||||
('macvtap', _('MacVTap'))]
|
||||
|
||||
|
||||
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.ChoiceField(choices=[('True', _('UP')),
|
||||
('False', _('DOWN'))],
|
||||
label=_("Admin State"))
|
||||
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.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'specify_ip',
|
||||
}))
|
||||
subnet_id = forms.ThemableChoiceField(
|
||||
label=_("Subnet"),
|
||||
required=False,
|
||||
widget=forms.Select(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'),
|
||||
}))
|
||||
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)
|
||||
|
||||
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'] == 'True',
|
||||
'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']
|
||||
|
||||
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']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return port
|
||||
except Exception:
|
||||
msg = _('Failed to create a port for network %s') \
|
||||
% data['network_id']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url,
|
||||
args=(data['network_id'],))
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class UpdatePort(forms.SelfHandlingForm):
|
||||
network_id = forms.CharField(widget=forms.HiddenInput())
|
||||
port_id = forms.CharField(label=_("ID"),
|
||||
|
@ -12,16 +12,22 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django import template
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard import policy
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_fixed_ips(port):
|
||||
template_name = 'project/networks/ports/_port_ips.html'
|
||||
@ -64,6 +70,51 @@ STATUS_DISPLAY_CHOICES = (
|
||||
)
|
||||
|
||||
|
||||
class CreatePort(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Port")
|
||||
url = "horizon:project:networks:addport"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "plus"
|
||||
policy_rules = (("network", "create_port"),)
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id,))
|
||||
|
||||
|
||||
class DeletePort(policy.PolicyTargetMixin, tables.DeleteAction):
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Delete Port",
|
||||
u"Delete Ports",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Deleted Port",
|
||||
u"Deleted Ports",
|
||||
count
|
||||
)
|
||||
|
||||
policy_rules = (("network", "delete_port"),)
|
||||
|
||||
def delete(self, request, port_id):
|
||||
failure_url = "horizon:project:networks:detail"
|
||||
try:
|
||||
api.neutron.port_delete(request, port_id)
|
||||
except Exception:
|
||||
msg = _('Failed to delete port: %s') % port_id
|
||||
LOG.info(msg)
|
||||
network_id = self.table.kwargs['network_id']
|
||||
redirect = reverse(failure_url,
|
||||
args=[network_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class PortsTable(tables.DataTable):
|
||||
name = tables.WrappingColumn("name_or_id",
|
||||
verbose_name=_("Name"),
|
||||
@ -85,8 +136,8 @@ class PortsTable(tables.DataTable):
|
||||
class Meta(object):
|
||||
name = "ports"
|
||||
verbose_name = _("Ports")
|
||||
table_actions = (tables.FilterAction,)
|
||||
row_actions = (UpdatePort,)
|
||||
table_actions = (tables.FilterAction, CreatePort, DeletePort)
|
||||
row_actions = (UpdatePort, DeletePort)
|
||||
hidden_title = False
|
||||
|
||||
def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
|
||||
|
@ -323,3 +323,222 @@ class NetworkPortTests(test.TestCase):
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
self.assertMessageCount(success=1)
|
||||
|
||||
@test.create_stubs({api.neutron: ('network_get',
|
||||
'is_extension_supported',)})
|
||||
def test_port_create_get(self):
|
||||
self._test_port_create_get()
|
||||
|
||||
@test.create_stubs({api.neutron: ('network_get',
|
||||
'is_extension_supported',)})
|
||||
def test_port_create_get_with_mac_learning(self):
|
||||
self._test_port_create_get(mac_learning=True)
|
||||
|
||||
def _test_port_create_get(self, mac_learning=False, binding=False):
|
||||
network = self.networks.first()
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id) \
|
||||
.AndReturn(self.networks.first())
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning') \
|
||||
.AndReturn(mac_learning)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:networks:addport',
|
||||
args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'project/networks/ports/create.html')
|
||||
|
||||
@test.create_stubs({api.neutron: ('network_get',
|
||||
'is_extension_supported',
|
||||
'port_create',)})
|
||||
def test_port_create_post(self):
|
||||
self._test_port_create_post()
|
||||
|
||||
@test.create_stubs({api.neutron: ('network_get',
|
||||
'is_extension_supported',
|
||||
'port_create',)})
|
||||
def test_port_create_post_with_mac_learning(self):
|
||||
self._test_port_create_post(mac_learning=True, binding=False)
|
||||
|
||||
def _test_port_create_post(self, mac_learning=False, binding=False):
|
||||
network = self.networks.first()
|
||||
port = self.ports.first()
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id) \
|
||||
.MultipleTimes().AndReturn(self.networks.first())
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning') \
|
||||
.AndReturn(mac_learning)
|
||||
extension_kwargs = {}
|
||||
if binding:
|
||||
extension_kwargs['binding__vnic_type'] = \
|
||||
port.binding__vnic_type
|
||||
if mac_learning:
|
||||
extension_kwargs['mac_learning_enabled'] = True
|
||||
api.neutron.port_create(IsA(http.HttpRequest),
|
||||
tenant_id=network.tenant_id,
|
||||
network_id=network.id,
|
||||
name=port.name,
|
||||
admin_state_up=port.admin_state_up,
|
||||
device_id=port.device_id,
|
||||
device_owner=port.device_owner,
|
||||
fixed_ips=port.fixed_ips,
|
||||
**extension_kwargs) \
|
||||
.AndReturn(port)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': port.network_id,
|
||||
'network_name': network.name,
|
||||
'name': port.name,
|
||||
'admin_state': port.admin_state_up,
|
||||
'device_id': port.device_id,
|
||||
'device_owner': port.device_owner,
|
||||
'specify_ip': 'fixed_ip',
|
||||
'fixed_ip': port.fixed_ips[0]['ip_address'],
|
||||
'subnet_id': port.fixed_ips[0]['subnet_id']}
|
||||
if binding:
|
||||
form_data['binding__vnic_type'] = port.binding__vnic_type
|
||||
if mac_learning:
|
||||
form_data['mac_state'] = True
|
||||
url = reverse('horizon:project:networks:addport',
|
||||
args=[port.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
redir_url = reverse(NETWORKS_DETAIL_URL, args=[port.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.neutron: ('network_get',
|
||||
'port_create',
|
||||
'is_extension_supported',)})
|
||||
def test_port_create_post_exception(self):
|
||||
self._test_port_create_post_exception()
|
||||
|
||||
@test.create_stubs({api.neutron: ('network_get',
|
||||
'port_create',
|
||||
'is_extension_supported',)})
|
||||
def test_port_create_post_exception_with_mac_learning(self):
|
||||
self._test_port_create_post_exception(mac_learning=True)
|
||||
|
||||
def _test_port_create_post_exception(self, mac_learning=False,
|
||||
binding=False):
|
||||
network = self.networks.first()
|
||||
port = self.ports.first()
|
||||
api.neutron.network_get(IsA(http.HttpRequest),
|
||||
network.id) \
|
||||
.MultipleTimes().AndReturn(self.networks.first())
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning') \
|
||||
.AndReturn(mac_learning)
|
||||
|
||||
extension_kwargs = {}
|
||||
if binding:
|
||||
extension_kwargs['binding__vnic_type'] = port.binding__vnic_type
|
||||
if mac_learning:
|
||||
extension_kwargs['mac_learning_enabled'] = True
|
||||
api.neutron.port_create(IsA(http.HttpRequest),
|
||||
tenant_id=network.tenant_id,
|
||||
network_id=network.id,
|
||||
name=port.name,
|
||||
admin_state_up=port.admin_state_up,
|
||||
device_id=port.device_id,
|
||||
device_owner=port.device_owner,
|
||||
**extension_kwargs) \
|
||||
.AndRaise(self.exceptions.neutron)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': port.network_id,
|
||||
'network_name': network.name,
|
||||
'name': port.name,
|
||||
'admin_state': port.admin_state_up,
|
||||
'mac_state': True,
|
||||
'device_id': port.device_id,
|
||||
'device_owner': port.device_owner,
|
||||
'specify_ip': 'fixed_ip',
|
||||
'fixed_ip': port.fixed_ips[0]['ip_address'],
|
||||
'subnet_id': port.fixed_ips[0]['subnet_id']}
|
||||
if binding:
|
||||
form_data['binding__vnic_type'] = port.binding__vnic_type
|
||||
if mac_learning:
|
||||
form_data['mac_learning_enabled'] = True
|
||||
url = reverse('horizon:project:networks:addport',
|
||||
args=[port.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
redir_url = reverse(NETWORKS_DETAIL_URL, args=[port.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.neutron: ('port_delete',
|
||||
'subnet_list',
|
||||
'port_list',
|
||||
'is_extension_supported',
|
||||
'network_get',
|
||||
'list_dhcp_agent_hosting_networks',)})
|
||||
def test_port_delete(self):
|
||||
self._test_port_delete()
|
||||
|
||||
@test.create_stubs({api.neutron: ('port_delete',
|
||||
'subnet_list',
|
||||
'port_list',
|
||||
'network_get',
|
||||
'is_extension_supported',)})
|
||||
def test_port_delete_with_mac_learning(self):
|
||||
self._test_port_delete(mac_learning=True)
|
||||
|
||||
def _test_port_delete(self, mac_learning=False):
|
||||
port = self.ports.first()
|
||||
network_id = port.network_id
|
||||
|
||||
api.neutron.port_delete(IsA(http.HttpRequest), port.id)
|
||||
api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id) \
|
||||
.AndReturn([self.ports.first()])
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning') \
|
||||
.AndReturn(mac_learning)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'action': 'ports__delete__%s' % port.id}
|
||||
url = reverse(NETWORKS_DETAIL_URL, args=[network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
||||
@test.create_stubs({api.neutron: ('port_delete',
|
||||
'subnet_list',
|
||||
'port_list',
|
||||
'is_extension_supported',
|
||||
'network_get',)})
|
||||
def test_port_delete_exception(self):
|
||||
self._test_port_delete_exception()
|
||||
|
||||
@test.create_stubs({api.neutron: ('port_delete',
|
||||
'subnet_list',
|
||||
'port_list',
|
||||
'is_extension_supported',
|
||||
'network_get',)})
|
||||
def test_port_delete_exception_with_mac_learning(self):
|
||||
self._test_port_delete_exception(mac_learning=True)
|
||||
|
||||
def _test_port_delete_exception(self, mac_learning=False):
|
||||
port = self.ports.first()
|
||||
network_id = port.network_id
|
||||
|
||||
api.neutron.port_delete(IsA(http.HttpRequest), port.id) \
|
||||
.AndRaise(self.exceptions.neutron)
|
||||
api.neutron.port_list(IsA(http.HttpRequest), network_id=network_id) \
|
||||
.AndReturn([self.ports.first()])
|
||||
api.neutron.is_extension_supported(IsA(http.HttpRequest),
|
||||
'mac-learning') \
|
||||
.AndReturn(mac_learning)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'action': 'ports__delete__%s' % port.id}
|
||||
url = reverse(NETWORKS_DETAIL_URL, args=[network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
@ -34,6 +34,45 @@ STATUS_DICT = dict(project_tables.STATUS_DISPLAY_CHOICES)
|
||||
VNIC_TYPES = dict(project_forms.VNIC_TYPES)
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = project_forms.CreatePort
|
||||
form_id = "create_port_form"
|
||||
modal_header = _("Create Port")
|
||||
submit_label = _("Create Port")
|
||||
submit_url = "horizon:project:networks:addport"
|
||||
page_title = _("Create Port")
|
||||
template_name = 'project/networks/ports/create.html'
|
||||
url = 'horizon:project:networks:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_network(self):
|
||||
try:
|
||||
network_id = self.kwargs["network_id"]
|
||||
return api.neutron.network_get(self.request, network_id)
|
||||
except Exception:
|
||||
redirect = reverse(self.url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
msg = _("Unable to retrieve network.")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
context['network'] = self.get_network()
|
||||
args = (self.kwargs['network_id'],)
|
||||
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||
context['cancel_url'] = reverse(self.url, args=args)
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
network = self.get_network()
|
||||
return {"network_id": self.kwargs['network_id'],
|
||||
"network_name": network.name}
|
||||
|
||||
|
||||
class DetailView(tabs.TabbedTableView):
|
||||
tab_group_class = project_tabs.PortDetailTabs
|
||||
template_name = 'horizon/common/_detail.html'
|
||||
|
@ -0,0 +1,11 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% blocktrans %} You can create a port for the network.
|
||||
If you specify device ID to be attached, the device specified will
|
||||
be attached to the port created.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% endblock %}
|
@ -0,0 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Port" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "project/networks/ports/_create.html" %}
|
||||
{% endblock %}
|
@ -42,6 +42,8 @@ urlpatterns = [
|
||||
url(NETWORKS % 'update', views.UpdateView.as_view(), name='update'),
|
||||
url(NETWORKS % 'subnets/create', subnet_views.CreateView.as_view(),
|
||||
name='addsubnet'),
|
||||
url(NETWORKS % 'ports/create',
|
||||
port_views.CreateView.as_view(), name='addport'),
|
||||
url(r'^(?P<network_id>[^/]+)/subnets/(?P<subnet_id>[^/]+)/update$',
|
||||
subnet_views.UpdateView.as_view(), name='editsubnet'),
|
||||
url(r'^(?P<network_id>[^/]+)/ports/(?P<port_id>[^/]+)/update$',
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- Gives end-users the ability to create and delete ports in their networks.
|
||||
The functionality will be implemented into the project network
|
||||
details table. Following the discussions in the bug discussion.
|
||||
This functionality will be enabled/disabled via policy.
|
||||
Blueprint can be found at
|
||||
[`blueprint network-ports-tenant <https://blueprints.launchpad.net/horizon/+spec/network-ports-tenant>`_]
|
||||
Bug can be found at [`bug 1399252 <https://bugs.launchpad.net/horizon/+bug/1399252>`_]
|
Loading…
x
Reference in New Issue
Block a user