Added support of multi-region environment

Octavia does not support keystone regions and can choose an incorrect
endpoint url when multiple regions provides same services.

Change-Id: I5ea9de380419592920555a2c2fe7ac6f6935e700
Closes-Bug: #1487359
This commit is contained in:
Bertrand Lallau 2015-08-25 11:59:21 +02:00 committed by Bertrand Lallau
parent cdd03b28eb
commit 0ab265ff41
13 changed files with 195 additions and 106 deletions

View File

@ -11,8 +11,10 @@
#
# octavia_plugins = hot_plug_plugin
# nova_region_name =
# Region in Identity service catalog to use for communication with the OpenStack services.
#
# os_region_name =
# Hostname to be used by the host machine for services running on it.
# The default value is the hostname of the host machine.
# host =
@ -143,4 +145,4 @@
# Cleanup interval for Deleted amphora
# cleanup_interval = 30
# Amphora expiry age in seconds. Default is 1 week
# amphora_expiry_age = 604800
# amphora_expiry_age = 604800

69
octavia/common/clients.py Normal file
View File

@ -0,0 +1,69 @@
# 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 neutronclient.neutron import client as neutron_client
from novaclient import client as nova_client
from oslo_log import log as logging
from oslo_utils import excutils
from octavia.common import keystone
from octavia.i18n import _LE
LOG = logging.getLogger(__name__)
NEUTRON_VERSION = '2.0'
NOVA_VERSION = '2'
class NovaAuth(object):
nova_client = None
@classmethod
def get_nova_client(cls, region):
"""Create nova client object.
:param region: The region of the service
:return: a Nova Client object.
:raises Exception: if the client cannot be created
"""
if not cls.nova_client:
try:
cls.nova_client = nova_client.Client(
NOVA_VERSION, session=keystone.get_session(),
region_name=region
)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Error creating Nova client."))
return cls.nova_client
class NeutronAuth(object):
neutron_client = None
@classmethod
def get_neutron_client(cls, region):
"""Create neutron client object.
:param region: The region of the service
:return: a Neutron Client object.
:raises Exception: if the client cannot be created
"""
if not cls.neutron_client:
try:
cls.neutron_client = neutron_client.Client(
NEUTRON_VERSION, session=keystone.get_session(),
region_name=region
)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Error creating Neutron client."))
return cls.neutron_client

View File

@ -72,9 +72,9 @@ core_opts = [
help=_('CA file for novaclient to verify server certificates')),
cfg.BoolOpt('nova_api_insecure', default=False,
help=_("If True, ignore any SSL validation issues")),
cfg.StrOpt('nova_region_name',
help=_('Name of nova region to use. Useful if keystone manages'
' more than one region.')),
cfg.StrOpt('os_region_name',
help=_('Region in Identity service catalog to use for '
'communication with the OpenStack services.')),
cfg.StrOpt('octavia_plugins',
default='hot_plug_plugin',
help=_('Name of the controller plugin to use'))

View File

@ -12,15 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
from novaclient import client as nova_client
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from octavia.common import clients
from octavia.common import constants
from octavia.common import data_models as models
from octavia.common import exceptions
from octavia.common import keystone
from octavia.compute import compute_base
from octavia.i18n import _LE
@ -37,7 +35,7 @@ class VirtualMachineManager(compute_base.ComputeBase):
def __init__(self, region=None):
super(VirtualMachineManager, self).__init__()
# Must initialize nova api
self._nova_client = NovaAuth.get_nova_client(region)
self._nova_client = clients.NovaAuth.get_nova_client(region)
self.manager = self._nova_client.servers
def build(self, name="amphora_name", amphora_flavor=None, image_id=None,
@ -144,26 +142,3 @@ class VirtualMachineManager(compute_base.ComputeBase):
lb_network_ip=lb_network_ip
)
return response
class NovaAuth(object):
_nova_client = None
@classmethod
def get_nova_client(cls, region):
"""Create nova client object.
:param region: The region of the service
:return: a Nova Client object.
:raises Exception: if the client cannot be created
"""
if not cls._nova_client:
try:
cls._nova_client = nova_client.Client(
constants.NOVA_2, session=keystone.get_session(),
region_name=region
)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception(_LE("Error creating Nova client."))
return cls._nova_client

View File

@ -39,7 +39,7 @@ class BaseComputeTask(task.Task):
namespace='octavia.compute.drivers',
name=CONF.controller_worker.compute_driver,
invoke_on_load=True,
invoke_kwds={'region': CONF.nova_region_name}
invoke_kwds={'region': CONF.os_region_name}
).driver

