Browse Source

Merge "Support Quantum L3 function"

Jenkins 6 years ago
parent
commit
511cac94fb
47 changed files with 1968 additions and 6 deletions
  1. 60
    1
      openstack_dashboard/api/quantum.py
  2. 1
    1
      openstack_dashboard/dashboards/admin/dashboard.py
  3. 0
    0
      openstack_dashboard/dashboards/admin/routers/__init__.py
  4. 71
    0
      openstack_dashboard/dashboards/admin/routers/forms.py
  5. 29
    0
      openstack_dashboard/dashboards/admin/routers/panel.py
  6. 0
    0
      openstack_dashboard/dashboards/admin/routers/ports/__init__.py
  7. 31
    0
      openstack_dashboard/dashboards/admin/routers/ports/forms.py
  8. 64
    0
      openstack_dashboard/dashboards/admin/routers/ports/tables.py
  9. 31
    0
      openstack_dashboard/dashboards/admin/routers/ports/tabs.py
  10. 24
    0
      openstack_dashboard/dashboards/admin/routers/ports/urls.py
  11. 43
    0
      openstack_dashboard/dashboards/admin/routers/ports/views.py
  12. 71
    0
      openstack_dashboard/dashboards/admin/routers/tables.py
  13. 28
    0
      openstack_dashboard/dashboards/admin/routers/tabs.py
  14. 21
    0
      openstack_dashboard/dashboards/admin/routers/templates/routers/_create.html
  15. 15
    0
      openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html
  16. 11
    0
      openstack_dashboard/dashboards/admin/routers/templates/routers/create.html
  17. 15
    0
      openstack_dashboard/dashboards/admin/routers/templates/routers/detail.html
  18. 11
    0
      openstack_dashboard/dashboards/admin/routers/templates/routers/index.html
  19. 104
    0
      openstack_dashboard/dashboards/admin/routers/tests.py
  20. 34
    0
      openstack_dashboard/dashboards/admin/routers/urls.py
  21. 76
    0
      openstack_dashboard/dashboards/admin/routers/views.py
  22. 2
    1
      openstack_dashboard/dashboards/project/dashboard.py
  23. 0
    0
      openstack_dashboard/dashboards/project/routers/__init__.py
  24. 41
    0
      openstack_dashboard/dashboards/project/routers/forms.py
  25. 29
    0
      openstack_dashboard/dashboards/project/routers/panel.py
  26. 0
    0
      openstack_dashboard/dashboards/project/routers/ports/__init__.py
  27. 132
    0
      openstack_dashboard/dashboards/project/routers/ports/forms.py
  28. 103
    0
      openstack_dashboard/dashboards/project/routers/ports/tables.py
  29. 47
    0
      openstack_dashboard/dashboards/project/routers/ports/tabs.py
  30. 24
    0
      openstack_dashboard/dashboards/project/routers/ports/urls.py
  31. 100
    0
      openstack_dashboard/dashboards/project/routers/ports/views.py
  32. 90
    0
      openstack_dashboard/dashboards/project/routers/tables.py
  33. 45
    0
      openstack_dashboard/dashboards/project/routers/tabs.py
  34. 21
    0
      openstack_dashboard/dashboards/project/routers/templates/routers/_create.html
  35. 15
    0
      openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html
  36. 11
    0
      openstack_dashboard/dashboards/project/routers/templates/routers/create.html
  37. 15
    0
      openstack_dashboard/dashboards/project/routers/templates/routers/detail.html
  38. 11
    0
      openstack_dashboard/dashboards/project/routers/templates/routers/index.html
  39. 25
    0
      openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html
  40. 25
    0
      openstack_dashboard/dashboards/project/routers/templates/routers/ports/_setgateway.html
  41. 11
    0
      openstack_dashboard/dashboards/project/routers/templates/routers/ports/create.html
  42. 11
    0
      openstack_dashboard/dashboards/project/routers/templates/routers/ports/setgateway.html
  43. 235
    0
      openstack_dashboard/dashboards/project/routers/tests.py
  44. 34
    0
      openstack_dashboard/dashboards/project/routers/urls.py
  45. 105
    0
      openstack_dashboard/dashboards/project/routers/views.py
  46. 67
    0
      openstack_dashboard/test/api_tests/quantum_tests.py
  47. 29
    3
      openstack_dashboard/test/test_data/quantum_data.py

+ 60
- 1
openstack_dashboard/api/quantum.py View File

@@ -78,6 +78,15 @@ class Port(QuantumAPIDictWrapper):
78 78
         super(Port, self).__init__(apiresource)
79 79
 
80 80
 
81
+class Router(QuantumAPIDictWrapper):
82
+    """Wrapper for quantum routers"""
83
+
84
+    def __init__(self, apiresource):
85
+        #apiresource['admin_state'] = \
86
+        #    'UP' if apiresource['admin_state_up'] else 'DOWN'
87
+        super(Router, self).__init__(apiresource)
88
+
89
+
81 90
 IP_VERSION_DICT = {4: 'IPv4', 6: 'IPv6'}
82 91
 
83 92
 
@@ -104,7 +113,7 @@ def network_list(request, **params):
104 113
     subnet_dict = SortedDict([(s['id'], s) for s in subnets])
105 114
     # Expand subnet list from subnet_id to values.
106 115
     for n in networks:
107
-        n['subnets'] = [subnet_dict[s] for s in n['subnets']]
116
+        n['subnets'] = [subnet_dict.get(s) for s in n.get('subnets', [])]
108 117
     return [Network(n) for n in networks]
109 118
 
110 119
 
@@ -256,3 +265,53 @@ def port_modify(request, port_id, **kwargs):
256 265
     body = {'port': kwargs}
257 266
     port = quantumclient(request).update_port(port_id, body=body).get('port')
258 267
     return Port(port)
268
+
269
+
270
+def router_create(request, **kwargs):
271
+    LOG.debug("router_create():, kwargs=%s" % kwargs)
272
+    body = {'router': {}}
273
+    body['router'].update(kwargs)
274
+    router = quantumclient(request).create_router(body=body).get('router')
275
+    return Router(router)
276
+
277
+
278
+def router_get(request, router_id, **params):
279
+    router = quantumclient(request).show_router(router_id,
280
+                                                **params).get('router')
281
+    return Router(router)
282
+
283
+
284
+def router_list(request, **params):
285
+    routers = quantumclient(request).list_routers(**params).get('routers')
286
+    return [Router(r) for r in routers]
287
+
288
+
289
+def router_delete(request, router_id):
290
+    quantumclient(request).delete_router(router_id)
291
+
292
+
293
+def router_add_interface(request, router_id, subnet_id=None, port_id=None):
294
+    body = {}
295
+    if subnet_id:
296
+        body['subnet_id'] = subnet_id
297
+    if port_id:
298
+        body['port_id'] = port_id
299
+    quantumclient(request).add_interface_router(router_id, body)
300
+
301
+
302
+def router_remove_interface(request, router_id, subnet_id=None, port_id=None):
303
+    body = {}
304
+    if subnet_id:
305
+        body['subnet_id'] = subnet_id
306
+    if port_id:
307
+        body['port_id'] = port_id
308
+    quantumclient(request).remove_interface_router(router_id, body)
309
+
310
+
311
+def router_add_gateway(request, router_id, network_id):
312
+    body = {'network_id': network_id}
313
+    quantumclient(request).add_gateway_router(router_id, body)
314
+
315
+
316
+def router_remove_gateway(request, router_id):
317
+    quantumclient(request).remove_gateway_router(router_id)

+ 1
- 1
openstack_dashboard/dashboards/admin/dashboard.py View File

@@ -23,7 +23,7 @@ class SystemPanels(horizon.PanelGroup):
23 23
     slug = "admin"
24 24
     name = _("System Panel")
25 25
     panels = ('overview', 'instances', 'volumes', 'flavors',
26
-              'images', 'projects', 'users', 'networks', 'info')
26
+              'images', 'projects', 'users', 'networks', 'routers', 'info')
27 27
 
28 28
 
29 29
 class Admin(horizon.Dashboard):

+ 0
- 0
openstack_dashboard/dashboards/admin/routers/__init__.py View File


+ 71
- 0
openstack_dashboard/dashboards/admin/routers/forms.py View File

@@ -0,0 +1,71 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+"""
18
+Views for managing Quantum Routers.
19
+"""
20
+import logging
21
+
22
+from django.core.urlresolvers import reverse
23
+from django.utils.translation import ugettext_lazy as _
24
+
25
+from horizon import forms
26
+from horizon import exceptions
27
+from horizon import messages
28
+from openstack_dashboard import api
29
+
30
+LOG = logging.getLogger(__name__)
31
+
32
+
33
+class CreateForm(forms.SelfHandlingForm):
34
+    name = forms.CharField(max_length="255",
35
+                           label=_("Router Name"),
36
+                           required=False)
37
+    tenant_id = forms.ChoiceField(label=_("Project"))
38
+    failure_url = 'horizon:admin:routers:index'
39
+
40
+    def __init__(self, request, *args, **kwargs):
41
+        super(CreateForm, self).__init__(request, *args, **kwargs)
42
+        tenant_choices = [('', _("Select a project"))]
43
+        try:
44
+            for tenant in api.keystone.tenant_list(request, admin=True):
45
+                if tenant.enabled:
46
+                    tenant_choices.append((tenant.id, tenant.name))
47
+        except:
48
+            msg = _('Failed to get tenants.')
49
+            LOG.warn(msg)
50
+            redirect = reverse(self.failure_url)
51
+            exceptions.handle(request, msg, redirect=redirect)
52
+            return False
53
+
54
+        self.fields['tenant_id'].choices = tenant_choices
55
+
56
+    def handle(self, request, data):
57
+        try:
58
+            params = {}
59
+            if data.get('tenant_id'):
60
+                params['tenant_id'] = data['tenant_id']
61
+            router = api.quantum.router_create(request,
62
+                                               name=data['name'], **params)
63
+            message = 'Creating router "%s"' % data['name']
64
+            messages.info(request, message)
65
+            return router
66
+        except:
67
+            msg = _('Failed to create router "%s".') % data['name']
68
+            LOG.warn(msg)
69
+            redirect = reverse(self.failure_url)
70
+            exceptions.handle(request, msg, redirect=redirect)
71
+            return False

