Introduce database functionality into KDS

Add database setup and initial testing into Key Distribution Server.
Also adds a migration script.

Implements: bp key-distribution-server
Change-Id: I22e914ef1ca6ec8217d131c073261b309d9f637e
This commit is contained in:
Jamie Lennox 2013-12-03 12:33:28 +10:00
parent 51602dcc25
commit 365e65dfd3
27 changed files with 937 additions and 1 deletions

View File

@ -0,0 +1,65 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 sys
from keystone.openstack.common import gettextutils
# gettextutils.install() must run to set _ before importing any modules that
# contain static translated strings.
gettextutils.install('keystone')
from oslo.config import cfg
from keystone.contrib.kds.common import service
from keystone.contrib.kds.db import migration
CONF = cfg.CONF
def do_db_version():
"""Print database's current migration level."""
print(migration.db_version())
def do_db_sync():
"""Place a database under migration control and upgrade,
creating first if necessary.
"""
return migration.db_sync(CONF.command.version)
def add_command_parsers(subparsers):
parser = subparsers.add_parser('db_version')
parser.set_defaults(func=do_db_version)
parser = subparsers.add_parser('db_sync')
parser.set_defaults(func=do_db_sync)
parser.add_argument('version', nargs='?')
command_opt = cfg.SubCommandOpt('command',
title='Commands',
help='Available commands',
handler=add_command_parsers)
def main():
CONF.register_cli_opt(command_opt)
service.prepare_service(sys.argv)
try:
CONF.command.func()
except Exception as e:
sys.exit("ERROR: %s" % e)

View File

@ -12,6 +12,45 @@
# License for the specific language governing permissions and limitations
# under the License.
_FATAL_EXCEPTION_FORMAT_ERRORS = False
class KdsException(Exception):
pass
"""Base Exception class.
To correctly use this class, inherit from it and define
a 'msg_fmt' property. That message will get printf'd
with the keyword arguments provided to the constructor.
"""
msg_fmt = _('An unknown exception occurred')
def __init__(self, **kwargs):
try:
self._error_string = self.msg_fmt % kwargs
except Exception:
if _FATAL_EXCEPTION_FORMAT_ERRORS:
raise
else:
# at least get the core message out if something happened
self._error_string = self.msg_fmt
def __str__(self):
return self._error_string
class BackendException(KdsException):
msg_fmt = _("Failed to load the '%(backend)s' backend because it is not "
"allowed. Allowed backends are: %(allowed)s")
class IntegrityError(KdsException):
msg_fmt = _('Cannot set key data for %(name)s: %(reason)s')
class GroupStatusChanged(IntegrityError):
def __init__(self, **kwargs):
kwargs.setdefault('reason', "Can't change group status of a host")
super(GroupStatusChanged, self).__init__(**kwargs)

View File

@ -0,0 +1,61 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 oslo.config import cfg
from keystone.contrib.kds.common import exception
CONF = cfg.CONF
# NOTE(jamielennox): This class is a direct copy from nova, glance, heat and
# a bunch of other projects. It has been submitted to OSLO
# https://review.openstack.org/#/c/67002/ and should be synced when available.
class LazyPluggable(object):
"""A pluggable backend loaded lazily based on some value."""
def __init__(self, pivot, config_group=None, **backends):
self.__backends = backends
self.__pivot = pivot
self.__backend = None
self.__config_group = config_group
def __get_backend(self):
if not self.__backend:
if self.__config_group is None:
backend_name = CONF[self.__pivot]
else:
backend_name = CONF[self.__config_group][self.__pivot]
if backend_name not in self.__backends:
allowed = ', '.join(self.__backends.iterkeys())
raise exception.BackendException(backend=backend_name,
allowed=allowed)
backend = self.__backends[backend_name]
if isinstance(backend, tuple):
name = backend[0]
fromlist = backend[1]
else:
name = backend
fromlist = backend
self.__backend = __import__(name=name, globals=None,
locals=None, fromlist=fromlist)
return self.__backend
def __getattr__(self, key):
backend = self.__get_backend()
return getattr(backend, key)

View File

View File

