From 7fada8ebbf3a198dbee6853a23b93852fe5fbec1 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sat, 20 May 2017 08:39:06 +0900 Subject: [PATCH] Initial commit --- .gitignore | 2 + CONTRIBUTING.rst | 17 + HACKING.rst | 13 + LICENSE | 176 ++++ README.rst | 9 + neutron_vpnaas_dashboard/api/vpn.py | 381 ++++++++ .../dashboards/project/__init__.py | 0 .../dashboards/project/forms.py | 323 +++++++ .../dashboards/project/panel.py | 47 + .../dashboards/project/tables.py | 465 ++++++++++ .../dashboards/project/tabs.py | 233 +++++ .../templates/vpn/_add_ike_policy_help.html | 20 + .../templates/vpn/_add_ipsec_policy_help.html | 19 + .../templates/vpn/_add_vpn_service_help.html | 7 + .../templates/vpn/_ikepolicy_details.html | 38 + .../templates/vpn/_ipsecpolicy_details.html | 38 + .../vpn/_ipsecsiteconnection_details.html | 69 ++ .../templates/vpn/_update_ikepolicy.html | 7 + .../templates/vpn/_update_ipsecpolicy.html | 7 + .../vpn/_update_ipsecsiteconnection.html | 7 + .../templates/vpn/_update_vpnservice.html | 7 + .../project/templates/vpn/_vpn_ips.html | 13 + .../templates/vpn/_vpnservice_details.html | 54 ++ .../project/templates/vpn/index.html | 11 + .../templates/vpn/update_ikepolicy.html | 7 + .../templates/vpn/update_ipsecpolicy.html | 7 + .../vpn/update_ipsecsiteconnection.html | 7 + .../templates/vpn/update_vpnservice.html | 7 + .../dashboards/project/tests.py | 813 ++++++++++++++++++ .../dashboards/project/urls.py | 48 ++ .../dashboards/project/views.py | 361 ++++++++ .../dashboards/project/workflows.py | 516 +++++++++++ .../enabled/_1470_project_vpn_panel.py | 23 + requirements.txt | 7 + setup.cfg | 32 + setup.py | 29 + 36 files changed, 3820 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.rst create mode 100644 HACKING.rst create mode 100644 LICENSE create mode 100644 README.rst create mode 100644 neutron_vpnaas_dashboard/api/vpn.py create mode 100644 neutron_vpnaas_dashboard/dashboards/project/__init__.py create mode 100644 neutron_vpnaas_dashboard/dashboards/project/forms.py create mode 100644 neutron_vpnaas_dashboard/dashboards/project/panel.py create mode 100644 neutron_vpnaas_dashboard/dashboards/project/tables.py create mode 100644 neutron_vpnaas_dashboard/dashboards/project/tabs.py create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_ike_policy_help.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_ipsec_policy_help.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_vpn_service_help.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ikepolicy_details.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ipsecpolicy_details.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ipsecsiteconnection_details.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ikepolicy.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ipsecpolicy.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ipsecsiteconnection.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_vpnservice.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_vpn_ips.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_vpnservice_details.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/index.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ikepolicy.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ipsecpolicy.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ipsecsiteconnection.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_vpnservice.html create mode 100644 neutron_vpnaas_dashboard/dashboards/project/tests.py create mode 100644 neutron_vpnaas_dashboard/dashboards/project/urls.py create mode 100644 neutron_vpnaas_dashboard/dashboards/project/views.py create mode 100644 neutron_vpnaas_dashboard/dashboards/project/workflows.py create mode 100644 neutron_vpnaas_dashboard/enabled/_1470_project_vpn_panel.py create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9b568f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*.swp diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..b317773 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,17 @@ +If you would like to contribute to the development of OpenStack, you must +follow the steps in this page: + + http://docs.openstack.org/infra/manual/developers.html + +If you already have a good understanding of how the system works and your +OpenStack accounts are set up, you can skip to the development workflow +section of this documentation to learn how changes to OpenStack should be +submitted for review via the Gerrit tool: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/neutron-vpnaas-dashbaard diff --git a/HACKING.rst b/HACKING.rst new file mode 100644 index 0000000..8f9a551 --- /dev/null +++ b/HACKING.rst @@ -0,0 +1,13 @@ +=========================================== +Neutron VPNaaS Dashboard Style Commandments +=========================================== + +Read the OpenStack Style Commandments +http://docs.openstack.org/developer/hacking/ + +Project Specific Commandments +----------------------------- + +- Read the Horizon contributing documentation at + http://docs.openstack.org/developer/horizon/contributing.html +- [M322] Method's default argument shouldn't be mutable. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68c771a --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..1ac0300 --- /dev/null +++ b/README.rst @@ -0,0 +1,9 @@ +======================== +Neutron VPNaaS Dashboard +======================== + +OpenStack Dashboard panels for Neutron VPNaaS + +* Documentation: http://docs.openstack.org/developer/neutron-vpnaas-dashboard +* Source: http://git.openstack.org/cgit/openstack/neutron-vpnaas-dashboard +* Bugs: http://bugs.launchpad.net/neutron-vpnaas-dashboard diff --git a/neutron_vpnaas_dashboard/api/vpn.py b/neutron_vpnaas_dashboard/api/vpn.py new file mode 100644 index 0000000..90b8a09 --- /dev/null +++ b/neutron_vpnaas_dashboard/api/vpn.py @@ -0,0 +1,381 @@ +# Copyright 2013, Mirantis Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import absolute_import + +from collections import OrderedDict + +from horizon.utils.memoized import memoized + +from openstack_dashboard.api import neutron +from openstack_dashboard.contrib.developer.profiler import api as profiler + +neutronclient = neutron.neutronclient + + +class IKEPolicy(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron VPN IKEPolicy.""" + + +class IPSecPolicy(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron VPN IPSecPolicy.""" + + +class IPSecSiteConnection(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron IPSecSiteConnection.""" + + +class VPNService(neutron.NeutronAPIDictWrapper): + """Wrapper for neutron VPNService.""" + + +@profiler.trace +def vpnservice_create(request, **kwargs): + """Create VPNService + + :param request: request context + :param admin_state_up: admin state (default on) + :param name: name for VPNService + :param description: description for VPNService + :param router_id: router id for router of VPNService + :param subnet_id: subnet id for subnet of VPNService + """ + body = {'vpnservice': + {'admin_state_up': kwargs['admin_state_up'], + 'name': kwargs['name'], + 'description': kwargs['description'], + 'router_id': kwargs['router_id'], + 'subnet_id': kwargs['subnet_id']} + } + vpnservice = neutronclient(request).create_vpnservice(body).get( + 'vpnservice') + return VPNService(vpnservice) + + +@profiler.trace +def vpnservice_list(request, **kwargs): + return _vpnservice_list(request, expand_subnet=True, expand_router=True, + expand_conns=True, **kwargs) + + +def _vpnservice_list(request, expand_subnet=False, expand_router=False, + expand_conns=False, **kwargs): + vpnservices = neutronclient(request).list_vpnservices( + **kwargs).get('vpnservices') + if expand_subnet: + subnets = neutron.subnet_list(request) + subnet_dict = OrderedDict((s.id, s) for s in subnets) + for s in vpnservices: + s['subnet_name'] = subnet_dict.get(s['subnet_id']).cidr + if expand_router: + routers = neutron.router_list(request) + router_dict = OrderedDict((r.id, r) for r in routers) + for s in vpnservices: + s['router_name'] = router_dict.get(s['router_id']).name_or_id + if expand_conns: + ipsecsiteconns = _ipsecsiteconnection_list(request, **kwargs) + for s in vpnservices: + s['ipsecsiteconns'] = [c.id for c in ipsecsiteconns + if c.vpnservice_id == s['id']] + return [VPNService(v) for v in vpnservices] + + +@profiler.trace +def vpnservice_get(request, vpnservice_id): + return _vpnservice_get(request, vpnservice_id, expand_subnet=True, + expand_router=True, expand_conns=True) + + +def _vpnservice_get(request, vpnservice_id, expand_subnet=False, + expand_router=False, expand_conns=False): + vpnservice = neutronclient(request).show_vpnservice(vpnservice_id).get( + 'vpnservice') + if expand_subnet: + vpnservice['subnet'] = neutron.subnet_get( + request, vpnservice['subnet_id']) + if expand_router: + vpnservice['router'] = neutron.router_get( + request, vpnservice['router_id']) + if expand_conns: + ipsecsiteconns = _ipsecsiteconnection_list(request) + vpnservice['ipsecsiteconns'] = [c for c in ipsecsiteconns + if c.vpnservice_id == vpnservice['id']] + return VPNService(vpnservice) + + +@profiler.trace +def vpnservice_update(request, vpnservice_id, **kwargs): + vpnservice = neutronclient(request).update_vpnservice( + vpnservice_id, kwargs).get('vpnservice') + return VPNService(vpnservice) + + +@profiler.trace +def vpnservice_delete(request, vpnservice_id): + neutronclient(request).delete_vpnservice(vpnservice_id) + + +@profiler.trace +def ikepolicy_create(request, **kwargs): + """Create IKEPolicy + + :param request: request context + :param name: name for IKEPolicy + :param description: description for IKEPolicy + :param auth_algorithm: authorization algorithm for IKEPolicy + :param encryption_algorithm: encryption algorithm for IKEPolicy + :param ike_version: IKE version for IKEPolicy + :param lifetime: Lifetime Units and Value for IKEPolicy + :param pfs: Perfect Forward Secrecy for IKEPolicy + :param phase1_negotiation_mode: IKE Phase1 negotiation mode for IKEPolicy + """ + body = {'ikepolicy': + {'name': kwargs['name'], + 'description': kwargs['description'], + 'auth_algorithm': kwargs['auth_algorithm'], + 'encryption_algorithm': kwargs['encryption_algorithm'], + 'ike_version': kwargs['ike_version'], + 'lifetime': kwargs['lifetime'], + 'pfs': kwargs['pfs'], + 'phase1_negotiation_mode': kwargs['phase1_negotiation_mode']} + } + ikepolicy = neutronclient(request).create_ikepolicy(body).get( + 'ikepolicy') + return IKEPolicy(ikepolicy) + + +@profiler.trace +def ikepolicy_list(request, **kwargs): + return _ikepolicy_list(request, expand_conns=True, **kwargs) + + +def _ikepolicy_list(request, expand_conns=False, **kwargs): + ikepolicies = neutronclient(request).list_ikepolicies( + **kwargs).get('ikepolicies') + if expand_conns: + ipsecsiteconns = _ipsecsiteconnection_list(request, **kwargs) + for p in ikepolicies: + p['ipsecsiteconns'] = [c.id for c in ipsecsiteconns + if c.ikepolicy_id == p['id']] + return [IKEPolicy(v) for v in ikepolicies] + + +@profiler.trace +def ikepolicy_get(request, ikepolicy_id): + return _ikepolicy_get(request, ikepolicy_id, expand_conns=True) + + +def _ikepolicy_get(request, ikepolicy_id, expand_conns=False): + ikepolicy = neutronclient(request).show_ikepolicy( + ikepolicy_id).get('ikepolicy') + if expand_conns: + ipsecsiteconns = _ipsecsiteconnection_list(request) + ikepolicy['ipsecsiteconns'] = [c for c in ipsecsiteconns + if c.ikepolicy_id == ikepolicy['id']] + return IKEPolicy(ikepolicy) + + +@profiler.trace +def ikepolicy_update(request, ikepolicy_id, **kwargs): + ikepolicy = neutronclient(request).update_ikepolicy( + ikepolicy_id, kwargs).get('ikepolicy') + return IKEPolicy(ikepolicy) + + +@profiler.trace +def ikepolicy_delete(request, ikepolicy_id): + neutronclient(request).delete_ikepolicy(ikepolicy_id) + + +@profiler.trace +def ipsecpolicy_create(request, **kwargs): + """Create IPSecPolicy + + :param request: request context + :param name: name for IPSecPolicy + :param description: description for IPSecPolicy + :param auth_algorithm: authorization algorithm for IPSecPolicy + :param encapsulation_mode: encapsulation mode for IPSecPolicy + :param encryption_algorithm: encryption algorithm for IPSecPolicy + :param lifetime: Lifetime Units and Value for IPSecPolicy + :param pfs: Perfect Forward Secrecy for IPSecPolicy + :param transform_protocol: Transform Protocol for IPSecPolicy + """ + body = {'ipsecpolicy': + {'name': kwargs['name'], + 'description': kwargs['description'], + 'auth_algorithm': kwargs['auth_algorithm'], + 'encapsulation_mode': kwargs['encapsulation_mode'], + 'encryption_algorithm': kwargs['encryption_algorithm'], + 'lifetime': kwargs['lifetime'], + 'pfs': kwargs['pfs'], + 'transform_protocol': kwargs['transform_protocol']} + } + ipsecpolicy = neutronclient(request).create_ipsecpolicy(body).get( + 'ipsecpolicy') + return IPSecPolicy(ipsecpolicy) + + +@profiler.trace +def ipsecpolicy_list(request, **kwargs): + return _ipsecpolicy_list(request, expand_conns=True, **kwargs) + + +def _ipsecpolicy_list(request, expand_conns=False, **kwargs): + ipsecpolicies = neutronclient(request).list_ipsecpolicies( + **kwargs).get('ipsecpolicies') + if expand_conns: + ipsecsiteconns = _ipsecsiteconnection_list(request, **kwargs) + for p in ipsecpolicies: + p['ipsecsiteconns'] = [c.id for c in ipsecsiteconns + if c.ipsecpolicy_id == p['id']] + return [IPSecPolicy(v) for v in ipsecpolicies] + + +@profiler.trace +def ipsecpolicy_get(request, ipsecpolicy_id): + return _ipsecpolicy_get(request, ipsecpolicy_id, expand_conns=True) + + +def _ipsecpolicy_get(request, ipsecpolicy_id, expand_conns=False): + ipsecpolicy = neutronclient(request).show_ipsecpolicy( + ipsecpolicy_id).get('ipsecpolicy') + if expand_conns: + ipsecsiteconns = _ipsecsiteconnection_list(request) + ipsecpolicy['ipsecsiteconns'] = [c for c in ipsecsiteconns + if (c.ipsecpolicy_id == + ipsecpolicy['id'])] + return IPSecPolicy(ipsecpolicy) + + +@profiler.trace +def ipsecpolicy_update(request, ipsecpolicy_id, **kwargs): + ipsecpolicy = neutronclient(request).update_ipsecpolicy( + ipsecpolicy_id, kwargs).get('ipsecpolicy') + return IPSecPolicy(ipsecpolicy) + + +@profiler.trace +def ipsecpolicy_delete(request, ipsecpolicy_id): + neutronclient(request).delete_ipsecpolicy(ipsecpolicy_id) + + +@profiler.trace +def ipsecsiteconnection_create(request, **kwargs): + """Create IPSecSiteConnection + + :param request: request context + :param name: name for IPSecSiteConnection + :param description: description for IPSecSiteConnection + :param dpd: dead peer detection action, interval and timeout + :param ikepolicy_id: IKEPolicy associated with this connection + :param initiator: initiator state + :param ipsecpolicy_id: IPsecPolicy associated with this connection + :param mtu: MTU size for the connection + :param peer_address: Peer gateway public address + :param peer_cidrs: remote subnet(s) in CIDR format + :param peer_id: Peer router identity for authentication" + :param psk: Pre-Shared Key string + :param vpnservice_id: VPNService associated with this connection + :param admin_state_up: admin state (default on) + """ + body = {'ipsec_site_connection': + {'name': kwargs['name'], + 'description': kwargs['description'], + 'dpd': kwargs['dpd'], + 'ikepolicy_id': kwargs['ikepolicy_id'], + 'initiator': kwargs['initiator'], + 'ipsecpolicy_id': kwargs['ipsecpolicy_id'], + 'mtu': kwargs['mtu'], + 'peer_address': kwargs['peer_address'], + 'peer_cidrs': kwargs['peer_cidrs'], + 'peer_id': kwargs['peer_id'], + 'psk': kwargs['psk'], + 'vpnservice_id': kwargs['vpnservice_id'], + 'admin_state_up': kwargs['admin_state_up']} + } + ipsecsiteconnection = neutronclient(request).create_ipsec_site_connection( + body).get('ipsec_site_connection') + return IPSecSiteConnection(ipsecsiteconnection) + + +@profiler.trace +@memoized +def ipsecsiteconnection_list(request, **kwargs): + return _ipsecsiteconnection_list(request, expand_ikepolicies=True, + expand_ipsecpolicies=True, + expand_vpnservices=True, **kwargs) + + +@memoized +def _ipsecsiteconnection_list(request, expand_ikepolicies=False, + expand_ipsecpolicies=False, + expand_vpnservices=False, **kwargs): + ipsecsiteconnections = neutronclient(request).list_ipsec_site_connections( + **kwargs).get('ipsec_site_connections') + if expand_ikepolicies: + ikepolicies = _ikepolicy_list(request) + policy_dict = OrderedDict((p.id, p) for p in ikepolicies) + for c in ipsecsiteconnections: + c['ikepolicy_name'] = policy_dict.get(c['ikepolicy_id']).name_or_id + if expand_ipsecpolicies: + ipsecpolicies = _ipsecpolicy_list(request) + policy_dict = OrderedDict((p.id, p) for p in ipsecpolicies) + for c in ipsecsiteconnections: + c['ipsecpolicy_name'] = policy_dict.get(c['ipsecpolicy_id'] + ).name_or_id + if expand_vpnservices: + vpnservices = _vpnservice_list(request) + service_dict = OrderedDict((s.id, s) for s in vpnservices) + for c in ipsecsiteconnections: + c['vpnservice_name'] = service_dict.get(c['vpnservice_id'] + ).name_or_id + return [IPSecSiteConnection(v) for v in ipsecsiteconnections] + + +@profiler.trace +def ipsecsiteconnection_get(request, ipsecsiteconnection_id): + return _ipsecsiteconnection_get(request, ipsecsiteconnection_id, + expand_ikepolicies=True, + expand_ipsecpolicies=True, + expand_vpnservices=True) + + +def _ipsecsiteconnection_get(request, ipsecsiteconnection_id, + expand_ikepolicies, expand_ipsecpolicies, + expand_vpnservices): + ipsecsiteconnection = neutronclient(request).show_ipsec_site_connection( + ipsecsiteconnection_id).get('ipsec_site_connection') + if expand_ikepolicies: + ipsecsiteconnection['ikepolicy'] = _ikepolicy_get( + request, ipsecsiteconnection['ikepolicy_id']) + if expand_ipsecpolicies: + ipsecsiteconnection['ipsecpolicy'] = _ipsecpolicy_get( + request, ipsecsiteconnection['ipsecpolicy_id']) + if expand_vpnservices: + ipsecsiteconnection['vpnservice'] = _vpnservice_get( + request, ipsecsiteconnection['vpnservice_id']) + return IPSecSiteConnection(ipsecsiteconnection) + + +@profiler.trace +def ipsecsiteconnection_update(request, ipsecsiteconnection_id, **kwargs): + ipsecsiteconnection = neutronclient(request).update_ipsec_site_connection( + ipsecsiteconnection_id, kwargs).get('ipsec_site_connection') + return IPSecSiteConnection(ipsecsiteconnection) + + +@profiler.trace +def ipsecsiteconnection_delete(request, ipsecsiteconnection_id): + neutronclient(request).delete_ipsec_site_connection(ipsecsiteconnection_id) diff --git a/neutron_vpnaas_dashboard/dashboards/project/__init__.py b/neutron_vpnaas_dashboard/dashboards/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neutron_vpnaas_dashboard/dashboards/project/forms.py b/neutron_vpnaas_dashboard/dashboards/project/forms.py new file mode 100644 index 0000000..7cc0930 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/forms.py @@ -0,0 +1,323 @@ +# Copyright 2013, Mirantis Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages + +from neutron_vpnaas_dashboard import api as api_vpn + + +LOG = logging.getLogger(__name__) + + +class UpdateVPNService(forms.SelfHandlingForm): + name = forms.CharField(max_length=80, label=_("Name"), required=False) + vpnservice_id = forms.CharField( + label=_("ID"), + widget=forms.TextInput(attrs={'readonly': 'readonly'})) + description = forms.CharField( + required=False, max_length=80, label=_("Description")) + admin_state_up = forms.BooleanField(label=_("Enable Admin State"), + required=False) + + failure_url = 'horizon:project:vpn:index' + + def handle(self, request, context): + try: + data = {'vpnservice': {'name': context['name'], + 'description': context['description'], + 'admin_state_up': context['admin_state_up'], + }} + vpnservice = api_vpn.vpnservice_update( + request, context['vpnservice_id'], **data) + msg = (_('VPN Service %s was successfully updated.') + % context['name']) + LOG.debug(msg) + messages.success(request, msg) + return vpnservice + except Exception as e: + msg = _('Failed to update VPN Service %s') % context['name'] + LOG.info('%(msg)s: %(exception)s', {'msg': msg, 'exception': e}) + redirect = reverse(self.failure_url) + exceptions.handle(request, msg, redirect=redirect) + + +class UpdateIKEPolicy(forms.SelfHandlingForm): + name = forms.CharField(max_length=80, label=_("Name"), required=False) + ikepolicy_id = forms.CharField( + label=_("ID"), + widget=forms.TextInput(attrs={'readonly': 'readonly'})) + description = forms.CharField( + required=False, max_length=80, label=_("Description")) + # Currently this field has only one choice, so mark it as readonly. + auth_algorithm = forms.ChoiceField( + label=_("Authorization algorithm"), + choices=[('sha1', _('sha1'))], + widget=forms.ThemableSelectWidget(attrs={'readonly': 'readonly'}), + required=False) + encryption_algorithm = forms.ChoiceField( + label=_("Encryption algorithm"), + choices=[('3des', _('3des')), + ('aes-128', _('aes-128')), + ('aes-192', _('aes-192')), + ('aes-256', _('aes-256'))], + required=False) + ike_version = forms.ChoiceField( + label=_("IKE version"), + choices=[('v1', _('v1')), + ('v2', _('v2'))], + required=False) + # Currently this field has only one choice, so mark it as readonly. + lifetime_units = forms.ChoiceField( + label=_("Lifetime units for IKE keys"), + choices=[('seconds', _('seconds'))], + widget=forms.ThemableSelectWidget(attrs={'readonly': 'readonly'}), + required=False) + lifetime_value = forms.IntegerField( + min_value=60, + label=_("Lifetime value for IKE keys"), + help_text=_("Equal to or greater than 60"), + required=False) + pfs = forms.ChoiceField( + label=_("Perfect Forward Secrecy"), + choices=[('group2', _('group2')), + ('group5', _('group5')), + ('group14', _('group14'))], + required=False) + # Currently this field has only one choice, so mark it as readonly. + phase1_negotiation_mode = forms.ChoiceField( + label=_("IKE Phase1 negotiation mode"), + choices=[('main', 'main')], + widget=forms.ThemableSelectWidget(attrs={'readonly': 'readonly'}), + required=False) + + failure_url = 'horizon:project:vpn:index' + + def handle(self, request, context): + try: + data = {'ikepolicy': + {'name': context['name'], + 'description': context['description'], + 'auth_algorithm': context['auth_algorithm'], + 'encryption_algorithm': context['encryption_algorithm'], + 'ike_version': context['ike_version'], + 'lifetime': {'units': context['lifetime_units'], + 'value': context['lifetime_value']}, + 'pfs': context['pfs'], + 'phase1_negotiation_mode': + context['phase1_negotiation_mode'], + }} + ikepolicy = api_vpn.ikepolicy_update( + request, context['ikepolicy_id'], **data) + msg = (_('IKE Policy %s was successfully updated.') + % context['name']) + LOG.debug(msg) + messages.success(request, msg) + return ikepolicy + except Exception as e: + msg = _('Failed to update IKE Policy %s') % context['name'] + LOG.info('%(msg)s: %(exception)s', {'msg': msg, 'exception': e}) + redirect = reverse(self.failure_url) + exceptions.handle(request, msg, redirect=redirect) + + +class UpdateIPSecPolicy(forms.SelfHandlingForm): + name = forms.CharField(max_length=80, label=_("Name"), required=False) + ipsecpolicy_id = forms.CharField( + label=_("ID"), + widget=forms.TextInput(attrs={'readonly': 'readonly'})) + description = forms.CharField( + required=False, max_length=80, label=_("Description")) + # Currently this field has only one choice, so mark it as readonly. + auth_algorithm = forms.ChoiceField( + label=_("Authorization algorithm"), + choices=[('sha1', _('sha1'))], + widget=forms.TextInput(attrs={'readonly': 'readonly'}), + required=False) + encapsulation_mode = forms.ChoiceField( + label=_("Encapsulation mode"), + choices=[('tunnel', _('tunnel')), + ('transport', _('transport'))], + required=False) + encryption_algorithm = forms.ChoiceField( + label=_("Encryption algorithm"), + choices=[('3des', _('3des')), + ('aes-128', _('aes-128')), + ('aes-192', _('aes-192')), + ('aes-256', _('aes-256'))], + required=False) + # Currently this field has only one choice, so mark it as readonly. + lifetime_units = forms.ChoiceField( + label=_("Lifetime units"), + choices=[('seconds', _('seconds'))], + widget=forms.ThemableSelectWidget(attrs={'readonly': 'readonly'}), + required=False) + lifetime_value = forms.IntegerField( + min_value=60, + label=_("Lifetime value"), + help_text=_("Equal to or greater than 60"), + required=False) + pfs = forms.ChoiceField( + label=_("Perfect Forward Secrecy"), + choices=[('group2', _('group2')), + ('group5', _('group5')), + ('group14', _('group14'))], + required=False) + transform_protocol = forms.ChoiceField( + label=_("Transform Protocol"), + choices=[('esp', _('esp')), + ('ah', _('ah')), + ('ah-esp', _('ah-esp'))], + required=False) + + failure_url = 'horizon:project:vpn:index' + + def handle(self, request, context): + try: + data = {'ipsecpolicy': + {'name': context['name'], + 'description': context['description'], + 'auth_algorithm': context['auth_algorithm'], + 'encapsulation_mode': context['encapsulation_mode'], + 'encryption_algorithm': context['encryption_algorithm'], + 'lifetime': {'units': context['lifetime_units'], + 'value': context['lifetime_value']}, + 'pfs': context['pfs'], + 'transform_protocol': context['transform_protocol'], + }} + ipsecpolicy = api_vpn.ipsecpolicy_update( + request, context['ipsecpolicy_id'], **data) + msg = (_('IPSec Policy %s was successfully updated.') + % context['name']) + LOG.debug(msg) + messages.success(request, msg) + return ipsecpolicy + except Exception as e: + msg = _('Failed to update IPSec Policy %s') % context['name'] + LOG.info('%(msg)s: %(exception)s', {'msg': msg, 'exception': e}) + redirect = reverse(self.failure_url) + exceptions.handle(request, msg, redirect=redirect) + + +class UpdateIPSecSiteConnection(forms.SelfHandlingForm): + name = forms.CharField(max_length=80, label=_("Name"), required=False) + ipsecsiteconnection_id = forms.CharField( + label=_("ID"), + widget=forms.TextInput(attrs={'readonly': 'readonly'})) + description = forms.CharField( + required=False, max_length=80, label=_("Description")) + peer_address = forms.IPField( + label=_("Peer gateway public IPv4/IPv6 Address or FQDN"), + help_text=_("Peer gateway public IPv4/IPv6 address or FQDN for " + "the VPN Connection"), + version=forms.IPv4 | forms.IPv6, + mask=False) + peer_id = forms.IPField( + label=_("Peer router identity for authentication (Peer ID)"), + help_text=_("Peer router identity for authentication. " + "Can be IPv4/IPv6 address, e-mail, key ID, or FQDN"), + version=forms.IPv4 | forms.IPv6, + mask=False) + peer_cidrs = forms.MultiIPField( + label=_("Remote peer subnet(s)"), + help_text=_("Remote peer subnet(s) address(es) " + "with mask(s) in CIDR format " + "separated with commas if needed " + "(e.g. 20.1.0.0/24, 21.1.0.0/24)"), + version=forms.IPv4 | forms.IPv6, + mask=True) + psk = forms.CharField( + widget=forms.PasswordInput(render_value=True), + max_length=80, label=_("Pre-Shared Key (PSK) string")) + mtu = forms.IntegerField( + min_value=68, + required=False, + label=_("Maximum Transmission Unit size for the connection"), + help_text=_("Equal to or greater than 68 if the local subnet is IPv4. " + "Equal to or greater than 1280 if the local subnet " + "is IPv6.")) + dpd_action = forms.ChoiceField( + label=_("Dead peer detection actions"), + required=False, + choices=[('hold', _('hold')), + ('clear', _('clear')), + ('disabled', _('disabled')), + ('restart', _('restart')), + ('restart-by-peer', _('restart-by-peer'))]) + dpd_interval = forms.IntegerField( + min_value=1, + required=False, + label=_("Dead peer detection interval"), + help_text=_("Valid integer lesser than the DPD timeout")) + dpd_timeout = forms.IntegerField( + min_value=1, + required=False, + label=_("Dead peer detection timeout"), + help_text=_("Valid integer greater than the DPD interval")) + initiator = forms.ChoiceField( + label=_("Initiator state"), + required=False, + choices=[('bi-directional', _('bi-directional')), + ('response-only', _('response-only'))]) + admin_state_up = forms.BooleanField(label=_("Enable Admin State"), + required=False) + + failure_url = 'horizon:project:vpn:index' + + def clean(self): + cleaned_data = super(UpdateIPSecSiteConnection, self).clean() + interval = cleaned_data.get('dpd_interval') + timeout = cleaned_data.get('dpd_timeout') + + if not interval < timeout: + msg = _("DPD Timeout must be greater than DPD Interval") + self._errors['dpd_timeout'] = self.error_class([msg]) + return cleaned_data + + def handle(self, request, context): + try: + data = {'ipsec_site_connection': + {'name': context['name'], + 'description': context['description'], + 'peer_address': context['peer_address'], + 'peer_id': context['peer_id'], + 'peer_cidrs': context[ + 'peer_cidrs'].replace(" ", "").split(","), + 'psk': context['psk'], + 'mtu': context['mtu'], + 'dpd': {'action': context['dpd_action'], + 'interval': context['dpd_interval'], + 'timeout': context['dpd_timeout']}, + 'initiator': context['initiator'], + 'admin_state_up': context['admin_state_up'], + }} + ipsecsiteconnection = api_vpn.ipsecsiteconnection_update( + request, context['ipsecsiteconnection_id'], **data) + msg = (_('IPSec Site Connection %s was successfully updated.') + % context['name']) + LOG.debug(msg) + messages.success(request, msg) + return ipsecsiteconnection + except Exception as e: + msg = (_('Failed to update IPSec Site Connection %s') + % context['name']) + LOG.info('%(msg)s: %(exception)s', {'msg': msg, 'exception': e}) + redirect = reverse(self.failure_url) + exceptions.handle(request, msg, redirect=redirect) diff --git a/neutron_vpnaas_dashboard/dashboards/project/panel.py b/neutron_vpnaas_dashboard/dashboards/project/panel.py new file mode 100644 index 0000000..9ce2fc2 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/panel.py @@ -0,0 +1,47 @@ +# Copyright 2013, Mirantis Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from django.utils.translation import ugettext_lazy as _ + +import horizon + +from openstack_dashboard.api import neutron + +LOG = logging.getLogger(__name__) + + +class VPN(horizon.Panel): + name = _("VPN") + slug = 'vpn' + permissions = ('openstack.services.network',) + + def allowed(self, context): + request = context['request'] + if not request.user.has_perms(self.permissions): + return False + try: + if not neutron.is_service_enabled(request, + config_name='enable_vpn', + ext_name='vpnaas'): + return False + except Exception: + LOG.error("Call to list enabled services failed. This is likely " + "due to a problem communicating with the Neutron " + "endpoint. VPN panel will not be displayed.") + return False + if not super(VPN, self).allowed(context): + return False + return True diff --git a/neutron_vpnaas_dashboard/dashboards/project/tables.py b/neutron_vpnaas_dashboard/dashboards/project/tables.py new file mode 100644 index 0000000..21dceed --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/tables.py @@ -0,0 +1,465 @@ +# Copyright 2013, Mirantis Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +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 policy + +from neutron_vpnaas_dashboard import api + + +forbid_updates = set(["PENDING_CREATE", "PENDING_UPDATE", "PENDING_DELETE"]) + + +class AddIKEPolicyLink(tables.LinkAction): + name = "addikepolicy" + verbose_name = _("Add IKE Policy") + url = "horizon:project:vpn:addikepolicy" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("network", "create_ikepolicy"),) + + +class AddIPSecPolicyLink(tables.LinkAction): + name = "addipsecpolicy" + verbose_name = _("Add IPSec Policy") + url = "horizon:project:vpn:addipsecpolicy" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("network", "create_ipsecpolicy"),) + + +class AddVPNServiceLink(tables.LinkAction): + name = "addvpnservice" + verbose_name = _("Add VPN Service") + url = "horizon:project:vpn:addvpnservice" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("network", "create_vpnservice"),) + + +class AddIPSecSiteConnectionLink(tables.LinkAction): + name = "addipsecsiteconnection" + verbose_name = _("Add IPSec Site Connection") + url = "horizon:project:vpn:addipsecsiteconnection" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("network", "create_ipsec_site_connection"),) + + +class DeleteVPNServiceLink(policy.PolicyTargetMixin, tables.DeleteAction): + name = "deletevpnservice" + policy_rules = (("network", "delete_vpnservice"),) + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete VPN Service", + u"Delete VPN Services", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Scheduled deletion of VPN Service", + u"Scheduled deletion of VPN Services", + count + ) + + def allowed(self, request, datum=None): + if datum and datum.ipsecsiteconns: + return False + return True + + def delete(self, request, obj_id): + try: + api.vpn.vpnservice_delete(request, obj_id) + except Exception as e: + exceptions.handle( + request, _('Unable to delete VPN Service. %s') % e) + + +class DeleteIKEPolicyLink(policy.PolicyTargetMixin, tables.DeleteAction): + name = "deleteikepolicy" + policy_rules = (("network", "delete_ikepolicy"),) + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete IKE Policy", + u"Delete IKE Policies", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Scheduled deletion of IKE Policy", + u"Scheduled deletion of IKE Policies", + count + ) + + def allowed(self, request, datum=None): + if datum and datum.ipsecsiteconns: + return False + return True + + def delete(self, request, obj_id): + try: + api.vpn.ikepolicy_delete(request, obj_id) + except Exception as e: + exceptions.handle( + request, _('Unable to delete IKE Policy. %s') % e) + + +class DeleteIPSecPolicyLink(policy.PolicyTargetMixin, tables.DeleteAction): + name = "deleteipsecpolicy" + policy_rules = (("network", "delete_ipsecpolicy"),) + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete IPSec Policy", + u"Delete IPSec Policies", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Scheduled deletion of IPSec Policy", + u"Scheduled deletion of IPSec Policies", + count + ) + + def allowed(self, request, datum=None): + if datum and datum.ipsecsiteconns: + return False + return True + + def delete(self, request, obj_id): + try: + api.vpn.ipsecpolicy_delete(request, obj_id) + except Exception as e: + exceptions.handle( + request, _('Unable to delete IPSec Policy. %s') % e) + + +class DeleteIPSecSiteConnectionLink(policy.PolicyTargetMixin, + tables.DeleteAction): + name = "deleteipsecsiteconnection" + policy_rules = (("network", "delete_ipsec_site_connection"),) + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete IPSec Site Connection", + u"Delete IPSec Site Connections", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Scheduled deletion of IPSec Site Connection", + u"Scheduled deletion of IPSec Site Connections", + count + ) + + def delete(self, request, obj_id): + try: + api.vpn.ipsecsiteconnection_delete(request, obj_id) + except Exception as e: + exceptions.handle( + request, _('Unable to delete IPSec Site Connection. %s') % e) + + +class UpdateVPNServiceLink(tables.LinkAction): + name = "update_vpnservice" + verbose_name = _("Edit VPN Service") + classes = ("ajax-modal", "btn-update",) + policy_rules = (("network", "update_vpnservice"),) + + def get_link_url(self, vpnservice): + return reverse("horizon:project:vpn:update_vpnservice", + kwargs={'vpnservice_id': vpnservice.id}) + + def allowed(self, request, datum=None): + if datum and datum.status not in forbid_updates: + return True + return False + + +class UpdateIKEPolicyLink(tables.LinkAction): + name = "updateikepolicy" + verbose_name = _("Edit IKE Policy") + classes = ("ajax-modal", "btn-update",) + policy_rules = (("network", "update_ikepolicy"),) + + def get_link_url(self, ikepolicy): + return reverse("horizon:project:vpn:update_ikepolicy", + kwargs={'ikepolicy_id': ikepolicy.id}) + + def allowed(self, request, datum=None): + return not datum['ipsecsiteconns'] + + +class UpdateIPSecPolicyLink(tables.LinkAction): + name = "updateipsecpolicy" + verbose_name = _("Edit IPSec Policy") + classes = ("ajax-modal", "btn-update",) + policy_rules = (("network", "update_ipsecpolicy"),) + + def get_link_url(self, ipsecpolicy): + return reverse("horizon:project:vpn:update_ipsecpolicy", + kwargs={'ipsecpolicy_id': ipsecpolicy.id}) + + def allowed(self, request, datum=None): + return not datum['ipsecsiteconns'] + + +class UpdateIPSecSiteConnectionLink(tables.LinkAction): + name = "updateipsecsiteconnection" + verbose_name = _("Edit Connection") + classes = ("ajax-modal", "btn-update",) + policy_rules = (("network", "update_ipsec_site_connection"),) + + def get_link_url(self, ipsecsiteconnection): + return reverse("horizon:project:vpn:update_ipsecsiteconnection", + kwargs={'ipsecsiteconnection_id': + ipsecsiteconnection.id}) + + def allowed(self, request, datum=None): + if datum and datum.status not in forbid_updates: + return True + return False + + +STATUS_CHOICES = ( + ("active", True), + ("down", True), + ("created", True), + ("error", False), + ("inactive", False), +) + + +STATUS_DISPLAY_CHOICES = ( + ("active", pgettext_lazy("Current status of an IPSec Site Connection" + " and VPN Service", u"Active")), + ("down", pgettext_lazy("Current status of an IPSec Site Connection" + " and VPN Service", u"Down")), + ("error", pgettext_lazy("Current status of an IPSec Site Connection" + " and VPN Service", u"Error")), + ("created", pgettext_lazy("Current status of an IPSec Site Connection" + " and VPN Service", u"Created")), + ("pending_create", pgettext_lazy("Current status of an" + " IPSec Site Connection and VPN Service", + u"Pending Create")), + ("pending_update", pgettext_lazy("Current status of an" + " IPSec Site Connection and VPN Service", + u"Pending Update")), + ("pending_delete", pgettext_lazy("Current status of an" + " IPSec Site Connection and VPN Service", + u"Pending Delete")), + ("inactive", pgettext_lazy("Current status of an IPSec Site Connection" + " and VPN Service", u"Inactive")), +) + + +class UpdateIPSecSiteConnectionRow(tables.Row): + ajax = True + + def get_data(self, request, conn_id): + conn = api.vpn.ipsecsiteconnection_get(request, conn_id) + conn.ikepolicy_name = conn['ikepolicy'].get('name', + conn['ikepolicy_id']) + conn.ipsecpolicy_name = conn['ipsecpolicy'].get('name', + conn['ipsecpolicy_id']) + conn.vpnservice_name = conn['vpnservice'].get('name', + conn['vpnservice_id']) + return conn + + +class IPSSCFilterAction(tables.FilterAction): + name = 'IPSSC_project_IKEPolicies' + filter_type = 'server' + filter_choices = ( + ('name', _("Name ="), True), + ('vpnservice', _("VPN Service ="), True), + ('vpnservice_id', _("VPN Service ID ="), True), + ('ikepolicy', _("IKE Policy ="), True), + ('ikepolicy_id', _("IKE Policy ID ="), True), + ('ipsecpolicy', _("IPSec Policy ="), True), + ('ipsecpolicy_id', _("IPSec Policy ID ="), True), + ('status', _("Status ="), True) + ) + + +class IPSecSiteConnectionsTable(tables.DataTable): + id = tables.Column('id', hidden=True) + name = tables.Column('name_or_id', verbose_name=_('Name'), + link="horizon:project:vpn:ipsecsiteconnectiondetails") + description = tables.Column('description', verbose_name=_('Description')) + vpnservice_name = tables.Column('vpnservice_name', + verbose_name=_('VPN Service')) + ikepolicy_name = tables.Column('ikepolicy_name', + verbose_name=_('IKE Policy')) + ipsecpolicy_name = tables.Column('ipsecpolicy_name', + verbose_name=_('IPSec Policy')) + status = tables.Column("status", + verbose_name=_("Status"), + status=True, + status_choices=STATUS_CHOICES, + display_choices=STATUS_DISPLAY_CHOICES) + + def get_object_display(self, ipsecsiteconnection): + return ipsecsiteconnection.name_or_id + + class Meta(object): + name = "ipsecsiteconnectionstable" + verbose_name = _("IPSec Site Connections") + status_columns = ['status'] + row_class = UpdateIPSecSiteConnectionRow + table_actions = (AddIPSecSiteConnectionLink, + DeleteIPSecSiteConnectionLink, + IPSSCFilterAction) + row_actions = (UpdateIPSecSiteConnectionLink, + DeleteIPSecSiteConnectionLink) + + +def get_local_ips(vpn): + template_name = 'project/vpn/_vpn_ips.html' + context = {"external_v4_ip": vpn.get('external_v4_ip'), + "external_v6_ip": vpn.get('external_v6_ip')} + return template.loader.render_to_string(template_name, context) + + +class UpdateVPNServiceRow(tables.Row): + ajax = True + + def get_data(self, request, vpn_id): + vpn = api.vpn.vpnservice_get(request, vpn_id) + vpn.router_name = vpn['router'].get('name', vpn['router_id']) + vpn.subnet_name = vpn['subnet'].get('cidr', vpn['subnet_id']) + return vpn + + +class VPNServicesFilterAction(tables.FilterAction): + name = 'vpnservices_project_IKEPolicies' + filter_type = 'server' + filter_choices = ( + ('name', _("Name ="), True), + ('subnet_id', _("Subnet ID ="), True), + ('subnet_name', _("Subnet ="), True), + ('router_id', _("Router ID ="), True), + ('router_name', _("Router ="), True), + ) + + +class VPNServicesTable(tables.DataTable): + id = tables.Column('id', hidden=True) + name = tables.Column("name_or_id", verbose_name=_('Name'), + link="horizon:project:vpn:vpnservicedetails") + description = tables.Column('description', verbose_name=_('Description')) + local_ips = tables.Column(get_local_ips, + verbose_name=_("Local Side Public IPs")) + subnet_name = tables.Column('subnet_name', verbose_name=_('Subnet')) + router_name = tables.Column('router_name', verbose_name=_('Router')) + status = tables.Column("status", + verbose_name=_("Status"), + status=True, + status_choices=STATUS_CHOICES, + display_choices=STATUS_DISPLAY_CHOICES) + + def get_object_display(self, vpnservice): + return vpnservice.name_or_id + + class Meta(object): + name = "vpnservicestable" + verbose_name = _("VPN Services") + status_columns = ['status'] + row_class = UpdateVPNServiceRow + table_actions = (AddVPNServiceLink, + DeleteVPNServiceLink, + VPNServicesFilterAction) + row_actions = (UpdateVPNServiceLink, DeleteVPNServiceLink) + + +class PoliciesFilterAction(tables.FilterAction): + name = 'filter_project_IKEPolicies' + filter_type = 'server' + filter_choices = ( + ('name', _("Name ="), True), + ('auth_algorithm', _("Authorization algorithm ="), True), + ('encryption_algorithm', _("Encryption algorithm ="), True), + ('pfs', _("PFS ="), True), + ) + + +class IKEPoliciesTable(tables.DataTable): + id = tables.Column('id', hidden=True) + name = tables.Column("name_or_id", verbose_name=_('Name'), + link="horizon:project:vpn:ikepolicydetails") + description = tables.Column('description', verbose_name=_('Description')) + auth_algorithm = tables.Column('auth_algorithm', + verbose_name=_('Authorization algorithm')) + encryption_algorithm = tables.Column( + 'encryption_algorithm', + verbose_name=_('Encryption algorithm')) + pfs = tables.Column("pfs", verbose_name=_('PFS')) + + def get_object_display(self, ikepolicy): + return ikepolicy.name_or_id + + class Meta(object): + name = "ikepoliciestable" + verbose_name = _("IKE Policies") + table_actions = (AddIKEPolicyLink, + DeleteIKEPolicyLink, + PoliciesFilterAction) + row_actions = (UpdateIKEPolicyLink, DeleteIKEPolicyLink) + + +class IPSecPoliciesTable(tables.DataTable): + id = tables.Column('id', hidden=True) + name = tables.Column("name_or_id", verbose_name=_('Name'), + link="horizon:project:vpn:ipsecpolicydetails") + description = tables.Column('description', verbose_name=_('Description')) + auth_algorithm = tables.Column('auth_algorithm', + verbose_name=_('Authorization algorithm')) + encryption_algorithm = tables.Column( + 'encryption_algorithm', + verbose_name=_('Encryption algorithm')) + pfs = tables.Column("pfs", verbose_name=_('PFS')) + + def get_object_display(self, ipsecpolicy): + return ipsecpolicy.name_or_id + + class Meta(object): + name = "ipsecpoliciestable" + verbose_name = _("IPSec Policies") + table_actions = (AddIPSecPolicyLink, + DeleteIPSecPolicyLink, + PoliciesFilterAction) + row_actions = (UpdateIPSecPolicyLink, DeleteIPSecPolicyLink) diff --git a/neutron_vpnaas_dashboard/dashboards/project/tabs.py b/neutron_vpnaas_dashboard/dashboards/project/tabs.py new file mode 100644 index 0000000..5a13745 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/tabs.py @@ -0,0 +1,233 @@ +# Copyright 2013, Mirantis Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tables as htables +from horizon import tabs + +from openstack_dashboard import api + +from neutron_vpnaas_dashboard import api as api_vpn +from neutron_vpnaas_dashboard.dashboards.project import tables + + +class IPSecSiteConnectionsTab(tabs.TableTab, htables.DataTableView): + table_classes = (tables.IPSecSiteConnectionsTable,) + name = _("IPSec Site Connections") + slug = "ipsecsiteconnections" + template_name = ("horizon/common/_detail_table.html") + FILTERS_MAPPING = {'admin_state_up': {_("up"): True, _("down"): False}} + + def get_ipsecsiteconnectionstable_data(self): + try: + filters = self.get_filters() + tenant_id = self.request.user.tenant_id + if 'vpnservice' in filters: + filters['vpnservice_id'] = \ + [v.id for v in api_vpn.vpnservice_list( + self.tab_group.request, tenant_id=tenant_id, + name=filters['vpnservice'])] + del filters['vpnservice'] + if 'ikepolicy' in filters: + filters['ikepolicy_id'] = \ + [i.id for i in api_vpn.ikepolicy_list( + self.tab_group.request, tenant_id=tenant_id, + name=filters['ikepolicy'])] + del filters['ikepolicy'] + if 'ipsecpolicy' in filters: + filters['ipsecpolicy_id'] = \ + [i.id for i in api_vpn.ipsecpolicy_list( + self.tab_group.request, tenant_id=tenant_id, + name=filters['ipsecpolicy'])] + del filters['ipsecpolicy'] + ipsecsiteconnections = api_vpn.ipsecsiteconnection_list( + self.tab_group.request, tenant_id=tenant_id, **filters) + except Exception: + ipsecsiteconnections = [] + exceptions.handle( + self.tab_group.request, + _('Unable to retrieve IPSec Site Connections list.')) + return ipsecsiteconnections + + def get_filters(self): + self.table = self._tables['ipsecsiteconnectionstable'] + self.handle_server_filter(self.request, table=self.table) + self.update_server_filter_action(self.request, table=self.table) + + return super(IPSecSiteConnectionsTab, + self).get_filters(filters_map=self.FILTERS_MAPPING) + + +class VPNServicesTab(tabs.TableTab, htables.DataTableView): + table_classes = (tables.VPNServicesTable,) + name = _("VPN Services") + slug = "vpnservices" + template_name = ("horizon/common/_detail_table.html") + + def get_vpnservicestable_data(self): + try: + filters = self.get_filters() + tenant_id = self.request.user.tenant_id + if 'subnet_name' in filters: + subnets = api.neutron.subnet_list(self.tab_group.request, + tenant_id=tenant_id, + cidr=filters['subnet_name']) + subnets_ids = [n.id for n in subnets] + del filters['subnet_name'] + if not subnets_ids: + return [] + filters['subnet_id'] = subnets_ids + if 'router_name' in filters: + routers = api.neutron.router_list(self.tab_group.request, + tenant_id=tenant_id, + name=filters['router_name']) + routers_ids = [r.id for r in routers] + if not routers: + return [] + filters['router_id'] = routers_ids + vpnservices = api_vpn.vpnservice_list( + self.tab_group.request, tenant_id=tenant_id, **filters) + except Exception: + vpnservices = [] + exceptions.handle(self.tab_group.request, + _('Unable to retrieve VPN Services list.')) + return vpnservices + + def get_filters(self): + self.table = self._tables['vpnservicestable'] + self.handle_server_filter(self.request, table=self.table) + self.update_server_filter_action(self.request, table=self.table) + + return super(VPNServicesTab, self).get_filters() + + +class IKEPoliciesTab(tabs.TableTab, htables.DataTableView): + table_classes = (tables.IKEPoliciesTable,) + name = _("IKE Policies") + slug = "ikepolicies" + template_name = ("horizon/common/_detail_table.html") + + def get_ikepoliciestable_data(self): + try: + filters = self.get_filters() + tenant_id = self.request.user.tenant_id + ikepolicies = api_vpn.ikepolicy_list( + self.tab_group.request, tenant_id=tenant_id, **filters) + except Exception: + ikepolicies = [] + exceptions.handle(self.tab_group.request, + _('Unable to retrieve IKE Policies list.')) + return ikepolicies + + def get_filters(self): + self.table = self._tables['ikepoliciestable'] + self.handle_server_filter(self.request, table=self.table) + self.update_server_filter_action(self.request, table=self.table) + + return super(IKEPoliciesTab, self).get_filters() + + +class IPSecPoliciesTab(tabs.TableTab, htables.DataTableView): + table_classes = (tables.IPSecPoliciesTable,) + name = _("IPSec Policies") + slug = "ipsecpolicies" + template_name = ("horizon/common/_detail_table.html") + + def get_ipsecpoliciestable_data(self): + try: + filters = self.get_filters() + tenant_id = self.request.user.tenant_id + ipsecpolicies = api_vpn.ipsecpolicy_list( + self.tab_group.request, tenant_id=tenant_id, **filters) + except Exception: + ipsecpolicies = [] + exceptions.handle(self.tab_group.request, + _('Unable to retrieve IPSec Policies list.')) + return ipsecpolicies + + def get_filters(self): + self.table = self._tables['ipsecpoliciestable'] + self.handle_server_filter(self.request, table=self.table) + self.update_server_filter_action(self.request, table=self.table) + + return super(IPSecPoliciesTab, self).get_filters() + + +class VPNTabs(tabs.TabGroup): + slug = "vpntabs" + tabs = (IKEPoliciesTab, IPSecPoliciesTab, + VPNServicesTab, IPSecSiteConnectionsTab,) + sticky = True + + +class IKEPolicyDetailsTab(tabs.Tab): + name = _("IKE Policy Details") + slug = "ikepolicydetails" + template_name = "project/vpn/_ikepolicy_details.html" + + def get_context_data(self, request): + ikepolicy = self.tab_group.kwargs['ikepolicy'] + return {'ikepolicy': ikepolicy} + + +class IKEPolicyDetailsTabs(tabs.TabGroup): + slug = "ikepolicytabs" + tabs = (IKEPolicyDetailsTab,) + + +class IPSecPolicyDetailsTab(tabs.Tab): + name = _("IPSec Policy Details") + slug = "ipsecpolicydetails" + template_name = "project/vpn/_ipsecpolicy_details.html" + + def get_context_data(self, request): + ipsecpolicy = self.tab_group.kwargs['ipsecpolicy'] + return {'ipsecpolicy': ipsecpolicy} + + +class IPSecPolicyDetailsTabs(tabs.TabGroup): + slug = "ipsecpolicytabs" + tabs = (IPSecPolicyDetailsTab,) + + +class VPNServiceDetailsTab(tabs.Tab): + name = _("VPN Service Details") + slug = "vpnservicedetails" + template_name = "project/vpn/_vpnservice_details.html" + + def get_context_data(self, request): + vpnservice = self.tab_group.kwargs['vpnservice'] + return {'vpnservice': vpnservice} + + +class VPNServiceDetailsTabs(tabs.TabGroup): + slug = "vpnservicetabs" + tabs = (VPNServiceDetailsTab,) + + +class IPSecSiteConnectionDetailsTab(tabs.Tab): + name = _("IPSec Site Connection Details") + slug = "ipsecsiteconnectiondetails" + template_name = "project/vpn/_ipsecsiteconnection_details.html" + + def get_context_data(self, request): + ipsecsiteconnection = self.tab_group.kwargs['ipsecsiteconnection'] + return {'ipsecsiteconnection': ipsecsiteconnection} + + +class IPSecSiteConnectionDetailsTabs(tabs.TabGroup): + slug = "ipsecsiteconnectiontabs" + tabs = (IPSecSiteConnectionDetailsTab,) diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_ike_policy_help.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_ike_policy_help.html new file mode 100644 index 0000000..bda75fc --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_ike_policy_help.html @@ -0,0 +1,20 @@ +{% load i18n %} + +

