Merge "Objects from Bay - Replication Controller"

This commit is contained in:
Jenkins 2015-11-18 19:06:38 +00:00 committed by Gerrit Code Review
commit ad4e792a2d
17 changed files with 704 additions and 540 deletions

View File

@ -210,17 +210,7 @@ class ReplicationControllersController(rest.RestController):
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.ReplicationController.get_by_uuid(
pecan.request.context,
marker)
rcs = pecan.request.rpcapi.rc_list(
pecan.request.context, limit,
marker_obj, sort_key=sort_key,
sort_dir=sort_dir)
rcs = pecan.request.rpcapi.rc_list(pecan.request.context, bay_ident)
return ReplicationControllerCollection.convert_with_links(
rcs, limit,
@ -279,7 +269,8 @@ class ReplicationControllersController(rest.RestController):
:param rc_ident: UUID or logical name of a ReplicationController.
:param bay_ident: UUID or logical name of the Bay.
"""
rpc_rc = api_utils.get_rpc_resource('ReplicationController', rc_ident)
context = pecan.request.context
rpc_rc = pecan.request.rpcapi.rc_show(context, rc_ident, bay_ident)
return ReplicationController.convert_with_links(rpc_rc)
@policy.enforce_wsgi("rc", "create")
@ -316,13 +307,10 @@ class ReplicationControllersController(rest.RestController):
:param bay_ident: UUID or logical name of the Bay.
:param patch: a json PATCH document to apply to this rc.
"""
rpc_rc = api_utils.get_rpc_resource('ReplicationController', rc_ident)
# Init manifest and manifest_url field because we don't store them
# in database.
rpc_rc['manifest'] = None
rpc_rc['manifest_url'] = None
rc_dict = {}
rc_dict['manifest'] = None
rc_dict['manifest_url'] = None
try:
rc_dict = rpc_rc.as_dict()
rc = ReplicationController(**api_utils.apply_jsonpatch(rc_dict,
patch))
if rc.manifest or rc.manifest_url:
@ -330,22 +318,9 @@ class ReplicationControllersController(rest.RestController):
except api_utils.JSONPATCH_EXCEPTIONS as e:
raise exception.PatchError(patch=patch, reason=e)
# Update only the fields that have changed
for field in objects.ReplicationController.fields:
try:
patch_val = getattr(rc, field)
except AttributeError:
# Ignore fields that aren't exposed in the API
continue
if patch_val == wtypes.Unset:
patch_val = None
if rpc_rc[field] != patch_val:
rpc_rc[field] = patch_val
if rc.manifest or rc.manifest_url:
pecan.request.rpcapi.rc_update(rpc_rc)
else:
rpc_rc.save()
rpc_rc = pecan.request.rpcapi.rc_update(rc_ident,
bay_ident,
rc.manifest)
return ReplicationController.convert_with_links(rpc_rc)
@policy.enforce_wsgi("rc")
@ -357,5 +332,4 @@ class ReplicationControllersController(rest.RestController):
:param rc_ident: UUID or logical name of a ReplicationController.
:param bay_ident: UUID or logical name of the Bay.
"""
rpc_rc = api_utils.get_rpc_resource('ReplicationController', rc_ident)
pecan.request.rpcapi.rc_delete(rpc_rc.uuid)
pecan.request.rpcapi.rc_delete(rc_ident, bay_ident)

View File

@ -72,7 +72,7 @@ def is_docker_library_version_atleast(version):
def docker_for_container(context, container):
if magnum_utils.is_uuid_like(container):
container = objects.Container.get_by_uuid(context, container)
bay = conductor_utils.retrieve_bay(context, container)
bay = conductor_utils.retrieve_bay(context, container.bay_uuid)
with docker_for_bay(context, bay) as docker:
yield docker

View File

@ -412,6 +412,16 @@ class ReplicationControllerAlreadyExists(Conflict):
message = _("A ReplicationController with UUID %(uuid)s already exists.")
class ReplicationControllerListNotFound(ResourceNotFound):
message = _("ReplicationController list could not be found"
" for Bay %(bay_uuid)s.")
class ReplicationControllerCreationFailed(Invalid):
message = _("ReplicationController creation failed"
" for Bay %(bay_uuid)s.")
class ServiceNotFound(ResourceNotFound):
message = _("Service %(service)s could not be found.")

View File

@ -102,18 +102,19 @@ class API(rpc_service.API):
def rc_create(self, rc):
return self._call('rc_create', rc=rc)
def rc_update(self, rc):
return self._call('rc_update', rc=rc)
def rc_update(self, rc_ident, bay_ident, manifest):
return self._call('rc_update', rc_ident=rc_ident,
bay_ident=bay_ident, manifest=manifest)
def rc_list(self, context, limit, marker, sort_key, sort_dir):
return objects.ReplicationController.list(context, limit, marker,
sort_key, sort_dir)
def rc_list(self, context, bay_ident):
return self._call('rc_list', bay_ident=bay_ident)
def rc_delete(self, uuid):
return self._call('rc_delete', uuid=uuid)
def rc_delete(self, rc_ident, bay_ident):
return self._call('rc_delete', rc_ident=rc_ident, bay_ident=bay_ident)
def rc_show(self, context, uuid):
return objects.ReplicationController.get_by_uuid(context, uuid)
def rc_show(self, context, rc_ident, bay_ident):
return self._call('rc_show', rc_ident=rc_ident,
bay_ident=bay_ident)
# Container operations

View File

@ -16,10 +16,13 @@ from oslo_log import log as logging
from magnum.common import exception
from magnum.common import k8s_manifest
from magnum.common.pythonk8sclient.swagger_client import rest
from magnum.common import utils
from magnum.conductor import k8s_api as k8s
from magnum.conductor import utils as conductor_utils
from magnum import objects
import ast
LOG = logging.getLogger(__name__)
@ -136,43 +139,154 @@ class Handler(object):
# Replication Controller Operations
def rc_create(self, context, rc):
LOG.debug("rc_create")
self.k8s_api = k8s.create_k8s_api(context, rc)
self.k8s_api = k8s.create_k8s_api_rc(context, rc.bay_uuid)
manifest = k8s_manifest.parse(rc.manifest)
try:
self.k8s_api.create_namespaced_replication_controller(
body=manifest, namespace='default')
resp = self.k8s_api.create_namespaced_replication_controller(
body=manifest,
namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
# call the rc object to persist in db
rc.create(context)
if resp is None:
raise exception.ReplicationControllerCreationFailed(
bay_uuid=rc.bay_uuid)
rc['uuid'] = resp.metadata.uid
rc['name'] = resp.metadata.name
rc['images'] = [c.image for c in resp.spec.template.spec.containers]
rc['labels'] = ast.literal_eval(resp.metadata.labels)
rc['replicas'] = resp.status.replicas
return rc
def rc_update(self, context, rc):
LOG.debug("rc_update %s", rc.uuid)
self.k8s_api = k8s.create_k8s_api(context, rc)
manifest = k8s_manifest.parse(rc.manifest)
def rc_update(self, context, rc_ident, bay_ident, manifest):
LOG.debug("rc_update %s", rc_ident)
# Since bay identifier is specified verify whether its a UUID
# or Name. If name is specified as bay identifier need to extract
# the bay uuid since its needed to get the k8s_api object.
if not utils.is_uuid_like(bay_ident):
bay = objects.Bay.get_by_name(context, bay_ident)
bay_ident = bay.uuid
bay_uuid = bay_ident
self.k8s_api = k8s.create_k8s_api_rc(context, bay_uuid)
if utils.is_uuid_like(rc_ident):
rc = objects.ReplicationController.get_by_uuid(context, rc_ident,
bay_uuid,
self.k8s_api)
else:
rc = objects.ReplicationController.get_by_name(context, rc_ident,
bay_uuid,
self.k8s_api)
try:
self.k8s_api.replace_namespaced_replication_controller(
name=str(rc.name), body=manifest, namespace='default')
resp = self.k8s_api.replace_namespaced_replication_controller(
name=str(rc.name),
body=manifest,
namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
# call the rc object to persist in db
rc.refresh(context)
rc.save()
if resp is None:
raise exception.ReplicationControllerNotFound(rc=rc.uuid)
rc['uuid'] = resp.metadata.uid
rc['name'] = resp.metadata.name
rc['project_id'] = context.project_id
rc['user_id'] = context.user_id
rc['images'] = [c.image for c in resp.spec.template.spec.containers]
rc['bay_uuid'] = bay_uuid
rc['labels'] = ast.literal_eval(resp.metadata.labels)
rc['replicas'] = resp.status.replicas
return rc
def rc_delete(self, context, uuid):
LOG.debug("rc_delete %s", uuid)
rc = objects.ReplicationController.get_by_uuid(context, uuid)
self.k8s_api = k8s.create_k8s_api(context, rc)
if conductor_utils.object_has_stack(context, rc):
def rc_delete(self, context, rc_ident, bay_ident):
LOG.debug("rc_delete %s", rc_ident)
# Since bay identifier is specified verify whether its a UUID
# or Name. If name is specified as bay identifier need to extract
# the bay uuid since its needed to get the k8s_api object.
if not utils.is_uuid_like(bay_ident):
bay = objects.Bay.get_by_name(context, bay_ident)
bay_ident = bay.uuid
bay_uuid = bay_ident
self.k8s_api = k8s.create_k8s_api_rc(context, bay_uuid)
if utils.is_uuid_like(rc_ident):
rc = objects.ReplicationController.get_by_uuid(context, rc_ident,
bay_uuid,
self.k8s_api)
rc_name = rc.name
else:
rc_name = rc_ident
if conductor_utils.object_has_stack(context, bay_uuid):
try:
self.k8s_api.delete_namespaced_replication_controller(
name=str(rc.name), body={}, namespace='default')
name=str(rc_name),
body={},
namespace='default')
except rest.ApiException as err:
if err.status == 404:
pass
else:
raise exception.KubernetesAPIFailed(err=err)
# call the rc object to persist in db
rc.destroy(context)
def rc_show(self, context, rc_ident, bay_ident):
LOG.debug("rc_show %s", rc_ident)
# Since bay identifier is specified verify whether its a UUID
# or Name. If name is specified as bay identifier need to extract
# the bay uuid since its needed to get the k8s_api object.
if not utils.is_uuid_like(bay_ident):
bay = objects.Bay.get_by_name(context, bay_ident)
bay_ident = bay.uuid
bay_uuid = bay_ident
self.k8s_api = k8s.create_k8s_api_rc(context, bay_uuid)
if utils.is_uuid_like(rc_ident):
rc = objects.ReplicationController.get_by_uuid(context, rc_ident,
bay_uuid,
self.k8s_api)
else:
rc = objects.ReplicationController.get_by_name(context, rc_ident,
bay_uuid,
self.k8s_api)
return rc
def rc_list(self, context, bay_ident):
# Since bay identifier is specified verify whether its a UUID
# or Name. If name is specified as bay identifier need to extract
# the bay uuid since its needed to get the k8s_api object.
if not utils.is_uuid_like(bay_ident):
bay = objects.Bay.get_by_name(context, bay_ident)
bay_ident = bay.uuid
bay_uuid = bay_ident
self.k8s_api = k8s.create_k8s_api_rc(context, bay_uuid)
try:
resp = self.k8s_api.list_namespaced_replication_controller(
namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
if resp is None:
raise exception.ReplicationControllerListNotFound(
bay_uuid=bay_uuid)
rcs = []
for entry in resp._items:
rc = {}
rc['uuid'] = entry.metadata.uid
rc['name'] = entry.metadata.name
rc['project_id'] = context.project_id
rc['user_id'] = context.user_id
rc['images'] = [
c.image for c in entry.spec.template.spec.containers]
rc['bay_uuid'] = bay_uuid
# Convert string to dictionary
rc['labels'] = ast.literal_eval(entry.metadata.labels)
rc['replicas'] = entry.status.replicas
rc_obj = objects.ReplicationController(context, **rc)
rcs.append(rc_obj)
return rcs

View File

@ -54,7 +54,7 @@ class K8sAPI(apiv_api.ApivApi):
if isinstance(obj, Bay):
bay = obj
else:
bay = utils.retrieve_bay(context, obj)
bay = utils.retrieve_bay(context, obj.bay_uuid)
if bay.magnum_cert_ref:
self._create_certificate_files(bay)
@ -110,3 +110,87 @@ def create_k8s_api(context, obj):
:param obj: A bay or a k8s object (Pod, Service, ReplicationController)
"""
return K8sAPI(context, obj)
# NB : This is a place holder class. This class and create_k8s_api_rc
# method will be removed once the objects from bay code for k8s
# objects is merged. These changes are temporary to get the Unit
# test working.
class K8sAPI_RC(apiv_api.ApivApi):
def _create_temp_file_with_content(self, content):
"""Creates temp file and write content to the file.
:param content: file content
:returns: temp file
"""
try:
tmp = NamedTemporaryFile(delete=True)
tmp.write(content)
tmp.flush()
except Exception as err:
LOG.error("Error while creating temp file: %s" % err)
raise err
return tmp
def __init__(self, context, bay_uuid):
self.ca_file = None
self.cert_file = None
self.key_file = None
bay = utils.retrieve_bay(context, bay_uuid)
if bay.magnum_cert_ref:
self._create_certificate_files(bay)
# build a connection with Kubernetes master
client = api_client.ApiClient(bay.api_address,
key_file=self.key_file.name,
cert_file=self.cert_file.name,
ca_certs=self.ca_file.name)
super(K8sAPI_RC, self).__init__(client)
def _create_certificate_files(self, bay):
"""Read certificate and key for a bay and stores in files.
:param bay: Bay object
"""
magnum_cert_obj = cert_manager.get_backend().CertManager.get_cert(
bay.magnum_cert_ref)
self.cert_file = self._create_temp_file_with_content(
magnum_cert_obj.certificate)
private_key = serialization.load_pem_private_key(
magnum_cert_obj.private_key,
password=magnum_cert_obj.private_key_passphrase,
backend=default_backend(),
)
private_key = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption())
self.key_file = self._create_temp_file_with_content(
private_key)
ca_cert_obj = cert_manager.get_backend().CertManager.get_cert(
bay.ca_cert_ref)
self.ca_file = self._create_temp_file_with_content(
ca_cert_obj.certificate)
def __del__(self):
if self.ca_file:
self.ca_file.close()
if self.cert_file:
self.cert_file.close()
if self.key_file:
self.key_file.close()
def create_k8s_api_rc(context, bay_uuid):
"""Create a kubernetes API client
Creates connection with Kubernetes master and creates ApivApi instance
to call Kubernetes APIs.
:param context: The security context
:param bay_uuid: Unique identifier for the Bay
"""
return K8sAPI_RC(context, bay_uuid)

