Add Drivers for OpenStack communication

Added OpenStack Driver based on python-openstackclient.
Added nova, cinder & neutron clients.
Added UTs.

Change-Id: I6930d4a85ecf96c32d840928eebc32e87dfb81e9
Implements: Blueprint for Kingbird OpenStack Driver
This commit is contained in:
Ashish Singh 2015-11-30 18:34:21 +05:30
parent abb015f41b
commit 9ae8bfc2fe
20 changed files with 642 additions and 6 deletions

View File

@ -19,7 +19,8 @@ from oslo_context import context as oslo_ctx
class ContextBase(oslo_ctx.RequestContext):
def __init__(self, auth_token=None, user_id=None, tenant_id=None,
is_admin=False, request_id=None, overwrite=True,
user_name=None, tenant_name=None, **kwargs):
user_name=None, tenant_name=None, auth_url=None,
region=None, password=None, domain='default', **kwargs):
super(ContextBase, self).__init__(
auth_token=auth_token,
user=user_id or kwargs.get('user', None),
@ -35,12 +36,35 @@ class ContextBase(oslo_ctx.RequestContext):
overwrite=overwrite)
self.user_name = user_name
self.tenant_name = tenant_name
self.auth_url = auth_url
self.password = password
self.default_name = domain
self.region_name = region
def to_dict(self):
ctx_dict = super(ContextBase, self).to_dict()
ctx_dict.update({
'user_name': self.user_name,
'tenant_name': self.tenant_name
'tenant_name': self.tenant_name,
'auth_url': self.auth_url,
'auth_token': self.auth_token,
'auth_token_info': self.auth_token_info,
'user': self.user,
'user_domain': self.user_domain,
'user_domain_name': self.user_domain_name,
'project': self.project,
'project_name': self.project_name,
'project_domain': self.project_domain,
'project_domain_name': self.project_domain_name,
'domain': self.domain,
'domain_name': self.domain_name,
'trusts': self.trusts,
'region_name': self.region_name,
'roles': self.roles,
'show_deleted': self.show_deleted,
'is_admin': self.is_admin,
'request_id': self.request_id,
'password': self.password,
})
return ctx_dict

View File

@ -0,0 +1,6 @@
===============================
OpenStack Drivers
================================
Driver for openstack communication based on python-openstackclient.
Implements nova, cinder & neutron clients based on the same.

View File

55
kingbird/drivers/base.py Normal file
View File

@ -0,0 +1,55 @@
# 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.
'''
Base class for all drivers.
'''
import abc
import six
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class DriverBase(object):
six.add_metaclass(abc.ABCMeta)
'''Base class for all drivers.'''
def __init__(self, context):
self.context = context
@abc.abstractmethod
def get_resource_usages(self, project_id):
'''Collects resource usages for a given project
:params: project_id
:return: dictionary of corresponding resources with its usage
'''
@abc.abstractmethod
def update_quota_limits(self, project_id, new_quota):
'''Updates quota limits for a given project
:params: project_id, dictionary with the quota limits to update
:return: Nothing
'''
@abc.abstractmethod
def delete_quota_limits(self, project_id):
'''Delete quota limits for a given project
:params: project_id
:return: Nothing
'''

View File

View File

@ -0,0 +1,35 @@
# 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_log import log
from kingbird.drivers import base
LOG = log.getLogger(__name__)
class CinderClient(base.DriverBase):
'''Cinder V2 driver.'''
def __init__(self, client):
pass
def get_resource_usages(self, project_id):
'''Calcualte resources usage and return the dict'''
return {}
def update_quota_limits(self, project_id, new_quota):
'''Update the limits'''
pass
def delete_quota_limits(self, project_id):
'''Delete/Reset the limits'''
pass

View File

@ -0,0 +1,40 @@
# Copyright 2012-2013 OpenStack Foundation
#
# 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_utils import importutils
from kingbird.common import exceptions
from kingbird.drivers import base
# Ensure keystonemiddleware options are imported
importutils.import_module('keystonemiddleware.auth_token')
class KeystoneClient(base.DriverBase):
'''Keystone V3 driver.'''
def __init__(self, client):
self.keystone = client
def get_enabled_projects(self):
try:
return [current_project.id for current_project in
self.keystone.projects.list() if current_project.enabled]
except exceptions.HttpException as ex:
raise ex
def get_regions(self):
'''returns lists of cached regions'''
pass

View File

@ -0,0 +1,35 @@
# 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_log import log
from kingbird.drivers import base
LOG = log.getLogger(__name__)
class NeutronClient(base.DriverBase):
'''Neutron V2 driver.'''
def __init__(self, client):
pass
def get_resource_usages(self, project_id):
'''Calcualte resources usage and return the dict'''
return {}
def update_quota_limits(self, project_id, new_quota):
'''Update the limits'''
pass
def delete_quota_limits(self, project_id):
'''Delete/Reset the limits'''
pass

