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:
Andreas Jaeger 2018-10-14 12:52:23 +02:00
parent a6cd8a5594
commit d139d81213
137 changed files with 10 additions and 10943 deletions

18
.gitignore vendored
View File

@ -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

View File

@ -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
View File

@ -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.

View File

@ -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
View 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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
---
- name: install extras
apt: name={{item}} state=installed install_recommends=no
with_items:
- mtr
- tshark

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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}}

View File

@ -1 +0,0 @@
gunicorn ALL = (root) NOPASSWD: /usr/local/bin/astara-rootwrap /etc/rootwrap.conf *

View File

@ -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"

View File

@ -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
}

View File

@ -1,8 +0,0 @@
___ _
/ _ \ | | L3 for OpenStack
/ /_\ \___| |_ __ _ _ __ __ _
| _ / __| __/ _` | '__/ _` |
| | | \__ \ || (_| | | | (_| |
\_| |_/___/\__\__,_|_| \__,_|
Welcome to Astara: Powered by Unicorns.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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'

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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']

View File

@ -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.

View File

@ -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

View File

@ -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))

View File

@ -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', ' ')

View File

@ -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
}
}

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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 %}

View File

@ -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

View File

@ -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)

View File

@ -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 %}

View File

@ -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'

View File

@ -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,
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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')

View File

@ -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

View File

@ -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/

View File

@ -1,5 +0,0 @@
#!/bin/bash
set -eu
sudo rm -fr "${TMP_MOUNT_PATH}/opt/stack/tripleo-ansible"

View File

@ -1 +0,0 @@
pip-and-virtualenv

View File

@ -1,4 +0,0 @@
#!/bin/bash
set -ue
export ANSIBLE_VENV_DIR=${ANSIBLE_VENV_DIR:-"/opt/stack/venvs/ansible"}

View File

@ -1,4 +0,0 @@
#!/bin/bash
set -eux
install-packages ansible

View File

@ -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

View File

@ -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

View File

@ -1,3 +0,0 @@
cloud-init-datasources
source-repositories
ansible

View File

@ -1 +0,0 @@
export DIB_CLOUD_INIT_DATASOURCES="ConfigDrive, NoCloud"

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
astara git /tmp/astara-appliance https://github.com/openstack/astara-appliance.git

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
source-repositories

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,2 +0,0 @@
This element installs the open source nginx package.

View File

@ -1,2 +0,0 @@
package-installs
astara

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -1,3 +0,0 @@
---
prelude: Astara Appliance Mitaka Series Release v8.0.0.

View File

@ -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.

View File

@ -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

View File

@ -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