@ -0,0 +1,34 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 oslo.config import cfg
from keystone.openstack.common.db import api as db_api
CONF = cfg.CONF
_BACKEND_MAPPING = {'sqlalchemy': 'keystone.contrib.kds.db.sqlalchemy.api',
'kvs': 'keystone.contrib.kds.db.kvs.api'}
IMPL = db_api.DBAPI(backend_mapping=_BACKEND_MAPPING)
def reset():
global IMPL
IMPL = db_api.DBAPI(backend_mapping=_BACKEND_MAPPING)
def get_instance():
"""Return a DB API instance."""
return IMPL

View File

@ -0,0 +1,65 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 abc
import six
@six.add_metaclass(abc.ABCMeta)
class Connection(object):
@abc.abstractmethod
def set_key(self, name, key, signature, group, expiration=None):
"""Set a key for a name in the database.
If a key is set for an existing key name then a new key entry with a
new generation value is created.
:param string name: The unique name of the key to set.
:param string key: The key data to save.
:param string signature: The signature of the key data to save.
:param bool group: Whether this is a group key or not.
:param DateTime expiration: When the key should expire
(None is never expire).
:raises IntegrityError: If a key exists then new keys assigned to the
name must have the same 'group' setting. If the
value of group is changed an IntegrityError is
raised.
:returns int: The generation number of this key.
"""
@abc.abstractmethod
def get_key(self, name, generation=None, group=None):
"""Get key related to kds_id.
:param string name: The unique name of the key to fetch.
:param int generation: A specific generation of the key to retrieve. If
not specified the most recent generation is
retrieved.
:param bool group: If provided only retrieve this key if its group
value is the same.
:returns dict: A dictionary of the key information or None if not
found. Keys will contain:
- name: Unique name of the key.
- group: If this key is a group key or not.
- key: The key data.
- signature: The signature of the key data.
- generation: The generation of this key.
- expiration: When the key expires (or None).
Expired keys can be returned.
"""

View File

View File

@ -0,0 +1,68 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 keystone.contrib.kds.common import exception
from keystone.contrib.kds.db import connection
def get_backend():
return KvsDbImpl()
class KvsDbImpl(connection.Connection):
"""A simple in-memory Key Value backend.
KVS backends are designed for use in testing and for simple debugging.
This backend should not be deployed in any production systems.
"""
def __init__(self):
super(KvsDbImpl, self).__init__()
self.clear()
def clear(self):
self._data = dict()
def set_key(self, name, key, signature, group, expiration=None):
host = self._data.setdefault(name, {'latest_generation': 0,
'keys': dict(), 'group': group})
if host['group'] != group:
raise exception.GroupStatusChanged(name=name)
host['latest_generation'] += 1
host['keys'][host['latest_generation']] = {'key': key,
'signature': signature,
'expiration': expiration}
return host['latest_generation']
def get_key(self, name, generation=None, group=None):
response = {'name': name}
try:
host = self._data[name]
if generation is None:
generation = host['latest_generation']
key_data = host['keys'][generation]
except KeyError:
return None
response['generation'] = generation
response['group'] = host['group']
if group is not None and host['group'] != group:
return None
response.update(key_data)
return response

View File

@ -0,0 +1,41 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.
"""Database setup and migration commands."""
from oslo.config import cfg
from keystone.contrib.kds.common import utils
CONF = cfg.CONF
CONF.import_opt('backend',
'keystone.openstack.common.db.api',
group='database')
_sqlalchemy_repo = 'keystone.contrib.kds.db.sqlalchemy.migration'
IMPL = utils.LazyPluggable(pivot='backend',
config_group='database',
sqlalchemy=_sqlalchemy_repo)
INIT_VERSION = 0
def db_sync(version=None):
"""Migrate the database to `version` or the most recent version."""
return IMPL.db_sync(version=version)
def db_version():
"""Display the current database version."""
return IMPL.db_version()

View File