View File

@ -0,0 +1,35 @@
# 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_log import log
from kingbird.drivers import base
LOG = log.getLogger(__name__)
class NovaClient(base.DriverBase):
'''Nova V2.1 driver.'''
def __init__(self, client):
pass
def get_resource_usages(self, project_id):
'''Calcualte resources usage and return the dict'''
return {}
def update_quota_limits(self, project_id, new_quota):
'''Update the limits'''
pass
def delete_quota_limits(self, project_id):
'''Delete/Reset the limits'''
pass

View File

@ -0,0 +1,108 @@
# 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.
'''
OpenStack Driver
'''
import argparse
from openstackclient.common import clientmanager
from os_client_config import config as cloud_config
from oslo_log import log
from kingbird.common import context
from kingbird.drivers.openstack.cinder_v2 import CinderClient
from kingbird.drivers.openstack.keystone_v3 import KeystoneClient
from kingbird.drivers.openstack.neutron_v2 import NeutronClient
from kingbird.drivers.openstack.nova_v2 import NovaClient
LOG = log.getLogger(__name__)
class OpenStackDriver(object):
def __init__(self, ctx, region_name=None, token=None):
self.os_client = self._create_connection(ctx, region_name, token)
self.nova_client = NovaClient(self.os_client.compute)
self.cinder_client = CinderClient(self.os_client.volume)
self.neutron_client = NeutronClient(self.os_client.network)
self.keystone_client = KeystoneClient(self.os_client.identity)
def _create_connection(self, ctx, region_name=None, token=None):
# Create cloudmanager object to connect to openstack
if isinstance(ctx, dict):
ctx = context.RequestContext.from_dict(ctx)
kwargs = {
'auth_url': ctx.auth_url,
'default_domain': ctx.domain,
'tenant_name': ctx.tenant_name,
'username': ctx.user_name,
'password': ctx.password,
'token': token,
'region_name': region_name or ctx.region_name,
'cloud': ''
}
parser = argparse.ArgumentParser()
clientmanager.build_plugin_option_parser(parser).parse_args()
options = argparse.Namespace(**kwargs)
api_version = {}
# auth_type below defines the type of authentication to be used
cc = cloud_config.OpenStackConfig(
override_defaults={
'interface': None,
'auth_type': 'token' if token else 'password',
},
)
cloud = cc.get_one_cloud(
cloud=options.cloud,
argparse=options,
)
for mod in clientmanager.PLUGIN_MODULES:
default_version = getattr(mod, 'DEFAULT_API_VERSION', None)
option = mod.API_VERSION_OPTION.replace('os_', '')
version_opt = str(cloud.config.get(option, default_version))
if version_opt:
api = mod.API_NAME
api_version[api] = version_opt
verify = True
os_client = clientmanager.ClientManager(cli_options=cloud,
verify=verify,
api_version=api_version)
os_client.auth_ref
return os_client
def get_all_regions(self):
return self.keystone_client.get_regions()
def get_enabled_projects(self):
return self.keystone_client.get_enabled_projects()
def get_resource_usages(self, project_id):
nova_usages = self.nova_client.get_resource_usages(project_id)
neutron_usages = self.neutron_client.get_resource_usages(project_id)
cinder_usages = self.cinder_client.get_resource_usages(project_id)
return nova_usages, neutron_usages, cinder_usages
def write_quota_limits(self, project_id, limits_to_write):
self.nova_client.update_quota_limits(project_id,
limits_to_write['nova'])
self.cinder_client.update_quota_limits(project_id,
limits_to_write['cinder'])
self.neutron_client.update_quota_limits(project_id,
limits_to_write['neutron'])
def delete_quota_limit(self, project_id):
self.nova_client.delete_quota_limits(project_id)
self.neutron_client.delete_quota_limits(project_id)
self.cinder_client.delete_quota_limits(project_id)

View File

@ -16,5 +16,5 @@
from oslotest import base
class TestCase(base.BaseTestCase):
class KingbirdTestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""

View File

@ -23,7 +23,7 @@ FAKE_SERVICE = 'fake_service'
FAKE_URL = 'fake_url'
class EndpointCacheTest(base.TestCase):
class EndpointCacheTest(base.KingbirdTestCase):
def setUp(self):
super(EndpointCacheTest, self).setUp()

View File

@ -31,7 +31,7 @@ UUID2 = utils.UUID2
UUID3 = utils.UUID3
class DBAPIQuotaTest(base.TestCase):
class DBAPIQuotaTest(base.KingbirdTestCase):
def setup_dummy_db(self):
options.cfg.set_defaults(options.database_opts,
sqlite_synchronous=False)

