Remove service object

Following on from removing the k8s specific APIs in
I1f6f04a35dfbb39f217487fea104ded035b75569 the objects associated with
these APIs need removal.

Remove the service object, drop the db table and remove references to
the service object.

Change-Id: I4f06bb779caa0ad369a2b96b4714e1bf2db8acc6
Partially-Implements: blueprint delete-container-endpoint
changes/43/320943/2
Tom Cammann 7 years ago
parent 220e74da1f
commit 3e02840628
  1. 7
      etc/magnum/policy.json
  2. 21
      magnum/conductor/api.py
  3. 153
      magnum/conductor/handlers/k8s_conductor.py
  4. 82
      magnum/db/api.py
  5. 28
      magnum/db/sqlalchemy/alembic/versions/085e601a39f6_remove_service.py
  6. 104
      magnum/db/sqlalchemy/api.py
  7. 20
      magnum/db/sqlalchemy/models.py
  8. 3
      magnum/objects/__init__.py
  9. 137
      magnum/objects/service.py
  10. 7
      magnum/tests/fake_policy.py
  11. 6
      magnum/tests/unit/api/controllers/v1/test_bay.py
  12. 201
      magnum/tests/unit/conductor/handlers/test_k8s_conductor.py
  13. 27
      magnum/tests/unit/conductor/test_rpcapi.py
  14. 7
      magnum/tests/unit/conductor/test_utils.py
  15. 18
      magnum/tests/unit/db/test_bay.py
  16. 178
      magnum/tests/unit/db/test_service.py
  17. 32
      magnum/tests/unit/db/utils.py
  18. 1
      magnum/tests/unit/objects/test_objects.py
  19. 100
      magnum/tests/unit/objects/test_service.py
  20. 27
      magnum/tests/unit/objects/utils.py

@ -27,13 +27,6 @@
"rc:get_all": "rule:default",
"rc:update": "rule:default",
"service:create": "rule:default",
"service:delete": "rule:default",
"service:detail": "rule:default",
"service:get": "rule:default",
"service:get_all": "rule:default",
"service:update": "rule:default",
"container:create": "rule:admin_or_user",
"container:delete": "rule:admin_or_user",
"container:detail": "rule:default",

@ -40,27 +40,6 @@ class API(rpc_service.API):
def bay_update(self, bay):
return self._call('bay_update', bay=bay)
# Service Operations
def service_create(self, service):
return self._call('service_create', service=service)
def service_update(self, service_ident, bay_ident, manifest):
return self._call('service_update', service_ident=service_ident,
bay_ident=bay_ident, manifest=manifest)
def service_list(self, context, bay_ident):
return self._call('service_list', bay_ident=bay_ident)
def service_delete(self, service_ident, bay_ident):
return self._call('service_delete',
service_ident=service_ident,
bay_ident=bay_ident)
def service_show(self, context, service_ident, bay_ident):
return self._call('service_show', service_ident=service_ident,
bay_ident=bay_ident)
# ReplicationController Operations
def rc_create(self, rc):

