Merge "Introduces advanced service drivers to akanda-appliance"
This commit is contained in:
commit
3a359c6a29
1
MANIFEST.in
Normal file
1
MANIFEST.in
Normal file
@ -0,0 +1 @@
|
||||
include akanda/router/drivers/loadbalancer/nginx.conf.template
|
@ -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
0
akanda/router/drivers/iptables.py
Executable file → Normal file
37
akanda/router/drivers/loadbalancer/__init__.py
Normal file
37
akanda/router/drivers/loadbalancer/__init__.py
Normal 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)
|
19
akanda/router/drivers/loadbalancer/nginx.conf.template
Normal file
19
akanda/router/drivers/loadbalancer/nginx.conf.template
Normal 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 %}
|
||||
|
75
akanda/router/drivers/loadbalancer/nginx.py
Normal file
75
akanda/router/drivers/loadbalancer/nginx.py
Normal 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'
|
@ -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):
|
||||
|
@ -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
13
akanda/router/settings.py
Normal 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
|
@ -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:
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
16
diskimage-builder/elements/nginx/post-install.d/99-disable-default-nginx
Executable file
16
diskimage-builder/elements/nginx/post-install.d/99-disable-default-nginx
Executable 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
|
@ -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
|
||||
|
10
setup.cfg
10
setup.cfg
@ -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
|
||||
|
@ -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:
|
||||
|
||||
|
||||
@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({}),
|
||||
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)
|
||||
|
@ -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': {
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
138
test/unit/fakes.py
Normal 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
|
@ -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)
|
||||
|
@ -129,4 +129,3 @@ class ExecuteTest(TestCase):
|
||||
utils.execute(['/bin/ls', '/no-such-directory'])
|
||||
except RuntimeError as e:
|
||||
self.assertIn('cannot access', str(e))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user