+ 29
- 0
openstack_dashboard/dashboards/admin/routers/panel.py View File

@@ -0,0 +1,29 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+from django.utils.translation import ugettext_lazy as _
18
+
19
+import horizon
20
+
21
+from openstack_dashboard.dashboards.admin import dashboard
22
+
23
+
24
+class Routers(horizon.Panel):
25
+    name = "Routers"
26
+    slug = 'routers'
27
+    permissions = ('openstack.services.network',)
28
+
29
+dashboard.Admin.register(Routers)

+ 0
- 0
openstack_dashboard/dashboards/admin/routers/ports/__init__.py View File


+ 31
- 0
openstack_dashboard/dashboards/admin/routers/ports/forms.py View File

@@ -0,0 +1,31 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+import logging
18
+
19
+from openstack_dashboard.dashboards.project.routers.ports import (
20
+    forms as p_forms)
21
+
22
+
23
+LOG = logging.getLogger(__name__)
24
+
25
+
26
+class AddInterface(p_forms.AddInterface):
27
+    failure_url = 'horizon:admin:routers:detail'
28
+
29
+
30
+class SetGatewayForm(p_forms.SetGatewayForm):
31
+    failure_url = 'horizon:admin:routers:detail'

+ 64
- 0
openstack_dashboard/dashboards/admin/routers/ports/tables.py View File

@@ -0,0 +1,64 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012 NEC Corporation
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+import logging
18
+
19
+from django.utils.translation import ugettext_lazy as _
20
+
21
+from horizon import tables
22
+from openstack_dashboard.dashboards.project.networks.ports.tables import\
23
+        get_fixed_ips, get_attached
24
+from openstack_dashboard.dashboards.project.routers.ports import\
25
+        tables as r_tables
26
+from openstack_dashboard.dashboards.project.routers.ports.tables import\
27
+        get_device_owner
28
+
29
+
30
+LOG = logging.getLogger(__name__)
31
+
32
+
33
+class SetGateway(r_tables.SetGateway):
34
+    url = "horizon:admin:routers:setgateway"
35
+
36
+
37
+class AddInterface(r_tables.AddInterface):
38
+    url = "horizon:admin:routers:addinterface"
39
+
40
+
41
+class RemoveInterface(r_tables.RemoveInterface):
42
+    failure_url = 'horizon:admin:routers:detail'
43
+
44
+
45
+class PortsTable(tables.DataTable):
46
+    name = tables.Column("name",
47
+                         verbose_name=_("Name"),
48
+                         link="horizon:admin:networks:ports:detail")
49
+    fixed_ips = tables.Column(get_fixed_ips, verbose_name=_("Fixed IPs"))
50
+    attached = tables.Column(get_attached, verbose_name=_("Device Attached"))
51
+    status = tables.Column("status", verbose_name=_("Status"))
52
+    device_owner = tables.Column(get_device_owner,
53
+                                 verbose_name=_("Type"))
54
+    admin_state = tables.Column("admin_state",
55
+                                verbose_name=_("Admin State"))
56
+
57
+    def get_object_display(self, port):
58
+        return port.id
59
+
60
+    class Meta:
61
+        name = "interfaces"
62
+        verbose_name = _("Interfaces")
63
+        table_actions = (AddInterface, SetGateway, RemoveInterface)
64
+        row_actions = (RemoveInterface, )

+ 31
- 0
openstack_dashboard/dashboards/admin/routers/ports/tabs.py View File

@@ -0,0 +1,31 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+import logging
17
+
18
+from horizon import tabs
19
+from openstack_dashboard.dashboards.project.routers.ports import tabs as r_tabs
20
+
21
+LOG = logging.getLogger(__name__)
22
+
23
+
24
+class OverviewTab(r_tabs.OverviewTab):
25
+    template_name = "admin/networks/ports/_detail_overview.html"
26
+    failure_url = "horizon:admin:routers:index"
27
+
28
+
29
+class PortDetailTabs(tabs.TabGroup):
30
+    slug = "port_details"
31
+    tabs = (OverviewTab,)

+ 24
- 0
openstack_dashboard/dashboards/admin/routers/ports/urls.py View File

@@ -0,0 +1,24 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012 NTT MCL
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+from django.conf.urls.defaults import patterns, url
18
+
19
+from .views import DetailView
20
+
21
+PORTS = r'^(?P<port_id>[^/]+)/%s$'
22
+
23
+urlpatterns = patterns('horizon.dashboards.admin.networks.ports.views',
24
+    url(PORTS % 'detail', DetailView.as_view(), name='detail'))

+ 43
- 0
openstack_dashboard/dashboards/admin/routers/ports/views.py View File

@@ -0,0 +1,43 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+import logging
18
+
19
+from horizon import tabs
20
+from .tabs import PortDetailTabs
21
+from .forms import (AddInterface, SetGatewayForm)
22
+from openstack_dashboard.dashboards.project.routers.ports import views
23
+
24
+
25
+LOG = logging.getLogger(__name__)
26
+
27
+
28
+class AddInterfaceView(views.AddInterfaceView):
29
+    form_class = AddInterface
30
+    template_name = 'admin/routers/ports/create.html'
31
+    success_url = 'horizon:admin:routers:detail'
32
+    failure_url = 'horizon:admin:routers:detail'
33
+
34
+
35
+class SetGatewayView(views.SetGatewayView):
36
+    form_class = SetGatewayForm
37
+    success_url = 'horizon:admin:routers:detail'
38
+    failure_url = 'horizon:admin:routers:detail'
39
+
40
+
41
+class DetailView(tabs.TabView):
42
+    tab_group_class = PortDetailTabs
43
+    template_name = 'admin/networks/ports/detail.html'

+ 71
- 0
openstack_dashboard/dashboards/admin/routers/tables.py View File

@@ -0,0 +1,71 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+import logging
18
+
19
+from django.template.defaultfilters import title
20
+from django.utils.translation import ugettext_lazy as _
21
+
22
+from horizon import tables
23
+from openstack_dashboard import api
24
+from openstack_dashboard.dashboards.project.routers import tables as r_tables
25
+
26
+
27
+LOG = logging.getLogger(__name__)
28
+
29
+
30
+class DeleteRouter(r_tables.DeleteRouter):
31
+    redirect_url = "horizon:admin:routers:index"
32
+
33
+    def allowed(self, request, router=None):
34
+        return True
35
+
36
+
37
+class CreateRouter(tables.LinkAction):
38
+    name = "create"
39
+    verbose_name = _("Create Router")
40
+    url = "horizon:admin:routers:create"
41
+    classes = ("ajax-modal", "btn-create")
42
+
43
+
44
+class UpdateRow(tables.Row):
45
+    ajax = True
46
+
47
+    def get_data(self, request, router_id):
48
+        router = api.router_get(request, router_id)
49
+        return router
50
+
51
+
52
+class RoutersTable(tables.DataTable):
53
+    tenant = tables.Column("tenant_name", verbose_name=_("Project"))
54
+    name = tables.Column("name",
55
+                         verbose_name=_("Name"),
56
+                         link="horizon:admin:routers:detail")
57
+    status = tables.Column("status",
58
+                           filters=(title,),
59
+                           verbose_name=_("Status"),
60
+                           status=True)
61
+
62
+    def get_object_display(self, obj):
63
+        return obj.name
64
+
65
+    class Meta:
66
+        name = "Routers"
67
+        verbose_name = _("Routers")
68
+        status_columns = ["status"]
69
+        row_class = UpdateRow
70
+        table_actions = (CreateRouter, DeleteRouter)
71
+        row_actions = (DeleteRouter, )

+ 28
- 0
openstack_dashboard/dashboards/admin/routers/tabs.py View File

@@ -0,0 +1,28 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+from horizon import tabs
18
+from openstack_dashboard.dashboards.project.routers import tabs as r_tabs
19
+
20
+
21
+class OverviewTab(r_tabs.OverviewTab):
22
+    template_name = ("admin/routers/_detail_overview.html")
23
+    redirect_url = 'horizon:admin:routers:index'
24
+
25
+
26
+class RouterDetailTabs(tabs.TabGroup):
27
+    slug = "router_details"
28
+    tabs = (OverviewTab,)

+ 21
- 0
openstack_dashboard/dashboards/admin/routers/templates/routers/_create.html View File

@@ -0,0 +1,21 @@
1
+{% extends "horizon/common/_modal_form.html" %}
2
+{% load i18n horizon humanize %}
3
+
4
+{% block form_id %}{% endblock %}
5
+{% block form_action %}{% url horizon:admin:routers:create %}?{{ request.GET.urlencode }}{% endblock %}
6
+
7
+{% block modal_id %}create_router_modal{% endblock %}
8
+{% block modal-header %}{% trans "Create router" %}{% endblock %}
9
+
10
+{% block modal-body %}
11
+  <div class="left">
12
+    <fieldset>
13
+      {% include "horizon/common/_form_fields.html" %}
14
+    </fieldset>
15
+  </div>
16
+{% endblock %}
17
+
18
+{% block modal-footer %}
19
+  <input class="btn btn-primary pull-right" type="submit" value="{% trans "Create router" %}" />
20
+  <a href="{% url horizon:admin:routers:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
21
+{% endblock %}