@ -38,159 +38,6 @@ class Handler(object):
def __init__(self):
super(Handler, self).__init__()
def service_create(self, context, service):
LOG.debug("service_create")
bay = conductor_utils.retrieve_bay(context, service.bay_uuid)
self.k8s_api = k8s.create_k8s_api(context, bay)
manifest = k8s_manifest.parse(service.manifest)
try:
resp = self.k8s_api.create_namespaced_service(body=manifest,
namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
if resp is None:
raise exception.ServiceCreationFailed(bay_uuid=service.bay_uuid)
service['uuid'] = resp.metadata.uid
service['name'] = resp.metadata.name
service['labels'] = ast.literal_eval(resp.metadata.labels)
service['selector'] = ast.literal_eval(resp.spec.selector)
service['ip'] = resp.spec.cluster_ip
service_value = []
for p in resp.spec.ports:
ports = p.to_dict()
if not ports['name']:
ports['name'] = 'k8s-service'
service_value.append(ports)
service['ports'] = service_value
return service
def service_update(self, context, service_ident, bay_ident, manifest):
LOG.debug("service_update %s", service_ident)
bay = conductor_utils.retrieve_bay(context, bay_ident)
self.k8s_api = k8s.create_k8s_api(context, bay)
if uuidutils.is_uuid_like(service_ident):
service = objects.Service.get_by_uuid(context,
service_ident,
bay.uuid,
self.k8s_api)
else:
service = objects.Service.get_by_name(context,
service_ident,
bay.uuid,
self.k8s_api)
service_ident = service.name
try:
resp = self.k8s_api.replace_namespaced_service(
name=str(service_ident),
body=manifest,
namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
if resp is None:
raise exception.ServiceNotFound(service=service.uuid)
service['uuid'] = resp.metadata.uid
service['name'] = resp.metadata.name
service['project_id'] = context.project_id
service['user_id'] = context.user_id
service['bay_uuid'] = bay.uuid
service['labels'] = ast.literal_eval(resp.metadata.labels)
if not resp.spec.selector:
service['selector'] = {}
else:
service['selector'] = ast.literal_eval(resp.spec.selector)
service['ip'] = resp.spec.cluster_ip
service_value = []
for p in resp.spec.ports:
ports = p.to_dict()
if not ports['name']:
ports['name'] = 'k8s-service'
service_value.append(ports)
service['ports'] = service_value
return service
def service_delete(self, context, service_ident, bay_ident):
LOG.debug("service_delete %s", service_ident)
bay = conductor_utils.retrieve_bay(context, bay_ident)
self.k8s_api = k8s.create_k8s_api(context, bay)
if uuidutils.is_uuid_like(service_ident):
service = objects.Service.get_by_uuid(context, service_ident,
bay.uuid, self.k8s_api)
service_name = service.name
else:
service_name = service_ident
if conductor_utils.object_has_stack(context, bay.uuid):
try:
self.k8s_api.delete_namespaced_service(name=str(service_name),
namespace='default')
except rest.ApiException as err:
if err.status == 404:
pass
else:
raise exception.KubernetesAPIFailed(err=err)
def service_show(self, context, service_ident, bay_ident):
LOG.debug("service_show %s", service_ident)
bay = conductor_utils.retrieve_bay(context, bay_ident)
self.k8s_api = k8s.create_k8s_api(context, bay)
if uuidutils.is_uuid_like(service_ident):
service = objects.Service.get_by_uuid(context, service_ident,
bay.uuid, self.k8s_api)
else:
service = objects.Service.get_by_name(context, service_ident,
bay.uuid, self.k8s_api)
return service
def service_list(self, context, bay_ident):
bay = conductor_utils.retrieve_bay(context, bay_ident)
self.k8s_api = k8s.create_k8s_api(context, bay)
try:
resp = self.k8s_api.list_namespaced_service(namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
if resp is None:
raise exception.ServiceListNotFound(bay_uuid=bay.uuid)
services = []
for service_entry in resp.items:
service = {}
service['uuid'] = service_entry.metadata.uid
service['name'] = service_entry.metadata.name
service['project_id'] = context.project_id
service['user_id'] = context.user_id
service['bay_uuid'] = bay.uuid
service['labels'] = ast.literal_eval(
service_entry.metadata.labels)
if not service_entry.spec.selector:
service['selector'] = {}
else:
service['selector'] = ast.literal_eval(
service_entry.spec.selector)
service['ip'] = service_entry.spec.cluster_ip
service_value = []
for p in service_entry.spec.ports:
ports = p.to_dict()
if not ports['name']:
ports['name'] = 'k8s-service'
service_value.append(ports)
service['ports'] = service_value
service_obj = objects.Service(context, **service)
services.append(service_obj)
return services
# Replication Controller Operations
def rc_create(self, context, rc):
LOG.debug("rc_create")

@ -288,88 +288,6 @@ class Connection(object):
:raises: ContainerNotFound
"""
@abc.abstractmethod
def get_service_list(self, context, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get matching services.
Return a list of the specified columns for all services that match the
specified filters.
:param context: The security context
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of services to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
:returns: A list of tuples of the specified columns.
"""
@abc.abstractmethod
def create_service(self, values):
"""Create a new service.
:param values: A dict containing several items used to identify
and track the service, and several dicts which are
passed into the Drivers when managing this service.
For example:
::
{
'uuid': uuidutils.generate_uuid(),
'name': 'example',
'type': 'virt'
}
:returns: A service.
"""
@abc.abstractmethod
def get_service_by_id(self, context, service_id):
"""Return a service.
:param context: The security context
:param service_id: The id of a service.
:returns: A service.
"""
@abc.abstractmethod
def get_service_by_uuid(self, context, service_uuid):
"""Return a service.
:param context: The security context
:param service_uuid: The uuid of a service.
:returns: A service.
"""
@abc.abstractmethod
def get_service_by_name(self, context, service_name):
"""Return a service.
:param context: The security context
:param service_name: The name of a service
:returns: A service.
"""
@abc.abstractmethod
def destroy_service(self, service_id):
"""Destroy a service and all associated interfaces.
:param service_id: The id or uuid of a service.
"""
@abc.abstractmethod
def update_service(self, service_id, values):
"""Update properties of a service.
:param service_id: The id or uuid of a service.
:returns: A service.
:raises: ServiceNotFound
"""
@abc.abstractmethod
def get_rc_list(self, context, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):

@ -0,0 +1,28 @@
# 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.
"""remove service object
Revision ID: 085e601a39f6
Revises: a1136d335540
Create Date: 2016-05-25 12:05:30.790282
"""
# revision identifiers, used by Alembic.
revision = '085e601a39f6'
down_revision = 'a1136d335540'
from alembic import op
def upgrade():
op.drop_table('service')

@ -193,11 +193,6 @@ class Connection(api.Connection):
def destroy_bay(self, bay_id):
def destroy_bay_resources(session, bay_uuid):
"""Checks whether the bay does not have resources."""
query = model_query(models.Service, session=session)
query = self._add_services_filters(query, {'bay_uuid': bay_uuid})
if query.count() != 0:
query.delete()
query = model_query(models.ReplicationController, session=session)
query = self._add_rcs_filters(query, {'bay_uuid': bay_uuid})
if query.count() != 0:
@ -472,105 +467,6 @@ class Connection(api.Connection):
ref.update(values)
return ref
def _add_services_filters(self, query, filters):
if filters is None:
filters = {}
if 'bay_uuid' in filters:
query = query.filter_by(bay_uuid=filters['bay_uuid'])
if 'name' in filters:
query = query.filter_by(name=filters['name'])
if 'ip' in filters:
query = query.filter_by(ip=filters['ip'])
if 'ports' in filters:
query = query.filter_by(ports=filters['ports'])
return query
def get_service_list(self, context, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Service)
query = self._add_tenant_filters(context, query)
query = self._add_services_filters(query, filters)
return _paginate_query(models.Service, limit, marker,
sort_key, sort_dir, query)
def create_service(self, values):
# ensure defaults are present for new services
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
service = models.Service()
service.update(values)
try:
service.save()
except db_exc.DBDuplicateEntry:
raise exception.ServiceAlreadyExists(uuid=values['uuid'])
return service
def get_service_by_id(self, context, service_id):
query = model_query(models.Service)
query = self._add_tenant_filters(context, query)
query = query.filter_by(id=service_id)
try:
return query.one()
except NoResultFound:
raise exception.ServiceNotFound(service=service_id)
def get_service_by_uuid(self, context, service_uuid):
query = model_query(models.Service)
query = self._add_tenant_filters(context, query)
query = query.filter_by(uuid=service_uuid)
try:
return query.one()
except NoResultFound:
raise exception.ServiceNotFound(service=service_uuid)
def get_service_by_name(self, context, service_name):
query = model_query(models.Service)
query = self._add_tenant_filters(context, query)
query = query.filter_by(name=service_name)
try:
return query.one()
except MultipleResultsFound:
raise exception.Conflict('Multiple services exist with same name.'
' Please use the service uuid instead.')
except NoResultFound:
raise exception.ServiceNotFound(service=service_name)
def destroy_service(self, service_id):
session = get_session()
with session.begin():
query = model_query(models.Service, session=session)
query = add_identity_filter(query, service_id)
count = query.delete()
if count != 1:
raise exception.ServiceNotFound(service_id)
def update_service(self, service_id, values):
# NOTE(dtantsur): this can lead to very strange errors
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing Service.")
raise exception.InvalidParameterValue(err=msg)
return self._do_update_service(service_id, values)
def _do_update_service(self, service_id, values):
session = get_session()
with session.begin():
query = model_query(models.Service, session=session)
query = add_identity_filter(query, service_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.ServiceNotFound(service=service_id)
if 'provision_state' in values:
values['provision_updated_at'] = timeutils.utcnow()
ref.update(values)
return ref
def _add_rcs_filters(self, query, filters):
if filters is None:
filters = {}

@ -195,26 +195,6 @@ class Container(Base):
environment = Column(JSONEncodedDict)
class Service(Base):
"""Represents a software service."""
__tablename__ = 'service'
__table_args__ = (
schema.UniqueConstraint('uuid', name='uniq_service0uuid'),
table_args()
)
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
name = Column(String(255))
bay_uuid = Column(String(36))
labels = Column(JSONEncodedDict)
selector = Column(JSONEncodedDict)
ip = Column(String(36))
ports = Column(JSONEncodedList)
project_id = Column(String(255))
user_id = Column(String(255))
class ReplicationController(Base):
"""Represents a pod replication controller."""

@ -18,7 +18,6 @@ from magnum.objects import certificate
from magnum.objects import container
from magnum.objects import magnum_service
from magnum.objects import replicationcontroller as rc
from magnum.objects import service
from magnum.objects import x509keypair
@ -27,7 +26,6 @@ Bay = bay.Bay
BayModel = baymodel.BayModel
MagnumService = magnum_service.MagnumService
ReplicationController = rc.ReplicationController
Service = service.Service
X509KeyPair = x509keypair.X509KeyPair
Certificate = certificate.Certificate
__all__ = (Bay,
@ -35,6 +33,5 @@ __all__ = (Bay,
Container,
MagnumService,
ReplicationController,
Service,
X509KeyPair,
Certificate)

@ -1,137 +0,0 @@
# 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 ast
from k8sclient.client import rest
from oslo_versionedobjects import fields
from magnum.common import exception
from magnum.db import api as dbapi
from magnum.objects import base
from magnum.objects import fields as magnum_fields
@base.MagnumObjectRegistry.register
class Service(base.MagnumPersistentObject, base.MagnumObject,
base.MagnumObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'id': fields.IntegerField(),
'uuid': fields.StringField(nullable=True),
'name': fields.StringField(nullable=True),
'project_id': fields.StringField(nullable=True),
'user_id': fields.StringField(nullable=True),
'bay_uuid': fields.StringField(nullable=True),
'labels': fields.DictOfStringsField(nullable=True),
'selector': fields.DictOfStringsField(nullable=True),
'ip': fields.StringField(nullable=True),
'ports': magnum_fields.ListOfDictsField(nullable=True),
'manifest_url': fields.StringField(nullable=True),
'manifest': fields.StringField(nullable=True),
}
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid, bay_uuid, k8s_api):
"""Find a service based on service uuid and UUID of the Bay
:param context: Security context
:param uuid: the uuid of a service.
:param bay_uuid: the UUID of the Bay
:param k8s_api: k8s API object
:returns: a :class:`Service` object.
"""
try:
resp = k8s_api.list_namespaced_service(namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
if resp is None:
raise exception.ServiceListNotFound(bay_uuid=bay_uuid)
service = {}
for service_entry in resp.items:
if service_entry.metadata.uid == uuid:
service['uuid'] = service_entry.metadata.uid
service['name'] = service_entry.metadata.name
service['project_id'] = context.project_id
service['user_id'] = context.user_id
service['bay_uuid'] = bay_uuid
service['labels'] = ast.literal_eval(
service_entry.metadata.labels)
if not service_entry.spec.selector:
service['selector'] = {}
else:
service['selector'] = ast.literal_eval(
service_entry.spec.selector)
service['ip'] = service_entry.spec.cluster_ip
service_value = []
for p in service_entry.spec.ports:
ports = p.to_dict()
if not ports['name']:
ports['name'] = 'k8s-service'
service_value.append(ports)
service['ports'] = service_value
service_obj = Service(context, **service)
return service_obj
raise exception.ServiceNotFound(service=uuid)
@base.remotable_classmethod
def get_by_name(cls, context, name, bay_uuid, k8s_api):
"""Find a service based on service name and UUID of the Bay
:param context: Security context
:param name: the name of a service.
:param bay_uuid: the UUID of the Bay
:param k8s_api: k8s API object
:returns: a :class:`Service` object.
"""
try:
resp = k8s_api.read_namespaced_service(name=name,
namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
if resp is None:
raise exception.ServiceNotFound(service=name)
service = {}
service['uuid'] = resp.metadata.uid
service['name'] = resp.metadata.name
service['project_id'] = context.project_id
service['user_id'] = context.user_id
service['bay_uuid'] = bay_uuid
service['labels'] = ast.literal_eval(resp.metadata.labels)
if not resp.spec.selector:
service['selector'] = {}
else:
service['selector'] = ast.literal_eval(resp.spec.selector)
service['ip'] = resp.spec.cluster_ip
service_value = []
for p in resp.spec.ports:
ports = p.to_dict()
if not ports['name']:
ports['name'] = 'k8s-service'
service_value.append(ports)
service['ports'] = service_value
service_obj = Service(context, **service)
return service_obj

@ -41,13 +41,6 @@ policy_data = """
"rc:get_all": "",
"rc:update": "",
"service:create": "",
"service:delete": "",
"service:detail": "",
"service:get": "",
"service:get_all": "",
"service:update": "",
"container:create": "",
"container:delete": "",
"container:detail": "",

@ -647,12 +647,6 @@ class TestDelete(api_base.FunctionalTest):
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_delete_bay_with_services(self):
obj_utils.create_test_service(self.context, bay_uuid=self.bay.uuid)
response = self.delete('/bays/%s' % self.bay.uuid,
expect_errors=True)
self.assertEqual(204, response.status_int)
def test_delete_bay_with_replication_controllers(self):
obj_utils.create_test_rc(self.context, bay_uuid=self.bay.uuid)
response = self.delete('/bays/%s' % self.bay.uuid,

@ -27,9 +27,6 @@ class TestK8sConductor(base.TestCase):
super(TestK8sConductor, self).setUp()
self.kube_handler = k8s_conductor.Handler()
def mock_service(self):
return objects.Service({})
def mock_rc(self):
return objects.ReplicationController({})
@ -39,137 +36,6 @@ class TestK8sConductor(base.TestCase):
def mock_baymodel(self):
return objects.BayModel({})
@patch('magnum.objects.Bay.get_by_name')
@patch('magnum.conductor.k8s_api.create_k8s_api')
@patch('ast.literal_eval')
def test_service_create_with_success(self,
mock_ast,
mock_kube_api,
mock_get_bay):
fake_service = mock.MagicMock()
fake_service.name = 'test-name'
expected_service = mock.MagicMock()
expected_service.bay_uuid = 'test-bay-uuid'
expected_service.name = 'test-name'
expected_service.uuid = 'test-uuid'
manifest = {"key": "value"}
expected_service.manifest = '{"key": "value"}'
mock_ast.return_value = {}
with patch('magnum.conductor.k8s_api.create_k8s_api') as \
mock_kube_api:
self.kube_handler.service_create(self.context, expected_service)
(mock_kube_api.return_value.create_namespaced_service
.assert_called_once_with(body=manifest, namespace='default'))
@patch('magnum.objects.Bay.get_by_name')
def test_service_create_with_failure(self, mock_get_bay):
expected_service = mock.MagicMock()
expected_service.create = mock.MagicMock()
manifest = {"key": "value"}
expected_service.manifest = '{"key": "value"}'
with patch('magnum.conductor.k8s_api.create_k8s_api') as \
mock_kube_api:
err = rest.ApiException(status=404)
(mock_kube_api.return_value.create_namespaced_service
.side_effect) = err
self.assertRaises(exception.KubernetesAPIFailed,
self.kube_handler.service_create,
self.context, expected_service)
(mock_kube_api.return_value.create_namespaced_service
.assert_called_once_with(body=manifest, namespace='default'))
@patch('magnum.conductor.utils.object_has_stack')
@patch('magnum.objects.Service.get_by_name')
@patch('magnum.objects.Bay.get_by_name')
def test_service_delete_with_success(
self, mock_bay_get_by_name,
mock_service_get_by_name,
mock_object_has_stack):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
mock_service = mock.MagicMock()
mock_service.name = 'test-service'
mock_service.uuid = 'test-uuid'
mock_service.bay_uuid = 'test-bay-uuid'
mock_service_get_by_name.return_value = mock_service
mock_object_has_stack.return_value = True
with patch('magnum.conductor.k8s_api.create_k8s_api') as \
mock_kube_api:
self.kube_handler.service_delete(self.context,
mock_service.name,
mock_service.bay_uuid)
(mock_kube_api.return_value.delete_namespaced_service
.assert_called_once_with(
name=mock_service.name, namespace='default'))
@patch('magnum.conductor.utils.object_has_stack')
@patch('magnum.objects.Service.get_by_uuid')
@patch('magnum.objects.Bay.get_by_name')
def test_service_delete_with_failure(
self, mock_bay_get_by_name,
mock_service_get_by_uuid,
mock_object_has_stack):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
mock_service = mock.MagicMock()
mock_service.name = 'test-service'
mock_service.uuid = 'test-uuid'
mock_service.bay_uuid = 'test-bay-uuid'
mock_service_get_by_uuid.return_value = mock_service
mock_object_has_stack.return_value = True
with patch('magnum.conductor.k8s_api.create_k8s_api') as \
mock_kube_api:
err = rest.ApiException(status=500)
(mock_kube_api.return_value.delete_namespaced_service
.side_effect) = err
self.assertRaises(exception.KubernetesAPIFailed,
self.kube_handler.service_delete,
self.context, mock_service.name,
mock_service.bay_uuid)
(mock_kube_api.return_value.delete_namespaced_service
.assert_called_once_with(
name=mock_service.name, namespace='default'))
self.assertFalse(mock_service.destroy.called)
@patch('magnum.conductor.utils.object_has_stack')
@patch('magnum.objects.Service.get_by_uuid')
@patch('magnum.objects.Bay.get_by_name')
def test_service_delete_succeeds_when_not_found(
self, mock_bay_get_by_name,
mock_service_get_by_uuid,
mock_object_has_stack):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
mock_service = mock.MagicMock()
mock_service.name = 'test-service'
mock_service.uuid = 'test-uuid'
mock_service.bay_uuid = 'test-bay-uuid'
mock_service_get_by_uuid.return_value = mock_service
mock_object_has_stack.return_value = True
with patch('magnum.conductor.k8s_api.create_k8s_api') as \
mock_kube_api:
err = rest.ApiException(status=404)
(mock_kube_api.return_value.delete_namespaced_service
.side_effect) = err
self.kube_handler.service_delete(self.context,
mock_service.name,
mock_service.bay_uuid)
(mock_kube_api.return_value.delete_namespaced_service
.assert_called_once_with(
name=mock_service.name, namespace='default'))
@patch('magnum.conductor.utils.retrieve_bay')
@patch('ast.literal_eval')
def test_rc_create_with_success(self, mock_ast, mock_retrieve):
@ -363,70 +229,3 @@ class TestK8sConductor(base.TestCase):
.assert_called_once_with(body=expected_rc.manifest,
name=name_rc,
namespace='default'))
@patch('magnum.objects.Service.get_by_name')
@patch('magnum.objects.Service.get_by_uuid')
@patch('magnum.objects.Bay.get_by_name')
@patch('ast.literal_eval')
def test_service_update_with_success(self, mock_ast,
mock_bay_get_by_name,
mock_service_get_by_uuid,
mock_service_get_by_name):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
expected_service = mock.MagicMock()
expected_service.uuid = 'test-uuid'
expected_service.name = 'test-name'
expected_service.bay_uuid = 'test-bay-uuid'
expected_service.manifest = '{"key": "value"}'
mock_ast.return_value = {}
mock_service_get_by_name.return_value = expected_service
mock_service_get_by_uuid.return_value = expected_service
service_name = expected_service.name
with patch('magnum.conductor.k8s_api.create_k8s_api') as \
mock_kube_api:
self.kube_handler.service_update(self.context,
expected_service.name,
expected_service.bay_uuid,
expected_service.manifest)
(mock_kube_api.return_value.replace_namespaced_service
.assert_called_once_with(body=expected_service.manifest,
name=service_name,
namespace='default'))
@patch('magnum.objects.Service.get_by_name')
@patch('magnum.objects.Service.get_by_uuid')
@patch('magnum.objects.Bay.get_by_name')
def test_service_update_with_failure(self, mock_bay_get_by_name,
mock_service_get_by_uuid,
mock_service_get_by_name):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
expected_service = mock.MagicMock()
expected_service.uuid = 'test-uuid'
expected_service.name = 'test-name'
expected_service.bay_uuid = 'test-bay-uuid'
expected_service.manifest = '{"key": "value"}'
mock_service_get_by_uuid.return_value = expected_service
mock_service_get_by_name.return_value = expected_service
service_name = expected_service.name
with patch('magnum.conductor.k8s_api.create_k8s_api') as \
mock_kube_api:
err = rest.ApiException(status=404)
(mock_kube_api.return_value.replace_namespaced_service
.side_effect) = err
self.assertRaises(exception.KubernetesAPIFailed,
self.kube_handler.service_update,
self.context, expected_service.name,
expected_service.bay_uuid,
expected_service.manifest)
(mock_kube_api.return_value.replace_namespaced_service
.assert_called_once_with(body=expected_service.manifest,
name=service_name,
namespace='default'))

@ -30,7 +30,6 @@ class RPCAPITestCase(base.DbTestCase):
self.fake_bay = dbutils.get_test_bay(driver='fake-driver')
self.fake_container = dbutils.get_test_container(driver='fake-driver')
self.fake_rc = dbutils.get_test_rc(driver='fake-driver')
self.fake_service = dbutils.get_test_service(driver='fake-driver')
self.fake_certificate = objects.Certificate.from_db_bay(self.fake_bay)
self.fake_certificate.csr = 'fake-csr'
@ -99,32 +98,6 @@ class RPCAPITestCase(base.DbTestCase):
version='1.1',
bay=self.fake_bay['name'])
def test_service_create(self):
self._test_rpcapi('service_create',
'call',
version='1.0',
service=self.fake_service)
def test_service_update(self):
self._test_rpcapi('service_update',
'call',
version='1.0',
service_ident=self.fake_service['uuid'],
bay_ident=self.fake_service['bay_uuid'],
manifest={})
def test_service_delete(self):
self._test_rpcapi('service_delete',
'call',
version='1.0',
service_ident=self.fake_service['uuid'],
bay_ident=self.fake_service['bay_uuid'])
self._test_rpcapi('service_delete',
'call',
version='1.1',
service_ident=self.fake_service['uuid'],
bay_ident=self.fake_service['bay_uuid'])
def test_rc_create(self):
self._test_rpcapi('rc_create',
'call',

@ -27,13 +27,6 @@ class TestConductorUtils(base.TestCase):
mock_bay_get_by_uuid.assert_called_once_with(expected_context,
expected_bay_uuid)
@patch('magnum.objects.Bay.get_by_uuid')
def test_retrieve_bay_from_service(self,
mock_bay_get_by_uuid):
service = objects.Service({})
service.bay_uuid = '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
self._test_retrieve_bay(service.bay_uuid, mock_bay_get_by_uuid)
@patch('magnum.objects.Bay.get_by_uuid')
def test_retrieve_bay_from_rc(self,
mock_bay_get_by_uuid):

@ -192,24 +192,6 @@ class DbBayTestCase(base.DbTestCase):
self.dbapi.destroy_bay,
'12345678-9999-0000-aaaa-123456789012')
def test_destroy_bay_that_has_services(self):
bay = utils.create_test_bay()
service = utils.create_test_service(bay_uuid=bay.uuid)
self.assertEqual(bay.uuid, service.bay_uuid)
self.dbapi.destroy_bay(bay.id)
self.assertRaises(exception.ServiceNotFound,
self.dbapi.get_service_by_id,
self.context, service.id)
def test_destroy_bay_that_has_services_by_uuid(self):
bay = utils.create_test_bay()
service = utils.create_test_service(bay_uuid=bay.uuid)
self.assertEqual(bay.uuid, service.bay_uuid)
self.dbapi.destroy_bay(bay.id)
self.assertRaises(exception.ServiceNotFound,
self.dbapi.get_service_by_id,
self.context, service.id)
def test_destroy_bay_that_has_rc(self):
bay = utils.create_test_bay()
rc = utils.create_test_rc(bay_uuid=bay.uuid)

@ -1,178 +0,0 @@
# Copyright 2015 OpenStack Foundation
# 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.
"""Tests for manipulating Services via the DB API"""
from oslo_utils import uuidutils
import six
from magnum.common import exception
from magnum.tests.unit.db import base
from magnum.tests.unit.db import utils as utils
class DbServiceTestCase(base.DbTestCase):
def setUp(self):
# This method creates a service for every test and
# replaces a test for creating a service.
super(DbServiceTestCase, self).setUp()
self.bay = utils.create_test_bay()
self.service = utils.create_test_service(bay_uuid=self.bay.uuid)
def test_create_service_duplicated_uuid(self):
self.assertRaises(exception.ServiceAlreadyExists,
utils.create_test_service,
uuid=self.service.uuid,
bay_uuid=self.bay.uuid)
def test_get_service_by_id(self):
res = self.dbapi.get_service_by_id(self.context, self.service.id)
self.assertEqual(self.service.id, res.id)
self.assertEqual(self.service.uuid, res.uuid)
def test_get_service_by_uuid(self):
res = self.dbapi.get_service_by_uuid(self.context, self.service.uuid)
self.assertEqual(self.service.id, res.id)
self.assertEqual(self.service.uuid, res.uuid)
def test_get_service_by_name(self):
res = self.dbapi.get_service_by_name(self.context, self.service.name)
self.assertEqual(self.service.id, res.id)
self.assertEqual(self.service.uuid, res.uuid)
def test_get_service_by_name_multiple_service(self):
utils.create_test_service(bay_uuid=self.bay.uuid,
uuid=uuidutils.generate_uuid())
self.assertRaises(exception.Conflict, self.dbapi.get_service_by_name,
self.context, self.service.name)
def test_get_service_by_name_not_found(self):
self.assertRaises(exception.ServiceNotFound,
self.dbapi.get_service_by_name,
self.context, 'not_found')
def test_get_service_that_does_not_exist(self):
self.assertRaises(exception.ServiceNotFound,
self.dbapi.get_service_by_id, self.context, 999)
self.assertRaises(exception.ServiceNotFound,
self.dbapi.get_service_by_uuid,
self.context,
uuidutils.generate_uuid())
def test_get_service_list(self):
uuids = [self.service.uuid]
for i in range(1, 6):
service = utils.create_test_service(
bay_uuid=self.bay.uuid,
uuid=uuidutils.generate_uuid())
uuids.append(six.text_type(service.uuid))
res = self.dbapi.get_service_list(self.context)
res_uuids = [r.uuid for r in res]
self.assertEqual(sorted(uuids), sorted(res_uuids))
def test_get_service_list_sorted(self):
uuids = [self.service.uuid]
for _ in range(5):
service = utils.create_test_service(
uuid=uuidutils.generate_uuid())
uuids.append(six.text_type(service.uuid))
res = self.dbapi.get_service_list(self.context, sort_key='uuid')
res_uuids = [r.uuid for r in res]
self.assertEqual(sorted(uuids), res_uuids)
self.assertRaises(exception.InvalidParameterValue,
self.dbapi.get_service_list,
self.context,
sort_key='foo')
def test_get_service_list_with_filters(self):
bay1 = utils.get_test_bay(id=11, uuid=uuidutils.generate_uuid())
bay2 = utils.get_test_bay(id=12, uuid=uuidutils.generate_uuid())
self.dbapi.create_bay(bay1)
self.dbapi.create_bay(bay2)
service1 = utils.create_test_service(
name='service-one',
uuid=uuidutils.generate_uuid(),
bay_uuid=bay1['uuid'],
ports=[{'port': 8000}])
service2 = utils.create_test_service(
name='service-two',
uuid=uuidutils.generate_uuid(),
bay_uuid=bay2['uuid'],
ports=[{'port': 8001}])
res = self.dbapi.get_service_list(self.context,
filters={'bay_uuid': bay1['uuid']})
self.assertEqual([service1.id], [r.id for r in res])
res = self.dbapi.get_service_list(self.context,
filters={'bay_uuid': bay2['uuid']})
self.assertEqual([service2.id], [r.id for r in res])
res = self.dbapi.get_service_list(self.context,
filters={'name': 'service-one'})
self.assertEqual([service1.id], [r.id for r in res])
res = self.dbapi.get_service_list(self.context,
filters={'name': 'bad-service'})
self.assertEqual([], [r.id for r in res])
res = self.dbapi.get_service_list(self.context,
filters={'ports': [{'port': 8000}]})
self.assertEqual([service1.id], [r.id for r in res])
res = self.dbapi.get_service_list(self.context,
filters={'ports': [{'port': 8001}]})
self.assertEqual([service2.id], [r.id for r in res])
def test_get_service_list_bay_not_exist(self):
res = self.dbapi.get_service_list(self.context, filters={
'bay_uuid': self.bay.uuid})
self.assertEqual(1, len(res))
res = self.dbapi.get_service_list(self.context, filters={
'bay_uuid': uuidutils.generate_uuid()})
self.assertEqual(0, len(res))
def test_destroy_service_by_uuid(self):
self.assertIsNotNone(self.dbapi.get_service_by_uuid(self.context,
self.service.uuid))
self.dbapi.destroy_service(self.service.uuid)
self.assertRaises(exception.ServiceNotFound,
self.dbapi.get_service_by_uuid,
self.context, self.service.uuid)
def test_destroy_service_that_does_not_exist(self):
self.assertRaises(exception.ServiceNotFound,
self.dbapi.destroy_service,
uuidutils.generate_uuid())
def test_update_service(self):
old_name = self.service.name
new_name = 'new-service'
self.assertNotEqual(old_name, new_name)
res = self.dbapi.update_service(self.service.id, {'name': new_name})
self.assertEqual(new_name, res.name)
def test_update_service_not_found(self):
service_uuid = uuidutils.generate_uuid()
self.assertRaises(exception.ServiceNotFound, self.dbapi.update_service,
service_uuid, {'port': 80})
def test_update_service_uuid(self):
self.assertRaises(exception.InvalidParameterValue,
self.dbapi.update_service, self.service.id,
{'uuid': ''})

@ -111,38 +111,6 @@ def create_test_bay(**kw):
return dbapi.create_bay(bay)
def get_test_service(**kw):
return {
'id': kw.get('id', 42),
'uuid': kw.get('uuid', '10a47dd1-4874-4298-91cf-eff046dbdb8d'),
'name': kw.get('name', 'service1'),
'project_id': kw.get('project_id', 'fake_project'),
'user_id': kw.get('user_id', 'fake_user'),
'bay_uuid': kw.get('bay_uuid', '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'),
'labels': kw.get('labels', {'name': 'foo'}),
'selector': kw.get('selector', {'name': 'foo'}),
'ip': kw.get('ip', '172.17.2.2'),
'ports': kw.get('ports', [{'port': 80}]),
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at'),
}
def create_test_service(**kw):
"""Create test service entry in DB and return Service DB object.
Function to be used to create test Service objects in the database.
:param kw: kwargs with overriding values for service's attributes.
:returns: Test Service DB object.
"""
service = get_test_service(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del service['id']
dbapi = db_api.get_instance()
return dbapi.create_service(service)
def get_test_container(**kw):
return {
'id': kw.get('id', 42),

@ -363,7 +363,6 @@ object_data = {
'Container': '1.3-e2d9d2e8a8844d421148cd9fde6c6bd6',
'MyObj': '1.0-b43567e512438205e32f4e95ca616697',
'ReplicationController': '1.0-a471c2429c212ed91833cfcf0f934eab',
'Service': '1.0-f4a1c5a4618708824a553568c1ada0ea',
'X509KeyPair': '1.2-d81950af36c59a71365e33ce539d24f9',
'MagnumService': '1.0-2d397ec59b0046bd5ec35cd3e06efeca',
}

@ -1,100 +0,0 @@
# Copyright 2015 OpenStack Foundation
# 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 magnum import objects
from magnum.tests.unit.db import base
from magnum.tests.unit.db import utils
class TestServiceObject(base.DbTestCase):
def setUp(self):
super(TestServiceObject, self).setUp()
self.fake_service = utils.get_test_service()
@mock.patch('magnum.conductor.k8s_api.create_k8s_api')
@mock.patch('ast.literal_eval')
def test_get_by_uuid(self, mock_ast, mock_kube_api):
uuid = self.fake_service['uuid']
bay_uuid = self.fake_service['bay_uuid']
mock_ast.return_value = {}
k8s_api_mock = mock.MagicMock()
mock_kube_api.return_value = k8s_api_mock
fake_obj = mock.MagicMock()
items = [
{
'metadata': {
'uid': '10a47dd1-4874-4298-91cf-eff046dbdb8d',
'name': 'fake-name',
'labels': {}
},
'spec': {
'selector': {},
'cluster_ip': '10.98.100.19',
'ports': [
{
'port': 80
}
]
}
}
]