Add a Congress driver for Keystone
This driver uses three keystone API calls to populate Congress tables users, roles, and tenants. These are the columns for these three tables: columns ('username', 'name', enabled', 'tenantId', 'id', 'email'). roles ('id', 'name') tenants ('enabled', 'description', 'name', 'id') The keystone driver includes a test. blueprint: refactor-drivers Change-Id: I8b77576a72e953971bd99bd7cb0a947bf5dc5aa9
This commit is contained in:
parent
030aea7e7f
commit
4c5c3d71b8
|
@ -0,0 +1,129 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright (c) 2014 VMware, 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 datetime
|
||||
import keystoneclient.v2_0.client
|
||||
|
||||
from congress.datasources.datasource_driver import DataSourceDriver
|
||||
|
||||
|
||||
def d6service(name, keys, inbox, datapath, args):
|
||||
"""This method is called by d6cage to create a dataservice instance."""
|
||||
return KeystoneDriver(name, keys, inbox, datapath, args)
|
||||
|
||||
|
||||
# TODO(thinrichs): figure out how to move even more of this boilerplate
|
||||
# into DataSourceDriver. E.g. change all the classes to Driver instead of
|
||||
# NeutronDriver, NovaDriver, etc. and move the d6instantiate function to
|
||||
# DataSourceDriver.
|
||||
class KeystoneDriver(DataSourceDriver):
|
||||
# Table names
|
||||
USERS = "users"
|
||||
ROLES = "roles"
|
||||
TENANTS = "tenants"
|
||||
|
||||
def __init__(self, name='', keys='', inbox=None, datapath=None, args=None):
|
||||
if args is None:
|
||||
args = self.empty_credentials()
|
||||
super(KeystoneDriver, self).__init__(name, keys, inbox, datapath, args)
|
||||
if 'client' in args:
|
||||
self.client = args['client']
|
||||
else:
|
||||
self.creds = self.get_keystone_credentials_v2(name, args)
|
||||
self.client = keystoneclient.v2_0.client.Client(**self.creds)
|
||||
|
||||
def update_from_datasource(self):
|
||||
# Fetch state from keystone
|
||||
self.state = {}
|
||||
self.users = self._get_tuple_list(self.client.users.list(),
|
||||
self.USERS)
|
||||
self.roles = self._get_tuple_list(self.client.roles.list(), self.ROLES)
|
||||
self.tenants = self._get_tuple_list(self.client.tenants.list(),
|
||||
self.TENANTS)
|
||||
|
||||
self.last_updated = datetime.datetime.now()
|
||||
|
||||
# Set local state
|
||||
# TODO(thinrichs): use self.state everywhere instead of self.servers...
|
||||
self.state[self.USERS] = set(self.users)
|
||||
self.state[self.ROLES] = set(self.roles)
|
||||
self.state[self.TENANTS] = set(self.tenants)
|
||||
|
||||
@classmethod
|
||||
def get_schema(cls):
|
||||
"""Returns a dictionary mapping tablenames to the list of
|
||||
column names for that table. Both tablenames and columnnames
|
||||
are strings.
|
||||
"""
|
||||
d = {}
|
||||
d[cls.USERS] = ('username', 'name', 'enabled', 'tenantId', 'id',
|
||||
'email')
|
||||
d[cls.ROLES] = ('id', 'name')
|
||||
d[cls.TENANTS] = ('enabled', 'description', 'name', 'id')
|
||||
return d
|
||||
|
||||
def get_keystone_credentials_v2(self, name, args):
|
||||
creds = self.get_credentials(name, args)
|
||||
d = {}
|
||||
d['version'] = '2'
|
||||
d['username'] = creds['username']
|
||||
d['password'] = creds['password']
|
||||
d['auth_url'] = creds['auth_url']
|
||||
d['tenant_name'] = creds['tenant_name']
|
||||
return d
|
||||
|
||||
def _get_tuple_list(self, obj, type):
|
||||
if type == self.USERS:
|
||||
t_list = [(u.username, u.name, u.enabled, u.tenantId, u.id,
|
||||
u.email) for u in obj]
|
||||
elif type == self.ROLES:
|
||||
t_list = [(r.id, r.name) for r in obj]
|
||||
elif type == self.TENANTS:
|
||||
t_list = [(t.enabled, t.description, t.name, t.id) for t in obj]
|
||||
else:
|
||||
raise AssertionError('Unexpected tuple type: %s' % type)
|
||||
return t_list
|
||||
|
||||
|
||||
def main():
|
||||
def get_all(self, type):
|
||||
if type not in self.state:
|
||||
self.update_from_datasource()
|
||||
assert type in self.state, "Must choose existing tablename"
|
||||
return self.state[type]
|
||||
|
||||
driver = KeystoneDriver()
|
||||
print "Last updated: %s" % driver.get_last_updated_time()
|
||||
|
||||
print "Starting Keystone Sync Service"
|
||||
|
||||
# sync with the keystone service
|
||||
driver.update_from_datasource()
|
||||
print "Users: %s" % driver.get_all(driver.USERS)
|
||||
print "Roles: %s" % driver.get_all(driver.ROLES)
|
||||
print "Tenants: %s" % driver.get_all(driver.TENANTS)
|
||||
print "Last updated: %s" % driver.get_last_updated_time()
|
||||
print "Sync completed"
|
||||
print "-----------------------------------------"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except SystemExit:
|
||||
# Let system.exit() calls complete normally
|
||||
raise
|
||||
except Exception:
|
||||
raise
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright (c) 2014 VMware, 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 congress.datasources.keystone_driver import KeystoneDriver
|
||||
from congress.datasources.tests.unit.util import ResponseObj
|
||||
from congress.tests import base
|
||||
from congress.tests import helper
|
||||
|
||||
|
||||
class TestKeystoneDriver(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestKeystoneDriver, self).setUp()
|
||||
self.keystone_client = mock.MagicMock()
|
||||
|
||||
args = helper.datasource_openstack_args()
|
||||
args['poll_time'] = 0
|
||||
args['client'] = self.keystone_client
|
||||
self.driver = KeystoneDriver(args=args)
|
||||
|
||||
def test_list_users(self):
|
||||
"""Test conversion of complex user objects to tables."""
|
||||
users_data = [
|
||||
ResponseObj({'username': 'alice',
|
||||
'name': 'alice foo',
|
||||
'enabled': True,
|
||||
'tenantId': '019b18a15f2a44c1880d57704b2c4009',
|
||||
'id': '00f2c34a156c40058004ee8eb3320e04',
|
||||
'email': 'alice@foo.com'}),
|
||||
ResponseObj({'username': 'bob',
|
||||
'name': 'bob bar',
|
||||
'enabled': False,
|
||||
'tenantId': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'id': 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'email': 'bob@bar.edu'}),
|
||||
]
|
||||
|
||||
user_list = self.driver._get_tuple_list(users_data,
|
||||
KeystoneDriver.USERS)
|
||||
self.assertIsNotNone(user_list)
|
||||
self.assertEqual(2, len(user_list))
|
||||
|
||||
# Check an individual user entry
|
||||
self.assertEqual(('alice', 'alice foo', True,
|
||||
'019b18a15f2a44c1880d57704b2c4009',
|
||||
'00f2c34a156c40058004ee8eb3320e04',
|
||||
'alice@foo.com'),
|
||||
user_list[0])
|
||||
self.assertEqual(('bob', 'bob bar', False,
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
'bob@bar.edu'),
|
||||
user_list[1])
|
||||
|
||||
def test_list_roles(self):
|
||||
"""Test conversion of complex role objects to tables."""
|
||||
roles_data = [
|
||||
ResponseObj({'id': 'cccccccccccccccccccccccccccccccc',
|
||||
'name': 'admin'}),
|
||||
ResponseObj({'id': 'dddddddddddddddddddddddddddddddd',
|
||||
'name': 'viewer'}),
|
||||
]
|
||||
|
||||
roles_list = self.driver._get_tuple_list(roles_data,
|
||||
KeystoneDriver.ROLES)
|
||||
self.assertIsNotNone(roles_list)
|
||||
self.assertEqual(2, len(roles_list))
|
||||
|
||||
# Check an individual role entry
|
||||
self.assertEqual(('cccccccccccccccccccccccccccccccc', 'admin'),
|
||||
roles_list[0])
|
||||
self.assertEqual(('dddddddddddddddddddddddddddddddd', 'viewer'),
|
||||
roles_list[1])
|
||||
|
||||
def test_list_tenants(self):
|
||||
"""Test conversion of complex tenant objects to tables."""
|
||||
tenants_data = [
|
||||
ResponseObj({'enabled': True,
|
||||
'description': 'accounting team',
|
||||
'name': 'accounting',
|
||||
'id': '00000000000000000000000000000001'}),
|
||||
ResponseObj({'enabled': False,
|
||||
'description': 'eng team',
|
||||
'name': 'eng',
|
||||
'id': '00000000000000000000000000000002'}),
|
||||
]
|
||||
|
||||
tenants_list = self.driver._get_tuple_list(tenants_data,
|
||||
KeystoneDriver.TENANTS)
|
||||
self.assertIsNotNone(tenants_list)
|
||||
self.assertEqual(2, len(tenants_list))
|
||||
|
||||
# Check an individual role entry
|
||||
self.assertEqual((True, 'accounting team', 'accounting',
|
||||
'00000000000000000000000000000001'),
|
||||
tenants_list[0])
|
||||
self.assertEqual((False, 'eng team', 'eng',
|
||||
'00000000000000000000000000000002'),
|
||||
tenants_list[1])
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright (c) 2014 VMware, 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.
|
||||
|
||||
|
||||
class ResponseObj(object):
|
||||
"""Allows callers to use dot notation to access a dictionary."""
|
||||
def __init__(self, values):
|
||||
self.values = values
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.values[name]
|
|
@ -13,4 +13,10 @@ password: password
|
|||
auth_url: http://127.0.0.1:5000/v2.0
|
||||
tenant_name: demo
|
||||
|
||||
[keystone]
|
||||
module: datasources/keystone_driver.py
|
||||
username: demo
|
||||
password: password
|
||||
auth_url: http://127.0.0.1:5000/v2.0
|
||||
tenant_name: demo
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ Paste
|
|||
PasteDeploy>=1.5.0
|
||||
pbr>=0.6,!=0.7,<1.0
|
||||
posix_ipc
|
||||
python-keystoneclient>=0.10.0
|
||||
python-novaclient>=2.18.0
|
||||
python-neutronclient>=2.3.6,<3
|
||||
Routes>=1.12.3,!=2.0
|
||||
|
|
Loading…
Reference in New Issue