View File

@ -0,0 +1,35 @@
# 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 kingbird.drivers.openstack import sdk
from kingbird.tests import base
class TestCinderClient(base.KingbirdTestCase):
@mock.patch.object(sdk.OpenStackDriver, '_create_connection')
def setUp(self, mock_os_client):
super(TestCinderClient, self).setUp()
self.cinder_client = mock_os_client.return_value.volume
def test_init(self):
pass
def test_get_resource_usages(self):
pass
def test_update_quota_limits(self):
pass
def test_delete_quota_limits(self):
pass

View File

@ -0,0 +1,45 @@
# 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 kingbird.drivers.openstack import keystone_v3
from kingbird.drivers.openstack import sdk
from kingbird.tests import base
class TestKeystoneClient(base.KingbirdTestCase):
@mock.patch.object(sdk.OpenStackDriver, '_create_connection')
def setUp(self, mock_os_client):
super(TestKeystoneClient, self).setUp()
self.keystone_client = mock_os_client.return_value.identity
def test_init(self):
keystone_client = keystone_v3.KeystoneClient(self.keystone_client)
self.assertIsNotNone(keystone_client)
def test_get_enabled_projects(self):
raised = False
try:
self.keystone_client.get_enabled_projects()
except Exception:
raised = True
self.assertFalse(raised, 'get_enabled_projects Failed')
def test_get_regions(self):
raised = False
try:
self.keystone_client.get_regions()
except Exception:
raised = True
self.assertFalse(raised, 'get_regions Failed')

View File

@ -0,0 +1,35 @@
# 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 kingbird.drivers.openstack import sdk
from kingbird.tests import base
class TestNeutronClient(base.KingbirdTestCase):
@mock.patch.object(sdk.OpenStackDriver, '_create_connection')
def setUp(self, mock_os_client):
super(TestNeutronClient, self).setUp()
self.neutron_client = mock_os_client.return_value.network
def test_init(self):
pass
def test_get_resource_usages(self):
pass
def test_update_quota_limits(self):
pass
def test_delete_quota_limits(self):
pass

View File

@ -0,0 +1,35 @@
# 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 kingbird.drivers.openstack import sdk
from kingbird.tests import base
class TestNovaClient(base.KingbirdTestCase):
@mock.patch.object(sdk.OpenStackDriver, '_create_connection')
def setUp(self, mock_os_client):
super(TestNovaClient, self).setUp()
self.compute_client = mock_os_client.return_value.compute
def test_init(self):
pass
def test_get_resource_usages(self):
pass
def test_update_quota_limits(self):
pass
def test_delete_quota_limits(self):
pass

View File