+ 15
- 0
openstack_dashboard/dashboards/admin/routers/templates/routers/_detail_overview.html View File

@@ -0,0 +1,15 @@
1
+{% load i18n sizeformat parse_date %}
2
+
3
+<h3>{% trans "Router Overview" %}: {{router.display_name }}</h3>
4
+
5
+<div class="info detail">
6
+  <dl>
7
+    <dt>{% trans "Name" %}</dt>
8
+    <dd>{{ router.display_name }}</dd>
9
+    <dt>{% trans "ID" %}</dt>
10
+    <dd>{{ router.id }}</dd>
11
+    <dt>{% trans "Status" %}</dt>
12
+    <dd>{{ router.status|capfirst }}</dd>
13
+  </dl>
14
+</div>
15
+

+ 11
- 0
openstack_dashboard/dashboards/admin/routers/templates/routers/create.html View File

@@ -0,0 +1,11 @@
1
+{% extends 'base.html' %}
2
+{% load i18n %}
3
+{% block title %}Create router{% endblock %}
4
+
5
+{% block page_header %}
6
+  {% include "horizon/common/_page_header.html" with title=_("Create a router") %}
7
+{% endblock page_header %}
8
+
9
+{% block main %}
10
+    {% include 'admin/routers/_create.html' %}
11
+{% endblock %}

+ 15
- 0
openstack_dashboard/dashboards/admin/routers/templates/routers/detail.html View File

@@ -0,0 +1,15 @@
1
+{% extends 'base.html' %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Router Details" %}{% endblock %}
4
+
5
+{% block page_header %}
6
+  {% include "horizon/common/_page_header.html" with title=_("Router Detail") %}
7
+{% endblock page_header %}
8
+
9
+{% block main %}
10
+{% include "admin/routers/_detail_overview.html" %}
11
+<hr>
12
+<div id="interfaces">
13
+   {{ interfaces_table.render }}
14
+</div>
15
+{% endblock %}

+ 11
- 0
openstack_dashboard/dashboards/admin/routers/templates/routers/index.html View File

@@ -0,0 +1,11 @@
1
+{% extends 'base.html' %}
2
+{% load i18n %}
3
+{% block title %}{% trans "routers" %}{% endblock %}
4
+
5
+{% block page_header %}
6
+  {% include "horizon/common/_page_header.html" with title=_("routers") %}
7
+{% endblock page_header %}
8
+
9
+{% block main %}
10
+  {{ table.render }}
11
+{% endblock %}

+ 104
- 0
openstack_dashboard/dashboards/admin/routers/tests.py View File

@@ -0,0 +1,104 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+from mox import IsA
17
+from django import http
18
+from django.core.urlresolvers import reverse
19
+
20
+from openstack_dashboard import api
21
+from openstack_dashboard.dashboards.project.routers import tests as r_test
22
+from openstack_dashboard.test import helpers as test
23
+
24
+
25
+class RouterTests(test.BaseAdminViewTests, r_test.RouterTests):
26
+    DASHBOARD = 'admin'
27
+    INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
28
+    DETAIL_PATH = 'horizon:%s:routers:detail' % DASHBOARD
29
+
30
+    @test.create_stubs({api.quantum: ('router_list',),
31
+                        api.keystone: ('tenant_list',)})
32
+    def test_index(self):
33
+        tenants = self.tenants.list()
34
+        api.quantum.router_list(
35
+            IsA(http.HttpRequest),
36
+            search_opts=None).AndReturn(self.routers.list())
37
+        api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
38
+            .AndReturn(tenants)
39
+
40
+        self.mox.ReplayAll()
41
+
42
+        res = self.client.get(self.INDEX_URL)
43
+
44
+        self.assertTemplateUsed(res, '%s/routers/index.html' % self.DASHBOARD)
45
+        routers = res.context['table'].data
46
+        self.assertItemsEqual(routers, self.routers.list())
47
+
48
+    @test.create_stubs({api.quantum: ('router_list',),
49
+                        api.keystone: ('tenant_list',)})
50
+    def test_index_router_list_exception(self):
51
+        tenants = self.tenants.list()
52
+        api.quantum.router_list(
53
+            IsA(http.HttpRequest),
54
+            search_opts=None).AndRaise(self.exceptions.quantum)
55
+        self.mox.ReplayAll()
56
+
57
+        res = self.client.get(self.INDEX_URL)
58
+
59
+        self.assertTemplateUsed(res, '%s/routers/index.html' % self.DASHBOARD)
60
+        self.assertEqual(len(res.context['table'].data), 0)
61
+        self.assertMessageCount(res, error=1)
62
+
63
+    @test.create_stubs({api.quantum: ('router_list', 'router_create'),
64
+                        api.keystone: ('tenant_list',)})
65
+    def test_router_create_post(self):
66
+        router = self.routers.first()
67
+        tenants = self.tenants.list()
68
+        api.quantum.router_create(
69
+            IsA(http.HttpRequest),
70
+            name=router.name,
71
+            tenant_id=router.tenant_id).AndReturn(router)
72
+        api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
73
+            .AndReturn(tenants)
74
+
75
+        self.mox.ReplayAll()
76
+
77
+        form_data = {'name': router.name,
78
+                     'tenant_id': router.tenant_id}
79
+        url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
80
+        res = self.client.post(url, form_data)
81
+
82
+        self.assertNoFormErrors(res)
83
+        self.assertRedirectsNoFollow(res, self.INDEX_URL)
84
+
85
+    @test.create_stubs({api.quantum: ('router_list', 'router_create'),
86
+                        api.keystone: ('tenant_list',)})
87
+    def test_router_create_post_exception(self):
88
+        router = self.routers.first()
89
+        tenants = self.tenants.list()
90
+        api.quantum.router_create(
91
+            IsA(http.HttpRequest),
92
+            name=router.name,
93
+            tenant_id=router.tenant_id).AndRaise(self.exceptions.quantum)
94
+        api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
95
+            .AndReturn(tenants)
96
+        self.mox.ReplayAll()
97
+
98
+        form_data = {'name': router.name,
99
+                     'tenant_id': router.tenant_id}
100
+        url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
101
+        res = self.client.post(url, form_data)
102
+
103
+        self.assertNoFormErrors(res)
104
+        self.assertRedirectsNoFollow(res, self.INDEX_URL)

+ 34
- 0
openstack_dashboard/dashboards/admin/routers/urls.py View File

@@ -0,0 +1,34 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+from django.conf.urls.defaults import patterns, url
18
+
19
+from .views import (IndexView, CreateView, DetailView)
20
+from .ports.views import (AddInterfaceView, SetGatewayView)
21
+
22
+
23
+urlpatterns = patterns('horizon.dashboards.admin.routers.views',
24
+    url(r'^$', IndexView.as_view(), name='index'),
25
+    url(r'^create/$', CreateView.as_view(), name='create'),
26
+    url(r'^(?P<router_id>[^/]+)/$',
27
+        DetailView.as_view(),
28
+        name='detail'),
29
+    url(r'^(?P<router_id>[^/]+)/addinterface', AddInterfaceView.as_view(),
30
+        name='addinterface'),
31
+    url(r'^(?P<router_id>[^/]+)/setgateway',
32
+        SetGatewayView.as_view(),
33
+        name='setgateway'),
34
+)

+ 76
- 0
openstack_dashboard/dashboards/admin/routers/views.py View File

@@ -0,0 +1,76 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+"""
18
+Views for managing Quantum Routers.
19
+"""
20
+
21
+import logging
22
+
23
+from django.core.urlresolvers import reverse_lazy
24
+from django.utils.translation import ugettext_lazy as _
25
+
26
+from horizon import exceptions
27
+from horizon import forms
28
+from openstack_dashboard import api
29
+from openstack_dashboard.dashboards.admin.networks import views
30
+from openstack_dashboard.dashboards.project.routers import views as r_views
31
+
32
+from .ports.tables import PortsTable
33
+from .forms import CreateForm
34
+from .tables import RoutersTable
35
+
36
+
37
+LOG = logging.getLogger(__name__)
38
+
39
+
40
+class IndexView(views.IndexView):
41
+    table_class = RoutersTable
42
+    template_name = 'admin/routers/index.html'
43
+
44
+    def _get_routers(self, search_opts=None):
45
+        try:
46
+            routers = api.quantum.router_list(self.request,
47
+                                              search_opts=search_opts)
48
+        except:
49
+            routers = []
50
+            exceptions.handle(self.request,
51
+                              _('Unable to retrieve router list.'))
52
+        if routers:
53
+            tenant_dict = self._get_tenant_list()
54
+            for r in routers:
55
+                 # Set tenant name
56
+                tenant = tenant_dict.get(r.tenant_id, None)
57
+                r.tenant_name = getattr(tenant, 'name', None)
58
+                # If name is empty use UUID as name
59
+                r.set_id_as_name_if_empty()
60
+        return routers
61
+
62
+    def get_data(self):
63
+        routers = self._get_routers()
64
+        return routers
65
+
66
+
67
+class DetailView(r_views.DetailView):
68
+    table_classes = (PortsTable, )
69
+    template_name = 'admin/routers/detail.html'
70
+    failure_url = reverse_lazy('horizon:admin:routers:index')
71
+
72
+
73
+class CreateView(forms.ModalFormView):
74
+    form_class = CreateForm
75
+    template_name = 'admin/routers/create.html'
76
+    success_url = reverse_lazy("horizon:admin:routers:index")

+ 2
- 1
openstack_dashboard/dashboards/project/dashboard.py View File

@@ -27,7 +27,8 @@ class BasePanels(horizon.PanelGroup):
27 27
               'volumes',
28 28
               'images_and_snapshots',
29 29
               'access_and_security',
30
-              'networks')
30
+              'networks',
31
+              'routers')
31 32
 
