File 0001-keystone-v3-support-in-syn_neutron_to_infoblox.patch of Package openstack-neutron-infoblox
From 2137d2627e1f523101e8245c2c60afa389ccf1fa Mon Sep 17 00:00:00 2001
From: Aliaksandr Dziarkach <adziarkach@infoblox.com>
Date: Wed, 5 Oct 2016 17:26:24 +0300
Subject: [PATCH] keystone v3 support in syn_neutron_to_infoblox
sync_neutron_to_infoblox is assuming keystone v2, and not allowing for
v3-only deployments.
Some customers wants to use keystone v3 configuration.
So sync_neutron_to_infoblox.py fixed to support both keystone v2 and v3.
Verified manaully.
Change-Id: I7f8adecff2c8d613803e13c28db27015080c9548
Closes-Bug: 1606257
Closes-Bug: 1605621
---
.../neutron/common/keystone_manager.py | 31 ++-
.../tests/unit/common/test_keystone_manager.py | 280 +++++++++++++++++++++
.../tools/sync_neutron_to_infoblox.py | 74 +++---
3 files changed, 351 insertions(+), 34 deletions(-)
create mode 100644 networking_infoblox/tests/unit/common/test_keystone_manager.py
diff --git a/networking_infoblox/neutron/common/keystone_manager.py b/networking_infoblox/neutron/common/keystone_manager.py
index c3d6d58..f547ebf 100644
--- a/networking_infoblox/neutron/common/keystone_manager.py
+++ b/networking_infoblox/neutron/common/keystone_manager.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Infoblox Inc.
+# Copyright 2016 Infoblox Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -13,9 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+from keystoneclient.auth.identity.generic import token
from keystoneclient.auth import token_endpoint
from keystoneclient import session
-from keystoneclient.v2_0 import client as k_client
+from keystoneclient.v2_0 import client as client_2_0
+from keystoneclient.v3 import client as client_3
from neutron.common import config as cfg
from oslo_log import log
@@ -32,19 +34,36 @@ def init_keystone_session():
global _SESSION
if not _SESSION:
_SESSION = session.Session()
+ return _SESSION
def get_keystone_client(auth_token):
- init_keystone_session()
- url = CONF['keystone_authtoken']['auth_uri'] + '/v2.0/'
+ sess = init_keystone_session()
+ url = CONF['keystone_authtoken']['auth_uri']
+ # Create token to get available service version
+ generic_token = token.Token(url, token=auth_token)
+ generic_token.reauthenticate = False
+ version = generic_token.get_auth_ref(sess)['version']
+ # update auth url aith version if needed
+ if version not in url.split('/'):
+ url = url + '/' + version
+ # create endpoint token using right url and provided auth token
auth = token_endpoint.Token(url, auth_token)
- return k_client.Client(session=session, auth=auth)
+ # create keystone client
+ if version == 'v3':
+ k_client = client_3.Client(session=sess, auth=auth)
+ else:
+ k_client = client_2_0.Client(session=sess, auth=auth)
+ return k_client
def get_all_tenants(auth_token):
try:
keystone = get_keystone_client(auth_token)
- return keystone.tenants.list()
+ if keystone.version == 'v3':
+ return keystone.projects.list()
+ else:
+ return keystone.tenants.list()
except Exception as e:
LOG.warning("Could not get tenants due to error: %s", e)
return []
diff --git a/networking_infoblox/tests/unit/common/test_keystone_manager.py b/networking_infoblox/tests/unit/common/test_keystone_manager.py
new file mode 100644
index 0000000..ed4f09d
--- /dev/null
+++ b/networking_infoblox/tests/unit/common/test_keystone_manager.py
@@ -0,0 +1,280 @@
+# Copyright (c) 2016 Infoblox 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 mock
+
+from networking_infoblox.neutron.common import keystone_manager
+
+from networking_infoblox.tests import base
+
+
+class TestKeystoneManager(base.TestCase):
+
+ def setUp(self):
+ super(TestKeystoneManager, self).setUp()
+ self.tenants = []
+ for i in range(1, 4):
+ tenant = mock.Mock()
+ tenant.id = str(i)
+ tenant.tenant_id = tenant.id
+ tenant.name = 'tenant_%s' % i
+ self.tenants.append(tenant)
+ self.networks = [
+ {'tenant_id': '1'},
+ {'tenant_id': '2'},
+ {'tenant_id': '3'},
+ {'tenant_id': '4'},
+ ]
+
+ @mock.patch('keystoneclient.session.Session')
+ def test_init_keystone_session(self, SessionMock):
+ keystone_manager._SESSION = None # reset glogal variable
+ # call function and check that Session created without params
+ session1 = keystone_manager.init_keystone_session()
+ SessionMock.assert_called_once_with()
+ # reset mock, call function again and check that Session is not called
+ # and function return same session
+ SessionMock.reset_mock()
+ session2 = keystone_manager.init_keystone_session()
+ assert not SessionMock.called
+ assert session1 is session2
+
+ @mock.patch('keystoneclient.auth.token_endpoint.Token')
+ @mock.patch('networking_infoblox.neutron.common.keystone_manager.CONF')
+ @mock.patch('networking_infoblox.neutron.common.keystone_manager.'
+ 'init_keystone_session')
+ @mock.patch('keystoneclient.auth.identity.generic.token.Token')
+ def _test_get_keystone_client(self, ClientMock, keystone_authtoken,
+ TokenMock, InitMock, ConfMock,
+ TokenEndpointMock):
+ # prepare mocks
+ session = 'test_session'
+ auth_token = 'test_auth_token'
+ auth_uri = 'test_auth_uri'
+ token_endpoint = 'token_endpoint'
+ InitMock.return_value = session
+ ConfMock.__getitem__ = mock.Mock()
+ ConfMock.__getitem__.return_value = {'auth_uri': auth_uri}
+ TokenObjMock = mock.Mock()
+ TokenObjMock.get_auth_ref.return_value = keystone_authtoken
+ TokenMock.return_value = TokenObjMock
+ TokenEndpointMock.return_value = token_endpoint
+ # call tested function
+ k_client = keystone_manager.get_keystone_client(auth_token)
+ # check calls
+ keystone_manager.init_keystone_session.assert_called_once_with()
+ TokenMock.assert_called_once_with(auth_uri, token=auth_token)
+ TokenObjMock.get_auth_ref.assert_called_once_with(session)
+ TokenEndpointMock.assert_called_once_with(
+ auth_uri + '/' + keystone_authtoken['version'], auth_token)
+ ClientMock.assert_called_once_with(session=session,
+ auth=token_endpoint)
+ assert k_client == ClientMock.return_value
+
+ @mock.patch('keystoneclient.v2_0.client.Client')
+ def test_get_keystone_client_v2_0(self, ClientMock):
+ ClientMock.return_value = 'keystone_client_v2_0'
+ self._test_get_keystone_client(ClientMock, {'version': 'v2_0'})
+
+ @mock.patch('keystoneclient.v3.client.Client')
+ def test_get_keystone_client_v3(self, ClientMock):
+ ClientMock.return_value = 'keystone_client_v3'
+ self._test_get_keystone_client(ClientMock, {'version': 'v3'})
+
+ @mock.patch('networking_infoblox.neutron.common.keystone_manager.'
+ 'get_keystone_client')
+ def _test_get_all_tenants(self, version, get_keystone_client_mock):
+ # prepare mocks
+ auth_token = 'test_auth_token'
+ client_mock = mock.MagicMock()
+ client_mock.version = version
+ get_keystone_client_mock.return_value = client_mock
+ # call tested function
+ keystone_manager.get_all_tenants(auth_token)
+ # check calls
+ get_keystone_client_mock.assert_called_once_with(auth_token)
+ if version == 'v3':
+ client_mock.projects.list.assert_called_once_with()
+ else:
+ client_mock.tenants.list.assert_called_once_with()
+
+ def test_get_all_tenants_v2_0(self):
+ self._test_get_all_tenants('v2_0')
+
+ def test_get_all_tenants_v3(self):
+ self._test_get_all_tenants('v3')
+
+ @mock.patch('networking_infoblox.neutron.common.keystone_manager.'
+ 'sync_tenants_from_keystone')
+ @mock.patch('networking_infoblox.neutron.db.infoblox_db.'
+ 'add_or_update_tenant')
+ @mock.patch('networking_infoblox.neutron.db.infoblox_db.get_tenants')
+ def _test_update_tenant_mapping(self, auth_token, networks, tenants,
+ expected_results,
+ get_tenants, add_or_update_tenant,
+ sync_tenants_from_keystone):
+ context = mock.Mock()
+ context.session = 'test_session'
+ tenant_id = '1'
+ tenant_name = 'test_tenant_name_1'
+ get_unknown = mock.Mock()
+ get_unknown_params = []
+ func = keystone_manager._get_unknown_ids_from_dict
+
+ def store_params(param):
+ get_unknown_params.append(param.copy())
+ return func(param)
+
+ get_unknown.side_effect = store_params
+ get_tenants.return_value = tenants
+ with mock.patch('networking_infoblox.neutron.common.keystone_manager.'
+ '_get_unknown_ids_from_dict', get_unknown):
+ keystone_manager.update_tenant_mapping(
+ context, networks, tenant_id, tenant_name, auth_token)
+ add_or_update_tenant.assert_called_once_with(context.session,
+ tenant_id, tenant_name)
+ assert get_unknown.call_count == expected_results['get_unknown_count']
+ assert get_unknown_params == expected_results['get_unknown_params']
+ if expected_results['get_tenants_called']:
+ get_tenants.assert_called_once()
+ assert get_tenants.call_args[0] == (context.session,)
+ tenant_ids = expected_results['get_tenant_tenant_ids']
+ assert(
+ sorted(
+ get_tenants.call_args[1]['tenant_ids']) == tenant_ids)
+ else:
+ get_tenants.assert_not_called()
+ if expected_results['sync_tenants_from_keystone_called']:
+ sync_tenants_from_keystone.assert_called_once_with(context,
+ auth_token)
+ else:
+ sync_tenants_from_keystone.assert_not_called()
+
+ def test_update_tenant_mapping(self):
+ auth_token = 'test_auth_token'
+ networks = self.networks
+ tenants = self.tenants
+ expected_results = {
+ 'get_unknown_count': 2,
+ 'get_unknown_params': [
+ {
+ networks[0]['tenant_id']: False,
+ networks[1]['tenant_id']: True,
+ networks[2]['tenant_id']: True,
+ networks[3]['tenant_id']: True
+ },
+ {
+ networks[0]['tenant_id']: False,
+ networks[1]['tenant_id']: False,
+ networks[2]['tenant_id']: False,
+ networks[3]['tenant_id']: True
+ }],
+ 'get_tenants_called': True,
+ 'get_tenant_tenant_ids': ['2', '3', '4'],
+ 'sync_tenants_from_keystone_called': True
+ }
+ self._test_update_tenant_mapping(auth_token, networks, tenants,
+ expected_results)
+
+ def test_update_tenant_mapping_all_tenant_in_db(self):
+ auth_token = 'test_auth_token'
+ networks = self.networks
+ networks[3]['tenant_id'] = networks[0]['tenant_id']
+ tenants = self.tenants
+ expected_results = {
+ 'get_unknown_count': 2,
+ 'get_unknown_params': [
+ {
+ networks[0]['tenant_id']: False,
+ networks[1]['tenant_id']: True,
+ networks[2]['tenant_id']: True,
+ networks[3]['tenant_id']: False
+ },
+ {
+ networks[0]['tenant_id']: False,
+ networks[1]['tenant_id']: False,
+ networks[2]['tenant_id']: False,
+ networks[3]['tenant_id']: False
+ }],
+ 'get_tenants_called': True,
+ 'get_tenant_tenant_ids': ['2', '3'],
+ 'sync_tenants_from_keystone_called': False
+ }
+ self._test_update_tenant_mapping(auth_token, networks, tenants,
+ expected_results)
+
+ def test_update_tenant_mapping_all_tenant_in_networks(self):
+ auth_token = 'test_auth_token'
+ networks = self.networks
+ for i in range(1, len(networks)):
+ networks[i]['tenant_id'] = networks[0]['tenant_id']
+ tenants = self.tenants
+ expected_results = {
+ 'get_unknown_count': 1,
+ 'get_unknown_params': [
+ {
+ networks[0]['tenant_id']: False,
+ networks[1]['tenant_id']: False,
+ networks[2]['tenant_id']: False,
+ networks[3]['tenant_id']: False
+ }],
+ 'get_tenants_called': False,
+ 'sync_tenants_from_keystone_called': False
+ }
+ self._test_update_tenant_mapping(auth_token, networks, tenants,
+ expected_results)
+
+ def test_update_tenant_mapping_without_token(self):
+ auth_token = ''
+ networks = self.networks
+ tenants = self.tenants
+ expected_results = {
+ 'get_unknown_count': 0,
+ 'get_unknown_params': [],
+ 'get_tenants_called': False,
+ 'sync_tenants_from_keystone_called': False
+ }
+ self._test_update_tenant_mapping(auth_token, networks, tenants,
+ expected_results)
+
+ @mock.patch('networking_infoblox.neutron.common.keystone_manager.'
+ 'get_all_tenants')
+ @mock.patch('networking_infoblox.neutron.db.infoblox_db.'
+ 'add_or_update_tenant')
+ def test_sync_tenants_from_keystone(self, AddTenantMock, get_all_tenants):
+ # prepare test data
+ context = mock.Mock()
+ context.session = 'test_session'
+ auth_token = 'test_auth_token'
+ get_all_tenants.return_value = self.tenants
+ # call tested function
+ ret = keystone_manager.sync_tenants_from_keystone(context, auth_token)
+ # check return value and calls
+ assert ret == len(self.tenants)
+ get_all_tenants.assert_called_once_with(auth_token)
+ expected_call_list = [
+ mock.call(context.session, t.id, t.name) for t in self.tenants]
+ assert AddTenantMock.call_args_list == expected_call_list
+
+ @mock.patch('networking_infoblox.neutron.common.keystone_manager.'
+ 'get_all_tenants')
+ @mock.patch('networking_infoblox.neutron.db.infoblox_db.'
+ 'add_or_update_tenant')
+ def test_sync_tenants_from_keystone_without_token(self, AddTenantMock,
+ get_all_tenants):
+ ret = keystone_manager.sync_tenants_from_keystone('context', None)
+ assert ret is None
+ get_all_tenants.assert_not_called()
+ AddTenantMock.assert_not_called
diff --git a/networking_infoblox/tools/sync_neutron_to_infoblox.py b/networking_infoblox/tools/sync_neutron_to_infoblox.py
index f0b30ad..2551c47 100644
--- a/networking_infoblox/tools/sync_neutron_to_infoblox.py
+++ b/networking_infoblox/tools/sync_neutron_to_infoblox.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# Copyright 2015 Infoblox Inc.
+# Copyright 2016 Infoblox Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -17,15 +17,17 @@
import os
import sys
-from keystoneclient.v2_0 import client as ksclient
+from keystoneauth1.identity import v2
+from keystoneauth1.identity import v3
+from keystoneauth1 import session as ks_session
+from keystoneclient.v2_0 import client as client_2_0
+from keystoneclient.v3 import client as client_3
from oslo_config import cfg
from oslo_log import log as logging
from neutron.common import config as common_config
from neutron import context as neutron_context
-from neutron.i18n import _LE
-from neutron.i18n import _LW
from neutronclient.v2_0 import client as neutron_client
from novaclient import client as nova_client
@@ -33,6 +35,8 @@ from novaclient import client as nova_client
from infoblox_client import exceptions as ib_exc
from infoblox_client import objects as ib_objects
+from networking_infoblox._i18n import _LE
+from networking_infoblox._i18n import _LW
from networking_infoblox.neutron.common import config
from networking_infoblox.neutron.common import context as ib_context
from networking_infoblox.neutron.common import dns
@@ -71,20 +75,38 @@ def main():
config.register_infoblox_grid_opts(cfg.CONF, grid_id)
register_keystone_opts(cfg.CONF)
- credentials = get_credentials()
- if not credentials:
+ try:
+ credentials, version = get_credentials()
+ except KeyError:
print("\nYou must provide an admin user credentials in the shell "
"environment.\nPlease export variables such as env[OS_USERNAME],"
" env[OS_PASSWORD], env[OS_AUTH_URL], env[OS_TENANT_NAME], and "
"env[OS_REGION_NAME]\n")
return
+ password_creds = credentials.copy()
+ password_creds.pop('region_name', None)
+ if version == '3':
+ auth = v3.Password(**password_creds)
+ session = ks_session.Session(auth=auth)
+ client = client_3.Client(session=session)
+ else:
+ auth = v2.Password(**password_creds)
+ session = ks_session.Session(auth=auth)
+ client = client_2_0.Client(session=session)
+
context = neutron_context.get_admin_context()
- context.auth_token = get_auth_token(credentials)
+ context.auth_token = client.ec2.client.get_token()
+ context.user_id = client.ec2.client.get_user_id()
+ context.tenant_id = client.ec2.client.get_project_id()
grid_manager = grid.GridManager(context)
grid_manager.sync(force_sync=True)
+ credentials['session'] = session
+ for key in ('user_domain_id', 'project_domain_id'):
+ credentials.pop(key, None)
+
sync_neutron_to_infoblox(context, credentials, grid_manager)
@@ -103,23 +125,22 @@ def register_keystone_opts(conf):
def get_credentials():
d = dict()
- if ('OS_USERNAME' in os.environ and
- 'OS_PASSWORD' in os.environ and
- 'OS_AUTH_URL' in os.environ and
- 'OS_TENANT_NAME' in os.environ and
- 'OS_REGION_NAME' in os.environ):
- d['username'] = os.environ['OS_USERNAME']
- d['password'] = os.environ['OS_PASSWORD']
- d['auth_url'] = os.environ['OS_AUTH_URL']
+ version = '2'
+ if 'OS_IDENTITY_API_VERSION' in os.environ:
+ version = os.environ['OS_IDENTITY_API_VERSION']
+ if version == '3':
+ d['project_name'] = os.environ['OS_PROJECT_NAME']
+ d['user_domain_id'] = os.environ.get('OS_USER_DOMAIN_ID', 'default')
+ d['project_domain_id'] = os.environ.get('OS_PROJECT_DOMAIN_ID',
+ 'default')
+ else:
d['tenant_name'] = os.environ['OS_TENANT_NAME']
- d['region_name'] = os.environ['OS_REGION_NAME']
- return d
-
-
-def get_auth_token(credentials):
- keystone = ksclient.Client(**credentials)
+ d['username'] = os.environ['OS_USERNAME']
+ d['password'] = os.environ['OS_PASSWORD']
+ d['auth_url'] = os.environ['OS_AUTH_URL']
+ d['region_name'] = os.environ['OS_REGION_NAME']
- return keystone.auth_ref['token']['id']
+ return d, version
def sync_neutron_to_infoblox(context, credentials, grid_manager):
@@ -154,10 +175,7 @@ def sync_neutron_to_infoblox(context, credentials, grid_manager):
payload = neutron_api.list_ports()
ports = payload['ports']
nova_api = nova_client.Client(NOVA_API_VERSION,
- credentials['username'],
- credentials['password'],
- credentials['tenant_name'],
- credentials['auth_url'])
+ session=credentials['session'])
instance_names_by_instance_id = dict()
instance_names_by_floating_ip = dict()
@@ -170,8 +188,8 @@ def sync_neutron_to_infoblox(context, credentials, grid_manager):
for fip in floating_ips:
instance_names_by_floating_ip[fip] = server.name
- user_id = context.user_id or neutron_api.httpclient.auth_ref['user']['id']
- user_tenant_id = context.tenant_id or neutron_api.httpclient.auth_tenant_id
+ user_id = neutron_api.httpclient.get_user_id()
+ user_tenant_id = neutron_api.httpclient.get_project_id()
ib_networks = []
should_exit = False
--
2.14.1