Retire astara repo
Retire repository, following https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project Change-Id: I0a8240c961955447d65aee7af24e03cb81da12d3
This commit is contained in:
parent
a6cd8a5594
commit
d139d81213
18
.gitignore
vendored
18
.gitignore
vendored
@ -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
|
10
.travis.yml
10
.travis.yml
@ -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
|
175
LICENSE
175
LICENSE
@ -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.
|
10
README.md
10
README.md
@ -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.
|
10
README.rst
Normal file
10
README.rst
Normal file
@ -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.
|
@ -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
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
|
||||
- name: install extras
|
||||
apt: name={{item}} state=installed install_recommends=no
|
||||
with_items:
|
||||
- mtr
|
||||
- tshark
|
@ -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
|
@ -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
|
@ -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"
|
@ -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}}
|
@ -1 +0,0 @@
|
||||
gunicorn ALL = (root) NOPASSWD: /usr/local/bin/astara-rootwrap /etc/rootwrap.conf *
|
@ -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"
|
@ -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
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
___ _
|
||||
/ _ \ | | L3 for OpenStack
|
||||
/ /_\ \___| |_ __ _ _ __ __ _
|
||||
| _ / __| __/ _` | '__/ _` |
|
||||
| | | \__ \ || (_| | | | (_| |
|
||||
\_| |_/___/\__\__,_|_| \__,_|
|
||||
|
||||
Welcome to Astara: Powered by Unicorns.
|
@ -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
|
||||
|
@ -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
|
@ -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.
|
@ -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.
|
@ -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)
|
@ -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
|
@ -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'
|
@ -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()
|
@ -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
|
@ -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/<ifname>')
|
||||
@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)
|
@ -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.
|
@ -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
|
@ -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
|
@ -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']
|
@ -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.
|
@ -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 <config> 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 <ip>` to delete <ip> 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
|
@ -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. <root_helper> 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 <args> 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 <args> 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))
|
@ -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 <interface_map>.
|
||||
|
||||
: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', ' ')
|
@ -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
|
||||
}
|
||||
}
|
@ -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')
|
@ -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 <CONF_DIR>) 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 <ifname>. 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 <config> containing dnsmasq configuration information for
|
||||
<ifname>/<network>. 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)
|
@ -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)
|
@ -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 <ifname>`.
|
||||
|
||||
: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 <ifname> is a valid interface. Returns
|
||||
`False` if <ifname> 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(<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
|
||||
<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
|
||||
<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 <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) <cidr> dev <ifname>` 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<gateway>[^ ]+)', 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 <ifname>`
|
||||
|
||||
: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<if>\w+\d{1,3}): <(?P<flags>[^>]+)> mtu (?P<mtu>[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()
|
@ -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 = '\-(?P<flag>i|o)(?P<ws>[\s!])(?P<not>!?)(?P<if>ge)(?P<no>\d+)' # noqa
|
||||
ifname_sub = r'-\g<flag>\g<ws>\g<not>%s\g<no>' % 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
|
@ -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 %}
|
@ -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
|
@ -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)
|
@ -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 %}
|
||||
|
@ -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'
|
@ -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 <config>
|
||||
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 <config> to the metadata configuration file (<CONF_PATH>).
|
||||
|
||||
: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,
|
||||
}
|
@ -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 <ip> using the systems ping utility.
|
||||
|
||||
:type ip: str
|
||||
:param ip: The IP address to send ICMP packets to.
|
||||
:rtype: bool. If <ip> 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
|
@ -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
|
@ -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
|
@ -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 %}
|
||||
|
@ -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 %}
|
@ -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()
|
@ -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()
|
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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()
|
@ -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')
|
@ -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
|
||||
|
||||
|
@ -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/
|
@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
sudo rm -fr "${TMP_MOUNT_PATH}/opt/stack/tripleo-ansible"
|
@ -1 +0,0 @@
|
||||
pip-and-virtualenv
|
@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ue
|
||||
|
||||
export ANSIBLE_VENV_DIR=${ANSIBLE_VENV_DIR:-"/opt/stack/venvs/ansible"}
|
@ -1,4 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -eux
|
||||
|
||||
install-packages ansible
|
@ -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
|
@ -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
|
@ -1,3 +0,0 @@
|
||||
cloud-init-datasources
|
||||
source-repositories
|
||||
ansible
|
@ -1 +0,0 @@
|
||||
export DIB_CLOUD_INIT_DATASOURCES="ConfigDrive, NoCloud"
|
@ -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
|
@ -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
|
@ -1 +0,0 @@
|
||||
astara git /tmp/astara-appliance https://github.com/openstack/astara-appliance.git
|
@ -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.
|
@ -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 <<EOF
|
||||
$DIB_ASTARA_APPLIANCE_DEBUG_PASSWORD
|
||||
$DIB_ASTARA_APPLIANCE_DEBUG_PASSWORD
|
||||
EOF
|
||||
|
||||
cat > /etc/sudoers.d/astara-debug-user <<eof
|
||||
$DIB_ASTARA_APPLIANCE_DEBUG_USER ALL=(ALL) NOPASSWD:ALL
|
||||
eof
|
||||
|
||||
chmod 0440 /etc/sudoers.d/astara-debug-user
|
||||
visudo -c
|
@ -1 +0,0 @@
|
||||
vim:
|
@ -1,10 +0,0 @@
|
||||
This element requires that you have already downloaded a crt and key file from
|
||||
nginx.
|
||||
|
||||
Download nginx-repo.crt and nginx-repo.key into any directory. Then when
|
||||
executing set DIB_NGINX_PLUS_CERT_PATH to that directory.
|
||||
|
||||
i.e.
|
||||
|
||||
ELEMENTS_PATH=diskimage-builder/elements DIB_RELEASE=wheezy DIB_EXTLINUX=1 DIB_NGINX_PLUS_CERT_PATH=/home/david/nginxcerts \
|
||||
disk-image-create debian vm nginx-plus -o nginxplus
|
@ -1 +0,0 @@
|
||||
source-repositories
|
@ -1,22 +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.
|
||||
|
||||
if [ -z "${DIB_NGINX_PLUS_CERT_PATH}" ]; then
|
||||
echo "DIB_NGINX_PLUS_CERT_PATH is not set - not importing keys"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat $DIB_NGINX_PLUS_CERT_PATH/nginx-repo.crt >> $TMP_HOOKS_PATH/nginx-repo.crt
|
||||
cat $DIB_NGINX_PLUS_CERT_PATH/nginx-repo.key >> $TMP_HOOKS_PATH/nginx-repo.key
|
@ -1 +0,0 @@
|
||||
nginx-plus
|
@ -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
|
@ -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
|
@ -1,2 +0,0 @@
|
||||
This element installs the open source nginx package.
|
||||
|
@ -1,2 +0,0 @@
|
||||
package-installs
|
||||
astara
|
@ -1 +0,0 @@
|
||||
nginx
|
@ -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
|
@ -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
|
@ -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
|
@ -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.
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
fixes:
|
||||
- Bug `1531651 <https://bugs.launchpad.net/astara/+bug/1531651/>`_ \- Fixes a stale interface cache from preventing additional router interfaces from being attached
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
prelude: Astara Appliance Mitaka Series Release v8.0.0.
|
||||
|
@ -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.
|
@ -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
|
||||
# "<project> v<release> 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 <link> 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
|
@ -1,8 +0,0 @@
|
||||
==============================
|
||||
Astara Appliance Release Notes
|
||||
==============================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
mitaka
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user