View File

@ -13,21 +13,21 @@
# limitations under the License.
from magnum.common import clients
from magnum import objects
from magnum.objects import bay
from magnum.objects import baymodel
def retrieve_bay(context, obj):
return objects.Bay.get_by_uuid(context, obj.bay_uuid)
def retrieve_bay(context, bay_uuid):
return bay.Bay.get_by_uuid(context, bay_uuid)
def retrieve_baymodel(context, bay):
return objects.BayModel.get_by_uuid(context, bay.baymodel_id)
return baymodel.BayModel.get_by_uuid(context, bay.baymodel_id)
def object_has_stack(context, obj):
def object_has_stack(context, bay_uuid):
osc = clients.OpenStackClients(context)
if hasattr(obj, 'bay_uuid'):
obj = retrieve_bay(context, obj)
obj = retrieve_bay(context, bay_uuid)
stack = osc.heat().stacks.get(obj.stack_id)
if (stack.stack_status == 'DELETE_COMPLETE' or

View File

@ -14,9 +14,13 @@
from oslo_versionedobjects import fields
from magnum.common import exception
from magnum.common.pythonk8sclient.swagger_client import rest
from magnum.db import api as dbapi
from magnum.objects import base
import ast
@base.MagnumObjectRegistry.register
class ReplicationController(base.MagnumPersistentObject, base.MagnumObject,
@ -40,159 +44,75 @@ class ReplicationController(base.MagnumPersistentObject, base.MagnumObject,
'manifest': fields.StringField(nullable=True),
}
@staticmethod
def _from_db_object(rc, db_rc):
"""Converts a database entity to a formal object."""
for field in rc.fields:
# ignore manifest_url as it was used for create rc
if field == 'manifest_url':
continue
# ignore manifest as it was used for create rc
if field == 'manifest':
continue
rc[field] = db_rc[field]
rc.obj_reset_changes()
return rc
@staticmethod
def _from_db_object_list(db_objects, cls, context):
"""Converts a list of database entities to a list of formal objects."""
return [ReplicationController._from_db_object(cls(context), obj)
for obj in db_objects]
@base.remotable_classmethod
def get_by_id(cls, context, rc_id):
"""Find a ReplicationController based on its integer id.
Find ReplicationController based on id and return a
ReplicationController object.
:param rc_id: the id of a ReplicationController.
:returns: a :class:`ReplicationController` object.
"""
db_rc = cls.dbapi.get_rc_by_id(context, rc_id)
rc = ReplicationController._from_db_object(cls(context), db_rc)
return rc
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
"""Find a ReplicationController based on uuid.
Find ReplicationController by uuid and return a
:class:`ReplicationController` object.
def get_by_uuid(cls, context, uuid, bay_uuid, k8s_api):
"""Return a :class:`ReplicationController` object based on uuid.
:param context: Security context
:param uuid: the uuid of a ReplicationController.
:param context: Security context
:param bay_uuid: the UUID of the Bay.
:returns: a :class:`ReplicationController` object.
"""
db_rc = cls.dbapi.get_rc_by_uuid(context, uuid)
rc = ReplicationController._from_db_object(cls(context), db_rc)
return rc
try:
resp = k8s_api.list_namespaced_replication_controller(
namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
if resp is None:
raise exception.ReplicationControllerListNotFound(
bay_uuid=bay_uuid)
rc = {}
for entry in resp.items:
if entry.metadata.uid == uuid:
rc['uuid'] = entry.metadata.uid
rc['name'] = entry.metadata.name
rc['project_id'] = context.project_id
rc['user_id'] = context.user_id
rc['images'] = [
c.image for c in entry.spec.template.spec.containers]
rc['bay_uuid'] = bay_uuid
# Convert string to dictionary
rc['labels'] = ast.literal_eval(entry.metadata.labels)
rc['replicas'] = entry.status.replicas
rc_obj = ReplicationController(context, **rc)
return rc_obj
raise exception.ReplicationControllerNotFound(rc=uuid)
@base.remotable_classmethod
def get_by_name(cls, context, name):
"""Find a ReplicationController based on name.
Find ReplicationController by name and return a
:class:`ReplicationController` object.
def get_by_name(cls, context, name, bay_uuid, k8s_api):
"""Return a :class:`ReplicationController` object based on name.
:param context: Security context
:param name: the name of a ReplicationController.
:param context: Security context
:param bay_uuid: the UUID of the Bay.
:returns: a :class:`ReplicationController` object.
"""
db_rc = cls.dbapi.get_rc_by_name(context, name)
rc = ReplicationController._from_db_object(cls(context), db_rc)
return rc
try:
resp = k8s_api.read_namespaced_replication_controller(
name=name,
namespace='default')
except rest.ApiException as err:
raise exception.KubernetesAPIFailed(err=err)
@base.remotable_classmethod
def list(cls, context, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of ReplicationController objects.
if resp is None:
raise exception.ReplicationControllerNotFound(rc=name)
:param context: Security context.
:param limit: maximum number of resources to return in a single result.
:param marker: pagination marker for large data sets.
:param sort_key: column to sort results by.
:param sort_dir: direction to sort. "asc" or "desc".
:returns: a list of :class:`ReplicationController` object.
rc = {}
rc['uuid'] = resp.metadata.uid
rc['name'] = resp.metadata.name
rc['project_id'] = context.project_id
rc['user_id'] = context.user_id
rc['images'] = [c.image for c in resp.spec.template.spec.containers]
rc['bay_uuid'] = bay_uuid
# Convert string to dictionary
rc['labels'] = ast.literal_eval(resp.metadata.labels)
rc['replicas'] = resp.status.replicas
"""
db_rcs = cls.dbapi.get_rc_list(context, limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return ReplicationController._from_db_object_list(db_rcs, cls, context)
@base.remotable
def create(self, context=None):
"""Create a ReplicationController record in the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: ReplicationController(context)
"""
values = self.obj_get_changes()
db_rc = self.dbapi.create_rc(values)
self._from_db_object(self, db_rc)
@base.remotable
def destroy(self, context=None):
"""Delete the ReplicationController from the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: ReplicationController(context)
"""
self.dbapi.destroy_rc(self.uuid)
self.obj_reset_changes()
@base.remotable
def save(self, context=None):
"""Save updates to this ReplicationController.
Updates will be made column by column based on the result
of self.what_changed().
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: ReplicationController(context)
"""
updates = self.obj_get_changes()
self.dbapi.update_rc(self.uuid, updates)
self.obj_reset_changes()
@base.remotable
def refresh(self, context=None):
"""Loads updates for this ReplicationController.
Loads a rc with the same uuid from the database and
checks for updated attributes. Updates are applied from
the loaded rc column by column, if there are any updates.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: ReplicationController(context)
"""
current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
for field in self.fields:
if field == 'manifest_url':
continue
if field == 'manifest':
continue
if self.obj_attr_is_set(field) and self[field] != current[field]:
self[field] = current[field]
rc_obj = ReplicationController(context, **rc)
return rc_obj

View File

@ -149,6 +149,9 @@ extendedKeyUsage = clientAuth
resp = self.k8s_api.delete_namespaced_replication_controller(
name='frontend', body={}, namespace='default')
"""
NB : Bug1504379. This is placeholder and will be removed when all
the objects-from-bay patches are checked in.
def test_pods_list(self):
self.assertIsNotNone(self.cs.pods.list(self.bay.uuid))
@ -157,3 +160,4 @@ extendedKeyUsage = clientAuth
def test_services_list(self):
self.assertIsNotNone(self.cs.services.list(self.bay.uuid))
"""

View File

@ -15,14 +15,13 @@ import datetime
import mock
from oslo_config import cfg
from oslo_policy import policy
from oslo_utils import timeutils
from six.moves.urllib import parse as urlparse
from wsme import types as wtypes
from magnum.api.controllers.v1 import replicationcontroller as api_rc
from magnum.common.pythonk8sclient.swagger_client import rest
from magnum.common import utils
from magnum.conductor import api as rpcapi
from magnum import objects
from magnum.tests import base
from magnum.tests.unit.api import base as api_base
from magnum.tests.unit.api import utils as apiutils
@ -43,8 +42,11 @@ class TestListRC(api_base.FunctionalTest):
def setUp(self):
super(TestListRC, self).setUp()
obj_utils.create_test_bay(self.context)
self.rc = obj_utils.create_test_rc(self.context)
def test_empty(self):
@mock.patch.object(rpcapi.API, 'rc_list')
def test_empty(self, mock_rc_list):
mock_rc_list.return_value = []
response = self.get_json('/rcs')
self.assertEqual([], response['rcs'])
@ -54,72 +56,83 @@ class TestListRC(api_base.FunctionalTest):
for field in rc_fields:
self.assertIn(field, rc)
def test_one(self):
rc = obj_utils.create_test_rc(self.context)
response = self.get_json('/rcs')
self.assertEqual(rc.uuid, response['rcs'][0]["uuid"])
self._assert_rc_fields(response['rcs'][0])
def test_get_one(self):
@mock.patch.object(rpcapi.API, 'rc_show')
def test_get_one(self, mock_rc_show):
rc = obj_utils.create_test_rc(self.context)
mock_rc_show.return_value = rc
response = self.get_json('/rcs/%s/%s' % (rc['uuid'], rc['bay_uuid']))
self.assertEqual(rc.uuid, response['uuid'])
self._assert_rc_fields(response)
def test_get_one_by_name(self):
@mock.patch.object(rpcapi.API, 'rc_show')
def test_get_one_by_name(self, mock_rc_show):
rc = obj_utils.create_test_rc(self.context)
mock_rc_show.return_value = rc
response = self.get_json('/rcs/%s/%s' % (rc['name'], rc['bay_uuid']))
self.assertEqual(rc.uuid, response['uuid'])
self._assert_rc_fields(response)
def test_get_one_by_name_not_found(self):
@mock.patch.object(rpcapi.API, 'rc_show')
def test_get_one_by_name_not_found(self, mock_rc_show):
err = rest.ApiException(status=404)
mock_rc_show.side_effect = err
response = self.get_json(
'/rcs/not_found/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def test_get_one_by_name_multiple_rc(self):
@mock.patch.object(rpcapi.API, 'rc_show')
def test_get_one_by_name_multiple_rc(self, mock_rc_show):
obj_utils.create_test_rc(
self.context, name='test_rc',
uuid=utils.generate_uuid())
obj_utils.create_test_rc(
self.context, name='test_rc',
uuid=utils.generate_uuid())
err = rest.ApiException(status=500)
mock_rc_show.side_effect = err
response = self.get_json(
'/rcs/test_rc/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(409, response.status_int)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def test_get_all_with_pagination_marker(self):
@mock.patch.object(rpcapi.API, 'rc_list')
def test_get_all_with_pagination_marker(self, mock_rc_list):
rc_list = []
for id_ in range(4):
rc = obj_utils.create_test_rc(self.context, id=id_,
uuid=utils.generate_uuid())
rc_list.append(rc.uuid)
mock_rc_list.return_value = [rc]
response = self.get_json('/rcs?limit=3&marker=%s' % rc_list[2])
self.assertEqual(1, len(response['rcs']))
self.assertEqual(rc_list[-1], response['rcs'][0]['uuid'])
def test_detail(self):
@mock.patch.object(rpcapi.API, 'rc_list')
def test_detail(self, mock_rc_list):
rc = obj_utils.create_test_rc(self.context)
mock_rc_list.return_value = [rc]
response = self.get_json('/rcs/detail')
self.assertEqual(rc.uuid, response['rcs'][0]["uuid"])
self._assert_rc_fields(response['rcs'][0])
def test_detail_with_pagination_marker(self):
@mock.patch.object(rpcapi.API, 'rc_list')
def test_detail_with_pagination_marker(self, mock_rc_list):
rc_list = []
for id_ in range(4):
rc = obj_utils.create_test_rc(self.context, id=id_,
uuid=utils.generate_uuid())
rc_list.append(rc.uuid)
mock_rc_list.return_value = [rc]
response = self.get_json('/rcs/detail?limit=3&marker=%s'
% rc_list[2])
% (rc_list[2]))
self.assertEqual(1, len(response['rcs']))
self.assertEqual(rc_list[-1], response['rcs'][0]['uuid'])
self._assert_rc_fields(response['rcs'][0])
@ -130,46 +143,47 @@ class TestListRC(api_base.FunctionalTest):
expect_errors=True)
self.assertEqual(404, response.status_int)
def test_many(self):
@mock.patch.object(rpcapi.API, 'rc_list')
def test_many(self, mock_rc_list):
rc_list = []
for id_ in range(5):
for id_ in range(1):
rc = obj_utils.create_test_rc(self.context, id=id_,
uuid=utils.generate_uuid())
rc_list.append(rc.uuid)
mock_rc_list.return_value = [rc]
response = self.get_json('/rcs')
self.assertEqual(len(rc_list), len(response['rcs']))
uuids = [r['uuid'] for r in response['rcs']]
self.assertEqual(sorted(rc_list), sorted(uuids))
def test_links(self):
@mock.patch.object(rpcapi.API, 'rc_show')
def test_links(self, mock_rc_show):
uuid = utils.generate_uuid()
obj_utils.create_test_rc(self.context, id=1, uuid=uuid)
response = self.get_json(
'/rcs/%s/%s' % (uuid, '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'))
rc = obj_utils.create_test_rc(self.context, id=1, uuid=uuid)
mock_rc_show.return_value = rc
response = self.get_json('/rcs/%s/%s' % (uuid, rc.bay_uuid))
self.assertIn('links', response.keys())
self.assertEqual(2, len(response['links']))
self.assertIn(uuid, response['links'][0]['href'])
def test_collection_links(self):
@mock.patch.object(rpcapi.API, 'rc_list')
def test_collection_links(self, mock_rc_list):
for id_ in range(5):
obj_utils.create_test_rc(self.context, id=id_,
uuid=utils.generate_uuid())
response = self.get_json('/rcs/?limit=3')
self.assertEqual(3, len(response['rcs']))
rc = obj_utils.create_test_rc(self.context, id=id_,
uuid=utils.generate_uuid())
mock_rc_list.return_value = [rc]
response = self.get_json('/rcs/?limit=1')
self.assertEqual(1, len(response['rcs']))
next_marker = response['rcs'][-1]['uuid']
self.assertIn(next_marker, response['next'])
def test_collection_links_default_limit(self):
@mock.patch.object(rpcapi.API, 'rc_list')
def test_collection_links_default_limit(self, mock_rc_list):
cfg.CONF.set_override('max_limit', 3, 'api')
for id_ in range(5):
obj_utils.create_test_rc(self.context, id=id_,
uuid=utils.generate_uuid())
rc = obj_utils.create_test_rc(self.context, id=id_,
uuid=utils.generate_uuid())
mock_rc_list.return_value = [rc]
response = self.get_json('/rcs')
self.assertEqual(3, len(response['rcs']))
next_marker = response['rcs'][-1]['uuid']
self.assertIn(next_marker, response['next'])
self.assertEqual(1, len(response['rcs']))
class TestPatch(api_base.FunctionalTest):
@ -182,49 +196,58 @@ class TestPatch(api_base.FunctionalTest):
self.another_bay = obj_utils.create_test_bay(
self.context,
uuid=utils.generate_uuid())
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok(self, mock_utcnow):
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
new_image = 'rc_example_B_image'
response = self.get_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid))
self.assertNotEqual(new_image, response['images'][0])
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
[{'path': '/images/0',
'value': new_image,
'op': 'replace'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
response = self.get_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid))
self.assertEqual(new_image, response['images'][0])
return_updated_at = timeutils.parse_isotime(
response['updated_at']).replace(tzinfo=None)
self.assertEqual(test_time, return_updated_at)
self.manifest = '''{
"metadata": {
"name": "name_of_rc"
},
"spec":{
"replicas":2,
"selector":{
"name":"frontend"
},
"template":{
"metadata":{
"labels":{
"name":"frontend"
}
},
"spec":{
"containers":[
{
"name":"test-redis",
"image":"steak/for-dinner",
"ports":[
{
"containerPort":80,
"protocol":"TCP"
}
]
}
]
}
}
}
}'''
def test_replace_bay_uuid(self):
response = self.patch_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid),
[{'path': '/bay_uuid',
'value': self.another_bay.uuid,
'op': 'replace'}],
expect_errors=True)
self.rc.manifest = '{"bay_uuid": "self.rc.bay_uuid"}'
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
[{'path': '/bay_uuid',
'value': self.another_bay.uuid,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
self.assertEqual(400, response.status_code)
def test_replace_non_existent_bay_uuid(self):
response = self.patch_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid),
[{'path': '/bay_uuid',
'value': utils.generate_uuid(),
'op': 'replace'}],
expect_errors=True)
self.rc.manifest = '{"key": "value"}'
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
[{'path': '/bay_uuid',
'value': utils.generate_uuid(),
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
self.assertTrue(response.json['error_message'])
@ -246,18 +269,19 @@ class TestPatch(api_base.FunctionalTest):
'value': 'rc_example_B_image',
'op': 'replace'}],
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
@mock.patch.object(rpcapi.API, 'rc_update')
@mock.patch.object(api_rc.ReplicationController, 'parse_manifest')
def test_replace_with_manifest(self, parse_manifest, rc_update):
response = self.patch_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid),
[{'path': '/manifest',
'value': '{}',
'op': 'replace'}])
rc_update.return_value = self.rc
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
[{'path': '/manifest',
'value': '{"foo": "bar"}',
'op': 'replace'}])
self.assertEqual(200, response.status_int)
self.assertEqual('application/json', response.content_type)
parse_manifest.assert_called_once_with()
@ -272,26 +296,32 @@ class TestPatch(api_base.FunctionalTest):
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['error_message'])
def test_remove_ok(self):
response = self.get_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid))
@mock.patch.object(rpcapi.API, 'rc_update')
@mock.patch.object(rpcapi.API, 'rc_show')
def test_remove_ok(self, mock_rc_show, mock_rc_update):
mock_rc_show.return_value = self.rc
response = self.get_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid))
self.assertNotEqual(len(response['images']), 0)
response = self.patch_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid),
[{'path': '/images', 'op': 'remove'}])
mock_rc_update.return_value = self.rc
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
[{'path': '/manifest',
'op': 'remove'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
response = self.get_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid))
self.assertEqual(0, len(response['images']))
mock_rc_show.return_value = self.rc
response = self.get_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid))
self.assertEqual(len(response['images']), 1)
def test_remove_uuid(self):
response = self.patch_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid),
[{'path': '/uuid', 'op': 'remove'}],
expect_errors=True)
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
[{'path': '/uuid', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
@ -323,25 +353,30 @@ class TestPatch(api_base.FunctionalTest):
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok_by_name(self, mock_utcnow):
new_image = 'rc_example_B_image'
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
@mock.patch.object(rpcapi.API, 'rc_show')
@mock.patch.object(rpcapi.API, 'rc_update')
@mock.patch.object(api_rc.ReplicationController, 'parse_manifest')
def test_replace_ok_by_name(self, parse_manifest,
mock_rc_update,
mock_rc_show):
mock_rc_update.return_value = self.rc
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.name, self.rc.bay_uuid),
[{'path': '/images/0',
'value': new_image,
[{'path': '/manifest',
'value': '{"foo": "bar"}',
'op': 'replace'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
parse_manifest.assert_called_once_with()
self.assertTrue(mock_rc_update.is_called)
response = self.get_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid))
return_updated_at = timeutils.parse_isotime(
response['updated_at']).replace(tzinfo=None)
self.assertEqual(test_time, return_updated_at)
mock_rc_show.return_value = self.rc
response = self.get_json(
'/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid),
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok_by_name_not_found(self, mock_utcnow):
@ -350,13 +385,14 @@ class TestPatch(api_base.FunctionalTest):
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
response = self.patch_json('/rcs/%s/%s' % (name, self.rc.bay_uuid),
[{'path': '/images/0',
'value': new_image,
'op': 'replace'}],
expect_errors=True)
response = self.patch_json(
'/rcs/%s/%s' % (name, self.rc.bay_uuid),
[{'path': '/images/0',
'value': new_image,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(404, response.status_code)
self.assertEqual(400, response.status_code)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok_by_name_multiple_rc(self, mock_utcnow):
@ -376,7 +412,7 @@ class TestPatch(api_base.FunctionalTest):
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(409, response.status_code)
self.assertEqual(400, response.status_code)
class TestPost(api_base.FunctionalTest):
@ -384,25 +420,24 @@ class TestPost(api_base.FunctionalTest):
def setUp(self):
super(TestPost, self).setUp()
obj_utils.create_test_bay(self.context)
self.rc_obj = obj_utils.create_test_rc(self.context)
p = mock.patch.object(rpcapi.API, 'rc_create')
self.mock_rc_create = p.start()
self.mock_rc_create.side_effect = self._simulate_rpc_rc_create
self.mock_rc_create.return_value = self.rc_obj
self.addCleanup(p.stop)
p = mock.patch('magnum.objects.BayModel.get_by_uuid')
self.mock_baymodel_get_by_uuid = p.start()
self.mock_baymodel_get_by_uuid.return_value.coe = 'kubernetes'
self.addCleanup(p.stop)
def _simulate_rpc_rc_create(self, rc):
rc.create(self.context)
return rc
@mock.patch('oslo_utils.timeutils.utcnow')
def test_create_rc(self, mock_utcnow):
@mock.patch.object(rpcapi.API, 'rc_create')
def test_create_rc(self, mock_rc_create, mock_utcnow):
rc_dict = apiutils.rc_post_data()
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
mock_rc_create.return_value = self.rc_obj
response = self.post_json('/rcs', rc_dict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
@ -412,38 +447,24 @@ class TestPost(api_base.FunctionalTest):
self.assertEqual(expected_location,
urlparse.urlparse(response.location).path)
self.assertEqual(rc_dict['uuid'], response.json['uuid'])
self.assertNotIn('updated_at', response.json.keys)
return_created_at = timeutils.parse_isotime(
response.json['created_at']).replace(tzinfo=None)
self.assertEqual(test_time, return_created_at)
def test_create_rc_set_project_id_and_user_id(self):
rc_dict = apiutils.rc_post_data()
def _simulate_rpc_rc_create(rc):
self.assertEqual(self.context.project_id, rc.project_id)
self.assertEqual(self.context.user_id, rc.user_id)
rc.create()
self.assertEqual(rc.project_id, self.context.project_id)
self.assertEqual(rc.user_id, self.context.user_id)
return rc
self.mock_rc_create.side_effect = _simulate_rpc_rc_create
self.post_json('/rcs', rc_dict)
def test_create_rc_doesnt_contain_id(self):
with mock.patch.object(self.dbapi, 'create_rc',
wraps=self.dbapi.create_rc) as cc_mock:
rc_dict = apiutils.rc_post_data()
response = self.post_json('/rcs', rc_dict)
self.assertEqual(rc_dict['images'], response.json['images'])
cc_mock.assert_called_once_with(mock.ANY)
# Check that 'id' is not in first arg of positional args
self.assertNotIn('id', cc_mock.call_args[0][0])
def test_create_rc_generate_uuid(self):
@mock.patch.object(rpcapi.API, 'rc_create')
def test_create_rc_generate_uuid(self, mock_rc_create):
rc_dict = apiutils.rc_post_data()
del rc_dict['uuid']
response = self.post_json('/rcs', rc_dict)
mock_rc_create.return_value = self.rc_obj
response = self.post_json('/rcs', rc_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(rc_dict['images'], response.json['images'])
@ -494,54 +515,60 @@ class TestDelete(api_base.FunctionalTest):
super(TestDelete, self).setUp()
obj_utils.create_test_bay(self.context)
self.rc = obj_utils.create_test_rc(self.context)
p = mock.patch.object(rpcapi.API, 'rc_delete')
self.mock_rc_delete = p.start()
self.mock_rc_delete.side_effect = self._simulate_rpc_rc_delete
self.addCleanup(p.stop)
def _simulate_rpc_rc_delete(self, rc_uuid):
rc = objects.ReplicationController.get_by_uuid(self.context, rc_uuid)
rc.destroy()
def test_delete_rc(self):
@mock.patch.object(rpcapi.API, 'rc_delete')
@mock.patch.object(rpcapi.API, 'rc_show')
def test_delete_rc(self, mock_rc_show, mock_rc_delete):
self.delete('/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid))
response = self.get_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid),
expect_errors=True)
self.assertEqual(404, response.status_int)
err = rest.ApiException(status=404)
mock_rc_show.side_effect = err
response = self.get_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def test_delete_rc_not_found(self):
@mock.patch.object(rpcapi.API, 'rc_delete')
def test_delete_rc_not_found(self, mock_rc_delete):
uuid = utils.generate_uuid()
err = rest.ApiException(status=404)
mock_rc_delete.side_effect = err
response = self.delete('/rcs/%s/%s' % (uuid, self.rc.bay_uuid),
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def test_delete_rc_with_name_not_found(self):
@mock.patch.object(rpcapi.API, 'rc_delete')
def test_delete_rc_with_name_not_found(self, mock_rc_delete):
err = rest.ApiException(status=404)
mock_rc_delete.side_effect = err
response = self.delete(
'/rcs/not_found/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def test_delete_rc_with_name(self):
@mock.patch.object(rpcapi.API, 'rc_delete')
def test_delete_rc_with_name(self, mock_rc_delete):
response = self.delete('/rcs/%s/%s' % (self.rc.name, self.rc.bay_uuid),
expect_errors=True)
self.assertEqual(204, response.status_int)
def test_delete_multiple_rc_by_name(self):
@mock.patch.object(rpcapi.API, 'rc_delete')
def test_delete_multiple_rc_by_name(self, mock_rc_delete):
err = rest.ApiException(status=409)
mock_rc_delete.side_effect = err
obj_utils.create_test_rc(self.context, name='test_rc',
uuid=utils.generate_uuid())
obj_utils.create_test_rc(self.context, name='test_rc',
uuid=utils.generate_uuid())
response = self.delete(
'/rcs/test_rc/5d12f6fd-a196-4bf0-ae4c-1f639a523a5',
'/rcs/test_rc/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(409, response.status_int)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])

View File

@ -125,9 +125,11 @@ class TestK8sConductor(base.TestCase):
self.assertRaises(exception.KubernetesAPIFailed,
self.kube_handler.pod_delete,
self.context, mock_pod.uuid)
(mock_kube_api.return_value.delete_namespaced_pod
.assert_called_once_with(
name=mock_pod.name, body={}, namespace='default'))
(mock_kube_api.return_value.
delete_namespaced_pod.
assert_called_once_with(name=mock_pod.name,
body={},
namespace='default'))
self.assertFalse(mock_pod.destroy.called)
@patch('magnum.conductor.utils.object_has_stack')
@ -253,25 +255,30 @@ class TestK8sConductor(base.TestCase):
name=mock_service.name, namespace='default'))
mock_service.destroy.assert_called_once_with(self.context)
def test_rc_create_with_success(self):
expected_rc = self.mock_rc()
expected_rc.create = mock.MagicMock()
@patch('ast.literal_eval')
def test_rc_create_with_success(self, mock_ast):
expected_rc = mock.MagicMock()
manifest = {"key": "value"}
expected_rc.name = 'test-name'
expected_rc.uuid = 'test-uuid'
expected_rc.bay_uuid = 'test-bay-uuid'
expected_rc.manifest = '{"key": "value"}'
mock_ast.return_value = {}
with patch('magnum.conductor.k8s_api.create_k8s_api') as mock_kube_api:
with patch('magnum.conductor.k8s_api.create_k8s_api_rc') as \
mock_kube_api:
self.kube_handler.rc_create({}, expected_rc)
(mock_kube_api.return_value
.create_namespaced_replication_controller
.assert_called_once_with(body=manifest, namespace='default'))
def test_rc_create_with_failure(self):
expected_rc = self.mock_rc()
expected_rc.create = mock.MagicMock()
expected_rc = mock.MagicMock()
manifest = {"key": "value"}
expected_rc.manifest = '{"key": "value"}'
with patch('magnum.conductor.k8s_api.create_k8s_api') as mock_kube_api:
with patch('magnum.conductor.k8s_api.create_k8s_api_rc') as \
mock_kube_api:
err = rest.ApiException(status=500)
(mock_kube_api.return_value
.create_namespaced_replication_controller.side_effect) = err
@ -282,46 +289,57 @@ class TestK8sConductor(base.TestCase):
(mock_kube_api.return_value
.create_namespaced_replication_controller
.assert_called_once_with(body=manifest, namespace='default'))
self.assertFalse(expected_rc.create.called)
@patch('magnum.conductor.utils.object_has_stack')
@patch('magnum.objects.ReplicationController.get_by_uuid')
def test_rc_delete_with_success(self,
mock_rc_get_by_uuid,
@patch('magnum.objects.ReplicationController.get_by_name')
@patch('magnum.objects.Bay.get_by_name')
def test_rc_delete_with_success(self, mock_bay_get_by_name,
mock_rc_get_by_name,
mock_object_has_stack):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
mock_rc = mock.MagicMock()
mock_rc.name = 'test-rc'
mock_rc.uuid = 'test-uuid'
mock_rc_get_by_uuid.return_value = mock_rc
mock_rc_get_by_name.return_value = mock_rc
bay_uuid = 'test-bay-uuid'
mock_object_has_stack.return_value = True
with patch('magnum.conductor.k8s_api.create_k8s_api') as mock_kube_api:
self.kube_handler.rc_delete(self.context, mock_rc.uuid)
with patch('magnum.conductor.k8s_api.create_k8s_api_rc') as \
mock_kube_api:
self.kube_handler.rc_delete(self.context, mock_rc.name, bay_uuid)
(mock_kube_api.return_value
.delete_namespaced_replication_controller
.assert_called_once_with(name=mock_rc.name, body={},
namespace='default'))
mock_rc.destroy.assert_called_once_with(self.context)
@patch('magnum.conductor.utils.object_has_stack')
@patch('magnum.objects.ReplicationController.get_by_uuid')
def test_rc_delete_with_failure(self, mock_rc_get_by_uuid,
@patch('magnum.objects.Bay.get_by_name')
def test_rc_delete_with_failure(self, mock_bay_get_by_name,
mock_rc_get_by_uuid,
mock_object_has_stack):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
mock_rc = mock.MagicMock()
mock_rc.name = 'test-rc'
mock_rc.uuid = 'test-uuid'
mock_rc.bay_uuid = 'test-bay-uuid'
mock_rc_get_by_uuid.return_value = mock_rc
mock_object_has_stack.return_value = True
with patch('magnum.conductor.k8s_api.create_k8s_api') as mock_kube_api:
with patch('magnum.conductor.k8s_api.create_k8s_api_rc') as \
mock_kube_api:
err = rest.ApiException(status=500)
(mock_kube_api.return_value
.delete_namespaced_replication_controller.side_effect) = err
self.assertRaises(exception.KubernetesAPIFailed,
self.kube_handler.rc_delete,
self.context, mock_rc.uuid)
self.context, mock_rc.name,
mock_rc.bay_uuid)
(mock_kube_api.return_value
.delete_namespaced_replication_controller
@ -331,56 +349,88 @@ class TestK8sConductor(base.TestCase):
@patch('magnum.conductor.utils.object_has_stack')
@patch('magnum.objects.ReplicationController.get_by_uuid')
@patch('magnum.objects.Bay.get_by_name')
def test_rc_delete_succeeds_when_not_found(
self,
self, mock_bay_get_by_name,
mock_rc_get_by_uuid,
mock_object_has_stack):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
mock_rc = mock.MagicMock()
mock_rc.name = 'test-rc'
mock_rc.uuid = 'test-uuid'
mock_rc.bay_uuid = 'test-bay-uuid'
mock_rc_get_by_uuid.return_value = mock_rc
mock_object_has_stack.return_value = True
with patch('magnum.conductor.k8s_api.create_k8s_api') as mock_kube_api:
with patch('magnum.conductor.k8s_api.create_k8s_api_rc') as \
mock_kube_api:
err = rest.ApiException(status=404)
(mock_kube_api.return_value
.delete_namespaced_replication_controller.side_effect) = err
self.kube_handler.rc_delete(self.context, mock_rc.uuid)
self.kube_handler.rc_delete(self.context,
mock_rc.name,
mock_rc.bay_uuid)
(mock_kube_api.return_value
.delete_namespaced_replication_controller
.assert_called_once_with(name=mock_rc.name, body={},
namespace='default'))
self.assertTrue(mock_rc.destroy.called)
def test_rc_update_with_success(self):
expected_rc = self.mock_rc()
@patch('magnum.objects.ReplicationController.get_by_name')
@patch('magnum.objects.ReplicationController.get_by_uuid')
@patch('magnum.objects.Bay.get_by_name')
@patch('ast.literal_eval')
def test_rc_update_with_success(self, mock_ast,
mock_bay_get_by_name,
mock_rc_get_by_uuid,
mock_rc_get_by_name):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
expected_rc = mock.MagicMock()
expected_rc.uuid = 'test-uuid'
expected_rc.name = 'test-name'
expected_rc.refresh = mock.MagicMock()
expected_rc.save = mock.MagicMock()
manifest = {"key": "value"}
expected_rc.bay_uuid = 'test-bay-uuid'
expected_rc.manifest = '{"key": "value"}'
mock_ast.return_value = {}
mock_rc_get_by_uuid.return_value = expected_rc
mock_rc_get_by_name.return_value = expected_rc
name_rc = expected_rc.name
with patch('magnum.conductor.k8s_api.create_k8s_api') as mock_kube_api:
self.kube_handler.rc_update(self.context, expected_rc)
with patch('magnum.conductor.k8s_api.create_k8s_api_rc') as \
mock_kube_api:
self.kube_handler.rc_update(self.context, expected_rc.name,
expected_rc.bay_uuid,
expected_rc.manifest)
(mock_kube_api.return_value
.replace_namespaced_replication_controller
.assert_called_once_with(body=manifest, name=expected_rc.name,
namespace='default'))
expected_rc.refresh.assert_called_once_with(self.context)
expected_rc.save.assert_called_once_with()
.replace_namespaced_replication_controller
.assert_called_once_with(body=expected_rc.manifest,
name=name_rc,
namespace='default'))
def test_rc_update_with_failure(self):
expected_rc = self.mock_rc()
@patch('magnum.objects.ReplicationController.get_by_name')
@patch('magnum.objects.ReplicationController.get_by_uuid')
@patch('magnum.objects.Bay.get_by_name')
def test_rc_update_with_failure(self, mock_bay_get_by_name,
mock_rc_get_by_uuid,
mock_rc_get_by_name):
mock_bay = mock.MagicMock()
mock_bay_get_by_name.return_value = mock_bay
expected_rc = mock.MagicMock()
expected_rc.uuid = 'test-uuid'
expected_rc.name = 'test-name'
expected_rc.update = mock.MagicMock()
manifest = {"key": "value"}
expected_rc.bay_uuid = 'test-bay-uuid'
mock_rc_get_by_uuid.return_value = expected_rc
mock_rc_get_by_name.return_value = expected_rc
expected_rc.manifest = '{"key": "value"}'
name_rc = expected_rc.name
with patch('magnum.conductor.k8s_api.create_k8s_api') as mock_kube_api:
with patch('magnum.conductor.k8s_api.create_k8s_api_rc') as \
mock_kube_api:
err = rest.ApiException(status=404)
(mock_kube_api.return_value
.replace_namespaced_replication_controller
@ -388,12 +438,14 @@ class TestK8sConductor(base.TestCase):
self.assertRaises(exception.KubernetesAPIFailed,
self.kube_handler.rc_update,
self.context, expected_rc)
self.context, expected_rc.name,
expected_rc.bay_uuid,
expected_rc.manifest)
(mock_kube_api.return_value
.replace_namespaced_replication_controller
.assert_called_once_with(body=manifest, name=expected_rc.name,
.assert_called_once_with(body=expected_rc.manifest,
name=name_rc,
namespace='default'))
self.assertFalse(expected_rc.update.called)
def test_service_update_with_success(self):
expected_service = self.mock_service()

View File

@ -102,7 +102,7 @@ class TestK8sAPI(base.TestCase):
k8s_api.create_k8s_api(context, obj)
if cls is not 'Bay':
mock_bay_retrieval.assert_called_once_with(context, obj)
mock_bay_retrieval.assert_called_once_with(context, obj.bay_uuid)
mock_api_client.assert_called_once_with(
bay_obj.api_address,

View File

@ -157,18 +157,22 @@ class RPCAPITestCase(base.DbTestCase):
self._test_rpcapi('rc_update',
'call',
version='1.0',
rc=self.fake_rc)
rc_ident=self.fake_rc['uuid'],
bay_ident=self.fake_rc['bay_uuid'],
manifest={})
def test_rc_delete(self):
self._test_rpcapi('rc_delete',
'call',
version='1.0',
uuid=self.fake_rc['uuid'])
rc_ident=self.fake_rc['uuid'],
bay_ident=self.fake_rc['bay_uuid'])
self._test_rpcapi('rc_delete',
'call',
version='1.1',
uuid=self.fake_rc['name'])
rc_ident=self.fake_rc['uuid'],
bay_ident=self.fake_rc['bay_uuid'])
def test_container_create(self):
self._test_rpcapi('container_create',

View File

@ -21,35 +21,40 @@ from magnum.tests import base
class TestConductorUtils(base.TestCase):
def _test_retrieve_bay(self, obj, mock_bay_get_by_uuid):
def _test_retrieve_bay(self, expected_bay_uuid, mock_bay_get_by_uuid):
expected_context = 'context'
expected_bay_uuid = 'bay_uuid'
obj.bay_uuid = expected_bay_uuid
utils.retrieve_bay(expected_context, obj)
utils.retrieve_bay(expected_context, expected_bay_uuid)
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_pod(self,
mock_bay_get_by_uuid):
self._test_retrieve_bay(objects.Pod({}), mock_bay_get_by_uuid)
pod = objects.Pod({})
pod.bay_uuid = '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
self._test_retrieve_bay(pod.bay_uuid, mock_bay_get_by_uuid)
@patch('magnum.objects.Bay.get_by_uuid')
def test_retrieve_bay_from_service(self,
mock_bay_get_by_uuid):
self._test_retrieve_bay(objects.Service({}), 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):
self._test_retrieve_bay(objects.ReplicationController({}),
rc = objects.ReplicationController({})
rc.bay_uuid = '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
self._test_retrieve_bay(rc.bay_uuid,
mock_bay_get_by_uuid)
@patch('magnum.objects.Bay.get_by_uuid')
def test_retrieve_bay_from_container(self,
mock_bay_get_by_uuid):
self._test_retrieve_bay(objects.Container({}), mock_bay_get_by_uuid)
container = objects.Container({})
container.bay_uuid = '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
self._test_retrieve_bay(container.bay_uuid, mock_bay_get_by_uuid)
@patch('magnum.objects.BayModel.get_by_uuid')
def test_retrieve_baymodel(self, mock_baymodel_get_by_uuid):

View File

@ -431,7 +431,7 @@ object_data = {
'MyObj': '1.0-b43567e512438205e32f4e95ca616697',
'Node': '1.0-30943e6e3387a2fae7490b57c4239a17',
'Pod': '1.1-7a31c372f163742845c10a008f47cc15',
'ReplicationController': '1.0-782b7deb9307b2807101541b7e58b8a2',
'ReplicationController': '1.0-a471c2429c212ed91833cfcf0f934eab',
'Service': '1.0-a8cf7e95fced904419164dbcb6d32b38',
'X509KeyPair': '1.1-4aecc268e23e32b8a762d43ba1a4b159',
'MagnumService': '1.0-2d397ec59b0046bd5ec35cd3e06efeca',

View File

@ -13,14 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from testtools.matchers import HasLength
from magnum.common import utils as magnum_utils
from magnum import objects
from magnum.tests.unit.db import base
from magnum.tests.unit.db import utils
import mock
class TestReplicationControllerObject(base.DbTestCase):
@ -28,99 +26,70 @@ class TestReplicationControllerObject(base.DbTestCase):
super(TestReplicationControllerObject, self).setUp()
self.fake_rc = utils.get_test_rc()
def test_get_by_id(self):
rc_id = self.fake_rc['id']
with mock.patch.object(self.dbapi, 'get_rc_by_id',
autospec=True) as mock_get_rc:
mock_get_rc.return_value = self.fake_rc
rc = objects.ReplicationController.get_by_id(self.context,
rc_id)
mock_get_rc.assert_called_once_with(self.context, rc_id)
self.assertEqual(self.context, rc._context)
def test_get_by_uuid(self):
@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_rc['uuid']
with mock.patch.object(self.dbapi, 'get_rc_by_uuid',
autospec=True) as mock_get_rc:
mock_get_rc.return_value = self.fake_rc
rc = objects.ReplicationController.get_by_uuid(self.context,
uuid)
mock_get_rc.assert_called_once_with(self.context, uuid)
self.assertEqual(self.context, rc._context)
bay_uuid = self.fake_rc['bay_uuid']
mock_ast.return_value = {}
k8s_api_mock = mock.MagicMock()
mock_kube_api.return_value = k8s_api_mock
def test_get_by_name(self):
fake_obj = mock.MagicMock()
items = [
{
'metadata': {
'uid': '10a47dd1-4874-4298-91cf-eff046dbdb8d',
'name': 'fake-name',
'labels': {}
},
'status': {'replicas': 10},
'spec': {
'template': {
'spec': {
'containers': [
{
'image': 'fake-images'
}
]
}
}
}
}
]
fake_obj.items = items
fake_obj.items[0] = mock.MagicMock()
fake_obj.items[0].metadata = mock.MagicMock()
fake_obj.items[0].metadata.uid = '10a47dd1-4874-4298-91cf-eff046dbdb8d'
fake_obj.items[0].metadata.name = 'fake-name'
k8s_api_mock.list_namespaced_replication_controller\
.return_value = fake_obj
objects.ReplicationController.get_by_uuid(self.context,
uuid,
bay_uuid,
k8s_api_mock)
(k8s_api_mock.list_namespaced_replication_controller
.assert_called_once_with(namespace='default'))
@mock.patch('magnum.conductor.k8s_api.create_k8s_api')
@mock.patch('ast.literal_eval')
def test_get_by_name(self, mock_ast, mock_kube_api):
name = self.fake_rc['name']
with mock.patch.object(self.dbapi, 'get_rc_by_name',
autospec=True) as mock_get_rc:
mock_get_rc.return_value = self.fake_rc
rc = objects.ReplicationController.get_by_name(self.context,
name)
mock_get_rc.assert_called_once_with(self.context, name)
self.assertEqual(self.context, rc._context)
def test_list(self):
with mock.patch.object(self.dbapi, 'get_rc_list',
autospec=True) as mock_get_list:
mock_get_list.return_value = [self.fake_rc]
rcs = objects.ReplicationController.list(self.context)
self.assertEqual(1, mock_get_list.call_count)
self.assertThat(rcs, HasLength(1))
self.assertIsInstance(rcs[0], objects.ReplicationController)
self.assertEqual(self.context, rcs[0]._context)
def test_create(self):
with mock.patch.object(self.dbapi, 'create_rc',
autospec=True) as mock_create_rc:
mock_create_rc.return_value = self.fake_rc
rc = objects.ReplicationController(self.context, **self.fake_rc)
rc.create()
mock_create_rc.assert_called_once_with(self.fake_rc)
self.assertEqual(self.context, rc._context)
def test_destroy(self):
uuid = self.fake_rc['uuid']
with mock.patch.object(self.dbapi, 'get_rc_by_uuid',
autospec=True) as mock_get_rc:
mock_get_rc.return_value = self.fake_rc
with mock.patch.object(self.dbapi, 'destroy_rc',
autospec=True) as mock_destroy_rc:
rc = objects.ReplicationController.get_by_uuid(self.context,
uuid)
rc.destroy()
mock_get_rc.assert_called_once_with(self.context, uuid)
mock_destroy_rc.assert_called_once_with(uuid)
self.assertEqual(self.context, rc._context)
def test_save(self):
uuid = self.fake_rc['uuid']
with mock.patch.object(self.dbapi, 'get_rc_by_uuid',
autospec=True) as mock_get_rc:
mock_get_rc.return_value = self.fake_rc
with mock.patch.object(self.dbapi, 'update_rc',
autospec=True) as mock_update_rc:
rc = objects.ReplicationController.get_by_uuid(self.context,
uuid)
rc.replicas = 10
rc.save()
mock_get_rc.assert_called_once_with(self.context, uuid)
mock_update_rc.assert_called_once_with(
uuid, {'replicas': 10})
self.assertEqual(self.context, rc._context)
def test_refresh(self):
uuid = self.fake_rc['uuid']
new_uuid = magnum_utils.generate_uuid()
returns = [dict(self.fake_rc, uuid=uuid),
dict(self.fake_rc, uuid=new_uuid)]
expected = [mock.call(self.context, uuid),
mock.call(self.context, uuid)]
with mock.patch.object(self.dbapi, 'get_rc_by_uuid',
side_effect=returns,
autospec=True) as mock_get_rc:
rc = objects.ReplicationController.get_by_uuid(self.context, uuid)
self.assertEqual(uuid, rc.uuid)
rc.refresh()
self.assertEqual(new_uuid, rc.uuid)
self.assertEqual(expected, mock_get_rc.call_args_list)
self.assertEqual(self.context, rc._context)
bay_uuid = self.fake_rc['bay_uuid']
mock_ast.return_value = {}
k8s_api_mock = mock.MagicMock()
mock_kube_api.return_value = k8s_api_mock
fake_rc = mock.MagicMock()
fake_rc.metadata.uid = 'fake-uuid'
fake_rc.metadata.name = 'fake-name'
fake_rc.items[0].spec.template.spec.containers.image = ['fake-images']
fake_rc.metadata.labels = mock_ast.return_value
fake_rc.status.replicas = 10
k8s_api_mock.read_namespaced_replication_controller\
.return_value = fake_rc
objects.ReplicationController.get_by_name(self.context,
name,
bay_uuid,
k8s_api_mock)
(k8s_api_mock.read_namespaced_replication_controller
.assert_called_once_with(name=name, namespace='default'))

View File

@ -149,7 +149,7 @@ def create_test_rc(context, **kw):
ReplicationController object with appropriate attributes.
"""
rc = get_test_rc(context, **kw)
rc.create()
rc.manifest = '{"foo": "bar"}'
return rc