Add Keystone service resource methods

This change adds keystone service resource methods to
OperatorClouds.

Only Keystone v2 API is currently supported

Change-Id: I5d0b44664a6839502d86fed8d68717b086c34a81
This commit is contained in:
Davide Guerri 2015-04-27 00:03:39 +01:00 committed by Monty Taylor
parent 3fef40f07e
commit da6929777d
5 changed files with 363 additions and 0 deletions

View File

@ -2724,3 +2724,103 @@ class OperatorCloud(OpenStackCloud):
self.log.debug(
"Failed to delete instance_info", exc_info=True)
raise OpenStackCloudException(str(e))
def create_service(self, name, service_type, description=None):
"""Create a service.
:param name: Service name.
:param service_type: Service type.
:param description: Service description (optional).
:returns: a dict containing the services description, i.e. the
following attributes::
- id: <service id>
- name: <service name>
- service_type: <service type>
- description: <service description>
:raises: ``OpenStackCloudException`` if something goes wrong during the
openstack API call.
"""
try:
service = self.manager.submitTask(_tasks.ServiceCreate(
name=name, service_type=service_type,
description=description))
except Exception as e:
self.log.debug(
"Failed to create service {name}".format(name=name),
exc_info=True)
raise OpenStackCloudException(str(e))
return meta.obj_to_dict(service)
def list_services(self):
"""List all Keystone services.
:returns: a list of dict containing the services description.
:raises: ``OpenStackCloudException`` if something goes wrong during the
openstack API call.
"""
try:
services = self.manager.submitTask(_tasks.ServiceList())
except Exception as e:
self.log.debug("Failed to list services", exc_info=True)
raise OpenStackCloudException(str(e))
return meta.obj_list_to_dict(services)
def search_services(self, name_or_id=None, filters=None):
"""Search Keystone services.
:param name_or_id: Name or id of the desired service.
:param filters: a dict containing additional filters to use. e.g.
{'service_type': 'network'}.
:returns: a list of dict containing the services description.
:raises: ``OpenStackCloudException`` if something goes wrong during the
openstack API call.
"""
services = self.list_services()
return _utils._filter_list(services, name_or_id, filters)
def get_service(self, name_or_id, filters=None):
"""Get exactly one Keystone service.
:param name_or_id: Name or id of the desired service.
:param filters: a dict containing additional filters to use. e.g.
{'service_type': 'network'}
:returns: a dict containing the services description, i.e. the
following attributes::
- id: <service id>
- name: <service name>
- service_type: <service type>
- description: <service description>
:raises: ``OpenStackCloudException`` if something goes wrong during the
openstack API call or if multiple matches are found.
"""
return _utils._get_entity(self.search_services, name_or_id, filters)
def delete_service(self, name_or_id):
"""Delete a Keystone service.
:param name_or_id: Service name or id.
:returns: None
:raises: ``OpenStackCloudException`` if something goes wrong during
the openstack API call
"""
service = self.get_service(name_or_id=name_or_id)
if service is None:
return
try:
self.manager.submitTask(_tasks.ServiceDelete(id=service['id']))
except Exception as e:
self.log.debug(
"Failed to delete service {id}".format(id=service['id']),
exc_info=True)
raise OpenStackCloudException(str(e))

View File

@ -316,3 +316,18 @@ class MachineSetPower(task_manager.Task):
class MachineSetProvision(task_manager.Task):
def main(self, client):
return client.ironic_client.node.set_provision_state(**self.args)
class ServiceCreate(task_manager.Task):
def main(self, client):
return client.keystone_client.services.create(**self.args)
class ServiceList(task_manager.Task):
def main(self, client):
return client.keystone_client.services.list()
class ServiceDelete(task_manager.Task):
def main(self, client):
return client.keystone_client.services.delete(**self.args)

View File

@ -51,6 +51,14 @@ class FakeServer(object):
self.status = status
class FakeService(object):
def __init__(self, id, name, type, description=''):
self.id = id
self.name = name
self.type = type
self.description = description
class FakeUser(object):
def __init__(self, id, email, name):
self.id = id

View File