32 33
 
33 34
 class ObjectStorePanels(horizon.PanelGroup):

+ 0
- 0
openstack_dashboard/dashboards/project/routers/__init__.py View File


+ 41
- 0
openstack_dashboard/dashboards/project/routers/forms.py View File

@@ -0,0 +1,41 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+# All rights reserved.
5
+
6
+"""
7
+Views for managing Quantum Routers.
8
+"""
9
+import logging
10
+
11
+from django.core.urlresolvers import reverse
12
+from django.utils.translation import ugettext_lazy as _
13
+
14
+from horizon import forms
15
+from horizon import exceptions
16
+from horizon import messages
17
+from openstack_dashboard import api
18
+
19
+LOG = logging.getLogger(__name__)
20
+
21
+
22
+class CreateForm(forms.SelfHandlingForm):
23
+    name = forms.CharField(max_length="255", label=_("Router Name"))
24
+    failure_url = 'horizon:project:routers:index'
25
+
26
+    def __init__(self, request, *args, **kwargs):
27
+        super(CreateForm, self).__init__(request, *args, **kwargs)
28
+
29
+    def handle(self, request, data):
30
+        try:
31
+            router = api.quantum.router_create(request,
32
+                                               name=data['name'])
33
+            message = 'Router created "%s"' % data['name']
34
+            messages.success(request, message)
35
+            return router
36
+        except:
37
+            msg = _('Failed to create router "%s".') % data['name']
38
+            LOG.info(msg)
39
+            redirect = reverse(self.failure_url)
40
+            exceptions.handle(request, msg, redirect=redirect)
41
+            return False

+ 29
- 0
openstack_dashboard/dashboards/project/routers/panel.py View File

@@ -0,0 +1,29 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+from django.utils.translation import ugettext_lazy as _
18
+
19
+import horizon
20
+
21
+from openstack_dashboard.dashboards.project import dashboard
22
+
23
+
24
+class Routers(horizon.Panel):
25
+    name = "Routers"
26
+    slug = 'routers'
27
+    permissions = ('openstack.services.network',)
28
+
29
+dashboard.Project.register(Routers)

+ 0
- 0
openstack_dashboard/dashboards/project/routers/ports/__init__.py View File


+ 132
- 0
openstack_dashboard/dashboards/project/routers/ports/forms.py View File

@@ -0,0 +1,132 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+import logging
18
+
19
+from django.core.urlresolvers import reverse
20
+from django.utils.translation import ugettext_lazy as _
21
+
22
+from horizon import forms
23
+from horizon import messages
24
+from horizon import exceptions
25
+from openstack_dashboard import api
26
+
27
+LOG = logging.getLogger(__name__)
28
+
29
+
30
+class AddInterface(forms.SelfHandlingForm):
31
+    subnet_id = forms.ChoiceField(label=_("Subnet ID"), required=False)
32
+    router_id = forms.CharField(label=_("Router ID"),
33
+                                widget=forms.TextInput(
34
+                                    attrs={'readonly': 'readonly'}))
35
+    failure_url = 'horizon:project:routers:detail'
36
+
37
+    def __init__(self, request, *args, **kwargs):
38
+        super(AddInterface, self).__init__(request, *args, **kwargs)
39
+        c = self.populate_subnet_id_choices(request)
40
+        self.fields['subnet_id'].choices = c
41
+
42
+    def populate_subnet_id_choices(self, request):
43
+        tenant_id = self.request.user.tenant_id
44
+        networks = []
45
+        try:
46
+            networks = api.quantum.network_list_for_tenant(request, tenant_id)
47
+        except Exception as e:
48
+            msg = _('Failed to get network list %s') % e.message
49
+            LOG.info(msg)
50
+            messages.error(request, msg)
51
+            redirect = reverse(self.failure_url,
52
+                               args=[request.REQUEST['router_id']])
53
+            exceptions.handle(request, msg, redirect=redirect)
54
+            return
55
+
56
+        choices = []
57
+        for n in networks:
58
+            net_name = n.name + ': ' if n.name else ''
59
+            choices += [(subnet.id,
60
+                         '%s%s (%s)' % (net_name, subnet.cidr,
61
+                                        subnet.name or subnet.id))
62
+                        for subnet in n['subnets']]
63
+        if choices:
64
+            choices.insert(0, ("", _("Select Subnet")))
65
+        else:
66
+            choices.insert(0, ("", _("No subnets available.")))
67
+        return choices
68
+
69
+    def handle(self, request, data):
70
+        try:
71
+            api.quantum.router_add_interface(request,
72
+                                             data['router_id'],
73
+                                             subnet_id=data['subnet_id'])
74
+            msg = _('Interface added')
75
+            LOG.debug(msg)
76
+            messages.success(request, msg)
77
+            return True
78
+        except Exception as e:
79
+            msg = _('Failed to add_interface %s') % e.message
80
+            LOG.info(msg)
81
+            messages.error(request, msg)
82
+            redirect = reverse(self.failure_url, args=[data['router_id']])
83
+            exceptions.handle(request, msg, redirect=redirect)
84
+
85
+
86
+class SetGatewayForm(forms.SelfHandlingForm):
87
+    network_id = forms.ChoiceField(label=_("Network ID"), required=False)
88
+    router_id = forms.CharField(label=_("Router ID"),
89
+                                widget=forms.TextInput(
90
+                                    attrs={'readonly': 'readonly'}))
91
+    failure_url = 'horizon:project:routers:detail'
92
+
93
+    def __init__(self, request, *args, **kwargs):
94
+        super(SetGatewayForm, self).__init__(request, *args, **kwargs)
95
+        c = self.populate_network_id_choices(request)
96
+        self.fields['network_id'].choices = c
97
+
98
+    def populate_network_id_choices(self, request):
99
+        search_opts = {'router:external': True}
100
+        try:
101
+            networks = api.quantum.network_list(request, **search_opts)
102
+        except Exception as e:
103
+            msg = _('Failed to get network list %s') % e.message
104
+            LOG.info(msg)
105
+            messages.error(request, msg)
106
+            redirect = reverse(self.failure_url,
107
+                               args=[request.REQUEST['router_id']])
108
+            exceptions.handle(request, msg, redirect=redirect)
109
+            return
110
+        choices = [(network.id, network.name or network.id)
111
+                   for network in networks]
112
+        if choices:
113
+            choices.insert(0, ("", _("Select network")))
114
+        else:
115
+            choices.insert(0, ("", _("No networks available.")))
116
+        return choices
117
+
118
+    def handle(self, request, data):
119
+        try:
120
+            api.quantum.router_add_gateway(request,
121
+                                           data['router_id'],
122
+                                           data['network_id'])
123
+            msg = _('Gateway interface is added')
124
+            LOG.debug(msg)
125
+            messages.success(request, msg)
126
+            return True
127
+        except Exception as e:
128
+            msg = _('Failed to set gateway %s') % e.message
129
+            LOG.info(msg)
130
+            messages.error(request, msg)
131
+            redirect = reverse(self.failure_url, args=[data['router_id']])
132
+            exceptions.handle(request, msg, redirect=redirect)

+ 103
- 0
openstack_dashboard/dashboards/project/routers/ports/tables.py View File

