Merge "Introduces advanced service drivers to akanda-appliance"

This commit is contained in:
Jenkins 2015-10-15 22:42:24 +00:00 committed by Gerrit Code Review
commit 3a359c6a29
24 changed files with 1209 additions and 115 deletions

1
MANIFEST.in Normal file
View File

@ -0,0 +1 @@
include akanda/router/drivers/loadbalancer/nginx.conf.template

View File

@ -24,6 +24,7 @@ from dogpile.cache import make_region
from akanda.router import models
from akanda.router import utils
from akanda.router import settings
from akanda.router.manager import manager
blueprint = utils.blueprint_factory(__name__)
@ -32,6 +33,9 @@ blueprint = utils.blueprint_factory(__name__)
_cache = None
ADVANCED_SERVICES_KEY = 'services'
def _get_cache():
global _cache
if _cache is None:
@ -51,7 +55,7 @@ def get_interface(ifname):
Show interface parameters given an interface name.
For example ge1, ge2 for generic ethernet
'''
return dict(interface=manager.get_interface(ifname))
return dict(interface=manager.router.get_interface(ifname))
@blueprint.route('/interfaces')
@ -60,7 +64,7 @@ def get_interfaces():
'''
Show all interfaces and parameters
'''
return dict(interfaces=manager.get_interfaces())
return dict(interfaces=manager.router.get_interfaces())
@blueprint.route('/config', methods=['GET'])
@ -77,17 +81,75 @@ def put_configuration():
abort(415)
try:
config_candidate = models.Configuration(request.json)
system_config_candidate = models.SystemConfiguration(request.json)
except ValueError, e:
return Response(
'The config failed to deserialize.\n' + str(e),
'The system config failed to deserialize.\n' + str(e),
status=422)
errors = config_candidate.validate()
errors = system_config_candidate.validate()
if errors:
return Response(
'The config failed to validate.\n' + '\n'.join(errors),
status=422)
manager.update_config(config_candidate, _get_cache())
# 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)

0
akanda/router/drivers/iptables.py Executable file → Normal file
View File

View File

@ -0,0 +1,37 @@
# 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 akanda.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

@ -0,0 +1,19 @@
{%- 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

@ -0,0 +1,75 @@
# 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 jinja2
import os
from akanda.router.drivers import base
from akanda.router.utils import execute
class NginxTemplateNotFound(Exception):
# TODO(adam_g): These should return 50x errors and not logged
# exceptions.
pass
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'):
"""
Initializes DHCPManager class.
:type root_helper: str
:param root_helper: System utility used to gain escalate privileges.
"""
super(NginxLB, self).__init__(root_helper)
self._load_template()
def _load_template(self):
if not os.path.exists(self.CONFIG_FILE_TEMPLATE):
raise NginxTemplateNotFound(
'NGINX Config template not found @ %s' %
self.CONFIG_FILE_TEMPLATE
)
self.config_tmpl = jinja2.Template(
open(self.CONFIG_FILE_TEMPLATE).read())
def _render_config_template(self, path, config):
self._load_template()
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)
pass
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

@ -19,35 +19,61 @@ import os
import re
from akanda.router import models
from akanda.router import settings
from akanda.router.drivers import (bird, dnsmasq, ip, metadata,
iptables, arp, hostname)
iptables, arp, hostname, loadbalancer)
class Manager(object):
class ServiceManagerBase(object):
def __init__(self, state_path='.'):
self._config = None
self.state_path = os.path.abspath(state_path)
self.ip_mgr = ip.IPManager()
self.ip_mgr.ensure_mapping()
self._config = models.Configuration()
def management_address(self, ensure_configuration=False):
return self.ip_mgr.get_management_address(ensure_configuration)
@property
def config(self):
"""Make config a read-only property.
To update the value, update_config() must called to change the global
state of router.
state of appliance.
"""
return self._config
def update_config(self, config, cache):
self._config = config
pass
class SystemManager(ServiceManagerBase):
def __init__(self, state_path='.'):
super(SystemManager, self).__init__(state_path)
self._config = models.SystemConfiguration()
self.ip_mgr = ip.IPManager()
self.ip_mgr.ensure_mapping()
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)
def update_interfaces(self):
for network in self._config.networks:
self.ip_mgr.disable_duplicate_address_detection(network)
self.ip_mgr.update_interfaces(self._config.interfaces)
class RouterManager(ServiceManagerBase):
def __init__(self, state_path='.'):
super(RouterManager, self).__init__(state_path)
self.ip_mgr = ip.IPManager()
self.ip_mgr.ensure_mapping()
def update_config(self, config, cache):
self._config = config
self.update_interfaces()
self.update_dhcp()
self.update_metadata()
self.update_bgp_and_radv()
@ -55,30 +81,24 @@ class Manager(object):
self.update_routes(cache)
self.update_arp()
# TODO(mark): update_vpn
def update_hostname(self):
mgr = hostname.HostnameManager()
mgr.update(self.config)
def update_interfaces(self):
for network in self.config.networks:
for network in self._config.networks:
self.ip_mgr.disable_duplicate_address_detection(network)
self.ip_mgr.update_interfaces(self.config.interfaces)
self.ip_mgr.update_interfaces(self._config.interfaces)
def update_dhcp(self):
mgr = dnsmasq.DHCPManager()
mgr.delete_all_config()
for network in self.config.networks:
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.networks_have_changed(self.config)
mgr.save_config(self.config)
should_restart = mgr.networks_have_changed(self._config)
mgr.save_config(self._config)
if should_restart:
mgr.restart()
else:
@ -86,26 +106,26 @@ class Manager(object):
def update_bgp_and_radv(self):
mgr = bird.BirdManager()
mgr.save_config(self.config, self.ip_mgr.generic_mapping)
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.save_config(self._config, self.ip_mgr.generic_mapping)
mgr.restart()
def update_routes(self, cache):
mgr = ip.IPManager()
mgr.update_default_gateway(self.config)
mgr.update_host_routes(self.config, cache)
mgr.update_default_gateway(self._config)
mgr.update_host_routes(self._config, cache)
def update_arp(self):
mgr = arp.ARPManager()
mgr.send_gratuitous_arp_for_floating_ips(
self.config,
self._config,
self.ip_mgr.generic_to_host
)
mgr.remove_stale_entries(self.config)
mgr.remove_stale_entries(self._config)
def get_interfaces(self):
return self.ip_mgr.get_interfaces()
@ -123,6 +143,133 @@ class Manager(object):
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):

View File

@ -365,6 +365,7 @@ class Network(ModelBase):
TYPE_INTERNAL = 'internal'
TYPE_ISOLATED = 'isolated'
TYPE_MANAGEMENT = 'management'
TYPE_LOADBALANCER = 'loadbalancer'
# TODO(mark): add subnet support for Quantum subnet host routes
@ -406,7 +407,8 @@ class Network(ModelBase):
@network_type.setter
def network_type(self, value):
network_types = (self.TYPE_EXTERNAL, self.TYPE_INTERNAL,
self.TYPE_ISOLATED, self.TYPE_MANAGEMENT)
self.TYPE_ISOLATED, self.TYPE_MANAGEMENT,
self.TYPE_LOADBALANCER)
if value not in network_types:
msg = ('network must be one of %s not (%s).' %
('|'.join(network_types), value))
@ -451,6 +453,13 @@ class Network(ModelBase):
@classmethod
def from_dict(cls, d):
missing = []
for k in ['network_id', 'interface']:
if not d.get(k):
missing.append(k)
if missing:
raise ValueError('Missing required data: %s.' % missing)
return cls(
d['network_id'],
interface=Interface.from_dict(d['interface']),
@ -463,15 +472,234 @@ class Network(ModelBase):
subnets=[Subnet.from_dict(s) for s in d.get('subnets', [])])
class Configuration(ModelBase):
class LoadBalancer(ModelBase):
def __init__(self, id_, tenant_id, name, admin_state_up, status,
vip_address, vip_port=None, listeners=()):
self.id = id_
self.tenant_id = tenant_id
self.name = name
self.admin_state_up = admin_state_up
self.status = status
self.vip_address = vip_address
self.vip_port = vip_port
self.listeners = listeners
@classmethod
def from_dict(cls, d):
if d.get('listeners'):
d['listeners'] = [
Listener.from_dict(l) for l in d.get('listeners', [])
]
if d.get('vip_port'):
d['vip_port'] = Port.from_dict(d.get('vip_port'))
out = cls(
d['id'],
d['tenant_id'],
d['name'],
d['admin_state_up'],
d['status'],
d['vip_address'],
d['vip_port'],
d['listeners'],
)
return out
class Listener(ModelBase):
def __init__(self, id_, tenant_id, name, admin_state_up, protocol,
protocol_port, default_pool=None):
self.id = id_
self.tenant_id = tenant_id
self.name = name
self.admin_state_up = admin_state_up
self.protocol = protocol
self.protocol_port = protocol_port
self.default_pool = default_pool
@classmethod
def from_dict(cls, d):
if d.get('default_pool'):
def_pool = Pool.from_dict(d['default_pool'])
else:
def_pool = None
return cls(
d['id'],
d['tenant_id'],
d['name'],
d['admin_state_up'],
d['protocol'],
d['protocol_port'],
def_pool,
)
def to_dict(self):
fields = ('id', 'tenant_id', 'name', 'admin_state_up', 'protocol',
'protocol_port')
out = dict((f, getattr(self, f)) for f in fields)
if self.default_pool:
out['default_pool'] = self.default_pool.to_dict()
else:
out['default_pool'] = None
return out
class Pool(ModelBase):
def __init__(self, id_, tenant_id, name, admin_state_up, lb_algorithm,
protocol, healthmonitor=None, session_persistence=None,
members=()):
self.id = id_
self.tenant_id = tenant_id
self.name = name
self.admin_state_up = admin_state_up
self.lb_algorithm = lb_algorithm
self.protocol = protocol
self.healthmonitor = healthmonitor
self.session_persistence = session_persistence
self.members = members
@classmethod
def from_dict(cls, d):
return cls(
d['id'],
d['tenant_id'],
d['name'],
d['admin_state_up'],
d['lb_algorithm'],
d['protocol'],
d.get('healthmonitor'),
d.get('session_persistence'),
[Member.from_dict(m) for m in d.get('members', [])],
)
def to_dict(self):
fields = ('id', 'tenant_id', 'name', 'admin_state_up',
'lb_algorithm', 'protocol', 'healthmonitor',
'session_persistence')
out = dict((f, getattr(self, f)) for f in fields)
out['members'] = [m.to_dict() for m in self.members]
return out
class Member(ModelBase):
def __init__(self, id_, tenant_id, admin_state_up, address, protocol_port,
weight, subnet=None):
self.id = id_
self.tenant_id = tenant_id
self.admin_state_up = admin_state_up
self.address = str(netaddr.IPAddress(address))
self.protocol_port = protocol_port
self.weight = weight
self.subnet = subnet
@classmethod
def from_dict(cls, d):
return cls(
d['id'],
d['tenant_id'],
d['admin_state_up'],
d['address'],
d['protocol_port'],
d['weight'],
)
def to_dict(self):
fields = ('id', 'tenant_id', 'admin_state_up', 'address',
'protocol_port', 'weight', 'subnet')
return dict((f, getattr(self, f)) for f in fields)
class Port(ModelBase):
def __init__(self, id_, device_id='', fixed_ips=None, mac_address='',
network_id='', device_owner='', name=''):
self.id = id_
self.device_id = device_id
self.fixed_ips = fixed_ips or []
self.mac_address = mac_address
self.network_id = network_id
self.device_owner = device_owner
self.name = name
@classmethod
def from_dict(cls, d):
return cls(
d['id'],
d['device_id'],
fixed_ips=[FixedIp.from_dict(fip) for fip in d['fixed_ips']],
mac_address=d['mac_address'],
network_id=d['network_id'],
device_owner=d['device_owner'],
name=d['name'])
def to_dict(self):
fields = ('id', 'device_id', 'mac_address', 'network_id',
'device_owner', 'name')
out = dict((f, getattr(self, f)) for f in fields)
out['fixed_ips'] = [fip.to_dict() for fip in self.fixed_ips]
return out
class FixedIp(ModelBase):
def __init__(self, subnet_id, ip_address):
self.subnet_id = subnet_id
self.ip_address = netaddr.IPAddress(ip_address)
@classmethod
def from_dict(cls, d):
return cls(d['subnet_id'], d['ip_address'])
def to_dict(self):
fields = ('subnet_id', 'ip_address')
return dict((f, getattr(self, f)) for f in fields)
class SystemConfiguration(ModelBase):
service_name = 'system'
def __init__(self, conf_dict={}):
self.tenant_id = conf_dict.get('tenant_id')
self.hostname = conf_dict.get('hostname')
self.networks = [
Network.from_dict(n) for n in conf_dict.get('networks', [])]
def validate(self):
# TODO: Improve this interface, it currently sucks.
errors = []
for attr in ['tenant_id', 'hostname']:
if not getattr(self, attr):
errors.append((attr, 'Config does not contain a %s' % attr))
return errors
@property
def management_address(self):
addrs = []
for net in self.networks:
if net.is_management_network:
addrs.extend((net.interface.first_v4, net.interface.first_v6))
addrs = sorted(a for a in addrs if a)
if addrs:
return addrs[0]
@property
def interfaces(self):
return [n.interface for n in self.networks if n.interface]
def to_dict(self):
fields = ('tenant_id', 'hostname', 'management_address', 'interfaces')
return dict((f, getattr(self, f)) for f in fields)
class RouterConfiguration(SystemConfiguration):
service_name = 'router'
def __init__(self, conf_dict={}):
super(RouterConfiguration, self).__init__(conf_dict)
gw = conf_dict.get('default_v4_gateway')
self.default_v4_gateway = netaddr.IPAddress(gw) if gw else None
self.asn = conf_dict.get('asn', DEFAULT_AS)
self.neighbor_asn = conf_dict.get('neighbor_asn', self.asn)
self.networks = [
Network.from_dict(n) for n in conf_dict.get('networks', [])]
self.static_routes = [StaticRoute(*r) for r in
conf_dict.get('static_routes', [])]
@ -491,17 +719,13 @@ class Configuration(ModelBase):
FloatingIP.from_dict(fip)
for fip in conf_dict.get('floating_ips', [])
]
self.tenant_id = conf_dict.get('tenant_id')
self.hostname = conf_dict.get('hostname')
self._attach_floating_ips(self.floating_ips)
def validate(self):
"""Validate anchor rules to ensure that ifaces and tables exist."""
errors = []
interfaces = set(n.interface.ifname for n in self.networks)
errors = []
for anchor in self.anchors:
for rule in anchor.rules:
for iface in (rule.interface, rule.destination_interface):
@ -561,18 +785,49 @@ class Configuration(ModelBase):
if addrs:
return addrs[0]
@property
def interfaces(self):
return [n.interface for n in self.networks if n.interface]
@property
def management_address(self):
addrs = []
for net in self.networks:
if net.is_management_network:
addrs.extend((net.interface.first_v4, net.interface.first_v6))
class LoadBalancerConfiguration(SystemConfiguration):
service_name = 'loadbalancer'
addrs = sorted(a for a in addrs if a)
def __init__(self, conf_dict={}):
super(LoadBalancerConfiguration, self).__init__(conf_dict)
self.id = conf_dict.get('id')
self.name = conf_dict.get('name')
if conf_dict:
self._loadbalancer = LoadBalancer.from_dict(conf_dict)
self.vip_port = self._loadbalancer.vip_port
self.vip_address = self._loadbalancer.vip_address
self.listeners = self._loadbalancer.listeners
else:
self.vip_port = None
self.vip_address = None
self.listeners = []
if addrs:
return addrs[0]
def validate(self):
super(LoadBalancerConfiguration, self).validate()
errors = []
if not self.id:
errors.append(['id', 'Missing in config id'])
return errors
def to_dict(self):
if self.vip_port:
vip_port = self.vip_port.to_dict()
else:
vip_port = {}
return {
'id': self.id,
'name': self.name,
'vip_port': vip_port,
'vip_address': self.vip_address,
'listeners': [l.to_dict() for l in self.listeners],
}
SERVICE_MAP = {
RouterConfiguration.service_name: RouterConfiguration,
LoadBalancerConfiguration.service_name: LoadBalancerConfiguration,
}
def get_config_model(service):
return SERVICE_MAP[service]

13
akanda/router/settings.py Normal file
View File

@ -0,0 +1,13 @@
# Configures which advanced service drivers are loaded by this
# instance of the appliance.
ENABLED_SERVICES = ['router']
# If akanda_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/akanda and append that path to the gunicorn's
# python path.
try:
from akanda_local_settings import * # noqa
except ImportError:
pass

View File

@ -14,7 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
import json
import os
@ -27,6 +26,9 @@ import netaddr
from akanda.router import models
DEFAULT_ENABLED_SERVICES = ['router']
VALID_SERVICES = ['router', 'loadbalancer']
def execute(args, root_helper=None):
if root_helper:

View File

@ -12,7 +12,7 @@
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"

View File

@ -34,6 +34,15 @@
- metadata
- akanda-router-api-server
- name: create /usr/local/share/akanda/
file: path=/usr/local/share/akanda state=directory
- name: make /usr/local/share/akanda/ importable
copy: dest=/usr/local/share/akanda/__init__.py content=''
- name: install akanda_local_settings.py
copy: dest=/usr/local/share/akanda/akanda_local_settings.py content='ENABLED_SERVICES = {{enabled_advanced_services.split(',')}}\n'
- name: update-rc
command: update-rc.d akanda-router-api-server start

View File

@ -1,3 +1,9 @@
This is the base element for building an Akanda appliance image.
Ansible is required on the local system.
Advanced service drivers may be enabled in the appliance by setting
``DIB_AKANDA_ADVANCED_SERVICES``. This defaults to enabling only the
router driver, but you may enabled other avialable drivers ie:
DIB_AKANDA_ADVANCED_SERVICES=router,loadbalancer

View File

@ -2,8 +2,10 @@
set -eux
set -o pipefail
DIB_AKANDA_ADVANCED_SERVICES=${DIB_AKANDA_ADVANCED_SERVICES:-"router"}
APP_SRC_DIR="/tmp/akanda-appliance"
[ -d "${APP_SRC_DIR}" ] || exit 0
ansible-playbook -i "localhost," -c local $APP_SRC_DIR/ansible/main.yml
ansible-playbook -i "localhost," -c local -e enabled_advanced_services="$DIB_AKANDA_ADVANCED_SERVICES" $APP_SRC_DIR/ansible/main.yml

View File

@ -0,0 +1,16 @@
#!/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

View File

@ -13,7 +13,7 @@
PATH=/bin:/usr/bin:/sbin:/usr/sbin
DAEMON="/usr/local/bin/gunicorn"
NAME="akanda-router-api-server"
OPTIONS="-c /etc/akanda_gunicorn_config akanda.router.api.server:app"
OPTIONS="--pythonpath /usr/local/share/akanda -c /etc/akanda_gunicorn_config akanda.router.api.server:app"
PIDFILE=/var/run/gunicorn.pid
test -x $DAEMON || exit 0

View File

@ -42,8 +42,8 @@ all_files = 1
build-dir = doc/build
source-dir = doc/source
[nosetests]
where = test
verbosity = 2
detailed-errors = 1
cover-package = akanda
#[nosetests]
#where = test
#verbosity = 2
#detailed-errors = 1
#cover-package = akanda

View File

@ -26,9 +26,15 @@ import flask
import json
import mock
from akanda.router import manager
from akanda.router.api import v1
SYSTEM_CONFIG = {
'tenant_id': 'foo_tenant_id',
'hostname': 'foohostname',
}
class SystemAPITestCase(unittest.TestCase):
"""
This test case contains the unit tests for the Python server implementation
@ -54,7 +60,7 @@ class SystemAPITestCase(unittest.TestCase):
'unsupported platform'
)
def test_get_interface(self):
with mock.patch.object(v1.system.manager, 'get_interface') as get_if:
with mock.patch.object(v1.system.manager.router, 'get_interface') as get_if:
get_if.return_value = 'ge1'
result = self.test_app.get('/v1/system/interface/ge1')
get_if.assert_called_once_with('ge1')
@ -68,7 +74,7 @@ class SystemAPITestCase(unittest.TestCase):
'unsupported platform'
)
def test_get_interfaces(self):
with mock.patch.object(v1.system.manager, 'get_interfaces') as get_ifs:
with mock.patch.object(v1.system.manager.router, 'get_interfaces') as get_ifs:
get_ifs.return_value = ['ge0', 'ge1']
result = self.test_app.get('/v1/system/interfaces')
get_ifs.assert_called_once_with()
@ -81,14 +87,29 @@ class SystemAPITestCase(unittest.TestCase):
not distutils.spawn.find_executable('ip'),
'unsupported platform'
)
def test_get_configuration(self):
@mock.patch.object(manager, 'settings')
@mock.patch.object(v1.system, 'settings')
def test_get_configuration(self, fake_api_settings, fake_mgr_settings):
fake_api_settings.ENABLED_SERVICES = ['router', 'loadbalancer']
fake_mgr_settings.ENABLED_SERVICES = ['router', 'loadbalancer']
result = self.test_app.get('/v1/system/config')
expected = {
'configuration': {
'address_book': {},
'anchors': [],
'networks': [],
'services': {
'loadbalancer': None,
'router': None
},
'static_routes': [],
'anchors': []
'system': {
'hostname': None,
'interfaces': [],
'management_address': None,
'tenant_id': None
}
}
}
self.assertEqual(json.loads(result.data), expected)
@ -102,7 +123,7 @@ class SystemAPITestCase(unittest.TestCase):
self.assertEqual(result.status_code, 415)
def test_put_configuration_returns_422_for_ValueError(self):
with mock.patch('akanda.router.models.Configuration') as Config:
with mock.patch('akanda.router.models.RouterConfiguration') as Config:
Config.side_effect = ValueError
result = self.test_app.put(
'/v1/system/config',
@ -112,11 +133,11 @@ class SystemAPITestCase(unittest.TestCase):
self.assertEqual(result.status_code, 422)
def test_put_configuration_returns_422_for_errors(self):
with mock.patch('akanda.router.models.Configuration') as Config:
with mock.patch('akanda.router.models.SystemConfiguration') as Config:
Config.return_value.validate.return_value = ['error1']
result = self.test_app.put(
'/v1/system/config',
data=json.dumps({'networks': [{}]}), # malformed dict
data=json.dumps(SYSTEM_CONFIG),
content_type='application/json'
)
self.assertEqual(result.status_code, 422)
@ -129,13 +150,149 @@ class SystemAPITestCase(unittest.TestCase):
not distutils.spawn.find_executable('ip'),
'unsupported platform'
)
def test_put_configuration_returns_200(self):
with mock.patch.object(v1.system.manager, 'update_config') as update:
result = self.test_app.put(
'/v1/system/config',
data=json.dumps({}),
content_type='application/json'
)
self.assertEqual(result.status_code, 200)
self.assertTrue(json.loads(result.data))
@mock.patch('akanda.router.api.v1.system._get_cache')
@mock.patch('akanda.router.models.SystemConfiguration')
@mock.patch.object(v1.system.manager, 'update_config')
def test_put_configuration_returns_200(self, mock_update,
fake_system_config, fake_cache):
fake_cache.return_value = 'fake_cache'
sys_config_obj = mock.Mock()
sys_config_obj.validate = mock.Mock()
sys_config_obj.validate.return_value = []
fake_system_config.return_value = sys_config_obj
result = self.test_app.put(
'/v1/system/config',
data=json.dumps({
'tenant_id': 'foo_tenant_id',
'hostname': 'foo_hostname',
}),
content_type='application/json'
)
self.assertEqual(result.status_code, 200)
self.assertTrue(json.loads(result.data))
mock_update.assert_called_with(
cache='fake_cache', service_configs=[], system_config=sys_config_obj)
@mock.patch('akanda.router.manager.Manager.config',
new_callable=mock.PropertyMock, return_value={})
@mock.patch('akanda.router.api.v1.system._get_cache')
@mock.patch('akanda.router.models.RouterConfiguration')
@mock.patch('akanda.router.models.SystemConfiguration')
@mock.patch.object(v1.system.manager, 'update_config')
def test_put_configuration_with_router(self, mock_update,
fake_system_config, fake_router_config, fake_cache, fake_config):
fake_config.return_value = 'foo'
fake_cache.return_value = 'fake_cache'
sys_config_obj = mock.Mock()
sys_config_obj.validate = mock.Mock()
sys_config_obj.validate.return_value = []
fake_system_config.return_value = sys_config_obj
router_config_obj = mock.Mock()
router_config_obj.validate = mock.Mock()
router_config_obj.validate.return_value = []
fake_router_config.return_value = router_config_obj
result = self.test_app.put(
'/v1/system/config',
data=json.dumps({
'tenant_id': 'foo_tenant_id',
'hostname': 'foo_hostname',
'asn': 'foo_asn',
}),
content_type='application/json'
)
self.assertEqual(result.status_code, 200)
self.assertTrue(json.loads(result.data))
mock_update.assert_called_with(
cache='fake_cache', service_configs=[router_config_obj],
system_config=sys_config_obj)
@mock.patch('akanda.router.models.get_config_model')
@mock.patch.object(manager, 'settings')
@mock.patch.object(v1.system, 'settings')
@mock.patch('akanda.router.manager.Manager.config',
new_callable=mock.PropertyMock, return_value={})
@mock.patch('akanda.router.api.v1.system._get_cache')
@mock.patch('akanda.router.models.LoadBalancerConfiguration')
@mock.patch('akanda.router.models.SystemConfiguration')
@mock.patch.object(v1.system.manager, 'update_config')
def test_put_configuration_with_adv_services(self, mock_update,
fake_system_config, fake_lb_config, fake_cache, fake_config,
fake_api_settings, fake_mgr_settings, fake_get_config_model):
fake_api_settings.ENABLED_SERVICES = ['loadbalancer']
fake_mgr_settings.ENABLED_SERVICES = ['loadbalancer']
fake_config.return_value = 'foo'
fake_cache.return_value = 'fake_cache'
sys_config_obj = mock.Mock()
sys_config_obj.validate = mock.Mock()
sys_config_obj.validate.return_value = []
fake_system_config.return_value = sys_config_obj
lb_config_obj = mock.Mock()
lb_config_obj.validate = mock.Mock()
lb_config_obj.validate.return_value = []
fake_lb_config.return_value = lb_config_obj
fake_get_config_model.return_value = fake_lb_config
result = self.test_app.put(
'/v1/system/config',
data=json.dumps({
'tenant_id': 'foo_tenant_id',
'hostname': 'foo_hostname',
'services': {
'loadbalancer': {'id': 'foo'}
}
}),
content_type='application/json'
)
self.assertEqual(result.status_code, 200)
self.assertTrue(json.loads(result.data))
mock_update.assert_called_with(
cache='fake_cache', service_configs=[lb_config_obj],
system_config=sys_config_obj)
@mock.patch('akanda.router.models.get_config_model')
@mock.patch.object(manager, 'settings')
@mock.patch.object(v1.system, 'settings')
@mock.patch('akanda.router.manager.Manager.config',
new_callable=mock.PropertyMock, return_value={})
@mock.patch('akanda.router.api.v1.system._get_cache')
@mock.patch('akanda.router.models.LoadBalancerConfiguration')
@mock.patch('akanda.router.models.SystemConfiguration')
@mock.patch.object(v1.system.manager, 'update_config')
def test_put_configuration_with_disabled_svc_returns_400(self, mock_update,
fake_system_config, fake_lb_config, fake_cache, fake_config,
fake_api_settings, fake_mgr_settings, fake_get_config_model):
fake_api_settings.ENABLED_SERVICES = ['foo']
fake_mgr_settings.ENABLED_SERVICES = ['foo']
fake_config.return_value = 'foo'
fake_cache.return_value = 'fake_cache'
sys_config_obj = mock.Mock()
sys_config_obj.validate = mock.Mock()
sys_config_obj.validate.return_value = []
fake_system_config.return_value = sys_config_obj
lb_config_obj = mock.Mock()
lb_config_obj.validate = mock.Mock()
lb_config_obj.validate.return_value = []
fake_lb_config.return_value = lb_config_obj
fake_get_config_model.return_value = fake_lb_config
result = self.test_app.put(
'/v1/system/config',
data=json.dumps({
'tenant_id': 'foo_tenant_id',
'hostname': 'foo_hostname',
'services': {
'loadbalancer': {'id': 'foo'}
}
}),
content_type='application/json'
)
self.assertEqual(result.status_code, 400)

View File

@ -94,7 +94,7 @@ class ARPTest(unittest2.TestCase):
])
def test_send_gratuitous_arp_for_config(self):
config = models.Configuration({
config = models.RouterConfiguration({
'networks': [{
'network_id': 'ABC456',
'interface': {

View File

@ -7,7 +7,7 @@ import netaddr
from akanda.router import models
from akanda.router.drivers import iptables
CONFIG = models.Configuration({
CONFIG = models.RouterConfiguration({
'networks': [{
'network_id': 'ABC123',
'interface': {
@ -127,16 +127,16 @@ V6_OUTPUT = [
]
class TestIPTablesConfiguration(TestCase):
class TestIPTablesRouterConfiguration(TestCase):
def setUp(self):
super(TestIPTablesConfiguration, self).setUp()
super(TestIPTablesRouterConfiguration, self).setUp()
self.execute = mock.patch('akanda.router.utils.execute').start()
self.replace = mock.patch('akanda.router.utils.replace_file').start()
self.patches = [self.execute, self.replace]
def tearDown(self):
super(TestIPTablesConfiguration, self).tearDown()
super(TestIPTablesRouterConfiguration, self).tearDown()
for p in self.patches:
p.stop()

View File

@ -161,7 +161,7 @@ class RouteTest(unittest2.TestCase):
)
def test_update_default_no_inputs(self):
c = models.Configuration({})
c = models.RouterConfiguration({})
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
set.side_effect = AssertionError(
'should not try to set default gw'
@ -169,7 +169,7 @@ class RouteTest(unittest2.TestCase):
self.mgr.update_default_gateway(c)
def test_update_default_v4_from_gateway(self):
c = models.Configuration({'default_v4_gateway': '172.16.77.1'})
c = models.RouterConfiguration({'default_v4_gateway': '172.16.77.1'})
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
self.mgr.update_default_gateway(c)
set.assert_called_once_with(c.default_v4_gateway, None)
@ -189,7 +189,7 @@ class RouteTest(unittest2.TestCase):
subnets=[subnet],
network_type='external',
)
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
self.mgr.update_default_gateway(c)
net = c.networks[0]
@ -217,7 +217,7 @@ class RouteTest(unittest2.TestCase):
subnets=[subnet, subnet2],
network_type='external',
)
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
self.mgr.update_default_gateway(c)
net = c.networks[0]
@ -239,7 +239,7 @@ class RouteTest(unittest2.TestCase):
subnets=[subnet],
network_type='external',
)
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
self.mgr.update_default_gateway(c)
net = c.networks[0]
@ -267,7 +267,7 @@ class RouteTest(unittest2.TestCase):
subnets=[subnet, subnet2],
network_type='external',
)
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
self.mgr.update_default_gateway(c)
net = c.networks[0]
@ -292,7 +292,7 @@ class RouteTest(unittest2.TestCase):
interface=dict(ifname='ge0', addresses=['fe80::2']),
subnets=[subnet]
)
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
cache = make_region().configure('dogpile.cache.memory')
with mock.patch.object(self.mgr, 'sudo') as sudo:
@ -319,7 +319,7 @@ class RouteTest(unittest2.TestCase):
# Empty the host_routes list
sudo.reset_mock()
subnet['host_routes'] = []
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
self.mgr.update_host_routes(c, cache)
sudo.assert_called_once_with(
'-4', 'route', 'del', '192.240.128.0/20', 'via',
@ -336,7 +336,7 @@ class RouteTest(unittest2.TestCase):
'destination': '192.220.128.0/20',
'nexthop': '192.168.89.3'
}]
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
self.mgr.update_host_routes(c, cache)
self.assertEqual(sudo.call_args_list, [
mock.call('-4', 'route', 'add', '192.240.128.0/20',
@ -354,7 +354,7 @@ class RouteTest(unittest2.TestCase):
'destination': '192.185.128.0/20',
'nexthop': '192.168.89.4'
}]
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
self.mgr.update_host_routes(c, cache)
self.assertEqual(sudo.call_args_list, [
mock.call('-4', 'route', 'del', '192.220.128.0/20',
@ -376,7 +376,7 @@ class RouteTest(unittest2.TestCase):
'nexthop': '192.168.90.1'
}]
))
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
self.mgr.update_host_routes(c, cache)
self.assertEqual(sudo.call_args_list, [
mock.call('-4', 'route', 'add', '192.240.128.0/20',
@ -388,7 +388,7 @@ class RouteTest(unittest2.TestCase):
sudo.reset_mock()
network['subnets'][0]['host_routes'] = []
network['subnets'][1]['host_routes'] = []
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
self.mgr.update_host_routes(c, cache)
self.assertEqual(sudo.call_args_list, [
mock.call('-4', 'route', 'del', '192.185.128.0/20',
@ -416,7 +416,7 @@ class RouteTest(unittest2.TestCase):
interface=dict(ifname='ge0', addresses=['fe80::2']),
subnets=[subnet]
)
c = models.Configuration({'networks': [network]})
c = models.RouterConfiguration({'networks': [network]})
cache = make_region().configure('dogpile.cache.memory')
with mock.patch.object(self.mgr, 'sudo') as sudo:

138
test/unit/fakes.py Normal file
View File

@ -0,0 +1,138 @@
from copy import copy
FAKE_SYSTEM_DICT = {
"tenant_id": "d22b149cee9b4eac8349c517eda00b89",
"hostname": "ak-loadbalancer-d22b149cee9b4eac8349c517eda00b89",
"networks": [
{
"v4_conf_service": "static",
"network_type": "loadbalancer",
"v6_conf_service": "static",
"network_id": "b7fc9b39-401c-47cc-a07d-9f8cde75ccbf",
"allocations": [],
"subnets": [
{
"host_routes": [],
"cidr": "192.168.0.0/24",
"gateway_ip": "192.168.0.1",
"dns_nameservers": [],
"dhcp_enabled": True
},
{
"host_routes": [],
"cidr": "fdd6:a1fa:cfa8:6af6::/64",
"gateway_ip": "fdd6:a1fa:cfa8:6af6::1",
"dns_nameservers": [],
"dhcp_enabled": False
}],
"interface": {
"ifname": "ge1",
"addresses": [
"192.168.0.137/24", "fdd6:a1fa:cfa8:6af6:f816:3eff:fea0:8082/64"
]
},
},
{
"v4_conf_service": "static",
"network_type": "management",
"v6_conf_service": "static",
"network_id": "43dc2fad-f6f9-4668-9695-fed50f7768aa",
"allocations": [],
"subnets": [
{
"host_routes": [],
"cidr": "fdca:3ba5:a17a:acda::/64",
"gateway_ip": "fdca:3ba5:a17a:acda::1",
"dns_nameservers": [],
"dhcp_enabled": True}
],
"interface": {
"ifname": "ge0",
"addresses": ["fdca:3ba5:a17a:acda:f816:3eff:fee0:e1b0/64"]
},
}]
}
FAKE_LOADBALANCER_DICT = {
"id": "8ac54799-b143-48e5-94d4-e5e989592229",
"status": "ACTIVE",
"name": "balancer1",
"admin_state_up": True,
"tenant_id": "d22b149cee9b4eac8349c517eda00b89",
"vip_port": {
"name": "loadbalancer-8ac54799-b143-48e5-94d4-e5e989592229",
"network_id": "b7fc9b39-401c-47cc-a07d-9f8cde75ccbf",
"device_owner": "neutron:LOADBALANCERV2",
"mac_address": "fa:16:3e:a0:80:82",
"fixed_ips": [
{
"subnet_id": "8c58b558-be54-45de-9873-169fe845bb80",
"ip_address": "192.168.0.137"
},
{
"subnet_id": "89fe7a9d-be92-469c-9a1e-503a39462ed1",
"ip_address": "fdd6:a1fa:cfa8:6af6:f816:3eff:fea0:8082"}
],
"id": "352e2867-06c6-4ced-8e81-1c016991fb38",
"device_id": "8ac54799-b143-48e5-94d4-e5e989592229"},
"vip_address": "192.168.0.137",
"id": "8ac54799-b143-48e5-94d4-e5e989592229",
"listeners": [],
}
FAKE_LISTENER_DICT = {
'admin_state_up': True,
'default_pool': None,
'id': '8dca64a2-beaa-484e-a3c8-59c9b63913e0',
'name': 'listener1',
'protocol': 'HTTP',
'protocol_port': 80,
'tenant_id': 'd22b149cee9b4eac8349c517eda00b89'
}
FAKE_POOL_DICT = {
'admin_state_up': True,
'healthmonitor': None,
'id': u'255c4d63-6199-4afc-abec-48c5ab46ac2e',
'lb_algorithm': u'ROUND_ROBIN',
'members': [],
'name': u'pool1',
'protocol': u'HTTP',
'session_persistence': None,
'tenant_id': u'd22b149cee9b4eac8349c517eda00b89'
}
FAKE_MEMBER_DICT = {
'address': u'192.168.0.194',
'admin_state_up': True,
'id': u'30fc9549-7804-4196-bb86-8ebabc3a79e2',
'protocol_port': 80,
'subnet': None,
'tenant_id': u'd22b149cee9b4eac8349c517eda00b89',
'weight': 1
}
def fake_loadbalancer_dict(listener=False, pool=False, members=False):
lb_dict = copy(FAKE_LOADBALANCER_DICT)
if listener:
lb_dict['listeners'] = [copy(FAKE_LISTENER_DICT)]
if pool:
if not listener:
raise Exception("Cannot create pool without a listener")
lb_dict['listeners'][0]['default_pool'] = \
copy(FAKE_POOL_DICT)
if members:
if not pool:
raise Exception("Cannot create member without a pool")
lb_dict['listeners'][0]['default_pool']['members'] = \
[copy(FAKE_MEMBER_DICT)]
return lb_dict

View File

@ -17,11 +17,14 @@
import textwrap
import copy
import mock
import netaddr
from unittest2 import TestCase
from akanda.router import models
from test.unit import fakes
class InterfaceModelTestCase(TestCase):
@ -360,7 +363,7 @@ class NetworkTestCase(TestCase):
n = models.Network('id', None, v6_conf_service='invalid')
class ConfigurationTestCase(TestCase):
class RouterConfigurationTestCase(TestCase):
def test_init_only_networks(self):
subnet = dict(
cidr='192.168.1.0/24',
@ -375,28 +378,28 @@ class ConfigurationTestCase(TestCase):
allocations=[],
subnets=[subnet])
c = models.Configuration(dict(networks=[network]))
c = models.RouterConfiguration(dict(networks=[network]))
self.assertEqual(len(c.networks), 1)
self.assertEqual(c.networks[0],
models.Network.from_dict(network))
def test_init_tenant_id(self):
c = models.Configuration({'tenant_id': 'abc123'})
c = models.RouterConfiguration({'tenant_id': 'abc123'})
self.assertEqual(c.tenant_id, 'abc123')
def test_no_default_v4_gateway(self):
c = models.Configuration({})
c = models.RouterConfiguration({})
self.assertIsNone(c.default_v4_gateway)
def test_valid_default_v4_gateway(self):
c = models.Configuration({'default_v4_gateway': '172.16.77.1'})
c = models.RouterConfiguration({'default_v4_gateway': '172.16.77.1'})
self.assertEqual(c.default_v4_gateway.version, 4)
self.assertEqual(str(c.default_v4_gateway), '172.16.77.1')
def test_init_only_static_routes(self):
routes = [('0.0.0.0/0', '192.168.1.1'),
('172.16.77.0/16', '192.168.1.254')]
c = models.Configuration(dict(networks=[], static_routes=routes))
c = models.RouterConfiguration(dict(networks=[], static_routes=routes))
self.assertEqual(len(c.static_routes), 2)
self.assertEqual(
@ -406,7 +409,7 @@ class ConfigurationTestCase(TestCase):
def test_init_address_book(self):
ab = {"webservers": ["192.168.57.101/32", "192.168.57.230/32"]}
c = models.Configuration(dict(networks=[], address_book=ab))
c = models.RouterConfiguration(dict(networks=[], address_book=ab))
self.assertEqual(
c.address_book.get('webservers'),
models.AddressBookEntry('webservers', ab['webservers']))
@ -414,7 +417,7 @@ class ConfigurationTestCase(TestCase):
def test_init_label(self):
labels = {"external": ["192.168.57.0/24"]}
c = models.Configuration(dict(networks=[], labels=labels))
c = models.RouterConfiguration(dict(networks=[], labels=labels))
self.assertEqual(
c.labels[0],
models.Label('external', ['192.168.57.0/24']))
@ -424,30 +427,30 @@ class ConfigurationTestCase(TestCase):
name='theanchor',
rules=[])
c = models.Configuration(dict(networks=[], anchors=[anchor_dict]))
c = models.RouterConfiguration(dict(networks=[], anchors=[anchor_dict]))
self.assertEqual(len(c.anchors), 1)
def test_init_anchor(self):
test_rule = dict(action='block', source='192.168.1.1/32')
anchor_dict = dict(name='theanchor', rules=[test_rule])
c = models.Configuration(dict(networks=[], anchors=[anchor_dict]))
c = models.RouterConfiguration(dict(networks=[], anchors=[anchor_dict]))
self.assertEqual(len(c.anchors), 1)
self.assertEqual(len(c.anchors[0].rules), 1)
self.assertEqual(c.anchors[0].rules[0].action, 'block')
def test_asn_default(self):
c = models.Configuration({'networks': []})
c = models.RouterConfiguration({'networks': []})
self.assertEqual(c.asn, 64512)
self.assertEqual(c.neighbor_asn, 64512)
def test_asn_provided_with_neighbor_fallback(self):
c = models.Configuration({'networks': [], 'asn': 12345})
c = models.RouterConfiguration({'networks': [], 'asn': 12345})
self.assertEqual(c.asn, 12345)
self.assertEqual(c.neighbor_asn, 12345)
def test_asn_provided_with_neighbor_different(self):
c = models.Configuration(
c = models.RouterConfiguration(
{'networks': [], 'asn': 12, 'neighbor_asn': 34}
)
self.assertEqual(c.asn, 12)
@ -463,7 +466,7 @@ class ConfigurationTestCase(TestCase):
ab = {"webservers": ["192.168.57.101/32", "192.168.57.230/32"]}
anchor_dict = dict(name='theanchor', rules=[rule_dict])
c = models.Configuration(
c = models.RouterConfiguration(
dict(networks=[network], anchors=[anchor_dict], address_book=ab))
errors = c.validate()
@ -517,10 +520,163 @@ class ConfigurationTestCase(TestCase):
self.assertEqual(len(errors), 1)
def test_to_dict(self):
c = models.Configuration({'networks': []})
c = models.RouterConfiguration({'networks': []})
expected = dict(networks=[],
address_book={},
static_routes=[],
anchors=[])
self.assertEqual(c.to_dict(), expected)
class LBListenerTest(TestCase):
def test_from_dict(self):
ldict = copy.copy(fakes.FAKE_LISTENER_DICT)
listener = models.Listener.from_dict(ldict)
for k in ldict.keys():
self.assertEqual(getattr(listener, k), ldict[k])
def test_from_dict_with_pool(self):
ldict = copy.copy(fakes.FAKE_LISTENER_DICT)
pdict = copy.copy(fakes.FAKE_POOL_DICT)
ldict['default_pool'] = pdict
listener = models.Listener.from_dict(ldict)
keys = ldict.keys()
keys.remove('default_pool')
for k in keys:
self.assertEqual(getattr(listener, k), ldict[k])
self.assertTrue(isinstance(listener.default_pool, models.Pool))
def test_to_dict(self):
ldict = copy.copy(fakes.FAKE_LISTENER_DICT)
listener = models.Listener.from_dict(ldict)
l_to_dict = listener.to_dict()
for k in ldict.keys():
self.assertEqual(l_to_dict[k], ldict[k])
def test_to_dict_with_pool(self):
ldict = copy.copy(fakes.FAKE_LISTENER_DICT)
pdict = copy.copy(fakes.FAKE_POOL_DICT)
ldict['default_pool'] = pdict
listener = models.Listener.from_dict(ldict).to_dict()
self.assertEqual(listener['default_pool']['id'], pdict['id'])
class LBPoolTest(TestCase):
def test_from_dict(self):
pdict = copy.copy(fakes.FAKE_POOL_DICT)
pool = models.Pool.from_dict(pdict)
for k in pdict.keys():
self.assertEqual(getattr(pool, k), pdict[k])
def test_from_dict_with_member(self):
pdict = copy.copy(fakes.FAKE_POOL_DICT)
mdict = copy.copy(fakes.FAKE_MEMBER_DICT)
pdict['members'] = [mdict]
pool = models.Pool.from_dict(pdict)
keys = pdict.keys()
keys.remove('members')
for k in keys:
self.assertEqual(getattr(pool, k), pdict[k])
self.assertTrue(isinstance(pool.members[0], models.Member))
def test_to_dict(self):
pdict = copy.copy(fakes.FAKE_POOL_DICT)
pool = models.Pool.from_dict(pdict)
p_to_dict = pool.to_dict()
for k in pdict.keys():
self.assertEqual(p_to_dict[k], pdict[k])
def test_to_dict_with_member(self):
pdict = copy.copy(fakes.FAKE_POOL_DICT)
mdict = copy.copy(fakes.FAKE_MEMBER_DICT)
pdict['members'] = [mdict]
pool = models.Pool.from_dict(pdict)
pool_to_dict = pool.to_dict()
self.assertEqual(pool_to_dict['members'][0]['id'], mdict['id'])
class LBMemberTest(TestCase):
def test_from_dict(self):
mdict = copy.copy(fakes.FAKE_MEMBER_DICT)
member = models.Member.from_dict(mdict)
for k in mdict.keys():
self.assertEqual(getattr(member, k), mdict[k])
def test_to_dict(self):
mdict = copy.copy(fakes.FAKE_MEMBER_DICT)
member = models.Member.from_dict(mdict)
m_to_dict = member.to_dict()
for k in mdict.keys():
self.assertEqual(m_to_dict[k], mdict[k])
class LoadBalancerTest(TestCase):
def test_from_dict_lb(self):
lb_dict = fakes.fake_loadbalancer_dict()
lb = models.LoadBalancer.from_dict(lb_dict)
for k in lb_dict.keys():
self.assertEqual(getattr(lb, k), lb_dict[k])
def test_from_dict_lb_listener(self):
lb_dict = fakes.fake_loadbalancer_dict(listener=True)
expected_listener_id = lb_dict['listeners'][0]['id']
lb = models.LoadBalancer.from_dict(lb_dict)
for k in lb_dict.keys():
self.assertEqual(getattr(lb, k), lb_dict[k])
self.assertTrue(isinstance(lb.listeners[0], models.Listener))
self.assertEqual(lb.listeners[0].id, expected_listener_id)
def test_from_dict_lb_listener_pool(self):
lb_dict = fakes.fake_loadbalancer_dict(listener=True, pool=True)
expected_listener_id = lb_dict['listeners'][0]['id']
expected_pool_id = lb_dict['listeners'][0]['default_pool']['id']
lb = models.LoadBalancer.from_dict(lb_dict)
for k in lb_dict.keys():
self.assertEqual(getattr(lb, k), lb_dict[k])
self.assertTrue(isinstance(lb.listeners[0], models.Listener))
self.assertTrue(isinstance(lb.listeners[0].default_pool,
models.Pool))
self.assertEqual(lb.listeners[0].id, expected_listener_id)
self.assertEqual(lb.listeners[0].default_pool.id, expected_pool_id)
def test_from_dict_lb_listener_pool_members(self):
lb_dict = fakes.fake_loadbalancer_dict(listener=True, pool=True,
members=True)
expected_listener_id = lb_dict['listeners'][0]['id']
expected_pool_id = lb_dict['listeners'][0]['default_pool']['id']
expected_member = lb_dict['listeners'][0]['default_pool']['members'][0]
lb = models.LoadBalancer.from_dict(lb_dict)
for k in lb_dict.keys():
self.assertEqual(getattr(lb, k), lb_dict[k])
self.assertTrue(isinstance(lb.listeners[0], models.Listener))
self.assertTrue(isinstance(lb.listeners[0].default_pool,
models.Pool))
self.assertTrue(isinstance(lb.listeners[0].default_pool.members[0],
models.Member))
self.assertEqual(lb.listeners[0].id, expected_listener_id)
self.assertEqual(lb.listeners[0].default_pool.id, expected_pool_id)
self.assertEqual(lb.listeners[0].default_pool.members[0].id,
expected_member['id'])
class LoadBalancerConfigurationTest(TestCase):
def setUp(self):
super(LoadBalancerConfigurationTest, self).setUp()
self.conf_dict = fakes.fake_loadbalancer_dict(
listener=True, pool=True, members=True
)
def test_loadbalancer_config(self):
lb_conf = models.LoadBalancerConfiguration(self.conf_dict)
errors = lb_conf.validate()
lb_conf.to_dict()
self.assertEqual(errors, [])
def test_loadbalancer_config_validation_failed(self):
self.conf_dict.pop('id')
lb_conf = models.LoadBalancerConfiguration({})
errors = lb_conf.validate()
# id is required
self.assertEqual(len(errors), 1)

View File

@ -129,4 +129,3 @@ class ExecuteTest(TestCase):
utils.execute(['/bin/ls', '/no-such-directory'])
except RuntimeError as e:
self.assertIn('cannot access', str(e))