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
This commit is contained in:
Adam Gandelman 2015-12-09 16:45:36 -08:00
parent 01c4e9052d
commit 44610ac1cd
9 changed files with 164 additions and 28 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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