Merge "Add Port-Create in Project Dashboard"
This commit is contained in:
commit
5679634fed
@ -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
|
||||
|
@ -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…
Reference in New Issue
Block a user