@@ -0,0 +1,103 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+import logging
18
+
19
+from django.core.urlresolvers import reverse
20
+from django.utils.translation import ugettext_lazy as _
21
+
22
+from horizon import exceptions
23
+from horizon import tables
24
+from openstack_dashboard import api
25
+from openstack_dashboard.dashboards.project.networks.ports.tables import\
26
+        get_fixed_ips, get_attached
27
+
28
+LOG = logging.getLogger(__name__)
29
+
30
+
31
+def get_device_owner(port):
32
+    if port['device_owner'] == 'network:router_gateway':
33
+        return _('Gateway')
34
+    else:
35
+        return ' '
36
+
37
+
38
+class SetGateway(tables.LinkAction):
39
+    name = "setgateway"
40
+    verbose_name = _("Add Gateway Interface")
41
+    url = "horizon:project:routers:setgateway"
42
+    classes = ("ajax-modal", "btn-camera")
43
+
44
+    def get_link_url(self, datum=None):
45
+        router_id = self.table.kwargs['router_id']
46
+        return reverse(self.url, args=(router_id,))
47
+
48
+
49
+class AddInterface(tables.LinkAction):
50
+    name = "create"
51
+    verbose_name = _("Add Interface")
52
+    url = "horizon:project:routers:addinterface"
53
+    classes = ("ajax-modal", "btn-create")
54
+
55
+    def get_link_url(self, datum=None):
56
+        router_id = self.table.kwargs['router_id']
57
+        return reverse(self.url, args=(router_id,))
58
+
59
+
60
+class RemoveInterface(tables.DeleteAction):
61
+    data_type_singular = _("Interface")
62
+    data_type_plural = _("Interfaces")
63
+    failure_url = 'horizon:project:routers:detail'
64
+
65
+    def delete(self, request, obj_id):
66
+        try:
67
+            router_id = self.table.kwargs['router_id']
68
+            port = api.quantum.port_get(request, obj_id)
69
+            if port['device_owner'] == 'network:router_gateway':
70
+                api.quantum.router_remove_gateway(request, router_id)
71
+            else:
72
+                api.quantum.router_remove_interface(request,
73
+                                                    router_id,
74
+                                                    port_id=obj_id)
75
+        except:
76
+            msg = _('Failed to delete interface %s') % obj_id
77
+            LOG.info(msg)
78
+            router_id = self.table.kwargs['router_id']
79
+            redirect = reverse(self.failure_url,
80
+                               args=[router_id])
81
+            exceptions.handle(request, msg, redirect=redirect)
82
+
83
+
84
+class PortsTable(tables.DataTable):
85
+    name = tables.Column("name",
86
+                         verbose_name=_("Name"),
87
+                         link="horizon:project:networks:ports:detail")
88
+    fixed_ips = tables.Column(get_fixed_ips, verbose_name=_("Fixed IPs"))
89
+    attached = tables.Column(get_attached, verbose_name=_("Device Attached"))
90
+    status = tables.Column("status", verbose_name=_("Status"))
91
+    device_owner = tables.Column(get_device_owner,
92
+                                 verbose_name=_("Type"))
93
+    admin_state = tables.Column("admin_state",
94
+                                verbose_name=_("Admin State"))
95
+
96
+    def get_object_display(self, port):
97
+        return port.id
98
+
99
+    class Meta:
100
+        name = "interfaces"
101
+        verbose_name = _("Interfaces")
102
+        table_actions = (AddInterface, SetGateway, RemoveInterface)
103
+        row_actions = (RemoveInterface, )

+ 47
- 0
openstack_dashboard/dashboards/project/routers/ports/tabs.py View File

@@ -0,0 +1,47 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+import logging
17
+
18
+from django.core.urlresolvers import reverse
19
+from django.utils.translation import ugettext_lazy as _
20
+
21
+from horizon import exceptions
22
+from horizon import tabs
23
+from openstack_dashboard import api
24
+
25
+LOG = logging.getLogger(__name__)
26
+
27
+
28
+class OverviewTab(tabs.Tab):
29
+    name = _("Overview")
30
+    slug = "overview"
31
+    template_name = "project/networks/ports/_detail_overview.html"
32
+    failure_url = 'horizon:project:routers:index'
33
+
34
+    def get_context_data(self, request):
35
+        port_id = self.tab_group.kwargs['port_id']
36
+        try:
37
+            port = api.quantum.port_get(self.request, port_id)
38
+        except:
39
+            redirect = reverse(self.failure_url)
40
+            msg = _('Unable to retrieve port details.')
41
+            exceptions.handle(request, msg, redirect=redirect)
42
+        return {'port': port}
43
+
44
+
45
+class PortDetailTabs(tabs.TabGroup):
46
+    slug = "port_details"
47
+    tabs = (OverviewTab,)

+ 24
- 0
openstack_dashboard/dashboards/project/routers/ports/urls.py View File

@@ -0,0 +1,24 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+from django.conf.urls.defaults import patterns, url
18
+
19
+from .views import DetailView
20
+
21
+PORTS = r'^(?P<port_id>[^/]+)/%s$'
22
+
23
+urlpatterns = patterns('horizon.dashboards.project.networks.ports.views',
24
+    url(PORTS % 'detail', DetailView.as_view(), name='detail'))

+ 100
- 0
openstack_dashboard/dashboards/project/routers/ports/views.py View File

@@ -0,0 +1,100 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+import logging
18
+
19
+from django.core.urlresolvers import reverse
20
+
21
+from horizon import tabs
22
+from horizon import forms
23
+from horizon import exceptions
24
+from openstack_dashboard import api
25
+from .tabs import PortDetailTabs
26
+from .forms import (AddInterface, SetGatewayForm)
27
+
28
+
29
+LOG = logging.getLogger(__name__)
30
+
31
+
32
+class AddInterfaceView(forms.ModalFormView):
33
+    form_class = AddInterface
34
+    template_name = 'project/routers/ports/create.html'
35
+    success_url = 'horizon:project:routers:detail'
36
+    failure_url = 'horizon:project:routers:detail'
37
+
38
+    def get_success_url(self):
39
+        return reverse(self.success_url,
40
+                       args=(self.kwargs['router_id'],))
41
+
42
+    def get_object(self):
43
+        if not hasattr(self, "_object"):
44
+            try:
45
+                router_id = self.kwargs["router_id"]
46
+                self._object = api.quantum.router_get(self.request,
47
+                                                      router_id)
48
+            except:
49
+                redirect = reverse(self.failure_url, args=[router_id])
50
+                msg = _("Unable to retrieve router.")
51
+                exceptions.handle(self.request, msg, redirect=redirect)
52
+        return self._object
53
+
54
+    def get_context_data(self, **kwargs):
55
+        context = super(AddInterfaceView, self).get_context_data(**kwargs)
56
+        context['router'] = self.get_object()
57
+        return context
58
+
59
+    def get_initial(self):
60
+        router = self.get_object()
61
+        return {"router_id": self.kwargs['router_id'],
62
+                "router_name": router.name}
63
+
64
+
65
+class SetGatewayView(forms.ModalFormView):
66
+    form_class = SetGatewayForm
67
+    template_name = 'project/routers/ports/setgateway.html'
68
+    success_url = 'horizon:project:routers:detail'
69
+    failure_url = 'horizon:project:routers:detail'
70
+
71
+    def get_success_url(self):
72
+        return reverse(self.success_url,
73
+                       args=(self.kwargs['router_id'],))
74
+
75
+    def get_object(self):
76
+        if not hasattr(self, "_object"):
77
+            try:
78
+                router_id = self.kwargs["router_id"]
79
+                self._object = api.quantum.router_get(self.request,
80
+                                                      router_id)
81
+            except:
82
+                redirect = reverse(self.failure_url)
83
+                msg = _("Unable to set gateway.")
84
+                exceptions.handle(self.request, msg, redirect=redirect)
85
+        return self._object
86
+
87
+    def get_context_data(self, **kwargs):
88
+        context = super(SetGatewayView, self).get_context_data(**kwargs)
89
+        context['router'] = self.get_object()
90
+        return context
91
+
92
+    def get_initial(self):
93
+        router = self.get_object()
94
+        return {"router_id": self.kwargs['router_id'],
95
+                "router_name": router.name}
96
+
97
+
98
+class DetailView(tabs.TabView):
99
+    tab_group_class = PortDetailTabs
100
+    template_name = 'project/networks/ports/detail.html'

+ 90
- 0
openstack_dashboard/dashboards/project/routers/tables.py View File

@@ -0,0 +1,90 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+import logging
18
+
19
+from django.core.urlresolvers import reverse
20
+from django.template.defaultfilters import title
21
+from django.utils.translation import ugettext_lazy as _
22
+
23
+from horizon import exceptions
24
+from horizon import messages
25
+from horizon import tables
26
+from openstack_dashboard import api
27
+from quantumclient.common import exceptions as q_ext
28
+
29
+LOG = logging.getLogger(__name__)
30
+
31
+
32
+class DeleteRouter(tables.DeleteAction):
33
+    data_type_singular = _("Router")
34
+    data_type_plural = _("Routers")
35
+    redirect_url = "horizon:project:routers:index"
36
+
37
+    def delete(self, request, obj_id):
38
+        obj = self.table.get_object_by_id(obj_id)
39
+        name = self.table.get_object_display(obj)
40
+        try:
41
+            api.router_delete(request, obj_id)
42
+        except q_ext.QuantumClientException as e:
43
+            msg = _('Unable to delete router "%s"') % e.message
44
+            LOG.info(msg)
45
+            messages.error(request, msg)
46
+            redirect = reverse(self.redirect_url)
47
+            raise exceptions.Http302(redirect, message=msg)
48
+        except Exception as e:
49
+            msg = _('Unable to delete router "%s"') % name
50
+            LOG.info(msg)
51
+            exceptions.handle(request, msg)
52
+
53
+    def allowed(self, request, router=None):
54
+        return True
55
+
56
+
57
+class CreateRouter(tables.LinkAction):
58
+    name = "create"
59
+    verbose_name = _("Create Router")
60
+    url = "horizon:project:routers:create"
61
+    classes = ("ajax-modal", "btn-create")
62
+
63
+
64
+class UpdateRow(tables.Row):
65
+    ajax = True
66
+
67
+    def get_data(self, request, router_id):
68
+        router = api.router_get(request, router_id)
69
+        return router
70
+
71
+
72
+class RoutersTable(tables.DataTable):
73
+    name = tables.Column("name",
74
+                         verbose_name=_("Name"),
75
+                         link="horizon:project:routers:detail")
76
+    status = tables.Column("status",
77
+                           filters=(title,),
78
+                           verbose_name=_("Status"),
79
+                           status=True)
80
+
81
+    def get_object_display(self, obj):
82
+        return obj.name
83
+
84
+    class Meta:
85
+        name = "Routers"
86
+        verbose_name = _("Routers")
87
+        status_columns = ["status"]
88
+        row_class = UpdateRow
89
+        table_actions = (CreateRouter, DeleteRouter)
90
+        row_actions = (DeleteRouter, )

+ 45
- 0
openstack_dashboard/dashboards/project/routers/tabs.py View File

