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:
Alexander Yip 2014-09-10 16:37:14 -07:00
parent 030aea7e7f
commit 4c5c3d71b8
5 changed files with 271 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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