{% trans "Create IKE Policy for current project." %}

+

{% trans "An IKE policy is an association of the following attributes:" %}

+
+
{% trans 'Authorization algorithm' %}
+
{% trans 'Auth algorithm limited to SHA1 only.' %}
+
{% trans 'Encryption algorithm' %}
+
{% trans 'The type of algorithm (3des, aes-128, aes-192, aes-256) used in the IKE Policy.' %}
+
{% trans 'IKE version' %}
+
{% trans 'The type of version (v1/v2) that needs to be filtered.' %}
+
{% trans 'Lifetime' %}
+
{% trans "Life time consists of units and value. Units in 'seconds' and the default value is 3600." %}
+
{% trans 'Perfect Forward Secrecy' %}
+
{% trans 'PFS limited to using Diffie-Hellman groups 2, 5 (default) and 14.' %}
+
{% trans 'IKE Phase 1 negotiation mode' %}
+
{% trans "Limited to 'main' mode only." %}
+
+ +

{% trans "All fields are optional." %}

diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_ipsec_policy_help.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_ipsec_policy_help.html new file mode 100644 index 0000000..270855b --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_ipsec_policy_help.html @@ -0,0 +1,19 @@ +{% load i18n %} + +

{% trans 'Create IPSec Policy for current project.' %}

