From 44610ac1cd3dca771307aba4bd3c24ed01a4acc1 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Wed, 9 Dec 2015 16:45:36 -0800 Subject: [PATCH] Accept new orchestrator config bucket This adds the ability for the orchestrator to add a new bucket into the config dict keyed 'orchestrator', which can be used to notify the appliance of the specifics about the orchestrator currently managing it. Initially this will be used to inform the appliance where the metadata service is running, but in the future could be extended to do more, specifically around coordination. Change-Id: I4a4009f12ce025d3dc6577d27f877aeb8427b963 Partial-bug: #1524068 --- astara_router/defaults.py | 7 ++- astara_router/drivers/iptables.py | 3 +- astara_router/drivers/metadata.py | 24 ++++++--- astara_router/manager.py | 2 +- astara_router/metadata_proxy.py | 33 +++++++----- astara_router/models.py | 8 +++ test/unit/drivers/test_metadata.py | 84 ++++++++++++++++++++++++++++++ test/unit/test_metadata_proxy.py | 12 +++-- test/unit/test_models.py | 19 ++++++- 9 files changed, 164 insertions(+), 28 deletions(-) create mode 100644 test/unit/drivers/test_metadata.py diff --git a/astara_router/defaults.py b/astara_router/defaults.py index c535fd3..2ad2978 100644 --- a/astara_router/defaults.py +++ b/astara_router/defaults.py @@ -39,8 +39,11 @@ METADATA_DEST_ADDRESS = '169.254.169.254' # port for internal network metadata proxy BASE_METADATA_PORT = 9600 -# port for rug metadata service -RUG_META_PORT = 9697 +# default address of orchestrator metadata service +ORCHESTRATOR_METADATA_ADDRESS = 'fdca:3ba5:a17a:acda::1' + +# default port for orchestrator metadata service +ORCHESTRATOR_METADATA_PORT = 9697 def internal_metadata_port(ifname): diff --git a/astara_router/drivers/iptables.py b/astara_router/drivers/iptables.py index ffabbef..f441416 100644 --- a/astara_router/drivers/iptables.py +++ b/astara_router/drivers/iptables.py @@ -196,7 +196,8 @@ class IPTablesManager(base.Manager): # Open SSH, the HTTP API (5000) and the Nova metadata proxy (9697) for port in ( - defaults.SSH, defaults.API_SERVICE, defaults.RUG_META_PORT + defaults.SSH, defaults.API_SERVICE, + defaults.ORCHESTRATOR_METADATA_PORT ): rules.append(Rule( '-A INPUT -i %s -p tcp -m tcp --dport %s -j ACCEPT' % ( diff --git a/astara_router/drivers/metadata.py b/astara_router/drivers/metadata.py index cb298df..b8bf655 100644 --- a/astara_router/drivers/metadata.py +++ b/astara_router/drivers/metadata.py @@ -41,7 +41,7 @@ class MetadataManager(base.Manager): """ super(MetadataManager, self).__init__(root_helper) - def networks_have_changed(self, config): + def should_restart(self, config): """ This function determines if the networks have changed since was initialized. @@ -59,8 +59,14 @@ class MetadataManager(base.Manager): except: # If we can't read the file, assume networks were added/removed return True - config_dict.pop('tenant_id') - return net_ids != set(config_dict.keys()) + + orchestrator_addr = config_dict.get('orchestrator_metadata_address') + orchestrator_port = config_dict.get('orchestrator_metadata_port') + + return ( + net_ids != set(config_dict.get('networks', {}).keys()) or + orchestrator_addr != config.metadata_address or + orchestrator_port != config.metadata_port) def save_config(self, config): """ @@ -108,7 +114,7 @@ def build_config(config): :param config: :rtype: astara_router.models.Configuration """ - config_data = {} + network_data = {} for net in config.networks: if not net.is_tenant_network: @@ -119,10 +125,14 @@ def build_config(config): for ip in a.ip_addresses: ip_instance_map[ip] = a.device_id - config_data[net.id] = { + network_data[net.id] = { 'listen_port': internal_metadata_port(net.interface.ifname), 'ip_instance_map': ip_instance_map } - config_data['tenant_id'] = config.tenant_id - return config_data + return { + 'tenant_id': config.tenant_id, + 'orchestrator_metadata_address': config.metadata_address, + 'orchestrator_metadata_port': config.metadata_port, + 'networks': network_data, + } diff --git a/astara_router/manager.py b/astara_router/manager.py index 786fe94..ade202b 100644 --- a/astara_router/manager.py +++ b/astara_router/manager.py @@ -97,7 +97,7 @@ class RouterManager(ServiceManagerBase): def update_metadata(self): mgr = metadata.MetadataManager() - should_restart = mgr.networks_have_changed(self._config) + should_restart = mgr.should_restart(self._config) mgr.save_config(self._config) if should_restart: mgr.restart() diff --git a/astara_router/metadata_proxy.py b/astara_router/metadata_proxy.py index 7dc1e73..b70f958 100644 --- a/astara_router/metadata_proxy.py +++ b/astara_router/metadata_proxy.py @@ -31,9 +31,6 @@ import requests from werkzeug import exceptions from werkzeug import wrappers -from astara_router import defaults -from astara_router.drivers import ip - LOG = logging.getLogger(__name__) @@ -48,8 +45,18 @@ class NetworkMetadataProxyHandler(object): self.network_id = network_id self.config_file = config_file self.config_mtime = 0 + self._config_dict = {} self._ip_instance_map = {} + @property + def config_dict(self): + config_mtime = os.stat(self.config_file).st_mtime + if config_mtime > self.config_mtime: + LOG.debug("Metadata proxy configuration has changed; reloading...") + self._config_dict = json.load(open(self.config_file)) + self.config_mtime = config_mtime + return self._config_dict + def __call__(self, environ, start_response): request = wrappers.Request(environ) @@ -68,16 +75,16 @@ class NetworkMetadataProxyHandler(object): @property def ip_instance_map(self): - config_mtime = os.stat(self.config_file).st_mtime - if config_mtime > self.config_mtime: - LOG.debug("Metadata proxy configuration has changed; reloading...") - config_dict = json.load(open(self.config_file)) - self._ip_instance_map = config_dict[ - self.network_id - ]['ip_instance_map'] - self.config_mtime = config_mtime + self._ip_instance_map = self.config_dict['networks'][ + self.network_id]['ip_instance_map'] return self._ip_instance_map + @property + def orchestrator_loc(self): + addr = self.config_dict['orchestrator_metadata_address'] + port = self.config_dict['orchestrator_metadata_port'] + return '[%s]:%d' % (addr, port) + def _proxy_request(self, remote_address, path_info, query_string): headers = { 'X-Forwarded-For': remote_address, @@ -88,7 +95,7 @@ class NetworkMetadataProxyHandler(object): url = urlparse.urlunsplit(( 'http', - '[%s]:%d' % (ip.get_rug_address(), defaults.RUG_META_PORT), + self.orchestrator_loc, path_info, query_string, '')) @@ -172,7 +179,7 @@ def main(): pool = eventlet.GreenPool(1000) tenant_id = config_dict.pop('tenant_id') - for network_id, config in config_dict.items(): + for network_id, config in config_dict['networks'].items(): app = NetworkMetadataProxyHandler(tenant_id, network_id, args.config_file) diff --git a/astara_router/models.py b/astara_router/models.py index dbe5319..8140129 100644 --- a/astara_router/models.py +++ b/astara_router/models.py @@ -20,6 +20,8 @@ import re import netaddr +from astara_router import defaults + GROUP_NAME_LENGTH = 15 DEFAULT_AS = 64512 @@ -715,6 +717,12 @@ class RouterConfiguration(SystemConfiguration): Label(name, cidr) for name, cidr in conf_dict.get('labels', {}).iteritems()] + orchestrator_conf = conf_dict.get('orchestrator', {}) + self.metadata_address = orchestrator_conf.get( + 'address', defaults.ORCHESTRATOR_METADATA_ADDRESS) + self.metadata_port = orchestrator_conf.get( + 'metadata_port', defaults.ORCHESTRATOR_METADATA_PORT) + self.floating_ips = [ FloatingIP.from_dict(fip) for fip in conf_dict.get('floating_ips', []) diff --git a/test/unit/drivers/test_metadata.py b/test/unit/drivers/test_metadata.py new file mode 100644 index 0000000..56490cb --- /dev/null +++ b/test/unit/drivers/test_metadata.py @@ -0,0 +1,84 @@ +# Copyright 2014 DreamHost, LLC +# +# Author: DreamHost, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import __builtin__ + +import json +import mock + +from unittest2 import TestCase + +from astara_router.drivers import metadata + +CONFIG = mock.Mock() +CONFIG.hostname = 'astara' +CONFIG.management_address = 'fdca:3ba5:a17a:acda:f816:3eff:fe66:33b6' + + +class HostnameTestCase(TestCase): + """ + """ + def setUp(self): + self.mgr = metadata.MetadataManager() + self.config_dict = { + 'networks': {'tenant_net_id': [], 'public_net_id': []}, + 'orchestrator_metadata_address': '10.0.0.1', + 'orchestrator_metadata_port': '5000', + } + + tenant_net = mock.Mock( + is_tenant_network=mock.Mock(return_value=True), + id='tenant_net_id', + ) + public_net = mock.Mock( + is_tenant_network=mock.Mock(return_value=False), + id='public_net_id', + ) + + self.config = mock.Mock() + self.config.networks = [tenant_net, public_net] + self.config.metadata_address = '10.0.0.1' + self.config.metadata_port = '5000' + + def _test_should_restart(self, exp_result): + config_json = json.dumps(self.config_dict) + with mock.patch.object( + __builtin__, 'open', mock.mock_open(read_data=config_json)): + self.assertEqual( + self.mgr.should_restart(self.config), exp_result) + + def test_should_restart_false(self): + self._test_should_restart(False) + + def test_should_restart_true_networks_change(self): + self.config_dict['networks'] = { + 'foo_net_id': [], 'public_net_id': []} + self._test_should_restart(True) + + def test_should_restart_true_metadata_addr_change(self): + self.config_dict['orchestrator_metadata_address'] = '11.1.1.1' + self._test_should_restart(True) + + def test_should_restart_true_metadata_port_change(self): + self.config_dict['orchestrator_metadata_port'] = '6000' + self._test_should_restart(True) + + def test_should_restart_true_config_read_err(self): + with mock.patch.object( + __builtin__, 'open', mock.mock_open()) as _o: + _o.side_effect = IOError() + self.assertEqual( + self.mgr.should_restart(self.config), True) diff --git a/test/unit/test_metadata_proxy.py b/test/unit/test_metadata_proxy.py index ca76f92..770233b 100644 --- a/test/unit/test_metadata_proxy.py +++ b/test/unit/test_metadata_proxy.py @@ -11,8 +11,14 @@ from astara_router import metadata_proxy config = json.dumps({ "tenant_id": "ABC123", - "net1": {"listen_port": 9602, 'ip_instance_map': {'10.10.10.2': 'VM1'}}, - "net2": {"listen_port": 9603, 'ip_instance_map': {'10.10.10.2': 'VM2'}}, + "orchestrator_metadata_address": "192.168.25.30", + "orchestrator_metadata_port": 9697, + "networks": { + "net1": { + "listen_port": 9602, 'ip_instance_map': {'10.10.10.2': 'VM1'}}, + "net2": { + "listen_port": 9603, 'ip_instance_map': {'10.10.10.2': 'VM2'}}, + } }) class TestMetadataProxy(unittest.TestCase): @@ -66,7 +72,7 @@ class TestMetadataProxy(unittest.TestCase): get.return_value.status_code = 200 wsgi._proxy_request('10.10.10.2', '/', '') get.assert_called_once_with( - 'http://[fdca:3ba5:a17a:acda::1]:9697/', + 'http://[192.168.25.30]:9697/', headers={ 'X-Quantum-Network-ID': 'net1', 'X-Forwarded-For': '10.10.10.2', diff --git a/test/unit/test_models.py b/test/unit/test_models.py index 473ff56..25a33b9 100644 --- a/test/unit/test_models.py +++ b/test/unit/test_models.py @@ -23,7 +23,7 @@ import netaddr from unittest2 import TestCase -from astara_router import models +from astara_router import defaults, models from test.unit import fakes @@ -439,6 +439,23 @@ class RouterConfigurationTestCase(TestCase): self.assertEqual(len(c.anchors[0].rules), 1) self.assertEqual(c.anchors[0].rules[0].action, 'block') + def test_init_metadata_config(self): + c = models.RouterConfiguration({ + 'orchestrator': { + 'address': '192.168.25.30', + 'metadata_port': 9697, + } + }) + self.assertEqual(c.metadata_address, '192.168.25.30') + self.assertEqual(c.metadata_port, 9697) + + def test_init_metadata_config_missing(self): + c = models.RouterConfiguration({}) + self.assertEqual( + c.metadata_address, defaults.ORCHESTRATOR_METADATA_ADDRESS) + self.assertEqual( + c.metadata_port, defaults.ORCHESTRATOR_METADATA_PORT) + def test_asn_default(self): c = models.RouterConfiguration({'networks': []}) self.assertEqual(c.asn, 64512)