View File

@ -40,7 +40,8 @@ class BaseNetworkTask(task.Task):
self.network_driver = stevedore_driver.DriverManager(
namespace='octavia.network.drivers',
name=CONF.controller_worker.network_driver,
invoke_on_load=True
invoke_on_load=True,
invoke_kwds={'region': CONF.os_region_name}
).driver
@ -341,4 +342,4 @@ class GetAmphoraeNetworkConfigs(BaseNetworkTask):
ha_subnet=ha_subnet,
ha_port=ha_port
)
return amp_net_configs
return amp_net_configs

View File

@ -13,11 +13,10 @@
# under the License.
from neutronclient.common import exceptions as neutron_client_exceptions
from neutronclient.neutron import client as neutron_client
from oslo_log import log as logging
from octavia.common import clients
from octavia.common import data_models
from octavia.common import keystone
from octavia.i18n import _LE, _LI
from octavia.network import base
from octavia.network import data_models as network_models
@ -25,16 +24,14 @@ from octavia.network.drivers.neutron import utils
LOG = logging.getLogger(__name__)
NEUTRON_VERSION = '2.0'
SEC_GRP_EXT_ALIAS = 'security-group'
class BaseNeutronDriver(base.AbstractNetworkDriver):
def __init__(self):
def __init__(self, region=None):
self.sec_grp_enabled = True
self.neutron_client = neutron_client.Client(
NEUTRON_VERSION, session=keystone.get_session())
self.neutron_client = clients.NeutronAuth.get_neutron_client(region)
extensions = self.neutron_client.list_extensions()
self._extensions = extensions.get('extensions')
self._check_sec_grps()
@ -172,4 +169,4 @@ class BaseNeutronDriver(base.AbstractNetworkDriver):
'(port id: {port_id}.').format(
port_id=port_id)
LOG.exception(message)
raise base.NetworkException(message)
raise base.NetworkException(message)

View File

@ -94,7 +94,7 @@ class NoopManager(object):
class NoopNetworkDriver(driver_base.AbstractNetworkDriver):
def __init__(self):
def __init__(self, region=None):
super(NoopNetworkDriver, self).__init__()
self.driver = NoopManager()
@ -130,4 +130,4 @@ class NoopNetworkDriver(driver_base.AbstractNetworkDriver):
self.driver.get_subnet(subnet_id)
def get_port(self, port_id):
self.driver.get_port(port_id)
self.driver.get_port(port_id)

View File

@ -15,7 +15,7 @@
import mock
import testtools
from octavia.compute.drivers import nova_driver
from octavia.common import clients
class TestCase(testtools.TestCase):
@ -26,4 +26,5 @@ class TestCase(testtools.TestCase):
self.addCleanup(self.clean_caches)
def clean_caches(self):
nova_driver.NovaAuth._nova_client = None
clients.NovaAuth.nova_client = None
clients.NeutronAuth.neutron_client = None

View File

@ -0,0 +1,96 @@
# 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 mock
import neutronclient.v2_0
import novaclient.v2
from oslo_config import cfg
from octavia.common import clients
from octavia.common import keystone
from octavia.tests.unit import base
CONF = cfg.CONF
class TestNovaAuth(base.TestCase):
def setUp(self):
CONF.set_override(group='keystone_authtoken', name='auth_version',
override='2')
# Reset the session and client
clients.NovaAuth.nova_client = None
keystone._SESSION = None
super(TestNovaAuth, self).setUp()
def test_get_nova_client(self):
# There should be no existing client
self.assertIsNone(
clients.NovaAuth.nova_client
)
# Mock out the keystone session and get the client
keystone._SESSION = mock.MagicMock()
bc1 = clients.NovaAuth.get_nova_client(region=None)
# Our returned client should also be the saved client
self.assertIsInstance(
clients.NovaAuth.nova_client,
novaclient.v2.client.Client
)
self.assertIs(
clients.NovaAuth.nova_client,
bc1
)
# Getting the session again should return the same object
bc2 = clients.NovaAuth.get_nova_client(
region="test-region")
self.assertIs(bc1, bc2)
class TestNeutronAuth(base.TestCase):
def setUp(self):
CONF.set_override(group='keystone_authtoken', name='auth_version',
override='2')
# Reset the session and client
clients.NeutronAuth.neutron_client = None
keystone._SESSION = None
super(TestNeutronAuth, self).setUp()
def test_get_neutron_client(self):
# There should be no existing client
self.assertIsNone(
clients.NeutronAuth.neutron_client
)
# Mock out the keystone session and get the client
keystone._SESSION = mock.MagicMock()
bc1 = clients.NeutronAuth.get_neutron_client(region=None)
# Our returned client should also be the saved client
self.assertIsInstance(
clients.NeutronAuth.neutron_client,
neutronclient.v2_0.client.Client
)
self.assertIs(
clients.NeutronAuth.neutron_client,
bc1
)
# Getting the session again should return the same object
bc2 = clients.NeutronAuth.get_neutron_client(
region="test-region")
self.assertIs(bc1, bc2)