+

{% trans 'An IPSec policy is an association of the following attributes' %}

+
+
{% trans 'Authorization algorithm' %}
+
{% trans 'Auth algorithm limited to SHA1 only.' %}
+
{% trans 'Encapsulation mode' %}
+
{% trans 'The type of IPsec tunnel (tunnel/transport) to be used.' %}
+
{% trans 'Encryption algorithm' %}
+
{% trans 'The type of algorithm (3des, aes-128, aes-192, aes-256) used in the IPSec Policy.' %}
+
{% trans 'Lifetime' %}
+
{% trans "Life time consists of units and value. Units in 'seconds' and the default value is 3600." %}
+
{% trans 'Perfect Forward Secrecy' %}
+
{% trans 'PFS limited to using Diffie-Hellman groups 2, 5 (default) and 14.' %}
+
{% trans 'Transform Protocol' %}
+
{% trans 'The type of protocol (esp, ah, ah-esp) used in IPSec Policy.' %}
+
+

{% trans 'All fields are optional.' %}

diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_vpn_service_help.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_vpn_service_help.html new file mode 100644 index 0000000..4d682dc --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_add_vpn_service_help.html @@ -0,0 +1,7 @@ +{% load i18n %} + +

{% trans "Create VPN service for current project." %}

+

{% trans "The VPN service is attached to a router and references to a single subnet to push to a remote site." %}