@ -0,0 +1,82 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 sqlalchemy.orm import exc
from keystone.contrib.kds.common import exception
from keystone.contrib.kds.db import connection
from keystone.contrib.kds.db.sqlalchemy import models
from keystone.openstack.common.db.sqlalchemy import session as db_session
def get_backend():
return SqlalchemyDbImpl()
class SqlalchemyDbImpl(connection.Connection):
def set_key(self, name, key, signature, group, expiration=None):
session = db_session.get_session()
with session.begin():
q = session.query(models.Host)
q = q.filter(models.Host.name == name)
try:
host = q.one()
except exc.NoResultFound:
host = models.Host(name=name,
latest_generation=0,
group=group)
else:
if host.group != group:
raise exception.GroupStatusChanged(name=name)
host.latest_generation += 1
host.keys.append(models.Key(signature=signature,
enc_key=key,
generation=host.latest_generation,
expiration=expiration))
session.add(host)
return host.latest_generation
def get_key(self, name, generation=None, group=None):
session = db_session.get_session()
query = session.query(models.Host, models.Key)
query = query.filter(models.Host.id == models.Key.host_id)
query = query.filter(models.Host.name == name)
if group is not None:
query = query.filter(models.Host.group == group)
if generation is not None:
query = query.filter(models.Key.generation == generation)
else:
query = query.filter(models.Host.latest_generation ==
models.Key.generation)
try:
result = query.one()
except exc.NoResultFound:
return None
return {'name': result.Host.name,
'group': result.Host.group,
'key': result.Key.enc_key,
'signature': result.Key.signature,
'generation': result.Key.generation,
'expiration': result.Key.expiration}

View File

@ -0,0 +1,19 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 migrate.versioning.shell import main
if __name__ == '__main__':
main(debug=False, repository='.')

View File

@ -0,0 +1,20 @@
[db_settings]
# Used to identify which repository this database is versioned under.
# You can use the name of your project.
repository_id=kds
# The name of the database table used to track the schema version.
# This name shouldn't already be used by your project.
# If this is changed once a database is under version control, you'll need to
# change the table name in each database too.
version_table=migrate_version
# When committing a change script, Migrate will attempt to generate the
# sql for all supported databases; normally, if one of them fails - probably
# because you don't have that database installed - it is ignored and the
# commit continues, perhaps ending successfully.
# Databases in this list MUST compile successfully during a commit, or the
# entire commit will fail. List the databases your application will actually
# be using to ensure your updates to that database work properly.
# This must be a list; example: ['postgres','sqlite']
required_dbs=[]

View File

