diff --git a/magnum/api/controllers/v1/replicationcontroller.py b/magnum/api/controllers/v1/replicationcontroller.py index f5affac0ab..c8d4afa696 100644 --- a/magnum/api/controllers/v1/replicationcontroller.py +++ b/magnum/api/controllers/v1/replicationcontroller.py @@ -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) diff --git a/magnum/common/docker_utils.py b/magnum/common/docker_utils.py index 2818b85196..d4083f7a7c 100644 --- a/magnum/common/docker_utils.py +++ b/magnum/common/docker_utils.py @@ -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 diff --git a/magnum/common/exception.py b/magnum/common/exception.py index ea7c371285..5f4114da83 100644 --- a/magnum/common/exception.py +++ b/magnum/common/exception.py @@ -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.") diff --git a/magnum/conductor/api.py b/magnum/conductor/api.py index 360704809d..70fad37de7 100644 --- a/magnum/conductor/api.py +++ b/magnum/conductor/api.py @@ -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 diff --git a/magnum/conductor/handlers/k8s_conductor.py b/magnum/conductor/handlers/k8s_conductor.py index a42e8f4aca..35871fecba 100644 --- a/magnum/conductor/handlers/k8s_conductor.py +++ b/magnum/conductor/handlers/k8s_conductor.py @@ -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 diff --git a/magnum/conductor/k8s_api.py b/magnum/conductor/k8s_api.py index c7721d83b8..68f3f2aae3 100644 --- a/magnum/conductor/k8s_api.py +++ b/magnum/conductor/k8s_api.py @@ -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) diff --git a/magnum/conductor/utils.py b/magnum/conductor/utils.py index faef25753a..c988c1f9f4 100644 --- a/magnum/conductor/utils.py +++ b/magnum/conductor/utils.py @@ -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 diff --git a/magnum/objects/replicationcontroller.py b/magnum/objects/replicationcontroller.py index 762b3bfb4d..cc612b6469 100644 --- a/magnum/objects/replicationcontroller.py +++ b/magnum/objects/replicationcontroller.py @@ -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 diff --git a/magnum/tests/functional/k8s/test_k8s_python_client.py b/magnum/tests/functional/k8s/test_k8s_python_client.py index 5ebf50beff..24cbb910a2 100644 --- a/magnum/tests/functional/k8s/test_k8s_python_client.py +++ b/magnum/tests/functional/k8s/test_k8s_python_client.py @@ -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)) + """ diff --git a/magnum/tests/unit/api/controllers/v1/test_replicationcontroller.py b/magnum/tests/unit/api/controllers/v1/test_replicationcontroller.py index 4aa7f1345e..5047530724 100644 --- a/magnum/tests/unit/api/controllers/v1/test_replicationcontroller.py +++ b/magnum/tests/unit/api/controllers/v1/test_replicationcontroller.py @@ -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']) diff --git a/magnum/tests/unit/conductor/handlers/test_k8s_conductor.py b/magnum/tests/unit/conductor/handlers/test_k8s_conductor.py index 8e6ce8c4ce..db2e225528 100644 --- a/magnum/tests/unit/conductor/handlers/test_k8s_conductor.py +++ b/magnum/tests/unit/conductor/handlers/test_k8s_conductor.py @@ -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() diff --git a/magnum/tests/unit/conductor/test_k8s_api.py b/magnum/tests/unit/conductor/test_k8s_api.py index 8ca8584d13..744a42d51e 100644 --- a/magnum/tests/unit/conductor/test_k8s_api.py +++ b/magnum/tests/unit/conductor/test_k8s_api.py @@ -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, diff --git a/magnum/tests/unit/conductor/test_rpcapi.py b/magnum/tests/unit/conductor/test_rpcapi.py index d70b58e6c0..96abba5f07 100644 --- a/magnum/tests/unit/conductor/test_rpcapi.py +++ b/magnum/tests/unit/conductor/test_rpcapi.py @@ -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', diff --git a/magnum/tests/unit/conductor/test_utils.py b/magnum/tests/unit/conductor/test_utils.py index 00a9845e70..4295d37799 100644 --- a/magnum/tests/unit/conductor/test_utils.py +++ b/magnum/tests/unit/conductor/test_utils.py @@ -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): diff --git a/magnum/tests/unit/objects/test_objects.py b/magnum/tests/unit/objects/test_objects.py index d1093bbb3e..4b3ecc8e66 100644 --- a/magnum/tests/unit/objects/test_objects.py +++ b/magnum/tests/unit/objects/test_objects.py @@ -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', diff --git a/magnum/tests/unit/objects/test_replicationcontroller.py b/magnum/tests/unit/objects/test_replicationcontroller.py index e0ee005c2d..d57150f848 100644 --- a/magnum/tests/unit/objects/test_replicationcontroller.py +++ b/magnum/tests/unit/objects/test_replicationcontroller.py @@ -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')) diff --git a/magnum/tests/unit/objects/utils.py b/magnum/tests/unit/objects/utils.py index 57b5e792c5..a7b60503f1 100644 --- a/magnum/tests/unit/objects/utils.py +++ b/magnum/tests/unit/objects/utils.py @@ -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