+

{% trans "Specify a name, description, router, and subnet for the VPN Service." %}

+

{% trans "Admin State is enabled by default." %}

+

{% trans "The router, subnet and admin state fields require to be enabled. All others are optional." %}

diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ikepolicy_details.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ikepolicy_details.html new file mode 100644 index 0000000..6e925ac --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ikepolicy_details.html @@ -0,0 +1,38 @@ +{% load i18n sizeformat parse_date %} + +
+
+
{% trans "Name" %}
+
{{ ikepolicy.name|default:_("None") }}
+ +
{% trans "Description" %}
+
{{ ikepolicy.description|default:_("None") }}
+ +
{% trans "ID" %}
+
{{ ikepolicy.id }}
+ +
{% trans "Project ID" %}
+
{{ ikepolicy.tenant_id }}
+ +
{% trans "Authorization algorithm" %}
+
{{ ikepolicy.auth_algorithm }}
+ +
{% trans "Encryption algorithm" %}
+
{{ ikepolicy.encryption_algorithm }}
+ +
{% trans "IKE version" %}
+
{{ ikepolicy.ike_version }}
+ +
{% trans "Lifetime Units" %}
+
{{ ikepolicy.lifetime.units }}
+ +
{% trans "Lifetime Value" %}
+
{{ ikepolicy.lifetime.value }}
+ +
{% trans "Perfect Forward Secrecy" %}
+
{{ ikepolicy.pfs }}
+ +
{% trans "IKE Phase1 negotiation mode" %}
+
{{ ikepolicy.phase1_negotiation_mode }}
+
+
diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ipsecpolicy_details.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ipsecpolicy_details.html new file mode 100644 index 0000000..66cb333 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ipsecpolicy_details.html @@ -0,0 +1,38 @@ +{% load i18n sizeformat parse_date %} + +
+
+
{% trans "Name" %}
+
{{ ipsecpolicy.name|default:_("None") }}
+ +
{% trans "Description" %}
+
{{ ipsecpolicy.description|default:_("None") }}
+ +
{% trans "ID" %}
+
{{ ipsecpolicy.id }}
+ +
{% trans "Project ID" %}
+
{{ ipsecpolicy.tenant_id }}
+ +
{% trans "Authorization algorithm" %}
+
{{ ipsecpolicy.auth_algorithm }}
+ +
{% trans "Encapsulation mode" %}
+
{{ ipsecpolicy.encapsulation_mode }}
+ +
{% trans "Encryption algorithm" %}
+
{{ ipsecpolicy.encryption_algorithm }}
+ +
{% trans "Lifetime Units" %}
+
{{ ipsecpolicy.lifetime.units }}
+ +
{% trans "Lifetime Value" %}
+
{{ ipsecpolicy.lifetime.value }}
+ +
{% trans "Perfect Forward Secrecy" %}
+
{{ ipsecpolicy.pfs }}
+ +
{% trans "Transform Protocol" %}
+
{{ ipsecpolicy.transform_protocol }}
+
+
diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ipsecsiteconnection_details.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ipsecsiteconnection_details.html new file mode 100644 index 0000000..307a30b --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_ipsecsiteconnection_details.html @@ -0,0 +1,69 @@ +{% load i18n sizeformat parse_date %} + +
+
+
{% trans "Name" %}
+
{{ ipsecsiteconnection.name|default:_("None") }}
+ +
{% trans "Description" %}
+
{{ ipsecsiteconnection.description|default:_("None") }}
+ +
{% trans "ID" %}
+
{{ ipsecsiteconnection.id }}
+ +
{% trans "Project ID" %}
+
{{ ipsecsiteconnection.tenant_id }}
+ +
{% trans "VPN Service" %}
+ {% url 'horizon:project:vpn:vpnservicedetails' ipsecsiteconnection.vpnservice_id as vpnservice_url %} +
{{ ipsecsiteconnection.vpnservice.name_or_id }}
+ +
{% trans "IKE Policy" %}
+ {% url 'horizon:project:vpn:ikepolicydetails' ipsecsiteconnection.ikepolicy_id as ikepolicy_url %} +
{{ ipsecsiteconnection.ikepolicy.name_or_id }}
+ +
{% trans "IPSec Policy" %}
+ {% url 'horizon:project:vpn:ipsecpolicydetails' ipsecsiteconnection.ipsecpolicy_id as ipsecpolicy_url %} +
{{ ipsecsiteconnection.ipsecpolicy.name_or_id }}
+ +
{% trans "Peer gateway public IPv4/IPv6 Address or FQDN" %}
+
{{ ipsecsiteconnection.peer_address }}
+ +
{% trans "Peer router identity for authentication (Peer ID)" %}
+
{{ ipsecsiteconnection.peer_id }}
+ +
{% trans "Remote peer subnet" %}
+
+ {% for peer_cidr in ipsecsiteconnection.peer_cidrs %} + {{ peer_cidr }}
+ {% endfor %} +
+ +
{% trans "MTU" %}
+
{{ ipsecsiteconnection.mtu }}
+ +
{% trans "Initiator state" %}
+
{{ ipsecsiteconnection.initiator }}
+ +
{% trans "Dead peer detection action" %}
+
{{ ipsecsiteconnection.dpd.action }}
+ +
{% trans "Dead peer detection interval" %}
+
{{ ipsecsiteconnection.dpd.interval }}
+ +
{% trans "Dead peer detection timeout" %}
+
{{ ipsecsiteconnection.dpd.timeout }}
+ +
{% trans "Authorization mode" %}
+
{{ ipsecsiteconnection.auth_mode }}
+ +
{% trans "Route mode" %}
+
{{ ipsecsiteconnection.route_mode }}
+ +
{% trans "Admin State" %}
+
{{ ipsecsiteconnection.admin_state_up }}
+ +
{% trans "Status" %}
+
{{ ipsecsiteconnection.status }}
+
+
diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ikepolicy.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ikepolicy.html new file mode 100644 index 0000000..14c3bf2 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ikepolicy.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "You may update IKE Policy details here." %}

+{% endblock %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ipsecpolicy.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ipsecpolicy.html new file mode 100644 index 0000000..848fe6a --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ipsecpolicy.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "You may update IPSec Policy details here." %}

+{% endblock %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ipsecsiteconnection.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ipsecsiteconnection.html new file mode 100644 index 0000000..025c9c5 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_ipsecsiteconnection.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "You may update IPSec Site Connection details here." %}

+{% endblock %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_vpnservice.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_vpnservice.html new file mode 100644 index 0000000..225ad46 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_update_vpnservice.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "You may update VPN Service details here." %}