@@ -0,0 +1,45 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+from django.core.urlresolvers import reverse
18
+from django.utils.translation import ugettext_lazy as _
19
+
20
+from horizon import exceptions
21
+from horizon import tabs
22
+from openstack_dashboard import api
23
+
24
+
25
+class OverviewTab(tabs.Tab):
26
+    name = _("Overview")
27
+    slug = "overview"
28
+    template_name = ("project/routers/_detail_overview.html")
29
+    redirect_url = 'horizon:project:routers:index'
30
+
31
+    def get_context_data(self, request):
32
+        router_id = self.tab_group.kwargs['router_id']
33
+        try:
34
+            router = api.router_get(request, router_id)
35
+        except:
36
+            redirect = reverse(redirect_url)
37
+            exceptions.handle(self.request,
38
+                              _('Unable to retrieve router details.'),
39
+                              redirect=redirect)
40
+        return {'router': router}
41
+
42
+
43
+class RouterDetailTabs(tabs.TabGroup):
44
+    slug = "router_details"
45
+    tabs = (OverviewTab,)

+ 21
- 0
openstack_dashboard/dashboards/project/routers/templates/routers/_create.html View File

@@ -0,0 +1,21 @@
1
+{% extends "horizon/common/_modal_form.html" %}
2
+{% load i18n horizon humanize %}
3
+
4
+{% block form_id %}{% endblock %}
5
+{% block form_action %}{% url horizon:project:routers:create %}?{{ request.GET.urlencode }}{% endblock %}
6
+
7
+{% block modal_id %}create_router_modal{% endblock %}
8
+{% block modal-header %}{% trans "Create router" %}{% endblock %}
9
+
10
+{% block modal-body %}
11
+  <div class="left">
12
+    <fieldset>
13
+      {% include "horizon/common/_form_fields.html" %}
14
+    </fieldset>
15
+  </div>
16
+{% endblock %}
17
+
18
+{% block modal-footer %}
19
+  <input class="btn btn-primary pull-right" type="submit" value="{% trans "Create router" %}" />
20
+  <a href="{% url horizon:project:routers:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
21
+{% endblock %}

+ 15
- 0
openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html View File

@@ -0,0 +1,15 @@
1
+{% load i18n sizeformat parse_date %}
2
+
3
+<h3>{% trans "Router Overview" %}: {{router.name|default:"None" }}</h3>
4
+
5
+<div class="info detail">
6
+  <dl>
7
+    <dt>{% trans "Name" %}</dt>
8
+    <dd>{{ router.name|default:"None" }}</dd>
9
+    <dt>{% trans "ID" %}</dt>
10
+    <dd>{{ router.id|default:"None" }}</dd>
11
+    <dt>{% trans "Status" %}</dt>
12
+    <dd>{{ router.status|default:"Unknown" }}</dd>
13
+  </dl>
14
+</div>
15
+

+ 11
- 0
openstack_dashboard/dashboards/project/routers/templates/routers/create.html View File

@@ -0,0 +1,11 @@
1
+{% extends 'base.html' %}
2
+{% load i18n %}
3
+{% block title %}Create router{% endblock %}
4
+
5
+{% block page_header %}
6
+  {% include "horizon/common/_page_header.html" with title=_("Create a router") %}
7
+{% endblock page_header %}
8
+
9
+{% block main %}
10
+    {% include 'project/routers/_create.html' %}
11
+{% endblock %}

+ 15
- 0
openstack_dashboard/dashboards/project/routers/templates/routers/detail.html View File