@ -0,0 +1,147 @@
# 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 kingbird.drivers.openstack import sdk
from kingbird.tests import base
from kingbird.tests import utils
class TestOpenStackDriver(base.KingbirdTestCase):
def setUp(self):
super(TestOpenStackDriver, self).setUp()
self.context = utils.dummy_context()
self.region_name = 'RegionOne'
self.os_client = None
@mock.patch.object(sdk.OpenStackDriver, '_create_connection')
@mock.patch.object(sdk, 'NovaClient')
@mock.patch.object(sdk, 'NeutronClient')
@mock.patch.object(sdk, 'CinderClient')
@mock.patch.object(sdk, 'KeystoneClient')
def test_init(self, mock_keystone_client, mock_cinder_client,
mock_neutron_client, mock_nova_client, mock_os_client):
sdk.OpenStackDriver(self.context, self.region_name)
self.os_client = mock_os_client.return_value
mock_os_client.assert_called_once_with(self.context,
self.region_name, None)
mock_nova_client.assert_called_once_with(self.os_client.compute)
mock_neutron_client.assert_called_once_with(self.os_client.network)
mock_cinder_client.assert_called_once_with(self.os_client.volume)
mock_keystone_client.assert_called_once_with(self.os_client.identity)
@mock.patch.object(sdk, 'argparse')
@mock.patch.object(sdk, 'cloud_config')
@mock.patch.object(sdk, 'clientmanager')
def test_connection(self, mock_cm, mock_cloud_config, mock_argparse):
os_driver = sdk.OpenStackDriver(self.context, self.region_name)
os_driver._create_connection(self.context,
self.region_name)
mock_argparse.ArgumentParser.assert_called_with()
parser = mock_argparse.return_value
cloud = mock_cloud_config.OpenStackConfig().get_one_cloud.return_value
mock_cloud_config.OpenStackConfig.assert_called_with()
mock_cm.build_plugin_option_parser(parser
).parse_args.assert_called_with()
mock_cm.ClientManager.assert_called_with(cli_options=cloud,
api_version={}, verify=True)
@mock.patch.object(sdk, 'NovaClient')
@mock.patch.object(sdk, 'NeutronClient')
@mock.patch.object(sdk, 'CinderClient')
@mock.patch.object(sdk, 'argparse')
@mock.patch.object(sdk, 'cloud_config')
@mock.patch.object(sdk, 'clientmanager')
def test_get_resource_usages(self, mock_clientmanager, mock_cloud_config,
mock_argparse, mock_cinder_client,
mock_neutron_client, mock_nova_client):
project_id = 'fake_project'
os_driver = sdk.OpenStackDriver(self.context, self.region_name)
total_quotas = os_driver.get_resource_usages(project_id)
mock_nova_client().get_resource_usages.assert_called_once_with(
project_id)
mock_neutron_client().get_resource_usages.assert_called_once_with(
project_id)
mock_cinder_client().get_resource_usages.assert_called_once_with(
project_id)
self.assertIsNotNone(total_quotas)
@mock.patch.object(sdk, 'NovaClient')
@mock.patch.object(sdk, 'NeutronClient')
@mock.patch.object(sdk, 'CinderClient')
@mock.patch.object(sdk, 'argparse')
@mock.patch.object(sdk, 'cloud_config')
@mock.patch.object(sdk, 'clientmanager')
def test_write_quota_limits(self, mock_clientmanager, mock_cloud_config,
mock_argparse, mock_cinder_client,
mock_network_client, mock_nova_client):
project_id = 'fake_project'
write_limits = {}
write_limits['nova'] = {'ram': 1222, 'vcpus': 10, 'instances': 7}
write_limits['cinder'] = {'disk': 1222}
write_limits['neutron'] = {'network': 10, 'subnet': 10}
os_driver = sdk.OpenStackDriver(self.context, self.region_name)
os_driver.write_quota_limits(project_id, write_limits)
mock_nova_client(
).update_quota_limits.assert_called_once_with(project_id,
write_limits['nova'])
mock_network_client(
).update_quota_limits.assert_called_once_with(project_id,
write_limits['neutron'])
mock_cinder_client(
).update_quota_limits.assert_called_once_with(project_id,
write_limits['cinder'])
@mock.patch.object(sdk, 'NovaClient')
@mock.patch.object(sdk, 'NeutronClient')
@mock.patch.object(sdk, 'CinderClient')
@mock.patch.object(sdk, 'argparse')
@mock.patch.object(sdk, 'cloud_config')
@mock.patch.object(sdk, 'clientmanager')
def test_delete_quota_limit(self, mock_clientmanager, mock_cloud_config,
mock_argparse, mock_cinder_client,
mock_network_client, mock_nova_client):
project_id = 'fake_project'
os_driver = sdk.OpenStackDriver(self.context, self.region_name)
os_driver.delete_quota_limit(project_id)
mock_nova_client().delete_quota_limits.assert_called_once_with(
project_id)
mock_network_client().delete_quota_limits.assert_called_once_with(
project_id)
mock_cinder_client().delete_quota_limits.assert_called_once_with(
project_id)
@mock.patch.object(sdk, 'KeystoneClient')
@mock.patch.object(sdk, 'argparse')
@mock.patch.object(sdk, 'cloud_config')
@mock.patch.object(sdk, 'clientmanager')
def test_get_all_regions(self, mock_clientmanager, mock_cloud_config,
mock_argparse, mock_keystone_client):
os_driver = sdk.OpenStackDriver(self.context, self.region_name)
os_driver.get_all_regions()
mock_keystone_client().get_regions.assert_called_once_with()
@mock.patch.object(sdk, 'KeystoneClient')
@mock.patch.object(sdk, 'argparse')
@mock.patch.object(sdk, 'cloud_config')
@mock.patch.object(sdk, 'clientmanager')
def test_get_enabled_projects(self, mock_clientmanager, mock_cloud_config,
mock_argparse, mock_keystone_client):
os_driver = sdk.OpenStackDriver(self.context, self.region_name)
os_driver.get_enabled_projects()
mock_keystone_client().get_enabled_projects.assert_called_once_with()

View File

@ -22,6 +22,6 @@ Tests for `kingbird` module.
from kingbird.tests import base
class TestKingbird(base.TestCase):
class TestKingbird(base.KingbirdTestCase):
def test_something(self):
pass

View File

@ -41,3 +41,4 @@ oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0
oslo.versionedobjects>=0.9.0
SQLAlchemy<1.1.0,>=0.9.9
sqlalchemy-migrate>=0.9.6
python-openstackclient>=1.5.0