Introduces advanced service drivers to akanda-appliance
This introduces the ability to create service manager drivers to handle managing advanced services within the akanda-appliance. It splits some common things into a System manager. Existing stuff that is router-specific is moved to a Router manager and we begin implementing LBAAS drivers using Nginx. At the moment, configuration for which drivers are loaded by the appliance code itself is stored in /etc/default/akanda-appliance. This is setup by a DIB_* variable and accessed by the appliance via environment variable. We should improve this later when we need to expose richer configuration to the appliance. We could and should work on the API for this. Currently, our v1 API is entirely router-specific. This adds to that and allows the RUG to attach other advanced service configuratino data to the config object it pushes. If the corresponding service's driver has been enabled in the appliance, it will attempt to find that data and configure the advanced service accordingly. Ideally, longterm we want a v2 API that can reference all services the same. There's a few ugly compat hacks added here to maintain compatability with where the RUG expects certain router resources to be. We can evolve this over time. Partially-implements: blueprint appliance-provisioning-driver Depends-on: Ic19a883f56fb6d65a83b1f4d93b581f9e242d97f Change-Id: I6048789ec15fad1dbc899cbbd82508433cb96d44
This commit is contained in:
parent
35529c9050
commit
433a4c7190
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 models
|
||||||
from akanda.router import utils
|
from akanda.router import utils
|
||||||
|
from akanda.router import settings
|
||||||
from akanda.router.manager import manager
|
from akanda.router.manager import manager
|
||||||
|
|
||||||
blueprint = utils.blueprint_factory(__name__)
|
blueprint = utils.blueprint_factory(__name__)
|
||||||
@ -32,6 +33,9 @@ blueprint = utils.blueprint_factory(__name__)
|
|||||||
_cache = None
|
_cache = None
|
||||||
|
|
||||||
|
|
||||||
|
ADVANCED_SERVICES_KEY = 'services'
|
||||||
|
|
||||||
|
|
||||||
def _get_cache():
|
def _get_cache():
|
||||||
global _cache
|
global _cache
|
||||||
if _cache is None:
|
if _cache is None:
|
||||||
@ -51,7 +55,7 @@ def get_interface(ifname):
|
|||||||
Show interface parameters given an interface name.
|
Show interface parameters given an interface name.
|
||||||
For example ge1, ge2 for generic ethernet
|
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')
|
@blueprint.route('/interfaces')
|
||||||
@ -60,7 +64,7 @@ def get_interfaces():
|
|||||||
'''
|
'''
|
||||||
Show all interfaces and parameters
|
Show all interfaces and parameters
|
||||||
'''
|
'''
|
||||||
return dict(interfaces=manager.get_interfaces())
|
return dict(interfaces=manager.router.get_interfaces())
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/config', methods=['GET'])
|
@blueprint.route('/config', methods=['GET'])
|
||||||
@ -77,17 +81,75 @@ def put_configuration():
|
|||||||
abort(415)
|
abort(415)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config_candidate = models.Configuration(request.json)
|
system_config_candidate = models.SystemConfiguration(request.json)
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
return Response(
|
return Response(
|
||||||
'The config failed to deserialize.\n' + str(e),
|
'The system config failed to deserialize.\n' + str(e),
|
||||||
status=422)
|
status=422)
|
||||||
|
|
||||||
errors = config_candidate.validate()
|
errors = system_config_candidate.validate()
|
||||||
if errors:
|
if errors:
|
||||||
return Response(
|
return Response(
|
||||||
'The config failed to validate.\n' + '\n'.join(errors),
|
'The config failed to validate.\n' + '\n'.join(errors),
|
||||||
status=422)
|
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)
|
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
|
import re
|
||||||
|
|
||||||
from akanda.router import models
|
from akanda.router import models
|
||||||
|
from akanda.router import settings
|
||||||
from akanda.router.drivers import (bird, dnsmasq, ip, metadata,
|
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='.'):
|
def __init__(self, state_path='.'):
|
||||||
|
self._config = None
|
||||||
self.state_path = os.path.abspath(state_path)
|
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
|
@property
|
||||||
def config(self):
|
def config(self):
|
||||||
"""Make config a read-only property.
|
"""Make config a read-only property.
|
||||||
|
|
||||||
To update the value, update_config() must called to change the global
|
To update the value, update_config() must called to change the global
|
||||||
state of router.
|
state of appliance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self._config
|
return self._config
|
||||||
|
|
||||||
def update_config(self, config, cache):
|
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_hostname()
|
||||||
self.update_interfaces()
|
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_dhcp()
|
||||||
self.update_metadata()
|
self.update_metadata()
|
||||||
self.update_bgp_and_radv()
|
self.update_bgp_and_radv()
|
||||||
@ -55,30 +81,24 @@ class Manager(object):
|
|||||||
self.update_routes(cache)
|
self.update_routes(cache)
|
||||||
self.update_arp()
|
self.update_arp()
|
||||||
|
|
||||||
# TODO(mark): update_vpn
|
|
||||||
|
|
||||||
def update_hostname(self):
|
|
||||||
mgr = hostname.HostnameManager()
|
|
||||||
mgr.update(self.config)
|
|
||||||
|
|
||||||
def update_interfaces(self):
|
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.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):
|
def update_dhcp(self):
|
||||||
mgr = dnsmasq.DHCPManager()
|
mgr = dnsmasq.DHCPManager()
|
||||||
|
|
||||||
mgr.delete_all_config()
|
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)
|
real_ifname = self.ip_mgr.generic_to_host(network.interface.ifname)
|
||||||
mgr.update_network_dhcp_config(real_ifname, network)
|
mgr.update_network_dhcp_config(real_ifname, network)
|
||||||
mgr.restart()
|
mgr.restart()
|
||||||
|
|
||||||
def update_metadata(self):
|
def update_metadata(self):
|
||||||
mgr = metadata.MetadataManager()
|
mgr = metadata.MetadataManager()
|
||||||
should_restart = mgr.networks_have_changed(self.config)
|
should_restart = mgr.networks_have_changed(self._config)
|
||||||
mgr.save_config(self.config)
|
mgr.save_config(self._config)
|
||||||
if should_restart:
|
if should_restart:
|
||||||
mgr.restart()
|
mgr.restart()
|
||||||
else:
|
else:
|
||||||
@ -86,26 +106,26 @@ class Manager(object):
|
|||||||
|
|
||||||
def update_bgp_and_radv(self):
|
def update_bgp_and_radv(self):
|
||||||
mgr = bird.BirdManager()
|
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()
|
mgr.restart()
|
||||||
|
|
||||||
def update_firewall(self):
|
def update_firewall(self):
|
||||||
mgr = iptables.IPTablesManager()
|
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()
|
mgr.restart()
|
||||||
|
|
||||||
def update_routes(self, cache):
|
def update_routes(self, cache):
|
||||||
mgr = ip.IPManager()
|
mgr = ip.IPManager()
|
||||||
mgr.update_default_gateway(self.config)
|
mgr.update_default_gateway(self._config)
|
||||||
mgr.update_host_routes(self.config, cache)
|
mgr.update_host_routes(self._config, cache)
|
||||||
|
|
||||||
def update_arp(self):
|
def update_arp(self):
|
||||||
mgr = arp.ARPManager()
|
mgr = arp.ARPManager()
|
||||||
mgr.send_gratuitous_arp_for_floating_ips(
|
mgr.send_gratuitous_arp_for_floating_ips(
|
||||||
self.config,
|
self._config,
|
||||||
self.ip_mgr.generic_to_host
|
self.ip_mgr.generic_to_host
|
||||||
)
|
)
|
||||||
mgr.remove_stale_entries(self.config)
|
mgr.remove_stale_entries(self._config)
|
||||||
|
|
||||||
def get_interfaces(self):
|
def get_interfaces(self):
|
||||||
return self.ip_mgr.get_interfaces()
|
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))
|
rules.append(re.sub('([\s!])(ge\d+([\s:]|$))', r'\1$\2', virt_data))
|
||||||
return '\n'.join(rules)
|
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):
|
class ManagerProxy(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -365,6 +365,7 @@ class Network(ModelBase):
|
|||||||
TYPE_INTERNAL = 'internal'
|
TYPE_INTERNAL = 'internal'
|
||||||
TYPE_ISOLATED = 'isolated'
|
TYPE_ISOLATED = 'isolated'
|
||||||
TYPE_MANAGEMENT = 'management'
|
TYPE_MANAGEMENT = 'management'
|
||||||
|
TYPE_LOADBALANCER = 'loadbalancer'
|
||||||
|
|
||||||
# TODO(mark): add subnet support for Quantum subnet host routes
|
# TODO(mark): add subnet support for Quantum subnet host routes
|
||||||
|
|
||||||
@ -406,7 +407,8 @@ class Network(ModelBase):
|
|||||||
@network_type.setter
|
@network_type.setter
|
||||||
def network_type(self, value):
|
def network_type(self, value):
|
||||||
network_types = (self.TYPE_EXTERNAL, self.TYPE_INTERNAL,
|
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:
|
if value not in network_types:
|
||||||
msg = ('network must be one of %s not (%s).' %
|
msg = ('network must be one of %s not (%s).' %
|
||||||
('|'.join(network_types), value))
|
('|'.join(network_types), value))
|
||||||
@ -451,6 +453,13 @@ class Network(ModelBase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, d):
|
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(
|
return cls(
|
||||||
d['network_id'],
|
d['network_id'],
|
||||||
interface=Interface.from_dict(d['interface']),
|
interface=Interface.from_dict(d['interface']),
|
||||||
@ -463,15 +472,234 @@ class Network(ModelBase):
|
|||||||
subnets=[Subnet.from_dict(s) for s in d.get('subnets', [])])
|
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={}):
|
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')
|
gw = conf_dict.get('default_v4_gateway')
|
||||||
self.default_v4_gateway = netaddr.IPAddress(gw) if gw else None
|
self.default_v4_gateway = netaddr.IPAddress(gw) if gw else None
|
||||||
self.asn = conf_dict.get('asn', DEFAULT_AS)
|
self.asn = conf_dict.get('asn', DEFAULT_AS)
|
||||||
self.neighbor_asn = conf_dict.get('neighbor_asn', self.asn)
|
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
|
self.static_routes = [StaticRoute(*r) for r in
|
||||||
conf_dict.get('static_routes', [])]
|
conf_dict.get('static_routes', [])]
|
||||||
|
|
||||||
@ -491,17 +719,13 @@ class Configuration(ModelBase):
|
|||||||
FloatingIP.from_dict(fip)
|
FloatingIP.from_dict(fip)
|
||||||
for fip in conf_dict.get('floating_ips', [])
|
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)
|
self._attach_floating_ips(self.floating_ips)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
"""Validate anchor rules to ensure that ifaces and tables exist."""
|
"""Validate anchor rules to ensure that ifaces and tables exist."""
|
||||||
errors = []
|
|
||||||
|
|
||||||
interfaces = set(n.interface.ifname for n in self.networks)
|
interfaces = set(n.interface.ifname for n in self.networks)
|
||||||
|
errors = []
|
||||||
for anchor in self.anchors:
|
for anchor in self.anchors:
|
||||||
for rule in anchor.rules:
|
for rule in anchor.rules:
|
||||||
for iface in (rule.interface, rule.destination_interface):
|
for iface in (rule.interface, rule.destination_interface):
|
||||||
@ -561,18 +785,49 @@ class Configuration(ModelBase):
|
|||||||
if addrs:
|
if addrs:
|
||||||
return addrs[0]
|
return addrs[0]
|
||||||
|
|
||||||
@property
|
|
||||||
def interfaces(self):
|
|
||||||
return [n.interface for n in self.networks if n.interface]
|
|
||||||
|
|
||||||
@property
|
class LoadBalancerConfiguration(SystemConfiguration):
|
||||||
def management_address(self):
|
service_name = 'loadbalancer'
|
||||||
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)
|
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:
|
def validate(self):
|
||||||
return addrs[0]
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@ -27,6 +26,9 @@ import netaddr
|
|||||||
|
|
||||||
from akanda.router import models
|
from akanda.router import models
|
||||||
|
|
||||||
|
DEFAULT_ENABLED_SERVICES = ['router']
|
||||||
|
VALID_SERVICES = ['router', 'loadbalancer']
|
||||||
|
|
||||||
|
|
||||||
def execute(args, root_helper=None):
|
def execute(args, root_helper=None):
|
||||||
if root_helper:
|
if root_helper:
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
do_cleanup: True
|
do_cleanup: True
|
||||||
router_appliance: True
|
router_appliance: True
|
||||||
update_kernel: True
|
update_kernel: True
|
||||||
|
enabled_advanced_services: "router"
|
||||||
tasks:
|
tasks:
|
||||||
- include: tasks/debian_backports.yml
|
- include: tasks/debian_backports.yml
|
||||||
when: ansible_distribution == "Debian" and ansible_distribution_release == "wheezy"
|
when: ansible_distribution == "Debian" and ansible_distribution_release == "wheezy"
|
||||||
|
@ -34,6 +34,15 @@
|
|||||||
- metadata
|
- metadata
|
||||||
- akanda-router-api-server
|
- 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
|
- name: update-rc
|
||||||
command: update-rc.d akanda-router-api-server start
|
command: update-rc.d akanda-router-api-server start
|
||||||
|
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
This is the base element for building an Akanda appliance image.
|
This is the base element for building an Akanda appliance image.
|
||||||
|
|
||||||
Ansible is required on the local system.
|
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 -eux
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
|
DIB_AKANDA_ADVANCED_SERVICES=${DIB_AKANDA_ADVANCED_SERVICES:-"router"}
|
||||||
|
|
||||||
APP_SRC_DIR="/tmp/akanda-appliance"
|
APP_SRC_DIR="/tmp/akanda-appliance"
|
||||||
|
|
||||||
[ -d "${APP_SRC_DIR}" ] || exit 0
|
[ -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
|
PATH=/bin:/usr/bin:/sbin:/usr/sbin
|
||||||
DAEMON="/usr/local/bin/gunicorn"
|
DAEMON="/usr/local/bin/gunicorn"
|
||||||
NAME="akanda-router-api-server"
|
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
|
PIDFILE=/var/run/gunicorn.pid
|
||||||
|
|
||||||
test -x $DAEMON || exit 0
|
test -x $DAEMON || exit 0
|
||||||
|
10
setup.cfg
10
setup.cfg
@ -42,8 +42,8 @@ all_files = 1
|
|||||||
build-dir = doc/build
|
build-dir = doc/build
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
|
|
||||||
[nosetests]
|
#[nosetests]
|
||||||
where = test
|
#where = test
|
||||||
verbosity = 2
|
#verbosity = 2
|
||||||
detailed-errors = 1
|
#detailed-errors = 1
|
||||||
cover-package = akanda
|
#cover-package = akanda
|
||||||
|
@ -26,9 +26,15 @@ import flask
|
|||||||
import json
|
import json
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from akanda.router import manager
|
||||||
from akanda.router.api import v1
|
from akanda.router.api import v1
|
||||||
|
|
||||||
|
|
||||||
|
SYSTEM_CONFIG = {
|
||||||
|
'tenant_id': 'foo_tenant_id',
|
||||||
|
'hostname': 'foohostname',
|
||||||
|
}
|
||||||
|
|
||||||
class SystemAPITestCase(unittest.TestCase):
|
class SystemAPITestCase(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
This test case contains the unit tests for the Python server implementation
|
This test case contains the unit tests for the Python server implementation
|
||||||
@ -54,7 +60,7 @@ class SystemAPITestCase(unittest.TestCase):
|
|||||||
'unsupported platform'
|
'unsupported platform'
|
||||||
)
|
)
|
||||||
def test_get_interface(self):
|
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'
|
get_if.return_value = 'ge1'
|
||||||
result = self.test_app.get('/v1/system/interface/ge1')
|
result = self.test_app.get('/v1/system/interface/ge1')
|
||||||
get_if.assert_called_once_with('ge1')
|
get_if.assert_called_once_with('ge1')
|
||||||
@ -68,7 +74,7 @@ class SystemAPITestCase(unittest.TestCase):
|
|||||||
'unsupported platform'
|
'unsupported platform'
|
||||||
)
|
)
|
||||||
def test_get_interfaces(self):
|
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']
|
get_ifs.return_value = ['ge0', 'ge1']
|
||||||
result = self.test_app.get('/v1/system/interfaces')
|
result = self.test_app.get('/v1/system/interfaces')
|
||||||
get_ifs.assert_called_once_with()
|
get_ifs.assert_called_once_with()
|
||||||
@ -81,14 +87,29 @@ class SystemAPITestCase(unittest.TestCase):
|
|||||||
not distutils.spawn.find_executable('ip'),
|
not distutils.spawn.find_executable('ip'),
|
||||||
'unsupported platform'
|
'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')
|
result = self.test_app.get('/v1/system/config')
|
||||||
expected = {
|
expected = {
|
||||||
'configuration': {
|
'configuration': {
|
||||||
'address_book': {},
|
'address_book': {},
|
||||||
|
'anchors': [],
|
||||||
'networks': [],
|
'networks': [],
|
||||||
|
'services': {
|
||||||
|
'loadbalancer': None,
|
||||||
|
'router': None
|
||||||
|
},
|
||||||
'static_routes': [],
|
'static_routes': [],
|
||||||
'anchors': []
|
'system': {
|
||||||
|
'hostname': None,
|
||||||
|
'interfaces': [],
|
||||||
|
'management_address': None,
|
||||||
|
'tenant_id': None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.assertEqual(json.loads(result.data), expected)
|
self.assertEqual(json.loads(result.data), expected)
|
||||||
@ -102,7 +123,7 @@ class SystemAPITestCase(unittest.TestCase):
|
|||||||
self.assertEqual(result.status_code, 415)
|
self.assertEqual(result.status_code, 415)
|
||||||
|
|
||||||
def test_put_configuration_returns_422_for_ValueError(self):
|
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
|
Config.side_effect = ValueError
|
||||||
result = self.test_app.put(
|
result = self.test_app.put(
|
||||||
'/v1/system/config',
|
'/v1/system/config',
|
||||||
@ -112,11 +133,11 @@ class SystemAPITestCase(unittest.TestCase):
|
|||||||
self.assertEqual(result.status_code, 422)
|
self.assertEqual(result.status_code, 422)
|
||||||
|
|
||||||
def test_put_configuration_returns_422_for_errors(self):
|
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']
|
Config.return_value.validate.return_value = ['error1']
|
||||||
result = self.test_app.put(
|
result = self.test_app.put(
|
||||||
'/v1/system/config',
|
'/v1/system/config',
|
||||||
data=json.dumps({'networks': [{}]}), # malformed dict
|
data=json.dumps(SYSTEM_CONFIG),
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
self.assertEqual(result.status_code, 422)
|
self.assertEqual(result.status_code, 422)
|
||||||
@ -129,13 +150,149 @@ class SystemAPITestCase(unittest.TestCase):
|
|||||||
not distutils.spawn.find_executable('ip'),
|
not distutils.spawn.find_executable('ip'),
|
||||||
'unsupported platform'
|
'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(
|
result = self.test_app.put(
|
||||||
'/v1/system/config',
|
'/v1/system/config',
|
||||||
data=json.dumps({}),
|
data=json.dumps({
|
||||||
|
'tenant_id': 'foo_tenant_id',
|
||||||
|
'hostname': 'foo_hostname',
|
||||||
|
}),
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
self.assertTrue(json.loads(result.data))
|
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):
|
def test_send_gratuitous_arp_for_config(self):
|
||||||
config = models.Configuration({
|
config = models.RouterConfiguration({
|
||||||
'networks': [{
|
'networks': [{
|
||||||
'network_id': 'ABC456',
|
'network_id': 'ABC456',
|
||||||
'interface': {
|
'interface': {
|
||||||
|
@ -7,7 +7,7 @@ import netaddr
|
|||||||
from akanda.router import models
|
from akanda.router import models
|
||||||
from akanda.router.drivers import iptables
|
from akanda.router.drivers import iptables
|
||||||
|
|
||||||
CONFIG = models.Configuration({
|
CONFIG = models.RouterConfiguration({
|
||||||
'networks': [{
|
'networks': [{
|
||||||
'network_id': 'ABC123',
|
'network_id': 'ABC123',
|
||||||
'interface': {
|
'interface': {
|
||||||
@ -127,16 +127,16 @@ V6_OUTPUT = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TestIPTablesConfiguration(TestCase):
|
class TestIPTablesRouterConfiguration(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestIPTablesConfiguration, self).setUp()
|
super(TestIPTablesRouterConfiguration, self).setUp()
|
||||||
self.execute = mock.patch('akanda.router.utils.execute').start()
|
self.execute = mock.patch('akanda.router.utils.execute').start()
|
||||||
self.replace = mock.patch('akanda.router.utils.replace_file').start()
|
self.replace = mock.patch('akanda.router.utils.replace_file').start()
|
||||||
self.patches = [self.execute, self.replace]
|
self.patches = [self.execute, self.replace]
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(TestIPTablesConfiguration, self).tearDown()
|
super(TestIPTablesRouterConfiguration, self).tearDown()
|
||||||
for p in self.patches:
|
for p in self.patches:
|
||||||
p.stop()
|
p.stop()
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_update_default_no_inputs(self):
|
def test_update_default_no_inputs(self):
|
||||||
c = models.Configuration({})
|
c = models.RouterConfiguration({})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
set.side_effect = AssertionError(
|
set.side_effect = AssertionError(
|
||||||
'should not try to set default gw'
|
'should not try to set default gw'
|
||||||
@ -169,7 +169,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
self.mgr.update_default_gateway(c)
|
self.mgr.update_default_gateway(c)
|
||||||
|
|
||||||
def test_update_default_v4_from_gateway(self):
|
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:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default_gateway(c)
|
self.mgr.update_default_gateway(c)
|
||||||
set.assert_called_once_with(c.default_v4_gateway, None)
|
set.assert_called_once_with(c.default_v4_gateway, None)
|
||||||
@ -189,7 +189,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
subnets=[subnet],
|
subnets=[subnet],
|
||||||
network_type='external',
|
network_type='external',
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default_gateway(c)
|
self.mgr.update_default_gateway(c)
|
||||||
net = c.networks[0]
|
net = c.networks[0]
|
||||||
@ -217,7 +217,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
subnets=[subnet, subnet2],
|
subnets=[subnet, subnet2],
|
||||||
network_type='external',
|
network_type='external',
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default_gateway(c)
|
self.mgr.update_default_gateway(c)
|
||||||
net = c.networks[0]
|
net = c.networks[0]
|
||||||
@ -239,7 +239,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
subnets=[subnet],
|
subnets=[subnet],
|
||||||
network_type='external',
|
network_type='external',
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default_gateway(c)
|
self.mgr.update_default_gateway(c)
|
||||||
net = c.networks[0]
|
net = c.networks[0]
|
||||||
@ -267,7 +267,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
subnets=[subnet, subnet2],
|
subnets=[subnet, subnet2],
|
||||||
network_type='external',
|
network_type='external',
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default_gateway(c)
|
self.mgr.update_default_gateway(c)
|
||||||
net = c.networks[0]
|
net = c.networks[0]
|
||||||
@ -292,7 +292,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
interface=dict(ifname='ge0', addresses=['fe80::2']),
|
interface=dict(ifname='ge0', addresses=['fe80::2']),
|
||||||
subnets=[subnet]
|
subnets=[subnet]
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
|
|
||||||
cache = make_region().configure('dogpile.cache.memory')
|
cache = make_region().configure('dogpile.cache.memory')
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
@ -319,7 +319,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
# Empty the host_routes list
|
# Empty the host_routes list
|
||||||
sudo.reset_mock()
|
sudo.reset_mock()
|
||||||
subnet['host_routes'] = []
|
subnet['host_routes'] = []
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
sudo.assert_called_once_with(
|
sudo.assert_called_once_with(
|
||||||
'-4', 'route', 'del', '192.240.128.0/20', 'via',
|
'-4', 'route', 'del', '192.240.128.0/20', 'via',
|
||||||
@ -336,7 +336,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
'destination': '192.220.128.0/20',
|
'destination': '192.220.128.0/20',
|
||||||
'nexthop': '192.168.89.3'
|
'nexthop': '192.168.89.3'
|
||||||
}]
|
}]
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
self.assertEqual(sudo.call_args_list, [
|
self.assertEqual(sudo.call_args_list, [
|
||||||
mock.call('-4', 'route', 'add', '192.240.128.0/20',
|
mock.call('-4', 'route', 'add', '192.240.128.0/20',
|
||||||
@ -354,7 +354,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
'destination': '192.185.128.0/20',
|
'destination': '192.185.128.0/20',
|
||||||
'nexthop': '192.168.89.4'
|
'nexthop': '192.168.89.4'
|
||||||
}]
|
}]
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
self.assertEqual(sudo.call_args_list, [
|
self.assertEqual(sudo.call_args_list, [
|
||||||
mock.call('-4', 'route', 'del', '192.220.128.0/20',
|
mock.call('-4', 'route', 'del', '192.220.128.0/20',
|
||||||
@ -376,7 +376,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
'nexthop': '192.168.90.1'
|
'nexthop': '192.168.90.1'
|
||||||
}]
|
}]
|
||||||
))
|
))
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
self.assertEqual(sudo.call_args_list, [
|
self.assertEqual(sudo.call_args_list, [
|
||||||
mock.call('-4', 'route', 'add', '192.240.128.0/20',
|
mock.call('-4', 'route', 'add', '192.240.128.0/20',
|
||||||
@ -388,7 +388,7 @@ class RouteTest(unittest2.TestCase):
|
|||||||
sudo.reset_mock()
|
sudo.reset_mock()
|
||||||
network['subnets'][0]['host_routes'] = []
|
network['subnets'][0]['host_routes'] = []
|
||||||
network['subnets'][1]['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.mgr.update_host_routes(c, cache)
|
||||||
self.assertEqual(sudo.call_args_list, [
|
self.assertEqual(sudo.call_args_list, [
|
||||||
mock.call('-4', 'route', 'del', '192.185.128.0/20',
|
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']),
|
interface=dict(ifname='ge0', addresses=['fe80::2']),
|
||||||
subnets=[subnet]
|
subnets=[subnet]
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.RouterConfiguration({'networks': [network]})
|
||||||
|
|
||||||
cache = make_region().configure('dogpile.cache.memory')
|
cache = make_region().configure('dogpile.cache.memory')
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
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 textwrap
|
||||||
|
|
||||||
|
import copy
|
||||||
import mock
|
import mock
|
||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
from unittest2 import TestCase
|
from unittest2 import TestCase
|
||||||
|
|
||||||
from akanda.router import models
|
from akanda.router import models
|
||||||
|
from test.unit import fakes
|
||||||
|
|
||||||
|
|
||||||
class InterfaceModelTestCase(TestCase):
|
class InterfaceModelTestCase(TestCase):
|
||||||
@ -360,7 +363,7 @@ class NetworkTestCase(TestCase):
|
|||||||
n = models.Network('id', None, v6_conf_service='invalid')
|
n = models.Network('id', None, v6_conf_service='invalid')
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationTestCase(TestCase):
|
class RouterConfigurationTestCase(TestCase):
|
||||||
def test_init_only_networks(self):
|
def test_init_only_networks(self):
|
||||||
subnet = dict(
|
subnet = dict(
|
||||||
cidr='192.168.1.0/24',
|
cidr='192.168.1.0/24',
|
||||||
@ -375,28 +378,28 @@ class ConfigurationTestCase(TestCase):
|
|||||||
allocations=[],
|
allocations=[],
|
||||||
subnets=[subnet])
|
subnets=[subnet])
|
||||||
|
|
||||||
c = models.Configuration(dict(networks=[network]))
|
c = models.RouterConfiguration(dict(networks=[network]))
|
||||||
self.assertEqual(len(c.networks), 1)
|
self.assertEqual(len(c.networks), 1)
|
||||||
self.assertEqual(c.networks[0],
|
self.assertEqual(c.networks[0],
|
||||||
models.Network.from_dict(network))
|
models.Network.from_dict(network))
|
||||||
|
|
||||||
def test_init_tenant_id(self):
|
def test_init_tenant_id(self):
|
||||||
c = models.Configuration({'tenant_id': 'abc123'})
|
c = models.RouterConfiguration({'tenant_id': 'abc123'})
|
||||||
self.assertEqual(c.tenant_id, 'abc123')
|
self.assertEqual(c.tenant_id, 'abc123')
|
||||||
|
|
||||||
def test_no_default_v4_gateway(self):
|
def test_no_default_v4_gateway(self):
|
||||||
c = models.Configuration({})
|
c = models.RouterConfiguration({})
|
||||||
self.assertIsNone(c.default_v4_gateway)
|
self.assertIsNone(c.default_v4_gateway)
|
||||||
|
|
||||||
def test_valid_default_v4_gateway(self):
|
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(c.default_v4_gateway.version, 4)
|
||||||
self.assertEqual(str(c.default_v4_gateway), '172.16.77.1')
|
self.assertEqual(str(c.default_v4_gateway), '172.16.77.1')
|
||||||
|
|
||||||
def test_init_only_static_routes(self):
|
def test_init_only_static_routes(self):
|
||||||
routes = [('0.0.0.0/0', '192.168.1.1'),
|
routes = [('0.0.0.0/0', '192.168.1.1'),
|
||||||
('172.16.77.0/16', '192.168.1.254')]
|
('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(len(c.static_routes), 2)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -406,7 +409,7 @@ class ConfigurationTestCase(TestCase):
|
|||||||
def test_init_address_book(self):
|
def test_init_address_book(self):
|
||||||
ab = {"webservers": ["192.168.57.101/32", "192.168.57.230/32"]}
|
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(
|
self.assertEqual(
|
||||||
c.address_book.get('webservers'),
|
c.address_book.get('webservers'),
|
||||||
models.AddressBookEntry('webservers', ab['webservers']))
|
models.AddressBookEntry('webservers', ab['webservers']))
|
||||||
@ -414,7 +417,7 @@ class ConfigurationTestCase(TestCase):
|
|||||||
def test_init_label(self):
|
def test_init_label(self):
|
||||||
labels = {"external": ["192.168.57.0/24"]}
|
labels = {"external": ["192.168.57.0/24"]}
|
||||||
|
|
||||||
c = models.Configuration(dict(networks=[], labels=labels))
|
c = models.RouterConfiguration(dict(networks=[], labels=labels))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
c.labels[0],
|
c.labels[0],
|
||||||
models.Label('external', ['192.168.57.0/24']))
|
models.Label('external', ['192.168.57.0/24']))
|
||||||
@ -424,30 +427,30 @@ class ConfigurationTestCase(TestCase):
|
|||||||
name='theanchor',
|
name='theanchor',
|
||||||
rules=[])
|
rules=[])
|
||||||
|
|
||||||
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), 1)
|
||||||
|
|
||||||
def test_init_anchor(self):
|
def test_init_anchor(self):
|
||||||
test_rule = dict(action='block', source='192.168.1.1/32')
|
test_rule = dict(action='block', source='192.168.1.1/32')
|
||||||
anchor_dict = dict(name='theanchor', rules=[test_rule])
|
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), 1)
|
||||||
self.assertEqual(len(c.anchors[0].rules), 1)
|
self.assertEqual(len(c.anchors[0].rules), 1)
|
||||||
self.assertEqual(c.anchors[0].rules[0].action, 'block')
|
self.assertEqual(c.anchors[0].rules[0].action, 'block')
|
||||||
|
|
||||||
def test_asn_default(self):
|
def test_asn_default(self):
|
||||||
c = models.Configuration({'networks': []})
|
c = models.RouterConfiguration({'networks': []})
|
||||||
self.assertEqual(c.asn, 64512)
|
self.assertEqual(c.asn, 64512)
|
||||||
self.assertEqual(c.neighbor_asn, 64512)
|
self.assertEqual(c.neighbor_asn, 64512)
|
||||||
|
|
||||||
def test_asn_provided_with_neighbor_fallback(self):
|
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.asn, 12345)
|
||||||
self.assertEqual(c.neighbor_asn, 12345)
|
self.assertEqual(c.neighbor_asn, 12345)
|
||||||
|
|
||||||
def test_asn_provided_with_neighbor_different(self):
|
def test_asn_provided_with_neighbor_different(self):
|
||||||
c = models.Configuration(
|
c = models.RouterConfiguration(
|
||||||
{'networks': [], 'asn': 12, 'neighbor_asn': 34}
|
{'networks': [], 'asn': 12, 'neighbor_asn': 34}
|
||||||
)
|
)
|
||||||
self.assertEqual(c.asn, 12)
|
self.assertEqual(c.asn, 12)
|
||||||
@ -463,7 +466,7 @@ class ConfigurationTestCase(TestCase):
|
|||||||
ab = {"webservers": ["192.168.57.101/32", "192.168.57.230/32"]}
|
ab = {"webservers": ["192.168.57.101/32", "192.168.57.230/32"]}
|
||||||
anchor_dict = dict(name='theanchor', rules=[rule_dict])
|
anchor_dict = dict(name='theanchor', rules=[rule_dict])
|
||||||
|
|
||||||
c = models.Configuration(
|
c = models.RouterConfiguration(
|
||||||
dict(networks=[network], anchors=[anchor_dict], address_book=ab))
|
dict(networks=[network], anchors=[anchor_dict], address_book=ab))
|
||||||
|
|
||||||
errors = c.validate()
|
errors = c.validate()
|
||||||
@ -517,10 +520,163 @@ class ConfigurationTestCase(TestCase):
|
|||||||
self.assertEqual(len(errors), 1)
|
self.assertEqual(len(errors), 1)
|
||||||
|
|
||||||
def test_to_dict(self):
|
def test_to_dict(self):
|
||||||
c = models.Configuration({'networks': []})
|
c = models.RouterConfiguration({'networks': []})
|
||||||
expected = dict(networks=[],
|
expected = dict(networks=[],
|
||||||
address_book={},
|
address_book={},
|
||||||
static_routes=[],
|
static_routes=[],
|
||||||
anchors=[])
|
anchors=[])
|
||||||
|
|
||||||
self.assertEqual(c.to_dict(), expected)
|
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'])
|
utils.execute(['/bin/ls', '/no-such-directory'])
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
self.assertIn('cannot access', str(e))
|
self.assertIn('cannot access', str(e))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user