@@ -0,0 +1,15 @@
1
+{% extends 'base.html' %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Router Details" %}{% endblock %}
4
+
5
+{% block page_header %}
6
+  {% include "horizon/common/_page_header.html" with title=_("Router Detail") %}
7
+{% endblock page_header %}
8
+
9
+{% block main %}
10
+{% include "project/routers/_detail_overview.html" %}
11
+<hr>
12
+<div id="interfaces">
13
+   {{ interfaces_table.render }}
14
+</div>
15
+{% endblock %}

+ 11
- 0
openstack_dashboard/dashboards/project/routers/templates/routers/index.html View File

@@ -0,0 +1,11 @@
1
+{% extends 'base.html' %}
2
+{% load i18n %}
3
+{% block title %}{% trans "routers" %}{% endblock %}
4
+
5
+{% block page_header %}
6
+  {% include "horizon/common/_page_header.html" with title=_("routers") %}
7
+{% endblock page_header %}
8
+
9
+{% block main %}
10
+  {{ table.render }}
11
+{% endblock %}

+ 25
- 0
openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html View File

@@ -0,0 +1,25 @@
1
+{% extends "horizon/common/_modal_form.html" %}
2
+{% load i18n %}
3
+
4
+{% block form_id %}add_interface_form{% endblock %}
5
+{% block form_action %}{% url horizon:project:routers:addinterface router.id %}
6
+{% endblock %}
7
+
8
+{% block modal-header %}{% trans "Add interface" %}{% endblock %}
9
+
10
+{% block modal-body %}
11
+<div class="left">
12
+    <fieldset>
13
+        {% include "horizon/common/_form_fields.html" %}
14
+    </fieldset>
15
+</div>
16
+<div class="right">
17
+    <h3>{% trans "Description" %}:</h3>
18
+    <p>{% trans "You can add interface to the network with subnet_id." %}</p>
19
+</div>
20
+{% endblock %}
21
+
22
+{% block modal-footer %}
23
+  <input class="btn btn-primary pull-right" type="submit" value="{% trans "Add interface" %}" />
24
+  <a href="{% url horizon:project:routers:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
25
+{% endblock %}

+ 25
- 0
openstack_dashboard/dashboards/project/routers/templates/routers/ports/_setgateway.html View File

@@ -0,0 +1,25 @@
1
+{% extends "horizon/common/_modal_form.html" %}
2
+{% load i18n %}
3
+
4
+{% block form_id %}setgateway_form{% endblock %}
5
+{% block form_action %}{% url horizon:project:routers:setgateway router.id %}
6
+{% endblock %}
7
+
8
+{% block modal-header %}{% trans "Add Gateway Interface" %}{% endblock %}
9
+
10
+{% block modal-body %}
11
+<div class="left">
12
+    <fieldset>
13
+        {% include "horizon/common/_form_fields.html" %}
14
+    </fieldset>
15
+</div>
16
+<div class="right">
17
+    <h3>{% trans "Description" %}:</h3>
18
+    <p>{% trans "You can add inteface for Gateway. In this interface, NAT rule of floating IP will be set." %}</p>
19
+</div>
20
+{% endblock %}
21
+
22
+{% block modal-footer %}
23
+  <input class="btn btn-primary pull-right" type="submit" value="{% trans "Set Gateway" %}" />
24
+  <a href="{% url horizon:project:routers:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
25
+{% endblock %}

+ 11
- 0
openstack_dashboard/dashboards/project/routers/templates/routers/ports/create.html View File

@@ -0,0 +1,11 @@
1
+{% extends 'base.html' %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Add Interface" %}{% endblock %}
4
+
5
+{% block page_header %}
6
+  {% include "horizon/common/_page_header.html" with title=_("Add Interface") %}
7
+{% endblock page_header %}
8
+
9
+{% block main %}
10
+  {% include "project/routers/ports/_create.html" %}
11
+{% endblock %}

+ 11
- 0
openstack_dashboard/dashboards/project/routers/templates/routers/ports/setgateway.html View File

@@ -0,0 +1,11 @@
1
+{% extends 'base.html' %}
2
+{% load i18n %}
3
+{% block title %}{% trans "Set Gateway" %}{% endblock %}
4
+
5
+{% block page_header %}
6
+  {% include "horizon/common/_page_header.html" with title=_("Set Gateway") %}
7
+{% endblock page_header %}
8
+
9
+{% block main %}
10
+  {% include "project/routers/ports/_setgateway.html" %}
11
+{% endblock %}

+ 235
- 0
openstack_dashboard/dashboards/project/routers/tests.py View File

@@ -0,0 +1,235 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+from mox import IsA
17
+from django import http
18
+from django.core.urlresolvers import reverse
19
+
20
+from openstack_dashboard import api
21
+from openstack_dashboard.test import helpers as test
22
+
23
+
24
+class RouterTests(test.TestCase):
25
+    DASHBOARD = 'project'
26
+    INDEX_URL = reverse('horizon:%s:routers:index' % DASHBOARD)
27
+    DETAIL_PATH = 'horizon:%s:routers:detail' % DASHBOARD
28
+
29
+    @test.create_stubs({api.quantum: ('router_list',)})
30
+    def test_index(self):
31
+        api.quantum.router_list(
32
+            IsA(http.HttpRequest),
33
+            tenant_id=self.tenant.id,
34
+            search_opts=None).AndReturn(self.routers.list())
35
+
36
+        self.mox.ReplayAll()
37
+
38
+        res = self.client.get(self.INDEX_URL)
39
+
40
+        self.assertTemplateUsed(res, '%s/routers/index.html' % self.DASHBOARD)
41
+        routers = res.context['table'].data
42
+        self.assertItemsEqual(routers, self.routers.list())
43
+
44
+    @test.create_stubs({api.quantum: ('router_list',)})
45
+    def test_index_router_list_exception(self):
46
+        api.quantum.router_list(
47
+            IsA(http.HttpRequest),
48
+            tenant_id=self.tenant.id,
49
+            search_opts=None).AndRaise(self.exceptions.quantum)
50
+
51
+        self.mox.ReplayAll()
52
+
53
+        res = self.client.get(self.INDEX_URL)
54
+
55
+        self.assertTemplateUsed(res, '%s/routers/index.html' % self.DASHBOARD)
56
+        self.assertEqual(len(res.context['table'].data), 0)
57
+        self.assertMessageCount(res, error=1)
58
+
59
+    @test.create_stubs({api.quantum: ('router_get', 'port_list')})
60
+    def test_router_detail(self):
61
+        router_id = self.routers.first().id
62
+        api.quantum.router_get(IsA(http.HttpRequest), router_id)\
63
+            .AndReturn(self.routers.first())
64
+        api.quantum.port_list(IsA(http.HttpRequest),
65
+                              device_id=router_id)\
66
+            .AndReturn([self.ports.first()])
67
+
68
+        self.mox.ReplayAll()
69
+
70
+        res = self.client.get(reverse('horizon:%s'
71
+                                      ':routers:detail' % self.DASHBOARD,
72
+                                      args=[router_id]))
73
+
74
+        self.assertTemplateUsed(res, '%s/routers/detail.html' % self.DASHBOARD)
75
+        ports = res.context['interfaces_table'].data
76
+        self.assertItemsEqual(ports, [self.ports.first()])
77
+
78
+    @test.create_stubs({api.quantum: ('router_get', 'port_list')})
79
+    def test_router_detail_exception(self):
80
+        router_id = self.routers.first().id
81
+        api.quantum.router_get(IsA(http.HttpRequest), router_id)\
82
+            .AndRaise(self.exceptions.quantum)
83
+        api.quantum.port_list(IsA(http.HttpRequest),
84
+                              device_id=router_id)\
85
+            .AndReturn([self.ports.first()])
86
+        self.mox.ReplayAll()
87
+
88
+        res = self.client.get(reverse('horizon:%s'
89
+                                      ':routers:detail' % self.DASHBOARD,
90
+                                      args=[router_id]))
91
+        self.assertRedirectsNoFollow(res, self.INDEX_URL)
92
+
93
+    @test.create_stubs({api.quantum: ('router_create',)})
94
+    def test_router_create_post(self):
95
+        router = self.routers.first()
96
+        api.quantum.router_create(IsA(http.HttpRequest), name=router.name)\
97
+            .AndReturn(router)
98
+        self.mox.ReplayAll()
99
+
100
+        form_data = {'name': router.name}
101
+        url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
102
+        res = self.client.post(url, form_data)
103
+
104
+        self.assertNoFormErrors(res)
105
+        self.assertRedirectsNoFollow(res, self.INDEX_URL)
106
+
107
+    @test.create_stubs({api.quantum: ('router_create',)})
108
+    def test_router_create_post_exception(self):
109
+        router = self.routers.first()
110
+        api.quantum.router_create(IsA(http.HttpRequest), name=router.name)\
111
+            .AndRaise(self.exceptions.quantum)
112
+        self.mox.ReplayAll()
113
+
114
+        form_data = {'name': router.name}
115
+        url = reverse('horizon:%s:routers:create' % self.DASHBOARD)
116
+        res = self.client.post(url, form_data)
117
+
118
+        self.assertNoFormErrors(res)
119
+        self.assertRedirectsNoFollow(res, self.INDEX_URL)
120
+
121
+    @test.create_stubs({api.quantum: ('router_get',
122
+                                      'router_add_interface',
123
+                                      'network_list')})
124
+    def test_router_addinterface(self):
125
+        router = self.routers.first()
126
+        subnet = self.subnets.first()
127
+        api.quantum.router_add_interface(
128
+            IsA(http.HttpRequest),
129
+            router.id,
130
+            subnet_id=subnet.id).AndReturn(None)
131
+        api.quantum.router_get(IsA(http.HttpRequest), router.id)\
132
+            .AndReturn(router)
133
+        api.quantum.network_list(
134
+            IsA(http.HttpRequest),
135
+            shared=False,
136
+            tenant_id=router['tenant_id']).AndReturn(self.networks.list())
137
+        api.quantum.network_list(
138
+            IsA(http.HttpRequest),
139
+            shared=True).AndReturn([])
140
+        self.mox.ReplayAll()
141
+
142
+        form_data = {'router_id': router.id,
143
+                     'subnet_id': subnet.id}
144
+
145
+        url = reverse('horizon:%s:routers:addinterface' % self.DASHBOARD,
146
+                      args=[router.id])
147
+        res = self.client.post(url, form_data)
148
+        self.assertNoFormErrors(res)
149
+        detail_url = reverse(self.DETAIL_PATH, args=[router.id])
150
+        self.assertRedirectsNoFollow(res, detail_url)
151
+
152
+    @test.create_stubs({api.quantum: ('router_get',
153
+                                      'router_add_interface',
154
+                                      'network_list')})
155
+    def test_router_addinterface_exception(self):
156
+        router = self.routers.first()
157
+        subnet = self.subnets.first()
158
+        api.quantum.router_add_interface(
159
+            IsA(http.HttpRequest),
160
+            router.id,
161
+            subnet_id=subnet.id).AndRaise(self.exceptions.quantum)
162
+        api.quantum.router_get(IsA(http.HttpRequest), router.id)\
163
+            .AndReturn(router)
164
+        api.quantum.network_list(
165
+            IsA(http.HttpRequest),
166
+            shared=False,
167
+            tenant_id=router['tenant_id']).AndReturn(self.networks.list())
168
+        api.quantum.network_list(
169
+            IsA(http.HttpRequest),
170
+            shared=True).AndReturn([])
171
+        self.mox.ReplayAll()
172
+
173
+        form_data = {'router_id': router.id,
174
+                     'subnet_id': subnet.id}
175
+
176
+        url = reverse('horizon:%s:routers:addinterface' % self.DASHBOARD,
177
+                      args=[router.id])
178
+        res = self.client.post(url, form_data)
179
+        self.assertNoFormErrors(res)
180
+        detail_url = reverse(self.DETAIL_PATH, args=[router.id])
181
+        self.assertRedirectsNoFollow(res, detail_url)
182
+
183
+    @test.create_stubs({api.quantum: ('router_get',
184
+                                      'router_add_gateway',
185
+                                      'network_list')})
186
+    def test_router_add_gateway(self):
187
+        router = self.routers.first()
188
+        network = self.networks.first()
189
+        api.quantum.router_add_gateway(
190
+            IsA(http.HttpRequest),
191
+            router.id,
192
+            network.id).AndReturn(None)
193
+        api.quantum.router_get(
194
+            IsA(http.HttpRequest), router.id).AndReturn(router)
195
+        search_opts = {'router:external': True}
196
+        api.quantum.network_list(
197
+            IsA(http.HttpRequest), **search_opts).AndReturn([network])
198
+        self.mox.ReplayAll()
199
+
200
+        form_data = {'router_id': router.id,
201
+                     'network_id': network.id}
202
+
203
+        url = reverse('horizon:%s:routers:setgateway' % self.DASHBOARD,
204
+                      args=[router.id])
205
+        res = self.client.post(url, form_data)
206
+        self.assertNoFormErrors(res)
207
+        detail_url = reverse(self.DETAIL_PATH, args=[router.id])
208
+        self.assertRedirectsNoFollow(res, detail_url)
209
+
210
+    @test.create_stubs({api.quantum: ('router_get',
211
+                                      'router_add_gateway',
212
+                                      'network_list')})
213
+    def test_router_add_gateway_exception(self):
214
+        router = self.routers.first()
215
+        network = self.networks.first()
216
+        api.quantum.router_add_gateway(
217
+            IsA(http.HttpRequest),
218
+            router.id,
219
+            network.id).AndRaise(self.exceptions.quantum)
220
+        api.quantum.router_get(
221
+            IsA(http.HttpRequest), router.id).AndReturn(router)
222
+        search_opts = {'router:external': True}
223
+        api.quantum.network_list(
224
+            IsA(http.HttpRequest), **search_opts).AndReturn([network])
225
+        self.mox.ReplayAll()
226
+
227
+        form_data = {'router_id': router.id,
228
+                     'network_id': network.id}
229
+
230
+        url = reverse('horizon:%s:routers:setgateway' % self.DASHBOARD,
231
+                      args=[router.id])
232
+        res = self.client.post(url, form_data)
233
+        self.assertNoFormErrors(res)
234
+        detail_url = reverse(self.DETAIL_PATH, args=[router.id])
235
+        self.assertRedirectsNoFollow(res, detail_url)

+ 34
- 0
openstack_dashboard/dashboards/project/routers/urls.py View File

@@ -0,0 +1,34 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+from django.conf.urls.defaults import patterns, url
18
+
19
+from .views import (IndexView, CreateView, DetailView)
20
+from .ports.views import (AddInterfaceView, SetGatewayView)
21
+
22
+
23
+urlpatterns = patterns('horizon.dashboards.project.routers.views',
24
+    url(r'^$', IndexView.as_view(), name='index'),
25
+    url(r'^create/$', CreateView.as_view(), name='create'),
26
+    url(r'^(?P<router_id>[^/]+)/$',
27
+        DetailView.as_view(),
28
+        name='detail'),
29
+    url(r'^(?P<router_id>[^/]+)/addinterface', AddInterfaceView.as_view(),
30
+        name='addinterface'),
31
+    url(r'^(?P<router_id>[^/]+)/setgateway',
32
+        SetGatewayView.as_view(),
33
+        name='setgateway'),
34
+)

+ 105
- 0
openstack_dashboard/dashboards/project/routers/views.py View File

@@ -0,0 +1,105 @@
1
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
+
3
+# Copyright 2012,  Nachi Ueno,  NTT MCL,  Inc.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+"""
18
+Views for managing Quantum Routers.
19
+"""
20
+
21
+import logging
22
+
23
+from django import shortcuts
24
+from django.core.urlresolvers import reverse_lazy
25
+from django.utils.translation import ugettext_lazy as _
26
+from django.utils.datastructures import SortedDict
27
+
28
+from horizon import exceptions
29
+from horizon import forms
30
+from horizon import tables
31
+from horizon import tabs
32
+from openstack_dashboard import api
33
+from .ports.tables import PortsTable
34
+from .forms import CreateForm
35
+from .tables import RoutersTable
36
+from .tabs import RouterDetailTabs
37
+
38
+
39
+LOG = logging.getLogger(__name__)
40
+
41
+
42
+class IndexView(tables.DataTableView):
43
+    table_class = RoutersTable
44
+    template_name = 'project/routers/index.html'
45
+
46
+    def _get_routers(self, search_opts=None):
47
+        try:
48
+            tenant_id = self.request.user.tenant_id
49
+            routers = api.quantum.router_list(self.request,
50
+                                              tenant_id=tenant_id,
51
+                                              search_opts=search_opts)
52
+        except:
53
+            routers = []
54
+            exceptions.handle(self.request,
55
+                              _('Unable to retrieve router list.'))
56
+        for r in routers:
57
+            r.set_id_as_name_if_empty()
58
+        return routers
59
+
60
+    def get_data(self):
61
+        routers = self._get_routers()
62
+        return routers
63
+
64
+
65
+class DetailView(tables.MultiTableView):
66
+    table_classes = (PortsTable, )
67
+    template_name = 'project/routers/detail.html'
68
+    failure_url = reverse_lazy('horizon:project:routers:index')
69
+
70
+    def _get_data(self):
71
+        if not hasattr(self, "_router"):
72
+            try:
73
+                router_id = self.kwargs['router_id']
74
+                router = api.quantum.router_get(self.request, router_id)
75
+                router.set_id_as_name_if_empty(length=0)
76
+            except:
77
+                msg = _('Unable to retrieve details for router "%s".') \
78
+                        % (router_id)
79
+                exceptions.handle(self.request, msg, redirect=self.failure_url)
80
+            self._router = router
81
+        return self._router
82
+
83
+    def get_context_data(self, **kwargs):
84
+        context = super(DetailView, self).get_context_data(**kwargs)
85
+        context["router"] = self._get_data()
86
+        return context
87
+
88
+    def get_interfaces_data(self):
89
+        try:
90
+            device_id = self.kwargs['router_id']
91
+            ports = api.quantum.port_list(self.request,
92
+                                          device_id=device_id)
93
+        except:
94
+            ports = []
95
+            msg = _('Port list can not be retrieved.')
96
+            exceptions.handle(self.request, msg)
97
+        for p in ports:
98
+            p.set_id_as_name_if_empty()
99
+        return ports
100
+
101
+
102
+class CreateView(forms.ModalFormView):
103
+    form_class = CreateForm
104
+    template_name = 'project/routers/create.html'
105
+    success_url = reverse_lazy("horizon:project:routers:index")

+ 67
- 0
openstack_dashboard/test/api_tests/quantum_tests.py View File

@@ -203,3 +203,70 @@ class QuantumApiTests(test.APITestCase):
203 203
         self.mox.ReplayAll()
204 204
 
205 205
         api.quantum.port_delete(self.request, port_id)
206
+
207
+    def test_router_list(self):
208
+        routers = {'routers': self.api_routers.list()}
209
+
210
+        quantumclient = self.stub_quantumclient()
211
+        quantumclient.list_routers().AndReturn(routers)
212
+        self.mox.ReplayAll()
213
+
214
+        ret_val = api.quantum.router_list(self.request)
215
+        for n in ret_val:
216
+            self.assertIsInstance(n, api.quantum.Router)
217
+
218
+    def test_router_get(self):
219
+        router = {'router': self.api_routers.first()}
220
+        router_id = self.api_routers.first()['id']
221
+
222
+        quantumclient = self.stub_quantumclient()
223
+        quantumclient.show_router(router_id).AndReturn(router)
224
+        self.mox.ReplayAll()
225
+
226
+        ret_val = api.quantum.router_get(self.request, router_id)
227
+        self.assertIsInstance(ret_val, api.quantum.Router)
228
+
229
+    def test_router_create(self):
230
+        router = {'router': self.api_routers.first()}
231
+
232
+        quantumclient = self.stub_quantumclient()
233
+        form_data = {'router': {'name': 'router1'}}
234
+        quantumclient.create_router(body=form_data).AndReturn(router)
235
+        self.mox.ReplayAll()
236
+
237
+        ret_val = api.quantum.router_create(self.request, name='router1')
238
+        self.assertIsInstance(ret_val, api.quantum.Router)
239
+
240
+    def test_router_delete(self):
241
+        router_id = self.api_routers.first()['id']
242
+
243
+        quantumclient = self.stub_quantumclient()
244
+        quantumclient.delete_router(router_id)
245
+        self.mox.ReplayAll()
246
+
247
+        api.quantum.router_delete(self.request, router_id)
248
+
249
+    def test_router_add_interface(self):
250
+        subnet_id = self.api_subnets.first()['id']
251
+        router_id = self.api_routers.first()['id']
252
+
253
+        quantumclient = self.stub_quantumclient()
254
+        form_data = {'subnet_id': subnet_id}
255
+        quantumclient.add_interface_router(
256
+            router_id, form_data).AndReturn(None)
257
+        self.mox.ReplayAll()
258
+
259
+        api.quantum.router_add_interface(
260
+            self.request, router_id, subnet_id=subnet_id)
261
+
262
+    def test_router_remove_interface(self):
263
+        router_id = self.api_routers.first()['id']
264
+        fake_port = self.api_ports.first()['id']
265
+
266
+        quantumclient = self.stub_quantumclient()
267
+        quantumclient.remove_interface_router(
268
+            router_id, {'port_id': fake_port})
269
+        self.mox.ReplayAll()
270
+
271
+        api.quantum.router_remove_interface(
272
+            self.request, router_id, port_id=fake_port)

+ 29
- 3
openstack_dashboard/test/test_data/quantum_data.py View File

@@ -14,7 +14,7 @@
14 14
 
15 15
 import copy
16 16
 
17
-from openstack_dashboard.api.quantum import Network, Subnet, Port
17
+from openstack_dashboard.api.quantum import Network, Subnet, Port, Router
18 18
 
19 19
 from .utils import TestDataContainer
20 20
 
@@ -24,11 +24,13 @@ def data(TEST):
24 24
     TEST.networks = TestDataContainer()
25 25
     TEST.subnets = TestDataContainer()
26 26
     TEST.ports = TestDataContainer()
27
+    TEST.routers = TestDataContainer()
27 28
 
28 29
     # data return by quantumclient
29 30
     TEST.api_networks = TestDataContainer()
30 31
     TEST.api_subnets = TestDataContainer()
31 32
     TEST.api_ports = TestDataContainer()
33
+    TEST.api_routers = TestDataContainer()
32 34
 
33 35
     # 1st network
34 36
     network_dict = {'admin_state_up': True,
@@ -62,7 +64,6 @@ def data(TEST):
62 64
                  'network_id': network_dict['id'],
63 65
                  'status': 'ACTIVE',
64 66
                  'tenant_id': network_dict['tenant_id']}
65
-
66 67
     TEST.api_networks.add(network_dict)
67 68
     TEST.api_subnets.add(subnet_dict)
68 69
     TEST.api_ports.add(port_dict)
@@ -109,7 +110,6 @@ def data(TEST):
109 110
                  'network_id': network_dict['id'],
110 111
                  'status': 'ACTIVE',
111 112
                  'tenant_id': network_dict['tenant_id']}
112
-
113 113
     TEST.api_networks.add(network_dict)
114 114
     TEST.api_subnets.add(subnet_dict)
115 115
     TEST.api_ports.add(port_dict)
@@ -120,3 +120,29 @@ def data(TEST):
120 120
     TEST.networks.add(Network(network))
121 121
     TEST.subnets.add(subnet)
122 122
     TEST.ports.add(Port(port_dict))
123
+
124
+    # Set up router data
125
+    port_dict = {'admin_state_up': True,
126
+                 'device_id': '7180cede-bcd8-4334-b19f-f7ef2f331f53',
127
+                 'device_owner': 'network:router_gateway',
128
+                 'fixed_ips': [{'ip_address': '10.0.0.3',
129
+                                'subnet_id': subnet_dict['id']}],
130
+                 'id': '3ec7f3db-cb2f-4a34-ab6b-69a64d3f008c',
131
+                 'mac_address': 'fa:16:3e:9c:d5:7e',
132
+                 'name': '',
133
+                 'network_id': network_dict['id'],
134
+                 'status': 'ACTIVE',
135
+                 'tenant_id': '1'}
136
+    TEST.api_ports.add(port_dict)
137
+    TEST.ports.add(Port(port_dict))
138
+
139
+    router_dict = {'id': '279989f7-54bb-41d9-ba42-0d61f12fda61',
140
+                   'name': 'router1',
141
+                   'tenant_id': '1'}
142
+    TEST.api_routers.add(router_dict)
143
+    TEST.routers.add(Router(router_dict))
144
+    router_dict = {'id': '279989f7-54bb-41d9-ba42-0d61f12fda61',
145
+                   'name': 'router1',
146
+                   'tenant_id': '1'}
147
+    TEST.api_routers.add(router_dict)
148
+    TEST.routers.add(Router(router_dict))

Loading…
Cancel
Save