View File

@ -12,8 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneclient import session
import novaclient.v2
from oslo_config import cfg
from oslo_utils import uuidutils
import six
@ -21,7 +19,6 @@ import six
from octavia.common import constants
from octavia.common import data_models as models
from octavia.common import exceptions
from octavia.common import keystone
import octavia.compute.drivers.nova_driver as nova_common
import octavia.tests.unit.base as base
@ -123,55 +120,3 @@ class TestNovaClient(base.TestCase):
self.manager.manager.get.side_effect = Exception
self.assertRaises(exceptions.ComputeGetException,
self.manager.get_amphora, self.amphora.id)
class TestNovaAuth(base.TestCase):
def setUp(self):
CONF.set_override(group='keystone_authtoken', name='auth_version',
override='2')
# Reset the session and client
nova_common.NovaAuth._nova_client = None
keystone._SESSION = None
super(TestNovaAuth, self).setUp()
def test_get_keystone_client(self):
# There should be no existing session
self.assertIsNone(keystone._SESSION)
# Get us a session
ks1 = keystone.get_session()
# Our returned session should also be the saved session
self.assertIsInstance(keystone._SESSION, session.Session)
self.assertIs(keystone._SESSION, ks1)
# Getting the session again should return the same object
ks2 = keystone.get_session()
self.assertIs(ks1, ks2)
def test_get_nova_client(self):
# There should be no existing client
self.assertIsNone(
nova_common.NovaAuth._nova_client
)
# Mock out the keystone session and get the client
keystone._SESSION = mock.MagicMock()
bc1 = nova_common.NovaAuth.get_nova_client(region=None)
# Our returned client should also be the saved client
self.assertIsInstance(
nova_common.NovaAuth._nova_client,
novaclient.v2.client.Client
)
self.assertIs(
nova_common.NovaAuth._nova_client,
bc1
)
# Getting the session again should return the same object
bc2 = nova_common.NovaAuth.get_nova_client(
region="test-region")
self.assertIs(bc1, bc2)

View File

@ -18,6 +18,7 @@ import mock
from neutronclient.common import exceptions as neutron_exceptions
from novaclient.client import exceptions as nova_exceptions
from octavia.common import clients
from octavia.common import constants
from octavia.common import data_models
from octavia.network import base as network_base
@ -35,10 +36,11 @@ class TestAllowedAddressPairsDriver(base.TestCase):
def setUp(self):
super(TestAllowedAddressPairsDriver, self).setUp()
with mock.patch('neutronclient.neutron.client.Client',
with mock.patch('octavia.common.clients.neutron_client.Client',
autospec=True) as neutron_client:
with mock.patch('novaclient.client.Client', autospec=True):
client = neutron_client(neutron_base.NEUTRON_VERSION)
with mock.patch('octavia.common.clients.nova_client.Client',
autospec=True):
client = neutron_client(clients.NEUTRON_VERSION)
client.list_extensions.return_value = {
'extensions': [
{'alias': allowed_address_pairs.AAP_EXT_ALIAS},

View File

@ -14,6 +14,7 @@
import mock
from octavia.common import clients
from octavia.common import data_models
from octavia.network import data_models as network_models
from octavia.network.drivers.neutron import base as neutron_base
@ -37,9 +38,9 @@ class TestBaseNeutronNetworkDriver(base.TestCase):
def setUp(self):
super(TestBaseNeutronNetworkDriver, self).setUp()
with mock.patch('neutronclient.neutron.client.Client',
with mock.patch('octavia.common.clients.neutron_client.Client',
autospec=True) as neutron_client:
client = neutron_client(neutron_base.NEUTRON_VERSION)
client = neutron_client(clients.NEUTRON_VERSION)
client.list_extensions.return_value = {
'extensions': [
{'alias': neutron_base.SEC_GRP_EXT_ALIAS}