@ -0,0 +1,78 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 sqlalchemy as sql
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
host_table = sql.Table('kds_hosts', meta,
sql.Column('id',
sql.Integer(),
primary_key=True,
autoincrement=True),
sql.Column('name',
sql.Text(),
nullable=False),
sql.Column('group',
sql.Boolean(),
nullable=False,
index=True),
sql.Column('latest_generation',
sql.Integer(),
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8')
# MySQL can't put an index on an unbound TEXT type so if we do it this way
# it will make the index on the first 20 characters which will be fine.
sql.Index('name_idx', host_table.c.name, unique=True, mysql_length=20)
host_table.create(migrate_engine, checkfirst=True)
key_table = sql.Table('kds_keys', meta,
sql.Column('host_id',
sql.Integer(),
sql.ForeignKey('kds_hosts.id'),
primary_key=True,
autoincrement=False),
sql.Column('generation',
sql.Integer(),
primary_key=True,
autoincrement=False),
sql.Column('signature',
sql.LargeBinary(),
nullable=False),
sql.Column('enc_key',
sql.LargeBinary(),
nullable=False),
sql.Column('expiration',
sql.DateTime(),
nullable=True,
index=True),
mysql_engine='InnoDB',
mysql_charset='utf8')
key_table.create(migrate_engine, checkfirst=True)
def downgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
for name in ['kds_keys', 'kds_hosts']:
table = sql.Table(name, meta, autoload=True)
table.drop(migrate_engine, checkfirst=True)

View File

@ -0,0 +1,34 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 os
from keystone.openstack.common.db.sqlalchemy import migration
def _repo_path():
return os.path.join(os.path.abspath(os.path.dirname(__file__)),
'migrate_repo')
def db_version_control(version=None):
return migration.db_version_control(_repo_path(), version=version)
def db_sync(version=None):
return migration.db_sync(_repo_path(), version=version)
def db_version(version=None):
return migration.db_version(_repo_path(), version)

View File

@ -0,0 +1,62 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 sqlalchemy as sql
from sqlalchemy.ext.declarative import declarative_base
from keystone.openstack.common.db.sqlalchemy import models
class KdsBase(models.ModelBase):
pass
Base = declarative_base(cls=KdsBase)
class Host(Base):
__tablename__ = 'kds_hosts'
id = sql.Column(sql.Integer(), primary_key=True, autoincrement=True)
name = sql.Column(sql.Text(), index=True, unique=True, nullable=False)
group = sql.Column(sql.Boolean(), nullable=False, index=True)
latest_generation = sql.Column(sql.Integer(), nullable=False)
class Key(Base):
__tablename__ = 'kds_keys'
host_id = sql.Column(sql.Integer(),
sql.ForeignKey('kds_hosts.id'),
primary_key=True,
autoincrement=False)
generation = sql.Column(sql.Integer(),
primary_key=True,
autoincrement=False)
signature = sql.Column(sql.LargeBinary(),
nullable=False)
enc_key = sql.Column(sql.LargeBinary(),
nullable=False)
expiration = sql.Column(sql.DateTime(),
nullable=True,
index=True)
owner = sql.orm.relationship('Host',
backref=sql.orm.backref('keys',
order_by=sql.desc(
generation)))

View File

@ -23,4 +23,8 @@ class BaseTestCase(test.BaseTestCase):
super(BaseTestCase, self).setUp()
self.config_fixture = self.useFixture(config.Config())
self.CONF = self.config_fixture.conf
service.parse_args(args=[])
def config(self, *args, **kwargs):
self.config_fixture.config(*args, **kwargs)

View File

@ -0,0 +1,30 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 keystone.contrib.kds.db import api as db_api
from keystone.tests.contrib.kds import base
from keystone.tests.contrib.kds import fixture
class BaseTestCase(base.BaseTestCase):
scenarios = [('sqlitedb', {'sql_fixture': fixture.SqliteDb}),
('kvsdb', {'sql_fixture': fixture.KvsDb})]
def setUp(self):
super(BaseTestCase, self).setUp()
self.useFixture(self.sql_fixture())
self.DB = db_api.get_instance()

View File

@ -0,0 +1,138 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 testscenarios import load_tests_apply_scenarios as load_tests # noqa
from keystone.contrib.kds.common import exception
from keystone.tests.contrib.kds.db import base
TEST_NAME = 'test-name'
TEST_SIG = 'test-sig'
TEST_KEY = 'test-enc'
class KeyDbTestCase(base.BaseTestCase):
def test_retrieve(self):
# Set a key and expect to get the same key back.
generation = self.DB.set_key(name=TEST_NAME,
signature=TEST_SIG,
key=TEST_KEY,
group=False)
key = self.DB.get_key(TEST_NAME)
self.assertEqual(key['name'], TEST_NAME)
self.assertEqual(key['key'], TEST_KEY)
self.assertEqual(key['signature'], TEST_SIG)
self.assertEqual(key['generation'], generation)
self.assertIs(key['group'], False)
self.assertIsNone(key['expiration'])
def test_no_key(self):
# return None if a key is not in the database
self.assertIsNone(self.DB.get_key(TEST_NAME))
def test_generations(self):
another_key = 'another-key'
# set a key and make sure that the generation is set and returned
gen1 = self.DB.set_key(name=TEST_NAME,
signature=TEST_SIG,
key=TEST_KEY,
group=False)
key1 = self.DB.get_key(TEST_NAME)
self.assertEqual(key1['key'], TEST_KEY)
self.assertEqual(key1['generation'], gen1)
# set a new key for the same name and make sure that the generation is
# updated
gen2 = self.DB.set_key(name=TEST_NAME,
signature='another-sig',
key=another_key,
group=False)
key2 = self.DB.get_key(TEST_NAME)
self.assertEqual(key2['generation'], gen2)
self.assertEqual(key2['key'], another_key)
# Check that if we ask specifically for the first key we get it back
key3 = self.DB.get_key(TEST_NAME, gen1)
self.assertEqual(key3['key'], TEST_KEY)
self.assertEqual(key3['generation'], gen1)
def test_no_group_filter(self):
# install a non group key
generation = self.DB.set_key(name=TEST_NAME,
signature=TEST_SIG,
key=TEST_KEY,
group=False)
# test that if i can retrieve and specify a non-group key
key1 = self.DB.get_key(TEST_NAME)
self.assertEqual(key1['key'], TEST_KEY)
self.assertEqual(key1['generation'], generation)
key2 = self.DB.get_key(TEST_NAME, group=False)
self.assertEqual(key2['key'], TEST_KEY)
self.assertEqual(key2['generation'], generation)
# if i ask for a group key of that name then it should fail
key3 = self.DB.get_key(TEST_NAME, group=True)
self.assertIsNone(key3)
def test_with_group_filter(self):
# install a group key
generation = self.DB.set_key(name=TEST_NAME,
signature=TEST_SIG,
key=TEST_KEY,
group=True)
# i should be able to ask for and retrieve a group key
key1 = self.DB.get_key(TEST_NAME)
self.assertEqual(key1['key'], TEST_KEY)
self.assertEqual(key1['generation'], generation)
key2 = self.DB.get_key(TEST_NAME, group=True)
self.assertEqual(key2['key'], TEST_KEY)
self.assertEqual(key2['generation'], generation)
# if i ask for that key but not a group key it will fail
key3 = self.DB.get_key(TEST_NAME, group=False)
self.assertIsNone(key3)
def test_cant_change_group_status(self):
group_key_name = 'name1'
host_key_name = 'name2'
# install a host and group key
self.DB.set_key(name=group_key_name,
signature=TEST_SIG,
key=TEST_KEY,
group=True)
self.DB.set_key(name=host_key_name,
signature=TEST_SIG,
key=TEST_KEY,
group=False)
# should not be able to change a group key to a host key
self.assertRaises(exception.IntegrityError, self.DB.set_key,
name=group_key_name, signature='xxx', key='xxx',
group=False)
# should not be able to change a host key to a group key
self.assertRaises(exception.IntegrityError, self.DB.set_key,
name=host_key_name, signature='xxx', key='xxx',
group=True)

View File

@ -11,3 +11,11 @@
# 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 keystone.tests.contrib.kds.fixture import kvsdb
from keystone.tests.contrib.kds.fixture import sqlitedb
SqliteDb = sqlitedb.SqliteDb
KvsDb = kvsdb.KvsDb
__all__ = [SqliteDb, KvsDb]

View File

@ -0,0 +1,30 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 fixtures
from oslo.config import cfg
from keystone.contrib.kds.db import api as db_api
CONF = cfg.CONF
class KvsDb(fixtures.Fixture):
def setUp(self):
super(KvsDb, self).setUp()
CONF.set_override('backend', 'kvs', 'database')
db_api.reset()

View File

@ -0,0 +1,56 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 os
import fixtures
from oslo.config import cfg
from keystone.contrib.kds.db import api as db_api
from keystone.contrib.kds.db.sqlalchemy import migration
from keystone.openstack.common.db import exception as db_exception
from keystone import tests
from keystone.tests.contrib.kds import paths
CONF = cfg.CONF
class SqliteDb(fixtures.Fixture):
"""Connect to Keystone's sqlite database.
KDS is not designed with the intention that it should run within the same
database as keystone however there is nothing preventing that. There seems
to be issues regarding the conflicting CONF objects between keystone and
KDS that prevent the connection to separate databases for testing.
Therefore this fixture must simply bridge the gap back to the testing
database for keystone and setup the KDS tables.
"""
def setUp(self):
super(SqliteDb, self).setUp()
sqlite_db = os.path.abspath(paths.tmp_path('test.db'))
CONF.set_override('connection_debug', '51', 'database')
CONF.set_override('connection', 'sqlite:///%s' % sqlite_db, 'database')
db_api.reset()
tests.setup_database()
try:
migration.db_sync()
except db_exception.DbMigrationError:
migration.db_version_control(0)
migration.db_sync()

View File

@ -65,3 +65,4 @@ msgid_bugs_address = https://bugs.launchpad.net/keystone
[entry_points]
console_scripts =
kds-api = keystone.contrib.kds.cli.api:main
kds-manage = keystone.contrib.kds.cli.manage:main

View File

@ -28,6 +28,7 @@ discover
python-subunit
testrepository>=0.0.17
testtools>=0.9.32
testscenarios>=0.4
# for python-keystoneclient
# keystoneclient <0.2.1