From d139d81213c743a4ed277f0496f263e55a86c78b Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Sun, 14 Oct 2018 12:52:23 +0200 Subject: [PATCH] Retire astara repo Retire repository, following https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project Change-Id: I0a8240c961955447d65aee7af24e03cb81da12d3 --- .gitignore | 18 - .travis.yml | 10 - LICENSE | 175 --- README.md | 10 - README.rst | 10 + ansible/main.yml | 30 - ansible/tasks/astara.yml | 97 -- ansible/tasks/base.yml | 48 - ansible/tasks/bird.yml | 26 - ansible/tasks/conntrackd.yml | 7 - ansible/tasks/debian_backports.yml | 5 - ansible/tasks/dnsmasq.yml | 13 - ansible/tasks/extras.yml | 7 - ansible/tasks/strongswan.yml | 9 - ansible/tasks/update_kernel.yml | 21 - ansible/templates/default_grub | 9 - ansible/templates/dnsmasq.conf.j2 | 9 - ansible/templates/gunicorn | 1 - ansible/templates/gunicorn.j2 | 12 - ansible/templates/logrotate.j2 | 10 - ansible/templates/motd.j2 | 8 - ansible/templates/rules_v4.j2 | 25 - ansible/templates/rules_v6.j2 | 7 - astara_router/__init__.py | 15 - astara_router/api/__init__.py | 15 - astara_router/api/server.py | 49 - astara_router/api/v1/__init__.py | 22 - astara_router/api/v1/base.py | 32 - astara_router/api/v1/firewall.py | 41 - astara_router/api/v1/status.py | 43 - astara_router/api/v1/system.py | 155 --- astara_router/commands/__init__.py | 15 - astara_router/commands/management.py | 102 -- astara_router/debug.py | 34 - astara_router/defaults.py | 57 - astara_router/drivers/__init__.py | 15 - astara_router/drivers/arp.py | 159 --- astara_router/drivers/base.py | 60 - astara_router/drivers/bird.py | 333 ----- .../drivers/conntrackd.conf.template | 38 - astara_router/drivers/conntrackd.py | 93 -- astara_router/drivers/dnsmasq.py | 170 --- astara_router/drivers/hostname.py | 48 - astara_router/drivers/ip.py | 611 ---------- astara_router/drivers/iptables.py | 490 -------- .../drivers/keepalived.conf.template | 46 - astara_router/drivers/keepalived.py | 158 --- .../drivers/loadbalancer/__init__.py | 37 - .../drivers/loadbalancer/nginx.conf.template | 19 - astara_router/drivers/loadbalancer/nginx.py | 58 - astara_router/drivers/metadata.py | 136 --- astara_router/drivers/ping.py | 59 - astara_router/drivers/vpn/__init__.py | 0 astara_router/drivers/vpn/ipsec.py | 105 -- astara_router/drivers/vpn/templates/README | 3 - .../drivers/vpn/templates/ipsec.conf.j2 | 39 - .../drivers/vpn/templates/ipsec.secrets.j2 | 6 - astara_router/manager.py | 329 ----- astara_router/metadata_proxy.py | 190 --- astara_router/models.py | 1080 ----------------- astara_router/settings.py | 10 - astara_router/utils.py | 126 -- astara_router/version.py | 18 - diskimage-builder/README.md | 22 - diskimage-builder/elements/ansible/README.md | 11 - .../cleanup.d/50-remove-source-ansible | 5 - .../elements/ansible/element-deps | 1 - .../environment.d/10-ansible-venv-dir.bash | 4 - .../30-ansible-package | 4 - .../ansible-source-install/30-ansible-source | 35 - diskimage-builder/elements/astara/README.rst | 9 - .../elements/astara/element-deps | 3 - .../astara/environment.d/10-cloudinit-akanda | 1 - .../install.d/astara-source-install/70-astara | 11 - .../astara/post-install.d/90-fix-locale | 5 - .../elements/astara/source-repository-astara | 1 - .../elements/debug-user/README.md | 10 - .../debug-user/install.d/50-debug-user | 21 - .../elements/debug-user/package-installs.yaml | 1 - .../elements/nginx-plus/README.rst | 10 - .../elements/nginx-plus/element-deps | 1 - .../nginx-plus/extra-data.d/00-copy-in-keys | 22 - .../install.d/package-installs-nginx-plus | 1 - .../nginx-plus/post-install.d/99-cleanup | 16 - .../pre-install.d/02-nginx-plus-prep | 34 - diskimage-builder/elements/nginx/README.rst | 2 - diskimage-builder/elements/nginx/element-deps | 2 - .../nginx/install.d/package-installs-nginx | 1 - .../post-install.d/99-set-nginx-sites-enabled | 19 - etc/rootwrap.conf | 27 - etc/rootwrap.d/network.filters | 48 - releasenotes/notes/.placeholder | 0 .../notes/conntrackd-c179372033f4134e.yaml | 4 - ...face_cache_refreshed-57ce5b0637282b60.yaml | 3 - .../notes/initial-note-fd05752e5f3b87fb.yaml | 3 - .../keepalived_manager-4ed9f900f3d3429b.yaml | 5 - releasenotes/source/_static/.placeholder | 0 releasenotes/source/_templates/.placeholder | 0 releasenotes/source/conf.py | 275 ----- releasenotes/source/index.rst | 8 - releasenotes/source/mitaka.rst | 5 - requirements.txt | 11 - scripts/build_dib_appliance.sh | 38 - scripts/debug-config.py | 32 - scripts/etc/init.d/astara-router-api-server | 47 - scripts/etc/init.d/bird6 | 139 --- scripts/etc/init.d/metadata | 47 - scripts/etc/init.d/ssh | 168 --- setup.cfg | 48 - setup.py | 29 - test-requirements.txt | 13 - test/__init__.py | 15 - test/unit/__init__.py | 15 - test/unit/api/__init__.py | 15 - test/unit/api/v1/__init__.py | 15 - test/unit/api/v1/fakes.py | 53 - test/unit/api/v1/payloads/__init__.py | 15 - test/unit/api/v1/test_base.py | 42 - test/unit/api/v1/test_firewall.py | 52 - test/unit/api/v1/test_system.py | 309 ----- test/unit/drivers/__init__.py | 15 - test/unit/drivers/test_arp.py | 186 --- test/unit/drivers/test_bird.py | 246 ---- test/unit/drivers/test_conntrackd.py | 75 -- test/unit/drivers/test_dnsmasq.py | 168 --- test/unit/drivers/test_hostname.py | 61 - test/unit/drivers/test_ip.py | 594 --------- test/unit/drivers/test_iptables.py | 222 ---- test/unit/drivers/test_keepalived.py | 141 --- test/unit/drivers/test_metadata.py | 86 -- test/unit/drivers/test_route.py | 440 ------- test/unit/drivers/test_strongswan.py | 96 -- test/unit/fakes.py | 255 ---- test/unit/test_metadata_proxy.py | 87 -- test/unit/test_models.py | 802 ------------ test/unit/test_utils.py | 131 -- tox.ini | 32 - 137 files changed, 10 insertions(+), 10943 deletions(-) delete mode 100644 .gitignore delete mode 100644 .travis.yml delete mode 100644 LICENSE delete mode 100644 README.md create mode 100644 README.rst delete mode 100644 ansible/main.yml delete mode 100644 ansible/tasks/astara.yml delete mode 100644 ansible/tasks/base.yml delete mode 100644 ansible/tasks/bird.yml delete mode 100644 ansible/tasks/conntrackd.yml delete mode 100644 ansible/tasks/debian_backports.yml delete mode 100644 ansible/tasks/dnsmasq.yml delete mode 100644 ansible/tasks/extras.yml delete mode 100644 ansible/tasks/strongswan.yml delete mode 100644 ansible/tasks/update_kernel.yml delete mode 100644 ansible/templates/default_grub delete mode 100644 ansible/templates/dnsmasq.conf.j2 delete mode 100644 ansible/templates/gunicorn delete mode 100644 ansible/templates/gunicorn.j2 delete mode 100644 ansible/templates/logrotate.j2 delete mode 100644 ansible/templates/motd.j2 delete mode 100644 ansible/templates/rules_v4.j2 delete mode 100644 ansible/templates/rules_v6.j2 delete mode 100644 astara_router/__init__.py delete mode 100644 astara_router/api/__init__.py delete mode 100644 astara_router/api/server.py delete mode 100644 astara_router/api/v1/__init__.py delete mode 100644 astara_router/api/v1/base.py delete mode 100644 astara_router/api/v1/firewall.py delete mode 100644 astara_router/api/v1/status.py delete mode 100644 astara_router/api/v1/system.py delete mode 100644 astara_router/commands/__init__.py delete mode 100644 astara_router/commands/management.py delete mode 100644 astara_router/debug.py delete mode 100644 astara_router/defaults.py delete mode 100644 astara_router/drivers/__init__.py delete mode 100644 astara_router/drivers/arp.py delete mode 100644 astara_router/drivers/base.py delete mode 100644 astara_router/drivers/bird.py delete mode 100644 astara_router/drivers/conntrackd.conf.template delete mode 100644 astara_router/drivers/conntrackd.py delete mode 100644 astara_router/drivers/dnsmasq.py delete mode 100644 astara_router/drivers/hostname.py delete mode 100644 astara_router/drivers/ip.py delete mode 100644 astara_router/drivers/iptables.py delete mode 100644 astara_router/drivers/keepalived.conf.template delete mode 100644 astara_router/drivers/keepalived.py delete mode 100644 astara_router/drivers/loadbalancer/__init__.py delete mode 100644 astara_router/drivers/loadbalancer/nginx.conf.template delete mode 100644 astara_router/drivers/loadbalancer/nginx.py delete mode 100644 astara_router/drivers/metadata.py delete mode 100644 astara_router/drivers/ping.py delete mode 100644 astara_router/drivers/vpn/__init__.py delete mode 100644 astara_router/drivers/vpn/ipsec.py delete mode 100644 astara_router/drivers/vpn/templates/README delete mode 100644 astara_router/drivers/vpn/templates/ipsec.conf.j2 delete mode 100644 astara_router/drivers/vpn/templates/ipsec.secrets.j2 delete mode 100644 astara_router/manager.py delete mode 100644 astara_router/metadata_proxy.py delete mode 100644 astara_router/models.py delete mode 100644 astara_router/settings.py delete mode 100644 astara_router/utils.py delete mode 100644 astara_router/version.py delete mode 100644 diskimage-builder/README.md delete mode 100644 diskimage-builder/elements/ansible/README.md delete mode 100755 diskimage-builder/elements/ansible/cleanup.d/50-remove-source-ansible delete mode 100644 diskimage-builder/elements/ansible/element-deps delete mode 100755 diskimage-builder/elements/ansible/environment.d/10-ansible-venv-dir.bash delete mode 100644 diskimage-builder/elements/ansible/install.d/ansible-package-install/30-ansible-package delete mode 100755 diskimage-builder/elements/ansible/install.d/ansible-source-install/30-ansible-source delete mode 100644 diskimage-builder/elements/astara/README.rst delete mode 100644 diskimage-builder/elements/astara/element-deps delete mode 100755 diskimage-builder/elements/astara/environment.d/10-cloudinit-akanda delete mode 100755 diskimage-builder/elements/astara/install.d/astara-source-install/70-astara delete mode 100755 diskimage-builder/elements/astara/post-install.d/90-fix-locale delete mode 100644 diskimage-builder/elements/astara/source-repository-astara delete mode 100644 diskimage-builder/elements/debug-user/README.md delete mode 100755 diskimage-builder/elements/debug-user/install.d/50-debug-user delete mode 100644 diskimage-builder/elements/debug-user/package-installs.yaml delete mode 100644 diskimage-builder/elements/nginx-plus/README.rst delete mode 100644 diskimage-builder/elements/nginx-plus/element-deps delete mode 100755 diskimage-builder/elements/nginx-plus/extra-data.d/00-copy-in-keys delete mode 100644 diskimage-builder/elements/nginx-plus/install.d/package-installs-nginx-plus delete mode 100755 diskimage-builder/elements/nginx-plus/post-install.d/99-cleanup delete mode 100755 diskimage-builder/elements/nginx-plus/pre-install.d/02-nginx-plus-prep delete mode 100644 diskimage-builder/elements/nginx/README.rst delete mode 100644 diskimage-builder/elements/nginx/element-deps delete mode 100644 diskimage-builder/elements/nginx/install.d/package-installs-nginx delete mode 100755 diskimage-builder/elements/nginx/post-install.d/99-set-nginx-sites-enabled delete mode 100644 etc/rootwrap.conf delete mode 100644 etc/rootwrap.d/network.filters delete mode 100644 releasenotes/notes/.placeholder delete mode 100644 releasenotes/notes/conntrackd-c179372033f4134e.yaml delete mode 100644 releasenotes/notes/ensure_interface_cache_refreshed-57ce5b0637282b60.yaml delete mode 100644 releasenotes/notes/initial-note-fd05752e5f3b87fb.yaml delete mode 100644 releasenotes/notes/keepalived_manager-4ed9f900f3d3429b.yaml delete mode 100644 releasenotes/source/_static/.placeholder delete mode 100644 releasenotes/source/_templates/.placeholder delete mode 100644 releasenotes/source/conf.py delete mode 100644 releasenotes/source/index.rst delete mode 100644 releasenotes/source/mitaka.rst delete mode 100644 requirements.txt delete mode 100755 scripts/build_dib_appliance.sh delete mode 100644 scripts/debug-config.py delete mode 100755 scripts/etc/init.d/astara-router-api-server delete mode 100755 scripts/etc/init.d/bird6 delete mode 100755 scripts/etc/init.d/metadata delete mode 100755 scripts/etc/init.d/ssh delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 test-requirements.txt delete mode 100644 test/__init__.py delete mode 100644 test/unit/__init__.py delete mode 100644 test/unit/api/__init__.py delete mode 100644 test/unit/api/v1/__init__.py delete mode 100644 test/unit/api/v1/fakes.py delete mode 100644 test/unit/api/v1/payloads/__init__.py delete mode 100644 test/unit/api/v1/test_base.py delete mode 100644 test/unit/api/v1/test_firewall.py delete mode 100644 test/unit/api/v1/test_system.py delete mode 100644 test/unit/drivers/__init__.py delete mode 100644 test/unit/drivers/test_arp.py delete mode 100644 test/unit/drivers/test_bird.py delete mode 100644 test/unit/drivers/test_conntrackd.py delete mode 100644 test/unit/drivers/test_dnsmasq.py delete mode 100644 test/unit/drivers/test_hostname.py delete mode 100644 test/unit/drivers/test_ip.py delete mode 100644 test/unit/drivers/test_iptables.py delete mode 100644 test/unit/drivers/test_keepalived.py delete mode 100644 test/unit/drivers/test_metadata.py delete mode 100644 test/unit/drivers/test_route.py delete mode 100644 test/unit/drivers/test_strongswan.py delete mode 100644 test/unit/fakes.py delete mode 100644 test/unit/test_metadata_proxy.py delete mode 100644 test/unit/test_models.py delete mode 100644 test/unit/test_utils.py delete mode 100644 tox.ini diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b7b1c2e..0000000 --- a/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -.coverage -*.py[co] -*.sw[po] -*.pyc -*.egg-info -.venv -dropin.cache -.tox -build -dist -fpm.log* -packages/ -.DS_Store -._.DS_Store - -# pbr output -AUTHORS -ChangeLog diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 60c90c9..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: python -python: - - "2.7" -install: - - pip install -r test_requirements.txt --use-mirror - - pip install flake8 --use-mirrors - - pip install -q . --use-mirrors -before_script: - - flake8 setup.py akanda -script: nosetests -d \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 67db858..0000000 --- a/LICENSE +++ /dev/null @@ -1,175 +0,0 @@ - - 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.md b/README.md deleted file mode 100644 index 4713355..0000000 --- a/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Astara Appliance - -*Part of the [Astara Project](https://github.com/openstack/astara).* - -A Linux-based L3 software router. Includes a REST API to monitor, configure, -and manage the router. - -Astara routers are recommended to run with 512 MB of RAM and a single vCPU, and -are intended to run within an virtualized L2 overlay to provide complete network -virtualization. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..d98af48 --- /dev/null +++ b/README.rst @@ -0,0 +1,10 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +For any further questions, please email +openstack-dev@lists.openstack.org or join #openstack-dev on +Freenode. diff --git a/ansible/main.yml b/ansible/main.yml deleted file mode 100644 index c75ba96..0000000 --- a/ansible/main.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -- hosts: all - sudo: True - - vars: - bird_enable: False - bird6_enable: True - bird_enable_service: True - strongswan_enable: True - strongswan_enable_service: False - dnsmasq_conf_dir: /etc/dnsmasq.d - dnsmasq_conf_file: /etc/dnsmasq.conf - install_extras: False - do_cleanup: True - router_appliance: True - update_kernel: True - enabled_advanced_services: "router" - tasks: - - include: tasks/debian_backports.yml - when: ansible_distribution == "Debian" and ansible_distribution_release == "wheezy" - - include: tasks/update_kernel.yml - when: update_kernel and ansible_distribution_release == "wheezy" - - include: tasks/base.yml - - include: tasks/astara.yml - - include: tasks/bird.yml - - include: tasks/conntrackd.yml - - include: tasks/strongswan.yml - - include: tasks/dnsmasq.yml - - include: tasks/extras.yml - when: install_extras diff --git a/ansible/tasks/astara.yml b/ansible/tasks/astara.yml deleted file mode 100644 index a0c179d..0000000 --- a/ansible/tasks/astara.yml +++ /dev/null @@ -1,97 +0,0 @@ ---- - -- name: install base packages - apt: name={{item}} state=installed install_recommends=no - with_items: - - gunicorn - - python-pip - - python-dev - - logrotate - -- name: copy astara-appliance code - synchronize: src={{ playbook_dir }}/.. dest=/tmp/astara-appliance - -- name: ensure latest setuptools - pip: name=setuptools state=latest - -- name: install required files - pip: requirements=/tmp/astara-appliance/requirements.txt - -- name: install astara-appliance - command: python setup.py install chdir=/tmp/astara-appliance - -- name: install astara gunicorn logging directory - file: path=/var/log/astara state=directory - -- name: install astara logrotate config - template: src=logrotate.j2 dest=/etc/logrotate.d/astara - -- name: install gunicorn config file - template: src=gunicorn.j2 dest=/etc/astara_gunicorn_config.py - - -- name: add gunicorn user - action: user name=gunicorn state=present - -- name: install init.d files - copy: src={{playbook_dir}}/../scripts/etc/init.d/{{item}} dest=/etc/init.d/{{item}} mode=0555 - with_items: - - metadata - - astara-router-api-server - -- name: install rootwrap config file - copy: src={{playbook_dir}}/../etc/rootwrap.conf dest=/etc/rootwrap.conf mode=0555 - -- name: create /etc/rootwrap.d/ - file: path=/etc/rootwrap.d/ state=directory - -- name: install rootwrap rules file - copy: src={{playbook_dir}}/../etc/rootwrap.d/ dest=/etc/rootwrap.d/ mode=0555 - with_items: - - network.filters - -- stat: path=/etc/sudoers.d - register: sudoers_dir - -- name: install sudoer file - when: sudoers_dir.stat.exists == True - template: src=gunicorn dest=/etc/sudoers.d/gunicorn mode=0440 - -- name: create /usr/local/share/astara/ - file: path=/usr/local/share/astara state=directory - -- name: make /usr/local/share/astara/ importable - copy: dest=/usr/local/share/astara/__init__.py content='' - -- name: install astara_local_settings.py - copy: dest=/usr/local/share/astara/astara_local_settings.py content='ENABLED_SERVICES = {{enabled_advanced_services.split(',')}}\n' - -- name: update-rc - command: update-rc.d astara-router-api-server start - -- name: add timestamp - shell: date > arg1 creates=/etc/astara-release - -- name: enable forwarding - sysctl: name={{item}} value=1 sysctl_set=yes state=present reload=yes - with_items: - - net.ipv4.ip_forward - - net.ipv6.conf.all.forwarding - when: router_appliance - -- name: remove packages only needed for build - apt: name={{item}} state=absent - with_items: - - python-pip - - python-dev - - build-essential - when: do_cleanup - -- name: Autoremove unused packages - command: apt-get -y autoremove - when: do_cleanup - -- name: Ensure gunicorn is restarted - service: name=astara-router-api-server state=restarted enabled=yes - - diff --git a/ansible/tasks/base.yml b/ansible/tasks/base.yml deleted file mode 100644 index 1ff1377..0000000 --- a/ansible/tasks/base.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- - -- name: install base packages - apt: name={{item}} state=installed install_recommends=no - with_items: - - wget - - iptables - - iptables-persistent - - iputils-ping - - conntrack - - ntp - - tcpdump - - vim - - keepalived - - conntrackd - -- name: latest bash (CVE-2014-6271) - apt: name=bash state=latest install_recommends=no - -- name: remove timezone - command: rm -f arg1 removes=/etc/localtime - -- name: set timezone to UTC - command: ln -s /usr/share/zoneinfo/UTC arg1 creates=/etc/localtime - -- name: setting hostname - copy: content="astara-linux" dest=/etc/hostname - -- name: set default nameserver - copy: content="nameserver 8.8.8.8" dest=/etc/resolv.conf - -- name: vanity motd - template: src=motd.j2 dest=/etc/motd - -- name: disable fsck on boot via fastboot - file: path=/fastboot state=touch - -- name: reset v4 persistent table rules - template: src=rules_v4.j2 dest=/etc/iptables/rules.v4 - -- name: reset v6 persistent table rules - template: src=rules_v6.j2 dest=/etc/iptables/rules.v6 - -- name: clear out network interfaces.d - shell: rm -f /etc/network/interfaces.d/* - -- name: reset network interfaces - file: content="auto lo\niface lo inet loopback" dest=/etc/network/interfaces diff --git a/ansible/tasks/bird.yml b/ansible/tasks/bird.yml deleted file mode 100644 index a9258f6..0000000 --- a/ansible/tasks/bird.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- - -- name: install bird - apt: name=bird state=installed install_recommends=no - when: bird_enable - -- name: install bird6 - apt: name=bird6 state=installed install_recommends=no - when: bird6_enable - -# Debian version does not support status ensure that it exists -- name: ensure bird status works in init.d - replace: dest=/etc/init.d/bird regexp='(\;\;\s*)\n(\s*reload\|)' replace='\1\n status)\n status_of_proc $DAEMON $NAME && exit 0 || exit $?\n ;;\n\2' - when: bird_enable - -- name: ensure bird6 status works in init.d - replace: dest=/etc/init.d/bird6 regexp='(\;\;\s*)\n(\s*reload\|)' replace='\1\n status)\n status_of_proc $DAEMON $NAME && exit 0 || exit $?\n ;;\n\2' - when: bird6_enable - -- name: Ensure bird is started - service: name=bird state=started enabled=yes - when: bird_enable and bird_enable_service - -- name: Ensure bird6 is started - service: name=bird6 state=started enabled=yes - when: bird6_enable and bird_enable_service diff --git a/ansible/tasks/conntrackd.yml b/ansible/tasks/conntrackd.yml deleted file mode 100644 index 4ed6e52..0000000 --- a/ansible/tasks/conntrackd.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- - -- name: install conntrackd - apt: name=conntrackd state=installed install_recommends=no - -- name: install conntrackd notify script to /etc/conntrackd - copy: src=/usr/share/doc/conntrackd/examples/sync/primary-backup.sh dest=/etc/conntrackd/primary-backup.sh mode=0755 diff --git a/ansible/tasks/debian_backports.yml b/ansible/tasks/debian_backports.yml deleted file mode 100644 index 7ee7ff9..0000000 --- a/ansible/tasks/debian_backports.yml +++ /dev/null @@ -1,5 +0,0 @@ -- name: Install Wheezy Backports and update - apt_repository: repo="deb http://http.debian.net/debian wheezy-backports main" - -- name: Update Cache - apt: update_cache=yes cache_valid_time=3600 diff --git a/ansible/tasks/dnsmasq.yml b/ansible/tasks/dnsmasq.yml deleted file mode 100644 index 68ed57c..0000000 --- a/ansible/tasks/dnsmasq.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -- name: install dnsmasq (Debian) - apt: name=dnsmasq state=installed install_recommends=no - -- name: Create config directory - file: path={{dnsmasq_conf_dir}} state=directory mode=0755 - -- name: Generate Config - template: src=dnsmasq.conf.j2 dest={{dnsmasq_conf_file}} - -- name: Ensure dnsmasq is started - service: name=dnsmasq state=started enabled=yes - diff --git a/ansible/tasks/extras.yml b/ansible/tasks/extras.yml deleted file mode 100644 index e6b87c4..0000000 --- a/ansible/tasks/extras.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- - -- name: install extras - apt: name={{item}} state=installed install_recommends=no - with_items: - - mtr - - tshark diff --git a/ansible/tasks/strongswan.yml b/ansible/tasks/strongswan.yml deleted file mode 100644 index ead5d1b..0000000 --- a/ansible/tasks/strongswan.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- - -- name: install strongswan - apt: name=strongswan state=installed install_recommends=yes - when: strongswan_enable - -- name: Ensure strongswan is started - service: name=strongswan state=started enabled=yes - when: strongswan_enable and strongswan_enable_service diff --git a/ansible/tasks/update_kernel.yml b/ansible/tasks/update_kernel.yml deleted file mode 100644 index 83492ea..0000000 --- a/ansible/tasks/update_kernel.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- - -- stat: path=/boot/grub - register: grub_dir - -- stat: path=/boot - register: boot_dir - -- name: install kernel (Debian) - apt: name=linux-image-amd64 state=latest install_recommends=no default_release=wheezy-backports - -- name: update grub conf - when: grub_dir.stat.exists == True - template: src=default_grub dest=/etc/default/grub - -- stat: path=/boot - register: boot_dir_after - -- name: update-grub - when: boot_dir_after.stat.mtime > boot_dir.stat.mtime and grub_dir.stat.exists == True - command: update-grub diff --git a/ansible/templates/default_grub b/ansible/templates/default_grub deleted file mode 100644 index f1f7f4e..0000000 --- a/ansible/templates/default_grub +++ /dev/null @@ -1,9 +0,0 @@ -# If you change this file, run 'update-grub' afterwards to update -# /boot/grub/grub.cfg. - -GRUB_DEFAULT=0 -GRUB_TIMEOUT=0 -GRUB_DISTRIBUTOR=Debian -GRUB_CMDLINE_LINUX_DEFAULT="console=tty1 console=ttyS0,115200n8" -# Disable GSO (Generic Segmentation Offload) in order to improve IPv6 forwarding performance -GRUB_CMDLINE_LINUX="debian-installer=en_US virtio_net.gso=0" diff --git a/ansible/templates/dnsmasq.conf.j2 b/ansible/templates/dnsmasq.conf.j2 deleted file mode 100644 index 239feea..0000000 --- a/ansible/templates/dnsmasq.conf.j2 +++ /dev/null @@ -1,9 +0,0 @@ -bind-interfaces -leasefile-ro -domain-needed -bogus-priv -no-hosts -no-poll -strict-order -dhcp-lease-max=256 -conf-dir={{dnsmasq_conf_dir}} diff --git a/ansible/templates/gunicorn b/ansible/templates/gunicorn deleted file mode 100644 index 54f6c86..0000000 --- a/ansible/templates/gunicorn +++ /dev/null @@ -1 +0,0 @@ -gunicorn ALL = (root) NOPASSWD: /usr/local/bin/astara-rootwrap /etc/rootwrap.conf * diff --git a/ansible/templates/gunicorn.j2 b/ansible/templates/gunicorn.j2 deleted file mode 100644 index ac18db4..0000000 --- a/ansible/templates/gunicorn.j2 +++ /dev/null @@ -1,12 +0,0 @@ -import multiprocessing - -bind = '[::]:5000' -workers = workers = multiprocessing.cpu_count() * 2 + 1 -timeout = 300 -backlog = 2048 -worker_class ="sync" -debug = False -daemon = True -pidfile = "/var/run/gunicorn.pid" -errorlog = "/var/log/astara/gunicorn_error.log" -accesslog = "/var/log/astara/gunicorn_access.log" diff --git a/ansible/templates/logrotate.j2 b/ansible/templates/logrotate.j2 deleted file mode 100644 index f26a6cc..0000000 --- a/ansible/templates/logrotate.j2 +++ /dev/null @@ -1,10 +0,0 @@ -/var/log/astara/*.log { - weekly - rotate 7 - compress - missingok - create 644 root root - postrotate - kill -USR1 `cat /var/run/gunicorn.pid` - endscript -} diff --git a/ansible/templates/motd.j2 b/ansible/templates/motd.j2 deleted file mode 100644 index 20e3fc5..0000000 --- a/ansible/templates/motd.j2 +++ /dev/null @@ -1,8 +0,0 @@ - ___ _ - / _ \ | | L3 for OpenStack -/ /_\ \___| |_ __ _ _ __ __ _ -| _ / __| __/ _` | '__/ _` | -| | | \__ \ || (_| | | | (_| | -\_| |_/___/\__\__,_|_| \__,_| - -Welcome to Astara: Powered by Unicorns. diff --git a/ansible/templates/rules_v4.j2 b/ansible/templates/rules_v4.j2 deleted file mode 100644 index a6ddb67..0000000 --- a/ansible/templates/rules_v4.j2 +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by iptables-save v1.4.14 on Sun Apr 12 20:07:15 2015 -*filter -:INPUT ACCEPT [0:0] -:FORWARD ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] -COMMIT -# Completed on Sun Apr 12 20:07:15 2015 -# Generated by iptables-save v1.4.14 on Sun Apr 12 20:07:15 2015 -*mangle -:PREROUTING ACCEPT [0:0] -:INPUT ACCEPT [0:0] -:FORWARD ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] -:POSTROUTING ACCEPT [0:0] -COMMIT -# Completed on Sun Apr 12 20:07:15 2015 -# Generated by iptables-save v1.4.14 on Sun Apr 12 20:07:15 2015 -*nat -:PREROUTING ACCEPT [0:0] -:INPUT ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] -:POSTROUTING ACCEPT [0:0] -COMMIT -# Completed on Sun Apr 12 20:07:15 2015 - diff --git a/ansible/templates/rules_v6.j2 b/ansible/templates/rules_v6.j2 deleted file mode 100644 index 9a89637..0000000 --- a/ansible/templates/rules_v6.j2 +++ /dev/null @@ -1,7 +0,0 @@ -# Generated by ip6tables-save v1.4.14 on Sun Apr 12 20:14:31 2015 -*filter -:INPUT ACCEPT [0:0] -:FORWARD ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] -COMMIT -# Completed on Sun Apr 12 20:14:31 2015 diff --git a/astara_router/__init__.py b/astara_router/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/astara_router/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/astara_router/api/__init__.py b/astara_router/api/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/astara_router/api/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/astara_router/api/server.py b/astara_router/api/server.py deleted file mode 100644 index cbc001c..0000000 --- a/astara_router/api/server.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. - - -"""Set up the API server application instance -""" -import flask - -from astara_router.api import v1 -from astara_router.debug import handle_traceback -from astara_router.manager import manager - -app = flask.Flask(__name__) -app.register_blueprint(v1.base.blueprint) -app.register_blueprint(v1.system.blueprint) -app.register_blueprint(v1.firewall.blueprint) -app.register_blueprint(v1.status.blueprint) -app.register_error_handler(500, handle_traceback) - - -@app.before_request -def attach_config(): - ''' - Attach any configuration before instantiating API - ''' - pass - - -def main(): - # TODO(mark): make this use a config file ie - # app.config.from_object('astara_router.config.Default') - # manager.state_path = app.config['STATE_PATH'] - - addr = str(manager.ip_mgr.get_interfaces()[0].addresses[0]) - app.run(host=addr, - port=5000) diff --git a/astara_router/api/v1/__init__.py b/astara_router/api/v1/__init__.py deleted file mode 100644 index d63ce93..0000000 --- a/astara_router/api/v1/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. - - -# flake8: noqa -import base -import firewall -import system -import status diff --git a/astara_router/api/v1/base.py b/astara_router/api/v1/base.py deleted file mode 100644 index 3c10b13..0000000 --- a/astara_router/api/v1/base.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. - - -""" -Blueprint for the "base" portion of the version 1 of the API. -""" -from astara_router import utils - - -blueprint = utils.blueprint_factory(__name__) - - -@blueprint.route('/') -def welcome(): - ''' - Show welcome message - ''' - return 'Astara appliance API service is active' diff --git a/astara_router/api/v1/firewall.py b/astara_router/api/v1/firewall.py deleted file mode 100644 index 21ab03e..0000000 --- a/astara_router/api/v1/firewall.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. - - -""" -Blueprint for version 1 of the firewall API. -""" - -from flask import request - -from astara_router import utils -from astara_router.drivers import iptables - - -blueprint = utils.blueprint_factory(__name__) - - -@blueprint.before_request -def get_manager(): - request.iptables_mgr = iptables.IPTablesManager() - - -@blueprint.route('/rules') -def get_rules(): - ''' - Show loaded firewall rules by iptables - ''' - return request.iptables_mgr.get_rules() diff --git a/astara_router/api/v1/status.py b/astara_router/api/v1/status.py deleted file mode 100644 index 3dfe3bb..0000000 --- a/astara_router/api/v1/status.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. - - -""" -Blueprint for the "status" portion of the version 1 of the API. -""" -from flask import request - -from astara_router import utils -from astara_router.drivers import ping - -blueprint = utils.blueprint_factory(__name__) - - -@blueprint.before_request -def get_manager(): - request.ping_mgr = ping.PingManager() - - -@blueprint.route('/') -@utils.json_response -def status(): - """ Return router healt status """ - - retval = {} - # Attempt to reach public Google DNS as an ext network test - retval['v4'] = request.ping_mgr.do('8.8.8.8') - retval['v6'] = request.ping_mgr.do('2001:4860:4860::8888') - return retval diff --git a/astara_router/api/v1/system.py b/astara_router/api/v1/system.py deleted file mode 100644 index 8c6ed91..0000000 --- a/astara_router/api/v1/system.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. - - -""" -Blueprint for the "system" portion of the version 1 of the API. -""" -from flask import Response -from flask import abort, request -from dogpile.cache import make_region - -from astara_router import models -from astara_router import utils -from astara_router import settings -from astara_router.manager import manager - -blueprint = utils.blueprint_factory(__name__) - -# Managed by _get_cache() -_cache = None - - -ADVANCED_SERVICES_KEY = 'services' - - -def _get_cache(): - global _cache - if _cache is None: - _cache = make_region().configure( - 'dogpile.cache.dbm', - arguments={ - "filename": "/tmp/astara-state" - } - ) - return _cache - - -@blueprint.route('/interface/') -@utils.json_response -def get_interface(ifname): - ''' - Show interface parameters given an interface name. - For example ge1, ge2 for generic ethernet - ''' - return dict(interface=manager.router.get_interface(ifname)) - - -@blueprint.route('/interfaces') -@utils.json_response -def get_interfaces(): - ''' - Show all interfaces and parameters - ''' - return dict(interfaces=manager.router.get_interfaces()) - - -@blueprint.route('/config', methods=['GET']) -@utils.json_response -def get_configuration(): - """Return the current router configuration.""" - return dict(configuration=manager.config) - - -@blueprint.route('/config', methods=['PUT']) -@utils.json_response -def put_configuration(): - if request.content_type != 'application/json': - abort(415) - - try: - system_config_candidate = models.SystemConfiguration(request.json) - except ValueError, e: - return Response( - 'The system config failed to deserialize.\n' + str(e), - status=422) - - errors = system_config_candidate.validate() - if errors: - return Response( - 'The config failed to validate.\n' + '\n'.join(errors), - status=422) - - # Config requests to a router appliance will always contain a default ASN, - # so we can key on that for now. Later on we need to move router stuff - # to the extensible list of things the appliance can handle - if request.json.get('asn'): - try: - router_config_candidate = models.RouterConfiguration(request.json) - except ValueError, e: - return Response( - 'The router config failed to deserialize.\n' + str(e), - status=422) - - errors = router_config_candidate.validate() - if errors: - return Response( - 'The config failed to validate.\n' + '\n'.join(errors), - status=422) - else: - router_config_candidate = None - - if router_config_candidate: - advanced_service_configs = [router_config_candidate] - else: - advanced_service_configs = [] - - advanced_services = request.json.get(ADVANCED_SERVICES_KEY, {}) - for svc in advanced_services.keys(): - if svc not in settings.ENABLED_SERVICES: - return Response( - 'This appliance cannot service requested advanced ' - 'service: %s' % svc, status=400) - - for svc in settings.ENABLED_SERVICES: - if not advanced_services.get(svc): - continue - - config_model = models.get_config_model(service=svc) - if not config_model: - continue - - try: - svc_config_candidate = config_model(advanced_services.get(svc)) - except ValueError, e: - return Response( - 'The %s config failed to deserialize.\n' + str(e) % - config_model.service_name, status=422) - - errors = svc_config_candidate.validate() - if errors: - return Response( - 'The %s config failed to validate.\n' + '\n'.join(errors), - config_model.service_name, status=422) - - advanced_service_configs.append(svc_config_candidate) - - manager.update_config( - system_config=system_config_candidate, - service_configs=advanced_service_configs, - cache=_get_cache()) - - return dict(configuration=manager.config) diff --git a/astara_router/commands/__init__.py b/astara_router/commands/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/astara_router/commands/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/astara_router/commands/management.py b/astara_router/commands/management.py deleted file mode 100644 index 074bc98..0000000 --- a/astara_router/commands/management.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# Copyright 2015 Akanda, 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 argparse -import re -import sys - -import netaddr - -from astara_router import settings -from astara_router import utils -from astara_router.drivers import ip - - -def configure_ssh(listen_ip): - """ - """ - config = open('/etc/ssh/sshd_config', 'r').read() - config = re.sub( - '(^|\n)(#)?(ListenAddress|AddressFamily|UseDNS) .*', - '', - config - ) - - config += '\n'.join([ - '', # make sure we have a blank line at the end before adding more - 'AddressFamily inet%s' % ('6' if listen_ip.version == 6 else ''), - 'ListenAddress ' + str(listen_ip), - 'UseDNS no' - ]) - try: - open('/etc/ssh/sshd_config', 'w+').write(config) - sys.stderr.write('sshd configured to listen on %s\n' % listen_ip) - except: - sys.stderr.write('Unable to write sshd configuration file.') - - -def configure_gunicorn(listen_ip): - """ - """ - if listen_ip.version == 6: - bind = "'[%s]:%d'" % (listen_ip, settings.API_SERVICE) - else: - bind = "'%s:%d'" % (listen_ip, settings.API_SERVICE) - - config = open('/etc/astara_gunicorn_config.py', 'r').read() - config = re.sub('\nbind(\s)?\=(\s)?.*', '\nbind = %s' % bind, config) - - try: - open('/etc/astara_gunicorn_config.py', 'w+').write(config) - sys.stderr.write('http configured to listen on %s\n' % listen_ip) - except: - sys.stderr.write('Unable to write gunicorn configuration file.') - - -def configure_management(): - parser = argparse.ArgumentParser( - description='Configure Management Interface' - ) - parser.add_argument('mac_address', metavar='lladdr', type=str) - parser.add_argument('ip_address', metavar='ipaddr', type=str) - parser.add_argument('--mtu', metavar='mtu', type=int, default=1280) - args = parser.parse_args() - - ip_addr = netaddr.IPNetwork(args.ip_address) - - mgr = ip.IPManager() - - for intf in mgr.get_interfaces(): - if args.mac_address == intf.lladdr: - if not intf.is_up: - mgr.up(intf) - - intf.mtu = args.mtu - - if ip_addr not in intf.addresses: - if ip_addr.version == 6: - real_ifname = mgr.generic_to_host(intf.ifname) - utils.execute([ - 'sysctl', - '-w', - 'net.ipv6.conf.%s.accept_dad=0' % real_ifname - ]) - - intf.addresses.append(ip_addr) - mgr.update_interface(intf) - configure_ssh(ip_addr.ip) - configure_gunicorn(ip_addr.ip) - break diff --git a/astara_router/debug.py b/astara_router/debug.py deleted file mode 100644 index cb272b6..0000000 --- a/astara_router/debug.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 traceback import print_exc -from pprint import pformat -import cStringIO - -from flask import request -from werkzeug import Response - - -def handle_traceback(exc): - out = cStringIO.StringIO() - print_exc(file=out) - formatted_environ = pformat(request.environ) - response = Response( - '%s\n%s\n' % (out.getvalue(), formatted_environ), - status=500 - ) - return response diff --git a/astara_router/defaults.py b/astara_router/defaults.py deleted file mode 100644 index 6750100..0000000 --- a/astara_router/defaults.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 re - - -SSH = 22 -SMTP = 25 -DNS = 53 -HTTP = 80 -BGP = 179 -HTTPS = 443 -HTTP_ALT = 8080 -API_SERVICE = 5000 - -DHCP = 67 -DHCPV6 = 546 - -ISAKMP = 500 -IPSEC_NAT_T = 4500 - -NFS_DEVELOPMENT = [111, 1110, 2049, 4045] - -MANAGEMENT_PORTS = [SSH, API_SERVICE] # + NFS_DEVELOPMENT - -# destination address for AWS compliant metadata guests -METADATA_DEST_ADDRESS = '169.254.169.254' - -# port for internal network metadata proxy -BASE_METADATA_PORT = 9600 - -# default address of orchestrator metadata service -ORCHESTRATOR_METADATA_ADDRESS = 'fdca:3ba5:a17a:acda::1' - -# default port for orchestrator metadata service -ORCHESTRATOR_METADATA_PORT = 9697 - - -def internal_metadata_port(ifname): - return BASE_METADATA_PORT + int(re.sub('[a-zA-Z]', '', ifname)) - -# Configures which advanced service drivers are loaded by this -# instance of the appliance. -ENABLED_SERVICES = ['router'] diff --git a/astara_router/drivers/__init__.py b/astara_router/drivers/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/astara_router/drivers/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/astara_router/drivers/arp.py b/astara_router/drivers/arp.py deleted file mode 100644 index 7b9f0b9..0000000 --- a/astara_router/drivers/arp.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 argparse -import re -import socket -import struct - -from astara_router import utils -from astara_router.drivers import base -from astara_router.models import Network - - -def send_gratuitous_arp(): - parser = argparse.ArgumentParser( - description='Send a gratuitous ARP' - ) - parser.add_argument('ifname', metavar='ifname', type=str) - parser.add_argument('address', metavar='address', type=str) - args = parser.parse_args() - - return _send_gratuitous_arp(args.ifname, args.address) - - -def _send_gratuitous_arp(ifname, address): - """ - Send a gratuitous ARP reply. Generally used when Floating IPs are - associated. - :type ifname: str - :param ifname: The real name of the interface to send an ARP on - :type address: str - :param address: The source IPv4 address - """ - HTYPE_ARP = 0x0806 - PTYPE_IPV4 = 0x0800 - - # Bind to the socket - sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW) - sock.bind((ifname, HTYPE_ARP)) - hwaddr = sock.getsockname()[4] - - # Build a gratuitous ARP packet - gratuitous_arp = [ - struct.pack("!h", 1), # HTYPE Ethernet - struct.pack("!h", PTYPE_IPV4), # PTYPE IPv4 - struct.pack("!B", 6), # HADDR length, 6 for IEEE 802 MAC addresses - struct.pack("!B", 4), # PADDR length, 4 for IPv4 - struct.pack("!h", 2), # OPER, 2 = ARP Reply - - # Sender's hardware and protocol address are duplicated in the - # target fields - - hwaddr, # Sender MAC - socket.inet_aton(address), # Sender IP address - hwaddr, # Target MAC - socket.inet_aton(address) # Target IP address - ] - frame = [ - '\xff\xff\xff\xff\xff\xff', # Broadcast destination - hwaddr, # Source address - struct.pack("!h", HTYPE_ARP), - ''.join(gratuitous_arp) - ] - sock.send(''.join(frame)) - sock.close() - - -class ARPManager(base.Manager): - """ - A class to interact with entries in the ARP cache. Currently only really - provides support for deleting stuff from the cache. - """ - EXECUTABLE = 'arp' - - def send_gratuitous_arp_for_floating_ips(self, config, generic_to_host): - """ - Send a gratuitous ARP for every Floating IP. - :type config: astara_router.models.Configuration - :param config: An astara_router.models.Configuration object containing - configuration information for the system's network - setup. - :type generic_to_host: callable - :param generic_to_host: A callable which translates a generic interface - name (e.g., "ge0") to a physical name (e.g., - "eth0") - """ - external_nets = filter( - lambda n: n.network_type == Network.TYPE_EXTERNAL, - config.networks - ) - for net in external_nets: - for fip in net.floating_ips: - utils.execute([ - 'astara-gratuitous-arp', - generic_to_host(net.interface.ifname), - str(fip.floating_ip) - ], self.root_helper) - - def remove_stale_entries(self, config): - """ - A wrapper function that iterates over the networks in and - removes arp entries that no longer have any networks associated with - them. This function calls _delete_from_arp_cache to do the actual - deletion and makes calls to _mac_address_for_ip to match arp entries - to network interface IPs. - - :type config: astara_router.models.Configuration - :param config: An astara_router.models.Configuration object containing - configuration information for the system's network - setup. - """ - for network in config.networks: - for a in network.address_allocations: - for ip in a.dhcp_addresses: - address_for_ip = self._mac_address_for_ip(ip) - if address_for_ip and address_for_ip != a.mac_address: - self._delete_from_arp_cache(ip) - - def _mac_address_for_ip(self, ip): - """ - Matches a network's IP address to an arp entry. This is used to - associate arp entries with networks that are configured on the system - and to determine which arp entries are stale through process of - elemination. - - :type ip: str - :param ip: IP address to search for in the ARP table. - """ - cmd_out = self.sudo('-an') - match = re.search(' \(%s\) at ([^\s]+)' % ip, cmd_out) - if match and match.groups(): - return match.group(1) - - def _delete_from_arp_cache(self, ip): - """ - Runs `arp -d ` to delete from the arp cache. - - :type ip: str - :param ip: IP address to search for in the ARP table. - """ - try: - self.sudo('-d', ip) - except: - # We may be attempting to delete from ARP for interfaces which - # are managed by keepalived and do not yet have addresses - pass diff --git a/astara_router/drivers/base.py b/astara_router/drivers/base.py deleted file mode 100644 index 17632a2..0000000 --- a/astara_router/drivers/base.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 astara_router import utils - - -class Manager(object): - """ - A base class that provides access to common functions used in other driver - modules. - """ - - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - """ - Initializes Manager class. provides a facility to specify - how this class accesses escalated privileges. Defaults to 'sudo'. - - :type root_helper: str - :param root_helper: The method used to obtain escalated privileges. - This command will be passed just before the command - passed to self.sudo/do. If root_helper is 'sudo' - then this will look like: `sudo ls`. - """ - self.root_helper = root_helper - - def sudo(self, *args): - """ - Executes command with the specified flags through the - root_helper facility (i.e. escalated privileges). - - :type args: tuple - :param args: A command, and flags, to execute. - :rtype: tuple - """ - return utils.execute([self.EXECUTABLE] + list(args), self.root_helper) - - def do(self, *args): - """ - Executes command with specified flags and without escalated - privileges. - - :type args: tuple - :param args: A command, and flags, to execute. - :rtype: tuple - """ - return utils.execute([self.EXECUTABLE] + list(args)) diff --git a/astara_router/drivers/bird.py b/astara_router/drivers/bird.py deleted file mode 100644 index b88b4db..0000000 --- a/astara_router/drivers/bird.py +++ /dev/null @@ -1,333 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 random -import textwrap - -from astara_router.drivers import base -from astara_router import utils - - -CONF_PATH = '/etc/bird/bird6.conf' -DEFAULT_AREA = 0 - - -class BirdManager(base.Manager): - """ - A class to interact with BIRD, an internet routing protocol daemon. - """ - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - """ - Initializes BirdManager class. - - :type root_helper: string - :param root_helper: System utility used to gain escalate privileges. - """ - super(BirdManager, self).__init__(root_helper) - - def save_config(self, config, if_map): - """ - Writes config file for bird daemon. - - :type config: astara_router.models.Configuration - :param config: - :type if_map: dict - :param if_map: A (dict) mapping of generic to physical hostname, e.g.: - {'ge0': 'eth0', 'ge1': 'eth1'} - """ - config_data = build_config(config, if_map) - - utils.replace_file('/tmp/bird6.conf', config_data) - utils.execute(['mv', '/tmp/bird6.conf', CONF_PATH], self.root_helper) - - def restart(self): - """ - Restart the BIRD daemon using the system provided init scripts. - """ - try: - utils.execute(['service', 'bird6', 'status'], self.root_helper) - except: # pragma no cover - utils.execute(['service', 'bird6', 'start'], self.root_helper) - else: # pragma no cover - utils.execute(['service', 'bird6', 'reload'], self.root_helper) - - -def build_config(config, interface_map): - """ - Generate a configuration file for the BIRD daemon with interface mapping - provided by . - - :type interface_map: dict - :param interface_map: A (dict) mapping of generic to physical hostname: - {'ge0': 'eth0', 'ge1': 'eth1'} - :rtype: str - """ - config_data = [ - _build_global_config(config), - _build_kernel_config(), - _build_device_config(), - _build_static_config(config), - _build_direct_config(config, interface_map), - # _build_ospf_config(config, interface_map), - _build_bgp_config(config, interface_map), - _build_radv_config(config, interface_map), - ] - - return '\n'.join(config_data) - - -def _find_external_v4_ip(config): - """ - Determines the external IPv4 address. - - :type config: astara_router.models.Configuration - :param config: - :rtype: str - """ - v4_id = config.external_v4_id - - if v4_id: - return v4_id - else: # fallback to random value - return '0.0.%d.%d' % (random.randint(0, 255), random.randint(0, 255)) - - -def _build_global_config(config): - """ - Generate the "global" section of the BIRD daemon configuration. - - :type config: astara_router.models.Configuration - :param config: - :rtype: str - """ - retval = [ - 'log syslog {warning, error, info};', - 'router id %s;' % _find_external_v4_ip(config), - ] - return '\n'.join(retval) - - -def _build_kernel_config(): - """ - Generate the "kernel" section of the BIRD daemon configuration. - - :type config: astara_router.models.Configuration - :param config: - :rtype: str - """ - config = """ - protocol kernel { - learn; - scan time 20; - import all; - export all; - }""" - - return textwrap.dedent(config).strip() - - -def _build_device_config(): - """ - Generate the "device" section of the BIRD daemon configuration. - - :type config: astara_router.models.Configuration - :param config: - :rtype: str - """ - return 'protocol device {\n scan time 10;\n}' - - -def _build_static_config(config): - """ - Generate the "static" section of the BIRD daemon configuration. - - :type config: astara_router.models.Configuration - :param config: - :rtype: - """ - retval = [] - # TODO: setup static routes - return '\n'.join(retval).replace('\t', ' ') - - -def _build_direct_config(config, interface_map): - """ - Generate the "direct" section of the BIRD daemon configuration. - - :type config: astara_router.models.Configuration - :param config: - :type interface_map: dict - :param interface_map: - :rtype: - """ - tmpl = "protocol direct {\n interface %s;\n}" - retval = tmpl % ','.join( - '"%s"' % i for i in sorted(interface_map.values()) - ) - return textwrap.dedent(retval) - - -def _build_ospf_config(config, interface_map): - """ - Generate the "ospf" section of the BIRD daemon configuration. - - :type config: astara_router.models.Configuration - :param config: - :type interface_map: dict - :param interface_map: - :rtype: - """ - retval = [ - 'protocol ospf {', - '\texport all;', - '\trfc1583compat yes;', - '\tarea %d {' % DEFAULT_AREA - ] - - for net in config.networks: - ifname = interface_map.get(net.interface.ifname) - if ifname and net.is_internal_network: - modifier = 'stub yes' - elif ifname and net.is_external_network: - modifier = 'type broadcast' - else: - continue - - retval.extend([ - '\t\tinterface "%s" {' % ifname, - '\t\t\tcost 10;', - '\t\t\t%s;' % modifier, - '\t\t};' - ]) - - retval.extend([ - '\t};', - '};' - ]) - return '\n'.join(retval).replace('\t', ' ') - - -def _build_bgp_config(config, interface_map): - """ - Generate the "BGP" section of the BIRD daemon configuration. - - :type config: astara_router.models.Configuration - :param config: - :type interface_map: dict - :param interface_map: - :rtype: - """ - - # build the filter rule - retval = [ - 'filter bgp_out {', - '\tif ! (source = RTS_DEVICE) then reject;', - '\tif net ~ fc00::/7 then reject;', # filter out private addresses - ] - - for net in config.networks: - if not net.is_internal_network: - continue - retval.extend( - '\tif net = %s then accept;' % s.cidr - for s in net.subnets if s.cidr.version == 6 and s.gateway_ip - ) - - retval.extend( - [ - '\telse reject;', - '}', - '' - ] - ) - - # build the bgp rule - for net in config.networks: - ifname = interface_map.get(net.interface.ifname) - - if not net.is_external_network or not ifname: - continue - - v6_subnets = (s for s in net.subnets - if s.cidr.version == 6 and s.gateway_ip) - - for subnet in v6_subnets: - retval.extend([ - 'protocol bgp {', - '\tlocal as %d;' % config.asn, - '\tneighbor %s as %d;' % (subnet.gateway_ip, - config.neighbor_asn), - '\timport all;', - '\texport filter bgp_out;', - '\trr client;', - '}' - ]) - - return '\n'.join(retval).replace('\t', ' ') - - -def _build_radv_config(config, interface_map): - """ - Generate the "radv" section of the BIRD daemon configuration. - - :type config: astara_router.models.Configuration - :param config: - :type interface_map: dict - :param interface_map: - :rtype: - """ - retval = [ - 'protocol radv {', - ] - - for net in config.networks: - if not net.is_tenant_network: - continue - - v6_subnets = [s for s in net.subnets if s.cidr.version == 6] - - if not v6_subnets: - continue - - real_ifname = interface_map.get(net.interface.ifname) - - if not real_ifname: - continue - - retval.extend([ - '\tinterface "%s" {' % real_ifname, - '\t\tmax ra interval 600;', - '\t\trdnss local yes;' - ]) - for subnet in v6_subnets: - retval.append('\t\tprefix %s {' % subnet.cidr) - if subnet.dhcp_enabled: - retval.append('\t\t\tautonomous off;') - retval.append('\t\t};') - - if subnet.dns_nameservers: - retval.append('\t\trdnss {') - retval.append('\t\t\tlifetime mult 10;') - - for ns in subnet.dns_nameservers: - retval.append('\t\t\tns %s;' % ns) - - retval.append('\t\t};') - retval.append('\t};') - - retval.append('}') - return '\n'.join(retval).replace('\t', ' ') diff --git a/astara_router/drivers/conntrackd.conf.template b/astara_router/drivers/conntrackd.conf.template deleted file mode 100644 index 0af321a..0000000 --- a/astara_router/drivers/conntrackd.conf.template +++ /dev/null @@ -1,38 +0,0 @@ -General { - HashSize 8192 - HashLimit 65535 - Syslog on - LockFile /var/lock/conntrackd.lock - UNIX { - Path /var/run/conntrackd.sock - Backlog 20 - } - SocketBufferSize 262142 - SocketBufferSizeMaxGrown 655355 - Filter { - Protocol Accept { - TCP - } - Address Ignore { - IPv4_address 127.0.0.1 - } - } -} -Sync { - Mode FTFW { - } - UDP Default { - {%- if management_ip_version == 4 %} - IPv4_address {{ source_address }} - IPv4_Destination_Address {{ destination_address }} - {%- else %} - IPv6_address {{ source_address }} - IPv6_Destination_Address {{ destination_address }} - {%- endif %} - Port 3780 - Interface {{ interface }} - SndSocketBuffer 24985600 - RcvSocketBuffer 24985600 - Checksum on - } -} diff --git a/astara_router/drivers/conntrackd.py b/astara_router/drivers/conntrackd.py deleted file mode 100644 index b82c282..0000000 --- a/astara_router/drivers/conntrackd.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (c) 2016 Akanda, Inc. All Rights Reserved. -# -# 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 os - -from astara_router.drivers import base -from astara_router import utils - - -class ConntrackdManager(base.Manager): - """ - A class to provide facilities to interact with the conntrackd daemon. - """ - EXECUTABLE = 'service' - CONFIG_FILE_TEMPLATE = os.path.join( - os.path.dirname(__file__), 'conntrackd.conf.template') - - # Debian defaults - CONFIG_FILE = '/etc/conntrackd/conntrackd.conf' - - # Debian installs this to /usr/share/doc/examples/sync but our - # DIB recipe will install it here. - NOTIFY_SCRIPT = '/etc/conntrackd/primary-backup.sh' - - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - """ - Initializes ConntrackdManager class. - - :type root_helper: str - :param root_helper: System utility used to gain escalate privileges. - """ - super(ConntrackdManager, self).__init__(root_helper) - self._config_templ = utils.load_template(self.CONFIG_FILE_TEMPLATE) - self._should_restart = False - - def save_config(self, config, generic_to_host): - """ - Renders template and writes to the conntrackd file - - :type config: astara_router.models.Configuration - :param config: An astara_router.models.Configuration object containing - the ha_config configuration. - :param generic_to_host: A callable used to resolve generic interface - name to system interface name. - """ - - mgt_interface = None - for interface in config.interfaces: - if interface.management: - mgt_interface = interface - break - mgt_addr = mgt_interface.first_v6 or mgt_interface.first_v4 - ctxt = { - 'source_address': str(mgt_addr), - 'management_ip_version': mgt_addr.version, - 'destination_address': config.ha_config['peers'][0], - 'interface': generic_to_host(interface.ifname), - } - - try: - old_config_hash = utils.hash_file(self.CONFIG_FILE) - except IOError: - old_config_hash = None - - utils.replace_file( - '/tmp/conntrackd.conf', - self._config_templ.render(ctxt)) - utils.execute( - ['mv', '/tmp/conntrackd.conf', self.CONFIG_FILE], - self.root_helper) - - if old_config_hash != utils.hash_file(self.CONFIG_FILE): - self._should_restart = True - - def restart(self): - """ - Restarts the conntrackd daemon if config has been changed - """ - if not self._should_restart: - return - self.sudo('conntrackd', 'restart') diff --git a/astara_router/drivers/dnsmasq.py b/astara_router/drivers/dnsmasq.py deleted file mode 100644 index 4d28090..0000000 --- a/astara_router/drivers/dnsmasq.py +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 operator -import os -import time -import itertools - -import netaddr - -from astara_router.drivers import base -from astara_router import utils - - -CONF_DIR = '/etc/dnsmasq.d' -RC_PATH = '/etc/init.d/dnsmasq' -DEFAULT_LEASE = 86400 - - -class DHCPManager(base.Manager): - """A class to manage dnsmasq.""" - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - """ - Initializes DHCPManager class. - - :type root_helper: str - :param root_helper: System utility used to gain escalate privileges. - """ - super(DHCPManager, self).__init__(root_helper) - - def delete_all_config(self): - """ - Deletes all the dnsmasq configuration files (in ) that end in - .conf. - """ - for f in os.listdir(CONF_DIR): - if f.endswith('.conf'): - utils.execute(['rm', '-f', os.path.join(CONF_DIR, f)], - self.root_helper) - - def update_network_dhcp_config(self, ifname, network): - """ - Updates the dnsmasq.conf config, enabling dhcp configuration for nova - networks that are mapped to tenants and disabling networks that do not - map to tenants. - - :type ifname: str - :param ifname: - :type network: - :param network: - - """ - if network.is_tenant_network: - config_data = self._build_dhcp_config(ifname, network) - else: - config_data = self._build_disabled_config(ifname) - - file_path = os.path.join(CONF_DIR, '%s.conf' % ifname) - utils.replace_file('/tmp/dnsmasq.conf', config_data) - utils.execute(['mv', '/tmp/dnsmasq.conf', file_path], self.root_helper) - - def _build_disabled_config(self, ifname): - """ - Appends "except-interface" for . This is used to disable an - interface in the dnsmasq file and should be called from the wrapper - update_network_dhcp_config. - - :type ifname: str - :param ifname: Name of the interface to add an exception to in dnsmasq - configuration. - :rtype: str - """ - return 'except-interface=%s\n' % ifname - - def _build_dhcp_config(self, ifname, network): - """ - Creates containing dnsmasq configuration information for - /. Should be called from wrapper - update_network_dhcp_config. - - :type ifname: str - :param ifname: - :type network: - :param network: - :rtype: dict - """ - config = ['interface=%s' % ifname] - - for index, subnet in enumerate(network.subnets): - if not subnet.dhcp_enabled: - continue - - tag = '%s_%s' % (ifname, index) - - config.append('dhcp-range=set:%s,%s,%s,%ss' % - (tag, - subnet.cidr.network, - 'static', - DEFAULT_LEASE)) - - if subnet.cidr.version == 6: - option_label = 'option6' - else: - option_label = 'option' - - config.extend( - 'dhcp-option=tag:%s,%s:dns-server,%s' % (tag, option_label, s) - for s in subnet.dns_nameservers - ) - - config.extend( - 'dhcp-option=tag:%s,%s:classless-static-route,%s,%s' % - (tag, option_label, r.destination, r.next_hop) - for r in subnet.host_routes - ) - - for a in network.address_allocations: - dhcp_addresses = sorted(map(netaddr.IPAddress, a.dhcp_addresses)) - groups = itertools.groupby(dhcp_addresses, - key=operator.attrgetter('version')) - dhcp_addresses = [str(next(members)) for k, members in groups] - config.extend([ - 'dhcp-host=%s,%s,%s' % ( - a.mac_address, - ','.join( - '[%s]' % ip if ':' in ip else ip - for ip in dhcp_addresses - ), - a.hostname - ) - ]) - - return '\n'.join(config) - - def restart(self): - """ - Restarts dnsmasq service using the system provided init script. - """ - try: - utils.execute(['service', 'dnsmasq', 'stop'], self.root_helper) - except: - pass - - # dnsmasq can get confused on startup - remaining = 5 - while remaining: - remaining -= 1 - try: - utils.execute( - ['service', 'dnsmasq', 'start'], self.root_helper - ) - return - except Exception: - if remaining <= 0: - raise - time.sleep(1) diff --git a/astara_router/drivers/hostname.py b/astara_router/drivers/hostname.py deleted file mode 100644 index b46acab..0000000 --- a/astara_router/drivers/hostname.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 astara_router.drivers import base -from astara_router import utils - - -class HostnameManager(base.Manager): - EXECUTABLE = 'hostname' - - def update(self, config): - self.update_hostname(config) - self.update_hosts(config) - - def update_hostname(self, config): - self.sudo(config.hostname) - utils.replace_file('/tmp/hostname', config.hostname) - utils.execute( - ['mv', '/tmp/hostname', '/etc/hostname'], self.root_helper - ) - - def update_hosts(self, config): - mgt_addr = config.management_address - - if not mgt_addr: - return - - config_data = [ - '127.0.0.1 localhost', - '::1 localhost ip6-localhost ip6-loopback', - '%s %s' % (mgt_addr, config.hostname) - ] - utils.replace_file('/tmp/hosts', '\n'.join(config_data)) - utils.execute(['mv', '/tmp/hosts', '/etc/hosts'], self.root_helper) diff --git a/astara_router/drivers/ip.py b/astara_router/drivers/ip.py deleted file mode 100644 index 7df2e8b..0000000 --- a/astara_router/drivers/ip.py +++ /dev/null @@ -1,611 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 functools -import logging -import re - -import netaddr - -from astara_router import models -from astara_router.drivers import base, keepalived -from astara_router import utils - -LOG = logging.getLogger(__name__) - - -GENERIC_IFNAME = 'ge' -PHYSICAL_INTERFACES = ['lo', 'eth', 'em', 're', 'en', 'vio', 'vtnet'] -ULA_PREFIX = 'fdca:3ba5:a17a:acda::/64' - - -class IPManager(base.Manager): - """ - A class that provides a pythonic interface to unix system network - configuration information. - """ - - EXECUTABLE = 'ip' - - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - """Initializes resources for the IPManager class""" - super(IPManager, self).__init__(root_helper) - self.next_generic_index = 0 - self.host_mapping = {} - self.generic_mapping = {} - - def ensure_mapping(self): - """ - Creates a mapping of generic interface names (e.g., ge0, ge1) to - physical interface names (eth1, eth2). - """ - self.get_interfaces() - - def get_interfaces(self): - """ - Returns a list of the available network interfaces. This information - is obtained through the `ip addr show` system command. - """ - interfaces = _parse_interfaces(self.do('addr', 'show'), - filters=PHYSICAL_INTERFACES) - - interfaces.sort(key=lambda x: x.ifname) - for i in interfaces: - if i.ifname not in self.host_mapping: - generic_name = 'ge%d' % self.next_generic_index - self.host_mapping[i.ifname] = generic_name - self.next_generic_index += 1 - - # change ifname to generic version - i.ifname = self.host_mapping[i.ifname] - self.generic_mapping = dict((v, k) for k, v in - self.host_mapping.iteritems()) - - return interfaces - - def get_interface(self, ifname): - """ - Returns network configuration information for the requested network - interface. This information is obtained through the system command `ip - addr show `. - - :param ifname: the name of the interface to retrieve, e.g., `eth1` - :type ifname: str - :rtype: astara_router.model.Interface - """ - real_ifname = self.generic_to_host(ifname) - retval = _parse_interface(self.do('addr', 'show', real_ifname)) - retval.ifname = ifname - return retval - - def is_valid(self, ifname): - """ - Validates if the supplied interface is a valid system network - interface. Returns `True` if is a valid interface. Returns - `False` if is not a valid interface. - - :param ifname: the name of the interface to retrieve, e.g., `eth1` - :type ifname: str - """ - self.ensure_mapping() - return ifname in self.generic_mapping - - def generic_to_host(self, generic_name): - """ - Translates a generic interface name into the physical network interface - name. - :param ifname: the generic name to translate, e.g., `ge0` - :type ifname: str - :rtype: str - """ - self.ensure_mapping() - return self.generic_mapping.get(generic_name) - - def host_to_generic(self, real_name): - """ - Translates a physical interface name into the generic network interface - name. - :param ifname: the physical name to translate, e.g., `eth0` - :type ifname: str - :rtype: str - """ - self.ensure_mapping() - return self.host_mapping.get(real_name) - - def update_interfaces(self, interfaces): - """ - Wrapper function that accepts a list of interfaces and iterates over - them, calling update_interface() in order to update - their configuration. - """ - for i in interfaces: - self.update_interface(i) - - def up(self, interface): - """ - Sets the administrative mode for the network link on interface - to "up". - :param interface: the interface to mark up - :type interface: astara_router.models.Interface - """ - real_ifname = self.generic_to_host(interface.ifname) - self.sudo('link', 'set', real_ifname, 'up') - return self.get_interface(interface.ifname) - - def down(self, interface): - """ - Sets the administrative mode for the network link on interface - to "down". - :param interface: the interface to mark down - :type interface: astara_router.models.Interface - """ - real_ifname = self.generic_to_host(interface.ifname) - self.sudo('link', 'set', real_ifname, 'down') - - def set_mtu(self, interface): - """ - Sets the mtu on interface to mtu. - :param interface: the interface to set mtu - :type interface: astara_router.models.Interface - """ - real_ifname = self.generic_to_host(interface.ifname) - self.sudo('link', 'set', real_ifname, 'mtu', str(interface.mtu)) - - def update_interface(self, interface, ignore_link_local=True): - """ - Updates a network interface, particularly its addresses - :param interface: the interface to update - :type interface: astara_router.models.Interface - :param ignore_link_local: When True, link local addresses will not be - added/removed - :type ignore_link_local: bool - """ - real_ifname = self.generic_to_host(interface.ifname) - old_interface = self.get_interface(interface.ifname) - - if ignore_link_local: - interface.addresses = [a for a in interface.addresses - if not a.is_link_local()] - old_interface.addresses = [a for a in old_interface.addresses - if not a.is_link_local()] - # Must update primary before aliases otherwise will lose address - # in case where primary and alias are swapped. - self._update_addresses(real_ifname, interface, old_interface) - - if interface.mtu is not None and old_interface.mtu != interface.mtu: - self.set_mtu(interface) - - def _update_addresses(self, real_ifname, interface, old_interface): - """ - Compare the state of an interface, and add/remove address that have - changed. - :param real_ifname: the name of the interface to modify - :param real_ifname: str - :param interface: the new interface reference - :type interface: astara_router.models.Interface - :param old_interface: the reference to the current network interface - :type old_interface: astara_router.models.Interface - """ - - def _gen_cmd(cmd, address): - """ - Generates an `ip addr (add|del) dev ` command. - """ - family = {4: 'inet', 6: 'inet6'}[address[0].version] - args = ['addr', cmd, '%s/%s' % (address[0], address[1])] - if family == 'inet' and cmd == 'add': - args += ['brd', '+'] - args += ['dev', real_ifname] - if family == 'inet6': - args = ['-6'] + args - return args - - add = functools.partial(_gen_cmd, 'add') - delete = functools.partial(_gen_cmd, 'del') - - self._update_set(real_ifname, interface, old_interface, - 'all_addresses', add, delete) - - def _update_set(self, real_ifname, interface, old_interface, attribute, - fmt_args_add, fmt_args_delete): - """ - Compare the set of addresses (the current set and the desired set) - for an interface and generate a series of `ip addr add` and `ip addr - del` commands. - """ - next_set = set((i.ip, i.prefixlen) - for i in getattr(interface, attribute)) - prev_set = set((i.ip, i.prefixlen) - for i in getattr(old_interface, attribute)) - - if next_set == prev_set: - return - - for item in (next_set - prev_set): - self.sudo(*fmt_args_add(item)) - self.up(interface) - - for item in (prev_set - next_set): - try: - self.sudo(*fmt_args_delete(item)) - except RuntimeError: - LOG.warning('IP could not be deleted: %s' % item) - ip, prefix = item - if ip.version == 4: - self._delete_conntrack_state(ip) - - def update_default_gateway(self, config): - """ - Sets the default gateway for v4 and v6 via the use of `ip route add`. - - :type config: astara_router.models.Configuration - """ - # Track whether we have set the default gateways, by IP - # version. - gw_set = { - 4: False, - 6: False, - } - - ifname = None - for net in config.networks: - if not net.is_external_network: - continue - ifname = net.interface.ifname - - # The default v4 gateway is pulled out as a special case - # because we only want one but we might have multiple v4 - # subnets on the external network. However, sometimes the RUG - # can't figure out what that value is, because it thinks we - # don't have any external IP addresses, yet. In that case, it - # doesn't give us a default. - if config.default_v4_gateway: - self._set_default_gateway(config.default_v4_gateway, ifname) - gw_set[4] = True - - # Look through our networks and make sure we have a default - # gateway set for each IP version, if we have an IP for that - # version on the external net. If we haven't already set the - # v4 gateway, this picks the gateway for the first subnet we - # find, which might be wrong. - for net in config.networks: - if not net.is_external_network: - continue - - for subnet in net.subnets: - if subnet.gateway_ip and not gw_set[subnet.gateway_ip.version]: - self._set_default_gateway( - subnet.gateway_ip, - net.interface.ifname - ) - gw_set[subnet.gateway_ip.version] = True - - def update_host_routes(self, config, cache): - """ - Update the network routes. This is primarily used to support static - routes that users provide to neutron. - - :type config: astara_router.models.Configuration - :param cache: a dbm cache for storing the "last applied routes". - Because Linux does not differentiate user-provided routes - from, for example, the default gateway, this is necessary - so that subsequent calls to this method can determine - "what changed" for the user-provided routes. - :type cache: dogpile.cache.region.CacheRegion - """ - db = cache.get_or_create('host_routes', lambda: {}) - for net in config.networks: - - # For each subnet... - for subnet in net.subnets: - cidr = str(subnet.cidr) - - # determine the set of previously written routes for this cidr - if cidr not in db: - db[cidr] = set() - - current = db[cidr] - - # build a set of new routes for this cidr - latest = set() - for r in subnet.host_routes: - latest.add((r.destination, r.next_hop)) - - # If the set of previously written routes contains routes that - # aren't defined in the new config, run commands to delete them - for x in current - latest: - if self._alter_route(net.interface.ifname, 'del', *x): - current.remove(x) - - # If the new config contains routes that aren't defined in the - # set of previously written routes, run commands to add them - for x in latest - current: - if self._alter_route(net.interface.ifname, 'add', *x): - current.add(x) - - if not current: - del db[cidr] - - cache.set('host_routes', db) - - def _get_default_gateway(self, version): - """ - Gets the default gateway. - - :param version: the IP version, 4 or 6 - :type version: int - :rtype: str - """ - try: - cmd_out = self.sudo('-%s' % version, 'route', 'show') - except: - # assume the route is missing and use defaults - pass - else: - for l in cmd_out.splitlines(): - l = l.strip() - if l.startswith('default'): - match = re.search('via (?P[^ ]+)', l) - if match: - return match.group('gateway') - - def _set_default_gateway(self, gateway_ip, ifname): - """ - Sets the default gateway. - - :param gateway_ip: the IP address to set as the default gateway_ip - :type gateway_ip: netaddr.IPAddress - :param ifname: the interface name (in our case, of the external - network) - :type ifname: str - """ - version = 4 - if gateway_ip.version == 6: - version = 6 - current = self._get_default_gateway(version) - desired = str(gateway_ip) - ifname = self.generic_to_host(ifname) - - if current and current != desired: - # Remove the current gateway and add the desired one - self.sudo( - '-%s' % version, 'route', 'del', 'default', 'via', current, - 'dev', ifname - ) - return self.sudo( - '-%s' % version, 'route', 'add', 'default', 'via', desired, - 'dev', ifname - ) - if not current: - # Add the desired gateway - return self.sudo( - '-%s' % version, 'route', 'add', 'default', 'via', desired, - 'dev', ifname - ) - - def _alter_route(self, ifname, action, destination, next_hop): - """ - Apply/remove a custom (generally, user-supplied) route using the `ip - route add/delete` command. - - :param ifname: The name of the interface on which to alter the route - :type ifname: str - :param action: The action, 'add' or 'del' - :type action: str - :param destination: The destination CIDR - :type destination: netaddr.IPNetwork - :param next_hop: The next hop IP addressj - :type next_hop: netaddr.IPAddress - """ - version = destination.version - ifname = self.generic_to_host(ifname) - try: - LOG.debug(self.sudo( - '-%s' % version, 'route', action, str(destination), 'via', - str(next_hop), 'dev', ifname - )) - return True - except RuntimeError as e: - # Since these are user-supplied custom routes, it's very possible - # that adding/removing them will fail. A failure to apply one of - # these custom rules, however, should *not* cause an overall router - # failure. - LOG.warning('Route could not be %sed: %s' % (action, unicode(e))) - return False - - def disable_duplicate_address_detection(self, network): - """ - Disabled duplicate address detection for a specific interface. - - :type network: astara.models.Network - """ - # For non-external networks, duplicate address detection isn't - # necessary (and it sometimes results in race conditions for services - # that attempt to bind to addresses before they're ready). - - if network.network_type != network.TYPE_EXTERNAL: - real_ifname = self.generic_to_host(network.interface.ifname) - try: - utils.execute([ - 'sysctl', '-w', 'net.ipv6.conf.%s.accept_dad=0' - % real_ifname - ], self.root_helper) - except RuntimeError: - LOG.debug( - 'Failed to disable v6 dad on %s' % real_ifname - ) - - def _delete_conntrack_state(self, ip): - """ - Explicitly remove an IP from in-kernel connection tracking. - - :param ip: The IP address to remove - :type ip: netaddr.IPAddress - """ - - # If no flow entries are deleted, `conntrack -D` will return 1 - try: - utils.execute(['conntrack', '-D', '-d', str(ip)], self.root_helper) - except RuntimeError: - LOG.debug( - 'Failed deleting ingress connection state of %s' % ip - ) - try: - utils.execute(['conntrack', '-D', '-q', str(ip)], self.root_helper) - except RuntimeError: - LOG.debug( - 'Failed deleting egress connection state of %s' % ip - ) - - -def get_rug_address(): - """ Return the RUG address """ - net = netaddr.IPNetwork(ULA_PREFIX) - return str(netaddr.IPAddress(net.first + 1)) - - -def _parse_interfaces(data, filters=None): - """ - Parse the output of `ip addr show`. - - :param data: the output of `ip addr show` - :type data: str - :param filter: a list of valid interface names to match on - :type data: list of str - :rtype: list of astara_router.models.Interface - """ - retval = [] - for iface_data in re.split('(^|\n)(?=[0-9]+: \w+\d{0,3}:)', data): - if not iface_data.strip(): - continue - number, interface = iface_data.split(': ', 1) - - # FIXME (mark): the logic works, but should be more readable - for f in filters or ['']: - if f == '': - break - elif interface.startswith(f) and interface[len(f)].isdigit(): - break - else: - continue - - retval.append(_parse_interface(iface_data)) - return retval - - -def _parse_interface(data): - """ - Parse details for an interface, given its data from `ip addr show ` - - :rtype: astara_router.models.Interface - """ - retval = dict(addresses=[]) - for line in data.split('\n'): - if line.startswith(' '): - line = line.strip() - if line.startswith('inet'): - retval['addresses'].append(_parse_inet(line)) - elif 'link/ether' in line: - retval['lladdr'] = _parse_lladdr(line) - else: - retval.update(_parse_head(line)) - - return models.Interface.from_dict(retval) - - -def _parse_head(line): - """ - Parse the line of `ip addr show` that contains the interface name, MTU, and - flags. - """ - retval = {} - m = re.match( - '[0-9]+: (?P\w+\d{1,3}): <(?P[^>]+)> mtu (?P[0-9]+)', - line - ) - if m: - retval['ifname'] = m.group('if') - retval['mtu'] = int(m.group('mtu')) - retval['flags'] = m.group('flags').split(',') - return retval - - -def _parse_inet(line): - """ - Parse a line of `ip addr show` that contains an address. - """ - tokens = line.split() - return netaddr.IPNetwork(tokens[1]) - - -def _parse_lladdr(line): - """ - Parse the line of `ip addr show` that contains the hardware address. - """ - tokens = line.split() - return tokens[1] - - -class VRRPIPManager(IPManager): - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - super(VRRPIPManager, self).__init__(root_helper) - self.keepalived = keepalived.KeepalivedManager(root_helper) - self.ensure_mapping() - - def set_peers(self, peers): - self.keepalived.peers = peers - - def set_priority(self, priority): - self.keepalived.set_priority(priority) - - def update_interfaces(self, interfaces): - for interface in interfaces: - if interface.management: - # the mgt interface is not managed as a vip, but - # it used for keepalived mcast cluster comms - self.update_interface(interface) - self.keepalived.set_management_address( - address=interface.first_v4 or interface.first_v6) - else: - self.up(interface) - self.keepalived.add_vrrp_instance( - interface=self.generic_to_host(interface.ifname), - addresses=interface.all_addresses) - - def _set_default_gateway(self, gateway_ip, ifname): - """ - Sets the default gateway. - - :param gateway_ip: the IP address to set as the default gateway_ip - :type gateway_ip: netaddr.IPAddress - :param ifname: the interface name (in our case, of the external - network) - :type ifname: str - """ - version = 4 - if gateway_ip.version == 6: - version = 6 - self.keepalived.set_default_gateway( - ip_version=version, gateway_ip=gateway_ip, - interface=self.generic_to_host(ifname)) - - def update_host_routes(self, config, cache): - # XXX TODO - return - - def reload(self): - self.keepalived.reload() diff --git a/astara_router/drivers/iptables.py b/astara_router/drivers/iptables.py deleted file mode 100644 index 689c1d0..0000000 --- a/astara_router/drivers/iptables.py +++ /dev/null @@ -1,490 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 re -import itertools -import os - -from astara_router.drivers import base -from astara_router.models import Network -from astara_router import settings, utils - - -class Rule(object): - - def __init__(self, rule, ip_version=None): - self.rule = rule - self.ip_version = ip_version - - def __str__(self): - return self.rule - - @property - def for_v4(self): - return self.ip_version in (None, 4) - - @property - def for_v6(self): - return self.ip_version in (None, 6) - - -class IPTablesManager(base.Manager): - """ - """ - - def save_config(self, config, interface_map): - ''' - Save iptables-persistent firewall rules to disk. - - :param config: The astara configuration to save to disk - :type config: astara.rug.models.Configuration - :param interface_map: A mapping of virtual ('ge0') to physical ('eth0') - interface names - :type interface_map: dict - ''' - rules = itertools.chain( - self._build_filter_table(config), - self._build_nat_table(config), - self._build_mangle_table(config), - self._build_raw_table(config) - ) - - for version, rules in zip((4, 6), itertools.tee(rules)): - data = '\n'.join(map( - str, - [r for r in rules if getattr(r, 'for_v%s' % version)] - )) - - # Map virtual interface names - real_name = interface_map.get('ge0')[:-1] - ifname_re = '\-(?Pi|o)(?P[\s!])(?P!?)(?Pge)(?P\d+)' # noqa - ifname_sub = r'-\g\g\g%s\g' % real_name - data = re.sub(ifname_re, ifname_sub, data) + '\n' - - utils.replace_file('/tmp/ip%stables.rules' % version, data) - - utils.execute([ - 'mv', - '/tmp/ip%stables.rules' % version, - '/etc/iptables/rules.v%s' % version - ], self.root_helper) - - def restart(self): - ''' - Reload firewall rules via [netfilter/iptables]-persistent - Note that at some point iptables-persistent merged into - netfilter-persistent as a plugin, so use that instead if it is - available - ''' - _init = '%s-persistent' - if os.path.isfile('/etc/init.d/netfilter-persistent'): - init = _init % 'netfilter' - else: - init = _init % 'iptables' - - utils.execute( - ['service', init, 'restart'], - self.root_helper - ) - - def get_rules(self): - ''' - Return the output of `iptables` and `ip6tables`. - This function is used by astara orchestrator -> HTTP as a test for - "router aliveness". - - :rtype: str - ''' - v4 = utils.execute(['iptables', '-L', '-n'], self.root_helper) - v6 = utils.execute(['ip6tables', '-L', '-n'], self.root_helper) - return v4 + v6 - - def get_external_network(self, config): - ''' - Returns the external network - - :rtype: astara_router.models.Network - ''' - try: - return self.networks_by_type(config, Network.TYPE_EXTERNAL)[0] - except IndexError: - return None - - def get_management_network(self, config): - ''' - Returns the management network - - :rtype: astara_router.models.Network - ''' - return self.networks_by_type(config, Network.TYPE_MANAGEMENT)[0] - - def get_internal_networks(self, config): - ''' - Returns the internal networks - - :rtype: [astara_router.models.Network] - ''' - return self.networks_by_type(config, Network.TYPE_INTERNAL) - - def networks_by_type(self, config, type): - ''' - Returns the external network - - :rtype: astara_router.models.Interface - ''' - return filter(lambda n: n.network_type == type, config.networks) - - def _build_filter_table(self, config): - ''' - Build a list of iptables and ip6tables rules to be written to disk. - - :param config: the astara configuration object: - :type config: astara_router.models.Configuration - :param rules: the list of rules to append to - :type rules: a list of astara_router.drivers.iptables.Rule objects - ''' - return itertools.chain( - self._build_default_filter_rules(), - self._build_management_filter_rules(config), - self._build_internal_network_filter_rules(config), - self._build_vpn_filter_rules(config), - [Rule('COMMIT')] - ) - - def _build_default_filter_rules(self): - ''' - Build rules for default filter policies and ICMP handling - ''' - return ( - Rule('*filter'), - Rule(':INPUT DROP [0:0]'), - Rule(':FORWARD ACCEPT [0:0]'), - Rule(':OUTPUT ACCEPT [0:0]'), - Rule('-A INPUT -i lo -j ACCEPT'), - Rule( - '-A INPUT -p icmp --icmp-type echo-request -j ACCEPT', - ip_version=4 - ), - Rule( - '-A INPUT -p icmpv6 -j ACCEPT', - ip_version=6 - ) - ) - - def _build_management_filter_rules(self, config): - ''' - Add rules specific to the management network, like allowances for SSH, - the HTTP API, and metadata proxying on the management interface. - ''' - rules = [] - - for network in self.networks_by_type(config, Network.TYPE_MANAGEMENT): - - # Allow established mgt traffic - rules.append(Rule( - '-A INPUT -i %s -m state --state RELATED,ESTABLISHED -j ACCEPT' - % network.interface.ifname - )) - - # Open SSH, the HTTP API (5000) and the Nova metadata proxy (9697) - for port in ( - settings.SSH, settings.API_SERVICE, - settings.ORCHESTRATOR_METADATA_PORT - ): - rules.append(Rule( - '-A INPUT -i %s -p tcp -m tcp --dport %s -j ACCEPT' % ( - network.interface.ifname, - port - ), ip_version=6 - )) - - # Disallow any other management network traffic - rules.append(Rule('-A INPUT -i !%s -d %s -j DROP' % ( - network.interface.ifname, - network.interface.first_v6 - ), ip_version=6)) - - return rules - - def _build_internal_network_filter_rules(self, config): - ''' - Add rules specific to private tenant networks. - ''' - rules = [] - ext_net = self.get_external_network(config) - if ext_net: - ext_if = ext_net.interface - else: - ext_if = None - - for network in self.get_internal_networks(config): - - for version, address, dhcp_port in ( - (4, network.interface.first_v4, settings.DHCP), - (6, network.interface.first_v6, settings.DHCPV6) - ): - if address: - # Allow DHCP - rules.append(Rule( - '-A INPUT -i %s -p udp -m udp --dport %s -j ACCEPT' % ( - network.interface.ifname, - dhcp_port - ), ip_version=version - )) - rules.append(Rule( - '-A INPUT -i %s -p tcp -m tcp --dport %s -j ACCEPT' % ( - network.interface.ifname, - dhcp_port - ), ip_version=version - )) - - rules.append(Rule( - '-A INPUT -i %s -j ACCEPT' % network.interface.ifname - )) - if ext_if: - rules.append(Rule( - '-A INPUT -i %s -m state ' - '--state RELATED,ESTABLISHED -j ACCEPT' % ext_if.ifname - )) - - return rules - - def _build_vpn_filter_rules(self, config): - rules = [] - ext_net = self.get_external_network(config) - if ext_net: - ext_if = ext_net.interface - else: - ext_net = None - - if ext_net is None or not config.vpn: - return rules - - template = ( - ('-A INPUT -i %%s -p udp -m udp --dport %d -j ACCEPT ' % - settings.ISAKMP), - ('-A INPUT -i %%s -p udp -m udp --dport %d -j ACCEPT ' % - settings.IPSEC_NAT_T), - '-A INPUT -i %s -p esp -j ACCEPT', - '-A INPUT -i %s -p ah -j ACCEPT' - ) - - for version in (4, 6): - rules.extend( - (Rule(t % ext_if.ifname, ip_version=version) for t in template) - ) - - return rules - - def _build_nat_table(self, config): - ''' - Add rules for generic v4 NAT for the internal tenant networks - ''' - rules = [ - Rule('*nat', ip_version=4), - ] - - rules.extend(self._build_public_snat_chain(config)) - - rules.extend([ - Rule(':PREROUTING ACCEPT [0:0]', ip_version=4), - Rule(':INPUT ACCEPT [0:0]', ip_version=4), - Rule(':OUTPUT ACCEPT [0:0]', ip_version=4), - Rule(':POSTROUTING ACCEPT [0:0]', ip_version=4), - ]) - - rules.extend(self._build_floating_ips(config)) - - rules.extend(self._build_v4_nat(config)) - - rules.append(Rule('COMMIT', ip_version=4)) - return rules - - def _build_v4_nat(self, config): - rules = [] - - for network in self.get_internal_networks(config): - if network.interface.first_v4: - # Forward metadata requests on the management interface - rules.append(Rule( - '-A PREROUTING -i %s -d %s -p tcp -m tcp ' - '--dport %s -j DNAT --to-destination %s:%s' % ( - network.interface.ifname, - settings.METADATA_DEST_ADDRESS, - settings.HTTP, - network.interface.first_v4, - settings.internal_metadata_port( - network.interface.ifname - ) - ), ip_version=4 - )) - - # Add a masquerade catch-all for VMs without floating IPs - ext_net = self.get_external_network(config) - if ext_net: - ext_if = ext_net.interface - rules.append(Rule( - '-A POSTROUTING -o %s -j MASQUERADE' % ( - ext_if.ifname - ), ip_version=4 - )) - - return rules - - def _build_floating_ips(self, config): - ''' - Add rules for neutron FloatingIPs. - ''' - rules = [] - ext_net = self.get_external_network(config) - if ext_net: - ext_if = ext_net.interface - else: - return [] - - # NAT floating IP addresses - for fip in ext_net.floating_ips: - - # Neutron has a bug whereby you can create a floating ip that has - # mixed IP versions between the fixed and floating address. If - # people create these accidentally, just ignore them (because - # iptables will barf if it encounters them) - if fip.fixed_ip.version == fip.floating_ip.version: - if ext_if: - rules.append(Rule( - '-A PREROUTING -i %s -d %s -j DNAT --to-destination %s' - % ( - ext_if.ifname, - fip.floating_ip, - fip.fixed_ip - ), ip_version=4 - )) - for network in self.get_internal_networks(config): - rules.append(Rule( - '-A PREROUTING -i %s -d %s -j DNAT ' - '--to-destination %s' % ( - network.interface.ifname, - fip.floating_ip, - fip.fixed_ip - ), ip_version=4 - )) - - if rules: - for network in self.get_internal_networks(config): - for subnet in network.subnets: - if subnet.cidr.version == 4: - rules.append( - Rule('-A POSTROUTING -s %s -j PUBLIC_SNAT' % ( - subnet.cidr - ), ip_version=4) - ) - - return rules - - def _build_public_snat_chain(self, config): - ''' - Build a chain for SNAT for neutron FloatingIPs. This chain ignores NAT - for traffic marked as private. - ''' - external_network = self.get_external_network(config) - if not external_network: - return [] - - rules = [ - Rule(':PUBLIC_SNAT - [0:0]', ip_version=4), - Rule( - '-A PUBLIC_SNAT -m mark --mark 0xACDA -j RETURN', - ip_version=4 - ) - ] - - # NAT floating IP addresses - for fip in external_network.floating_ips: - - if fip.fixed_ip.version == fip.floating_ip.version: - rules.append( - Rule('-A PUBLIC_SNAT -s %s -j SNAT --to %s' % ( - fip.fixed_ip, - fip.floating_ip - ), ip_version=4) - ) - - # Add source NAT to handle NAT loopback case where external floating IP - # is used as the destination from internal endpoint - mgt_if = self.get_management_network(config).interface - rules.append(Rule( - '-A PUBLIC_SNAT ! -o %s -j SNAT --to %s' % ( - mgt_if.ifname, - str(external_network.interface.first_v4) - ), - ip_version=4 - )) - - return rules - - def _build_mangle_table(self, config): - rules = [ - Rule('*mangle', ip_version=4), - Rule(':INPUT - [0:0]', ip_version=4), - Rule(':OUTPUT - [0:0]', ip_version=4), - Rule(':FORWARD - [0:0]', ip_version=4), - Rule(':PREROUTING - [0:0]', ip_version=4), - Rule(':POSTROUTING - [0:0]', ip_version=4), - Rule( - ('-A POSTROUTING -p udp -m udp --dport 68 ' - '-j CHECKSUM --checksum-fill'), - ip_version=4), - Rule('COMMIT', ip_version=4) - ] - return rules - - def _build_raw_table(self, config): - ''' - Add raw rules (so we can mark private traffic and avoid NATing it) - ''' - rules = [ - Rule('*raw', ip_version=4), - Rule(':INPUT - [0:0]', ip_version=4), - Rule(':OUTPUT - [0:0]', ip_version=4), - Rule(':FORWARD - [0:0]', ip_version=4), - Rule(':PREROUTING - [0:0]', ip_version=4) - ] - - # do not NAT traffic generated from within the appliance - rules.append(Rule('-A OUTPUT -j MARK --set-mark 0xACDA', ip_version=4)) - - ext_net = self.get_external_network(config) - if ext_net: - ext_if = ext_net.interface - rules.append(Rule( - '-A PREROUTING -i %s -j MARK --set-mark 0xACDA' % - ext_if.ifname, ip_version=4 - )) - - for network in self.networks_by_type(config, Network.TYPE_INTERNAL): - if network.interface.first_v4: - address = sorted( - str(a) for a in network.interface.addresses - if a.version == 4 - )[0] - rules.append(Rule( - '-A PREROUTING -d %s -j MARK --set-mark 0xACDA' % address, - ip_version=4 - )) - - rules.append(Rule(':POSTROUTING - [0:0]', ip_version=4)) - rules.append(Rule('COMMIT', ip_version=4)) - return rules diff --git a/astara_router/drivers/keepalived.conf.template b/astara_router/drivers/keepalived.conf.template deleted file mode 100644 index 0618a81..0000000 --- a/astara_router/drivers/keepalived.conf.template +++ /dev/null @@ -1,46 +0,0 @@ -vrrp_sync_group astara_vrrp_group { - group { - {%- for instance in vrrp_instances %} - {{ instance.name }} - {%- endfor %} - } - notify_master "{{ notify_script }} primary" - notify_backup "{{ notify_script }} backup" - notify_fault "{{ notify_script }} fault" -} - -{%- for instance in vrrp_instances %} -vrrp_instance {{ instance.name }} { - native_ipv6 - state {{ instance.state }} - interface {{ instance.interface }} - virtual_router_id {{ instance.vrrp_id }} - priority {{ priority }} - garp_master_delay {{ instance.garp_master_delay }} - unicast_src_ip {{ instance.unicast_src_ip }} - unicast_peer { - {%- for peer in peers %} - {{ peer }} - {%- endfor %} - } - {%- if instance.vips %} - virtual_ipaddress { - {{ instance.vips[0].address }} dev {{ instance.vips[0].interface }} - } - virtual_ipaddress_excluded { - {%- for vip in instance.vips[1:] %} - {{ vip.address }} dev {{ vip.interface }} - {%- endfor %} - } - {%- endif %} - - {%- if instance.routes %} - virtual_routes { - {%- for route in instance.routes %} - {{ route.destination }} via {{ route.gateway }} dev {{ instance.interface }} - {%- endfor %} - } - {%- endif %} -} - -{%- endfor %} diff --git a/astara_router/drivers/keepalived.py b/astara_router/drivers/keepalived.py deleted file mode 100644 index a47d6d3..0000000 --- a/astara_router/drivers/keepalived.py +++ /dev/null @@ -1,158 +0,0 @@ -# Copyright (c) 2016 Akanda, Inc. All Rights Reserved. -# -# 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 os - -from astara_router.drivers import base, conntrackd -from astara_router import utils - - -class KeepalivedVipAddress(object): - """A virtual address entry of a keepalived configuration.""" - - def __init__(self, address, interface): - self.address = address - self.interface = interface - - def __eq__(self, other): - return (isinstance(other, KeepalivedVipAddress) and - self.address.ip == other.address.ip) - - -class KeepalivedRoute(object): - """A virtual route entry in keepalived instance configuration""" - def __init__(self, destination, gateway): - self.destination = destination - self.gateway = gateway - - def __eq__(self, other): - return ( - isinstance(other, KeepalivedRoute) and - (self.destination, self.gateway) == - (other.destination, other.gateway) - ) - - -class KeepalivedInstance(object): - def __init__(self, interface, unicast_src_ip, vrrp_id, state='BACKUP', - garp_master_delay=60): - self.interface = interface - self.vrrp_id = vrrp_id - self.unicast_src_ip = unicast_src_ip - self.name = 'astara_vrrp_' + interface - self.state = state - self.garp_master_delay = 60 - self.vips = [] - self.routes = [] - - def add_vip(self, address): - vip = KeepalivedVipAddress(address, self.interface) - if vip not in self.vips: - self.vips.append(vip) - - def add_route(self, destination, gateway): - route = KeepalivedRoute(destination, gateway) - if route not in self.routes: - self.routes.append(route) - - -class KeepalivedManager(base.Manager): - CONFIG_FILE_TEMPLATE = os.path.join( - os.path.dirname(__file__), 'keepalived.conf.template') - - # Debian defaults - CONFIG_FILE = '/etc/keepalived/keepalived.conf' - PID_FILE = '/var/run/keepalived.pid' - - EXECUTABLE = 'service' - - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - super(KeepalivedManager, self).__init__(root_helper) - self.instances = {} - self.unicast_src_ip = None - self.config_tmpl = utils.load_template(self.CONFIG_FILE_TEMPLATE) - self.peers = [] - self.priority = 0 - self.notify_script = conntrackd.ConntrackdManager.NOTIFY_SCRIPT - self._last_config_hash = None - - def set_management_address(self, address): - """Specify the address used for keepalived cluster communication""" - self.unicast_src_ip = address - for instance in self.instances.values(): - instance.unicast_src_ip = address - - def _get_instance(self, interface): - if interface in self.instances: - return self.instances[interface] - - vrrp_id = len(self.instances) + 1 - self.instances[interface] = KeepalivedInstance( - interface, self.unicast_src_ip, vrrp_id=vrrp_id) - return self.instances[interface] - - def _is_running(self): - if not os.path.isfile(self.PID_FILE): - return False - - pid = open(self.PID_FILE).read().strip() - proc_cmd = os.path.join('/proc', pid, 'cmdline') - if not os.path.isfile(proc_cmd): - return False - - if 'keepalived' not in open(proc_cmd).read(): - return False - - return True - - def add_vrrp_instance(self, interface, addresses): - instance = self._get_instance(interface) - [instance.add_vip(addr) for addr in addresses] - - def config(self): - return self.config_tmpl.render( - priority=self.priority, - peers=self.peers, - notify_script=self.notify_script, - vrrp_instances=self.instances.values()) - - def reload(self): - try: - last_config_hash = utils.hash_file(self.CONFIG_FILE) - except IOError: - last_config_hash = None - - utils.replace_file('/tmp/keepalived.conf', self.config()) - utils.execute( - ['mv', '/tmp/keepalived.conf', '/etc/keepalived/keepalived.conf'], - self.root_helper) - - if utils.hash_file(self.CONFIG_FILE) == last_config_hash: - return - - if self._is_running(): - self.sudo('keepalived', 'reload') - else: - self.sudo('keepalived', 'restart') - - def set_default_gateway(self, ip_version, gateway_ip, interface): - instance = self._get_instance(interface) - if ip_version == 6: - default = 'default6' - else: - default = 'default' - instance.add_route(default, gateway_ip) - - def set_priority(self, priority): - self.priority = priority diff --git a/astara_router/drivers/loadbalancer/__init__.py b/astara_router/drivers/loadbalancer/__init__.py deleted file mode 100644 index 6711a7a..0000000 --- a/astara_router/drivers/loadbalancer/__init__.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2015 Akanda, Inc. All Rights Reserved. -# -# 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 astara_router.drivers.loadbalancer import nginx - -# XXX move to config -CONFIGURED_LB_DRIVER = 'nginx' - -AVAILABLE_DRIVERS = { - 'nginx': nginx.NginxLB, - 'nginx+': nginx.NginxPlusLB, - # 'haxproxy': HaProxyLB, -} - - -class InvalidDriverException(Exception): - pass - - -def get_loadbalancer_driver(name): - try: - return AVAILABLE_DRIVERS[name] - except KeyError: - raise InvalidDriverException( - 'Could not find LB driver by name %s' % name) diff --git a/astara_router/drivers/loadbalancer/nginx.conf.template b/astara_router/drivers/loadbalancer/nginx.conf.template deleted file mode 100644 index e668702..0000000 --- a/astara_router/drivers/loadbalancer/nginx.conf.template +++ /dev/null @@ -1,19 +0,0 @@ -{%- for listener in loadbalancer.listeners %} -{%- if listener.default_pool and listener.default_pool.members %} - -server { - listen {{ loadbalancer.vip_address }}:{{ listener.protocol_port }}; - location / { - proxy_pass {{ listener.protocol.lower() }}://pool_{{ listener.default_pool.id }}; - } -} - -upstream pool_{{ listener.default_pool.id }} { - {%- for member in listener.default_pool.members: %} - server {{ member.address }}:{{ member.protocol_port }} weight={{ member.weight }}; - {%- endfor %} -} - -{%- endif %} -{%- endfor %} - diff --git a/astara_router/drivers/loadbalancer/nginx.py b/astara_router/drivers/loadbalancer/nginx.py deleted file mode 100644 index fe2ef27..0000000 --- a/astara_router/drivers/loadbalancer/nginx.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2015 Akanda, Inc. All Rights Reserved. -# -# 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 os - -from astara_router.drivers import base -from astara_router.utils import execute, load_template - - -class NginxLB(base.Manager): - NAME = 'nginx' - CONFIG_PATH = '/etc/nginx/sites-enabled/' - CONFIG_FILE_TEMPLATE = os.path.join( - os.path.dirname(__file__), 'nginx.conf.template') - INIT = 'nginx' - - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - """ - Initializes NginxLB class. - - :type root_helper: str - :param root_helper: System utility used to gain escalate privileges. - """ - super(NginxLB, self).__init__(root_helper) - self.config_tmpl = load_template(self.CONFIG_FILE_TEMPLATE) - - def _render_config_template(self, path, config): - with open(path, 'w') as out: - out.write( - self.config_tmpl.render(loadbalancer=config) - ) - - def restart(self): - execute(['service', self.INIT, 'restart'], self.root_helper) - - def update_config(self, config): - path = os.path.join( - self.CONFIG_PATH, 'ak-loadbalancer-%s.conf' % config.id) - self._render_config_template(path=path, config=config) - self.restart() - - -class NginxPlusLB(NginxLB): - NAME = 'nginxplus' - CONFIG_FILE = '/tmp/nginx_plus.conf' - INIT = 'nginxplus' diff --git a/astara_router/drivers/metadata.py b/astara_router/drivers/metadata.py deleted file mode 100644 index 1b0dc59..0000000 --- a/astara_router/drivers/metadata.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 json - -from astara_router.settings import internal_metadata_port -from astara_router.drivers import base -from astara_router.utils import execute, replace_file - - -CONF_PATH = '/etc/metadata.conf' - - -class MetadataManager(base.Manager): - """ - A class to provide facilities to interact with the Nova metadata service. - """ - - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - """ - Initializes MetataManager class. - - :type root_helper: str - :param root_helper: System utility used to gain escalate privileges. - """ - super(MetadataManager, self).__init__(root_helper) - - def should_restart(self, config): - """ - This function determines if the networks have changed since - was initialized. - - :type config: astara_router.models.Configuration - :param config: An astara_router.models.Configuration object containing - the current configuration of the system's networks. - :rtype: bool - """ - net_ids = set( - [net.id for net in config.networks if net.is_tenant_network] - ) - try: - config_dict = json.load(open(CONF_PATH)) - except: - # If we can't read the file, assume networks were added/removed - return True - - orchestrator_addr = config_dict.get('orchestrator_metadata_address') - orchestrator_port = config_dict.get('orchestrator_metadata_port') - - return ( - net_ids != set(config_dict.get('networks', {}).keys()) or - orchestrator_addr != config.metadata_address or - orchestrator_port != config.metadata_port) - - def save_config(self, config): - """ - Writes to the metadata configuration file (). - - :type config: astara_router.models.Configuration - :param config: An astara_router.models.Configuration object containing - the configuration of metadata service. - """ - config_data = build_config(config) - - replace_file( - '/tmp/metadata.conf', - json.dumps(config_data, sort_keys=True) - ) - execute(['mv', '/tmp/metadata.conf', CONF_PATH], self.root_helper) - - def ensure_started(self): - """ - Checks if the metadata service is started and starts it if it is - determined to be stopped. - """ - try: - execute(['service', 'metadata', 'status'], self.root_helper) - except: - execute(['service', 'metadata', 'start'], self.root_helper) - - def restart(self): - """ - Restarts the metadata service using the init script. - """ - try: - execute(['service', 'metadata', 'stop'], self.root_helper) - except: - # failure is ok here - pass - execute(['service', 'metadata', 'start'], self.root_helper) - - -def build_config(config): - """ - Determines the configuration of the metadata service. - - :type config: astara_router.models.Configuration - :param config: - :rtype: astara_router.models.Configuration - """ - network_data = {} - - for net in config.networks: - if not net.is_tenant_network: - continue - - ip_instance_map = {} - for a in net.address_allocations: - for ip in a.ip_addresses: - ip_instance_map[ip] = a.device_id - - network_data[net.id] = { - 'listen_port': internal_metadata_port(net.interface.ifname), - 'ip_instance_map': ip_instance_map - } - - return { - 'tenant_id': config.tenant_id, - 'orchestrator_metadata_address': config.metadata_address, - 'orchestrator_metadata_port': config.metadata_port, - 'networks': network_data, - } diff --git a/astara_router/drivers/ping.py b/astara_router/drivers/ping.py deleted file mode 100644 index 1f4076c..0000000 --- a/astara_router/drivers/ping.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 netaddr - -from astara_router.drivers import base -from astara_router import utils - - -class PingManager(base.Manager): - """ - A class which provide a facade to the system ping utility. Supports both - IPv4 and IPv6. - """ - - exe_map = { - 4: 'ping', - 6: 'ping6' - } - - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - """ - Initializes PingManager class. - - :type root_helper: str - :param root_helper: System utility to escalate privileges. - """ - super(PingManager, self).__init__(root_helper) - - def do(self, ip): - """ - Sends a single ICMP packet to using the systems ping utility. - - :type ip: str - :param ip: The IP address to send ICMP packets to. - :rtype: bool. If responds to the ICMP packet, returns True else, - returns False - """ - version = netaddr.IPAddress(ip).version - args = ['-c', '1', ip] - try: - utils.execute([self.exe_map.get(version)] + args) - return True - except RuntimeError: - return False diff --git a/astara_router/drivers/vpn/__init__.py b/astara_router/drivers/vpn/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/astara_router/drivers/vpn/ipsec.py b/astara_router/drivers/vpn/ipsec.py deleted file mode 100644 index 814e77f..0000000 --- a/astara_router/drivers/vpn/ipsec.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright 2016 Akanda, 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 os - -import jinja2 - -from astara_router.drivers import base -from astara_router import utils - -TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'templates') - - -STRONGSWAN_TRANSLATIONS = { - "3des": "3des", - "aes-128": "aes128", - "aes-256": "aes256", - "aes-192": "aes192", - "group2": "modp1024", - "group5": "modp1536", - "group14": "modp2048", - "group15": "modp3072", - "bi-directional": "start", - "response-only": "add", -} - - -class StrongswanManager(base.Manager): - """ - A class to interact with strongswan, an IPSEC VPN daemon. - """ - def __init__(self, root_helper='sudo astara-rootwrap /etc/rootwrap.conf'): - """ - Initializes StrongswanManager class. - - :type root_helper: string - :param root_helper: System utility used to gain escalate privileges. - """ - super(StrongswanManager, self).__init__(root_helper) - - def save_config(self, config): - """ - Writes config file for strongswan daemon. - - :type config: astara_router.models.Configuration - """ - - env = jinja2.Environment( - loader=jinja2.FileSystemLoader(TEMPLATE_DIR) - ) - - env.filters['strongswan'] = lambda v: STRONGSWAN_TRANSLATIONS.get(v, v) - - templates = ('ipsec.conf', 'ipsec.secrets') - - for template_name in templates: - tmpl = env.get_template(template_name+'.j2') - - tmp = os.path.join('/tmp', template_name) - utils.replace_file(tmp, tmpl.render(vpnservices=config.vpn)) - - for template_name in templates: - tmp = os.path.join('/tmp', template_name) - etc = os.path.join('/etc', template_name) - utils.execute(['mv', tmp, etc], self.root_helper) - - def restart(self): - """ - Restart the Strongswan daemon using the system provided init scripts. - """ - try: - utils.execute( - ['service', 'strongswan', 'status'], - self.root_helper - ) - except: # pragma no cover - utils.execute(['service', 'strongswan', 'start'], self.root_helper) - else: # pragma no cover - utils.execute( - ['service', 'strongswan', 'reload'], - self.root_helper - ) - - def stop(self): - """ - Stop the Strongswan daemon using the system provided init scripts. - """ - try: - utils.execute( - ['service', 'strongswan', 'stop'], - self.root_helper - ) - except: # pragma no cover - pass diff --git a/astara_router/drivers/vpn/templates/README b/astara_router/drivers/vpn/templates/README deleted file mode 100644 index 8664e8f..0000000 --- a/astara_router/drivers/vpn/templates/README +++ /dev/null @@ -1,3 +0,0 @@ -These templates were originally copied from the upstream neutron-vpaas [1]. - -[1] http://git.openstack.org/cgit/openstack/neutron-vpnaas/tree/neutron_vpnaas/services/vpn/device_drivers/template/strongswan diff --git a/astara_router/drivers/vpn/templates/ipsec.conf.j2 b/astara_router/drivers/vpn/templates/ipsec.conf.j2 deleted file mode 100644 index cd33382..0000000 --- a/astara_router/drivers/vpn/templates/ipsec.conf.j2 +++ /dev/null @@ -1,39 +0,0 @@ -config setup - -conn %default - ikelifetime=60m - keylife=20m - rekeymargin=3m - keyingtries=1 - mobike=no -{% for vpnservice in vpnservices %} -# Configuration for {{vpnservice.name}} -{% for ipsec_site_connection in vpnservice.ipsec_site_connections%} -conn {{ipsec_site_connection.id}} - authby=secret - keyexchange=ike{{ipsec_site_connection.ikepolicy.ike_version}} - left={{vpnservice.get_external_ip(ipsec_site_connection.peer_address)}} - leftsubnet={{ipsec_site_connection.local_ep_group.cidrs|join(',')}} - leftid={{vpnservice.get_external_ip(ipsec_site_connection.peer_address)}} - leftfirewall=yes - right={{ipsec_site_connection.peer_address}} - rightsubnet={{ipsec_site_connection.peer_ep_group.cidrs|join(',')}} - rightid={{ipsec_site_connection.peer_id}} - auto=route - dpdaction={{ipsec_site_connection.dpd.action}} - dpddelay={{ipsec_site_connection.dpd.interval}} - dpdtimeout={{ipsec_site_connection.dpd.timeout}} - - # ike - ike={{ipsec_site_connection.ikepolicy.encryption_algorithm|strongswan}}-{{ipsec_site_connection.ikepolicy.auth_algorithm|strongswan}}-{{ipsec_site_connection.ikepolicy.pfs|strongswan}} - ikelifetime={{ipsec_site_connection.ikepolicy.lifetime.value}}s - - # ipsec - {{ipsec_site_connection.ipsecpolicy.transform_protocol}}={{ipsec_site_connection.ikepolicy.encryption_algorithm|strongswan}}-{{ipsec_site_connection.ikepolicy.auth_algorithm|strongswan}}-{{ipsec_site_connection.ikepolicy.pfs|strongswan}} - lifetime={{ipsec_site_connection.ipsecpolicy.lifetime.value}}s - - type={{ipsec_site_connection.ipsecpolicy.encapsulation_mode}} - -{% endfor %} -{% endfor %} - diff --git a/astara_router/drivers/vpn/templates/ipsec.secrets.j2 b/astara_router/drivers/vpn/templates/ipsec.secrets.j2 deleted file mode 100644 index 8240b47..0000000 --- a/astara_router/drivers/vpn/templates/ipsec.secrets.j2 +++ /dev/null @@ -1,6 +0,0 @@ -{% for vpnservice in vpnservices %} -# Configuration for {{vpnservice.name}} -{% for ipsec_site_connection in vpnservice.ipsec_site_connections %} -{{ipsec_site_connection.external_ip}} {{ipsec_site_connection.peer_id}} : PSK "{{ipsec_site_connection.psk}}" -{% endfor %} -{% endfor %} diff --git a/astara_router/manager.py b/astara_router/manager.py deleted file mode 100644 index 78e46fe..0000000 --- a/astara_router/manager.py +++ /dev/null @@ -1,329 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 os -import re - -from astara_router import models -from astara_router import settings -from astara_router.drivers import (bird, conntrackd, dnsmasq, ip, metadata, - iptables, arp, hostname, loadbalancer) -from astara_router.drivers.vpn import ipsec - - -class ServiceManagerBase(object): - def __init__(self, state_path='.'): - self._config = None - self.state_path = os.path.abspath(state_path) - self._vrrp_ip_mgr = None - self._reload_callbacks = [] - - @property - def ip_mgr(self): - ip_mgr = ip.IPManager() - ip_mgr.ensure_mapping() - - if not self._config: - # we do not yet have config, so use standard ip manager for - # ensuring initial intrefaces - return ip_mgr - if self._config and self._config.ha: - if not self._vrrp_ip_mgr: - self._vrrp_ip_mgr = ip.VRRPIPManager() - self._reload_callbacks.append(self._vrrp_ip_mgr.reload) - - # peers and prio can change and be updated via config, need to - # ensure the vrrp manager is up to date every access. - self._vrrp_ip_mgr.set_peers( - self._config.ha_config.get('peers', [])) - self._vrrp_ip_mgr.set_priority( - self._config.ha_config.get('priority', 0)) - - return self._vrrp_ip_mgr - else: - # we may not yet have config, so use standard ip manager for - # ensuring initial interfaces - return ip_mgr - - @property - def config(self): - """Make config a read-only property. - - To update the value, update_config() must called to change the global - state of appliance. - """ - return self._config - - def update_config(self, config, cache): - pass - - def update_interfaces(self): - if self._config is None: - return - for network in self._config.networks: - self.ip_mgr.disable_duplicate_address_detection(network) - - self.ip_mgr.update_interfaces(self._config.interfaces) - - def reload_config(self): - """Calls any post-config reload callbacks to reload services - - Required for things like keepalived, which gets its config built - by multiple drivers, in order to avoid unncessary restarts. - """ - [cb() for cb in self._reload_callbacks] - - -class SystemManager(ServiceManagerBase): - def __init__(self, state_path='.'): - super(SystemManager, self).__init__(state_path) - self._config = models.SystemConfiguration() - - def update_config(self, config, cache): - self._config = config - self.update_hostname() - self.update_interfaces() - - def update_hostname(self): - mgr = hostname.HostnameManager() - mgr.update(self._config) - - -class RouterManager(ServiceManagerBase): - - def update_config(self, config, cache): - self._config = config - self.update_interfaces() - self.update_dhcp() - self.update_metadata() - self.update_bgp_and_radv() - self.update_firewall() - self.update_routes(cache) - self.update_arp() - self.update_conntrackd() - self.update_ipsec_vpn() - self.reload_config() - - def update_conntrackd(self): - if not self._config.ha: - return - mgr = conntrackd.ConntrackdManager() - mgr.save_config(self._config, self.ip_mgr.generic_to_host) - mgr.restart() - - def update_dhcp(self): - mgr = dnsmasq.DHCPManager() - mgr.delete_all_config() - for network in self._config.networks: - real_ifname = self.ip_mgr.generic_to_host(network.interface.ifname) - mgr.update_network_dhcp_config(real_ifname, network) - mgr.restart() - - def update_metadata(self): - mgr = metadata.MetadataManager() - should_restart = mgr.should_restart(self._config) - mgr.save_config(self._config) - if should_restart: - mgr.restart() - else: - mgr.ensure_started() - - def update_bgp_and_radv(self): - mgr = bird.BirdManager() - mgr.save_config(self._config, self.ip_mgr.generic_mapping) - mgr.restart() - - def update_firewall(self): - mgr = iptables.IPTablesManager() - mgr.save_config(self._config, self.ip_mgr.generic_mapping) - mgr.restart() - - def update_routes(self, cache): - self.ip_mgr.update_default_gateway(self._config) - self.ip_mgr.update_host_routes(self._config, cache) - - def update_arp(self): - mgr = arp.ARPManager() - mgr.send_gratuitous_arp_for_floating_ips( - self._config, - self.ip_mgr.generic_to_host - ) - mgr.remove_stale_entries(self._config) - - def update_ipsec_vpn(self): - mgr = ipsec.StrongswanManager() - - if self._config.vpn: - mgr.save_config(self._config) - mgr.restart() - else: - mgr.stop() - - def get_interfaces(self): - return self.ip_mgr.get_interfaces() - - def get_interface(self, ifname): - return self.ip_mgr.get_interface(ifname) - - def _map_virtual_to_real_interfaces(self, virt_data): - rules = [] - - rules.extend( - '%s = "%s"' % i for i in self.ip_mgr.generic_mapping.items() - ) - - rules.append(re.sub('([\s!])(ge\d+([\s:]|$))', r'\1$\2', virt_data)) - return '\n'.join(rules) - - def get_config_or_default(self): - # This is a hack to provide compatability with the original API, see - # Manager.config() - if not self._config: - return models.RouterConfiguration() - else: - return self._config - - -class LoadBalancerManager(ServiceManagerBase): - def __init__(self, state_path='.'): - super(LoadBalancerManager, self).__init__(state_path) - self.lb_manager = loadbalancer.get_loadbalancer_driver( - # xxx pull from cfg - loadbalancer.CONFIGURED_LB_DRIVER)() - - def update_config(self, config, cache): - self._config = config - self.lb_manager.update_config(self.config) - - -SERVICE_MANAGER_MAP = { - 'router': RouterManager, - 'loadbalancer': LoadBalancerManager, -} - - -class Manager(object): - def __init__(self, state_path='.'): - self.state_path = os.path.abspath(state_path) - self.ip_mgr = ip.IPManager() - self.ip_mgr.ensure_mapping() - - # Holds the common system config - self._system_config = models.SystemConfiguration() - - # Holds config models for various services (router, loadbalancer) - self._service_configs = [] - - self._service_managers = { - 'system': SystemManager() - } - self._load_managers() - - def _load_managers(self): - for svc in settings.ENABLED_SERVICES: - manager = SERVICE_MANAGER_MAP.get(svc) - if manager: - self._service_managers[svc] = manager() - - def get_manager(self, service): - try: - return self._service_managers[service] - except: - raise Exception('No such service manager loaded for appliance ' - 'service %s' % service) - - def management_address(self, ensure_configuration=False): - return self.ip_mgr.get_management_address(ensure_configuration) - - @property - def router(self): - """Returns the router manager. - This is mostly to keep compat with the existing API. - """ - return self.get_manager('router') - - @property - def system_config(self): - """Make config a read-only property. - - To update the value, update_config() must called to change the global - state of appliance. - """ - - return self._system_config - - @property - def service_configs(self): - """Make config a read-only property. - - To update the value, update_config() must called to change the global - state of router. - """ - - return self._service_configs - - def update_config(self, system_config, service_configs, cache): - self._system_config = system_config - self._service_configs = service_configs - - # first update the system config - manager = self.get_manager(self.system_config.service_name) - manager.update_config(self.system_config, cache) - - for svc_cfg in self.service_configs: - manager = self.get_manager(svc_cfg.service_name) - manager.update_config(svc_cfg, cache) - - @property - def config(self): - out = {} - if 'router' in self._service_managers: - # The original appliance API provides router config - # in the root 'configuration' key. We want to move that - # to the 'services' bucket but provide compat to those who might - # still be expecting it in the root. This seeds the root with the - # default empty values if no router is associated with the - # appliance and allows for - # ['configuration']['services']['router'] to be None at the same - # time. - router_cfg = self.router.get_config_or_default().to_dict() - out = router_cfg - else: - out = {} - - out['services'] = {} - for svc in SERVICE_MANAGER_MAP: - try: - manager = self.get_manager(svc) - except: - continue - out['services'][svc] = manager.config - - out['system'] = self.system_config - return out - - -class ManagerProxy(object): - def __init__(self): - self.instance = None - - def __getattr__(self, name): - if not self.instance: - self.instance = Manager() - return getattr(self.instance, name) - -manager = ManagerProxy() diff --git a/astara_router/metadata_proxy.py b/astara_router/metadata_proxy.py deleted file mode 100644 index b70f958..0000000 --- a/astara_router/metadata_proxy.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 argparse -import atexit -import contextlib -import json -import functools -import logging -import os -import sys -import urlparse - -import eventlet -import eventlet.wsgi -import requests -from werkzeug import exceptions -from werkzeug import wrappers - -LOG = logging.getLogger(__name__) - - -class NetworkMetadataProxyHandler(object): - """Proxy metadata request onto the RUG proxy - The proxy allows access resources that are not accessible within the - isolated tenant context. - """ - - def __init__(self, tenant_id, network_id, config_file): - self.tenant_id = tenant_id - self.network_id = network_id - self.config_file = config_file - self.config_mtime = 0 - self._config_dict = {} - self._ip_instance_map = {} - - @property - def config_dict(self): - config_mtime = os.stat(self.config_file).st_mtime - if config_mtime > self.config_mtime: - LOG.debug("Metadata proxy configuration has changed; reloading...") - self._config_dict = json.load(open(self.config_file)) - self.config_mtime = config_mtime - return self._config_dict - - def __call__(self, environ, start_response): - request = wrappers.Request(environ) - - LOG.debug("Request: %s", request) - try: - response = self._proxy_request(request.remote_addr, - request.path, - request.query_string) - except Exception: - LOG.exception("Unexpected error.") - msg = ('An unknown error has occurred. ' - 'Please try your request again.') - response = exceptions.InternalServerError(description=unicode(msg)) - - return response(environ, start_response) - - @property - def ip_instance_map(self): - self._ip_instance_map = self.config_dict['networks'][ - self.network_id]['ip_instance_map'] - return self._ip_instance_map - - @property - def orchestrator_loc(self): - addr = self.config_dict['orchestrator_metadata_address'] - port = self.config_dict['orchestrator_metadata_port'] - return '[%s]:%d' % (addr, port) - - def _proxy_request(self, remote_address, path_info, query_string): - headers = { - 'X-Forwarded-For': remote_address, - 'X-Instance-ID': self.ip_instance_map.get(remote_address, ''), - 'X-Quantum-Network-ID': self.network_id, - 'X-Tenant-ID': self.tenant_id - } - - url = urlparse.urlunsplit(( - 'http', - self.orchestrator_loc, - path_info, - query_string, - '')) - - response = requests.get(url, headers=headers) - - if response.status_code == requests.codes.ok: - LOG.debug(response) - return wrappers.Response(response.content, mimetype='text/plain') - elif response.status_code == requests.codes.not_found: - return exceptions.NotFound() - elif response.status_code == requests.codes.internal_server_error: - msg = 'Remote metadata server experienced an error.' - return exceptions.InternalServerError(description=unicode(msg)) - else: - raise Exception('Unexpected response code: %s' % response.status) - - -def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): - """Daemonize process by doing Stevens double fork.""" - # fork first time - _fork() - - # decouple from parent environment - os.chdir("/") - os.setsid() - os.umask(0) - - # fork second time - _fork() - - # redirect standard file descriptors - sys.stdout.flush() - sys.stderr.flush() - stdin = file(stdin, 'r') - stdout = file(stdout, 'a+') - stderr = file(stderr, 'a+', 0) - os.dup2(stdin.fileno(), sys.stdin.fileno()) - os.dup2(stdout.fileno(), sys.stdout.fileno()) - os.dup2(stderr.fileno(), sys.stderr.fileno()) - - # write a pidfile - pidfile = '/var/run/metadata.pid' - atexit.register(functools.partial(os.remove, pidfile)) - pid = str(os.getpid()) - with contextlib.closing(open(pidfile, 'w+')) as f: - f.write("%s\n" % pid) - - -def _fork(): - try: - pid = os.fork() - if pid > 0: - sys.exit(0) - except OSError, e: - sys.stderr.write("fork failed %d (%s)\n" % (e.errno, e.strerror)) - sys.exit(1) - - -def main(): - eventlet.monkey_patch() - - parser = argparse.ArgumentParser() - parser.add_argument("-D", "--no-daemon", help="don't daemonize", - action="store_false", dest='daemonize', default=True) - parser.add_argument("config_file", help="Proxy configuration file") - args = parser.parse_args() - - try: - config_dict = json.load(open(args.config_file)) - except IOError: - raise SystemError('Unable to open config file at %s.' % - args.config_file) - except: - raise SystemError('Unable to parse config file at %s.' % - args.config_file) - - if args.daemonize: - daemonize() - - pool = eventlet.GreenPool(1000) - - tenant_id = config_dict.pop('tenant_id') - for network_id, config in config_dict['networks'].items(): - app = NetworkMetadataProxyHandler(tenant_id, - network_id, - args.config_file) - socket = eventlet.listen(('0.0.0.0', config['listen_port']), - backlog=128) - pool.spawn_n(eventlet.wsgi.server, socket, app, custom_pool=pool) - - pool.waitall() diff --git a/astara_router/models.py b/astara_router/models.py deleted file mode 100644 index e4f6e09..0000000 --- a/astara_router/models.py +++ /dev/null @@ -1,1080 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 abc -import itertools -import re - -import netaddr - -from astara_router import settings - -GROUP_NAME_LENGTH = 15 -DEFAULT_AS = 64512 - - -class ModelBase(object): - __metaclass__ = abc.ABCMeta - - def __eq__(self, other): - return type(self) == type(other) and vars(self) == vars(other) - - -class Interface(ModelBase): - """ - """ - def __init__(self, ifname=None, addresses=[], groups=None, flags=None, - lladdr=None, mtu=None, media=None, - description=None, management=False, **extra_params): - self.ifname = ifname - self.description = description - self.addresses = addresses - self.groups = [g[:GROUP_NAME_LENGTH] for g in (groups or [])] - self.flags = flags or [] - self.lladdr = lladdr - self.mtu = mtu - self.media = media - self.extra_params = extra_params - self._aliases = [] - self.management = management - - def __repr__(self): - return '' % (self.ifname, - [str(a) for a in self.addresses]) - - def __eq__(self, other): - """Check model equality only on limit fields.""" - return (type(self) == type(other) and - self.ifname == other.ifname and - self.all_addresses == other.all_addresses and - self.description == other.description and - self.mtu == other.mtu and - self.groups == other.groups) - - @property - def description(self): - return self._description - - @description.setter - def description(self, value): - if not value: - self._description = '' - elif re.match('\w*$', value): - self._description = value - else: - raise ValueError('Description must be chars from [a-zA-Z0-9_]') - - @property - def addresses(self): - return self._addresses - - @addresses.setter - def addresses(self, value): - self._addresses = [netaddr.IPNetwork(a) for a in value] - - @property - def aliases(self): - return self._aliases - - @aliases.setter - def aliases(self, value): - self._aliases = [netaddr.IPNetwork(a) for a in value] - - @property - def all_addresses(self): - return self._addresses + self._aliases - - @property - def is_up(self): - if self.extra_params.get('state', '').lower() == 'up': - return 'UP' - return 'UP' in self.flags - - @property - def first_v4(self): - return self._first_addr_for_version(4) - - @property - def first_v6(self): - return self._first_addr_for_version(6) - - def _first_addr_for_version(self, version): - addrs = sorted(a.ip for a in self._addresses if a.version == version) - - if addrs: - return addrs[0] - - @classmethod - def from_dict(cls, d): - return cls(**d) - - def to_dict(self, extended=False): - include = ['ifname', 'groups', 'mtu', 'lladdr', 'media'] - if extended: - include.extend(['flags', 'extra_params']) - retval = dict( - [(k, v) for k, v in vars(self).iteritems() if k in include]) - retval['description'] = self.description - retval['addresses'] = self.addresses - retval['state'] = (self.is_up and 'up') or 'down' - return retval - - -class FilterRule(ModelBase): - """ - """ - def __init__(self, action=None, direction=None, interface=None, - family=None, protocol=None, source=None, source_port=None, - destination_interface=None, - destination=None, destination_port=None, - redirect=None, redirect_port=None): - - self.action = action - self.direction = direction - self.interface = interface - self.family = family - self.protocol = protocol - self.source = source - self.source_port = source_port - self.destination_interface = destination_interface - self.destination = destination - self.destination_port = destination_port - self.redirect = redirect - self.redirect_port = redirect_port - - def __setattr__(self, name, value): - if name != 'action' and not value: - pass - elif name == 'action': - if value not in ('pass', 'block'): - raise ValueError("Action must be 'pass' or 'block' not '%s'" % - value) - elif name in ('source', 'destination'): - if '/' in value: - value = netaddr.IPNetwork(value) - elif value.lower() == 'any': - value = None # any is the default so conver to None - elif name == 'direction': - if value not in ('in', 'out'): - raise ValueError( - "Direction must be 'in' or 'out' not '%s'" % value - ) - elif name == 'redirect': - value = netaddr.IPAddress(value) - elif name.endswith('_port'): - value = int(value) - elif name == 'family': - if value not in ('inet', 'inet6'): - raise ValueError("Family must be 'inet', 'inet6', None and not" - " %s" % value) - elif name == 'protocol': - if value not in ('tcp', 'udp', 'imcp'): - raise ValueError("Protocol must be tcp|udp|imcp not '%s'." % - value) - - super(FilterRule, self).__setattr__(name, value) - - @classmethod - def from_dict(cls, d): - return FilterRule(**d) - - @staticmethod - def _format_ip_or_table(obj): - if isinstance(obj, netaddr.IPNetwork): - return str(obj) - else: # must be table name - return '<%s>' % obj - - -class Anchor(ModelBase): - def __init__(self, name, rules=[]): - self.name = name - self.rules = rules - - -class AddressBookEntry(ModelBase): - def __init__(self, name, cidrs=[]): - self.name = name - self.cidrs = cidrs - - @property - def cidrs(self): - return self._cidrs - - @cidrs.setter - def cidrs(self, values): - self._cidrs = [netaddr.IPNetwork(a) for a in values] - - def external_table_data(self): - return '\n'.join(map(str, self.cidrs)) - - -class Allocation(ModelBase): - def __init__(self, mac_address, ip_addresses, hostname, device_id): - self.mac_address = mac_address - self.ip_addresses = ip_addresses or {} - self.hostname = hostname - self.device_id = device_id - - @property - def dhcp_addresses(self): - return [ip for ip, dhcp in self.ip_addresses.items() if dhcp] - - @classmethod - def from_dict(cls, d): - return cls( - d['mac_address'], - d['ip_addresses'], - d['hostname'], - d['device_id'], - ) - - -class FloatingIP(ModelBase): - def __init__(self, floating_ip, fixed_ip): - self.floating_ip = floating_ip - self.fixed_ip = fixed_ip - self.network = None - - @property - def floating_ip(self): - return self._floating_ip - - @floating_ip.setter - def floating_ip(self, value): - self._floating_ip = netaddr.IPAddress(value) - - @property - def fixed_ip(self): - return self._fixed_ip - - @fixed_ip.setter - def fixed_ip(self, value): - self._fixed_ip = netaddr.IPAddress(value) - - @classmethod - def from_dict(cls, d): - return cls( - d['floating_ip'], - d['fixed_ip'] - ) - - -class StaticRoute(ModelBase): - def __init__(self, destination, next_hop): - self.destination = destination - self.next_hop = next_hop - - @property - def destination(self): - return self._destination - - @destination.setter - def destination(self, value): - self._destination = netaddr.IPNetwork(value) - - @property - def next_hop(self): - return self._next_hop - - @next_hop.setter - def next_hop(self, value): - self._next_hop = netaddr.IPAddress(value) - - def to_dict(self): - return dict(destination=self.destination, next_hop=self.next_hop) - - -class Label(ModelBase): - def __init__(self, name, cidrs=[]): - self.name = name - self.cidrs = cidrs - - @property - def cidrs(self): - return self._cidrs - - @cidrs.setter - def cidrs(self, values): - self._cidrs = [netaddr.IPNetwork(a) for a in values] - - -class Subnet(ModelBase): - def __init__(self, id_, cidr, gateway_ip, dhcp_enabled=True, - dns_nameservers=None, host_routes=None): - self.id = id_ - self.cidr = cidr - self.gateway_ip = gateway_ip - self.dhcp_enabled = bool(dhcp_enabled) - self.dns_nameservers = dns_nameservers - self.host_routes = host_routes - - @property - def cidr(self): - return self._cidr - - @cidr.setter - def cidr(self, value): - self._cidr = netaddr.IPNetwork(value) - - @property - def gateway_ip(self): - return self._gateway_ip - - @gateway_ip.setter - def gateway_ip(self, value): - if value: - self._gateway_ip = netaddr.IPAddress(value) - else: - self._gateway_ip = None - - @property - def dns_nameservers(self): - return self._dns_nameservers - - @dns_nameservers.setter - def dns_nameservers(self, value): - self._dns_nameservers = [netaddr.IPAddress(a) for a in value] - - @classmethod - def from_dict(cls, d): - host_routes = [StaticRoute(r['destination'], r['nexthop']) - for r in d.get('host_routes', [])] - return cls( - d['id'], - d['cidr'], - d['gateway_ip'], - d['dhcp_enabled'], - d['dns_nameservers'], - host_routes) - - -class Network(ModelBase): - SERVICE_STATIC = 'static' - SERVICE_RA = 'ra' - SERVICE_DHCP = 'dhcp' - TYPE_EXTERNAL = 'external' - TYPE_INTERNAL = 'internal' - TYPE_ISOLATED = 'isolated' - TYPE_MANAGEMENT = 'management' - TYPE_LOADBALANCER = 'loadbalancer' - - # TODO(mark): add subnet support for Quantum subnet host routes - - def __init__(self, id_, interface, name='', network_type=TYPE_ISOLATED, - v4_conf_service=SERVICE_STATIC, - v6_conf_service=SERVICE_STATIC, - address_allocations=None, - subnets=None, ha=False): - self.id = id_ - self.interface = interface - self.name = name - self.network_type = network_type - self.v4_conf_service = v4_conf_service - self.v6_conf_service = v6_conf_service - self.address_allocations = address_allocations or [] - self.subnets = subnets or [] - self.floating_ips = [] - self.ha = ha - - @property - def is_tenant_network(self): - return self._network_type in (self.TYPE_INTERNAL, self.TYPE_ISOLATED) - - @property - def is_internal_network(self): - return self._network_type == self.TYPE_INTERNAL - - @property - def is_external_network(self): - return self._network_type == self.TYPE_EXTERNAL - - @property - def is_management_network(self): - return self._network_type == self.TYPE_MANAGEMENT - - @property - def network_type(self): - return self._network_type - - @network_type.setter - def network_type(self, value): - network_types = (self.TYPE_EXTERNAL, self.TYPE_INTERNAL, - self.TYPE_ISOLATED, self.TYPE_MANAGEMENT, - self.TYPE_LOADBALANCER) - if value not in network_types: - msg = ('network must be one of %s not (%s).' % - ('|'.join(network_types), value)) - raise ValueError(msg) - self._network_type = value - - @property - def v4_conf_service(self): - return self._v4_conf_service - - @v4_conf_service.setter - def v4_conf_service(self, value): - if value not in (self.SERVICE_DHCP, self.SERVICE_STATIC): - msg = ('v4_conf_service must be one of dhcp|static not (%s).' % - value) - raise ValueError(msg) - self._v4_conf_service = value - - @property - def v6_conf_service(self): - return self._v6_conf_service - - @v6_conf_service.setter - def v6_conf_service(self, value): - if value not in (self.SERVICE_DHCP, self.SERVICE_RA, - self.SERVICE_STATIC): - msg = ('v6_conf_service must be one of dhcp|ra|static not (%s).' % - value) - raise ValueError(msg) - self._v6_conf_service = value - - def to_dict(self): - return dict( - network_id=self.id, - interface=self.interface, - name=self.name, - network_type=self.network_type, - v4_conf_service=self.v4_conf_service, - v6_conf_service=self.v6_conf_service, - address_allocations=self.address_allocations - ) - - @classmethod - def from_dict(cls, d): - missing = [] - for k in ['network_id', 'interface']: - if not d.get(k): - missing.append(k) - if missing: - raise ValueError('Missing required data: %s.' % missing) - - if d.get('network_type') == cls.TYPE_MANAGEMENT: - d['interface']['management'] = True - else: - d['interface']['management'] = False - - return cls( - d['network_id'], - interface=Interface.from_dict(d['interface']), - name=d.get('name', ''), - network_type=d.get('network_type', cls.TYPE_ISOLATED), - v6_conf_service=d.get('v6_conf_service', cls.SERVICE_STATIC), - v4_conf_service=d.get('v4_conf_service', cls.SERVICE_STATIC), - address_allocations=[ - Allocation.from_dict(a) for a in d.get('allocations', [])], - subnets=[Subnet.from_dict(s) for s in d.get('subnets', [])], - ha=d.get('ha', False)) - - -class LoadBalancer(ModelBase): - def __init__(self, id_, tenant_id, name, admin_state_up, status, - vip_address, vip_port=None, listeners=()): - self.id = id_ - self.tenant_id = tenant_id - self.name = name - self.admin_state_up = admin_state_up - self.status = status - self.vip_address = vip_address - self.vip_port = vip_port - self.listeners = listeners - - @classmethod - def from_dict(cls, d): - if d.get('listeners'): - d['listeners'] = [ - Listener.from_dict(l) for l in d.get('listeners', []) - ] - if d.get('vip_port'): - d['vip_port'] = Port.from_dict(d.get('vip_port')) - out = cls( - d['id'], - d['tenant_id'], - d['name'], - d['admin_state_up'], - d['status'], - d['vip_address'], - d['vip_port'], - d['listeners'], - ) - return out - - -class Listener(ModelBase): - def __init__(self, id_, tenant_id, name, admin_state_up, protocol, - protocol_port, default_pool=None): - self.id = id_ - self.tenant_id = tenant_id - self.name = name - self.admin_state_up = admin_state_up - self.protocol = protocol - self.protocol_port = protocol_port - self.default_pool = default_pool - - @classmethod - def from_dict(cls, d): - if d.get('default_pool'): - def_pool = Pool.from_dict(d['default_pool']) - else: - def_pool = None - - return cls( - d['id'], - d['tenant_id'], - d['name'], - d['admin_state_up'], - d['protocol'], - d['protocol_port'], - def_pool, - ) - - def to_dict(self): - fields = ('id', 'tenant_id', 'name', 'admin_state_up', 'protocol', - 'protocol_port') - out = dict((f, getattr(self, f)) for f in fields) - if self.default_pool: - out['default_pool'] = self.default_pool.to_dict() - else: - out['default_pool'] = None - return out - - -class Pool(ModelBase): - def __init__(self, id_, tenant_id, name, admin_state_up, lb_algorithm, - protocol, healthmonitor=None, session_persistence=None, - members=()): - self.id = id_ - self.tenant_id = tenant_id - self.name = name - self.admin_state_up = admin_state_up - self.lb_algorithm = lb_algorithm - self.protocol = protocol - self.healthmonitor = healthmonitor - self.session_persistence = session_persistence - self.members = members - - @classmethod - def from_dict(cls, d): - return cls( - d['id'], - d['tenant_id'], - d['name'], - d['admin_state_up'], - d['lb_algorithm'], - d['protocol'], - d.get('healthmonitor'), - d.get('session_persistence'), - [Member.from_dict(m) for m in d.get('members', [])], - ) - - def to_dict(self): - fields = ('id', 'tenant_id', 'name', 'admin_state_up', - 'lb_algorithm', 'protocol', 'healthmonitor', - 'session_persistence') - out = dict((f, getattr(self, f)) for f in fields) - out['members'] = [m.to_dict() for m in self.members] - return out - - -class Member(ModelBase): - def __init__(self, id_, tenant_id, admin_state_up, address, protocol_port, - weight, subnet=None): - self.id = id_ - self.tenant_id = tenant_id - self.admin_state_up = admin_state_up - self.address = str(netaddr.IPAddress(address)) - self.protocol_port = protocol_port - self.weight = weight - self.subnet = subnet - - @classmethod - def from_dict(cls, d): - return cls( - d['id'], - d['tenant_id'], - d['admin_state_up'], - d['address'], - d['protocol_port'], - d['weight'], - ) - - def to_dict(self): - fields = ('id', 'tenant_id', 'admin_state_up', 'address', - 'protocol_port', 'weight', 'subnet') - return dict((f, getattr(self, f)) for f in fields) - - -class Port(ModelBase): - def __init__(self, id_, device_id='', fixed_ips=None, mac_address='', - network_id='', device_owner='', name=''): - self.id = id_ - self.device_id = device_id - self.fixed_ips = fixed_ips or [] - self.mac_address = mac_address - self.network_id = network_id - self.device_owner = device_owner - self.name = name - - @classmethod - def from_dict(cls, d): - return cls( - d['id'], - d['device_id'], - fixed_ips=[FixedIp.from_dict(fip) for fip in d['fixed_ips']], - mac_address=d['mac_address'], - network_id=d['network_id'], - device_owner=d['device_owner'], - name=d['name']) - - def to_dict(self): - fields = ('id', 'device_id', 'mac_address', 'network_id', - 'device_owner', 'name') - out = dict((f, getattr(self, f)) for f in fields) - out['fixed_ips'] = [fip.to_dict() for fip in self.fixed_ips] - return out - - -class FixedIp(ModelBase): - def __init__(self, subnet_id, ip_address): - self.subnet_id = subnet_id - self.ip_address = netaddr.IPAddress(ip_address) - - @classmethod - def from_dict(cls, d): - return cls(d['subnet_id'], d['ip_address']) - - def to_dict(self): - fields = ('subnet_id', 'ip_address') - return dict((f, getattr(self, f)) for f in fields) - - -class DeadPeerDetection(ModelBase): - def __init__(self, action, interval, timeout): - self.action = action - self.interval = interval - self.timeout = timeout - - @classmethod - def from_dict(cls, d): - return cls( - d['action'], - d['interval'], - d['timeout'] - ) - - -class Lifetime(ModelBase): - def __init__(self, units, value): - self.units = units - self.value = value - - @classmethod - def from_dict(cls, d): - return cls( - d['units'], - d['value'] - ) - - -class EndpointGroup(ModelBase): - def __init__(self, id_, tenant_id, name, type_, endpoints=()): - self.id = id_ - self.tenant_id = tenant_id - self.name = name - self.type = type_ - if type_ == 'cidr': - self.endpoints = [netaddr.IPNetwork(ep) for ep in endpoints] - else: - self.endpoints = endpoints - self.subnet_map = {} - - @property - def cidrs(self): - if self.type == 'subnet': - return [ - self.subnet_map[ep].cidr - for ep in self.endpoints - if ep in self.subnet_map - ] - else: - return self.endpoints - - @classmethod - def from_dict(cls, d): - return cls( - d['id'], - d['tenant_id'], - d['name'], - d['type'], - d['endpoints'] - ) - - -class IkePolicy(ModelBase): - def __init__(self, id_, tenant_id, name, ike_version, auth_algorithm, - encryption_algorithm, pfs, phase1_negotiation_mode, lifetime): - self.id = id_ - self.tenant_id = tenant_id - self.name = name - self.ike_version = ike_version - self.auth_algorithm = auth_algorithm - self.encryption_algorithm = encryption_algorithm - self.pfs = pfs - self.phase1_negotiation_mode = phase1_negotiation_mode - self.lifetime = lifetime - - @classmethod - def from_dict(cls, d): - return cls( - d['id'], - d['tenant_id'], - d['name'], - d['ike_version'], - d['auth_algorithm'], - d['encryption_algorithm'], - d['pfs'], - d['phase1_negotiation_mode'], - Lifetime.from_dict(d['lifetime']) - ) - - -class IpsecPolicy(ModelBase): - def __init__(self, id_, tenant_id, name, transform_protocol, - auth_algorithm, encryption_algorithm, encapsulation_mode, - lifetime, pfs): - self.id = id_ - self.tenant_id = tenant_id - self.name = name - self.transform_protocol = transform_protocol - self.auth_algorithm = auth_algorithm - self.encryption_algorithm = encryption_algorithm - self.encapsulation_mode = encapsulation_mode - self.lifetime = lifetime - self.pfs = pfs - - @classmethod - def from_dict(cls, d): - return cls( - d['id'], - d['tenant_id'], - d['name'], - d['transform_protocol'], - d['auth_algorithm'], - d['encryption_algorithm'], - d['encapsulation_mode'], - Lifetime.from_dict(d['lifetime']), - d['pfs'] - ) - - -class IpsecSiteConnection(ModelBase): - def __init__(self, id_, tenant_id, name, peer_address, peer_id, - admin_state_up, route_mode, mtu, initiator, auth_mode, psk, - dpd, status, vpnservice_id, local_ep_group=None, - peer_ep_group=None, peer_cidrs=[], ikepolicy=None, - ipsecpolicy=None): - self.id = id_ - self.tenant_id = tenant_id - self.name = name - self.peer_address = netaddr.IPAddress(peer_address) - self.peer_id = peer_id - self.route_mode = route_mode - self.mtu = mtu - self.initiator = initiator - self.auth_mode = auth_mode - self.psk = psk - self.dpd = dpd - self.status = status - self.admin_state_up = admin_state_up - self.vpnservice_id = vpnservice_id - self.ipsecpolicy = ipsecpolicy - self.ikepolicy = ikepolicy - self.local_ep_group = local_ep_group - self.peer_ep_group = peer_ep_group - self.peer_cidrs = [netaddr.IPNetwork(pc) for pc in peer_cidrs] - - @classmethod - def from_dict(cls, d): - return cls( - d['id'], - d['tenant_id'], - d['name'], - d['peer_address'], - d['peer_id'], - d['admin_state_up'], - d['route_mode'], - d['mtu'], - d['initiator'], - d['auth_mode'], - d['psk'], - DeadPeerDetection.from_dict(d['dpd']), - d['status'], - d['vpnservice_id'], - peer_cidrs=d['peer_cidrs'], - ikepolicy=IkePolicy.from_dict(d['ikepolicy']), - ipsecpolicy=IpsecPolicy.from_dict(d['ipsecpolicy']), - local_ep_group=EndpointGroup.from_dict(d['local_ep_group']), - peer_ep_group=EndpointGroup.from_dict(d['peer_ep_group']), - ) - - -class VpnService(ModelBase): - def __init__(self, id_, name, status, admin_state_up, external_v4_ip, - external_v6_ip, router_id, subnet_id=None, - ipsec_site_connections=()): - self.id = id_ - self.name = name - self.status = status - self.admin_state_up = admin_state_up - self.external_v4_ip = netaddr.IPAddress(external_v4_ip) - self.external_v6_ip = netaddr.IPAddress(external_v6_ip) - self.router_id = router_id - self.subnet_id = subnet_id - self.ipsec_site_connections = ipsec_site_connections - - def get_external_ip(self, peer_ip): - if peer_ip.version == '6': - return self.external_v6_ip - else: - return self.external_v4_ip - - @classmethod - def from_dict(cls, d): - return cls( - d['id'], - d['name'], - d['status'], - d['admin_state_up'], - d['external_v4_ip'], - d['external_v6_ip'], - d['router_id'], - d.get('subnet_id'), - [IpsecSiteConnection.from_dict(c) for c in d['ipsec_connections']] - ) - - -class SystemConfiguration(ModelBase): - service_name = 'system' - - def __init__(self, conf_dict={}): - self.tenant_id = conf_dict.get('tenant_id') - self.hostname = conf_dict.get('hostname') - self.networks = [ - Network.from_dict(n) for n in conf_dict.get('networks', [])] - self.ha = conf_dict.get('ha_resource', False) - self.ha_config = conf_dict.get('ha_config', {}) - - def validate(self): - # TODO: Improve this interface, it currently sucks. - errors = [] - for attr in ['tenant_id', 'hostname']: - if not getattr(self, attr): - errors.append((attr, 'Config does not contain a %s' % attr)) - return errors - - @property - def management_address(self): - addrs = [] - for net in self.networks: - if net.is_management_network: - addrs.extend((net.interface.first_v4, net.interface.first_v6)) - - addrs = sorted(a for a in addrs if a) - - if addrs: - return addrs[0] - - @property - def interfaces(self): - return [n.interface for n in self.networks if n.interface] - - def to_dict(self): - fields = ('tenant_id', 'hostname', 'management_address', 'interfaces') - return dict((f, getattr(self, f)) for f in fields) - - -class RouterConfiguration(SystemConfiguration): - service_name = 'router' - - def __init__(self, conf_dict={}): - super(RouterConfiguration, self).__init__(conf_dict) - gw = conf_dict.get('default_v4_gateway') - self.default_v4_gateway = netaddr.IPAddress(gw) if gw else None - self.asn = conf_dict.get('asn', DEFAULT_AS) - self.neighbor_asn = conf_dict.get('neighbor_asn', self.asn) - self.static_routes = [StaticRoute(*r) for r in - conf_dict.get('static_routes', [])] - - self.address_book = dict( - (name, AddressBookEntry(name, cidrs)) for name, cidrs in - conf_dict.get('address_book', {}).iteritems()) - - self.anchors = [ - Anchor(a['name'], [FilterRule.from_dict(r) for r in a['rules']]) - for a in conf_dict.get('anchors', [])] - - self.labels = [ - Label(name, cidr) for name, cidr in - conf_dict.get('labels', {}).iteritems()] - - orchestrator_conf = conf_dict.get('orchestrator', {}) - self.metadata_address = orchestrator_conf.get( - 'address', settings.ORCHESTRATOR_METADATA_ADDRESS) - self.metadata_port = orchestrator_conf.get( - 'metadata_port', settings.ORCHESTRATOR_METADATA_PORT) - - self.floating_ips = [ - FloatingIP.from_dict(fip) - for fip in conf_dict.get('floating_ips', []) - ] - - self._attach_floating_ips(self.floating_ips) - - self.vpn = [ - VpnService.from_dict(s) - for s in conf_dict.get('vpn', {}).get('ipsec', []) - ] - - self._link_subnets() - - def validate(self): - """Validate anchor rules to ensure that ifaces and tables exist.""" - interfaces = set(n.interface.ifname for n in self.networks) - errors = [] - for anchor in self.anchors: - for rule in anchor.rules: - for iface in (rule.interface, rule.destination_interface): - if iface and iface not in interfaces: - errors.append((rule, '%s does not exist' % iface)) - - for address in (rule.source, rule.destination): - if not address or isinstance(address, netaddr.IPNetwork): - pass - elif address in self.address_book: - pass - else: - reason = '%s is not in the address book' % address - errors.append((rule, reason)) - - return ["'%s' %s" % e for e in errors] - - def _attach_floating_ips(self, floating_ips): - ext_cidr_map = {} - int_cidr_map = {} - - for network in self.networks: - if network.is_external_network: - m = ext_cidr_map - elif network.is_internal_network: - m = int_cidr_map - else: - continue - m.update((s.cidr, network) for s in network.subnets) - - for fip in floating_ips: - # add address to external interface - for ext_cidr, net in ext_cidr_map.items(): - if fip.floating_ip in ext_cidr: - addr = '%s/%s' % (fip.floating_ip, ext_cidr.prefixlen) - net.interface.aliases += [netaddr.IPNetwork(addr)] - net.floating_ips.append(fip) - break - - # add to internal - for int_cidr, net in int_cidr_map.items(): - if fip.fixed_ip in int_cidr: - fip.network = net - - def _link_subnets(self): - subnet_map = {} - for n in self.networks: - for s in n.subnets: - subnet_map[s.id] = s - - vpn_conn_generator = (v.ipsec_site_connections for v in self.vpn) - - for conn in itertools.chain.from_iterable(vpn_conn_generator): - if conn.local_ep_group.type == 'subnet': - conn.local_ep_group.subnet_map = subnet_map - - def to_dict(self): - fields = ( - 'networks', 'address_book', 'anchors', 'static_routes', 'vpn' - ) - return dict((f, getattr(self, f)) for f in fields) - - @property - def external_v4_id(self): - addrs = [n.interface.first_v4 - for n in self.networks if n.is_external_network] - - # remove any none - addrs = sorted(a for a in addrs if a) - - if addrs: - return addrs[0] - - -class LoadBalancerConfiguration(SystemConfiguration): - service_name = 'loadbalancer' - - def __init__(self, conf_dict={}): - super(LoadBalancerConfiguration, self).__init__(conf_dict) - self.id = conf_dict.get('id') - self.name = conf_dict.get('name') - if conf_dict: - self._loadbalancer = LoadBalancer.from_dict(conf_dict) - self.vip_port = self._loadbalancer.vip_port - self.vip_address = self._loadbalancer.vip_address - self.listeners = self._loadbalancer.listeners - else: - self.vip_port = None - self.vip_address = None - self.listeners = [] - - def validate(self): - super(LoadBalancerConfiguration, self).validate() - errors = [] - if not self.id: - errors.append(['id', 'Missing in config id']) - return errors - - def to_dict(self): - if self.vip_port: - vip_port = self.vip_port.to_dict() - else: - vip_port = {} - return { - 'id': self.id, - 'name': self.name, - 'vip_port': vip_port, - 'vip_address': self.vip_address, - 'listeners': [l.to_dict() for l in self.listeners], - } - -SERVICE_MAP = { - RouterConfiguration.service_name: RouterConfiguration, - LoadBalancerConfiguration.service_name: LoadBalancerConfiguration, -} - - -def get_config_model(service): - return SERVICE_MAP[service] diff --git a/astara_router/settings.py b/astara_router/settings.py deleted file mode 100644 index ff64550..0000000 --- a/astara_router/settings.py +++ /dev/null @@ -1,10 +0,0 @@ -from astara_router.defaults import * # noqa - -# If astara_local_settings.py is located in your python path, -# it can be used to override the defaults. DIB will install this -# into /usr/local/share/astara and append that path to the gunicorn's -# python path. -try: - from astara_local_settings import * # noqa -except ImportError: - pass diff --git a/astara_router/utils.py b/astara_router/utils.py deleted file mode 100644 index a9ba064..0000000 --- a/astara_router/utils.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 functools -import hashlib -import json -import os -import shlex -import subprocess -import tempfile - -import flask -import jinja2 -import netaddr - -from astara_router import models - -DEFAULT_ENABLED_SERVICES = ['router'] -VALID_SERVICES = ['router', 'loadbalancer'] - - -class TemplateNotFound(Exception): - pass - - -def execute(args, root_helper=None): - if root_helper: - cmd = shlex.split(root_helper) + args - else: - cmd = args - try: - return subprocess.check_output(map(str, cmd), stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - # The serialization layer doesn't know about the extra output - # attribute of the CalledProcessError, so we don't get - # stdout/stderr. Convert to a simpler exception type and - # include all of the info from the original exception in the - # message. - raise RuntimeError('%s: %s' % (e, e.output)) - - -def replace_file(file_name, data): - """Replaces the contents of file_name with data in a safe manner. - - First write to a temp file and then rename. Since POSIX renames are - atomic, the file is unlikely to be corrupted by competing writes. - - We create the tempfile on the same device to ensure that it can be renamed. - """ - base_dir = os.path.dirname(os.path.abspath(file_name)) - tmp_file = tempfile.NamedTemporaryFile('w+', dir=base_dir, delete=False) - tmp_file.write(data) - tmp_file.close() - os.chmod(tmp_file.name, 0644) - os.rename(tmp_file.name, file_name) - - -def ensure_directory(dir_path): - if not os.path.isdir(dir_path): - os.makedirs(dir_path, 0755) - - -class ModelSerializer(json.JSONEncoder): - """ - """ - def default(self, obj): - if isinstance(obj, set): - return list(obj) - elif isinstance(obj, netaddr.IPNetwork): - return str(obj) - elif isinstance(obj, netaddr.IPAddress): - return str(obj) - elif isinstance(obj, models.ModelBase): - if hasattr(obj, 'to_dict'): - return obj.to_dict() - else: - return vars(obj) - return super(ModelSerializer, self).default(obj) - - -def json_response(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - retval = f(*args, **kwargs) - if isinstance(retval, flask.Response): - return retval - else: - return flask.Response( - json.dumps(retval, cls=ModelSerializer, sort_keys=True), - status=200 - ) - return wrapper - - -def blueprint_factory(name): - name_parts = name.split(".")[-2:] - blueprint_name = "_".join(name_parts) - url_prefix = "/" + "/".join(name_parts) - return flask.Blueprint(blueprint_name, name, url_prefix=url_prefix) - - -def load_template(template_file): - if not os.path.exists(template_file): - raise TemplateNotFound( - 'Config template not found @ %s' % template_file) - return jinja2.Template(open(template_file).read()) - - -def hash_file(path): - h = hashlib.md5() - with open(path, 'rb') as _in: - h.update(_in.read()) - return h.hexdigest() diff --git a/astara_router/version.py b/astara_router/version.py deleted file mode 100644 index b5a1b05..0000000 --- a/astara_router/version.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2011 OpenStack Foundation -# All Rights Reserved. -# -# 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 pbr.version - -version_info = pbr.version.VersionInfo('astara_router') diff --git a/diskimage-builder/README.md b/diskimage-builder/README.md deleted file mode 100644 index 8b24fba..0000000 --- a/diskimage-builder/README.md +++ /dev/null @@ -1,22 +0,0 @@ -This directory contains elements necessary to build the Astara appliance with -the diskimage-builder from the OpenStack project. - -1) Install diskimage-builder via: - - pip install diskimage-builder - or source at: - http://git.openstack.org/cgit/openstack/diskimage-builder - -2) Ensure a few require packages are installed: - - debootstrap - - qemu-utils - -3) Add elements to path - $ export ELEMENTS_PATH=~/astara-appliance/diskimage-builder/elements - -4) Build image - $ DIB_RELEASE=jessie DIB_EXTLINUX=1 disk-image-create debian vm astara - -5) If you're testing with kvm, don't forget to build the nocloud iso image - - diff --git a/diskimage-builder/elements/ansible/README.md b/diskimage-builder/elements/ansible/README.md deleted file mode 100644 index dfad942..0000000 --- a/diskimage-builder/elements/ansible/README.md +++ /dev/null @@ -1,11 +0,0 @@ -Install Ansible. - -Configuration -------------- - -At Present there is no configuration for this element. - -NOTICE ------- -This element is copied from the OpenStack Tripleo project at -http://git.openstack.org/cgit/openstack/tripleo-image-elements/ diff --git a/diskimage-builder/elements/ansible/cleanup.d/50-remove-source-ansible b/diskimage-builder/elements/ansible/cleanup.d/50-remove-source-ansible deleted file mode 100755 index 5a8d8b4..0000000 --- a/diskimage-builder/elements/ansible/cleanup.d/50-remove-source-ansible +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -set -eu - -sudo rm -fr "${TMP_MOUNT_PATH}/opt/stack/tripleo-ansible" diff --git a/diskimage-builder/elements/ansible/element-deps b/diskimage-builder/elements/ansible/element-deps deleted file mode 100644 index 7c8922a..0000000 --- a/diskimage-builder/elements/ansible/element-deps +++ /dev/null @@ -1 +0,0 @@ -pip-and-virtualenv diff --git a/diskimage-builder/elements/ansible/environment.d/10-ansible-venv-dir.bash b/diskimage-builder/elements/ansible/environment.d/10-ansible-venv-dir.bash deleted file mode 100755 index 3eb9403..0000000 --- a/diskimage-builder/elements/ansible/environment.d/10-ansible-venv-dir.bash +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -ue - -export ANSIBLE_VENV_DIR=${ANSIBLE_VENV_DIR:-"/opt/stack/venvs/ansible"} diff --git a/diskimage-builder/elements/ansible/install.d/ansible-package-install/30-ansible-package b/diskimage-builder/elements/ansible/install.d/ansible-package-install/30-ansible-package deleted file mode 100644 index 99caabc..0000000 --- a/diskimage-builder/elements/ansible/install.d/ansible-package-install/30-ansible-package +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -eux - -install-packages ansible diff --git a/diskimage-builder/elements/ansible/install.d/ansible-source-install/30-ansible-source b/diskimage-builder/elements/ansible/install.d/ansible-source-install/30-ansible-source deleted file mode 100755 index 5a6a23f..0000000 --- a/diskimage-builder/elements/ansible/install.d/ansible-source-install/30-ansible-source +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# -# Copyright 2014 Hewlett-Packard Development Company, L.P. -# All Rights Reserved. -# -# 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. -# - -set -eux -set -o pipefail - -install-packages build-essential libssl-dev libyaml-dev python-dev libxml2-dev libxslt-dev libffi-dev - -virtualenv $ANSIBLE_VENV_DIR - -set +u -source $ANSIBLE_VENV_DIR/bin/activate -set -u - -$ANSIBLE_VENV_DIR/bin/pip install paramiko PyYAML jinja2 httplib2 - -$ANSIBLE_VENV_DIR/bin/pip install ansible==1.8.1 - -ln -s $ANSIBLE_VENV_DIR/bin/ansible /usr/local/bin/ansible -ln -s $ANSIBLE_VENV_DIR/bin/ansible-playbook /usr/local/bin/ansible-playbook diff --git a/diskimage-builder/elements/astara/README.rst b/diskimage-builder/elements/astara/README.rst deleted file mode 100644 index 796311f..0000000 --- a/diskimage-builder/elements/astara/README.rst +++ /dev/null @@ -1,9 +0,0 @@ -This is the base element for building an Astara appliance image. - -Ansible is required on the local system. - -Advanced service drivers may be enabled in the appliance by setting -``DIB_ASTARA_ADVANCED_SERVICES``. This defaults to enabling only the -router driver, but you may enabled other avialable drivers ie: - -DIB_ASTARA_ADVANCED_SERVICES=router,loadbalancer diff --git a/diskimage-builder/elements/astara/element-deps b/diskimage-builder/elements/astara/element-deps deleted file mode 100644 index 9ee9ee8..0000000 --- a/diskimage-builder/elements/astara/element-deps +++ /dev/null @@ -1,3 +0,0 @@ -cloud-init-datasources -source-repositories -ansible diff --git a/diskimage-builder/elements/astara/environment.d/10-cloudinit-akanda b/diskimage-builder/elements/astara/environment.d/10-cloudinit-akanda deleted file mode 100755 index 10cdc63..0000000 --- a/diskimage-builder/elements/astara/environment.d/10-cloudinit-akanda +++ /dev/null @@ -1 +0,0 @@ -export DIB_CLOUD_INIT_DATASOURCES="ConfigDrive, NoCloud" diff --git a/diskimage-builder/elements/astara/install.d/astara-source-install/70-astara b/diskimage-builder/elements/astara/install.d/astara-source-install/70-astara deleted file mode 100755 index b196b15..0000000 --- a/diskimage-builder/elements/astara/install.d/astara-source-install/70-astara +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -set -eux -set -o pipefail - -DIB_ASTARA_ADVANCED_SERVICES=${DIB_ASTARA_ADVANCED_SERVICES:-"router"} - -APP_SRC_DIR="/tmp/astara-appliance" - -[ -d "${APP_SRC_DIR}" ] || exit 0 - -ansible-playbook -i "localhost," -c local -e enabled_advanced_services="$DIB_ASTARA_ADVANCED_SERVICES" $APP_SRC_DIR/ansible/main.yml diff --git a/diskimage-builder/elements/astara/post-install.d/90-fix-locale b/diskimage-builder/elements/astara/post-install.d/90-fix-locale deleted file mode 100755 index f83fe84..0000000 --- a/diskimage-builder/elements/astara/post-install.d/90-fix-locale +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# ensure the locale is properly setup -sed -i 's/^# en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen -locale-gen diff --git a/diskimage-builder/elements/astara/source-repository-astara b/diskimage-builder/elements/astara/source-repository-astara deleted file mode 100644 index 0d6058c..0000000 --- a/diskimage-builder/elements/astara/source-repository-astara +++ /dev/null @@ -1 +0,0 @@ -astara git /tmp/astara-appliance https://github.com/openstack/astara-appliance.git diff --git a/diskimage-builder/elements/debug-user/README.md b/diskimage-builder/elements/debug-user/README.md deleted file mode 100644 index 3db28f6..0000000 --- a/diskimage-builder/elements/debug-user/README.md +++ /dev/null @@ -1,10 +0,0 @@ - -Creates a sudo privileged user in the appliance VM that can be used for -debugging connectivity issues via the console, when SSH connectivity is -not possible. Note that an 'astara' user is created by the RUG and setup -to authenticate using a SSH public key. This element should only be included -when building images for develoment environments. - -The username and password can be set in the build environment as -$DIB_ASTARA_APPLIANCE_DEBUG_USER and $DIB_ASTARA_APPLIANCE_DEBUG_PASSWORD -The defaults are astara-debug/astara. diff --git a/diskimage-builder/elements/debug-user/install.d/50-debug-user b/diskimage-builder/elements/debug-user/install.d/50-debug-user deleted file mode 100755 index 4648316..0000000 --- a/diskimage-builder/elements/debug-user/install.d/50-debug-user +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -DIB_ASTARA_APPLIANCE_DEBUG_USER=${DIB_ASTARA_APPLIANCE_DEBUG_USER:-astara-debug} -DIB_ASTARA_APPLIANCE_DEBUG_PASSWORD=${DIB_ASTARA_APPLIANCE_DEBUG_PASSWORD:-astara} - -set -eu -set -o xtrace - -useradd -m $DIB_ASTARA_APPLIANCE_DEBUG_USER -s /bin/bash - -passwd $DIB_ASTARA_APPLIANCE_DEBUG_USER < /etc/sudoers.d/astara-debug-user <> $TMP_HOOKS_PATH/nginx-repo.crt -cat $DIB_NGINX_PLUS_CERT_PATH/nginx-repo.key >> $TMP_HOOKS_PATH/nginx-repo.key diff --git a/diskimage-builder/elements/nginx-plus/install.d/package-installs-nginx-plus b/diskimage-builder/elements/nginx-plus/install.d/package-installs-nginx-plus deleted file mode 100644 index 76235f3..0000000 --- a/diskimage-builder/elements/nginx-plus/install.d/package-installs-nginx-plus +++ /dev/null @@ -1 +0,0 @@ -nginx-plus diff --git a/diskimage-builder/elements/nginx-plus/post-install.d/99-cleanup b/diskimage-builder/elements/nginx-plus/post-install.d/99-cleanup deleted file mode 100755 index 30d4257..0000000 --- a/diskimage-builder/elements/nginx-plus/post-install.d/99-cleanup +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -xe -# Copyright (c) 2015 Akanda, Inc. All Rights Reserved. -# -# 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. - -rm -rf /etc/nginx/conf.d/default diff --git a/diskimage-builder/elements/nginx-plus/pre-install.d/02-nginx-plus-prep b/diskimage-builder/elements/nginx-plus/pre-install.d/02-nginx-plus-prep deleted file mode 100755 index 959f06c..0000000 --- a/diskimage-builder/elements/nginx-plus/pre-install.d/02-nginx-plus-prep +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -e -# Copyright (c) 2015 Akanda, Inc. All Rights Reserved. -# -# 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 scripts preps apt with the nginx plus repo - -# install required deps for this script -apt-get install lsb-release ca-certificates krb5-locales libsasl2-modules apt-utils apt-transport-https wget -y - -# make nginx ssl dir -mkdir -p /etc/ssl/nginx -cp /tmp/in_target.d/nginx-repo.crt /etc/ssl/nginx/ -cp /tmp/in_target.d/nginx-repo.key /etc/ssl/nginx/ - -wget https://cs.nginx.com/static/files/CA.crt -P /etc/ssl/nginx/ -wget http://nginx.org/keys/nginx_signing.key -P /etc/ssl/nginx/ - -apt-key add /etc/ssl/nginx/nginx_signing.key - -printf "deb https://plus-pkgs.nginx.com/debian `lsb_release -cs` nginx-plus\n" >/etc/apt/sources.list.d/nginx-plus.list - -wget https://cs.nginx.com/static/files/90nginx -P /etc/apt/apt.conf.d - -apt-get update diff --git a/diskimage-builder/elements/nginx/README.rst b/diskimage-builder/elements/nginx/README.rst deleted file mode 100644 index 2b1a1fb..0000000 --- a/diskimage-builder/elements/nginx/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -This element installs the open source nginx package. - diff --git a/diskimage-builder/elements/nginx/element-deps b/diskimage-builder/elements/nginx/element-deps deleted file mode 100644 index f59cb68..0000000 --- a/diskimage-builder/elements/nginx/element-deps +++ /dev/null @@ -1,2 +0,0 @@ -package-installs -astara diff --git a/diskimage-builder/elements/nginx/install.d/package-installs-nginx b/diskimage-builder/elements/nginx/install.d/package-installs-nginx deleted file mode 100644 index 68b7d12..0000000 --- a/diskimage-builder/elements/nginx/install.d/package-installs-nginx +++ /dev/null @@ -1 +0,0 @@ -nginx diff --git a/diskimage-builder/elements/nginx/post-install.d/99-set-nginx-sites-enabled b/diskimage-builder/elements/nginx/post-install.d/99-set-nginx-sites-enabled deleted file mode 100755 index 3c0bdf1..0000000 --- a/diskimage-builder/elements/nginx/post-install.d/99-set-nginx-sites-enabled +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -xe -# Copyright (c) 2015 Akanda, Inc. All Rights Reserved. -# -# 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. - -rm -rf /etc/nginx/sites-enabled/default - -# astara API appliance runs as gunicorn user and needs to create files here -chown gunicorn.gunicorn /etc/nginx/sites-enabled diff --git a/etc/rootwrap.conf b/etc/rootwrap.conf deleted file mode 100644 index cf4258d..0000000 --- a/etc/rootwrap.conf +++ /dev/null @@ -1,27 +0,0 @@ -# Configuration for astara-rootwrap -# This file should be owned by (and only-writeable by) the root user - -[DEFAULT] -# List of directories to load filter definitions from (separated by ','). -# These directories MUST all be only writeable by root ! -filters_path=/etc/rootwrap.d - -# List of directories to search executables in, in case filters do not -# explicitely specify a full path (separated by ',') -# If not specified, defaults to system PATH environment variable. -# These directories MUST all be only writeable by root ! -exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin - -# Enable logging to syslog -# Default value is False -use_syslog=False - -# Which syslog facility to use. -# Valid values include auth, authpriv, syslog, local0, local1... -# Default value is 'syslog' -syslog_log_facility=syslog - -# Which messages to log. -# INFO means log all usage -# ERROR means only log unsuccessful attempts -syslog_log_level=ERROR diff --git a/etc/rootwrap.d/network.filters b/etc/rootwrap.d/network.filters deleted file mode 100644 index 235f69f..0000000 --- a/etc/rootwrap.d/network.filters +++ /dev/null @@ -1,48 +0,0 @@ -# astara-rootwrap command filters for astara-appliance -# This file should be owned by (and only-writeable by) the root user - -[Filters] -# astara_router/drivers/bird.py: -mv_bird: RegExpFilter, mv, root, mv, /tmp/bird6\.conf, /etc/bird/bird6\.conf - -# astara_router/drivers/arp.py: 'arp'.. -arp: CommandFilter, /usr/sbin/arp, root -astara_gratuitous_arp: CommandFilter, astara-gratuitous-arp, root - -# astara_router/drivers/conntrackd.py: -mv_conntrackd: RegExpFilter, mv, root, mv, /tmp/conntrackd\.conf, /etc/conntrackd/conntrackd\.conf - -# astara_router/drivers/dnsmasq.py: -mv_dnsmasq: RegExpFilter, mv, root, mv, /tmp/dnsmasq\.conf, /etc/dnsmasq\.d/.*\.conf -rm: CommandFilter, rm, root - -# astara_router/drivers/hostname.py: -hostname: CommandFilter, /bin/hostname, root -mv_hostname: RegExpFilter, mv, root, mv, /tmp/hostname, /etc/hostname -mv_hosts: RegExpFilter, mv, root, mv, /tmp/hosts, /etc/hosts - -# astara_router/drivers/ip.py: -ip: IpFilter, ip, root -sysctl: CommandFilter, sysctl, root -conntrack: CommandFilter, conntrack, root - -# astara_router/drivers/keepalived.py: -mv_keepalived: RegExpFilter, mv, root, mv, /tmp/keepalived\.conf, /etc/keepalived/keepalived\.conf - -# astara_router/drivers/ping.py: -ping: CommandFilter, ping, root -ping6: CommandFilter, ping6, root - -# astara_router/drivers/iptables.py: -mv_rules: RegExpFilter, mv, root, mv, /tmp/ip.*tables\.rules, /etc/iptables/rules\.v.* -iptables: CommandFilter, iptables, root -ip6tables: CommandFilter, ip6tables, root - -# astara_router/drivers/metadata.py: -mv_metadata: RegExpFilter, mv, root, mv, /tmp/metadata\.conf, /etc/metadata\.conf - -# astara_router/drivers/vpn/ipsec.py: -mv_strongswan: RegExpFilter, mv, root, mv, /tmp/ipsec.*, /etc/ipsec.* - -# astara services -services: CommandFilter, service, root diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/releasenotes/notes/conntrackd-c179372033f4134e.yaml b/releasenotes/notes/conntrackd-c179372033f4134e.yaml deleted file mode 100644 index 914b59d..0000000 --- a/releasenotes/notes/conntrackd-c179372033f4134e.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -features: - - The appliance is now built with conntrackd installed and supports configuring - the connection tracking service among pairs of clustered HA router appliances. diff --git a/releasenotes/notes/ensure_interface_cache_refreshed-57ce5b0637282b60.yaml b/releasenotes/notes/ensure_interface_cache_refreshed-57ce5b0637282b60.yaml deleted file mode 100644 index db91d72..0000000 --- a/releasenotes/notes/ensure_interface_cache_refreshed-57ce5b0637282b60.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -fixes: - - Bug `1531651 `_ \- Fixes a stale interface cache from preventing additional router interfaces from being attached diff --git a/releasenotes/notes/initial-note-fd05752e5f3b87fb.yaml b/releasenotes/notes/initial-note-fd05752e5f3b87fb.yaml deleted file mode 100644 index 8878049..0000000 --- a/releasenotes/notes/initial-note-fd05752e5f3b87fb.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -prelude: Astara Appliance Mitaka Series Release v8.0.0. - diff --git a/releasenotes/notes/keepalived_manager-4ed9f900f3d3429b.yaml b/releasenotes/notes/keepalived_manager-4ed9f900f3d3429b.yaml deleted file mode 100644 index 0c5659a..0000000 --- a/releasenotes/notes/keepalived_manager-4ed9f900f3d3429b.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - The appliance is now built with keepalived installed and supports receiving - cluster configuration from ``astara-orchestartor``, which allows pairs of - appliance VMs to cluster among themselves, providing HA routers. diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py deleted file mode 100644 index 8bfac7a..0000000 --- a/releasenotes/source/conf.py +++ /dev/null @@ -1,275 +0,0 @@ -# -*- coding: utf-8 -*- -# 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. - -# Astara Release Notes documentation build configuration file, created by -# sphinx-quickstart on Tue Nov 3 17:40:50 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'oslosphinx', - 'reno.sphinxext', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Astara Appliance Release Notes' -copyright = u'2015, Astara Developers' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -from astara_router.version import version_info as astara_version -# The full version, including alpha/beta/rc tags. -release = astara_version.version_string_with_vcs() -# The short X.Y version. -version = astara_version.canonical_version_string() - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'AstaraReleaseNotesdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'AstaraReleaseNotes.tex', u'Astara Release Notes Documentation', - u'Astara Developers', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'astarareleasenotes', - u'Astara Appliance Release Notes Documentation', - [u'Astara Developers'], 1) -] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'AstaraReleaseNotes', - u'Astara Appliance Release Notes Documentation', - u'Astara Developers', 'AstaraApplianceReleaseNotes', - 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst deleted file mode 100644 index 64c0a24..0000000 --- a/releasenotes/source/index.rst +++ /dev/null @@ -1,8 +0,0 @@ -============================== -Astara Appliance Release Notes -============================== - -.. toctree:: - :maxdepth: 1 - - mitaka diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst deleted file mode 100644 index ff9fd26..0000000 --- a/releasenotes/source/mitaka.rst +++ /dev/null @@ -1,5 +0,0 @@ -========================================================== - Astara Appliance Mitaka Series Release Notes -========================================================== - -.. release-notes:: diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0f8783c..0000000 --- a/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -# 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. -Flask!=0.11,<1.0,>=0.10 # BSD -dogpile.cache>=0.6.1 # BSD -netaddr!=0.7.16,>=0.7.12 # BSD -eventlet!=0.18.3,>=0.18.2 # MIT -requests>=2.10.0 # Apache-2.0 -greenlet>=0.3.2 # MIT -oslo.rootwrap>=2.0.0 # Apache-2.0 -Jinja2>=2.8 # BSD License (3 clause) diff --git a/scripts/build_dib_appliance.sh b/scripts/build_dib_appliance.sh deleted file mode 100755 index 7c8c6e0..0000000 --- a/scripts/build_dib_appliance.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -xe -# Copyright 2015 Akanda, Inc. -# -# Author: Akanda, 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. - -# This builds an astara appliance VM image at -# $SRC_ROOT/build/astara_appliance.qcow2, containing the astara-appliance code -# as it is currently checked out in this local repository. - -SRC_ROOT="$(dirname $0)/.." -IMG_OUT=$SRC_ROOT/build/astara_appliance - -ASTARA_DEBIAN_RELEASE=${ASTARA_DEBIAN_RELEASE:-"jessie"} -BASE_ELEMENTS="vm debian astara nginx" -EXTRA_ELEMENTS="$@" - -GIT_HEAD="$(cd $SRC_ROOT && git log | head -n1 | awk '{ print $2 }')" - -echo "Building astara-appliance from $GIT_HEAD" - -DIB_REPOLOCATION_astara=$SRC_ROOT \ -DIB_REPOREF_astara=$GIT_HEAD \ -ELEMENTS_PATH=$SRC_ROOT/diskimage-builder/elements \ -DIB_RELEASE=$ASTARA_DEBIAN_RELEASE DIB_EXTLINUX=1 \ -DIB_ASTARA_ADVANCED_SERVICES="router,loadbalancer" \ -disk-image-create $BASE_ELEMENTS $EXTRA_ELEMENTS -o $IMG_OUT diff --git a/scripts/debug-config.py b/scripts/debug-config.py deleted file mode 100644 index 089882b..0000000 --- a/scripts/debug-config.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 sys -import pdb -from astara_router.models import Configuration - -if __name__ == '__main__': - # Simple script that helps debug faulty configurations - with open(sys.argv[1], 'r') as c: - try: - conf = Configuration(conf_dict=eval(c.read())) - print conf - print '-' * 80 - print conf.validate() - print '-' * 80 - except Exception as e: - pdb.set_trace() diff --git a/scripts/etc/init.d/astara-router-api-server b/scripts/etc/init.d/astara-router-api-server deleted file mode 100755 index cdaa334..0000000 --- a/scripts/etc/init.d/astara-router-api-server +++ /dev/null @@ -1,47 +0,0 @@ -#! /bin/sh - -### BEGIN INIT INFO -# Provides: astara-router-api-server -# Required-Start: $all -# Required-Stop: $all -# Default-Start: 2 3 4 5 -# Default-Stop: -# Short-Description: Astara Router API Server -### END INIT INFO - - -PATH=/bin:/usr/bin:/sbin:/usr/sbin -DAEMON="/usr/bin/gunicorn" -NAME="astara-router-api-server" -OPTIONS="--user gunicorn --pythonpath /usr/local/share/astara -c /etc/astara_gunicorn_config.py astara_router.api.server:app" -PIDFILE=/var/run/gunicorn.pid - -test -x $DAEMON || exit 0 - -. /lib/lsb/init-functions - -case "$1" in - start) - log_daemon_msg "Starting astara-router-api-server" $NAME - start_daemon -p $PIDFILE $DAEMON $OPTIONS - log_end_msg $? - ;; - stop) - log_daemon_msg "Stopping astara-router-api-server" $NAME - killproc -p $PIDFILE $DAEMON - log_end_msg $? - ;; - force-reload|restart) - $0 stop - $0 start - ;; - status) - status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $? - ;; - *) - echo "Usage: /etc/init.d/astara-router-api-server {start|stop|restart|force-reload|status}" - exit 1 - ;; -esac - -exit 0 diff --git a/scripts/etc/init.d/bird6 b/scripts/etc/init.d/bird6 deleted file mode 100755 index 2db233e..0000000 --- a/scripts/etc/init.d/bird6 +++ /dev/null @@ -1,139 +0,0 @@ -#! /bin/sh -### BEGIN INIT INFO -# Provides: bird6 -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -### END INIT INFO - -# Author: Ondřej Surý -# - -# PATH should only include /usr/* if it runs after the mountnfs.sh script -PATH=/sbin:/usr/sbin:/bin:/usr/bin -DESC="BIRD Internet Routing Daemon (IPv6)" -NAME=bird6 -DAEMON=/usr/sbin/$NAME -BIRD_ARGS="" -SCRIPTNAME=/etc/init.d/$NAME - -# Exit if the package is not installed -[ -x "$DAEMON" ] || exit 0 - -# read the RUN variables -. /etc/bird/envvars - -# Define LSB log_* functions. -# Depend on lsb-base (>= 3.0-6) to ensure that this file is present. -. /lib/lsb/init-functions - -# Create /run/bird with correct permissions -/usr/lib/bird/prepare-environment - -# -# Function that starts the daemon/service -# -do_start() -{ - # Return - # 0 if daemon has been started - # 1 if daemon was already running - # 2 if daemon could not be started - start-stop-daemon --start --quiet --name $NAME --exec $DAEMON --test > /dev/null \ - || return 1 - start-stop-daemon --start --quiet --name $NAME --exec $DAEMON -- \ - -u $BIRD_RUN_USER -g $BIRD_RUN_GROUP $BIRD_ARGS \ - || return 2 - # Add code here, if necessary, that waits for the process to be ready - # to handle requests from services started subsequently which depend - # on this one. As a last resort, sleep for some time. -} - -# -# Function that stops the daemon/service -# -do_stop() -{ - # Return - # 0 if daemon has been stopped - # 1 if daemon was already stopped - # 2 if daemon could not be stopped - # other if a failure occurred - start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --name $NAME --exec $DAEMON - RETVAL="$?" - [ "$RETVAL" = 2 ] && return 2 - # Wait for children to finish too if this is a daemon that forks - # and if the daemon is only ever run from this initscript. - start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --name $NAME --exec $DAEMON - [ "$?" = 2 ] && return 2 - return "$RETVAL" -} - -# -# Function that sends a SIGHUP to the daemon/service -# -do_reload() { - # - # If the daemon can reload its configuration without - # restarting (for example, when it is sent a SIGHUP), - # then implement that here. - # - start-stop-daemon --stop --signal 1 --quiet --name $NAME --exec $DAEMON - return 0 -} - -case "$1" in - start) - log_daemon_msg "Starting $DESC" "$NAME" - do_start - case "$?" in - 0|1) log_end_msg 0 ;; - 2) log_end_msg 1 ;; - esac - ;; - stop) - log_daemon_msg "Stopping $DESC" "$NAME" - do_stop - case "$?" in - 0|1) log_end_msg 0 ;; - 2) log_end_msg 1 ;; - esac - ;; - status) - status_of_proc $DAEMON $NAME && exit 0 || exit $? - ;; - reload|force-reload) - # - # If do_reload() is not implemented then leave this commented out - # and leave 'force-reload' as an alias for 'restart'. - # - log_daemon_msg "Reloading $DESC" "$NAME" - do_reload - log_end_msg $? - ;; - restart) - log_daemon_msg "Restarting $DESC" "$NAME" - do_stop - case "$?" in - 0|1) - do_start - case "$?" in - 0) log_end_msg 0 ;; - 1) log_end_msg 1 ;; # Old process is still running - *) log_end_msg 1 ;; # Failed to start - esac - ;; - *) - # Failed to stop - log_end_msg 1 - ;; - esac - ;; - *) - echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 - exit 3 - ;; -esac - -: diff --git a/scripts/etc/init.d/metadata b/scripts/etc/init.d/metadata deleted file mode 100755 index 72c9205..0000000 --- a/scripts/etc/init.d/metadata +++ /dev/null @@ -1,47 +0,0 @@ -#! /bin/sh - -### BEGIN INIT INFO -# Provides: metadata -# Required-Start: $all -# Required-Stop: $all -# Default-Start: 2 3 4 5 -# Default-Stop: -# Short-Description: Astara OpenStack Metadata Service -### END INIT INFO - - -PATH=/bin:/usr/bin:/sbin:/usr/sbin -DAEMON="/usr/local/bin/astara-metadata-proxy" -NAME="astara-metadata-proxy" -OPTIONS="/etc/metadata.conf" -PIDFILE=/var/run/metadata.pid - -test -x $DAEMON || exit 0 - -. /lib/lsb/init-functions - -case "$1" in - start) - log_daemon_msg "Starting $NAME" $NAME - start_daemon -p $PIDFILE $DAEMON $OPTIONS - log_end_msg $? - ;; - stop) - log_daemon_msg "Stopping $NAME" $NAME - killproc -p $PIDFILE $DAEMON - log_end_msg $? - ;; - force-reload|restart) - $0 stop - $0 start - ;; - status) - status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $? - ;; - *) - echo "Usage: /etc/init.d/astara-metadata-proxy {start|stop|restart|force-reload|status}" - exit 1 - ;; -esac - -exit 0 diff --git a/scripts/etc/init.d/ssh b/scripts/etc/init.d/ssh deleted file mode 100755 index 6886bc5..0000000 --- a/scripts/etc/init.d/ssh +++ /dev/null @@ -1,168 +0,0 @@ -#! /bin/sh - -### BEGIN INIT INFO -# Provides: sshd -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: -# Short-Description: OpenBSD Secure Shell server -### END INIT INFO - -set -e - -# /etc/init.d/ssh: start and stop the OpenBSD "secure shell(tm)" daemon - -test -x /usr/sbin/sshd || exit 0 -( /usr/sbin/sshd -\? 2>&1 | grep -q OpenSSH ) 2>/dev/null || exit 0 - -umask 022 - -if test -f /etc/default/ssh; then - . /etc/default/ssh -fi - -. /lib/lsb/init-functions - -if [ -n "$2" ]; then - SSHD_OPTS="$SSHD_OPTS $2" -fi - -# Are we running from init? -run_by_init() { - ([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ] -} - -astara_configure_ssh() { - /usr/local/bin/astara-configure-ssh -} - -check_for_no_start() { - # forget it if we're trying to start, and /etc/ssh/sshd_not_to_be_run exists - if [ -e /etc/ssh/sshd_not_to_be_run ]; then - if [ "$1" = log_end_msg ]; then - log_end_msg 0 || true - fi - if ! run_by_init; then - log_action_msg "OpenBSD Secure Shell server not in use (/etc/ssh/sshd_not_to_be_run)" || true - fi - exit 0 - fi -} - -check_dev_null() { - if [ ! -c /dev/null ]; then - if [ "$1" = log_end_msg ]; then - log_end_msg 1 || true - fi - if ! run_by_init; then - log_action_msg "/dev/null is not a character device!" || true - fi - exit 1 - fi -} - -check_privsep_dir() { - # Create the PrivSep empty dir if necessary - if [ ! -d /var/run/sshd ]; then - mkdir /var/run/sshd - chmod 0755 /var/run/sshd - fi -} - -check_config() { - if [ ! -e /etc/ssh/sshd_not_to_be_run ]; then - /usr/sbin/sshd $SSHD_OPTS -t || exit 1 - fi -} - -export PATH="${PATH:+$PATH:}/usr/sbin:/sbin" - -case "$1" in - start) - check_privsep_dir - check_for_no_start - check_dev_null - astara_configure_ssh - log_daemon_msg "Starting OpenBSD Secure Shell server" "sshd" || true - if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sshd.pid --exec /usr/sbin/sshd -- $SSHD_OPTS; then - log_end_msg 0 || true - else - log_end_msg 1 || true - fi - ;; - stop) - log_daemon_msg "Stopping OpenBSD Secure Shell server" "sshd" || true - if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/sshd.pid; then - log_end_msg 0 || true - else - log_end_msg 1 || true - fi - ;; - - reload|force-reload) - check_for_no_start - check_config - log_daemon_msg "Reloading OpenBSD Secure Shell server's configuration" "sshd" || true - if start-stop-daemon --stop --signal 1 --quiet --oknodo --pidfile /var/run/sshd.pid --exec /usr/sbin/sshd; then - log_end_msg 0 || true - else - log_end_msg 1 || true - fi - ;; - - restart) - check_privsep_dir - check_config - astara_configure_ssh - log_daemon_msg "Restarting OpenBSD Secure Shell server" "sshd" || true - start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile /var/run/sshd.pid - check_for_no_start log_end_msg - check_dev_null log_end_msg - if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sshd.pid --exec /usr/sbin/sshd -- $SSHD_OPTS; then - log_end_msg 0 || true - else - log_end_msg 1 || true - fi - ;; - - try-restart) - check_privsep_dir - check_config - log_daemon_msg "Restarting OpenBSD Secure Shell server" "sshd" || true - RET=0 - start-stop-daemon --stop --quiet --retry 30 --pidfile /var/run/sshd.pid || RET="$?" - case $RET in - 0) - # old daemon stopped - check_for_no_start log_end_msg - check_dev_null log_end_msg - if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sshd.pid --exec /usr/sbin/sshd -- $SSHD_OPTS; then - log_end_msg 0 || true - else - log_end_msg 1 || true - fi - ;; - 1) - # daemon not running - log_progress_msg "(not running)" || true - log_end_msg 0 || true - ;; - *) - # failed to stop - log_progress_msg "(failed to stop)" || true - log_end_msg 1 || true - ;; - esac - ;; - - status) - status_of_proc -p /var/run/sshd.pid /usr/sbin/sshd sshd && exit 0 || exit $? - ;; - - *) - log_action_msg "Usage: /etc/init.d/ssh {start|stop|reload|force-reload|restart|try-restart|status}" || true - exit 1 -esac - -exit 0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 7625025..0000000 --- a/setup.cfg +++ /dev/null @@ -1,48 +0,0 @@ -[metadata] -name = astara-appliance -summary = Astara Linux-based network function appliance -description-file = - README.md -author = OpenStack -author-email = openstack-dev@lists.openstack.org -home-page = http://github.com/openstack/astara-appliance -classifier = - Environment :: OpenStack - Intended Audience :: Developers - 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 - -[files] -packages = - astara_router - -[global] -setup-hooks = - pbr.hooks.setup_hook - -[entry_points] -console_scripts = - akanda-configure-management=astara_router.commands.management:configure_management - akanda-api-dev-server=astara_router.api.server:main - akanda-metadata-proxy=astara_router.metadata_proxy:main - akanda-gratuitous-arp=astara_router.drivers.arp:send_gratuitous_arp - astara-configure-management=astara_router.commands.management:configure_management - astara-api-dev-server=astara_router.api.server:main - astara-metadata-proxy=astara_router.metadata_proxy:main - astara-gratuitous-arp=astara_router.drivers.arp:send_gratuitous_arp - astara-rootwrap=oslo_rootwrap.cmd:main - -[build_sphinx] -all_files = 1 -build-dir = doc/build -source-dir = doc/source - -[nosetests] -verbosity = 2 -detailed-errors = 1 -cover-package = astara_router diff --git a/setup.py b/setup.py deleted file mode 100644 index 782bb21..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# 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>=1.8'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index f15be92..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -# 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. -unittest2 # BSD -nose # LGPL -coverage>=3.6 # Apache-2.0 -mock>=2.0 # BSD - -# Doc requirements -sphinx!=1.3b1,<1.3,>=1.2.1 # BSD -oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 -reno>=1.8.0 # Apache2 - diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/test/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/test/unit/__init__.py b/test/unit/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/test/unit/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/test/unit/api/__init__.py b/test/unit/api/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/test/unit/api/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/test/unit/api/v1/__init__.py b/test/unit/api/v1/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/test/unit/api/v1/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/test/unit/api/v1/fakes.py b/test/unit/api/v1/fakes.py deleted file mode 100644 index 5da3024..0000000 --- a/test/unit/api/v1/fakes.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 astara_router import models - - -class FakeIFManager(object): - """ - The methods implemented here in the fake interface manager should not be - built using the payloads, since that's what we're using to verify the data. - Instead, each method should create astara objects as needed that will - serialize to the appropriate data to return the proper payload. - """ - @classmethod - def fake_get_interface(cls, ifname): - return models.Interface( - media="Ethernet autoselect (1000baseT full-duplex,master)", - state="up", - ifname="ge1", - groups=["egress"], - lladdr="00:0c:29:e8:f9:2e", - addresses=["fe80::20c:29ff:fee8:f92e/64", "192.168.229.129/24"]) - - @classmethod - def fake_get_interfaces(cls): - iface1 = models.Interface( - media="null", state="down", ifname="ge0", groups=["enc"], - lladdr="null", addresses=[]) - iface2 = models.Interface( - media="Ethernet autoselect (1000baseT full-duplex,master)", - state="up", ifname="ge1", groups=["egress"], - lladdr="00:0c:29:e8:f9:2e", - addresses=["fe80::20c:29ff:fee8:f92e/64", "192.168.229.129/24"]) - iface3 = models.Interface( - media="Ethernet autoselect (1000baseT full-duplex,master)", - state="up", ifname="ge2", groups=[], - lladdr="00:0c:29:e8:f9:38", - addresses=["192.168.57.101/24", "fe80::20c:29ff:fee8:f938/64"]) - return [iface1, iface2, iface3] diff --git a/test/unit/api/v1/payloads/__init__.py b/test/unit/api/v1/payloads/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/test/unit/api/v1/payloads/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/test/unit/api/v1/test_base.py b/test/unit/api/v1/test_base.py deleted file mode 100644 index b08a83e..0000000 --- a/test/unit/api/v1/test_base.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. - - -""" -Base classes for Router API tests. -""" -from unittest import TestCase - -import flask - -from astara_router.api import v1 - - -class BaseAPITestCase(TestCase): - """ - This test case contains the unit tests for the Python server implementation - of the Router API. The focus of these tests is to ensure that the server is - behaving appropriately. - """ - def setUp(self): - self.app = flask.Flask('base_test') - self.app.register_blueprint(v1.base.blueprint) - self.test_app = self.app.test_client() - - def test_root(self): - rv = self.test_app.get('/v1/base', follow_redirects=True) - self.assertEqual(rv.data, 'Astara appliance API service is active') - self.assertEqual(rv.status_code, 200) diff --git a/test/unit/api/v1/test_firewall.py b/test/unit/api/v1/test_firewall.py deleted file mode 100644 index 954d232..0000000 --- a/test/unit/api/v1/test_firewall.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. - - -""" -Base classes for Router API tests. -""" -import flask -import mock -from unittest2 import TestCase - -from astara_router.api import v1 - - -class FirewallAPITestCase(TestCase): - """ - """ - def setUp(self): - ip_mgr_patch = mock.patch.object( - v1.firewall.iptables, - 'IPTablesManager' - ) - self.iptables_mgr = ip_mgr_patch.start().return_value - self.addCleanup(mock.patch.stopall) - self.app = flask.Flask('firewall_test') - self.app.register_blueprint(v1.firewall.blueprint) - self.test_app = self.app.test_client() - - def _test_passthrough_helper(self, resource_name, method_name, - response_code=200): - mock_method = getattr(self.iptables_mgr, method_name) - mock_method.return_value = 'the_value' - result = self.test_app.get('/v1/firewall/%s' % resource_name) - self.assertEqual(response_code, result.status_code) - self.assertTrue(mock_method.called) - self.assertEqual(result.data, 'the_value') - - def test_get_rules(self): - self._test_passthrough_helper('rules', 'get_rules') diff --git a/test/unit/api/v1/test_system.py b/test/unit/api/v1/test_system.py deleted file mode 100644 index c31d24f..0000000 --- a/test/unit/api/v1/test_system.py +++ /dev/null @@ -1,309 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. - - -""" -Base classes for System Router API tests. -""" -import unittest -import distutils - -from dogpile.cache import make_region -import flask -import json -import mock - -from astara_router import manager -from astara_router.api import v1 - - -SYSTEM_CONFIG = { - 'tenant_id': 'foo_tenant_id', - 'hostname': 'foohostname', -} - - -class SystemAPITestCase(unittest.TestCase): - """ - This test case contains the unit tests for the Python server implementation - of the Router API. The focus of these tests is to ensure that the server is - behaving appropriately. - """ - def setUp(self): - self.app = flask.Flask('system_test') - self.app.register_blueprint(v1.system.blueprint) - self.test_app = self.app.test_client() - # Replace the default cache with an in-memory version. - self._old_cache = v1.system._cache - v1.system._cache = make_region().configure( - 'dogpile.cache.memory', - ) - - def tearDown(self): - v1.system._cache = self._old_cache - super(SystemAPITestCase, self).tearDown() - - @unittest.skipIf( - not distutils.spawn.find_executable('ip'), - 'unsupported platform' - ) - def test_get_interface(self): - with mock.patch.object( - v1.system.manager.router, 'get_interface' - ) as get_if: - get_if.return_value = 'ge1' - result = self.test_app.get('/v1/system/interface/ge1') - get_if.assert_called_once_with('ge1') - self.assertEqual( - json.loads(result.data), - {'interface': 'ge1'} - ) - - @unittest.skipIf( - not distutils.spawn.find_executable('ip'), - 'unsupported platform' - ) - def test_get_interfaces(self): - with mock.patch.object( - v1.system.manager.router, 'get_interfaces' - ) as get_ifs: - get_ifs.return_value = ['ge0', 'ge1'] - result = self.test_app.get('/v1/system/interfaces') - get_ifs.assert_called_once_with() - self.assertEqual( - json.loads(result.data), - {'interfaces': ['ge0', 'ge1']} - ) - - @unittest.skipIf( - not distutils.spawn.find_executable('ip'), - 'unsupported platform' - ) - @mock.patch.object(manager, 'settings') - @mock.patch.object(v1.system, 'settings') - def test_get_configuration(self, fake_api_settings, fake_mgr_settings): - fake_api_settings.ENABLED_SERVICES = ['router', 'loadbalancer'] - fake_mgr_settings.ENABLED_SERVICES = ['router', 'loadbalancer'] - - result = self.test_app.get('/v1/system/config') - expected = { - 'configuration': { - 'address_book': {}, - 'anchors': [], - 'networks': [], - 'services': { - 'loadbalancer': None, - 'router': None - }, - 'static_routes': [], - 'system': { - 'hostname': None, - 'interfaces': [], - 'management_address': None, - 'tenant_id': None - }, - 'vpn': [], - } - } - self.assertEqual(json.loads(result.data), expected) - - def test_put_configuration_returns_405(self): - result = self.test_app.put( - '/v1/system/config', - data='plain text', - content_type='text/plain' - ) - self.assertEqual(result.status_code, 415) - - def test_put_configuration_returns_422_for_ValueError(self): - with mock.patch('astara_router.models.RouterConfiguration') as Config: - Config.side_effect = ValueError - result = self.test_app.put( - '/v1/system/config', - data=json.dumps({'networks': [{}]}), # malformed dict - content_type='application/json' - ) - self.assertEqual(result.status_code, 422) - - def test_put_configuration_returns_422_for_errors(self): - with mock.patch('astara_router.models.SystemConfiguration') as Config: - Config.return_value.validate.return_value = ['error1'] - result = self.test_app.put( - '/v1/system/config', - data=json.dumps(SYSTEM_CONFIG), - content_type='application/json' - ) - self.assertEqual(result.status_code, 422) - self.assertEqual( - result.data, - 'The config failed to validate.\nerror1' - ) - - @unittest.skipIf( - not distutils.spawn.find_executable('ip'), - 'unsupported platform' - ) - @mock.patch('astara_router.api.v1.system._get_cache') - @mock.patch('astara_router.models.SystemConfiguration') - @mock.patch.object(v1.system.manager, 'update_config') - def test_put_configuration_returns_200( - self, mock_update, fake_system_config, fake_cache - ): - fake_cache.return_value = 'fake_cache' - sys_config_obj = mock.Mock() - sys_config_obj.validate = mock.Mock() - sys_config_obj.validate.return_value = [] - fake_system_config.return_value = sys_config_obj - - result = self.test_app.put( - '/v1/system/config', - data=json.dumps({ - 'tenant_id': 'foo_tenant_id', - 'hostname': 'foo_hostname', - }), - content_type='application/json' - ) - - self.assertEqual(result.status_code, 200) - self.assertTrue(json.loads(result.data)) - mock_update.assert_called_with( - cache='fake_cache', service_configs=[], - system_config=sys_config_obj) - - @mock.patch('astara_router.manager.Manager.config', - new_callable=mock.PropertyMock, return_value={}) - @mock.patch('astara_router.api.v1.system._get_cache') - @mock.patch('astara_router.models.RouterConfiguration') - @mock.patch('astara_router.models.SystemConfiguration') - @mock.patch.object(v1.system.manager, 'update_config') - def test_put_configuration_with_router( - self, mock_update, fake_system_config, fake_router_config, - fake_cache, fake_config - ): - fake_config.return_value = 'foo' - fake_cache.return_value = 'fake_cache' - sys_config_obj = mock.Mock() - sys_config_obj.validate = mock.Mock() - sys_config_obj.validate.return_value = [] - fake_system_config.return_value = sys_config_obj - - router_config_obj = mock.Mock() - router_config_obj.validate = mock.Mock() - router_config_obj.validate.return_value = [] - fake_router_config.return_value = router_config_obj - - result = self.test_app.put( - '/v1/system/config', - data=json.dumps({ - 'tenant_id': 'foo_tenant_id', - 'hostname': 'foo_hostname', - 'asn': 'foo_asn', - }), - content_type='application/json' - ) - self.assertEqual(result.status_code, 200) - self.assertTrue(json.loads(result.data)) - mock_update.assert_called_with( - cache='fake_cache', service_configs=[router_config_obj], - system_config=sys_config_obj) - - @mock.patch('astara_router.models.get_config_model') - @mock.patch.object(manager, 'settings') - @mock.patch.object(v1.system, 'settings') - @mock.patch('astara_router.manager.Manager.config', - new_callable=mock.PropertyMock, return_value={}) - @mock.patch('astara_router.api.v1.system._get_cache') - @mock.patch('astara_router.models.LoadBalancerConfiguration') - @mock.patch('astara_router.models.SystemConfiguration') - @mock.patch.object(v1.system.manager, 'update_config') - def test_put_configuration_with_adv_services( - self, mock_update, - fake_system_config, fake_lb_config, fake_cache, fake_config, - fake_api_settings, fake_mgr_settings, fake_get_config_model - ): - fake_api_settings.ENABLED_SERVICES = ['loadbalancer'] - fake_mgr_settings.ENABLED_SERVICES = ['loadbalancer'] - fake_config.return_value = 'foo' - fake_cache.return_value = 'fake_cache' - sys_config_obj = mock.Mock() - sys_config_obj.validate = mock.Mock() - sys_config_obj.validate.return_value = [] - fake_system_config.return_value = sys_config_obj - - lb_config_obj = mock.Mock() - lb_config_obj.validate = mock.Mock() - lb_config_obj.validate.return_value = [] - fake_lb_config.return_value = lb_config_obj - fake_get_config_model.return_value = fake_lb_config - - result = self.test_app.put( - '/v1/system/config', - data=json.dumps({ - 'tenant_id': 'foo_tenant_id', - 'hostname': 'foo_hostname', - 'services': { - 'loadbalancer': {'id': 'foo'} - } - }), - content_type='application/json' - ) - self.assertEqual(result.status_code, 200) - self.assertTrue(json.loads(result.data)) - mock_update.assert_called_with( - cache='fake_cache', service_configs=[lb_config_obj], - system_config=sys_config_obj) - - @mock.patch('astara_router.models.get_config_model') - @mock.patch.object(manager, 'settings') - @mock.patch.object(v1.system, 'settings') - @mock.patch('astara_router.manager.Manager.config', - new_callable=mock.PropertyMock, return_value={}) - @mock.patch('astara_router.api.v1.system._get_cache') - @mock.patch('astara_router.models.LoadBalancerConfiguration') - @mock.patch('astara_router.models.SystemConfiguration') - @mock.patch.object(v1.system.manager, 'update_config') - def test_put_configuration_with_disabled_svc_returns_400( - self, mock_update, - fake_system_config, fake_lb_config, fake_cache, fake_config, - fake_api_settings, fake_mgr_settings, fake_get_config_model - ): - fake_api_settings.ENABLED_SERVICES = ['foo'] - fake_mgr_settings.ENABLED_SERVICES = ['foo'] - fake_config.return_value = 'foo' - fake_cache.return_value = 'fake_cache' - sys_config_obj = mock.Mock() - sys_config_obj.validate = mock.Mock() - sys_config_obj.validate.return_value = [] - fake_system_config.return_value = sys_config_obj - - lb_config_obj = mock.Mock() - lb_config_obj.validate = mock.Mock() - lb_config_obj.validate.return_value = [] - fake_lb_config.return_value = lb_config_obj - fake_get_config_model.return_value = fake_lb_config - - result = self.test_app.put( - '/v1/system/config', - data=json.dumps({ - 'tenant_id': 'foo_tenant_id', - 'hostname': 'foo_hostname', - 'services': { - 'loadbalancer': {'id': 'foo'} - } - }), - content_type='application/json' - ) - self.assertEqual(result.status_code, 400) diff --git a/test/unit/drivers/__init__.py b/test/unit/drivers/__init__.py deleted file mode 100644 index 63621ff..0000000 --- a/test/unit/drivers/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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. diff --git a/test/unit/drivers/test_arp.py b/test/unit/drivers/test_arp.py deleted file mode 100644 index 174130a..0000000 --- a/test/unit/drivers/test_arp.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 mock -import socket -import unittest2 - -from astara_router import models -from astara_router.drivers import arp - -config = mock.Mock() -network = mock.Mock() -alloc = mock.Mock() -network.address_allocations = [alloc] -config.networks = [network] - - -def _AF_PACKET_supported(): - try: - socket.AF_PACKET - return True - except: - return False - - -class ARPTest(unittest2.TestCase): - - def setUp(self): - self.mgr = arp.ARPManager() - - @mock.patch.object(alloc, 'dhcp_addresses', ['10.10.10.200']) - def test_ip_not_in_table(self): - # If there's a DHCP address that isn't in ARP, do nothing. - output = ''' -? (10.10.10.2) at fa:16:3e:4b:7a:f0 on vio2 -? (10.10.10.3) at fa:16:3e:4c:0e:27 on vio2 -? (208.113.176.1) at 78:fe:3d:d3:b5:c1 on vio1 -''' - with mock.patch.object(self.mgr, 'sudo') as sudo: - sudo.return_value = output - self.mgr.remove_stale_entries( - config - ) - sudo.assert_called_with('-an') - - @mock.patch.object(alloc, 'mac_address', 'fa:16:3e:4b:7a:f0') - @mock.patch.object(alloc, 'dhcp_addresses', ['10.10.10.2']) - def test_ip_mac_address_matches(self): - # If a DHCP address is in ARP, and the mac address is correct, do - # nothing. - output = ''' -? (10.10.10.2) at fa:16:3e:4b:7a:f0 on vio2 -? (10.10.10.3) at fa:16:3e:4c:0e:27 on vio2 -? (208.113.176.1) at 78:fe:3d:d3:b5:c1 on vio1 -''' - with mock.patch.object(self.mgr, 'sudo') as sudo: - sudo.return_value = output - self.mgr.remove_stale_entries( - config - ) - sudo.assert_called_with('-an') - - @mock.patch.object(alloc, 'mac_address', 'fa:20:30:40:50:f0') - @mock.patch.object(alloc, 'dhcp_addresses', ['10.10.10.2']) - def test_ip_mac_address_mismatch(self): - # If a DHCP address is in ARP, and the mac address has changed, - # delete the old record. - output = ''' -? (10.10.10.2) at fa:16:3e:4b:7a:f0 on vio2 -? (10.10.10.3) at fa:16:3e:4c:0e:27 on vio2 -? (208.113.176.1) at 78:fe:3d:d3:b5:c1 on vio1 -''' - with mock.patch.object(self.mgr, 'sudo') as sudo: - sudo.return_value = output - self.mgr.remove_stale_entries( - config - ) - sudo.assert_has_calls([ - mock.call('-an'), - mock.call('-d', '10.10.10.2') - ]) - - def test_send_gratuitous_arp_for_config(self): - config = models.RouterConfiguration({ - 'networks': [{ - 'network_id': 'ABC456', - 'interface': { - 'ifname': 'ge1', - 'name': 'ext', - }, - 'subnets': [{ - 'id': 'theid', - 'cidr': '172.16.77.0/24', - 'gateway_ip': '172.16.77.1', - 'dhcp_enabled': True, - 'dns_nameservers': [] - }], - 'network_type': models.Network.TYPE_EXTERNAL, - }], - 'floating_ips': [{ - 'fixed_ip': '192.168.0.2', - 'floating_ip': '172.16.77.50' - }, { - 'fixed_ip': '192.168.0.3', - 'floating_ip': '172.16.77.51' - }, { - 'fixed_ip': '192.168.0.4', - 'floating_ip': '172.16.77.52' - }, { - 'fixed_ip': '192.168.0.5', - 'floating_ip': '172.16.77.53' - }] - }) - - with mock.patch('astara_router.utils.execute') as execute: - self.mgr.send_gratuitous_arp_for_floating_ips( - config, - lambda x: x.replace('ge', 'eth') - ) - assert execute.call_args_list == [ - mock.call( - ['astara-gratuitous-arp', 'eth1', '172.16.77.50'], - 'sudo astara-rootwrap /etc/rootwrap.conf' - ), - mock.call( - ['astara-gratuitous-arp', 'eth1', '172.16.77.51'], - 'sudo astara-rootwrap /etc/rootwrap.conf' - ), - mock.call( - ['astara-gratuitous-arp', 'eth1', '172.16.77.52'], - 'sudo astara-rootwrap /etc/rootwrap.conf' - ), - mock.call( - ['astara-gratuitous-arp', 'eth1', '172.16.77.53'], - 'sudo astara-rootwrap /etc/rootwrap.conf' - ) - ] - - @unittest2.skipIf( - not _AF_PACKET_supported(), - 'socket.AF_PACKET not supported on this platform' - ) - @mock.patch('socket.socket') - def test_send_gratuitous_arp(self, socket_constr): - socket_inst = socket_constr.return_value - socket_inst.getsockname.return_value = ( - None, None, None, None, 'A1:B2:C3:D4:E5:F6' - ) - - arp._send_gratuitous_arp('eth1', '1.2.3.4') - socket_constr.assert_called_once_with( - socket.AF_PACKET, socket.SOCK_RAW - ) - socket_inst.bind.assert_called_once_with(( - 'eth1', - 0x0806 - )) - data = socket_inst.send.call_args_list[0][0][0] - assert data == ''.join([ - '\xff\xff\xff\xff\xff\xff', # Broadcast destination - 'A1:B2:C3:D4:E5:F6', # Source hardware address - '\x08\x06', # HTYPE ARP - '\x00\x01', # Ethernet - '\x08\x00', # Protocol IPv4 - '\x06', # HADDR length, 6 for IEEE 802 MAC addresses - '\x04', # PADDR length, 4 for IPv4 - '\x00\x02', # OPER, 2 = ARP Reply - 'A1:B2:C3:D4:E5:F6', # Source MAC - '\x01\x02\x03\x04', # Source IP - 'A1:B2:C3:D4:E5:F6', # Target MAC matches - '\x01\x02\x03\x04' # Target IP matches - ]) diff --git a/test/unit/drivers/test_bird.py b/test/unit/drivers/test_bird.py deleted file mode 100644 index 8e7cfd6..0000000 --- a/test/unit/drivers/test_bird.py +++ /dev/null @@ -1,246 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 unittest2 import TestCase - -import mock -import netaddr -import re -import textwrap - -from astara_router.drivers import bird -ext_subnet = mock.Mock() -ext_subnet.gateway_ip = netaddr.IPAddress('dead:beef::1') -ext_subnet.cidr = netaddr.IPNetwork('dead:beef::/64') -ext_subnet.dns_nameservers = ["1.2.3.4"] - -ext_net = mock.Mock() -ext_net.subnets = [ext_subnet] -ext_net.is_internal_network = False -ext_net.is_external_network = True -ext_net.is_tenant_network = False -ext_net.interface.ifname = 'ge0' - -int_subnet = mock.Mock() -int_subnet.gateway_ip = netaddr.IPAddress('face::1') -int_subnet.cidr = netaddr.IPNetwork('face::/64') -int_subnet.dns_nameservers = ["1.2.3.4"] - -int_net = mock.Mock() -int_net.subnets = [int_subnet] -int_net.is_internal_network = True -int_net.is_external_network = False -int_net.is_tenant_network = True -int_net.interface.ifname = 'ge1' - -mgt_net = mock.Mock() -mgt_net.subnets = [] -mgt_net.is_internal_network = False -mgt_net.is_external_network = False -mgt_net.is_tenant_network = False -mgt_net.interface.ifname = 'ge2' - -CONFIG = mock.Mock() -CONFIG.networks = [ext_net, int_net, mgt_net] -CONFIG.asn = 4321 -CONFIG.neighbor_asn = 65000 -IF_MAP = {'ge0': 'en0', 'ge1': 'en1', 'ge2': 'en2'} - - -class BirdTestCase(TestCase): - """ - """ - def setUp(self): - self.mock_execute = mock.patch('astara_router.utils.execute').start() - self.mock_replace_file = mock.patch( - 'astara_router.utils.replace_file' - ).start() - self.addCleanup(mock.patch.stopall) - - self.mgr = bird.BirdManager() - - def test_save_config(self): - with mock.patch.object(bird, 'build_config') as mock_build_config: - mock_build_config.return_value = 'the_config' - - self.mgr.save_config(mock.sentinel.config, mock.sentinel.if_map) - - mock_build_config.assert_called_once_with( - mock.sentinel.config, - mock.sentinel.if_map - ) - - self.mock_replace_file.assert_called_once_with( - '/tmp/bird6.conf', - 'the_config' - ) - self.mock_execute.assert_called_once_with( - ['mv', '/tmp/bird6.conf', '/etc/bird/bird6.conf'], - 'sudo astara-rootwrap /etc/rootwrap.conf' - ) - - def test_restart(self): - self.mgr.restart() - self.mock_execute.assert_has_calls([ - mock.call(['service', 'bird6', 'status'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['service', 'bird6', 'reload'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - ]) - - def test_restart_failure(self): - with mock.patch('astara_router.utils.execute') as execute: - execute.side_effect = [Exception('status failed!'), None] - self.mgr.restart() - execute.assert_has_calls([ - mock.call(['service', 'bird6', 'status'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['service', 'bird6', 'start'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - ]) - - def test_build_config(self): - patches = { - '_build_global_config': mock.Mock(return_value='global'), - '_build_kernel_config': mock.Mock(return_value='kernel'), - '_build_device_config': mock.Mock(return_value='device'), - '_build_static_config': mock.Mock(return_value='static'), - '_build_direct_config': mock.Mock(return_value='direct'), - '_build_bgp_config': mock.Mock(return_value='bgp'), - '_build_radv_config': mock.Mock(return_value='radv') - } - with mock.patch.multiple(bird, **patches): - result = bird.build_config( - mock.sentinel.config, - mock.sentinel.if_map - ) - - expected = [ - 'global', 'kernel', 'device', 'static', 'direct', 'bgp', 'radv' - ] - self.assertEqual(result, '\n'.join(expected)) - - def test_find_external_v4_ip_has_v4(self): - config = mock.Mock() - config.external_v4_id = '9.9.9.1' - self.assertEqual(bird._find_external_v4_ip(config), '9.9.9.1') - - def test_find_external_v4_id_no_v4(self): - config = mock.Mock() - config.external_v4_id = None - - result = bird._find_external_v4_ip(config) - self.assertTrue(re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', result)) - - def test_build_global_config(self): - with mock.patch.object(bird, '_find_external_v4_ip') as ext_v4_id: - ext_v4_id.return_value = '9.9.9.9' - - result = bird._build_global_config(mock.sentinel.config) - ext_v4_id.assert_called_once_with(mock.sentinel.config) - self.assertEqual( - result, - 'log syslog {warning, error, info};\nrouter id 9.9.9.9;' - ) - - def test_build_kernel_config(self): - result = bird._build_kernel_config() - expected = ( - 'protocol kernel {\n' - ' learn;\n' - ' scan time 20;\n' - ' import all;\n' - ' export all;\n' - '}' - ) - self.assertEqual(expected, result) - - def test_build_device_config(self): - self.assertEqual( - bird._build_device_config(), - 'protocol device {\n scan time 10;\n}' - ) - - def test_build_static_config(self): - self.assertFalse(bird._build_static_config(mock.Mock())) - - def test_build_direct_config(self): - result = bird._build_direct_config(mock.Mock(), IF_MAP) - expected = 'protocol direct {\n interface "en0","en1","en2";\n}' - self.assertEqual(result, expected) - - def test_build_ospf_config(self): - expected = """ - protocol ospf { - export all; - rfc1583compat yes; - area 0 { - interface "en0" { - cost 10; - type broadcast; - }; - interface "en1" { - cost 10; - stub yes; - }; - }; - }; - """ - expected = textwrap.dedent(expected).strip() - result = bird._build_ospf_config(CONFIG, IF_MAP) - self.assertEqual(result, expected) - - def test_build_bgp_config(self): - expected = """ - filter bgp_out { - if ! (source = RTS_DEVICE) then reject; - if net ~ fc00::/7 then reject; - if net = face::/64 then accept; - else reject; - } - - protocol bgp { - local as 4321; - neighbor dead:beef::1 as 65000; - import all; - export filter bgp_out; - rr client; - } - """ - expected = textwrap.dedent(expected).strip() - result = bird._build_bgp_config(CONFIG, IF_MAP) - self.assertEqual(result, expected) - - def test_build_radv_config(self): - expected = """ - protocol radv { - interface "en1" { - max ra interval 600; - rdnss local yes; - prefix face::/64 { - autonomous off; - }; - rdnss { - lifetime mult 10; - ns 1.2.3.4; - }; - }; - } - """ - expected = textwrap.dedent(expected).strip() - result = bird._build_radv_config(CONFIG, IF_MAP) - self.assertEqual(result, expected) diff --git a/test/unit/drivers/test_conntrackd.py b/test/unit/drivers/test_conntrackd.py deleted file mode 100644 index a032b1e..0000000 --- a/test/unit/drivers/test_conntrackd.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2016 Akanda, Inc. -# -# Author: Akanda, 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 __builtin__ - -from unittest2 import TestCase -import mock - -from test.unit import fakes - -from astara_router.drivers import conntrackd - - -class ConntrackddManagerTestCase(TestCase): - def setUp(self): - super(ConntrackddManagerTestCase, self).setUp() - self.mgr = conntrackd.ConntrackdManager() - self.mgr._config_templ = mock.Mock( - render=mock.Mock() - ) - - @mock.patch('astara_router.utils.execute') - @mock.patch('astara_router.utils.replace_file') - @mock.patch('astara_router.utils.hash_file') - def test_save_config(self, fake_hash, fake_replace, fake_execute): - fake_generic_to_host = mock.Mock(return_value='eth0') - fake_interface = fakes.fake_interface() - fake_mgt_interface = fakes.fake_mgt_interface() - ha_config = { - 'peers': ['10.0.0.2'], - } - fake_config = mock.Mock( - interfaces=[fake_interface, fake_mgt_interface], - ha_config=ha_config, - ) - - fake_hash.side_effect = ['hash1', 'hash2'] - self.mgr._config_templ.render.return_value = 'new_config' - self.mgr.save_config(fake_config, fake_generic_to_host) - self.mgr._config_templ.render.assert_called_with(dict( - source_address=str(fake_mgt_interface.addresses[0].ip), - management_ip_version=4, - destination_address='10.0.0.2', - interface='eth0', - )) - self.assertTrue(self.mgr._should_restart) - fake_replace.assert_called_with('/tmp/conntrackd.conf', 'new_config') - fake_execute.assert_called_with( - ['mv', '/tmp/conntrackd.conf', '/etc/conntrackd/conntrackd.conf'], - self.mgr.root_helper) - - @mock.patch.object(conntrackd.ConntrackdManager, 'sudo') - def test_restart(self, fake_sudo): - self.mgr._should_restart = True - self.mgr.restart() - fake_sudo.assert_called_with('conntrackd', 'restart') - - @mock.patch.object(conntrackd.ConntrackdManager, 'sudo') - def test_restart_skip(self, fake_sudo): - self.mgr._should_restart = False - self.mgr.restart() - self.assertFalse(fake_sudo.called) diff --git a/test/unit/drivers/test_dnsmasq.py b/test/unit/drivers/test_dnsmasq.py deleted file mode 100644 index f71d61b..0000000 --- a/test/unit/drivers/test_dnsmasq.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 unittest2 import TestCase - -import mock -import netaddr -from collections import OrderedDict - -from astara_router import models -from astara_router.drivers import dnsmasq -ext_subnet = mock.Mock() -ext_subnet.gateway_ip = netaddr.IPAddress('dead:beef::1') - -ext_subnet.cidr = netaddr.IPNetwork('dead:beef::/64') -ext_subnet.dns_nameservers = [] -ext_subnet.host_routes = [models.StaticRoute('172.16.0.0/16', '192.168.1.1')] -ext_subnet.dns_nameservers = ['8.8.8.8', '8.8.4.4'] - -ext_net = mock.Mock() -ext_net.subnets = [ext_subnet] -ext_net.is_internal_network = False -ext_net.is_external_network = True -ext_net.is_tenant_network = False -ext_net.interface.ifname = 'ge0' -ext_net.address_allocations = [models.Allocation( - 'fa:da:fa:da:fa:da:', - {'192.168.1.2': True, 'dead:beef::2': False}, # ip: DHCP enabled - '192-168-1-2.local', - 'e3300819-d7b9-4d8d-9d7c-a6380ff78ca7' -)] - -v6_subnet = mock.Mock() -v6_subnet.gateway_ip = netaddr.IPAddress('face::1') -v6_subnet.cidr = netaddr.IPNetwork('face::/64') -v6_subnet.dns_nameservers = [] -v6_subnet.host_routes = [] - -v4_subnet = mock.Mock() -v4_subnet.gateway_ip = netaddr.IPAddress('9.9.9.1') -v4_subnet.cidr = netaddr.IPNetwork('9.9.9.0/24') -v4_subnet.dns_nameservers = [] -v4_subnet.host_routes = [] - -int_net = mock.Mock() -int_net.subnets = [v4_subnet, v6_subnet] -int_net.is_internal_network = True -int_net.is_external_network = False -int_net.is_tenant_network = True -int_net.interface.ifname = 'ge1' -int_net.address_allocations = [models.Allocation( - 'fb:db:fb:db:fb:db', - OrderedDict([('face::2', True), ('9.9.9.2', True)]), # ip: DHCP enabled - '9-9-9-2.local', - 'e3300819-d7b9-4d8d-9d7c-a6380ff78ca8', -)] - -mgt_net = mock.Mock() -mgt_net.subnets = [] -mgt_net.is_internal_network = False -mgt_net.is_external_network = False -mgt_net.is_tenant_network = False -mgt_net.interface.ifname = 'ge2' - -CONFIG = mock.Mock() -CONFIG.networks = [ext_net, int_net, mgt_net] -IF_MAP = {'ge0': 'en0', 'ge1': 'en1', 'ge2': 'en2'} - - -class DnsmasqTestCase(TestCase): - """ - """ - def setUp(self): - self.mock_execute = mock.patch('astara_router.utils.execute').start() - self.mock_replace_file = mock.patch( - 'astara_router.utils.replace_file' - ).start() - self.addCleanup(mock.patch.stopall) - - self.mgr = dnsmasq.DHCPManager() - - def test_update_network_dhcp_config_tenant_net(self): - mock_net = mock.Mock() - mock_net.is_tenant_network = True - with mock.patch.object(self.mgr, '_build_dhcp_config') as build_config: - build_config.return_value = 'the_config' - - self.mgr.update_network_dhcp_config('em1', mock_net) - - build_config.assert_called_once_with('em1', mock_net) - - self.mock_replace_file.assert_called_once_with( - '/tmp/dnsmasq.conf', - 'the_config' - ) - self.mock_execute.assert_called_once_with( - ['mv', '/tmp/dnsmasq.conf', '/etc/dnsmasq.d/em1.conf'], - 'sudo astara-rootwrap /etc/rootwrap.conf' - ) - - def test_build_dhcp_config(self): - config = self.mgr._build_dhcp_config('ge0', ext_net) - assert config == '\n'.join([ - 'interface=ge0', - 'dhcp-range=set:ge0_0,dead:beef::,static,86400s', - 'dhcp-option=tag:ge0_0,option6:dns-server,8.8.8.8', - 'dhcp-option=tag:ge0_0,option6:dns-server,8.8.4.4', - ('dhcp-option=tag:ge0_0,option6:classless-static-route,' - '172.16.0.0/16,192.168.1.1'), - 'dhcp-host=fa:da:fa:da:fa:da:,192.168.1.2,192-168-1-2.local' - ]), config - - def test_build_dhcp_config_multiple_ips(self): - many_ips = mock.Mock() - many_ips.subnets = [ext_subnet] - many_ips.is_internal_network = False - many_ips.is_external_network = True - many_ips.is_tenant_network = False - many_ips.interface.ifname = 'ge0' - many_ips.address_allocations = [models.Allocation( - 'fa:da:fa:da:fa:da:', - {'192.168.1.2': True, '192.168.1.3': True, '192.168.1.4': True, 'dead:beef::2': True}, # ip: DHCP enabled - '192-168-1-2.local', - 'e3300819-d7b9-4d8d-9d7c-a6380ff78ca7' - )] - - config = self.mgr._build_dhcp_config('ge0', many_ips) - assert config == '\n'.join([ - 'interface=ge0', - 'dhcp-range=set:ge0_0,dead:beef::,static,86400s', - 'dhcp-option=tag:ge0_0,option6:dns-server,8.8.8.8', - 'dhcp-option=tag:ge0_0,option6:dns-server,8.8.4.4', - ('dhcp-option=tag:ge0_0,option6:classless-static-route,' - '172.16.0.0/16,192.168.1.1'), - 'dhcp-host=fa:da:fa:da:fa:da:,192.168.1.2,[dead:beef::2],192-168-1-2.local', - ]), config - - def test_build_dhcp_config_int(self): - config = self.mgr._build_dhcp_config('ge1', int_net) - assert config == '\n'.join([ - 'interface=ge1', - 'dhcp-range=set:ge1_0,9.9.9.0,static,86400s', - 'dhcp-range=set:ge1_1,face::,static,86400s', - 'dhcp-host=fb:db:fb:db:fb:db,9.9.9.2,[face::2],9-9-9-2.local', - ]), config - - def test_restart(self): - self.mgr.restart() - self.mock_execute.assert_has_calls([ - mock.call(['service', 'dnsmasq', 'stop'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['service', 'dnsmasq', 'start'], - 'sudo astara-rootwrap /etc/rootwrap.conf') - ]) diff --git a/test/unit/drivers/test_hostname.py b/test/unit/drivers/test_hostname.py deleted file mode 100644 index 35290e2..0000000 --- a/test/unit/drivers/test_hostname.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 unittest2 import TestCase - -import mock - -from astara_router.drivers import hostname - -CONFIG = mock.Mock() -CONFIG.hostname = 'astara' -CONFIG.management_address = 'fdca:3ba5:a17a:acda:f816:3eff:fe66:33b6' - - -class HostnameTestCase(TestCase): - """ - """ - def setUp(self): - self.mock_execute = mock.patch('astara_router.utils.execute').start() - self.mock_replace_file = mock.patch( - 'astara_router.utils.replace_file' - ).start() - self.addCleanup(mock.patch.stopall) - - self.mgr = hostname.HostnameManager() - - def test_update_hostname(self): - self.mgr.update_hostname(CONFIG) - self.mock_execute.assert_has_calls([ - mock.call(['hostname', 'astara'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['mv', '/tmp/hostname', '/etc/hostname'], - 'sudo astara-rootwrap /etc/rootwrap.conf') - ]) - - def test_update_hosts(self): - expected = mock.call('/tmp/hosts', '\n'.join([ - '127.0.0.1 localhost', - '::1 localhost ip6-localhost ip6-loopback', - 'fdca:3ba5:a17a:acda:f816:3eff:fe66:33b6 astara' - ])) - self.mgr.update_hosts(CONFIG) - self.mock_execute.assert_has_calls([ - mock.call(['mv', '/tmp/hosts', '/etc/hosts'], - 'sudo astara-rootwrap /etc/rootwrap.conf') - ]) - self.mock_replace_file.assert_has_calls([expected]) diff --git a/test/unit/drivers/test_ip.py b/test/unit/drivers/test_ip.py deleted file mode 100644 index 50bf1e0..0000000 --- a/test/unit/drivers/test_ip.py +++ /dev/null @@ -1,594 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 -import re -from cStringIO import StringIO - -from unittest2 import TestCase -import mock -import netaddr - -from test.unit import fakes -from astara_router import models -from astara_router.drivers import ip -from astara_router.drivers.keepalived import KeepalivedManager - - -SAMPLE_OUTPUT = """1: lo: mtu 16436 qdisc noqueue state UNKNOWN - link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 - inet 127.0.0.1/8 scope host lo - inet6 ::1/128 scope host - valid_lft forever preferred_lft forever -2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 - link/ether fa:16:3e:34:ba:28 brd ff:ff:ff:ff:ff:ff - inet6 fe80::f816:3eff:fe34:ba28/64 scope link - valid_lft forever preferred_lft forever -3: eth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000 - link/ether fa:16:3e:7a:d8:64 brd ff:ff:ff:ff:ff:ff - inet 192.168.105.2/24 brd 192.168.105.255 scope global eth1 - inet6 fe80::f816:3eff:fe7a:d864/64 scope link - valid_lft forever preferred_lft forever""" # noqa - -SAMPLE_SINGLE_OUTPUT = """3: eth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000 - link/ether fa:16:3e:7a:d8:64 brd ff:ff:ff:ff:ff:ff - inet 192.168.105.2/24 brd 192.168.105.255 scope global eth1 - inet6 fe80::f816:3eff:fe7a:d864/64 scope link - valid_lft forever preferred_lft forever""" # noqa - - -class IPTestCase(TestCase): - - def setUp(self): - self.execute_patch = mock.patch('astara_router.utils.execute') - self.mock_execute = self.execute_patch.start() - - def tearDown(self): - self.execute_patch.stop() - - def test_init(self): - mgr = ip.IPManager() - self.assertEqual(mgr.host_mapping.keys(), []) - - def test_get_interfaces(self): - iface_a = mock.Mock() - iface_a.ifname = 'em0' - iface_a.mtu = 1500 - - iface_b = mock.Mock() - iface_b.ifname = 'em1' - ifaces = 'astara_router.drivers.ip._parse_interfaces' - with mock.patch(ifaces) as parse: - parse.return_value = [iface_a, iface_b] - mgr = ip.IPManager() - interfaces = mgr.get_interfaces() - self.assertEqual(interfaces, [iface_a, iface_b]) - - self.mock_execute.assert_has_calls( - [mock.call(['ip', 'addr', 'show'])]) - - def test_get_interface(self): - iface_a = mock.Mock() - iface_a.ifname = 'em0' - iface_a.mtu = 1500 - iface = 'astara_router.drivers.ip._parse_interface' - ifaces = 'astara_router.drivers.ip._parse_interfaces' - with mock.patch(iface) as parse: - with mock.patch(ifaces) as pi: - pi.return_value = [iface_a] - parse.return_value = iface_a - mgr = ip.IPManager() - interface = mgr.get_interface('ge0') - self.assertEqual(interface, iface_a) - self.assertEqual(iface_a.ifname, 'ge0') - - self.mock_execute.assert_has_calls( - [mock.call(['ip', 'addr', 'show'])]) - - def test_ensure_mapping(self): - attr = 'get_interfaces' - with mock.patch.object(ip.IPManager, attr) as get_ifaces: - mgr = ip.IPManager() - mgr.ensure_mapping() - - get_ifaces.assert_called_once_with() - - @mock.patch.object(ip.IPManager, 'ensure_mapping') - def test_is_valid(self, mock_ensure): - mgr = ip.IPManager() - mgr.host_mapping = {'em0': 'ge0'} - mgr.generic_mapping = {'ge0': 'em0'} - self.assertTrue(mgr.is_valid('ge0')) - - @mock.patch.object(ip.IPManager, 'ensure_mapping') - def test_generic_to_host(self, mock_ensure): - mgr = ip.IPManager() - mgr.host_mapping = {'em0': 'ge0'} - mgr.generic_mapping = {'ge0': 'em0'} - self.assertEqual(mgr.generic_to_host('ge0'), 'em0') - self.assertIsNone(mgr.generic_to_host('ge1')) - - @mock.patch.object(ip.IPManager, 'ensure_mapping') - def test_host_to_generic(self, ensure_mapping): - mgr = ip.IPManager() - mgr.host_mapping = {'em0': 'ge0'} - mgr.generic_mapping = {'ge0': 'em0'} - self.assertEqual(mgr.host_to_generic('em0'), 'ge0') - self.assertIsNone(mgr.host_to_generic('em1')) - - def test_update_interfaces(self): - iface_a = mock.Mock() - iface_a.mtu = 1500 - iface_b = mock.Mock() - iface_b.mtu = 1500 - - attr = 'update_interface' - with mock.patch.object(ip.IPManager, attr) as update: - mgr = ip.IPManager() - mgr.update_interfaces([iface_a, iface_b]) - update.assert_has_calls([mock.call(iface_a), mock.call(iface_b)]) - - def test_up(self): - iface = mock.Mock() - iface.ifname = 'ge0' - - attr = 'ensure_mapping' - with mock.patch.object(ip.IPManager, attr): - mgr = ip.IPManager() - mgr.host_mapping = {'em0': 'ge0'} - mgr.generic_mapping = {'ge0': 'em0'} - mgr.up(iface) - self.mock_execute.assert_has_calls( - [mock.call(['ip', 'link', 'set', 'em0', 'up'], - 'sudo astara-rootwrap /etc/rootwrap.conf')]) - - def test_down(self): - iface = mock.Mock() - iface.ifname = 'ge0' - - attr = 'ensure_mapping' - with mock.patch.object(ip.IPManager, attr): - mgr = ip.IPManager() - mgr.host_mapping = {'em0': 'ge0'} - mgr.generic_mapping = {'ge0': 'em0'} - - mgr.down(iface) - - self.mock_execute.assert_has_calls( - [mock.call(['ip', 'link', 'set', 'em0', 'down'], - 'sudo astara-rootwrap /etc/rootwrap.conf')]) - - def test_set_mtu(self): - iface = mock.Mock() - iface.ifname = 'ge0' - iface.mtu = 1280 - - attr = 'ensure_mapping' - with mock.patch.object(ip.IPManager, attr): - mgr = ip.IPManager() - mgr.host_mapping = {'em0': 'ge0'} - mgr.generic_mapping = {'ge0': 'em0'} - - mgr.set_mtu(iface) - - self.mock_execute.assert_has_calls( - [mock.call(['ip', 'link', 'set', 'em0', 'mtu', '1280'], - 'sudo astara-rootwrap /etc/rootwrap.conf')]) - - def _update_interface_test_hlpr(self, new_iface, old_iface, - ignore_link_local=True): - mock_methods = { - 'generic_to_host': mock.Mock(return_value='em0'), - 'get_interface': mock.Mock(return_value=old_iface), - '_update_addresses': mock.Mock()} - - with mock.patch.multiple(ip.IPManager, **mock_methods): - mgr = ip.IPManager() - mgr.update_interface( - new_iface, - ignore_link_local=ignore_link_local, - ) - - mock_methods['generic_to_host'].assert_called_once_with('ge0') - mock_methods['get_interface'].assert_called_once_with('ge0') - mock_methods['_update_addresses'].assert_called_once_with( - 'em0', new_iface, old_iface) - - def test_update_interface(self): - iface = mock.Mock() - iface.ifname = 'ge0' - iface.addresses = [] - iface.mtu = 1500 - - old_iface = mock.Mock(name='old') - old_iface.ifname = 'ge0' - old_iface.addresses = [] - old_iface.mtu = 1500 - - self._update_interface_test_hlpr(iface, old_iface) - - def test_update_interface_ignore_link_local(self): - iface = mock.Mock() - iface.ifname = 'ge0' - iface.addresses = [] - iface.mtu = 1500 - - old_iface = mock.Mock(name='old') - old_iface.ifname = 'ge0' - old_iface.addresses = [netaddr.IPAddress('fe80::1')] - old_iface.mtu = 1500 - - self._update_interface_test_hlpr(iface, old_iface) - self.assertEqual(old_iface.addresses, []) - - def test_update_interface_do_not_ignore_link_local(self): - iface = mock.Mock() - iface.ifname = 'ge0' - iface.addresses = [] - iface.mtu = 1500 - - link_local = netaddr.IPAddress('fe80::1') - - old_iface = mock.Mock(name='old') - old_iface.ifname = 'ge0' - old_iface.addresses = [link_local] - old_iface.mtu = 1500 - - self._update_interface_test_hlpr(iface, old_iface, False) - self.assertEqual(old_iface.addresses, [link_local]) - - def test_update_addresses(self): - iface = mock.Mock() - old_iface = mock.Mock() - - with mock.patch.object(ip.IPManager, '_update_set') as us: - mgr = ip.IPManager() - mgr._update_addresses('em0', iface, old_iface) - - us.assert_called_once_with( - 'em0', - iface, - old_iface, - 'all_addresses', - mock.ANY, - mock.ANY, - ) - - def test_address_add(self): - cmd = 'ip' - v4 = netaddr.IPNetwork('192.168.105.2/24') - v6 = netaddr.IPNetwork('fdca:3ba5:a17a:acda:20c:29ff:fe94:723d/64') - iface = mock.Mock(all_addresses=[v4, v6], ifname='em0') - old_iface = mock.Mock(all_addresses=[], ifname='em0') - - mgr = ip.IPManager() - with mock.patch.object( - mgr, 'generic_to_host', lambda x: x.replace('ge', 'em') - ): - mgr._update_addresses('em0', iface, old_iface) - assert self.mock_execute.call_args_list == [ - mock.call([ - cmd, 'addr', 'add', '192.168.105.2/24', 'brd', '+', 'dev', - 'em0' - ], 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call([cmd, 'link', 'set', 'em0', 'up'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call([cmd, 'addr', 'show', 'em0']), - mock.call([ - cmd, '-6', 'addr', 'add', - 'fdca:3ba5:a17a:acda:20c:29ff:fe94:723d/64', 'dev', 'em0' - ], 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call([cmd, 'link', 'set', 'em0', 'up'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call([cmd, 'addr', 'show', 'em0']) - ] - - def test_address_remove(self): - cmd = 'ip' - v4 = netaddr.IPNetwork('192.168.105.2/24') - v6 = netaddr.IPNetwork('fdca:3ba5:a17a:acda:20c:29ff:fe94:723d/64') - iface = mock.Mock(all_addresses=[]) - old_iface = mock.Mock(all_addresses=[v4, v6]) - - mgr = ip.IPManager() - mgr._update_addresses('em0', iface, old_iface) - assert self.mock_execute.call_args_list == [ - mock.call([cmd, 'addr', 'del', str(v4), 'dev', 'em0'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['conntrack', '-D', '-d', str(v4.ip)], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['conntrack', '-D', '-q', str(v4.ip)], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call([ - cmd, '-6', 'addr', 'del', str(v6), 'dev', 'em0' - ], 'sudo astara-rootwrap /etc/rootwrap.conf'), - ] - - def test_update_set(self): - iface = mock.Mock() - a = netaddr.IPNetwork('192.168.101.2/24') - b = netaddr.IPNetwork('192.168.102.2/24') - c = netaddr.IPNetwork('192.168.103.2/24') - iface.all_addresses = [a, b] - iface.ifname = 'em0' - - old_iface = mock.Mock() - old_iface.all_addresses = [b, c] - old_iface.ifname = 'em0' - - mgr = ip.IPManager() - with mock.patch.object( - mgr, 'generic_to_host', lambda x: x.replace('ge', 'em') - ): - mgr._update_set( - 'em0', iface, old_iface, 'all_addresses', - lambda g: ('addr', 'add', '/'.join(map(str, g)), 'dev', 'em0'), - lambda g: ('addr', 'del', '/'.join(map(str, g)), 'dev', 'em0') - ) - - assert self.mock_execute.call_args_list == [ - mock.call([ - 'ip', 'addr', 'add', str(a), 'dev', 'em0' - ], 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['ip', 'link', 'set', 'em0', 'up'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['ip', 'addr', 'show', 'em0']), - mock.call([ - 'ip', 'addr', 'del', str(c), 'dev', 'em0' - ], 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['conntrack', '-D', '-d', str(c.ip)], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['conntrack', '-D', '-q', str(c.ip)], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - ] - - def test_update_set_no_diff(self): - a = netaddr.IPNetwork('192.168.101.2/24') - b = netaddr.IPNetwork('192.168.102.2/24') - iface = mock.Mock() - iface.all_addresses = [a, b] - - old_iface = mock.Mock() - old_iface.all_addresses = [a, b] - - mgr = ip.IPManager() - mgr._update_set( - 'em0', iface, old_iface, 'all_addresses', - lambda g: ('em0', 'add', g), lambda g: ('em0', 'del', g) - ) - self.assertEqual(self.mock_execute.call_count, 0) - - -class TestDisableDAD(TestCase): - """ - Duplicate Address Detection should be auto-disabled for non-external - networks. - """ - - def setUp(self): - self.execute_patch = mock.patch('astara_router.utils.execute') - self.mock_execute = self.execute_patch.start() - - def tearDown(self): - self.execute_patch.stop() - - def test_dad_for_external(self): - mgr = ip.IPManager() - with mock.patch.object(mgr, 'generic_to_host', lambda x: x): - mgr.disable_duplicate_address_detection(models.Network( - 'ABC123', - models.Interface('eth1'), - network_type=models.Network.TYPE_EXTERNAL - )) - assert self.mock_execute.call_count == 0 - - def test_dad_for_management(self): - mgr = ip.IPManager() - with mock.patch.object(mgr, 'generic_to_host', lambda x: x): - mgr.disable_duplicate_address_detection(models.Network( - 'ABC123', - models.Interface('eth0'), - network_type=models.Network.TYPE_MANAGEMENT - )) - assert self.mock_execute.call_count == 1 - assert self.mock_execute.call_args_list == [ - mock.call([ - 'sysctl', '-w', 'net.ipv6.conf.eth0.accept_dad=0' - ], 'sudo astara-rootwrap /etc/rootwrap.conf'), - ] - - def test_dad_for_internal(self): - mgr = ip.IPManager() - with mock.patch.object(mgr, 'generic_to_host', lambda x: x): - mgr.disable_duplicate_address_detection(models.Network( - 'ABC123', - models.Interface('eth2'), - network_type=models.Network.TYPE_INTERNAL - )) - assert self.mock_execute.call_count == 1 - assert self.mock_execute.call_args_list == [ - mock.call([ - 'sysctl', '-w', 'net.ipv6.conf.eth2.accept_dad=0' - ], 'sudo astara-rootwrap /etc/rootwrap.conf'), - ] - - def test_sysctl_failure(self): - logger = ip.LOG - logger.level = logging.DEBUG - buff = StringIO() - handler = logging.StreamHandler(buff) - - self.mock_execute.side_effect = RuntimeError - mgr = ip.IPManager() - with mock.patch.object(mgr, 'generic_to_host', lambda x: x): - try: - logger.addHandler(handler) - mgr.disable_duplicate_address_detection(models.Network( - 'ABC123', - models.Interface('eth0'), - network_type=models.Network.TYPE_MANAGEMENT - )) - assert 'Failed to disable v6 dad on eth0' in buff.getvalue() - finally: - logger.removeHandler(handler) - - -class ParseTestCase(TestCase): - def test_parse_interfaces(self): - with mock.patch.object(ip, '_parse_interface') as parse: - parse.side_effect = lambda x: x - - retval = ip._parse_interfaces(SAMPLE_OUTPUT) - self.assertEqual(len(retval), 3) - - def test_parse_over_many_interfaces(self): - out = SAMPLE_OUTPUT - for i in range(150): - out += """\n%d: eth%d: mtu 1500 qdisc noop state DOWN qlen 1000 - link/ether fa:16:3e:de:ad:be brd ff:ff:ff:ff:ff:ff""" % (i+4, i+2) - retval = ip._parse_interfaces(out, ['eth']) - self.assertEqual(len(retval), 152) - - def test_parse_interfaces_with_filter(self): - with mock.patch.object(ip, '_parse_interface') as parse: - parse.side_effect = lambda x: x - retval = ip._parse_interfaces(SAMPLE_OUTPUT, ['eth']) - self.assertEqual(len(retval), 2) - - for chunk in retval: - assert re.search('^[0-9]: eth', chunk) is not None - - def test_parse_interface(self): - retval = ip._parse_interface(SAMPLE_SINGLE_OUTPUT) - self.assertEqual(retval.ifname, 'eth1') - self.assertEqual(retval.lladdr, 'fa:16:3e:7a:d8:64') - self.assertEqual(retval.mtu, 1500) - self.assertEqual(retval.flags, [ - 'BROADCAST', - 'MULTICAST', - 'UP', - 'LOWER_UP' - ]) - - def test_parse_head(self): - expected = dict( - ifname='eth1', - mtu=1500, - flags=['BROADCAST', 'MULTICAST', 'UP', 'LOWER_UP'] - ) - retval = ip._parse_head(SAMPLE_SINGLE_OUTPUT.split('\n')[0]) - self.assertEqual(retval, expected) - - def test_parse_lladdr(self): - retval = ip._parse_lladdr(SAMPLE_SINGLE_OUTPUT.split('\n')[1]) - self.assertEqual( - retval, - 'fa:16:3e:7a:d8:64' - ) - - def test_parse_inet(self): - inet_sample = SAMPLE_SINGLE_OUTPUT.split('\n')[2].strip() - retval = ip._parse_inet(inet_sample) - - self.assertEqual(str(retval), - str(netaddr.IPNetwork('192.168.105.2/24'))) - - def test_parse_inet6(self): - inet_sample = SAMPLE_SINGLE_OUTPUT.split('\n')[3].strip() - retval = ip._parse_inet(inet_sample) - - self.assertEqual( - str(retval), - str(netaddr.IPNetwork('fe80::f816:3eff:fe7a:d864/64')) - ) - - -class TestVRRPIPManager(TestCase): - def setUp(self): - super(TestVRRPIPManager, self).setUp() - self.fake_keepalived = mock.Mock( - spec=KeepalivedManager) - p = 'astara_router.drivers.keepalived.KeepalivedManager' - with mock.patch(p) as ka: - ka.return_value = self.fake_keepalived - self.mgr = ip.VRRPIPManager() - - @mock.patch.object(ip.VRRPIPManager, 'ensure_mapping') - @mock.patch('astara_router.drivers.keepalived.KeepalivedManager') - def test_init(self, fake_keepalived, fake_ensure_map): - mgr = ip.VRRPIPManager() - self.assertTrue(fake_ensure_map.called) - fake_keepalived.return_value = 'fake_keepalived' - self.assertTrue(fake_keepalived.called) - mgr.keepalived = 'fake_keepalived' - - def test_set_peers(self): - self.mgr.set_peers(['foo', 'bar']) - self.assertEqual( - self.mgr.keepalived.peers, ['foo', 'bar']) - - def test_set_prio(self): - self.mgr.set_priority(100) - self.mgr.keepalived.set_priority.assert_called_with(100) - - @mock.patch.object(ip.VRRPIPManager, 'generic_to_host') - @mock.patch.object(ip.VRRPIPManager, 'update_interface') - @mock.patch.object(ip.VRRPIPManager, 'up') - def test_update_interfaces(self, fake_up, fake_update_int, fake_gth): - interface = fakes.fake_interface() - mgt_interface = fakes.fake_mgt_interface() - fake_gth.return_value = 'eth1' - self.mgr.update_interfaces( - interfaces=[interface, mgt_interface], - ) - - self.assertEqual(len(fake_update_int.call_args_list), 1) - fake_update_int.assert_called_with(mgt_interface) - self.mgr.keepalived.set_management_address.assert_called_with( - address=netaddr.IPAddress(mgt_interface.addresses[0])) - - self.assertEqual(len(fake_up.call_args_list), 1) - fake_up.assert_called_with(interface) - self.mgr.keepalived.add_vrrp_instance.assert_called_with( - addresses=[netaddr.IPNetwork(interface.addresses[0])], - interface='eth1') - - def test_reload(self): - self.mgr.reload() - self.assertTrue(self.mgr.keepalived.reload.called) - - @mock.patch.object(ip.VRRPIPManager, 'generic_to_host') - def test__set_default_gateway_v4(self, fake_gth): - fake_gth.return_value = 'eth0' - ip = netaddr.IPAddress('10.0.0.1') - self.mgr._set_default_gateway(gateway_ip=ip, ifname='ge0') - self.mgr.keepalived.set_default_gateway.assert_called_with( - ip_version=4, - gateway_ip=netaddr.IPAddress('10.0.0.1'), - interface='eth0', - ) - - @mock.patch.object(ip.VRRPIPManager, 'generic_to_host') - def test__set_default_gateway_v6(self, fake_gth): - fake_gth.return_value = 'eth0' - v6_ip = 'fdca:3ba5:a17a:acda:f816:3eff:fe5d:84' - ip = netaddr.IPAddress(v6_ip) - self.mgr._set_default_gateway(gateway_ip=ip, ifname='ge0') - self.mgr.keepalived.set_default_gateway.assert_called_with( - ip_version=6, - gateway_ip=netaddr.IPAddress(v6_ip), - interface='eth0', - ) diff --git a/test/unit/drivers/test_iptables.py b/test/unit/drivers/test_iptables.py deleted file mode 100644 index d032fd9..0000000 --- a/test/unit/drivers/test_iptables.py +++ /dev/null @@ -1,222 +0,0 @@ -from copy import deepcopy -from unittest import TestCase - -import mock -import netaddr - -from astara_router import models -from astara_router.drivers import iptables - -CONFIG = models.RouterConfiguration({ - 'networks': [{ - 'network_id': 'ABC123', - 'interface': { - 'ifname': 'eth0', - 'addresses': [ - 'fdca:3ba5:a17a:acda:f816:3eff:fe66:33b6/64', - 'fe80::f816:3eff:fe66:33b6/64' - ] - }, - 'name': 'mgt', - 'network_type': models.Network.TYPE_MANAGEMENT, - }, { - 'network_id': 'ABC456', - 'interface': { - 'ifname': 'eth1', - 'addresses': [ - '172.16.77.2/24', - 'fdee:9f85:83be:0:f816:3eff:fe42:a9f/48' - ] - }, - 'name': 'ext', - 'network_type': models.Network.TYPE_EXTERNAL, - 'subnets': [{ - 'id': 'theid', - 'cidr': '172.16.77.0/24', - 'gateway_ip': '172.16.77.1', - 'dhcp_enabled': True, - 'dns_nameservers': [] - }] - }, { - 'network_id': 'ABC789', - 'interface': { - 'ifname': 'eth2', - 'addresses': [ - '192.168.0.1/24', - 'fdd6:a1fa:cfa8:9df::1/64' - ] - }, - 'name': 'internal', - 'network_type': models.Network.TYPE_INTERNAL, - 'subnets': [{ - 'id': 'theid', - 'cidr': '192.168.0.0/24', - 'gateway_ip': '192.168.0.1', - 'dhcp_enabled': True, - 'dns_nameservers': [] - }] - }], - 'floating_ips': [{ - 'fixed_ip': '192.168.0.2', - 'floating_ip': '172.16.77.50' - }] -}) - -V4_OUTPUT = [ - '*filter', - ':INPUT DROP [0:0]', - ':FORWARD ACCEPT [0:0]', - ':OUTPUT ACCEPT [0:0]', - '-A INPUT -i lo -j ACCEPT', - '-A INPUT -p icmp --icmp-type echo-request -j ACCEPT', - '-A INPUT -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT', - '-A INPUT -i eth2 -p udp -m udp --dport 67 -j ACCEPT', - '-A INPUT -i eth2 -p tcp -m tcp --dport 67 -j ACCEPT', - '-A INPUT -i eth2 -j ACCEPT', - '-A INPUT -i eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT', - 'COMMIT', - '*nat', - ':PUBLIC_SNAT - [0:0]', - '-A PUBLIC_SNAT -m mark --mark 0xACDA -j RETURN', - '-A PUBLIC_SNAT -s 192.168.0.2 -j SNAT --to 172.16.77.50', - '-A PUBLIC_SNAT ! -o eth0 -j SNAT --to 172.16.77.2', - ':PREROUTING ACCEPT [0:0]', - ':INPUT ACCEPT [0:0]', - ':OUTPUT ACCEPT [0:0]', - ':POSTROUTING ACCEPT [0:0]', - '-A PREROUTING -i eth1 -d 172.16.77.50 -j DNAT --to-destination 192.168.0.2', # noqa - '-A PREROUTING -i eth2 -d 172.16.77.50 -j DNAT --to-destination 192.168.0.2', # noqa - '-A POSTROUTING -s 192.168.0.0/24 -j PUBLIC_SNAT', - '-A PREROUTING -i eth2 -d 169.254.169.254 -p tcp -m tcp --dport 80 -j DNAT --to-destination 192.168.0.1:9602', # noqa - '-A POSTROUTING -o eth1 -j MASQUERADE', - 'COMMIT', - '*mangle', - ':INPUT - [0:0]', - ':OUTPUT - [0:0]', - ':FORWARD - [0:0]', - ':PREROUTING - [0:0]', - ':POSTROUTING - [0:0]', - '-A POSTROUTING -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill', - 'COMMIT', - '*raw', - ':INPUT - [0:0]', - ':OUTPUT - [0:0]', - ':FORWARD - [0:0]', - ':PREROUTING - [0:0]', - '-A OUTPUT -j MARK --set-mark 0xACDA', - '-A PREROUTING -i eth1 -j MARK --set-mark 0xACDA', - '-A PREROUTING -d 192.168.0.1/24 -j MARK --set-mark 0xACDA', - ':POSTROUTING - [0:0]', - 'COMMIT' -] - -V6_OUTPUT = [ - '*filter', - ':INPUT DROP [0:0]', - ':FORWARD ACCEPT [0:0]', - ':OUTPUT ACCEPT [0:0]', - '-A INPUT -i lo -j ACCEPT', - '-A INPUT -p icmpv6 -j ACCEPT', - '-A INPUT -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT', - '-A INPUT -i eth0 -p tcp -m tcp --dport 22 -j ACCEPT', - '-A INPUT -i eth0 -p tcp -m tcp --dport 5000 -j ACCEPT', - '-A INPUT -i eth0 -p tcp -m tcp --dport 9697 -j ACCEPT', - '-A INPUT -i !eth0 -d fdca:3ba5:a17a:acda:f816:3eff:fe66:33b6 -j DROP', - '-A INPUT -i eth2 -p udp -m udp --dport 546 -j ACCEPT', - '-A INPUT -i eth2 -p tcp -m tcp --dport 546 -j ACCEPT', - '-A INPUT -i eth2 -j ACCEPT', - '-A INPUT -i eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT', - 'COMMIT' -] - - -class TestIPTablesRouterConfiguration(TestCase): - - def setUp(self): - super(TestIPTablesRouterConfiguration, self).setUp() - self.execute = mock.patch('astara_router.utils.execute').start() - self.replace = mock.patch('astara_router.utils.replace_file').start() - self.patches = [self.execute, self.replace] - - def tearDown(self): - super(TestIPTablesRouterConfiguration, self).tearDown() - for p in self.patches: - p.stop() - - def test_complete(self): - mgr = iptables.IPTablesManager() - mgr.save_config(CONFIG, { - 'ge0': 'eth0', - 'ge1': 'eth1', - 'ge2': 'eth2' - }) - - assert self.replace.call_count == 2 - - assert mock.call( - '/tmp/ip4tables.rules', - '\n'.join(V4_OUTPUT) + '\n' - ) in self.replace.call_args_list - - assert mock.call( - '/tmp/ip6tables.rules', - '\n'.join(V6_OUTPUT) + '\n' - ) in self.replace.call_args_list - - assert self.execute.call_args_list == [ - mock.call( - ['mv', '/tmp/ip4tables.rules', '/etc/iptables/rules.v4'], - 'sudo astara-rootwrap /etc/rootwrap.conf' - ), - mock.call( - ['mv', '/tmp/ip6tables.rules', '/etc/iptables/rules.v6'], - 'sudo astara-rootwrap /etc/rootwrap.conf' - ) - ] - - @mock.patch('os.path.isfile') - def test_restart_iptables_persistent(self, mock_isfile): - mock_isfile.return_value = False - mgr = iptables.IPTablesManager() - mgr.restart() - assert self.execute.call_args_list == [ - mock.call(['service', 'iptables-persistent', 'restart'], - 'sudo astara-rootwrap /etc/rootwrap.conf') - ] - - @mock.patch('os.path.isfile') - def test_restart_netfilter_persistent(self, mock_isfile): - mock_isfile.return_value = True - mgr = iptables.IPTablesManager() - mgr.restart() - assert self.execute.call_args_list == [ - mock.call(['service', 'netfilter-persistent', 'restart'], - 'sudo astara-rootwrap /etc/rootwrap.conf') - ] - - def test_mixed_floating_ip_versions(self): - # Neutron has a bug whereby you can create a floating ip that has - # mixed IP versions between the fixed and floating address. If - # people create these accidentally, just ignore them (because - # iptables will barf if it encounters them) - mgr = iptables.IPTablesManager() - config = deepcopy(CONFIG) - config.floating_ips[0].fixed_ip = netaddr.IPAddress( - 'fdca:3ba5:a17a:acda:f816:3eff:fe66:33b6' - ) - assert map(str, mgr._build_floating_ips(CONFIG)) == [ - '-A PREROUTING -i eth1 -d 172.16.77.50 -j DNAT --to-destination 192.168.0.2', # noqa - '-A PREROUTING -i eth2 -d 172.16.77.50 -j DNAT --to-destination 192.168.0.2', # noqa - '-A POSTROUTING -s 192.168.0.0/24 -j PUBLIC_SNAT' - ] - assert mgr._build_floating_ips(config) == [] - - @mock.patch.object(iptables.IPTablesManager, 'get_external_network') - def test_no_ext_port(self, fake_get_ext_net): - fake_get_ext_net.return_value = None - mgr = iptables.IPTablesManager() - mgr.save_config(CONFIG, { - 'ge0': 'eth0', - 'ge1': 'eth1', - 'ge2': 'eth2' - }) diff --git a/test/unit/drivers/test_keepalived.py b/test/unit/drivers/test_keepalived.py deleted file mode 100644 index eef3948..0000000 --- a/test/unit/drivers/test_keepalived.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright 2016 Akanda, Inc. -# -# Author: Akanda, 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 unittest2 import TestCase -import mock -import netaddr - -from astara_router.drivers import keepalived - - -class KeepalivedVipAddressTestCase(TestCase): - def test_vip_address(self): - addr = netaddr.IPNetwork('10.0.0.1/32') - vip = keepalived.KeepalivedVipAddress( - address=addr, interface='eth0') - self.assertEqual(vip.address, addr) - self.assertEqual(vip.interface, 'eth0') - - def test_vip_address_equal(self): - addr = netaddr.IPNetwork('10.0.0.1/32') - vip1 = keepalived.KeepalivedVipAddress( - address=addr, interface='eth0') - addr = netaddr.IPNetwork('10.0.0.1/32') - vip2 = keepalived.KeepalivedVipAddress( - address=addr, interface='eth0') - self.assertTrue(vip1 == vip2) - - def test_vip_address_not_equal(self): - addr = netaddr.IPNetwork('10.0.0.1/32') - vip1 = keepalived.KeepalivedVipAddress( - address=addr, interface='eth0') - addr = netaddr.IPNetwork('10.0.0.21/32') - vip2 = keepalived.KeepalivedVipAddress( - address=addr, interface='eth0') - self.assertFalse(vip1 == vip2) - - -class KeepalivedRouteTestCase(TestCase): - def test_keepalived_route(self): - route = keepalived.KeepalivedRoute( - destination='10.0.0.0/24', - gateway='10.0.0.1') - self.assertEqual(route.destination, '10.0.0.0/24') - self.assertEqual(route.gateway, '10.0.0.1') - - def test_keepalived_route_equal(self): - route1 = keepalived.KeepalivedRoute( - destination='10.0.0.0/24', - gateway='10.0.0.1') - route2 = keepalived.KeepalivedRoute( - destination='10.0.0.0/24', - gateway='10.0.0.1') - self.assertTrue(route1 == route2) - - def test_keepalived_route_not_equal(self): - route1 = keepalived.KeepalivedRoute( - destination='10.0.0.0/24', - gateway='10.0.0.1') - route2 = keepalived.KeepalivedRoute( - destination='10.0.0.0/24', - gateway='10.0.0.2') - self.assertFalse(route1 == route2) - - -class KeepalivedInstanceTestCase(TestCase): - def setUp(self): - self.instance = keepalived.KeepalivedInstance( - interface='eth0', - vrrp_id=1, - unicast_src_ip='10.0.0.1') - - def test_init(self): - self.assertEqual(self.instance.interface, 'eth0') - self.assertEqual(self.instance.vrrp_id, 1) - self.assertEqual(self.instance.unicast_src_ip, '10.0.0.1') - self.assertEqual(self.instance.name, 'astara_vrrp_eth0') - - @mock.patch.object(keepalived, 'KeepalivedVipAddress') - def test_add_vip(self, fake_vip): - addr = netaddr.IPNetwork('10.0.0.1/32') - fake_vip.return_value = 'fake_vip' - self.instance.add_vip(addr) - self.assertIn('fake_vip', self.instance.vips) - fake_vip.assert_called_with(addr, self.instance.interface) - - @mock.patch.object(keepalived, 'KeepalivedRoute') - def test_add_route(self, fake_route): - fake_route.return_value = 'fake_route' - self.instance.add_route('10.0.0.0/24', '10.0.0.1') - self.assertIn('fake_route', self.instance.routes) - fake_route.assert_called_with('10.0.0.0/24', '10.0.0.1') - - -class KeepalivedManagerTestCase(TestCase): - def setUp(self): - super(KeepalivedManagerTestCase, self).setUp() - self.fake_instance = mock.Mock( - spec=keepalived.KeepalivedInstance, name='fake_instance') - self.get_instance_p = mock.patch.object( - keepalived.KeepalivedManager, '_get_instance') - self.fake_get_instance = self.get_instance_p.start() - self.fake_get_instance.return_value = self.fake_instance - self.addCleanup(self.get_instance_p.stop) - self.mgr = keepalived.KeepalivedManager() - self.mgr.instances = { - 'eth0': self.fake_instance - } - - def test_set_management_address(self): - self.mgr.set_management_address('10.0.0.1') - self.assertEqual(self.fake_instance.unicast_src_ip, '10.0.0.1') - - def test_set_default_gateway(self): - self.mgr.set_default_gateway( - ip_version=4, gateway_ip='10.0.0.1', interface='eth0') - self.fake_instance.add_route.assert_called_with( - 'default', '10.0.0.1') - - def test_set_default_gateway_v6(self): - ip = 'fdca:3ba5:a17a:acda:f816:3eff:fe5d:84' - self.mgr.set_default_gateway( - ip_version=6, gateway_ip=ip, interface='eth0') - self.fake_instance.add_route.assert_called_with( - 'default6', ip) - - def test_set_priority(self): - self.mgr.set_priority(60) - self.assertEqual(self.mgr.priority, 60) diff --git a/test/unit/drivers/test_metadata.py b/test/unit/drivers/test_metadata.py deleted file mode 100644 index 6bb22b1..0000000 --- a/test/unit/drivers/test_metadata.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 __builtin__ - -import json -import mock - -from unittest2 import TestCase - -from astara_router.drivers import metadata - -CONFIG = mock.Mock() -CONFIG.hostname = 'astara' -CONFIG.management_address = 'fdca:3ba5:a17a:acda:f816:3eff:fe66:33b6' - - -class HostnameTestCase(TestCase): - """ - """ - def setUp(self): - self.mgr = metadata.MetadataManager() - self.config_dict = { - 'networks': {'tenant_net_id': [], 'public_net_id': []}, - 'orchestrator_metadata_address': '10.0.0.1', - 'orchestrator_metadata_port': '5000', - } - - tenant_net = mock.Mock( - is_tenant_network=mock.Mock(return_value=True), - id='tenant_net_id', - ) - public_net = mock.Mock( - is_tenant_network=mock.Mock(return_value=False), - id='public_net_id', - ) - - self.config = mock.Mock() - self.config.networks = [tenant_net, public_net] - self.config.metadata_address = '10.0.0.1' - self.config.metadata_port = '5000' - - def _test_should_restart(self, exp_result): - config_json = json.dumps(self.config_dict) - with mock.patch.object( - __builtin__, 'open', mock.mock_open(read_data=config_json) - ): - self.assertEqual( - self.mgr.should_restart(self.config), exp_result) - - def test_should_restart_false(self): - self._test_should_restart(False) - - def test_should_restart_true_networks_change(self): - self.config_dict['networks'] = { - 'foo_net_id': [], 'public_net_id': []} - self._test_should_restart(True) - - def test_should_restart_true_metadata_addr_change(self): - self.config_dict['orchestrator_metadata_address'] = '11.1.1.1' - self._test_should_restart(True) - - def test_should_restart_true_metadata_port_change(self): - self.config_dict['orchestrator_metadata_port'] = '6000' - self._test_should_restart(True) - - def test_should_restart_true_config_read_err(self): - with mock.patch.object( - __builtin__, 'open', mock.mock_open() - ) as _o: - _o.side_effect = IOError() - self.assertEqual( - self.mgr.should_restart(self.config), True) diff --git a/test/unit/drivers/test_route.py b/test/unit/drivers/test_route.py deleted file mode 100644 index db020f4..0000000 --- a/test/unit/drivers/test_route.py +++ /dev/null @@ -1,440 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 mock -import unittest2 - -import netaddr -from dogpile.cache import make_region - -from astara_router import models -from astara_router.drivers import ip - - -class RouteTest(unittest2.TestCase): - - def setUp(self): - super(RouteTest, self).setUp() - self.mgr = ip.IPManager() - self.host_patch = mock.patch.object( - self.mgr, 'generic_to_host', lambda x: x.replace('ge', 'eth') - ) - self.host_patch.start() - - def tearDown(self): - super(RouteTest, self).tearDown() - self.host_patch.stop() - - def test_get_default_gateway_v6_missing(self): - output = '' - with mock.patch.object(self.mgr, 'sudo') as sudo: - sudo.return_value = output - self.assertEqual( - None, - self.mgr._get_default_gateway(6) - ) - sudo.assert_called_with('-6', 'route', 'show') - - def test_get_default_gateway_v6(self): - output = "default via fe80::f816:3eff:fe33:deac dev eth2 metric 1024" - with mock.patch.object(self.mgr, 'sudo') as sudo: - sudo.return_value = output - self.assertEqual( - 'fe80::f816:3eff:fe33:deac', - self.mgr._get_default_gateway(6) - ) - sudo.assert_called_with('-6', 'route', 'show') - - def test_get_default_gateway_v4(self): - output = "default via 192.168.122.1 dev eth0 metric 100" - with mock.patch.object(self.mgr, 'sudo') as sudo: - sudo.return_value = output - self.assertEqual( - '192.168.122.1', - self.mgr._get_default_gateway(4) - ) - sudo.assert_called_with('-4', 'route', 'show') - - def test_set_default_v4_matches_current(self): - ip_s = '192.168.122.1' - ip = mock.MagicMock() - ip.version = 4 - ip.__str__.return_value = ip_s - with mock.patch.object(self.mgr, '_get_default_gateway') as get: - get.return_value = ip_s - with mock.patch.object(self.mgr, 'sudo') as sudo: - sudo.side_effect = AssertionError('should not be called') - self.mgr._set_default_gateway(ip, 'ge1') - - def test_set_default_v4_changes_current(self): - ip_s = '192.168.122.1' - ip = mock.MagicMock() - ip.version = 4 - ip.__str__.return_value = ip_s - with mock.patch.object(self.mgr, '_get_default_gateway') as get: - get.return_value = '192.168.122.254' - with mock.patch.object(self.mgr, 'sudo') as sudo: - self.mgr._set_default_gateway(ip, 'ge1') - assert sudo.call_args_list == [ - mock.call( - '-4', 'route', 'del', 'default', 'via', - get.return_value, 'dev', 'eth1' - ), - mock.call( - '-4', 'route', 'add', 'default', 'via', ip_s, - 'dev', 'eth1' - ) - ] - - def test_set_default_v4_no_current(self): - ip_s = '192.168.122.1' - ip = mock.MagicMock() - ip.version = 4 - ip.__str__.return_value = ip_s - with mock.patch.object(self.mgr, '_get_default_gateway') as get: - get.return_value = None - with mock.patch.object(self.mgr, 'sudo') as sudo: - self.mgr._set_default_gateway(ip, 'ge1') - sudo.assert_called_with( - '-4', 'route', 'add', 'default', 'via', '192.168.122.1', - 'dev', 'eth1' - ) - - def test_set_default_v6_matches_current(self): - ip_s = 'fe80::5054:ff:fee2:1d4f' - ip = mock.MagicMock() - ip.version = 6 - ip.__str__.return_value = ip_s - with mock.patch.object(self.mgr, '_get_default_gateway') as get: - get.return_value = ip_s - with mock.patch.object(self.mgr, 'sudo') as sudo: - sudo.side_effect = AssertionError('should not be called') - self.mgr._set_default_gateway(ip, 'ge1') - - def test_set_default_v6_changes_current(self): - ip_s = 'fe80::5054:ff:fee2:1d4f' - ip = mock.MagicMock() - ip.version = 6 - ip.__str__.return_value = ip_s - with mock.patch.object(self.mgr, '_get_default_gateway') as get: - get.return_value = 'fe80::5054:ff:fee2:aaaa' - with mock.patch.object(self.mgr, 'sudo') as sudo: - self.mgr._set_default_gateway(ip, 'ge1') - assert sudo.call_args_list == [ - mock.call( - '-6', 'route', 'del', 'default', 'via', - get.return_value, 'dev', 'eth1' - ), - mock.call( - '-6', 'route', 'add', 'default', 'via', ip_s, - 'dev', 'eth1' - ) - ] - - def test_set_default_v6_no_current(self): - ip_s = 'fe80::5054:ff:fee2:1d4f' - ip = mock.MagicMock() - ip.version = 6 - ip.__str__.return_value = ip_s - self.mgr.generic_mapping = {'ge1', 'eth1'} - with mock.patch.object(self.mgr, '_get_default_gateway') as get: - get.return_value = None - with mock.patch.object(self.mgr, 'sudo') as sudo: - self.mgr._set_default_gateway(ip, 'ge1') - sudo.assert_called_with( - '-6', 'route', 'add', 'default', 'via', ip_s, - 'dev', 'eth1' - ) - - def test_update_default_no_inputs(self): - c = models.RouterConfiguration({}) - with mock.patch.object(self.mgr, '_set_default_gateway') as set: - set.side_effect = AssertionError( - 'should not try to set default gw' - ) - self.mgr.update_default_gateway(c) - - def test_update_default_v4_from_gateway(self): - c = models.RouterConfiguration({'default_v4_gateway': '172.16.77.1'}) - with mock.patch.object(self.mgr, '_set_default_gateway') as set: - self.mgr.update_default_gateway(c) - set.assert_called_once_with(c.default_v4_gateway, None) - - def test_update_default_v4_from_subnet(self): - subnet = dict( - id='theid', - cidr='192.168.89.0/24', - gateway_ip='192.168.89.1', - dhcp_enabled=True, - dns_nameservers=[], - ) - network = dict( - network_id='netid', - name='thenet', - interface=dict(ifname='ge0', addresses=['fe80::2']), - allocations=[], - subnets=[subnet], - network_type='external', - ) - c = models.RouterConfiguration({'networks': [network]}) - with mock.patch.object(self.mgr, '_set_default_gateway') as set: - self.mgr.update_default_gateway(c) - net = c.networks[0] - snet = net.subnets[0] - set.assert_called_once_with(snet.gateway_ip, 'ge0') - - def test_update_multiple_v4_subnets(self): - subnet = dict( - id='id-1', - cidr='192.168.89.0/24', - gateway_ip='192.168.89.1', - dhcp_enabled=True, - dns_nameservers=[], - ) - subnet2 = dict( - id='id-2', - cidr='192.168.71.0/24', - gateway_ip='192.168.71.1', - dhcp_enabled=True, - dns_nameservers=[], - ) - network = dict( - network_id='netid', - name='thenet', - interface=dict(ifname='ge0', addresses=['fe80::2']), - allocations=[], - subnets=[subnet, subnet2], - network_type='external', - ) - c = models.RouterConfiguration({'networks': [network]}) - with mock.patch.object(self.mgr, '_set_default_gateway') as set: - self.mgr.update_default_gateway(c) - net = c.networks[0] - snet = net.subnets[0] - set.assert_called_once_with(snet.gateway_ip, 'ge0') - - def test_update_default_v6(self): - subnet = dict( - id='theid', - cidr='fe80::1/64', - gateway_ip='fe80::1', - dhcp_enabled=True, - dns_nameservers=[], - ) - network = dict( - network_id='netid', - name='thenet', - interface=dict(ifname='ge0', addresses=['fe80::2']), - allocations=[], - subnets=[subnet], - network_type='external', - ) - c = models.RouterConfiguration({'networks': [network]}) - with mock.patch.object(self.mgr, '_set_default_gateway') as set: - self.mgr.update_default_gateway(c) - net = c.networks[0] - snet = net.subnets[0] - set.assert_called_once_with(snet.gateway_ip, 'ge0') - - def test_update_default_multiple_v6(self): - subnet = dict( - id='id-1', - cidr='fe80::1/64', - gateway_ip='fe80::1', - dhcp_enabled=True, - dns_nameservers=[], - ) - subnet2 = dict( - id='id-2', - cidr='fe89::1/64', - gateway_ip='fe89::1', - dhcp_enabled=True, - dns_nameservers=[], - ) - network = dict( - network_id='netid', - name='thenet', - interface=dict(ifname='ge0', addresses=['fe80::2']), - allocations=[], - subnets=[subnet, subnet2], - network_type='external', - ) - c = models.RouterConfiguration({'networks': [network]}) - with mock.patch.object(self.mgr, '_set_default_gateway') as set: - self.mgr.update_default_gateway(c) - net = c.networks[0] - snet = net.subnets[0] - set.assert_called_once_with(snet.gateway_ip, 'ge0') - - @mock.patch.object(ip.IPManager, '_set_default_gateway', - lambda *a, **kw: None) - def test_custom_host_routes(self): - subnet = dict( - id='theid', - cidr='192.168.89.0/24', - gateway_ip='192.168.89.1', - dhcp_enabled=True, - dns_nameservers=[], - host_routes=[{ - 'destination': '192.240.128.0/20', - 'nexthop': '192.168.89.2' - }] - ) - network = dict( - network_id='netid', - interface=dict(ifname='ge0', addresses=['fe80::2']), - subnets=[subnet] - ) - c = models.RouterConfiguration({'networks': [network]}) - - cache = make_region().configure('dogpile.cache.memory') - with mock.patch.object(self.mgr, 'sudo') as sudo: - - # ...so let's add one! - self.mgr.update_host_routes(c, cache) - sudo.assert_called_once_with( - '-4', 'route', 'add', '192.240.128.0/20', 'via', - '192.168.89.2', 'dev', 'eth0' - ) - - # db[subnet.cidr] should contain the above route - expected = set() - expected.add(( - netaddr.IPNetwork('192.240.138.0/20'), - netaddr.IPAddress('192.168.89.2') - )) - self.assertEqual(len(cache.get('host_routes')), 1) - self.assertEqual( - cache.get('host_routes')[subnet['cidr']] - expected, - set() - ) - - # Empty the host_routes list - sudo.reset_mock() - subnet['host_routes'] = [] - c = models.RouterConfiguration({'networks': [network]}) - self.mgr.update_host_routes(c, cache) - sudo.assert_called_once_with( - '-4', 'route', 'del', '192.240.128.0/20', 'via', - '192.168.89.2', 'dev', 'eth0' - ) - self.assertEqual(len(cache.get('host_routes')), 0) - - # ...this time, let's add multiple routes and ensure they're added - sudo.reset_mock() - subnet['host_routes'] = [{ - 'destination': '192.240.128.0/20', - 'nexthop': '192.168.89.2' - }, { - 'destination': '192.220.128.0/20', - 'nexthop': '192.168.89.3' - }] - c = models.RouterConfiguration({'networks': [network]}) - self.mgr.update_host_routes(c, cache) - self.assertEqual(sudo.call_args_list, [ - mock.call('-4', 'route', 'add', '192.240.128.0/20', - 'via', '192.168.89.2', 'dev', 'eth0'), - mock.call('-4', 'route', 'add', '192.220.128.0/20', - 'via', '192.168.89.3', 'dev', 'eth0'), - ]) - - # ...let's remove one and add another... - sudo.reset_mock() - subnet['host_routes'] = [{ - 'destination': '192.240.128.0/20', - 'nexthop': '192.168.89.2' - }, { - 'destination': '192.185.128.0/20', - 'nexthop': '192.168.89.4' - }] - c = models.RouterConfiguration({'networks': [network]}) - self.mgr.update_host_routes(c, cache) - self.assertEqual(sudo.call_args_list, [ - mock.call('-4', 'route', 'del', '192.220.128.0/20', - 'via', '192.168.89.3', 'dev', 'eth0'), - mock.call('-4', 'route', 'add', '192.185.128.0/20', - 'via', '192.168.89.4', 'dev', 'eth0') - ]) - - # ...let's add another subnet... - self.assertEqual(len(cache.get('host_routes')), 1) - sudo.reset_mock() - network['subnets'].append(dict( - id='add-1', - cidr='192.168.90.0/24', - gateway_ip='192.168.90.1', - dhcp_enabled=True, - dns_nameservers=[], - host_routes=[{ - 'destination': '192.240.128.0/20', - 'nexthop': '192.168.90.1' - }] - )) - c = models.RouterConfiguration({'networks': [network]}) - self.mgr.update_host_routes(c, cache) - self.assertEqual(sudo.call_args_list, [ - mock.call('-4', 'route', 'add', '192.240.128.0/20', - 'via', '192.168.90.1', 'dev', 'eth0') - ]) - self.assertEqual(len(cache.get('host_routes')), 2) - - # ...and finally, delete all custom host_routes... - sudo.reset_mock() - network['subnets'][0]['host_routes'] = [] - network['subnets'][1]['host_routes'] = [] - c = models.RouterConfiguration({'networks': [network]}) - self.mgr.update_host_routes(c, cache) - self.assertEqual(sudo.call_args_list, [ - mock.call('-4', 'route', 'del', '192.185.128.0/20', - 'via', '192.168.89.4', 'dev', 'eth0'), - mock.call('-4', 'route', 'del', '192.240.128.0/20', - 'via', '192.168.89.2', 'dev', 'eth0'), - mock.call('-4', 'route', 'del', '192.240.128.0/20', - 'via', '192.168.90.1', 'dev', 'eth0'), - ]) - self.assertEqual(len(cache.get('host_routes')), 0) - - def test_custom_host_routes_failure(self): - subnet = dict( - id='theid', - cidr='192.168.89.0/24', - gateway_ip='192.168.89.1', - dhcp_enabled=True, - dns_nameservers=[], - host_routes=[{ - 'destination': '192.240.128.0/20', - 'nexthop': '192.168.89.2' - }] - ) - network = dict( - network_id='netid', - interface=dict(ifname='ge0', addresses=['fe80::2']), - subnets=[subnet] - ) - c = models.RouterConfiguration({'networks': [network]}) - - cache = make_region().configure('dogpile.cache.memory') - with mock.patch.object(self.mgr, 'sudo') as sudo: - - sudo.side_effect = RuntimeError("Kaboom!") - - self.mgr.update_host_routes(c, cache) - sudo.assert_called_once_with( - '-4', 'route', 'add', '192.240.128.0/20', 'via', - '192.168.89.2', 'dev', 'eth0' - ) - self.assertEqual(len(cache.get('host_routes')), 0) diff --git a/test/unit/drivers/test_strongswan.py b/test/unit/drivers/test_strongswan.py deleted file mode 100644 index 32b3146..0000000 --- a/test/unit/drivers/test_strongswan.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 unittest2 import TestCase - -import mock -import netaddr -import re -import textwrap - -from astara_router.drivers.vpn import ipsec - -class StrongswanTestCase(TestCase): - """ - """ - def setUp(self): - self.mock_execute = mock.patch('astara_router.utils.execute').start() - self.mock_replace_file = mock.patch( - 'astara_router.utils.replace_file' - ).start() - self.addCleanup(mock.patch.stopall) - - self.mgr = ipsec.StrongswanManager() - - def test_save_config(self): - mock_config = mock.Mock() - with mock.patch.object(ipsec, 'jinja2') as mock_jinja: - - mock_env = mock_jinja.Environment.return_value - mock_get_template = mock_env.get_template - mock_render_rv = mock_get_template.return_value.render.return_value - - self.mgr.save_config(mock_config) - - mock_get_template.assert_has_calls([ - mock.call('ipsec.conf.j2'), - mock.call().render(vpnservices=mock_config.vpn), - mock.call('ipsec.secrets.j2'), - mock.call().render(vpnservices=mock_config.vpn), - ]) - - self.mock_replace_file.assert_has_calls([ - mock.call('/tmp/ipsec.conf', mock_render_rv), - mock.call('/tmp/ipsec.secrets', mock_render_rv), - ]) - - sudo = 'sudo astara-rootwrap /etc/rootwrap.conf' - - self.mock_execute.assert_has_calls([ - mock.call(['mv','/tmp/ipsec.conf', '/etc/ipsec.conf'], sudo), - mock.call( - ['mv', '/tmp/ipsec.secrets', '/etc/ipsec.secrets'], - sudo - ), - ]) - - def test_restart(self): - self.mgr.restart() - self.mock_execute.assert_has_calls([ - mock.call(['service', 'strongswan', 'status'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['service', 'strongswan', 'reload'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - ]) - - def test_restart_failure(self): - with mock.patch('astara_router.utils.execute') as execute: - execute.side_effect = [Exception('status failed!'), None] - self.mgr.restart() - execute.assert_has_calls([ - mock.call(['service', 'strongswan', 'status'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - mock.call(['service', 'strongswan', 'start'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - ]) - - def test_stop(self): - self.mgr.stop() - self.mock_execute.assert_has_calls([ - mock.call(['service', 'strongswan', 'stop'], - 'sudo astara-rootwrap /etc/rootwrap.conf'), - ]) diff --git a/test/unit/fakes.py b/test/unit/fakes.py deleted file mode 100644 index 63b5db5..0000000 --- a/test/unit/fakes.py +++ /dev/null @@ -1,255 +0,0 @@ -from copy import copy -import netaddr - -from astara_router import models - - -FAKE_SYSTEM_DICT = { - "tenant_id": "d22b149cee9b4eac8349c517eda00b89", - "hostname": "ak-loadbalancer-d22b149cee9b4eac8349c517eda00b89", - "networks": [ - { - "v4_conf_service": "static", - "network_type": "loadbalancer", - "v6_conf_service": "static", - "network_id": "b7fc9b39-401c-47cc-a07d-9f8cde75ccbf", - "allocations": [], - "subnets": [ - { - "id": "98a6270e-cf5f-4a60-9d7f-0d4524c00606", - "host_routes": [], - "cidr": "192.168.0.0/24", - "gateway_ip": "192.168.0.1", - "dns_nameservers": [], - "dhcp_enabled": True, - }, - { - "id": "ext-subnet-id", - "host_routes": [], - "cidr": "fdd6:a1fa:cfa8:6af6::/64", - "gateway_ip": "fdd6:a1fa:cfa8:6af6::1", - "dns_nameservers": [], - "dhcp_enabled": False - }], - "interface": { - "ifname": "ge1", - "addresses": [ - "192.168.0.137/24", - "fdd6:a1fa:cfa8:6af6:f816:3eff:fea0:8082/64" - ] - }, - }, - { - "v4_conf_service": "static", - "network_type": "management", - "v6_conf_service": "static", - "network_id": "43dc2fad-f6f9-4668-9695-fed50f7768aa", - "allocations": [], - "subnets": [ - { - "id": "mgt-subnet-id", - "host_routes": [], - "cidr": "fdca:3ba5:a17a:acda::/64", - "gateway_ip": "fdca:3ba5:a17a:acda::1", - "dns_nameservers": [], - "dhcp_enabled": True} - ], - "interface": { - "ifname": "ge0", - "addresses": ["fdca:3ba5:a17a:acda:f816:3eff:fee0:e1b0/64"] - }, - }] -} - -FAKE_LOADBALANCER_DICT = { - "id": "8ac54799-b143-48e5-94d4-e5e989592229", - "status": "ACTIVE", - "name": "balancer1", - "admin_state_up": True, - "tenant_id": "d22b149cee9b4eac8349c517eda00b89", - "vip_port": { - "name": "loadbalancer-8ac54799-b143-48e5-94d4-e5e989592229", - "network_id": "b7fc9b39-401c-47cc-a07d-9f8cde75ccbf", - "device_owner": "neutron:LOADBALANCERV2", - "mac_address": "fa:16:3e:a0:80:82", - "fixed_ips": [ - { - "subnet_id": "8c58b558-be54-45de-9873-169fe845bb80", - "ip_address": "192.168.0.137" - }, - { - "subnet_id": "89fe7a9d-be92-469c-9a1e-503a39462ed1", - "ip_address": "fdd6:a1fa:cfa8:6af6:f816:3eff:fea0:8082"} - ], - "id": "352e2867-06c6-4ced-8e81-1c016991fb38", - "device_id": "8ac54799-b143-48e5-94d4-e5e989592229" - }, - "vip_address": "192.168.0.137", - "id": "8ac54799-b143-48e5-94d4-e5e989592229", - "listeners": [], -} - -FAKE_LISTENER_DICT = { - 'admin_state_up': True, - 'default_pool': None, - 'id': '8dca64a2-beaa-484e-a3c8-59c9b63913e0', - 'name': 'listener1', - 'protocol': 'HTTP', - 'protocol_port': 80, - 'tenant_id': 'd22b149cee9b4eac8349c517eda00b89' -} - -FAKE_POOL_DICT = { - 'admin_state_up': True, - 'healthmonitor': None, - 'id': u'255c4d63-6199-4afc-abec-48c5ab46ac2e', - 'lb_algorithm': u'ROUND_ROBIN', - 'members': [], - 'name': u'pool1', - 'protocol': u'HTTP', - 'session_persistence': None, - 'tenant_id': u'd22b149cee9b4eac8349c517eda00b89' -} - -FAKE_MEMBER_DICT = { - 'address': u'192.168.0.194', - 'admin_state_up': True, - 'id': u'30fc9549-7804-4196-bb86-8ebabc3a79e2', - 'protocol_port': 80, - 'subnet': None, - 'tenant_id': u'd22b149cee9b4eac8349c517eda00b89', - 'weight': 1 -} - -FAKE_LIFETIME_DICT = { - 'units': u'seconds', - 'value': 3600, -} - -FAKE_DEAD_PEER_DETECTION_DICT = { - 'action': u'hold', - 'interval': 30, - 'timeout': 120 -} - -FAKE_IKEPOLICY_DICT = { - 'auth_algorithm': u'sha1', - 'encryption_algorithm': u'aes-128', - 'id': u'2b7dddc7-721f-4b93-bff3-20a7ff765726', - 'ike_version': u'v1', - 'lifetime': FAKE_LIFETIME_DICT, - 'name': u'ikepolicy1', - 'pfs': u'group5', - 'phase1_negotiation_mode': u'main', - 'tenant_id': u'd01558034b144068a4884fa7d8c03cc8' -} - -FAKE_IPSECPOLICY_DICT = { - 'auth_algorithm': u'sha1', - 'encapsulation_mode': u'tunnel', - 'encryption_algorithm': u'aes-128', - 'id': u'48f7ab18-f900-4ebe-9ef6-b1cc675f4e51', - 'lifetime': FAKE_LIFETIME_DICT, - 'name': u'ipsecpolicy1', - 'pfs': u'group5', - 'tenant_id': u'd01558034b144068a4884fa7d8c03cc8', - 'transform_protocol': u'esp' -} - -FAKE_LOCAL_ENDPOINT_DICT = { - 'endpoints': [u'98a6270e-cf5f-4a60-9d7f-0d4524c00606'], - 'id': u'3fbb0b1f-3fbe-4f97-9ec7-eba7f6009b94', - 'name': u'local', - 'tenant_id': u'd01558034b144068a4884fa7d8c03cc8', - 'type': u'subnet' -} - -FAKE_PEER_ENDPOINT_DICT = { - 'endpoints': ['172.31.155.0/24'], - 'id': u'dc15b31c-54a6-4b83-a4b0-7a6b136bbb5b', - 'name': u'peer', - 'tenant_id': u'd01558034b144068a4884fa7d8c03cc8', - 'type': u'cidr' -} - -FAKE_IPSEC_CONNECTION_DICT = { - 'admin_state_up': True, - 'auth_mode': u'psk', - 'dpd': FAKE_DEAD_PEER_DETECTION_DICT, - 'id': u'bfb6da63-7979-405d-9193-eda5601cf74b', - 'ikepolicy': FAKE_IKEPOLICY_DICT, - 'initiator': u'bi-directional', - 'ipsecpolicy': FAKE_IPSECPOLICY_DICT, - 'local_ep_group': FAKE_LOCAL_ENDPOINT_DICT, - 'mtu': 1420, - 'name': u'theconn', - 'peer_address': '172.24.4.129', - 'peer_cidrs': [], - 'peer_ep_group': FAKE_PEER_ENDPOINT_DICT, - 'peer_id': u'172.24.4.129', - 'psk': u'secrete', - 'route_mode': u'static', - 'status': u'PENDING_CREATE', - 'tenant_id': u'd01558034b144068a4884fa7d8c03cc8', - 'vpnservice_id': u'1d5ff89a-d03f-4d57-b696-34ef5c53ae28' -} - -FAKE_IPSEC_VPNSERVICE_DICT = { - 'admin_state_up': True, - 'external_v4_ip': '172.24.4.2', - 'external_v6_ip': '2001:db8::1', - 'id': u'1d5ff89a-d03f-4d57-b696-34ef5c53ae28', - 'ipsec_connections': [FAKE_IPSEC_CONNECTION_DICT], - 'name': u'thevpn', - 'router_id': u'3d6d9ede-9b20-4610-9804-54ce1ef2bb43', - 'status': u'PENDING_CREATE', - 'subnet_id': None -} - -FAKE_VPN_DICT = { - 'vpn': { - 'ipsec': [FAKE_IPSEC_VPNSERVICE_DICT] - } -} - -FAKE_SYSTEM_WITH_VPN_DICT = dict(FAKE_SYSTEM_DICT, vpn=FAKE_VPN_DICT['vpn']) - - -def fake_loadbalancer_dict(listener=False, pool=False, members=False): - lb_dict = copy(FAKE_LOADBALANCER_DICT) - - if listener: - lb_dict['listeners'] = [copy(FAKE_LISTENER_DICT)] - - if pool: - if not listener: - raise Exception("Cannot create pool without a listener") - lb_dict['listeners'][0]['default_pool'] = \ - copy(FAKE_POOL_DICT) - - if members: - if not pool: - raise Exception("Cannot create member without a pool") - lb_dict['listeners'][0]['default_pool']['members'] = \ - [copy(FAKE_MEMBER_DICT)] - return lb_dict - - -def _fake_interface(ifname, addresses=None, management=False): - addresses = addresses or ['10.0.0.1'] - return models.Interface( - ifname=ifname, - description='fake_interface', - addresses=[netaddr.IPAddress(addr) for addr in addresses], - management=management, - ) - - -def fake_interface(ifname='ge1', addresses=None): - return _fake_interface( - ifname=ifname, addresses=(addresses or ['10.0.0.1']), management=False) - - -def fake_mgt_interface(ifname='ge0', addresses=None): - return _fake_interface( - ifname=ifname, addresses=(addresses or ['11.0.0.1']), management=True) diff --git a/test/unit/test_metadata_proxy.py b/test/unit/test_metadata_proxy.py deleted file mode 100644 index bccb644..0000000 --- a/test/unit/test_metadata_proxy.py +++ /dev/null @@ -1,87 +0,0 @@ -import argparse -import json -import tempfile -import unittest -import eventlet -import mock - -from astara_router import metadata_proxy - -config = json.dumps({ - "tenant_id": "ABC123", - "orchestrator_metadata_address": "192.168.25.30", - "orchestrator_metadata_port": 9697, - "networks": { - "net1": { - "listen_port": 9602, 'ip_instance_map': {'10.10.10.2': 'VM1'}}, - "net2": { - "listen_port": 9603, 'ip_instance_map': {'10.10.10.2': 'VM2'}}, - } -}) - - -class TestMetadataProxy(unittest.TestCase): - - @mock.patch('eventlet.monkey_patch', mock.Mock()) - @mock.patch('eventlet.listen') - @mock.patch.object(argparse.ArgumentParser, 'parse_args') - @mock.patch.object(eventlet.GreenPool, 'spawn_n') - @mock.patch.object(eventlet.GreenPool, 'waitall', mock.Mock()) - def test_spawn(self, spawn, parse_args, listen): - with tempfile.NamedTemporaryFile() as f: - f.write(config) - f.flush() - parse_args.return_value = mock.Mock( - daemonize=False, - config_file=f.name - ) - metadata_proxy.main() - listen.assert_has_calls( - [mock.call(('0.0.0.0', 9602), backlog=128), - mock.call(('0.0.0.0', 9603), backlog=128)], - any_order=True - ) - # call_args need to be order before we can test it - spawn_args = sorted(spawn.call_args_list, - key=lambda y: y[0][2].network_id) - server, socket, app = spawn_args[0][0] - assert server == eventlet.wsgi.server - assert isinstance(app, metadata_proxy.NetworkMetadataProxyHandler) - assert app.tenant_id == 'ABC123' - assert app.network_id in 'net1' - assert app.config_file == f.name - - server, socket, app = spawn_args[1][0] - assert server == eventlet.wsgi.server - assert isinstance(app, metadata_proxy.NetworkMetadataProxyHandler) - assert app.tenant_id == 'ABC123' - assert app.network_id in 'net2' - assert app.config_file == f.name - - @mock.patch('requests.get') - def test_request_proxying(self, get): - with tempfile.NamedTemporaryFile() as f: - f.write(config) - f.flush() - wsgi = metadata_proxy.NetworkMetadataProxyHandler( - 'ABC123', - 'net1', - f.name - ) - assert wsgi.config_mtime == 0 - get.return_value.status_code = 200 - wsgi._proxy_request('10.10.10.2', '/', '') - get.assert_called_once_with( - 'http://[192.168.25.30]:9697/', - headers={ - 'X-Quantum-Network-ID': 'net1', - 'X-Forwarded-For': '10.10.10.2', - 'X-Tenant-ID': 'ABC123', - 'X-Instance-ID': u'VM1' - } - ) - assert wsgi.config_mtime > 0 - mtime = wsgi.config_mtime - - wsgi._proxy_request('10.10.10.2', '/', '') - assert wsgi.config_mtime == mtime diff --git a/test/unit/test_models.py b/test/unit/test_models.py deleted file mode 100644 index c1c679b..0000000 --- a/test/unit/test_models.py +++ /dev/null @@ -1,802 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 copy -import mock -import netaddr - -from unittest2 import TestCase - -from astara_router import defaults, models -from test.unit import fakes - - -class InterfaceModelTestCase(TestCase): - """ - """ - def test_ifname(self): - iface = models.Interface(ifname="em0") - self.assertEqual(iface.ifname, "em0") - - def test_to_dict(self): - iface = models.Interface() - result = iface.to_dict() - expected = [ - 'addresses', 'description', 'groups', 'ifname', 'lladdr', - 'media', 'mtu', 'state'] - self.assertIsInstance(result, dict) - self.assertItemsEqual(result.keys(), expected) - - def test_to_dict_extended(self): - iface = models.Interface() - result = iface.to_dict(True) - expected = [ - 'addresses', 'description', 'groups', 'ifname', 'lladdr', - 'media', 'mtu', 'state', 'flags', 'extra_params'] - self.assertIsInstance(result, dict) - self.assertItemsEqual(result.keys(), expected) - - def test_repr(self): - iface = models.Interface(ifname='ge0', addresses=['192.168.1.1/24']) - expected = "" - self.assertEqual(expected, repr(iface)) - - def test_eq_other_none(self): - iface = models.Interface(ifname='ge0', addresses=['192.168.1.1/24']) - self.assertNotEqual(iface, None) - - def test_eq_other_same_instance(self): - iface = models.Interface(ifname='ge0', addresses=['192.168.1.1/24']) - self.assertEqual(iface, iface) - - def test_eq_other_same_values(self): - iface = models.Interface(ifname='ge0', addresses=['192.168.1.1/24']) - iface2 = models.Interface(ifname='ge0', addresses=['192.168.1.1/24']) - self.assertEqual(iface, iface2) - - def test_eq_other_changed_values(self): - iface = models.Interface(ifname='ge0', addresses=['192.168.1.1/24']) - iface2 = models.Interface(ifname='ge1', addresses=['192.168.1.2/24']) - self.assertNotEqual(iface, iface2) - - def test_description(self): - iface = models.Interface() - iface.description = 'the_description' - self.assertEqual('the_description', iface.description) - - def test_description_failure(self): - iface = models.Interface() - with self.assertRaises(ValueError): - iface.description = 'the description' - - def test_is_up_extra_params(self): - self.assertFalse(models.Interface().is_up) - iface = models.Interface(state='up') - self.assertTrue(iface.is_up) - - def test_is_up_flags(self): - self.assertFalse(models.Interface().is_up) - iface = models.Interface(flags=['UP']) - self.assertTrue(iface.is_up) - - def test_aliases(self): - addr1 = netaddr.IPNetwork('192.168.1.1/24') - addr2 = netaddr.IPNetwork('192.168.1.2/24') - - iface = models.Interface(ifname='ge0', addresses=[str(addr1)]) - iface.aliases = [addr2] - self.assertEqual(iface.addresses, [addr1]) - self.assertEqual(iface.aliases, [addr2]) - self.assertEqual(iface.all_addresses, [addr1, addr2]) - - def test_from_dict(self): - d = {'ifname': 'ge0', - 'addresses': ['192.168.1.1/24'], - 'state': 'up', - 'flags': ['UP', 'BROADCAST'], - 'lladdr': 'aa:bb:cc:dd:ee:ff'} - iface = models.Interface.from_dict(d) - self.assertEqual(iface.ifname, 'ge0') - self.assertEqual(iface.addresses, - [netaddr.IPNetwork('192.168.1.1/24')]) - self.assertEqual(iface.extra_params["state"], 'up') - self.assertEqual(iface.flags, ['UP', 'BROADCAST']) - self.assertEqual(iface.lladdr, 'aa:bb:cc:dd:ee:ff') - - def test_from_dict_function(self): - d = dict(ifname='ge0', - addresses=['192.168.1.1/24'], - flags=['UP', 'BROADCAST'], - lladdr='aa:bb:cc:dd:ee:ff') - iface = models.Interface.from_dict(d) - self.assertEqual(iface.ifname, 'ge0') - self.assertEqual(iface.addresses, - [netaddr.IPNetwork('192.168.1.1/24')]) - self.assertEqual(iface.flags, ['UP', 'BROADCAST']) - self.assertEqual(iface.lladdr, 'aa:bb:cc:dd:ee:ff') - - -class FilterRuleModelTestCase(TestCase): - def test_filter_rule(self): - fr = models.FilterRule(action='pass', family='inet', - destination='192.168.1.1/32') - - self.assertEqual(fr.action, 'pass') - self.assertEqual(fr.family, 'inet') - self.assertEqual(fr.destination, netaddr.IPNetwork('192.168.1.1/32')) - - def test_setattr_action_valid(self): - fr = models.FilterRule(action='block') - self.assertEqual(fr.action, 'block') - - def test_setattr_action_invalid(self): - with self.assertRaises(ValueError): - models.FilterRule(action='reject') - - def test_setattr_invalid_family(self): - with self.assertRaises(ValueError): - models.FilterRule(action='pass', family='raw') - - def test_setattr_source_destination_cidr(self): - fr = models.FilterRule(action='pass', - destination='192.168.1.2/32') - self.assertEqual(fr.destination, netaddr.IPNetwork('192.168.1.2/32')) - - fr = models.FilterRule(action='pass', - source='192.168.1.2/32') - self.assertEqual(fr.source, netaddr.IPNetwork('192.168.1.2/32')) - - def test_setattr_source_destination_label(self): - fr = models.FilterRule(action='pass', - destination='foo') - self.assertEqual(fr.destination, 'foo') - - fr = models.FilterRule(action='pass', - source='bar') - self.assertEqual(fr.source, 'bar') - - def test_setattr_redirect(self): - fr = models.FilterRule(action='pass', - redirect='192.168.1.1') - self.assertEqual(fr.redirect, netaddr.IPAddress('192.168.1.1')) - - def test_setattr_port(self): - fr = models.FilterRule(action='pass', - source_port='22') - self.assertEqual(fr.source_port, 22) - - fr = models.FilterRule(action='pass', - destination_port='23') - self.assertEqual(fr.destination_port, 23) - - def test_setattr_port_none(self): - fr = models.FilterRule(action='pass', - destination_port=None) - self.assertIs(fr.destination_port, None) - - def test_setattr_protocol_valid(self): - for p in ['tcp', 'udp', 'imcp']: - fr = models.FilterRule(action='pass', protocol=p) - self.assertEqual(fr.protocol, p) - - def test_setattr_protocol_invalid(self): - with self.assertRaises(ValueError): - models.FilterRule(action='pass', protocol='made_up_proto') - - -class AnchorTestCase(TestCase): - def test_anchor(self): - a = models.Anchor('foo', []) - self.assertEqual(a.name, 'foo') - self.assertEqual(a.rules, []) - - -class AddressBookTestCase(TestCase): - def test_entry(self): - ab = models.AddressBookEntry('foo', ['192.168.1.0/24']) - self.assertEqual(ab.name, 'foo') - self.assertEqual(ab.cidrs, [netaddr.IPNetwork('192.168.1.0/24')]) - - def test_external_table_data(self): - ab = models.AddressBookEntry('foo', ['192.168.1.0/24', - '172.16.16.0/16']) - self.assertEqual(ab.external_table_data(), - '192.168.1.0/24\n172.16.16.0/16') - - -class LabelTestCase(TestCase): - def test_label(self): - l = models.Label('foo', ['192.168.1.0/24']) - self.assertEqual(l.name, 'foo') - self.assertEqual(l.cidrs, [netaddr.IPNetwork('192.168.1.0/24')]) - - -class AllocationTestCase(TestCase): - def test_allocation(self): - a = models.Allocation( - 'aa:bb:cc:dd:ee:ff', - {'192.168.1.1': True}, # ipaddr: enable_dhcp - 'hosta.com', - 'device_id' - ) - self.assertEqual(a.mac_address, 'aa:bb:cc:dd:ee:ff') - self.assertEqual(a.hostname, 'hosta.com') - self.assertEqual(a.ip_addresses, {'192.168.1.1': True}) - self.assertEqual(a.device_id, 'device_id') - - -class FloatingIPTestCase(TestCase): - def test_floating_ip(self): - fip = models.FloatingIP( - '9.9.9.9', - '10.0.0.1', - ) - - network = mock.Mock() - network.interface.ifname = 'ge1' - - self.assertEqual(fip.floating_ip, netaddr.IPAddress('9.9.9.9')) - self.assertEqual(fip.fixed_ip, netaddr.IPAddress('10.0.0.1')) - - fip.network = network - - def test_floating_ip_with_different_ip_versions(self): - fip = models.FloatingIP( - '9.9.9.9', - 'fe80::1' - ) - - network = mock.Mock() - network.interface.ifname = 'ge1' - - fip.network = network - - -class StaticRouteTestCase(TestCase): - def test_static_route(self): - sr = models.StaticRoute('0.0.0.0/0', '192.168.1.1') - self.assertEqual(sr.destination, netaddr.IPNetwork('0.0.0.0/0')) - self.assertEqual(sr.next_hop, netaddr.IPAddress('192.168.1.1')) - - def test_eq_none(self): - sr = models.StaticRoute('0.0.0.0/0', '192.168.1.1') - self.assertNotEqual(sr, None) - - def test_eq_equal(self): - sr1 = models.StaticRoute('0.0.0.0/0', '192.168.1.1') - sr2 = models.StaticRoute('0.0.0.0/0', '192.168.1.1') - self.assertEqual(sr1, sr2) - - def test_eq_not_equal(self): - sr1 = models.StaticRoute('0.0.0.0/0', '192.168.1.1') - sr2 = models.StaticRoute('172.16.0.0/16', '192.168.1.1') - self.assertNotEqual(sr1, sr2) - - -class SubnetTestCase(TestCase): - def test_subnet(self): - s = models.Subnet( - 'id', - '192.168.1.0/24', - '192.168.1.1', - True, - ['8.8.8.8'], - [] - ) - - self.assertEqual(s.id, 'id') - self.assertEqual(s.cidr, netaddr.IPNetwork('192.168.1.0/24')) - self.assertEqual(s.gateway_ip, netaddr.IPAddress('192.168.1.1')) - self.assertTrue(s.dhcp_enabled) - self.assertEqual(s.dns_nameservers, [netaddr.IPAddress('8.8.8.8')]) - self.assertEqual(s.host_routes, []) - - def test_gateway_ip_empty(self): - s = models.Subnet('id', '192.168.1.0/24', '', True, ['8.8.8.8'], - []) - self.assertIsNone(s.gateway_ip) - - def test_gateway_ip_none(self): - s = models.Subnet('id', '192.168.1.0/24', None, True, ['8.8.8.8'], - []) - self.assertIsNone(s.gateway_ip) - - -class NetworkTestCase(TestCase): - def test_network(self): - interface = mock.Mock() - - n = models.Network('id', interface, 'name') - - self.assertEqual(n.id, 'id') - self.assertEqual(n.interface, interface) - self.assertEqual(n.name, 'name') - - def test_network_type_valid(self): - n = models.Network('id', None, network_type='external') - self.assertEqual(n.network_type, 'external') - - n = models.Network('id', None, network_type='internal') - self.assertEqual(n.network_type, 'internal') - - n = models.Network('id', None, network_type='isolated') - self.assertEqual(n.network_type, 'isolated') - - n = models.Network('id', None, network_type='management') - self.assertEqual(n.network_type, 'management') - - def test_network_type_invalid(self): - with self.assertRaises(ValueError): - models.Network('id', None, network_type='invalid') - - def test_v4_conf_service_valid(self): - n = models.Network('id', None, v4_conf_service='dhcp') - self.assertEqual(n.v4_conf_service, 'dhcp') - - n = models.Network('id', None, v4_conf_service='static') - self.assertEqual(n.v4_conf_service, 'static') - - def test_v4_conf_service_invalid(self): - with self.assertRaises(ValueError): - models.Network('id', None, v4_conf_service='invalid') - - def test_v6_conf_service_valid(self): - n = models.Network('id', None, v6_conf_service='dhcp') - self.assertEqual(n.v6_conf_service, 'dhcp') - - n = models.Network('id', None, v6_conf_service='static') - self.assertEqual(n.v6_conf_service, 'static') - - n = models.Network('id', None, v6_conf_service='ra') - self.assertEqual(n.v6_conf_service, 'ra') - - def test_v6_conf_service_invalid(self): - with self.assertRaises(ValueError): - models.Network('id', None, v6_conf_service='invalid') - - -class RouterConfigurationTestCase(TestCase): - def test_init_only_networks(self): - subnet = dict( - id='id', - cidr='192.168.1.0/24', - gateway_ip='192.168.1.1', - dhcp_enabled=True, - dns_nameservers=['8.8.8.8']) - - network = dict( - network_id='netid', - name='thenet', - interface=dict(ifname='ge0', addresses=['192.168.1.1/24']), - allocations=[], - subnets=[subnet]) - - c = models.RouterConfiguration(dict(networks=[network])) - self.assertEqual(len(c.networks), 1) - self.assertEqual(c.networks[0], - models.Network.from_dict(network)) - - def test_init_tenant_id(self): - c = models.RouterConfiguration({'tenant_id': 'abc123'}) - self.assertEqual(c.tenant_id, 'abc123') - - def test_no_default_v4_gateway(self): - c = models.RouterConfiguration({}) - self.assertIsNone(c.default_v4_gateway) - - def test_valid_default_v4_gateway(self): - c = models.RouterConfiguration({'default_v4_gateway': '172.16.77.1'}) - self.assertEqual(c.default_v4_gateway.version, 4) - self.assertEqual(str(c.default_v4_gateway), '172.16.77.1') - - def test_init_only_static_routes(self): - routes = [('0.0.0.0/0', '192.168.1.1'), - ('172.16.77.0/16', '192.168.1.254')] - c = models.RouterConfiguration(dict(networks=[], static_routes=routes)) - - self.assertEqual(len(c.static_routes), 2) - self.assertEqual( - c.static_routes, - [models.StaticRoute(*routes[0]), models.StaticRoute(*routes[1])]) - - def test_init_address_book(self): - ab = {"webservers": ["192.168.57.101/32", "192.168.57.230/32"]} - - c = models.RouterConfiguration(dict(networks=[], address_book=ab)) - self.assertEqual( - c.address_book.get('webservers'), - models.AddressBookEntry('webservers', ab['webservers'])) - - def test_init_label(self): - labels = {"external": ["192.168.57.0/24"]} - - c = models.RouterConfiguration(dict(networks=[], labels=labels)) - self.assertEqual( - c.labels[0], - models.Label('external', ['192.168.57.0/24'])) - - def test_init_empty_anchor(self): - anchor_dict = dict( - name='theanchor', - rules=[]) - - c = models.RouterConfiguration(dict(networks=[], - anchors=[anchor_dict])) - self.assertEqual(len(c.anchors), 1) - - def test_init_anchor(self): - test_rule = dict(action='block', source='192.168.1.1/32') - anchor_dict = dict(name='theanchor', rules=[test_rule]) - - c = models.RouterConfiguration(dict(networks=[], - anchors=[anchor_dict])) - self.assertEqual(len(c.anchors), 1) - self.assertEqual(len(c.anchors[0].rules), 1) - self.assertEqual(c.anchors[0].rules[0].action, 'block') - - def test_init_metadata_config(self): - c = models.RouterConfiguration({ - 'orchestrator': { - 'address': '192.168.25.30', - 'metadata_port': 9697, - } - }) - self.assertEqual(c.metadata_address, '192.168.25.30') - self.assertEqual(c.metadata_port, 9697) - - def test_init_metadata_config_missing(self): - c = models.RouterConfiguration({}) - self.assertEqual( - c.metadata_address, defaults.ORCHESTRATOR_METADATA_ADDRESS) - self.assertEqual( - c.metadata_port, defaults.ORCHESTRATOR_METADATA_PORT) - - def test_asn_default(self): - c = models.RouterConfiguration({'networks': []}) - self.assertEqual(c.asn, 64512) - self.assertEqual(c.neighbor_asn, 64512) - - def test_asn_provided_with_neighbor_fallback(self): - c = models.RouterConfiguration({'networks': [], 'asn': 12345}) - self.assertEqual(c.asn, 12345) - self.assertEqual(c.neighbor_asn, 12345) - - def test_asn_provided_with_neighbor_different(self): - c = models.RouterConfiguration( - {'networks': [], 'asn': 12, 'neighbor_asn': 34} - ) - self.assertEqual(c.asn, 12) - self.assertEqual(c.neighbor_asn, 34) - - def _validate_test_helper(self, rule_dict, expect_errors=False): - network = dict( - network_id='netid', - name='thenet', - interface=dict(ifname='ge0', addresses=['192.168.1.1/24']), - allocations=[]) - - ab = {"webservers": ["192.168.57.101/32", "192.168.57.230/32"]} - anchor_dict = dict(name='theanchor', rules=[rule_dict]) - - c = models.RouterConfiguration( - dict(networks=[network], anchors=[anchor_dict], address_book=ab)) - - errors = c.validate() - - if expect_errors: - return errors - else: - self.assertEqual(errors, []) - - def test_validate_block_all(self): - rule = dict(action='block') - self._validate_test_helper(rule) - - def test_validate_pass_all(self): - rule = dict(action='pass') - self._validate_test_helper(rule) - - def test_validate_interface_valid(self): - rule = dict(action='pass', interface='ge0') - self._validate_test_helper(rule) - - def test_validate_interface_invalid(self): - rule = dict(action='pass', interface='lo0') - errors = self._validate_test_helper(rule, True) - self.assertEqual(len(errors), 1) - - def test_validate_source_valid_addressbook(self): - rule = dict(action='pass', source='webservers') - self._validate_test_helper(rule) - - def test_validate_source_valid_cidr(self): - rule = dict(action='pass', source='192.168.1.1/32') - self._validate_test_helper(rule) - - def test_validate_source_invalid(self): - rule = dict(action='pass', source='foo') - errors = self._validate_test_helper(rule, True) - self.assertEqual(len(errors), 1) - - def test_validate_dest_valid_addressbook(self): - rule = dict(action='pass', destination='webservers') - self._validate_test_helper(rule) - - def test_validate_dest_valid_cidr(self): - rule = dict(action='pass', destination='192.168.1.1/32') - self._validate_test_helper(rule) - - def test_validate_destination_invalid(self): - rule = dict(action='pass', destination='foo') - errors = self._validate_test_helper(rule, True) - self.assertEqual(len(errors), 1) - - def test_to_dict(self): - c = models.RouterConfiguration({'networks': []}) - expected = dict(networks=[], - address_book={}, - static_routes=[], - anchors=[], - vpn=[]) - - self.assertEqual(c.to_dict(), expected) - - -class LBListenerTest(TestCase): - def test_from_dict(self): - ldict = copy.copy(fakes.FAKE_LISTENER_DICT) - listener = models.Listener.from_dict(ldict) - for k in ldict.keys(): - self.assertEqual(getattr(listener, k), ldict[k]) - - def test_from_dict_with_pool(self): - ldict = copy.copy(fakes.FAKE_LISTENER_DICT) - pdict = copy.copy(fakes.FAKE_POOL_DICT) - ldict['default_pool'] = pdict - listener = models.Listener.from_dict(ldict) - keys = ldict.keys() - keys.remove('default_pool') - for k in keys: - self.assertEqual(getattr(listener, k), ldict[k]) - self.assertIsInstance(listener.default_pool, models.Pool) - - def test_to_dict(self): - ldict = copy.copy(fakes.FAKE_LISTENER_DICT) - listener = models.Listener.from_dict(ldict) - l_to_dict = listener.to_dict() - for k in ldict.keys(): - self.assertEqual(l_to_dict[k], ldict[k]) - - def test_to_dict_with_pool(self): - ldict = copy.copy(fakes.FAKE_LISTENER_DICT) - pdict = copy.copy(fakes.FAKE_POOL_DICT) - ldict['default_pool'] = pdict - listener = models.Listener.from_dict(ldict).to_dict() - self.assertEqual(listener['default_pool']['id'], pdict['id']) - - -class LBPoolTest(TestCase): - def test_from_dict(self): - pdict = copy.copy(fakes.FAKE_POOL_DICT) - pool = models.Pool.from_dict(pdict) - for k in pdict.keys(): - self.assertEqual(getattr(pool, k), pdict[k]) - - def test_from_dict_with_member(self): - pdict = copy.copy(fakes.FAKE_POOL_DICT) - mdict = copy.copy(fakes.FAKE_MEMBER_DICT) - pdict['members'] = [mdict] - pool = models.Pool.from_dict(pdict) - keys = pdict.keys() - keys.remove('members') - for k in keys: - self.assertEqual(getattr(pool, k), pdict[k]) - self.assertIsInstance(pool.members[0], models.Member) - - def test_to_dict(self): - pdict = copy.copy(fakes.FAKE_POOL_DICT) - pool = models.Pool.from_dict(pdict) - p_to_dict = pool.to_dict() - for k in pdict.keys(): - self.assertEqual(p_to_dict[k], pdict[k]) - - def test_to_dict_with_member(self): - pdict = copy.copy(fakes.FAKE_POOL_DICT) - mdict = copy.copy(fakes.FAKE_MEMBER_DICT) - pdict['members'] = [mdict] - pool = models.Pool.from_dict(pdict) - pool_to_dict = pool.to_dict() - self.assertEqual(pool_to_dict['members'][0]['id'], mdict['id']) - - -class LBMemberTest(TestCase): - def test_from_dict(self): - mdict = copy.copy(fakes.FAKE_MEMBER_DICT) - member = models.Member.from_dict(mdict) - for k in mdict.keys(): - self.assertEqual(getattr(member, k), mdict[k]) - - def test_to_dict(self): - mdict = copy.copy(fakes.FAKE_MEMBER_DICT) - member = models.Member.from_dict(mdict) - m_to_dict = member.to_dict() - for k in mdict.keys(): - self.assertEqual(m_to_dict[k], mdict[k]) - - -class LoadBalancerTest(TestCase): - def test_from_dict_lb(self): - lb_dict = fakes.fake_loadbalancer_dict() - lb = models.LoadBalancer.from_dict(lb_dict) - for k in lb_dict.keys(): - self.assertEqual(getattr(lb, k), lb_dict[k]) - - def test_from_dict_lb_listener(self): - lb_dict = fakes.fake_loadbalancer_dict(listener=True) - expected_listener_id = lb_dict['listeners'][0]['id'] - lb = models.LoadBalancer.from_dict(lb_dict) - for k in lb_dict.keys(): - self.assertEqual(getattr(lb, k), lb_dict[k]) - self.assertIsInstance(lb.listeners[0], models.Listener) - self.assertEqual(lb.listeners[0].id, expected_listener_id) - - def test_from_dict_lb_listener_pool(self): - lb_dict = fakes.fake_loadbalancer_dict(listener=True, pool=True) - expected_listener_id = lb_dict['listeners'][0]['id'] - expected_pool_id = lb_dict['listeners'][0]['default_pool']['id'] - lb = models.LoadBalancer.from_dict(lb_dict) - for k in lb_dict.keys(): - self.assertEqual(getattr(lb, k), lb_dict[k]) - self.assertIsInstance(lb.listeners[0], models.Listener) - self.assertIsInstance(lb.listeners[0].default_pool, - models.Pool) - self.assertEqual(lb.listeners[0].id, expected_listener_id) - self.assertEqual(lb.listeners[0].default_pool.id, expected_pool_id) - - def test_from_dict_lb_listener_pool_members(self): - lb_dict = fakes.fake_loadbalancer_dict(listener=True, pool=True, - members=True) - expected_listener_id = lb_dict['listeners'][0]['id'] - expected_pool_id = lb_dict['listeners'][0]['default_pool']['id'] - expected_member = lb_dict['listeners'][0]['default_pool']['members'][0] - lb = models.LoadBalancer.from_dict(lb_dict) - for k in lb_dict.keys(): - self.assertEqual(getattr(lb, k), lb_dict[k]) - self.assertIsInstance(lb.listeners[0], models.Listener) - self.assertIsInstance(lb.listeners[0].default_pool, - models.Pool) - self.assertIsInstance(lb.listeners[0].default_pool.members[0], - models.Member) - self.assertEqual(lb.listeners[0].id, expected_listener_id) - self.assertEqual(lb.listeners[0].default_pool.id, expected_pool_id) - self.assertEqual(lb.listeners[0].default_pool.members[0].id, - expected_member['id']) - - -class LoadBalancerConfigurationTest(TestCase): - def setUp(self): - super(LoadBalancerConfigurationTest, self).setUp() - self.conf_dict = fakes.fake_loadbalancer_dict( - listener=True, pool=True, members=True - ) - - def test_loadbalancer_config(self): - lb_conf = models.LoadBalancerConfiguration(self.conf_dict) - errors = lb_conf.validate() - lb_conf.to_dict() - self.assertEqual(errors, []) - - def test_loadbalancer_config_validation_failed(self): - self.conf_dict.pop('id') - lb_conf = models.LoadBalancerConfiguration({}) - errors = lb_conf.validate() - # id is required - self.assertEqual(len(errors), 1) - - -class VPNModelsTest(TestCase): - def _test_model(self, model, config_dict, skip_keys=()): - instance = model.from_dict(config_dict) - for k in config_dict.keys(): - if k in skip_keys: - continue - - self.assertEqual(getattr(instance, k), config_dict[k]) - - return instance - - def test_lifetime_model(self): - self._test_model(models.Lifetime, fakes.FAKE_LIFETIME_DICT) - - def test_dead_peer_model(self): - self._test_model( - models.DeadPeerDetection, - fakes.FAKE_DEAD_PEER_DETECTION_DICT - ) - - def test_endpoint_model_local(self): - self._test_model(models.EndpointGroup, fakes.FAKE_LOCAL_ENDPOINT_DICT) - - def test_endpoint_model_peer(self): - conf_dict = fakes.FAKE_PEER_ENDPOINT_DICT - ep = self._test_model(models.EndpointGroup, conf_dict, ['endpoints']) - - self.assertEqual( - ep.endpoints, - [netaddr.IPNetwork(conf_dict['endpoints'][0])] - ) - - def _test_policy_model(self, config_dict, model): - with mock.patch.object(models, 'Lifetime') as mock_life: - self._test_model(model, config_dict, ['lifetime']) - mock_life.from_dict.assert_called_once_with(config_dict['lifetime']) - - def test_ikepolicy_model(self): - return self._test_policy_model( - fakes.FAKE_IKEPOLICY_DICT, - models.IkePolicy - ) - - def test_ipsecpolicy_model(self): - return self._test_policy_model( - fakes.FAKE_IPSECPOLICY_DICT, - models.IpsecPolicy - ) - - - def test_ipsec_site_connection_model(self): - config_dict = fakes.FAKE_IPSEC_CONNECTION_DICT - - skip_keys = [ - 'dpd', - 'ikepolicy', - 'ipsecpolicy', - 'local_ep_group', - 'peer_ep_group', - 'peer_address' - ] - - conn = self._test_model( - models.IpsecSiteConnection, - config_dict, - skip_keys - ) - - self.assertEqual( - conn.peer_address, - netaddr.IPAddress(config_dict['peer_address']) - ) - - self.assertIsInstance(conn.local_ep_group, models.EndpointGroup) - self.assertEqual(conn.local_ep_group.name, 'local') - self.assertIsInstance(conn.peer_ep_group, models.EndpointGroup) - self.assertEqual(conn.peer_ep_group.name, 'peer') - self.assertIsInstance(conn.dpd, models.DeadPeerDetection) - self.assertIsInstance(conn.ikepolicy, models.IkePolicy) - self.assertIsInstance(conn.ipsecpolicy, models.IpsecPolicy) - - def test_vpnservice_model(self): - config_dict = fakes.FAKE_IPSEC_VPNSERVICE_DICT - - vpn = self._test_model( - models.VpnService, - config_dict, - ['ipsec_connections', 'external_v4_ip', 'external_v6_ip'] - ) - - self.assertEqual(vpn.external_v4_ip, netaddr.IPAddress('172.24.4.2')) - self.assertEqual(vpn.external_v6_ip, netaddr.IPAddress('2001:db8::1')) diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py deleted file mode 100644 index 23be051..0000000 --- a/test/unit/test_utils.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright 2014 DreamHost, LLC -# -# Author: DreamHost, LLC -# -# 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 json -import subprocess -from unittest2 import TestCase - -import flask -import mock -import netaddr - -from astara_router import models -from astara_router import utils - - -class ModelSerializerTestCase(TestCase): - """ - """ - def test_default(self): - data = { - "a": [1, 2, 3], - "b": {"c": 4}, - "d": "e", - "f": u"g", - "h": 42, - "i": float(3), - "j": False, - "k": None, - "l": (4, 5, 6), - "m": 12345671238792347L, - "n": netaddr.IPNetwork('192.168.1.1/24'), - } - expected = ( - '{"a": [1, 2, 3], "b": {"c": 4}, "d": "e", "f": "g", ' - '"h": 42, "i": 3.0, "j": false, "k": null, ' - '"l": [4, 5, 6], "m": 12345671238792347, "n": "192.168.1.1/24"}') - serialized = json.dumps(data, cls=utils.ModelSerializer, - sort_keys=True) - self.assertEqual(serialized, expected) - - def test_default_with_set(self): - data = {"a": set([1, 2, 3])} - expected = '{"a": [1, 2, 3]}' - serialized = json.dumps(data, cls=utils.ModelSerializer, - sort_keys=True) - self.assertEqual(serialized, expected) - - def test_default_ipaddress(self): - data = dict(a=netaddr.IPAddress('192.168.1.1')) - expected = '{"a": "192.168.1.1"}' - serialized = json.dumps(data, cls=utils.ModelSerializer, - sort_keys=True) - self.assertEqual(serialized, expected) - - def test_default_ipnetwork(self): - data = dict(a=netaddr.IPNetwork('192.168.1.1/24')) - expected = '{"a": "192.168.1.1/24"}' - serialized = json.dumps(data, cls=utils.ModelSerializer, - sort_keys=True) - self.assertEqual(serialized, expected) - - def test_model_base_to_dict(self): - data = dict(r=models.StaticRoute('192.168.1.0/24', '172.16.77.1')) - expected = ('{"r": {"destination": "192.168.1.0/24", ' - '"next_hop": "172.16.77.1"}}') - serialized = json.dumps(data, cls=utils.ModelSerializer, - sort_keys=True) - self.assertEqual(serialized, expected) - - def test_model_base_fallback_to_vars(self): - data = dict(a=models.Anchor('foo', [])) - expected = '{"a": {"name": "foo", "rules": []}}' - serialized = json.dumps(data, cls=utils.ModelSerializer, - sort_keys=True) - self.assertEqual(serialized, expected) - - -class FlaskJsonResponse(TestCase): - def test_no_args(self): - @utils.json_response - def f(): - return dict(a=1) - - retval = f() - self.assertIsInstance(retval, flask.Response) - self.assertEqual(retval.data, '{"a": 1}') - self.assertEqual(retval.status_code, 200) - - def test_with_args(self): - @utils.json_response - def f(arg1, kwarg1=None): - return dict(arg1=arg1, kwarg1=kwarg1) - - retval = f(1, "foo") - self.assertIsInstance(retval, flask.Response) - self.assertEqual(retval.data, '{"arg1": 1, "kwarg1": "foo"}') - self.assertEqual(retval.status_code, 200) - - -class ExecuteTest(TestCase): - - def test_execute_exception(self): - with mock.patch('subprocess.check_output') as co: - co.side_effect = subprocess.CalledProcessError( - 1, ['command', 'with', 'args'], - output='output text', - ) - try: - utils.execute(['command', 'with', 'args']) - except RuntimeError as e: - self.assertIn('output text', str(e)) - - def test_execute_exception_real(self): - try: - utils.execute(['/bin/ls', '/no-such-directory']) - except RuntimeError as e: - self.assertIn('cannot access', str(e)) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 8c5b12b..0000000 --- a/tox.ini +++ /dev/null @@ -1,32 +0,0 @@ -[tox] -envlist = py27,pep8 - -[testenv] -deps = -r{toxinidir}/test-requirements.txt -commands = nosetests {posargs} -sitepackages = False - -[tox:jenkins] - -[testenv:cover] -setenv = NOSE_WITH_COVERAGE=1 - -[testenv:venv] -commands = {posargs} - -[testenv:pep8] -deps = flake8 -commands = flake8 setup.py astara_router - -[flake8] -show-source = True -exclude = .tox,dist,doc,*.egg,build -ignore = -builtins = _ - -[testenv:build_image] -deps = diskimage-builder -commands = bash -xec "./scripts/build_dib_appliance.sh {posargs}" - -[testenv:releasenotes] -commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html