@ -0,0 +1,108 @@
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
#
# 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.
"""
test_services
----------------------------------
Functional tests for `shade` service resource.
"""
import string
import random
from shade import operator_cloud
from shade.exc import OpenStackCloudException
from shade.tests import base
class TestServices(base.TestCase):
service_attributes = ['id', 'name', 'type', 'description']
def setUp(self):
super(TestServices, self).setUp()
# Shell should have OS-* envvars from openrc, typically loaded by job
self.operator_cloud = operator_cloud()
# Generate a random name for services in this test
self.new_service_name = 'test_' + ''.join(
random.choice(string.ascii_lowercase) for _ in range(5))
self.addCleanup(self._cleanup_services)
def _cleanup_services(self):
exception_list = list()
for s in self.operator_cloud.list_services():
if s['name'].startswith(self.new_service_name):
try:
self.operator_cloud.delete_service(name_or_id=s['id'])
except Exception as e:
# We were unable to delete a service, let's try with next
exception_list.append(e)
continue
if exception_list:
# Raise an error: we must make users aware that something went
# wrong
raise OpenStackCloudException('\n'.join(exception_list))
def test_create_service(self):
service = self.operator_cloud.create_service(
name=self.new_service_name + '_create', service_type='test_type',
description='this is a test description')
self.assertIsNotNone(service.get('id'))
def test_list_services(self):
service = self.operator_cloud.create_service(
name=self.new_service_name + '_list', service_type='test_type')
observed_services = self.operator_cloud.list_services()
self.assertIsInstance(observed_services, list)
found = False
for s in observed_services:
# Test all attributes are returned
if s['id'] == service['id']:
self.assertEqual(self.new_service_name + '_list',
s.get('name'))
self.assertEqual('test_type', s.get('type'))
found = True
self.assertTrue(found, msg='new service not found in service list!')
def test_delete_service_by_name(self):
# Test delete by name
service = self.operator_cloud.create_service(
name=self.new_service_name + '_delete_by_name',
service_type='test_type')
self.operator_cloud.delete_service(name_or_id=service['name'])
observed_services = self.operator_cloud.list_services()
found = False
for s in observed_services:
if s['id'] == service['id']:
found = True
break
self.failUnlessEqual(False, found, message='service was not deleted!')
def test_delete_service_by_id(self):
# Test delete by id
service = self.operator_cloud.create_service(
name=self.new_service_name + '_delete_by_id',
service_type='test_type')
self.operator_cloud.delete_service(name_or_id=service['id'])
observed_services = self.operator_cloud.list_services()
found = False
for s in observed_services:
if s['id'] == service['id']:
found = True
self.failUnlessEqual(False, found, message='service was not deleted!')

View File

@ -0,0 +1,132 @@
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
#
# 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.
"""
test_cloud_services
----------------------------------
Tests Keystone services commands.
"""
from mock import patch
from shade import OpenStackCloudException
from shade import OperatorCloud
from shade.tests.fakes import FakeService
from shade.tests.unit import base
class CloudServices(base.TestCase):
mock_services = [
{'id': 'id1', 'name': 'service1', 'type': 'type1',
'description': 'desc1'},
{'id': 'id2', 'name': 'service2', 'type': 'type2',
'description': 'desc2'},
{'id': 'id3', 'name': 'service3', 'type': 'type2',
'description': 'desc3'},
{'id': 'id4', 'name': 'service4', 'type': 'type3',
'description': 'desc4'}
]
def setUp(self):
super(CloudServices, self).setUp()
self.client = OperatorCloud("op_cloud", {})
self.mock_ks_services = [FakeService(**kwa) for kwa in
self.mock_services]
@patch.object(OperatorCloud, 'keystone_client')
def test_create_service(self, mock_keystone_client):
kwargs = {
'name': 'a service',
'service_type': 'network',
'description': 'This is a test service'
}
self.client.create_service(**kwargs)
mock_keystone_client.services.create.assert_called_with(**kwargs)
@patch.object(OperatorCloud, 'keystone_client')
def test_list_services(self, mock_keystone_client):
mock_keystone_client.services.list.return_value = \
self.mock_ks_services
services = self.client.list_services()
mock_keystone_client.services.list.assert_called_with()
self.assertItemsEqual(self.mock_services, services)
@patch.object(OperatorCloud, 'keystone_client')
def test_get_service(self, mock_keystone_client):
mock_keystone_client.services.list.return_value = \
self.mock_ks_services
# Search by id
service = self.client.get_service(name_or_id='id4')
# test we are getting exactly 1 element
self.assertEqual(service, self.mock_services[3])
# Search by name
service = self.client.get_service(name_or_id='service2')
# test we are getting exactly 1 element
self.assertEqual(service, self.mock_services[1])
# Not found
service = self.client.get_service(name_or_id='blah!')
self.assertIs(None, service)
# Multiple matches
# test we are getting an Exception
self.assertRaises(OpenStackCloudException, self.client.get_service,
name_or_id=None, filters={'type': 'type2'})
@patch.object(OperatorCloud, 'keystone_client')
def test_search_services(self, mock_keystone_client):
mock_keystone_client.services.list.return_value = \
self.mock_ks_services
# Search by id
services = self.client.search_services(name_or_id='id4')
# test we are getting exactly 1 element
self.assertEqual(1, len(services))
self.assertEqual(services, [self.mock_services[3]])
# Search by name
services = self.client.search_services(name_or_id='service2')
# test we are getting exactly 1 element
self.assertEqual(1, len(services))
self.assertEqual(services, [self.mock_services[1]])
# Not found
services = self.client.search_services(name_or_id='blah!')
self.assertEqual(0, len(services))
# Multiple matches
services = self.client.search_services(
filters={'type': 'type2'})
# test we are getting exactly 2 elements
self.assertEqual(2, len(services))
self.assertEqual(services, [self.mock_services[1],
self.mock_services[2]])
@patch.object(OperatorCloud, 'keystone_client')
def test_delete_service(self, mock_keystone_client):
mock_keystone_client.services.list.return_value = \
self.mock_ks_services
# Delete by name
self.client.delete_service(name_or_id='service3')
mock_keystone_client.services.delete.assert_called_with(id='id3')
# Delete by id
self.client.delete_service('id1')
mock_keystone_client.services.delete.assert_called_with(id='id1')