+{% endblock %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_vpn_ips.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_vpn_ips.html new file mode 100644 index 0000000..a686b5f --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_vpn_ips.html @@ -0,0 +1,13 @@ +{% load i18n %} +{% if external_v4_ip or external_v6_ip %} + +{% else %} + {% trans "-" %} +{% endif %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_vpnservice_details.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_vpnservice_details.html new file mode 100644 index 0000000..4db1bb6 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/_vpnservice_details.html @@ -0,0 +1,54 @@ +{% load i18n sizeformat parse_date %} + +
+
+
{% trans "Name" %}
+
{{ vpnservice.name|default:_("None") }}
+ +
{% trans "Description" %}
+
{{ vpnservice.description|default:_("None") }}
+ +
{% trans "ID" %}
+
{{ vpnservice.id }}
+ +
{% trans "Project ID" %}
+
{{ vpnservice.tenant_id }}
+ +
{% trans "Router ID" %}
+ {% url 'horizon:project:routers:detail' vpnservice.router_id as router_url %} +
{{ vpnservice.router.name_or_id }}
+ +
{% trans "Subnet ID" %}
+ {% url 'horizon:project:networks:subnets:detail' vpnservice.subnet_id as subnet_url %} +
{{ vpnservice.subnet.name_or_id }} {{ vpnservice.subnet.cidr }}
+ +
{% trans "VPN Connections" %}
+
+ {% if vpnservice.ipsecsiteconns %} + {% for conn in vpnservice.ipsecsiteconns %} + {% url 'horizon:project:vpn:ipsecsiteconnectiondetails' conn.id as conn_url %} + {{ conn.name_or_id }}
+ {% endfor %} + {% else %} + None + {% endif %} +
+ +
{% trans "Admin State" %}
+
{{ vpnservice.admin_state_up }}
+ +
{% trans "Status" %}
+
{{ vpnservice.status }}
+
+ + {% if vpnservice.external_v4_ip or vpnservice.external_v6_ip %} +

Local Side Public IP Addresses

+
+
+
{% trans "IPv4" %}
+
{{ vpnservice.external_v4_ip|default:_("None") }}
+
{% trans "IPv6" %}
+
{{ vpnservice.external_v6_ip|default:_("None") }}
+
+ {% endif %} +
diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/index.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/index.html new file mode 100644 index 0000000..9e1bac4 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Virtual Private Network" %}{% endblock %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ikepolicy.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ikepolicy.html new file mode 100644 index 0000000..70fc9b4 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ikepolicy.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit IKE Policy" %}{% endblock %} + +{% block main %} + {% include 'project/vpn/_update_ikepolicy.html' %} +{% endblock %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ipsecpolicy.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ipsecpolicy.html new file mode 100644 index 0000000..5b5b5fd --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ipsecpolicy.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit IPSec Policy" %}{% endblock %} + +{% block main %} + {% include 'project/vpn/_update_ipsecpolicy.html' %} +{% endblock %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ipsecsiteconnection.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ipsecsiteconnection.html new file mode 100644 index 0000000..18de700 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_ipsecsiteconnection.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit IPSec Site Connection" %}{% endblock %} + +{% block main %} + {% include 'project/vpn/_update_ipsecsiteconnection.html' %} +{% endblock %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_vpnservice.html b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_vpnservice.html new file mode 100644 index 0000000..08b94b6 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/templates/vpn/update_vpnservice.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Edit VPN Service" %}{% endblock %} + +{% block main %} + {% include 'project/vpn/_update_vpnservice.html' %} +{% endblock %} diff --git a/neutron_vpnaas_dashboard/dashboards/project/tests.py b/neutron_vpnaas_dashboard/dashboards/project/tests.py new file mode 100644 index 0000000..617e91e --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/tests.py @@ -0,0 +1,813 @@ +# Copyright 2013, Mirantis Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from mox3.mox import IsA + +from django.core.urlresolvers import reverse +from django import http + +from horizon.workflows import views + +from openstack_dashboard import api +from openstack_dashboard.test import helpers as test + +from openstack_dashboard.dashboards.project.vpn import workflows + + +class VPNTests(test.TestCase): + class AttributeDict(dict): + def __getattr__(self, attr): + return self[attr] + + def __setattr__(self, attr, value): + self[attr] = value + + DASHBOARD = 'project' + INDEX_URL = reverse('horizon:%s:vpn:index' % DASHBOARD) + + ADDIKEPOLICY_PATH = 'horizon:%s:vpn:addikepolicy' % DASHBOARD + ADDIPSECPOLICY_PATH = 'horizon:%s:vpn:addipsecpolicy' % DASHBOARD + ADDVPNSERVICE_PATH = 'horizon:%s:vpn:addvpnservice' % DASHBOARD + ADDVPNCONNECTION_PATH = 'horizon:%s:vpn:addipsecsiteconnection' % DASHBOARD + + IKEPOLICY_DETAIL_PATH = 'horizon:%s:vpn:ikepolicydetails' % DASHBOARD + IPSECPOLICY_DETAIL_PATH = 'horizon:%s:vpn:ipsecpolicydetails' % DASHBOARD + VPNSERVICE_DETAIL_PATH = 'horizon:%s:vpn:vpnservicedetails' % DASHBOARD + VPNCONNECTION_DETAIL_PATH = 'horizon:%s:vpn:ipsecsiteconnectiondetails' %\ + DASHBOARD + + UPDATEIKEPOLICY_PATH = 'horizon:%s:vpn:update_ikepolicy' % DASHBOARD + UPDATEIPSECPOLICY_PATH = 'horizon:%s:vpn:update_ipsecpolicy' % DASHBOARD + UPDATEVPNSERVICE_PATH = 'horizon:%s:vpn:update_vpnservice' % DASHBOARD + UPDATEVPNCONNECTION_PATH = 'horizon:%s:vpn:update_ipsecsiteconnection' %\ + DASHBOARD + + def set_up_expect(self): + # retrieves vpnservices + api.vpn.vpnservice_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id) \ + .AndReturn(self.vpnservices.list()) + + # retrieves ikepolicies + api.vpn.ikepolicy_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id) \ + .AndReturn(self.ikepolicies.list()) + + # retrieves ipsecpolicies + api.vpn.ipsecpolicy_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id) \ + .AndReturn(self.ipsecpolicies.list()) + + # retrieves ipsecsiteconnections + api.vpn.ipsecsiteconnection_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id) \ + .AndReturn(self.ipsecsiteconnections.list()) + + def set_up_expect_with_exception(self): + api.vpn.vpnservice_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndRaise(self.exceptions.neutron) + api.vpn.ikepolicy_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndRaise(self.exceptions.neutron) + api.vpn.ipsecpolicy_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndRaise(self.exceptions.neutron) + api.vpn.ipsecsiteconnection_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndRaise(self.exceptions.neutron) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_list')}) + def test_index_vpnservices(self): + self.set_up_expect() + + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL) + + self.assertTemplateUsed(res, '%s/vpn/index.html' + % self.DASHBOARD) + self.assertTemplateUsed(res, 'horizon/common/_detail_table.html') + self.assertEqual(len(res.context['table'].data), + len(self.vpnservices.list())) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_list')}) + def test_index_ikepolicies(self): + self.set_up_expect() + + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL + '?tab=vpntabs__ikepolicies') + + self.assertTemplateUsed(res, '%s/vpn/index.html' + % self.DASHBOARD) + self.assertTemplateUsed(res, 'horizon/common/_detail_table.html') + self.assertEqual(len(res.context['ikepoliciestable_table'].data), + len(self.ikepolicies.list())) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_list')}) + def test_index_ipsecpolicies(self): + self.set_up_expect() + + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL + '?tab=vpntabs__ipsecpolicies') + + self.assertTemplateUsed(res, '%s/vpn/index.html' + % self.DASHBOARD) + self.assertTemplateUsed(res, 'horizon/common/_detail_table.html') + self.assertEqual(len(res.context['ipsecpoliciestable_table'].data), + len(self.ipsecpolicies.list())) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_list')}) + def test_index_ipsecsiteconnections(self): + self.set_up_expect() + + self.mox.ReplayAll() + + res = self.client.get( + self.INDEX_URL + '?tab=vpntabs__ipsecsiteconnections') + + self.assertTemplateUsed(res, '%s/vpn/index.html' + % self.DASHBOARD) + self.assertTemplateUsed(res, 'horizon/common/_detail_table.html') + self.assertEqual( + len(res.context['ipsecsiteconnectionstable_table'].data), + len(self.ipsecsiteconnections.list())) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_list')}) + def test_index_exception_vpnservices(self): + self.set_up_expect_with_exception() + + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL) + + self.assertTemplateUsed(res, '%s/vpn/index.html' + % self.DASHBOARD) + self.assertTemplateUsed(res, + 'horizon/common/_detail_table.html') + self.assertEqual(len(res.context['table'].data), 0) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_list')}) + def test_index_exception_ikepolicies(self): + self.set_up_expect_with_exception() + + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL + '?tab=vpntabs__ikepolicies') + + self.assertTemplateUsed(res, '%s/vpn/index.html' + % self.DASHBOARD) + self.assertTemplateUsed(res, + 'horizon/common/_detail_table.html') + self.assertEqual(len(res.context['table'].data), 0) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_list')}) + def test_index_exception_ipsecpolicies(self): + self.set_up_expect_with_exception() + + self.mox.ReplayAll() + + res = self.client.get(self.INDEX_URL + '?tab=vpntabs__ipsecpolicies') + + self.assertTemplateUsed(res, '%s/vpn/index.html' + % self.DASHBOARD) + self.assertTemplateUsed(res, + 'horizon/common/_detail_table.html') + self.assertEqual(len(res.context['table'].data), 0) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_list')}) + def test_index_exception_ipsecsiteconnections(self): + self.set_up_expect_with_exception() + + self.mox.ReplayAll() + + res = self.client.get( + self.INDEX_URL + '?tab=vpntabs__ipsecsiteconnections') + + self.assertTemplateUsed(res, '%s/vpn/index.html' + % self.DASHBOARD) + self.assertTemplateUsed(res, + 'horizon/common/_detail_table.html') + self.assertEqual(len(res.context['table'].data), 0) + + @test.create_stubs({api.neutron: ('network_list_for_tenant', + 'router_list')}) + def test_add_vpnservice_get(self): + networks = [{'subnets': [self.subnets.first(), ]}, ] + routers = self.routers.list() + + api.neutron.network_list_for_tenant( + IsA(http.HttpRequest), self.tenant.id).AndReturn(networks) + api.neutron.router_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id).AndReturn(routers) + + self.mox.ReplayAll() + + res = self.client.get(reverse(self.ADDVPNSERVICE_PATH)) + + workflow = res.context['workflow'] + self.assertTemplateUsed(res, views.WorkflowView.template_name) + self.assertEqual(workflow.name, workflows.AddVPNService.name) + + expected_objs = ['', ] + self.assertQuerysetEqual(workflow.steps, expected_objs) + + @test.create_stubs({api.neutron: ('router_list', + 'network_list_for_tenant'), + api.vpn: ('vpnservice_create', )}) + def test_add_vpnservice_post(self): + vpnservice = self.vpnservices.first() + networks = [{'subnets': [self.subnets.first(), ]}, ] + routers = self.routers.list() + + api.neutron.network_list_for_tenant( + IsA(http.HttpRequest), self.tenant.id).AndReturn(networks) + api.neutron.router_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id).AndReturn(routers) + + form_data = {'name': vpnservice['name'], + 'description': vpnservice['description'], + 'subnet_id': vpnservice['subnet_id'], + 'router_id': vpnservice['router_id'], + 'admin_state_up': vpnservice['admin_state_up']} + + api.vpn.vpnservice_create( + IsA(http.HttpRequest), **form_data).AndReturn(vpnservice) + + self.mox.ReplayAll() + + res = self.client.post(reverse(self.ADDVPNSERVICE_PATH), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + @test.create_stubs({api.neutron: ('router_list', + 'network_list_for_tenant')}) + def test_add_vpnservice_post_error(self): + vpnservice = self.vpnservices.first() + networks = [{'subnets': [self.subnets.first(), ]}, ] + routers = self.routers.list() + + api.neutron.network_list_for_tenant( + IsA(http.HttpRequest), self.tenant.id).AndReturn(networks) + api.neutron.router_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id).AndReturn(routers) + + self.mox.ReplayAll() + + form_data = {'name': vpnservice['name'], + 'description': vpnservice['description'], + 'subnet_id': '', + 'router_id': '', + 'admin_state_up': vpnservice['admin_state_up']} + + res = self.client.post(reverse(self.ADDVPNSERVICE_PATH), form_data) + + self.assertFormErrors(res, 2) + + def test_add_ikepolicy_get(self): + res = self.client.get(reverse(self.ADDIKEPOLICY_PATH)) + + workflow = res.context['workflow'] + self.assertTemplateUsed(res, views.WorkflowView.template_name) + self.assertEqual(workflow.name, workflows.AddIKEPolicy.name) + + expected_objs = ['', ] + self.assertQuerysetEqual(workflow.steps, expected_objs) + + @test.create_stubs({api.vpn: ('ikepolicy_create', )}) + def test_add_ikepolicy_post(self): + ikepolicy = self.ikepolicies.first() + + form_data = {'name': ikepolicy['name'], + 'description': ikepolicy['description'], + 'auth_algorithm': ikepolicy['auth_algorithm'], + 'encryption_algorithm': ikepolicy[ + 'encryption_algorithm'], + 'ike_version': ikepolicy['ike_version'], + 'lifetime_units': ikepolicy['lifetime']['units'], + 'lifetime_value': ikepolicy['lifetime']['value'], + 'phase1_negotiation_mode': ikepolicy[ + 'phase1_negotiation_mode'], + 'pfs': ikepolicy['pfs']} + + api.vpn.ikepolicy_create( + IsA(http.HttpRequest), **form_data).AndReturn(ikepolicy) + + self.mox.ReplayAll() + + res = self.client.post(reverse(self.ADDIKEPOLICY_PATH), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + def test_add_ikepolicy_post_error(self): + ikepolicy = self.ikepolicies.first() + + form_data = {'name': ikepolicy['name'], + 'description': ikepolicy['description'], + 'auth_algorithm': ikepolicy['auth_algorithm'], + 'encryption_algorithm': ikepolicy[ + 'encryption_algorithm'], + 'ike_version': ikepolicy['ike_version'], + 'lifetime_units': ikepolicy['lifetime']['units'], + 'lifetime_value': 10, + 'phase1_negotiation_mode': ikepolicy[ + 'phase1_negotiation_mode'], + 'pfs': ikepolicy['pfs']} + + res = self.client.post(reverse(self.ADDIKEPOLICY_PATH), form_data) + + self.assertFormErrors(res, 1) + + def test_add_ipsecpolicy_get(self): + res = self.client.get(reverse(self.ADDIPSECPOLICY_PATH)) + + workflow = res.context['workflow'] + self.assertTemplateUsed(res, views.WorkflowView.template_name) + self.assertEqual(workflow.name, workflows.AddIPSecPolicy.name) + + expected_objs = ['', ] + self.assertQuerysetEqual(workflow.steps, expected_objs) + + @test.create_stubs({api.vpn: ('ipsecpolicy_create', )}) + def test_add_ipsecpolicy_post(self): + ipsecpolicy = self.ipsecpolicies.first() + + form_data = {'name': ipsecpolicy['name'], + 'description': ipsecpolicy['description'], + 'auth_algorithm': ipsecpolicy['auth_algorithm'], + 'encryption_algorithm': ipsecpolicy[ + 'encryption_algorithm'], + 'encapsulation_mode': ipsecpolicy[ + 'encapsulation_mode'], + 'lifetime_units': ipsecpolicy['lifetime']['units'], + 'lifetime_value': ipsecpolicy['lifetime']['value'], + 'pfs': ipsecpolicy['pfs'], + 'transform_protocol': ipsecpolicy[ + 'transform_protocol']} + + api.vpn.ipsecpolicy_create( + IsA(http.HttpRequest), **form_data).AndReturn(ipsecpolicy) + + self.mox.ReplayAll() + + res = self.client.post(reverse(self.ADDIPSECPOLICY_PATH), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + def test_add_ipsecpolicy_post_error(self): + ipsecpolicy = self.ipsecpolicies.first() + + form_data = {'name': ipsecpolicy['name'], + 'description': ipsecpolicy['description'], + 'auth_algorithm': ipsecpolicy['auth_algorithm'], + 'encryption_algorithm': ipsecpolicy[ + 'encryption_algorithm'], + 'encapsulation_mode': ipsecpolicy[ + 'encapsulation_mode'], + 'lifetime_units': ipsecpolicy['lifetime']['units'], + 'lifetime_value': 10, + 'pfs': ipsecpolicy['pfs'], + 'transform_protocol': ipsecpolicy[ + 'transform_protocol']} + + res = self.client.post(reverse(self.ADDIPSECPOLICY_PATH), form_data) + + self.assertFormErrors(res, 1) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list')}) + def test_add_ipsecsiteconnection_get(self): + ikepolicies = self.ikepolicies.list() + ipsecpolicies = self.ipsecpolicies.list() + vpnservices = self.vpnservices.list() + + api.vpn.ikepolicy_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndReturn(ikepolicies) + api.vpn.ipsecpolicy_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndReturn(ipsecpolicies) + api.vpn.vpnservice_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndReturn(vpnservices) + + self.mox.ReplayAll() + + res = self.client.get(reverse(self.ADDVPNCONNECTION_PATH)) + + workflow = res.context['workflow'] + self.assertTemplateUsed(res, views.WorkflowView.template_name) + self.assertEqual(workflow.name, workflows.AddIPSecSiteConnection.name) + + expected_objs = ['', + '', ] + self.assertQuerysetEqual(workflow.steps, expected_objs) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_create')}) + def test_add_ipsecsiteconnection_post(self): + self._test_add_ipsecsiteconnection_post() + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_create')}) + def test_add_ipsecsiteconnection_post_single_subnet(self): + self._test_add_ipsecsiteconnection_post(subnet_list=False) + + def _test_add_ipsecsiteconnection_post(self, subnet_list=True): + if subnet_list: + ipsecsiteconnection = self.ipsecsiteconnections.first() + else: + ipsecsiteconnection = self.ipsecsiteconnections.list()[1] + ikepolicies = self.ikepolicies.list() + ipsecpolicies = self.ipsecpolicies.list() + vpnservices = self.vpnservices.list() + + api.vpn.ikepolicy_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndReturn(ikepolicies) + api.vpn.ipsecpolicy_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndReturn(ipsecpolicies) + api.vpn.vpnservice_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndReturn(vpnservices) + + form_data = {'name': ipsecsiteconnection['name'], + 'description': ipsecsiteconnection['description'], + 'dpd_action': ipsecsiteconnection['dpd']['action'], + 'dpd_interval': ipsecsiteconnection['dpd']['interval'], + 'dpd_timeout': ipsecsiteconnection['dpd']['timeout'], + 'ikepolicy_id': ipsecsiteconnection['ikepolicy_id'], + 'initiator': ipsecsiteconnection['initiator'], + 'ipsecpolicy_id': ipsecsiteconnection[ + 'ipsecpolicy_id'], + 'mtu': ipsecsiteconnection['mtu'], + 'peer_address': ipsecsiteconnection['peer_address'], + 'peer_cidrs': ipsecsiteconnection['peer_cidrs'], + 'peer_id': ipsecsiteconnection['peer_id'], + 'psk': ipsecsiteconnection['psk'], + 'vpnservice_id': ipsecsiteconnection['vpnservice_id'], + 'admin_state_up': ipsecsiteconnection[ + 'admin_state_up']} + + api.vpn.ipsecsiteconnection_create( + IsA(http.HttpRequest), **form_data).AndReturn(ipsecsiteconnection) + + self.mox.ReplayAll() + + res = self.client.post(reverse(self.ADDVPNCONNECTION_PATH), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_create')}) + def test_add_ipsecsiteconnection_post_required_fields_error(self): + self._test_add_ipsecsiteconnection_post_error() + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ipsecpolicy_list', + 'vpnservice_list', + 'ipsecsiteconnection_create')}) + def test_add_ipsecsiteconnection_post_peer_cidrs_error(self): + self._test_add_ipsecsiteconnection_post_error(subnets=True) + + def _test_add_ipsecsiteconnection_post_error(self, subnets=False): + ipsecsiteconnection = self.ipsecsiteconnections.first() + ikepolicies = self.ikepolicies.list() + ipsecpolicies = self.ipsecpolicies.list() + vpnservices = self.vpnservices.list() + + api.vpn.ikepolicy_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndReturn(ikepolicies) + api.vpn.ipsecpolicy_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndReturn(ipsecpolicies) + api.vpn.vpnservice_list( + IsA(http.HttpRequest), + tenant_id=self.tenant.id).AndReturn(vpnservices) + + self.mox.ReplayAll() + + form_data = {'name': '', + 'description': ipsecsiteconnection['description'], + 'dpd_action': ipsecsiteconnection['dpd']['action'], + 'dpd_interval': ipsecsiteconnection['dpd']['interval'], + 'dpd_timeout': ipsecsiteconnection['dpd']['timeout'], + 'ikepolicy_id': '', + 'initiator': ipsecsiteconnection['initiator'], + 'ipsecpolicy_id': '', + 'mtu': ipsecsiteconnection['mtu'], + 'peer_address': '', + 'peer_cidrs': '', + 'peer_id': '', + 'psk': '', + 'vpnservice_id': '', + 'admin_state_up': ipsecsiteconnection[ + 'admin_state_up']} + if subnets: + form_data['peer_cidrs'] = '20.1.0.0/24; 21.1.0.0/24' + + res = self.client.post(reverse(self.ADDVPNCONNECTION_PATH), form_data) + + self.assertFormErrors(res, 7) + + @test.create_stubs({api.vpn: ('vpnservice_get', )}) + def test_update_vpnservice_get(self): + vpnservice = self.vpnservices.first() + + api.vpn.vpnservice_get(IsA(http.HttpRequest), vpnservice.id)\ + .AndReturn(vpnservice) + + self.mox.ReplayAll() + + res = self.client.get( + reverse(self.UPDATEVPNSERVICE_PATH, args=(vpnservice.id,))) + + self.assertTemplateUsed( + res, 'project/vpn/update_vpnservice.html') + + @test.create_stubs({api.vpn: ('vpnservice_get', 'vpnservice_update')}) + def test_update_vpnservice_post(self): + vpnservice = self.vpnservices.first() + + api.vpn.vpnservice_get(IsA(http.HttpRequest), vpnservice.id)\ + .AndReturn(vpnservice) + + data = {'name': vpnservice.name, + 'description': vpnservice.description, + 'admin_state_up': vpnservice.admin_state_up} + + api.vpn.vpnservice_update(IsA(http.HttpRequest), vpnservice.id, + vpnservice=data).AndReturn(vpnservice) + + self.mox.ReplayAll() + + form_data = data.copy() + form_data['vpnservice_id'] = vpnservice.id + + res = self.client.post(reverse( + self.UPDATEVPNSERVICE_PATH, args=(vpnservice.id,)), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + @test.create_stubs({api.vpn: ('ikepolicy_get', )}) + def test_update_ikepolicy_get(self): + ikepolicy = self.ikepolicies.first() + + api.vpn.ikepolicy_get(IsA(http.HttpRequest), ikepolicy.id)\ + .AndReturn(ikepolicy) + + self.mox.ReplayAll() + + res = self.client.get( + reverse(self.UPDATEIKEPOLICY_PATH, args=(ikepolicy.id,))) + + self.assertTemplateUsed( + res, 'project/vpn/update_ikepolicy.html') + + @test.create_stubs({api.vpn: ('ikepolicy_get', 'ikepolicy_update')}) + def test_update_ikepolicy_post(self): + ikepolicy = self.ikepolicies.first() + + api.vpn.ikepolicy_get(IsA(http.HttpRequest), ikepolicy.id)\ + .AndReturn(ikepolicy) + + data = {'name': ikepolicy.name, + 'description': ikepolicy.description, + 'auth_algorithm': ikepolicy.auth_algorithm, + 'encryption_algorithm': ikepolicy.encryption_algorithm, + 'ike_version': ikepolicy.ike_version, + 'lifetime': ikepolicy.lifetime, + 'pfs': ikepolicy.pfs, + 'phase1_negotiation_mode': ikepolicy.phase1_negotiation_mode} + + api.vpn.ikepolicy_update(IsA(http.HttpRequest), ikepolicy.id, + ikepolicy=data).AndReturn(ikepolicy) + + self.mox.ReplayAll() + + form_data = data.copy() + + form_data.update({'lifetime_units': form_data['lifetime']['units'], + 'lifetime_value': form_data['lifetime']['value'], + 'ikepolicy_id': ikepolicy.id}) + form_data.pop('lifetime') + + res = self.client.post(reverse( + self.UPDATEIKEPOLICY_PATH, args=(ikepolicy.id,)), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + @test.create_stubs({api.vpn: ('ipsecpolicy_get', )}) + def test_update_ipsecpolicy_get(self): + ipsecpolicy = self.ipsecpolicies.first() + + api.vpn.ipsecpolicy_get(IsA(http.HttpRequest), ipsecpolicy.id)\ + .AndReturn(ipsecpolicy) + + self.mox.ReplayAll() + + res = self.client.get( + reverse(self.UPDATEIPSECPOLICY_PATH, args=(ipsecpolicy.id,))) + + self.assertTemplateUsed( + res, 'project/vpn/update_ipsecpolicy.html') + + @test.create_stubs({api.vpn: ('ipsecpolicy_get', 'ipsecpolicy_update')}) + def test_update_ipsecpolicy_post(self): + ipsecpolicy = self.ipsecpolicies.first() + + api.vpn.ipsecpolicy_get(IsA(http.HttpRequest), ipsecpolicy.id)\ + .AndReturn(ipsecpolicy) + + data = {'name': ipsecpolicy.name, + 'description': ipsecpolicy.description, + 'auth_algorithm': ipsecpolicy.auth_algorithm, + 'encapsulation_mode': ipsecpolicy.encapsulation_mode, + 'encryption_algorithm': ipsecpolicy.encryption_algorithm, + 'lifetime': ipsecpolicy.lifetime, + 'pfs': ipsecpolicy.pfs, + 'transform_protocol': ipsecpolicy.transform_protocol} + + api.vpn.ipsecpolicy_update(IsA(http.HttpRequest), ipsecpolicy.id, + ipsecpolicy=data).AndReturn(ipsecpolicy) + + self.mox.ReplayAll() + + form_data = data.copy() + + form_data.update({'lifetime_units': form_data['lifetime']['units'], + 'lifetime_value': form_data['lifetime']['value'], + 'ipsecpolicy_id': ipsecpolicy.id}) + form_data.pop('lifetime') + + res = self.client.post(reverse( + self.UPDATEIPSECPOLICY_PATH, args=(ipsecpolicy.id,)), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + @test.create_stubs({api.vpn: ('ipsecsiteconnection_get', )}) + def test_update_ipsecsiteconnection_get(self): + ipsecsiteconnection = self.ipsecsiteconnections.first() + + api.vpn.ipsecsiteconnection_get( + IsA(http.HttpRequest), ipsecsiteconnection.id)\ + .AndReturn(ipsecsiteconnection) + + self.mox.ReplayAll() + + res = self.client.get( + reverse(self.UPDATEVPNCONNECTION_PATH, + args=(ipsecsiteconnection.id,))) + + self.assertTemplateUsed( + res, 'project/vpn/update_ipsecsiteconnection.html') + + @test.create_stubs({api.vpn: ('ipsecsiteconnection_get', + 'ipsecsiteconnection_update')}) + def test_update_ipsecsiteconnection_post(self): + ipsecsiteconnection = self.ipsecsiteconnections.first() + + api.vpn.ipsecsiteconnection_get( + IsA(http.HttpRequest), ipsecsiteconnection.id)\ + .AndReturn(ipsecsiteconnection) + + data = {'name': ipsecsiteconnection.name, + 'description': ipsecsiteconnection.description, + 'peer_address': ipsecsiteconnection.peer_address, + 'peer_id': ipsecsiteconnection.peer_id, + 'peer_cidrs': ipsecsiteconnection.peer_cidrs, + 'psk': ipsecsiteconnection.psk, + 'mtu': ipsecsiteconnection.mtu, + 'dpd': ipsecsiteconnection.dpd, + 'initiator': ipsecsiteconnection.initiator, + 'admin_state_up': ipsecsiteconnection.admin_state_up} + + api.vpn.ipsecsiteconnection_update( + IsA(http.HttpRequest), ipsecsiteconnection.id, + ipsec_site_connection=data).AndReturn(ipsecsiteconnection) + + self.mox.ReplayAll() + + form_data = data.copy() + + form_data.update({ + 'dpd_action': form_data['dpd']['action'], + 'dpd_interval': form_data['dpd']['interval'], + 'dpd_timeout': form_data['dpd']['timeout'], + 'peer_cidrs': ", ".join(ipsecsiteconnection['peer_cidrs']), + 'ipsecsiteconnection_id': ipsecsiteconnection.id, + }) + form_data.pop('dpd') + + res = self.client.post( + reverse(self.UPDATEVPNCONNECTION_PATH, + args=(ipsecsiteconnection.id,)), form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, str(self.INDEX_URL)) + + @test.create_stubs({api.vpn: ('vpnservice_list', 'vpnservice_delete',)}) + def test_delete_vpnservice(self): + vpnservice = self.vpnservices.list()[1] + api.vpn.vpnservice_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id) \ + .AndReturn(self.vpnservices.list()) + api.vpn.vpnservice_delete(IsA(http.HttpRequest), vpnservice.id) + self.mox.ReplayAll() + + form_data = {"action": + "vpnservicestable__deletevpnservice__%s" % vpnservice.id} + res = self.client.post(self.INDEX_URL, form_data) + + self.assertNoFormErrors(res) + + @test.create_stubs({api.vpn: ('ikepolicy_list', 'ikepolicy_delete',)}) + def test_delete_ikepolicy(self): + ikepolicy = self.ikepolicies.list()[1] + api.vpn.ikepolicy_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id) \ + .AndReturn(self.ikepolicies.list()) + api.vpn.ikepolicy_delete(IsA(http.HttpRequest), ikepolicy.id) + self.mox.ReplayAll() + + form_data = {"action": + "ikepoliciestable__deleteikepolicy__%s" % ikepolicy.id} + res = self.client.post(self.INDEX_URL, form_data) + + self.assertNoFormErrors(res) + + @test.create_stubs({api.vpn: ('ipsecpolicy_list', 'ipsecpolicy_delete',)}) + def test_delete_ipsecpolicy(self): + ipsecpolicy = self.ipsecpolicies.list()[1] + api.vpn.ipsecpolicy_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id) \ + .AndReturn(self.ipsecpolicies.list()) + api.vpn.ipsecpolicy_delete(IsA(http.HttpRequest), ipsecpolicy.id) + self.mox.ReplayAll() + + form_data = {"action": + "ipsecpoliciestable__deleteipsecpolicy__%s" + % ipsecpolicy.id} + res = self.client.post(self.INDEX_URL, form_data) + + self.assertNoFormErrors(res) + + @test.create_stubs({api.vpn: ('ipsecsiteconnection_list', + 'ipsecsiteconnection_delete',)}) + def test_delete_ipsecsiteconnection(self): + ipsecsiteconnection = self.ipsecsiteconnections.first() + api.vpn.ipsecsiteconnection_list( + IsA(http.HttpRequest), tenant_id=self.tenant.id) \ + .AndReturn(self.ipsecsiteconnections.list()) + api.vpn.ipsecsiteconnection_delete( + IsA(http.HttpRequest), ipsecsiteconnection.id) + self.mox.ReplayAll() + + form_data = {"action": + "ipsecsiteconnectionstable__deleteipsecsiteconnection__%s" + % ipsecsiteconnection.id} + res = self.client.post(self.INDEX_URL, form_data) + + self.assertNoFormErrors(res) diff --git a/neutron_vpnaas_dashboard/dashboards/project/urls.py b/neutron_vpnaas_dashboard/dashboards/project/urls.py new file mode 100644 index 0000000..0db8e40 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/urls.py @@ -0,0 +1,48 @@ +# Copyright 2013, Mirantis Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.conf.urls import url + +from neutron_vpnaas_dashboard.dashboards.project import views + +urlpatterns = [ + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^addikepolicy$', + views.AddIKEPolicyView.as_view(), name='addikepolicy'), + url(r'^update_ikepolicy/(?P[^/]+)/$', + views.UpdateIKEPolicyView.as_view(), name='update_ikepolicy'), + url(r'^addipsecpolicy$', + views.AddIPSecPolicyView.as_view(), name='addipsecpolicy'), + url(r'^update_ipsecpolicy/(?P[^/]+)/$', + views.UpdateIPSecPolicyView.as_view(), name='update_ipsecpolicy'), + url(r'^addipsecsiteconnection$', + views.AddIPSecSiteConnectionView.as_view(), + name='addipsecsiteconnection'), + url(r'^update_ipsecsiteconnection/(?P[^/]+)/$', + views.UpdateIPSecSiteConnectionView.as_view(), + name='update_ipsecsiteconnection'), + url(r'^addvpnservice$', + views.AddVPNServiceView.as_view(), name='addvpnservice'), + url(r'^update_vpnservice/(?P[^/]+)/$', + views.UpdateVPNServiceView.as_view(), name='update_vpnservice'), + url(r'^ikepolicy/(?P[^/]+)/$', + views.IKEPolicyDetailsView.as_view(), name='ikepolicydetails'), + url(r'^ipsecpolicy/(?P[^/]+)/$', + views.IPSecPolicyDetailsView.as_view(), name='ipsecpolicydetails'), + url(r'^vpnservice/(?P[^/]+)/$', + views.VPNServiceDetailsView.as_view(), name='vpnservicedetails'), + url(r'^ipsecsiteconnection/(?P[^/]+)/$', + views.IPSecSiteConnectionDetailsView.as_view(), + name='ipsecsiteconnectiondetails'), +] diff --git a/neutron_vpnaas_dashboard/dashboards/project/views.py b/neutron_vpnaas_dashboard/dashboards/project/views.py new file mode 100644 index 0000000..687dc88 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/views.py @@ -0,0 +1,361 @@ +# Copyright 2013, Mirantis Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import tabs +from horizon.utils import memoized +from horizon import workflows + +from neutron_vpnaas_dashboard import api as api_vpn +from neutron_vpnaas_dashboard.dashboards.project import forms as vpn_forms +from neutron_vpnaas_dashboard.dashboards.project import tables as vpn_tables +from neutron_vpnaas_dashboard.dashboards.project import tabs as vpn_tabs +from neutron_vpnaas_dashboard.dashboards.project import workflows as vpn_workflows + + +class IndexView(tabs.TabbedTableView): + tab_group_class = vpn_tabs.VPNTabs + template_name = 'project/vpn/index.html' + page_title = _("Virtual Private Network") + + +class AddVPNServiceView(workflows.WorkflowView): + workflow_class = vpn_workflows.AddVPNService + + +class AddIPSecSiteConnectionView(workflows.WorkflowView): + workflow_class = vpn_workflows.AddIPSecSiteConnection + + +class AddIKEPolicyView(workflows.WorkflowView): + workflow_class = vpn_workflows.AddIKEPolicy + + +class AddIPSecPolicyView(workflows.WorkflowView): + workflow_class = vpn_workflows.AddIPSecPolicy + + +class IKEPolicyDetailsView(tabs.TabView): + tab_group_class = vpn_tabs.IKEPolicyDetailsTabs + template_name = 'horizon/common/_detail.html' + page_title = "{{ ikepolicy.name|default:ikepolicy.id }}" + + @memoized.memoized_method + def get_data(self): + pid = self.kwargs['ikepolicy_id'] + try: + return api_vpn.ikepolicy_get(self.request, pid) + except Exception: + msg = _('Unable to retrieve IKE Policy details.') + exceptions.handle(self.request, msg, + redirect=self.get_redirect_url()) + + def get_context_data(self, **kwargs): + context = super(IKEPolicyDetailsView, self).get_context_data(**kwargs) + ikepolicy = self.get_data() + table = vpn_tables.IKEPoliciesTable(self.request) + context["ikepolicy"] = ikepolicy + context["url"] = self.get_redirect_url() + context["actions"] = table.render_row_actions(ikepolicy) + return context + + def get_tabs(self, request, *args, **kwargs): + ikepolicy = self.get_data() + return self.tab_group_class(request, ikepolicy=ikepolicy, **kwargs) + + @staticmethod + def get_redirect_url(): + return reverse_lazy('horizon:project:vpn:index') + + +class IPSecPolicyDetailsView(tabs.TabView): + tab_group_class = vpn_tabs.IPSecPolicyDetailsTabs + template_name = 'horizon/common/_detail.html' + page_title = "{{ ipsecpolicy.name|default:ipsecpolicy.id }}" + + @memoized.memoized_method + def get_data(self): + pid = self.kwargs['ipsecpolicy_id'] + try: + return api_vpn.ipsecpolicy_get(self.request, pid) + except Exception: + msg = _('Unable to retrieve IPSec Policy details.') + exceptions.handle(self.request, msg, + redirect=self.get_redirect_url()) + + def get_context_data(self, **kwargs): + context = super(IPSecPolicyDetailsView, self).get_context_data( + **kwargs) + ipsecpolicy = self.get_data() + table = vpn_tables.IPSecPoliciesTable(self.request) + context["ipsecpolicy"] = ipsecpolicy + context["url"] = self.get_redirect_url() + context["actions"] = table.render_row_actions(ipsecpolicy) + return context + + def get_tabs(self, request, *args, **kwargs): + ipsecpolicy = self.get_data() + return self.tab_group_class(request, ipsecpolicy=ipsecpolicy, **kwargs) + + @staticmethod + def get_redirect_url(): + return reverse_lazy('horizon:project:vpn:index') + + +class VPNServiceDetailsView(tabs.TabView): + tab_group_class = vpn_tabs.VPNServiceDetailsTabs + template_name = 'horizon/common/_detail.html' + page_title = "{{ vpnservice.name|default:vpnservice.id }}" + + @memoized.memoized_method + def get_data(self): + sid = self.kwargs['vpnservice_id'] + + try: + vpnservice = api_vpn.vpnservice_get(self.request, sid) + except Exception: + vpnservice = [] + msg = _('Unable to retrieve VPN Service details.') + exceptions.handle(self.request, msg, + redirect=self.get_redirect_url()) + try: + connections = api_vpn.ipsecsiteconnection_list( + self.request, vpnservice_id=sid) + vpnservice.vpnconnections = connections + except Exception: + vpnservice.vpnconnections = [] + + return vpnservice + + def get_context_data(self, **kwargs): + context = super(VPNServiceDetailsView, self).get_context_data(**kwargs) + vpnservice = self.get_data() + table = vpn_tables.VPNServicesTable(self.request) + context["vpnservice"] = vpnservice + context["url"] = self.get_redirect_url() + context["actions"] = table.render_row_actions(vpnservice) + return context + + def get_tabs(self, request, *args, **kwargs): + vpnservice = self.get_data() + return self.tab_group_class(request, vpnservice=vpnservice, **kwargs) + + @staticmethod + def get_redirect_url(): + return reverse_lazy('horizon:project:vpn:index') + + +class IPSecSiteConnectionDetailsView(tabs.TabView): + tab_group_class = vpn_tabs.IPSecSiteConnectionDetailsTabs + template_name = 'horizon/common/_detail.html' + page_title = "{{ ipsecsiteconnection.name|default:ipsecsiteconnection.id}}" + + @memoized.memoized_method + def get_data(self): + cid = self.kwargs['ipsecsiteconnection_id'] + try: + return api_vpn.ipsecsiteconnection_get(self.request, cid) + except Exception: + msg = _('Unable to retrieve IPSec Site Connection details.') + exceptions.handle(self.request, msg, + redirect=self.get_redirect_url()) + + def get_context_data(self, **kwargs): + context = super(IPSecSiteConnectionDetailsView, self).get_context_data( + **kwargs) + ipsecsiteconnection = self.get_data() + table = vpn_tables.IPSecSiteConnectionsTable(self.request) + context["ipsecsiteconnection"] = ipsecsiteconnection + context["url"] = self.get_redirect_url() + context["actions"] = table.render_row_actions(ipsecsiteconnection) + return context + + def get_tabs(self, request, *args, **kwargs): + ipsecsiteconnection = self.get_data() + return self.tab_group_class(request, + ipsecsiteconnection=ipsecsiteconnection, + **kwargs) + + @staticmethod + def get_redirect_url(): + return reverse_lazy('horizon:project:vpn:index') + + +class UpdateVPNServiceView(forms.ModalFormView): + form_class = vpn_forms.UpdateVPNService + form_id = "update_vpnservice_form" + template_name = "project/vpn/update_vpnservice.html" + context_object_name = 'vpnservice' + submit_label = _("Save Changes") + submit_url = "horizon:project:vpn:update_vpnservice" + success_url = reverse_lazy("horizon:project:vpn:index") + page_title = _("Edit VPN Service") + + def get_context_data(self, **kwargs): + context = super(UpdateVPNServiceView, self).get_context_data(**kwargs) + context["vpnservice_id"] = self.kwargs['vpnservice_id'] + args = (self.kwargs['vpnservice_id'],) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + @memoized.memoized_method + def _get_object(self, *args, **kwargs): + vpnservice_id = self.kwargs['vpnservice_id'] + try: + return api_vpn.vpnservice_get(self.request, vpnservice_id) + except Exception as e: + redirect = self.success_url + msg = _('Unable to retrieve VPN Service details. %s') % e + exceptions.handle(self.request, msg, redirect=redirect) + + def get_initial(self): + vpnservice = self._get_object() + return {'name': vpnservice['name'], + 'vpnservice_id': vpnservice['id'], + 'description': vpnservice['description'], + 'admin_state_up': vpnservice['admin_state_up']} + + +class UpdateIKEPolicyView(forms.ModalFormView): + form_class = vpn_forms.UpdateIKEPolicy + form_id = "update_ikepolicy_form" + template_name = "project/vpn/update_ikepolicy.html" + context_object_name = 'ikepolicy' + submit_label = _("Save Changes") + submit_url = "horizon:project:vpn:update_ikepolicy" + success_url = reverse_lazy("horizon:project:vpn:index") + page_title = _("Edit IKE Policy") + + def get_context_data(self, **kwargs): + context = super(UpdateIKEPolicyView, self).get_context_data(**kwargs) + context["ikepolicy_id"] = self.kwargs['ikepolicy_id'] + args = (self.kwargs['ikepolicy_id'],) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + @memoized.memoized_method + def _get_object(self, *args, **kwargs): + ikepolicy_id = self.kwargs['ikepolicy_id'] + try: + return api_vpn.ikepolicy_get(self.request, ikepolicy_id) + except Exception as e: + redirect = self.success_url + msg = _('Unable to retrieve IKE Policy details. %s') % e + exceptions.handle(self.request, msg, redirect=redirect) + + def get_initial(self): + ikepolicy = self._get_object() + return {'name': ikepolicy['name'], + 'ikepolicy_id': ikepolicy['id'], + 'description': ikepolicy['description'], + 'auth_algorithm': ikepolicy['auth_algorithm'], + 'encryption_algorithm': ikepolicy['encryption_algorithm'], + 'ike_version': ikepolicy['ike_version'], + 'lifetime_units': ikepolicy['lifetime']['units'], + 'lifetime_value': ikepolicy['lifetime']['value'], + 'pfs': ikepolicy['pfs'], + 'phase1_negotiation_mode': ikepolicy[ + 'phase1_negotiation_mode']} + + +class UpdateIPSecPolicyView(forms.ModalFormView): + form_class = vpn_forms.UpdateIPSecPolicy + form_id = "update_ipsecpolicy_form" + template_name = "project/vpn/update_ipsecpolicy.html" + context_object_name = 'ipsecpolicy' + submit_label = _("Save Changes") + submit_url = "horizon:project:vpn:update_ipsecpolicy" + success_url = reverse_lazy("horizon:project:vpn:index") + page_title = _("Edit IPSec Policy") + + def get_context_data(self, **kwargs): + context = super(UpdateIPSecPolicyView, self).get_context_data(**kwargs) + context["ipsecpolicy_id"] = self.kwargs['ipsecpolicy_id'] + args = (self.kwargs['ipsecpolicy_id'],) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + @memoized.memoized_method + def _get_object(self, *args, **kwargs): + ipsecpolicy_id = self.kwargs['ipsecpolicy_id'] + try: + return api_vpn.ipsecpolicy_get(self.request, ipsecpolicy_id) + except Exception as e: + redirect = self.success_url + msg = _('Unable to retrieve IPSec Policy details. %s') % e + exceptions.handle(self.request, msg, redirect=redirect) + + def get_initial(self): + ipsecpolicy = self._get_object() + return {'name': ipsecpolicy['name'], + 'ipsecpolicy_id': ipsecpolicy['id'], + 'description': ipsecpolicy['description'], + 'auth_algorithm': ipsecpolicy['auth_algorithm'], + 'encapsulation_mode': ipsecpolicy['encapsulation_mode'], + 'encryption_algorithm': ipsecpolicy['encryption_algorithm'], + 'lifetime_units': ipsecpolicy['lifetime']['units'], + 'lifetime_value': ipsecpolicy['lifetime']['value'], + 'pfs': ipsecpolicy['pfs'], + 'transform_protocol': ipsecpolicy['transform_protocol']} + + +class UpdateIPSecSiteConnectionView(forms.ModalFormView): + form_class = vpn_forms.UpdateIPSecSiteConnection + form_id = "update_ipsecsiteconnection_form" + template_name = "project/vpn/update_ipsecsiteconnection.html" + context_object_name = 'ipsecsiteconnection' + submit_label = _("Save Changes") + submit_url = "horizon:project:vpn:update_ipsecsiteconnection" + success_url = reverse_lazy("horizon:project:vpn:index") + page_title = _("Edit IPSec Site Connection") + + def get_context_data(self, **kwargs): + context = super( + UpdateIPSecSiteConnectionView, self).get_context_data(**kwargs) + context["ipsecsiteconnection_id"] = self.kwargs[ + 'ipsecsiteconnection_id'] + args = (self.kwargs['ipsecsiteconnection_id'],) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + @memoized.memoized_method + def _get_object(self, *args, **kwargs): + connection_id = self.kwargs['ipsecsiteconnection_id'] + try: + return api_vpn.ipsecsiteconnection_get(self.request, connection_id) + except Exception as e: + redirect = self.success_url + msg = _('Unable to retrieve IPSec Site Connection details. %s') % e + exceptions.handle(self.request, msg, redirect=redirect) + + def get_initial(self): + ipsecsiteconnection = self._get_object() + return {'name': ipsecsiteconnection['name'], + 'ipsecsiteconnection_id': ipsecsiteconnection['id'], + 'description': ipsecsiteconnection['description'], + 'peer_address': ipsecsiteconnection['peer_address'], + 'peer_id': ipsecsiteconnection['peer_id'], + 'peer_cidrs': ", ".join(ipsecsiteconnection['peer_cidrs']), + 'psk': ipsecsiteconnection['psk'], + 'mtu': ipsecsiteconnection['mtu'], + 'dpd_action': ipsecsiteconnection['dpd']['action'], + 'dpd_interval': ipsecsiteconnection['dpd']['interval'], + 'dpd_timeout': ipsecsiteconnection['dpd']['timeout'], + 'initiator': ipsecsiteconnection['initiator'], + 'admin_state_up': ipsecsiteconnection['admin_state_up']} diff --git a/neutron_vpnaas_dashboard/dashboards/project/workflows.py b/neutron_vpnaas_dashboard/dashboards/project/workflows.py new file mode 100644 index 0000000..935df00 --- /dev/null +++ b/neutron_vpnaas_dashboard/dashboards/project/workflows.py @@ -0,0 +1,516 @@ +# Copyright 2013, Mirantis Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import workflows + +from openstack_dashboard import api + +from neutron_vpnaas_dashboard import api as api_vpn + + +class AddVPNServiceAction(workflows.Action): + name = forms.CharField(max_length=80, label=_("Name"), required=False) + description = forms.CharField( + initial="", required=False, + max_length=80, label=_("Description")) + router_id = forms.ChoiceField(label=_("Router")) + subnet_id = forms.ChoiceField(label=_("Subnet")) + admin_state_up = forms.BooleanField( + label=_("Enable Admin State"), + help_text=_("The state of VPN service to start in. If disabled " + "(not checked), VPN service does not forward packets."), + initial=True, + required=False) + + def __init__(self, request, *args, **kwargs): + super(AddVPNServiceAction, self).__init__(request, *args, **kwargs) + + def populate_subnet_id_choices(self, request, context): + subnet_id_choices = [('', _("Select a Subnet"))] + try: + tenant_id = request.user.tenant_id + networks = api.neutron.network_list_for_tenant(request, tenant_id) + except Exception: + exceptions.handle(request, + _('Unable to retrieve networks list.')) + networks = [] + for n in networks: + for s in n['subnets']: + subnet_id_choices.append((s.id, s.cidr)) + self.fields['subnet_id'].choices = subnet_id_choices + return subnet_id_choices + + def populate_router_id_choices(self, request, context): + router_id_choices = [('', _("Select a Router"))] + try: + tenant_id = request.user.tenant_id + routers = api.neutron.router_list(request, tenant_id=tenant_id) + except Exception: + exceptions.handle(request, + _('Unable to retrieve routers list.')) + routers = [] + for r in routers: + router_id_choices.append((r.id, r.name)) + self.fields['router_id'].choices = router_id_choices + return router_id_choices + + class Meta(object): + name = _("Add New VPN Service") + permissions = ('openstack.services.network',) + help_text_template = "project/vpn/_add_vpn_service_help.html" + + +class AddVPNServiceStep(workflows.Step): + action_class = AddVPNServiceAction + contributes = ("name", "description", "subnet_id", + "router_id", "admin_state_up") + + def contribute(self, data, context): + context = super(AddVPNServiceStep, self).contribute(data, context) + if data: + return context + + +class AddVPNService(workflows.Workflow): + slug = "addvpnservice" + name = _("Add VPN Service") + finalize_button_name = _("Add") + success_message = _('Added VPN Service "%s".') + failure_message = _('Unable to add VPN Service "%s".') + success_url = "horizon:project:vpn:index" + default_steps = (AddVPNServiceStep,) + + def format_status_message(self, message): + return message % self.context.get('name') + + def handle(self, request, context): + try: + api_vpn.vpnservice_create(request, **context) + return True + except Exception: + return False + + +class AddIKEPolicyAction(workflows.Action): + name = forms.CharField(max_length=80, label=_("Name"), required=False) + description = forms.CharField( + initial="", required=False, + max_length=80, label=_("Description")) + auth_algorithm = forms.ChoiceField(label=_("Authorization algorithm"), + required=False) + encryption_algorithm = forms.ChoiceField(label=_("Encryption algorithm"), + required=False) + ike_version = forms.ChoiceField(label=_("IKE version"), required=False) + lifetime_units = forms.ChoiceField(label=_("Lifetime units for IKE keys"), + required=False) + lifetime_value = forms.IntegerField( + min_value=60, label=_("Lifetime value for IKE keys"), + initial=3600, + help_text=_("Equal to or greater than 60"), + required=False) + pfs = forms.ChoiceField(label=_("Perfect Forward Secrecy"), required=False) + phase1_negotiation_mode = forms.ChoiceField( + label=_("IKE Phase1 negotiation mode"), required=False) + + def __init__(self, request, *args, **kwargs): + super(AddIKEPolicyAction, self).__init__(request, *args, **kwargs) + + auth_algorithm_choices = [("sha1", "sha1")] + self.fields['auth_algorithm'].choices = auth_algorithm_choices + # Currently this field has only one choice, so mark it as readonly. + self.fields['auth_algorithm'].widget.attrs['readonly'] = True + + encryption_algorithm_choices = [("3des", "3des"), + ("aes-128", "aes-128"), + ("aes-192", "aes-192"), + ("aes-256", "aes-256")] + self.fields[ + 'encryption_algorithm'].choices = encryption_algorithm_choices + self.fields['encryption_algorithm'].initial = "aes-128" + + ike_version_choices = [("v1", "v1"), + ("v2", "v2")] + self.fields['ike_version'].choices = ike_version_choices + + lifetime_units_choices = [("seconds", "seconds")] + self.fields['lifetime_units'].choices = lifetime_units_choices + # Currently this field has only one choice, so mark it as readonly. + self.fields['lifetime_units'].widget.attrs['readonly'] = True + + pfs_choices = [("group2", "group2"), + ("group5", "group5"), + ("group14", "group14")] + self.fields['pfs'].choices = pfs_choices + self.fields['pfs'].initial = "group5" + + phase1_neg_mode_choices = [("main", "main")] + self.fields[ + 'phase1_negotiation_mode'].choices = phase1_neg_mode_choices + # Currently this field has only one choice, so mark it as readonly. + self.fields['phase1_negotiation_mode'].widget.attrs['readonly'] = True + + class Meta(object): + name = _("Add New IKE Policy") + permissions = ('openstack.services.network',) + help_text_template = "project/vpn/_add_ike_policy_help.html" + + +class AddIKEPolicyStep(workflows.Step): + action_class = AddIKEPolicyAction + contributes = ("name", "description", "auth_algorithm", + "encryption_algorithm", "ike_version", + "lifetime_units", "lifetime_value", + "pfs", "phase1_negotiation_mode") + + def contribute(self, data, context): + context = super(AddIKEPolicyStep, self).contribute(data, context) + context['lifetime'] = {'units': data['lifetime_units'], + 'value': data['lifetime_value']} + context.pop('lifetime_units') + context.pop('lifetime_value') + if data: + return context + + +class AddIKEPolicy(workflows.Workflow): + slug = "addikepolicy" + name = _("Add IKE Policy") + finalize_button_name = _("Add") + success_message = _('Added IKE Policy "%s".') + failure_message = _('Unable to add IKE Policy "%s".') + success_url = "horizon:project:vpn:index" + default_steps = (AddIKEPolicyStep,) + + def format_status_message(self, message): + return message % self.context.get('name') + + def handle(self, request, context): + try: + api_vpn.ikepolicy_create(request, **context) + return True + except Exception: + return False + + +class AddIPSecPolicyAction(workflows.Action): + name = forms.CharField(max_length=80, label=_("Name"), required=False) + description = forms.CharField( + initial="", required=False, + max_length=80, label=_("Description")) + auth_algorithm = forms.ChoiceField(label=_("Authorization algorithm"), + required=False) + encapsulation_mode = forms.ChoiceField(label=_("Encapsulation mode"), + required=False) + encryption_algorithm = forms.ChoiceField(label=_("Encryption algorithm"), + required=False) + lifetime_units = forms.ChoiceField(label=_("Lifetime units"), + required=False) + lifetime_value = forms.IntegerField( + min_value=60, label=_("Lifetime value for IKE keys "), + initial=3600, + help_text=_("Equal to or greater than 60"), + required=False) + pfs = forms.ChoiceField(label=_("Perfect Forward Secrecy"), required=False) + transform_protocol = forms.ChoiceField(label=_("Transform Protocol"), + required=False) + + def __init__(self, request, *args, **kwargs): + super(AddIPSecPolicyAction, self).__init__(request, *args, **kwargs) + + auth_algorithm_choices = [("sha1", "sha1")] + self.fields['auth_algorithm'].choices = auth_algorithm_choices + # Currently this field has only one choice, so mark it as readonly. + self.fields['auth_algorithm'].widget.attrs['readonly'] = True + + encapsulation_mode_choices = [("tunnel", "tunnel"), + ("transport", "transport")] + self.fields['encapsulation_mode'].choices = encapsulation_mode_choices + + encryption_algorithm_choices = [("3des", "3des"), + ("aes-128", "aes-128"), + ("aes-192", "aes-192"), + ("aes-256", "aes-256")] + self.fields[ + 'encryption_algorithm'].choices = encryption_algorithm_choices + self.fields['encryption_algorithm'].initial = "aes-128" + + lifetime_units_choices = [("seconds", "seconds")] + self.fields['lifetime_units'].choices = lifetime_units_choices + # Currently this field has only one choice, so mark it as readonly. + self.fields['lifetime_units'].widget.attrs['readonly'] = True + + pfs_choices = [("group2", "group2"), + ("group5", "group5"), + ("group14", "group14")] + self.fields['pfs'].choices = pfs_choices + self.fields['pfs'].initial = "group5" + + transform_protocol_choices = [("esp", "esp"), + ("ah", "ah"), + ("ah-esp", "ah-esp")] + self.fields['transform_protocol'].choices = transform_protocol_choices + + class Meta(object): + name = _("Add New IPSec Policy") + permissions = ('openstack.services.network',) + help_text_template = 'project/vpn/_add_ipsec_policy_help.html' + + +class AddIPSecPolicyStep(workflows.Step): + action_class = AddIPSecPolicyAction + contributes = ("name", "description", "auth_algorithm", + "encapsulation_mode", "encryption_algorithm", + "lifetime_units", "lifetime_value", + "pfs", "transform_protocol") + + def contribute(self, data, context): + context = super(AddIPSecPolicyStep, self).contribute(data, context) + context['lifetime'] = {'units': data['lifetime_units'], + 'value': data['lifetime_value']} + context.pop('lifetime_units') + context.pop('lifetime_value') + if data: + return context + + +class AddIPSecPolicy(workflows.Workflow): + slug = "addipsecpolicy" + name = _("Add IPSec Policy") + finalize_button_name = _("Add") + success_message = _('Added IPSec Policy "%s".') + failure_message = _('Unable to add IPSec Policy "%s".') + success_url = "horizon:project:vpn:index" + default_steps = (AddIPSecPolicyStep,) + + def format_status_message(self, message): + return message % self.context.get('name') + + def handle(self, request, context): + try: + api_vpn.ipsecpolicy_create(request, **context) + return True + except Exception: + return False + + +class AddIPSecSiteConnectionAction(workflows.Action): + name = forms.CharField(max_length=80, label=_("Name"), required=False) + description = forms.CharField( + initial="", required=False, + max_length=80, label=_("Description")) + vpnservice_id = forms.ChoiceField( + label=_("VPN Service associated with this connection")) + ikepolicy_id = forms.ChoiceField( + label=_("IKE Policy associated with this connection")) + ipsecpolicy_id = forms.ChoiceField( + label=_("IPSec Policy associated with this connection")) + peer_address = forms.IPField( + label=_("Peer gateway public IPv4/IPv6 Address or FQDN"), + help_text=_("Peer gateway public IPv4/IPv6 address or FQDN for " + "the VPN Connection"), + version=forms.IPv4 | forms.IPv6, + mask=False) + peer_id = forms.IPField( + label=_("Peer router identity for authentication (Peer ID)"), + help_text=_("Peer router identity for authentication. " + "Can be IPv4/IPv6 address, e-mail, key ID, or FQDN"), + version=forms.IPv4 | forms.IPv6, + mask=False) + peer_cidrs = forms.MultiIPField( + label=_("Remote peer subnet(s)"), + help_text=_("Remote peer subnet(s) address(es) " + "with mask(s) in CIDR format " + "separated with commas if needed " + "(e.g. 20.1.0.0/24, 21.1.0.0/24)"), + version=forms.IPv4 | forms.IPv6, + mask=True) + psk = forms.CharField( + max_length=80, + label=_("Pre-Shared Key (PSK) string"), + widget=forms.PasswordInput(render_value=False), + help_text=_("The pre-defined key string " + "between the two peers of the VPN connection")) + + def populate_ikepolicy_id_choices(self, request, context): + ikepolicy_id_choices = [('', _("Select IKE Policy"))] + try: + tenant_id = self.request.user.tenant_id + ikepolicies = api_vpn.ikepolicy_list(request, tenant_id=tenant_id) + except Exception: + exceptions.handle(request, + _('Unable to retrieve IKE Policies list.')) + ikepolicies = [] + for p in ikepolicies: + ikepolicy_id_choices.append((p.id, p.name)) + self.fields['ikepolicy_id'].choices = ikepolicy_id_choices + return ikepolicy_id_choices + + def populate_ipsecpolicy_id_choices(self, request, context): + ipsecpolicy_id_choices = [('', _("Select IPSec Policy"))] + try: + tenant_id = self.request.user.tenant_id + ipsecpolicies = api_vpn.ipsecpolicy_list(request, + tenant_id=tenant_id) + except Exception: + exceptions.handle(request, + _('Unable to retrieve IPSec Policies list.')) + ipsecpolicies = [] + for p in ipsecpolicies: + ipsecpolicy_id_choices.append((p.id, p.name)) + self.fields['ipsecpolicy_id'].choices = ipsecpolicy_id_choices + return ipsecpolicy_id_choices + + def populate_vpnservice_id_choices(self, request, context): + vpnservice_id_choices = [('', _("Select VPN Service"))] + try: + tenant_id = self.request.user.tenant_id + vpnservices = api_vpn.vpnservice_list(request, tenant_id=tenant_id) + except Exception: + exceptions.handle(request, + _('Unable to retrieve VPN Services list.')) + vpnservices = [] + for s in vpnservices: + vpnservice_id_choices.append((s.id, s.name)) + self.fields['vpnservice_id'].choices = vpnservice_id_choices + return vpnservice_id_choices + + class Meta(object): + name = _("Add New IPSec Site Connection") + permissions = ('openstack.services.network',) + help_text = _("Create IPSec Site Connection for current " + "project. Assign a name and description for the " + "IPSec Site Connection. " + "All fields in this tab are required." + ) + + +class AddIPSecSiteConnectionStep(workflows.Step): + action_class = AddIPSecSiteConnectionAction + contributes = ("name", "description", + "vpnservice_id", "ikepolicy_id", "ipsecpolicy_id", + "peer_address", "peer_id", "peer_cidrs", "psk") + + +class AddIPSecSiteConnectionOptionalAction(workflows.Action): + mtu = forms.IntegerField( + min_value=68, + label=_("Maximum Transmission Unit size for the connection"), + initial=1500, + required=False, + help_text=_("Equal to or greater than 68 if the local subnet is IPv4. " + "Equal to or greater than 1280 if the local subnet " + "is IPv6.")) + dpd_action = forms.ChoiceField(label=_("Dead peer detection actions"), + required=False) + dpd_interval = forms.IntegerField( + min_value=1, label=_("Dead peer detection interval"), + initial=30, + required=False, + help_text=_("Valid integer lesser than DPD timeout")) + dpd_timeout = forms.IntegerField( + min_value=1, label=_("Dead peer detection timeout"), + initial=120, + required=False, + help_text=_("Valid integer greater than the DPD interval")) + initiator = forms.ChoiceField(label=_("Initiator state"), required=False) + admin_state_up = forms.BooleanField( + label=_("Enable Admin State"), + help_text=_("The state of IPSec site connection to start in. " + "If disabled (not checked), IPSec site connection " + "does not forward packets."), + initial=True, + required=False) + + def __init__(self, request, *args, **kwargs): + super(AddIPSecSiteConnectionOptionalAction, self).__init__( + request, *args, **kwargs) + + initiator_choices = [("bi-directional", "bi-directional"), + ("response-only", "response-only")] + self.fields['initiator'].choices = initiator_choices + + def populate_dpd_action_choices(self, request, context): + dpd_action_choices = [("hold", "hold"), + ("clear", "clear"), + ("disabled", "disabled"), + ("restart", "restart"), + ("restart-by-peer", "restart-by-peer")] + self.fields['dpd_action'].choices = dpd_action_choices + return dpd_action_choices + + def clean(self): + cleaned_data = super(AddIPSecSiteConnectionOptionalAction, + self).clean() + interval = cleaned_data.get('dpd_interval') + timeout = cleaned_data.get('dpd_timeout') + + if not interval < timeout: + msg = _("DPD Timeout must be greater than DPD Interval") + self._errors['dpd_timeout'] = self.error_class([msg]) + return cleaned_data + + class Meta(object): + name = _("Optional Parameters") + permissions = ('openstack.services.network',) + help_text = _("Fields in this tab are optional. " + "You can configure the detail of " + "IPSec site connection created." + ) + + +class AddIPSecSiteConnectionOptionalStep(workflows.Step): + action_class = AddIPSecSiteConnectionOptionalAction + contributes = ("dpd_action", "dpd_interval", "dpd_timeout", + "initiator", "mtu", "admin_state_up") + + def contribute(self, data, context): + context = super( + AddIPSecSiteConnectionOptionalStep, self).contribute(data, context) + context['dpd'] = {'action': data['dpd_action'], + 'interval': data['dpd_interval'], + 'timeout': data['dpd_timeout']} + context.pop('dpd_action') + context.pop('dpd_interval') + context.pop('dpd_timeout') + + cidrs = context['peer_cidrs'] + context['peer_cidrs'] = cidrs.replace(" ", "").split(",") + + if data: + return context + + +class AddIPSecSiteConnection(workflows.Workflow): + slug = "addipsecsiteconnection" + name = _("Add IPSec Site Connection") + finalize_button_name = _("Add") + success_message = _('Added IPSec Site Connection "%s".') + failure_message = _('Unable to add IPSec Site Connection "%s".') + success_url = "horizon:project:vpn:index" + default_steps = (AddIPSecSiteConnectionStep, + AddIPSecSiteConnectionOptionalStep) + + def format_status_message(self, message): + return message % self.context.get('name') + + def handle(self, request, context): + try: + api_vpn.ipsecsiteconnection_create(request, **context) + return True + except Exception: + return False diff --git a/neutron_vpnaas_dashboard/enabled/_1470_project_vpn_panel.py b/neutron_vpnaas_dashboard/enabled/_1470_project_vpn_panel.py new file mode 100644 index 0000000..9efc991 --- /dev/null +++ b/neutron_vpnaas_dashboard/enabled/_1470_project_vpn_panel.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'vpn' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'network' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'neutron_vpnaas_dashboard.dashboards.project.panel.VPN' + +ADD_INSTALLED_APPS = ["neutron_vpnaas_dashboard"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7abeca5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +pbr!=2.1.0,>=2.0.0 # Apache-2.0 +Django<1.10,>=1.8 # BSD +python-neutronclient>=5.1.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..6a23822 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,32 @@ +[metadata] +name = neutron-vpnaas-dashboard +summary = Neutron VPNaaS Dashboard +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://docs.openstack.org/developer/trove/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.5 + +[files] +packages = + neutron_vpnaas_dashboard + +[build_sphinx] +source-dir = doc/source +build-dir = doc/build +all-files = 1 +warning-is-error = 1 + +[upload_sphinx] +upload-dir = doc/build/html diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..566d844 --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr>=2.0.0'], + pbr=True)