Remove k8s APIs pod, rcs, svc and container API

Magnum supports multiple COEs however it contains APIs that only work
with certain COEs. To create a coherent service we will only support
APIs that work across all bay types. We will allow API extensions to bay
drivers to allow Magnum to manage containers, but we are not attempting
to create a unified abstraction for containers across COEs.

A follow up patch will continue this refactor by removing the objects
for the k8s specific objects and the CLI commands from
python-magnumclient.

Implements: blueprint delete-container-endpoint
Change-Id: I1f6f04a35dfbb39f217487fea104ded035b75569
This commit is contained in:
Tom Cammann 2016-02-21 16:27:56 +00:00 committed by Ton Ngo
parent 45e394131e
commit 82f477bb90
14 changed files with 0 additions and 4339 deletions

View File

@ -29,11 +29,7 @@ from magnum.api.controllers import link
from magnum.api.controllers.v1 import bay
from magnum.api.controllers.v1 import baymodel
from magnum.api.controllers.v1 import certificate
from magnum.api.controllers.v1 import container
from magnum.api.controllers.v1 import magnum_services
from magnum.api.controllers.v1 import pod
from magnum.api.controllers.v1 import replicationcontroller as rc
from magnum.api.controllers.v1 import service
from magnum.api.controllers.v1 import x509keypair
from magnum.api import expose
from magnum.i18n import _
@ -89,24 +85,12 @@ class V1(controllers_base.APIBase):
links = [link.Link]
"""Links that point to a specific URL for this version and documentation"""
pods = [link.Link]
"""Links to the pods resource"""
rcs = [link.Link]
"""Links to the rcs resource"""
baymodels = [link.Link]
"""Links to the baymodels resource"""
bays = [link.Link]
"""Links to the bays resource"""
containers = [link.Link]
"""Links to the containers resource"""
services = [link.Link]
"""Links to the services resource"""
x509keypairs = [link.Link]
certificates = [link.Link]
@ -128,18 +112,6 @@ class V1(controllers_base.APIBase):
bookmark=True, type='text/html')]
v1.media_types = [MediaType('application/json',
'application/vnd.openstack.magnum.v1+json')]
v1.pods = [link.Link.make_link('self', pecan.request.host_url,
'pods', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'pods', '',
bookmark=True)]
v1.rcs = [link.Link.make_link('self', pecan.request.host_url,
'rcs', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'rcs', '',
bookmark=True)]
v1.baymodels = [link.Link.make_link('self', pecan.request.host_url,
'baymodels', ''),
link.Link.make_link('bookmark',
@ -152,18 +124,6 @@ class V1(controllers_base.APIBase):
pecan.request.host_url,
'bays', '',
bookmark=True)]
v1.containers = [link.Link.make_link('self', pecan.request.host_url,
'containers', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'containers', '',
bookmark=True)]
v1.services = [link.Link.make_link('self', pecan.request.host_url,
'services', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'services', '',
bookmark=True)]
v1.x509keypairs = [link.Link.make_link('self', pecan.request.host_url,
'x509keypairs', ''),
link.Link.make_link('bookmark',
@ -190,10 +150,6 @@ class Controller(rest.RestController):
bays = bay.BaysController()
baymodels = baymodel.BayModelsController()
containers = container.ContainersController()
pods = pod.PodsController()
rcs = rc.ReplicationControllersController()
services = service.ServicesController()
x509keypairs = x509keypair.X509KeyPairController()
certificates = certificate.CertificateController()
mservices = magnum_services.MagnumServiceController()

View File

@ -1,425 +0,0 @@
# Copyright 2013 UnitedStack Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from oslo_utils import timeutils
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
from magnum.api.controllers import base
from magnum.api.controllers import link
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api import expose
from magnum.api import utils as api_utils
from magnum.api import validation
from magnum.common import exception
from magnum.common import policy
from magnum.i18n import _LE
from magnum import objects
from magnum.objects import fields
LOG = logging.getLogger(__name__)
class ContainerPatchType(types.JsonPatchType):
@staticmethod
def mandatory_attrs():
return ['/bay_uuid']
class Container(base.APIBase):
"""API representation of a container.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of a
container.
"""
_bay_uuid = None
def _get_bay_uuid(self):
return self._bay_uuid
def _set_bay_uuid(self, value):
if value and self._bay_uuid != value:
try:
bay = objects.Bay.get_by_uuid(pecan.request.context, value)
self._bay_uuid = bay['uuid']
except exception.BayNotFound as e:
# Change error code because 404 (NotFound) is inappropriate
# response for a POST request to create a Service
e.code = 400 # BadRequest
raise e
elif value == wtypes.Unset:
self._bay_uuid = wtypes.Unset
uuid = types.uuid
"""Unique UUID for this container"""
name = wtypes.StringType(min_length=1, max_length=255)
"""Name of this container"""
image = wtypes.text
"""The image name or ID to use as a base image for this container"""
bay_uuid = wsme.wsproperty(types.uuid, _get_bay_uuid, _set_bay_uuid,
mandatory=True)
"""Unique UUID of the bay this runs on"""
links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link and associated container links"""
command = wtypes.text
"""The command execute when container starts"""
status = wtypes.text
"""The status of container"""
memory = wtypes.text
"""Memory limit for the container. Example: 512m"""
environment = wtypes.DictType(str, str)
"""One or more key/value pairs"""
def __init__(self, **kwargs):
self.fields = []
for field in objects.Container.fields:
# Skip fields we do not expose.
if not hasattr(self, field):
continue
self.fields.append(field)
setattr(self, field, kwargs.get(field, wtypes.Unset))
@staticmethod
def _convert_with_links(container, url, expand=True):
if not expand:
container.unset_fields_except(['uuid', 'name', 'bay_uuid',
'image', 'command', 'status',
'memory', 'environment'])
container.links = [link.Link.make_link(
'self', url,
'containers', container.uuid),
link.Link.make_link(
'bookmark', url,
'containers', container.uuid,
bookmark=True)]
return container
@classmethod
def convert_with_links(cls, rpc_container, expand=True):
container = Container(**rpc_container.as_dict())
return cls._convert_with_links(container, pecan.request.host_url,
expand)
@classmethod
def sample(cls, expand=True):
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
name='example',
image='ubuntu',
command='env',
status='Running',
memory='512m',
environment={'key1': 'val1', 'key2': 'val2'},
bay_uuid="fff114da-3bfa-4a0f-a123-c0dffad9718e",
created_at=timeutils.utcnow(),
updated_at=timeutils.utcnow())
return cls._convert_with_links(sample, 'http://localhost:9511', expand)
class ContainerCollection(collection.Collection):
"""API representation of a collection of containers."""
containers = [Container]
"""A list containing containers objects"""
def __init__(self, **kwargs):
self._type = 'containers'
@staticmethod
def convert_with_links(rpc_containers, limit, url=None,
expand=False, **kwargs):
collection = ContainerCollection()
collection.containers = [Container.convert_with_links(p, expand)
for p in rpc_containers]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
@classmethod
def sample(cls):
sample = cls()
sample.containers = [Container.sample(expand=False)]
return sample
def check_policy_on_container(container, action):
context = pecan.request.context
policy.enforce(context, action, container, action=action)
class StartController(object):
@expose.expose(types.uuid_or_name, wtypes.text)
def _default(self, container_ident):
if pecan.request.method != 'PUT':
pecan.abort(405, ('HTTP method %s is not allowed'
% pecan.request.method))
container = api_utils.get_resource('Container',
container_ident)
check_policy_on_container(container, "container:start")
LOG.debug('Calling conductor.container_start with %s',
container.uuid)
return pecan.request.rpcapi.container_start(container.uuid)
class StopController(object):
@expose.expose(types.uuid_or_name, wtypes.text)
def _default(self, container_ident):
if pecan.request.method != 'PUT':
pecan.abort(405, ('HTTP method %s is not allowed'
% pecan.request.method))
container = api_utils.get_resource('Container',
container_ident)
check_policy_on_container(container, "container:stop")
LOG.debug('Calling conductor.container_stop with %s',
container.uuid)
return pecan.request.rpcapi.container_stop(container.uuid)
class RebootController(object):
@expose.expose(types.uuid_or_name, wtypes.text)
def _default(self, container_ident):
if pecan.request.method != 'PUT':
pecan.abort(405, ('HTTP method %s is not allowed'
% pecan.request.method))
container = api_utils.get_resource('Container',
container_ident)
check_policy_on_container(container, "container:reboot")
LOG.debug('Calling conductor.container_reboot with %s',
container.uuid)
return pecan.request.rpcapi.container_reboot(container.uuid)
class PauseController(object):
@expose.expose(types.uuid_or_name, wtypes.text)
def _default(self, container_ident):
if pecan.request.method != 'PUT':
pecan.abort(405, ('HTTP method %s is not allowed'
% pecan.request.method))
container = api_utils.get_resource('Container',
container_ident)
check_policy_on_container(container, "container:pause")
LOG.debug('Calling conductor.container_pause with %s',
container.uuid)
return pecan.request.rpcapi.container_pause(container.uuid)
class UnpauseController(object):
@expose.expose(types.uuid_or_name, wtypes.text)
def _default(self, container_ident):
if pecan.request.method != 'PUT':
pecan.abort(405, ('HTTP method %s is not allowed'
% pecan.request.method))
container = api_utils.get_resource('Container',
container_ident)
check_policy_on_container(container, "container:unpause")
LOG.debug('Calling conductor.container_unpause with %s',
container.uuid)
return pecan.request.rpcapi.container_unpause(container.uuid)
class LogsController(object):
@expose.expose(types.uuid_or_name, wtypes.text)
def _default(self, container_ident):
if pecan.request.method != 'GET':
pecan.abort(405, ('HTTP method %s is not allowed'
% pecan.request.method))
container = api_utils.get_resource('Container',
container_ident)
check_policy_on_container(container, "container:logs")
LOG.debug('Calling conductor.container_logs with %s',
container.uuid)
return pecan.request.rpcapi.container_logs(container.uuid)
class ExecuteController(object):
@expose.expose(types.uuid_or_name, wtypes.text, wtypes.text)
def _default(self, container_ident, command):
if pecan.request.method != 'PUT':
pecan.abort(405, ('HTTP method %s is not allowed'
% pecan.request.method))
container = api_utils.get_resource('Container',
container_ident)
check_policy_on_container(container, "container:execute")
LOG.debug('Calling conductor.container_exec with %s command %s',
container.uuid, command)
return pecan.request.rpcapi.container_exec(container.uuid, command)
def check_policy_on_bay(bay_id, action):
context = pecan.request.context
bay = api_utils.get_resource('Bay', bay_id)
policy.enforce(context, action, bay, action=action)
class ContainersController(rest.RestController):
"""REST controller for Containers."""
def __init__(self):
super(ContainersController, self).__init__()
start = StartController()
stop = StopController()
reboot = RebootController()
pause = PauseController()
unpause = UnpauseController()
logs = LogsController()
execute = ExecuteController()
_custom_actions = {
'detail': ['GET'],
}
def _get_containers_collection(self, marker, limit,
sort_key, sort_dir, expand=False,
resource_url=None, bay_ident=None):
context = pecan.request.context
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
filters = None
marker_obj = None
if marker:
marker_obj = objects.Container.get_by_uuid(context,
marker)
if bay_ident:
bay_obj = api_utils.get_resource('Bay', bay_ident)
filters = {'bay_uuid': bay_obj.uuid}
containers = objects.Container.list(context,
limit,
marker_obj,
sort_key,
sort_dir,
filters=filters)
for i, c in enumerate(containers):
try:
containers[i] = pecan.request.rpcapi.container_show(c.uuid)
except Exception as e:
LOG.exception(_LE("Error while list container %(uuid)s: "
"%(e)s."),
{'uuid': c.uuid, 'e': e})
containers[i].status = fields.ContainerStatus.UNKNOWN
return ContainerCollection.convert_with_links(containers, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@expose.expose(ContainerCollection, types.uuid, int,
wtypes.text, wtypes.text, types.uuid_or_name)
def get_all(self, marker=None, limit=None, sort_key='id',
sort_dir='asc', bay_ident=None):
"""Retrieve a list of containers.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param bay_indent: UUID or logical name of bay.
"""
context = pecan.request.context
policy.enforce(context, "container:get_all",
action="container:get_all")
return self._get_containers_collection(marker, limit, sort_key,
sort_dir, bay_ident=bay_ident)
@expose.expose(ContainerCollection, types.uuid, int,
wtypes.text, wtypes.text)
def detail(self, marker=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of containers with detail.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
"""
context = pecan.request.context
policy.enforce(context, "container:detail",
action="container:detail")
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "containers":
raise exception.HTTPNotFound
expand = True
resource_url = '/'.join(['containers', 'detail'])
return self._get_containers_collection(marker, limit,
sort_key, sort_dir, expand,
resource_url)
@expose.expose(Container, types.uuid_or_name)
def get_one(self, container_ident):
"""Retrieve information about the given container.
:param container_ident: UUID or name of a container.
"""
container = api_utils.get_resource('Container',
container_ident)
check_policy_on_container(container, "container:get")
res_container = pecan.request.rpcapi.container_show(container.uuid)
return Container.convert_with_links(res_container)
@expose.expose(Container, body=Container, status_code=201)
@validation.enforce_bay_types('swarm')
def post(self, container):
"""Create a new container.
:param container: a container within the request body.
"""
container_dict = container.as_dict()
check_policy_on_bay(container_dict['bay_uuid'], "container:create")
context = pecan.request.context
container_dict['project_id'] = context.project_id
container_dict['user_id'] = context.user_id
if 'memory' in container_dict:
api_utils.validate_docker_memory(container_dict['memory'])
new_container = objects.Container(context, **container_dict)
new_container.create()
res_container = pecan.request.rpcapi.container_create(new_container)
# Set the HTTP Location Header
pecan.response.location = link.build_url('containers',
res_container.uuid)
return Container.convert_with_links(res_container)
@expose.expose(None, types.uuid_or_name, status_code=204)
def delete(self, container_ident):
"""Delete a container.
:param container_ident: UUID or Name of a container.
"""
container = api_utils.get_resource('Container',
container_ident)
check_policy_on_container(container, "container:delete")
pecan.request.rpcapi.container_delete(container.uuid)
container.destroy()

View File

@ -1,308 +0,0 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import timeutils
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
from magnum.api.controllers import link
from magnum.api.controllers.v1 import base as v1_base
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api import expose
from magnum.api import utils as api_utils
from magnum.api import validation
from magnum.common import exception
from magnum.common import k8s_manifest
from magnum.common import policy
from magnum.i18n import _
from magnum import objects
class PodPatchType(v1_base.K8sPatchType):
pass
class Pod(v1_base.K8sResourceBase):
"""API representation of a pod.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of a pod.
"""
uuid = types.uuid
"""Unique UUID for this pod"""
desc = wtypes.text
"""Description of this pod"""
images = [wtypes.text]
"""A list of images used by containers in this pod."""
status = wtypes.text
"""Staus of this pod """
links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link and associated pod links"""
host = wtypes.text
"""The host of this pod"""
def __init__(self, **kwargs):
super(Pod, self).__init__()
self.fields = []
for field in objects.Pod.fields:
# Skip fields we do not expose.
if not hasattr(self, field):
continue
self.fields.append(field)
setattr(self, field, kwargs.get(field, wtypes.Unset))
@staticmethod
def _convert_with_links(pod, url, expand=True):
if not expand:
pod.unset_fields_except(['uuid', 'name', 'desc', 'bay_uuid',
'images', 'labels', 'status', 'host'])
pod.links = [link.Link.make_link('self', url,
'pods', pod.uuid),
link.Link.make_link('bookmark', url,
'pods', pod.uuid,
bookmark=True)
]
return pod
@classmethod
def convert_with_links(cls, rpc_pod, expand=True):
pod = Pod(**rpc_pod.as_dict())
return cls._convert_with_links(pod, pecan.request.host_url, expand)
@classmethod
def sample(cls, expand=True):
sample = cls(uuid='f978db47-9a37-4e9f-8572-804a10abc0aa',
name='MyPod',
desc='Pod - Description',
bay_uuid='7ae81bb3-dec3-4289-8d6c-da80bd8001ae',
images=['MyImage'],
labels={'name': 'foo'},
status='Running',
host='10.0.0.3',
manifest_url='file:///tmp/rc.yaml',
manifest='''{
"metadata": {
"name": "name_of_pod"
},
"spec": {
"containers": [
{
"name": "test",
"image": "test"
}
]
}
}''',
created_at=timeutils.utcnow(),
updated_at=timeutils.utcnow())
return cls._convert_with_links(sample, 'http://localhost:9511', expand)
def parse_manifest(self):
try:
manifest = k8s_manifest.parse(self._get_manifest())
except ValueError as e:
raise exception.InvalidParameterValue(message=str(e))
try:
self.name = manifest["metadata"]["name"]
except (KeyError, TypeError):
raise exception.InvalidParameterValue(
_("Field metadata['name'] can't be empty in manifest."))
images = []
try:
for container in manifest["spec"]["containers"]:
images.append(container["image"])
self.images = images
except (KeyError, TypeError):
raise exception.InvalidParameterValue(
_("Field spec['containers'] can't be empty in manifest."))
if "labels" in manifest["metadata"]:
self.labels = manifest["metadata"]["labels"]
class PodCollection(collection.Collection):
"""API representation of a collection of pods."""
pods = [Pod]
"""A list containing pods objects"""
def __init__(self, **kwargs):
self._type = 'pods'
@staticmethod
def convert_with_links(rpc_pods, limit, url=None, expand=False, **kwargs):
collection = PodCollection()
collection.pods = [Pod.convert_with_links(p, expand)
for p in rpc_pods]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
@classmethod
def sample(cls):
sample = cls()
sample.pods = [Pod.sample(expand=False)]
return sample
class PodsController(rest.RestController):
"""REST controller for Pods."""
def __init__(self):
super(PodsController, self).__init__()
_custom_actions = {
'detail': ['GET'],
}
def _get_pods_collection(self, marker, limit,
sort_key, sort_dir,
bay_ident, expand=False,
resource_url=None):
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
context = pecan.request.context
pods = pecan.request.rpcapi.pod_list(context, bay_ident)
return PodCollection.convert_with_links(pods, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@expose.expose(PodCollection, types.uuid, types.uuid_or_name, int,
wtypes.text, wtypes.text)
@policy.enforce_wsgi("pod")
@validation.enforce_bay_types('kubernetes')
def get_all(self, marker=None, bay_ident=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of pods.
:param marker: pagination marker for large data sets.
:param bay_ident: UUID or logical name of the Bay.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
"""
return self._get_pods_collection(marker, limit, sort_key,
sort_dir, bay_ident)
@expose.expose(PodCollection, types.uuid, types.uuid_or_name, int,
wtypes.text, wtypes.text)
@policy.enforce_wsgi("pod")
@validation.enforce_bay_types('kubernetes')
def detail(self, marker=None, bay_ident=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of pods with detail.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param bay_ident: UUID or logical name of the Bay.
"""
# NOTE(lucasagomes): /detail should only work against collections
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "pods":
raise exception.HTTPNotFound
expand = True
resource_url = '/'.join(['pods', 'detail'])
return self._get_pods_collection(marker, limit,
sort_key, sort_dir,
bay_ident, expand,
resource_url)
@expose.expose(Pod, types.uuid_or_name,
types.uuid_or_name)
@policy.enforce_wsgi("pod", "get")
@validation.enforce_bay_types('kubernetes')
def get_one(self, pod_ident, bay_ident):
"""Retrieve information about the given pod.
:param pod_ident: UUID of a pod or logical name of the pod.
:param bay_ident: UUID or logical name of the Bay.
"""
context = pecan.request.context
rpc_pod = pecan.request.rpcapi.pod_show(context, pod_ident, bay_ident)
return Pod.convert_with_links(rpc_pod)
@expose.expose(Pod, body=Pod, status_code=201)
@policy.enforce_wsgi("pod", "create")
@validation.enforce_bay_types('kubernetes')
def post(self, pod):
"""Create a new pod.
:param pod: a pod within the request body.
"""
pod.parse_manifest()
pod_dict = pod.as_dict()
context = pecan.request.context
pod_dict['project_id'] = context.project_id
pod_dict['user_id'] = context.user_id
pod_obj = objects.Pod(context, **pod_dict)
new_pod = pecan.request.rpcapi.pod_create(pod_obj)
# Set the HTTP Location Header
pecan.response.location = link.build_url('pods', new_pod.uuid)
return Pod.convert_with_links(new_pod)
@wsme.validate(types.uuid, [PodPatchType])
@expose.expose(Pod, types.uuid_or_name,
types.uuid_or_name, body=[PodPatchType])
@policy.enforce_wsgi("pod", "update")
@validation.enforce_bay_types('kubernetes')
def patch(self, pod_ident, bay_ident, patch):
"""Update an existing pod.
:param pod_ident: UUID or logical name of a pod.
:param bay_ident: UUID or logical name of the Bay.
:param patch: a json PATCH document to apply to this pod.
"""
pod_dict = {}
pod_dict['manifest'] = None
pod_dict['manifest_url'] = None
try:
pod = Pod(**api_utils.apply_jsonpatch(pod_dict, patch))
if pod.manifest or pod.manifest_url:
pod.parse_manifest()
except api_utils.JSONPATCH_EXCEPTIONS as e:
raise exception.PatchError(patch=patch, reason=e)
rpc_pod = pecan.request.rpcapi.pod_update(pod_ident, bay_ident,
pod.manifest)
return Pod.convert_with_links(rpc_pod)
@expose.expose(None, types.uuid_or_name,
types.uuid_or_name, status_code=204)
@policy.enforce_wsgi("pod")
@validation.enforce_bay_types('kubernetes')
def delete(self, pod_ident, bay_ident):
"""Delete a pod.
:param pod_ident: UUID of a pod or logical name of the pod.
:param bay_ident: UUID or logical name of the Bay.
"""
pecan.request.rpcapi.pod_delete(pod_ident, bay_ident)

View File

@ -1,341 +0,0 @@
# Copyright 2015 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import timeutils
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
from magnum.api.controllers import link
from magnum.api.controllers.v1 import base as v1_base
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api import expose
from magnum.api import utils as api_utils
from magnum.api import validation
from magnum.common import exception
from magnum.common import k8s_manifest
from magnum.common import policy
from magnum.i18n import _
from magnum import objects
class ReplicationControllerPatchType(v1_base.K8sPatchType):
@staticmethod
def internal_attrs():
defaults = v1_base.K8sPatchType.internal_attrs()
return defaults + ['/replicas']
class ReplicationController(v1_base.K8sResourceBase):
"""API representation of a ReplicationController.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of a
ReplicationController.
"""
uuid = types.uuid
"""Unique UUID for this ReplicationController"""
images = [wtypes.text]
"""A list of images used by containers in this ReplicationController."""
replicas = wsme.wsattr(wtypes.IntegerType(), readonly=True)
"""Replicas of this ReplicationController"""
links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link and associated rc links"""
def __init__(self, **kwargs):
super(ReplicationController, self).__init__()
self.fields = []
for field in objects.ReplicationController.fields:
# Skip fields we do not expose.
if not hasattr(self, field):
continue
self.fields.append(field)
setattr(self, field, kwargs.get(field, wtypes.Unset))
@staticmethod
def _convert_with_links(rc, url, expand=True):
if not expand:
rc.unset_fields_except(['uuid', 'name', 'images', 'bay_uuid',
'labels', 'replicas'])
rc.links = [link.Link.make_link('self', url,
'rcs', rc.uuid),
link.Link.make_link('bookmark', url,
'rcs', rc.uuid,
bookmark=True)]
return rc
@classmethod
def convert_with_links(cls, rpc_rc, expand=True):
rc = ReplicationController(**rpc_rc.as_dict())
return cls._convert_with_links(rc, pecan.request.host_url, expand)
@classmethod
def sample(cls, expand=True):
sample = cls(uuid='f978db47-9a37-4e9f-8572-804a10abc0aa',
name='MyReplicationController',
images=['MyImage'],
bay_uuid='f978db47-9a37-4e9f-8572-804a10abc0ab',
labels={'name': 'foo'},
replicas=2,
manifest_url='file:///tmp/rc.yaml',
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"
}
]
}
]
}
}
}
}''',
created_at=timeutils.utcnow(),
updated_at=timeutils.utcnow())
return cls._convert_with_links(sample, 'http://localhost:9511', expand)
def parse_manifest(self):
try:
manifest = k8s_manifest.parse(self._get_manifest())
except ValueError as e:
raise exception.InvalidParameterValue(message=str(e))
try:
self.name = manifest["metadata"]["name"]
except (KeyError, TypeError):
raise exception.InvalidParameterValue(
_("Field metadata['name'] can't be empty in manifest."))
try:
self.replicas = manifest["spec"]["replicas"]
except (KeyError, TypeError):
pass
try:
self.selector = manifest["spec"]["selector"]
except (KeyError, TypeError):
raise exception.InvalidParameterValue(
_("Field spec['selector'] can't be empty in manifest."))
try:
self.labels = manifest["spec"]["template"]["metadata"]["labels"]
except (KeyError, TypeError):
raise exception.InvalidParameterValue(_(
"Field spec['template']['metadata']['labels'] "
"can't be empty in manifest."))
try:
images = []
for cont in manifest["spec"]["template"]["spec"]["containers"]:
images.append(cont["image"])
self.images = images
except (KeyError, TypeError):
raise exception.InvalidParameterValue(_(
"Field spec['template']['spec']['containers'] "
"can't be empty in manifest."))
class ReplicationControllerCollection(collection.Collection):
"""API representation of a collection of ReplicationControllers."""
rcs = [ReplicationController]
"""A list containing ReplicationController objects"""
def __init__(self, **kwargs):
self._type = 'rcs'
@staticmethod
def convert_with_links(rpc_rcs, limit, url=None, expand=False, **kwargs):
collection = ReplicationControllerCollection()
collection.rcs = [ReplicationController.convert_with_links(p, expand)
for p in rpc_rcs]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
@classmethod
def sample(cls):
sample = cls()
sample.rcs = [ReplicationController.sample(expand=False)]
return sample
class ReplicationControllersController(rest.RestController):
"""REST controller for ReplicationControllers."""
def __init__(self):
super(ReplicationControllersController, self).__init__()
_custom_actions = {
'detail': ['GET'],
}
def _get_rcs_collection(self, marker, limit,
sort_key, sort_dir,
bay_ident, expand=False,
resource_url=None):
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
rcs = pecan.request.rpcapi.rc_list(pecan.request.context, bay_ident)
return ReplicationControllerCollection.convert_with_links(
rcs, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@expose.expose(ReplicationControllerCollection, types.uuid,
types.uuid_or_name, int, wtypes.text, wtypes.text)
@policy.enforce_wsgi("rc")
@validation.enforce_bay_types('kubernetes')
def get_all(self, marker=None, bay_ident=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of ReplicationControllers.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param bay_ident: UUID or logical name of the Bay.
"""
return self._get_rcs_collection(marker, limit, sort_key,
sort_dir, bay_ident)
@expose.expose(ReplicationControllerCollection, types.uuid,
types.uuid_or_name, int, wtypes.text, wtypes.text)
@policy.enforce_wsgi("rc")
@validation.enforce_bay_types('kubernetes')
def detail(self, marker=None, bay_ident=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of ReplicationControllers with detail.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param bay_ident: UUID or logical name of the Bay.
"""
# NOTE(jay-lau-513): /detail should only work against collections
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "rcs":
raise exception.HTTPNotFound
expand = True
resource_url = '/'.join(['rcs', 'detail'])
return self._get_rcs_collection(marker, limit,
sort_key, sort_dir,
bay_ident, expand,
resource_url)
@expose.expose(ReplicationController, types.uuid_or_name,
types.uuid_or_name)
@policy.enforce_wsgi("rc", "get")
@validation.enforce_bay_types('kubernetes')
def get_one(self, rc_ident, bay_ident):
"""Retrieve information about the given ReplicationController.
:param rc_ident: UUID or logical name of a ReplicationController.
:param bay_ident: UUID or logical name of the Bay.
"""
context = pecan.request.context
rpc_rc = pecan.request.rpcapi.rc_show(context, rc_ident, bay_ident)
return ReplicationController.convert_with_links(rpc_rc)
@expose.expose(ReplicationController, body=ReplicationController,
status_code=201)
@policy.enforce_wsgi("rc", "create")
@validation.enforce_bay_types('kubernetes')
def post(self, rc):
"""Create a new ReplicationController.
:param rc: a ReplicationController within the request body.
"""
rc.parse_manifest()
rc_dict = rc.as_dict()
context = pecan.request.context
rc_dict['project_id'] = context.project_id
rc_dict['user_id'] = context.user_id
rc_obj = objects.ReplicationController(context, **rc_dict)
new_rc = pecan.request.rpcapi.rc_create(rc_obj)
if not new_rc:
raise exception.InvalidState()
# Set the HTTP Location Header
pecan.response.location = link.build_url('rcs', new_rc.uuid)
return ReplicationController.convert_with_links(new_rc)
@wsme.validate(types.uuid, [ReplicationControllerPatchType])
@expose.expose(ReplicationController, types.uuid_or_name,
types.uuid_or_name, body=[ReplicationControllerPatchType])
@policy.enforce_wsgi("rc", "update")
@validation.enforce_bay_types('kubernetes')
def patch(self, rc_ident, bay_ident, patch):
"""Update an existing rc.
:param rc_ident: UUID or logical name of a ReplicationController.
:param bay_ident: UUID or logical name of the Bay.
:param patch: a json PATCH document to apply to this rc.
"""
rc_dict = {}
rc_dict['manifest'] = None
rc_dict['manifest_url'] = None
try:
rc = ReplicationController(**api_utils.apply_jsonpatch(rc_dict,
patch))
if rc.manifest or rc.manifest_url:
rc.parse_manifest()
except api_utils.JSONPATCH_EXCEPTIONS as e:
raise exception.PatchError(patch=patch, reason=e)
rpc_rc = pecan.request.rpcapi.rc_update(rc_ident,
bay_ident,
rc.manifest)
return ReplicationController.convert_with_links(rpc_rc)
@expose.expose(None, types.uuid_or_name,
types.uuid_or_name, status_code=204)
@policy.enforce_wsgi("rc")
@validation.enforce_bay_types('kubernetes')
def delete(self, rc_ident, bay_ident):
"""Delete a ReplicationController.
:param rc_ident: UUID or logical name of a ReplicationController.
:param bay_ident: UUID or logical name of the Bay.
"""
pecan.request.rpcapi.rc_delete(rc_ident, bay_ident)

View File

@ -1,316 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_utils import timeutils
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
from magnum.api.controllers import link
from magnum.api.controllers.v1 import base as v1_base
from magnum.api.controllers.v1 import collection
from magnum.api.controllers.v1 import types
from magnum.api import expose
from magnum.api import utils as api_utils
from magnum.api import validation
from magnum.common import exception
from magnum.common import k8s_manifest
from magnum.common import policy
from magnum.i18n import _
from magnum import objects
class ServicePatchType(v1_base.K8sPatchType):
@staticmethod
def internal_attrs():
defaults = v1_base.K8sPatchType.internal_attrs()
return defaults + ['/selector', '/ports', '/ip']
class Service(v1_base.K8sResourceBase):
uuid = types.uuid
"""Unique UUID for this service"""
selector = wsme.wsattr({wtypes.text: wtypes.text}, readonly=True)
"""Selector of this service"""
ip = wtypes.text
"""IP of this service"""
ports = wsme.wsattr([{wtypes.text: wtypes.IntegerType()}], readonly=True)
"""Port of this service"""
links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link and associated service links"""
def __init__(self, **kwargs):
super(Service, self).__init__()
self.fields = []
for field in objects.Service.fields:
# Skip fields we do not expose.
if not hasattr(self, field):
continue
self.fields.append(field)
setattr(self, field, kwargs.get(field, wtypes.Unset))
@staticmethod
def _convert_with_links(service, url, expand=True):
if not expand:
service.unset_fields_except(['uuid', 'name', 'bay_uuid', 'labels',
'selector', 'ip', 'ports'])
service.links = [link.Link.make_link('self', url,
'services', service.uuid),
link.Link.make_link('bookmark', url,
'services', service.uuid,
bookmark=True)
]
return service
@classmethod
def convert_with_links(cls, rpc_service, expand=True):
service = Service(**rpc_service.as_dict())
return cls._convert_with_links(service, pecan.request.host_url, expand)
@classmethod
def sample(cls, expand=True):
sample = cls(uuid='fe78db47-9a37-4e9f-8572-804a10abc0aa',
name='MyService',
bay_uuid='7ae81bb3-dec3-4289-8d6c-da80bd8001ae',
labels={'label1': 'foo'},
selector={'label1': 'foo'},
ip='172.17.2.2',
ports=[{"port": 88,
"targetPort": 6379,
"protocol": "TCP"}],
manifest_url='file:///tmp/rc.yaml',
manifest='''{
"metadata": {
"name": "test",
"labels": {
"key": "value"
}
},
"spec": {
"ports": [
{
"port": 88,
"targetPort": 6379,
"protocol": "TCP"
}
],
"selector": {
"bar": "foo"
}
}
}''',
created_at=timeutils.utcnow(),
updated_at=timeutils.utcnow())
return cls._convert_with_links(sample, 'http://localhost:9511', expand)
def parse_manifest(self):
try:
manifest = k8s_manifest.parse(self._get_manifest())
except ValueError as e:
raise exception.InvalidParameterValue(message=str(e))
try:
self.name = manifest["metadata"]["name"]
except (KeyError, TypeError):
raise exception.InvalidParameterValue(
_("Field metadata['name'] can't be empty in manifest."))
try:
self.ports = manifest["spec"]["ports"][:]
except (KeyError, TypeError):
raise exception.InvalidParameterValue(
_("Field spec['ports'] can't be empty in manifest."))
if "selector" in manifest["spec"]:
self.selector = manifest["spec"]["selector"]
if "labels" in manifest["metadata"]:
self.labels = manifest["metadata"]["labels"]
class ServiceCollection(collection.Collection):
"""API representation of a collection of services."""
services = [Service]
"""A list containing services objects"""
def __init__(self, **kwargs):
self._type = 'services'
@staticmethod
def convert_with_links(rpc_services, limit, url=None,
expand=False, **kwargs):
collection = ServiceCollection()
collection.services = [Service.convert_with_links(p, expand)
for p in rpc_services]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
@classmethod
def sample(cls):
sample = cls()
sample.services = [Service.sample(expand=False)]
return sample
class ServicesController(rest.RestController):
"""REST controller for Services."""
def __init__(self):
super(ServicesController, self).__init__()
_custom_actions = {
'detail': ['GET'],
}
def _get_services_collection(self, marker, limit,
sort_key, sort_dir,
bay_ident, expand=False,
resource_url=None):
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
context = pecan.request.context
services = pecan.request.rpcapi.service_list(context, bay_ident)
return ServiceCollection.convert_with_links(services, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)
@expose.expose(ServiceCollection, types.uuid, types.uuid_or_name, int,
wtypes.text, wtypes.text)
@policy.enforce_wsgi("service")
@validation.enforce_bay_types('kubernetes')
def get_all(self, marker=None, bay_ident=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of services.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param bay_ident: UUID or logical name of the Bay.
"""
return self._get_services_collection(marker, limit, sort_key,
sort_dir, bay_ident)
@expose.expose(ServiceCollection, types.uuid, types.uuid_or_name, int,
wtypes.text, wtypes.text)
@policy.enforce_wsgi("service")
@validation.enforce_bay_types('kubernetes')
def detail(self, marker=None, bay_ident=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of services with detail.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param bay_ident: UUID or logical name of the Bay.
"""
# NOTE(lucasagomes): /detail should only work against collections
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "services":
raise exception.HTTPNotFound
expand = True
resource_url = '/'.join(['services', 'detail'])
return self._get_services_collection(marker, limit,
sort_key, sort_dir,
bay_ident, expand,
resource_url)
@expose.expose(Service, types.uuid_or_name,
types.uuid_or_name)
@policy.enforce_wsgi("service", "get")
@validation.enforce_bay_types('kubernetes')
def get_one(self, service_ident, bay_ident):
"""Retrieve information about the given service.
:param service_ident: UUID or logical name of the service.
:param bay_ident: UUID or logical name of the Bay.
"""
context = pecan.request.context
rpc_service = pecan.request.rpcapi.service_show(context,
service_ident,
bay_ident)
return Service.convert_with_links(rpc_service)
@expose.expose(Service, body=Service, status_code=201)
@policy.enforce_wsgi("service", "create")
@validation.enforce_bay_types('kubernetes')
def post(self, service):
"""Create a new service.
:param service: a service within the request body.
"""
service.parse_manifest()
service_dict = service.as_dict()
context = pecan.request.context
service_dict['project_id'] = context.project_id
service_dict['user_id'] = context.user_id
service_obj = objects.Service(context, **service_dict)
new_service = pecan.request.rpcapi.service_create(service_obj)
if new_service is None:
raise exception.InvalidState()
# Set the HTTP Location Header
pecan.response.location = link.build_url('services', new_service.uuid)
return Service.convert_with_links(new_service)
@wsme.validate(types.uuid, [ServicePatchType])
@expose.expose(Service, types.uuid_or_name,
types.uuid_or_name, body=[ServicePatchType])
@policy.enforce_wsgi("service", "update")
@validation.enforce_bay_types('kubernetes')
def patch(self, service_ident, bay_ident, patch):
"""Update an existing service.
:param service_ident: UUID or logical name of a service.
:param bay_ident: UUID or logical name of the Bay.
:param patch: a json PATCH document to apply to this service.
"""
service_dict = {}
service_dict['manifest'] = None
service_dict['manifest_url'] = None
try:
service = Service(**api_utils.apply_jsonpatch(service_dict, patch))
if service.manifest or service.manifest_url:
service.parse_manifest()
except api_utils.JSONPATCH_EXCEPTIONS as e:
raise exception.PatchError(patch=patch, reason=e)
rpc_service = pecan.request.rpcapi.service_update(service_ident,
bay_ident,
service.manifest)
return Service.convert_with_links(rpc_service)
@expose.expose(None, types.uuid_or_name,
types.uuid_or_name, status_code=204)
@policy.enforce_wsgi("service")
@validation.enforce_bay_types('kubernetes')
def delete(self, service_ident, bay_ident):
"""Delete a service.
:param service_ident: UUID or logical name of a service.
:param bay_ident: UUID or logical name of the Bay.
"""
pecan.request.rpcapi.service_delete(service_ident, bay_ident)

View File

@ -20,6 +20,3 @@ class TestListResources(BaseMagnumClient):
def test_bay_list(self):
self.assertIsNotNone(self.cs.bays.list())
def test_containers_list(self):
self.assertIsNotNone(self.cs.containers.list())

View File

@ -18,7 +18,6 @@ from requests import exceptions as req_exceptions
from magnum.common import docker_utils
from magnum.tests.functional.python_client_base import BayTest
from magnumclient.common.apiclient import exceptions
CONF = cfg.CONF
@ -150,28 +149,3 @@ class TestSwarmAPIs(BayTest):
def test_access_with_non_tls_client(self):
self.assertRaises(req_exceptions.SSLError,
self.docker_client_non_tls.containers)
def test_start_stop_container_from_cs(self):
# Leverage Magnum client to create a container on the bay we created,
# and try to start and stop it then delete it.
container = self.cs.containers.create(name="test_cs_start_stop",
image="docker.io/cirros",
bay_uuid=self.bay.uuid,
command='ping -c 1000 8.8.8.8')
self.assertIsNotNone(container)
container_uuid = container.uuid
resp = self.cs.containers.start(container_uuid)
self.assertEqual(200, resp[0].status_code)
container = self.cs.containers.get(container_uuid)
self.assertEqual('Running', container.status)
resp = self.cs.containers.stop(container_uuid)
container = self.cs.containers.get(container_uuid)
self.assertEqual('Stopped', container.status)
container = self.cs.containers.delete(container_uuid)
self.assertRaises(exceptions.NotFound,
self.cs.containers.get, container_uuid)

View File

@ -57,27 +57,11 @@ class TestRootController(api_base.FunctionalTest):
u'rel': u'self'},
{u'href': u'http://localhost/bays/',
u'rel': u'bookmark'}],
u'services': [{u'href': u'http://localhost/v1/services/',
u'rel': u'self'},
{u'href': u'http://localhost/services/',
u'rel': u'bookmark'}],
u'baymodels': [{u'href': u'http://localhost/v1/baymodels/',
u'rel': u'self'},
{u'href': u'http://localhost/baymodels/',
u'rel': u'bookmark'}],
u'pods': [{u'href': u'http://localhost/v1/pods/',
u'rel': u'self'},
{u'href': u'http://localhost/pods/',
u'rel': u'bookmark'}],
u'rcs': [{u'href': u'http://localhost/v1/rcs/',
u'rel': u'self'},
{u'href': u'http://localhost/rcs/',
u'rel': u'bookmark'}],
u'id': u'v1',
u'containers': [{u'href': u'http://localhost/v1/containers/',
u'rel': u'self'},
{u'href': u'http://localhost/containers/',
u'rel': u'bookmark'}],
u'x509keypairs': [{u'href': u'http://localhost/v1/x509keypairs/',
u'rel': u'self'},
{u'href': u'http://localhost/x509keypairs/',

View File

@ -1,713 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from mock import patch
from webtest.app import AppError
from magnum.common import utils as comm_utils
from magnum import objects
from magnum.objects import fields
from magnum.tests.unit.api import base as api_base
from magnum.tests.unit.db import utils
from magnum.tests.unit.objects import utils as obj_utils
class TestContainerController(api_base.FunctionalTest):
def setUp(self):
super(TestContainerController, self).setUp()
p = patch('magnum.objects.Bay.get_by_uuid')
self.mock_bay_get_by_uuid = p.start()
self.addCleanup(p.stop)
def fake_get_by_uuid(context, uuid):
bay_dict = utils.get_test_bay(uuid=uuid)
baymodel = obj_utils.get_test_baymodel(
context, coe='swarm', uuid=bay_dict['baymodel_id'])
bay = objects.Bay(self.context, **bay_dict)
bay.baymodel = baymodel
return bay
self.mock_bay_get_by_uuid.side_effect = fake_get_by_uuid
@patch('magnum.conductor.api.API.container_create')
def test_create_container(self, mock_container_create):
mock_container_create.side_effect = lambda x: x
params = ('{"name": "My Docker", "image": "ubuntu",'
'"command": "env", "memory": "512m",'
'"bay_uuid": "fff114da-3bfa-4a0f-a123-c0dffad9718e",'
'"environment": {"key1": "val1", "key2": "val2"}}')
response = self.app.post('/v1/containers',
params=params,
content_type='application/json')
self.assertEqual(201, response.status_int)
self.assertTrue(mock_container_create.called)
@patch('magnum.conductor.api.API.container_create')
def test_create_container_set_project_id_and_user_id(
self, mock_container_create):
def _create_side_effect(container):
self.assertEqual(self.context.project_id, container.project_id)
self.assertEqual(self.context.user_id, container.user_id)
return container
mock_container_create.side_effect = _create_side_effect
params = ('{"name": "My Docker", "image": "ubuntu",'
'"command": "env", "memory": "512m",'
'"bay_uuid": "fff114da-3bfa-4a0f-a123-c0dffad9718e",'
'"environment": {"key1": "val1", "key2": "val2"}}')
self.app.post('/v1/containers',
params=params,
content_type='application/json')
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.conductor.api.API.container_create')
@patch('magnum.conductor.api.API.container_delete')
def test_create_container_with_command(self,
mock_container_delete,
mock_container_create,
mock_container_show):
mock_container_create.side_effect = lambda x: x
bay = obj_utils.create_test_bay(self.context)
# Create a container with a command
params = ('{"name": "My Docker", "image": "ubuntu",'
'"command": "env", "memory": "512m",'
'"bay_uuid": "%s",'
'"environment": {"key1": "val1", "key2": "val2"}}' %
bay.uuid)
response = self.app.post('/v1/containers',
params=params,
content_type='application/json')
self.assertEqual(201, response.status_int)
# get all containers
container = objects.Container.list(self.context)[0]
container.status = 'Stopped'
mock_container_show.return_value = container
response = self.app.get('/v1/containers')
self.assertEqual(200, response.status_int)
self.assertEqual(1, len(response.json))
c = response.json['containers'][0]
self.assertIsNotNone(c.get('uuid'))
self.assertEqual('My Docker', c.get('name'))
self.assertEqual('env', c.get('command'))
self.assertEqual('Stopped', c.get('status'))
self.assertEqual('512m', c.get('memory'))
self.assertEqual({"key1": "val1", "key2": "val2"},
c.get('environment'))
# Delete the container we created
response = self.app.delete('/v1/containers/%s' % c.get('uuid'))
self.assertEqual(204, response.status_int)
response = self.app.get('/v1/containers')
self.assertEqual(200, response.status_int)
c = response.json['containers']
self.assertEqual(0, len(c))
self.assertTrue(mock_container_create.called)
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.conductor.api.API.container_create')
@patch('magnum.conductor.api.API.container_delete')
def test_create_container_with_bay_uuid(self,
mock_container_delete,
mock_container_create,
mock_container_show):
mock_container_create.side_effect = lambda x: x
# Create a container with a command
params = ('{"name": "My Docker", "image": "ubuntu",'
'"command": "env", "memory": "512m",'
'"bay_uuid": "fff114da-3bfa-4a0f-a123-c0dffad9718e",'
'"environment": {"key1": "val1", "key2": "val2"}}')
response = self.app.post('/v1/containers',
params=params,
content_type='application/json')
self.assertEqual(201, response.status_int)
# get all containers
container = objects.Container.list(self.context)[0]
container.status = 'Stopped'
mock_container_show.return_value = container
response = self.app.get('/v1/containers')
self.assertEqual(200, response.status_int)
self.assertEqual(1, len(response.json))
c = response.json['containers'][0]
self.assertIsNotNone(c.get('uuid'))
self.assertEqual('My Docker', c.get('name'))
self.assertEqual('env', c.get('command'))
self.assertEqual('Stopped', c.get('status'))
self.assertEqual('512m', c.get('memory'))
self.assertEqual({"key1": "val1", "key2": "val2"},
c.get('environment'))
# Delete the container we created
response = self.app.delete('/v1/containers/%s' % c.get('uuid'))
self.assertEqual(204, response.status_int)
response = self.app.get('/v1/containers')
self.assertEqual(200, response.status_int)
c = response.json['containers']
self.assertEqual(0, len(c))
self.assertTrue(mock_container_create.called)
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.conductor.api.API.container_create')
def test_create_container_without_memory(self,
mock_container_create,
mock_container_show):
mock_container_create.side_effect = lambda x: x
bay = obj_utils.create_test_bay(self.context)
# Create a container with a command
params = ('{"name": "My Docker", "image": "ubuntu",'
'"command": "env",'
'"bay_uuid": "%s",'
'"environment": {"key1": "val1", "key2": "val2"}}' %
bay.uuid)
response = self.app.post('/v1/containers',
params=params,
content_type='application/json')
self.assertEqual(201, response.status_int)
# get all containers
container = objects.Container.list(self.context)[0]
container.status = 'Stopped'
mock_container_show.return_value = container
response = self.app.get('/v1/containers')
self.assertEqual(200, response.status_int)
self.assertEqual(1, len(response.json))
c = response.json['containers'][0]
self.assertIsNotNone(c.get('uuid'))
self.assertEqual('My Docker', c.get('name'))
self.assertEqual('env', c.get('command'))
self.assertEqual('Stopped', c.get('status'))
self.assertIsNone(c.get('memory'))
self.assertEqual({"key1": "val1", "key2": "val2"},
c.get('environment'))
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.conductor.api.API.container_create')
def test_create_container_without_environment(self,
mock_container_create,
mock_container_show):
mock_container_create.side_effect = lambda x: x
# Create a container with a command
params = ('{"name": "My Docker", "image": "ubuntu",'
'"command": "env", "memory": "512m",'
'"bay_uuid": "fff114da-3bfa-4a0f-a123-c0dffad9718e"}')
response = self.app.post('/v1/containers',
params=params,
content_type='application/json')
self.assertEqual(201, response.status_int)
# get all containers
container = objects.Container.list(self.context)[0]
container.status = 'Stopped'
mock_container_show.return_value = container
response = self.app.get('/v1/containers')
self.assertEqual(200, response.status_int)
self.assertEqual(1, len(response.json))
c = response.json['containers'][0]
self.assertIsNotNone(c.get('uuid'))
self.assertEqual('My Docker', c.get('name'))
self.assertEqual('env', c.get('command'))
self.assertEqual('Stopped', c.get('status'))
self.assertEqual('512m', c.get('memory'))
self.assertEqual({}, c.get('environment'))
@patch('magnum.conductor.api.API.container_create')
def test_create_container_without_name(self, mock_container_create):
# No name param
params = ('{"image": "ubuntu", "command": "env", "memory": "512m",'
'"bay_uuid": "fff114da-3bfa-4a0f-a123-c0dffad9718e",'
'"environment": {"key1": "val1", "key2": "val2"}}')
self.assertRaises(AppError, self.app.post, '/v1/containers',
params=params, content_type='application/json')
self.assertTrue(mock_container_create.not_called)
@patch('magnum.conductor.api.API.container_create')
def _test_create_container_invalid_params(self, params,
mock_container_create):
self.assertRaises(AppError, self.app.post, '/v1/containers',
params=params, content_type='application/json')
self.assertTrue(mock_container_create.not_called)
def test_create_container_invalid_long_name(self):
# Long name
params = ('{"name": "' + 'i' * 256 + '", "image": "ubuntu",'
'"command": "env", "memory": "512m",'
'"bay_uuid": "fff114da-3bfa-4a0f-a123-c0dffad9718e"}')
self._test_create_container_invalid_params(params)
def test_create_container_no_memory_unit(self):
params = ('{"name": "ubuntu", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"bay_uuid": "fff114da-3bfa-4a0f-a123-c0dffad9718e"}')
self._test_create_container_invalid_params(params)
def test_create_container_bad_memory_unit(self):
params = ('{"name": "ubuntu", "image": "ubuntu",'
'"command": "env", "memory": "512S",'
'"bay_uuid": "fff114da-3bfa-4a0f-a123-c0dffad9718e"}')
self._test_create_container_invalid_params(params)
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.objects.Container.list')
def test_get_all_containers(self, mock_container_list,
mock_container_show):
test_container = utils.get_test_container()
containers = [objects.Container(self.context, **test_container)]
mock_container_list.return_value = containers
mock_container_show.return_value = containers[0]
response = self.app.get('/v1/containers')
mock_container_list.assert_called_once_with(mock.ANY,
1000, None, 'id', 'asc',
filters=None)
self.assertEqual(200, response.status_int)
actual_containers = response.json['containers']
self.assertEqual(1, len(actual_containers))
self.assertEqual(test_container['uuid'],
actual_containers[0].get('uuid'))
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.objects.Container.list')
def test_get_all_containers_with_pagination_marker(self,
mock_container_list,
mock_container_show):
container_list = []
for id_ in range(4):
test_container = utils.create_test_container(
id=id_, uuid=comm_utils.generate_uuid())
container_list.append(objects.Container(self.context,
**test_container))
mock_container_list.return_value = container_list[-1:]
mock_container_show.return_value = container_list[-1]
response = self.app.get('/v1/containers?limit=3&marker=%s'
% container_list[2].uuid)
self.assertEqual(200, response.status_int)
actual_containers = response.json['containers']
self.assertEqual(1, len(actual_containers))
self.assertEqual(container_list[-1].uuid,
actual_containers[0].get('uuid'))
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.objects.Container.list')
def test_detail_containers_with_pagination_marker(self,
mock_container_list,
mock_container_show):
container_list = []
for id_ in range(4):
test_container = utils.create_test_container(
id=id_, uuid=comm_utils.generate_uuid())
container_list.append(objects.Container(self.context,
**test_container))
mock_container_list.return_value = container_list[-1:]
mock_container_show.return_value = container_list[-1]
response = self.app.get('/v1/containers/detail?limit=3&marker=%s'
% container_list[2].uuid)
self.assertEqual(200, response.status_int)
actual_containers = response.json['containers']
self.assertEqual(1, len(actual_containers))
self.assertEqual(container_list[-1].uuid,
actual_containers[0].get('uuid'))
self.assertIn('name', actual_containers[0])
self.assertIn('bay_uuid', actual_containers[0])
self.assertIn('status', actual_containers[0])
self.assertIn('image', actual_containers[0])
self.assertIn('command', actual_containers[0])
self.assertIn('memory', actual_containers[0])
self.assertIn('environment', actual_containers[0])
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.objects.Container.list')
def test_get_all_containers_with_exception(self, mock_container_list,
mock_container_show):
test_container = utils.get_test_container()
containers = [objects.Container(self.context, **test_container)]
mock_container_list.return_value = containers
mock_container_show.side_effect = Exception
response = self.app.get('/v1/containers')
mock_container_list.assert_called_once_with(mock.ANY,
1000, None, 'id', 'asc',
filters=None)
self.assertEqual(200, response.status_int)
actual_containers = response.json['containers']
self.assertEqual(1, len(actual_containers))
self.assertEqual(test_container['uuid'],
actual_containers[0].get('uuid'))
self.assertEqual(fields.ContainerStatus.UNKNOWN,
actual_containers[0].get('status'))
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.api.utils.get_resource')
@patch('magnum.objects.Container.list')
def test_get_all_containers_with_bay_ident(self, mock_container_list,
mock_retrive_bay_uuid,
mock_container_show):
test_container = utils.get_test_container()
containers = [objects.Container(self.context, **test_container)]
mock_container_list.return_value = containers
mock_retrive_bay_uuid.return_value.uuid = '12'
mock_container_show.return_value = containers[0]
response = self.app.get('/v1/containers/?bay_ident=12')
mock_container_list.assert_called_once_with(mock.ANY,
1000, None, 'id', 'asc',
filters={'bay_uuid': '12'})
self.assertEqual(200, response.status_int)
actual_containers = response.json['containers']
self.assertEqual(1, len(actual_containers))
self.assertEqual(test_container['uuid'],
actual_containers[0].get('uuid'))
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.objects.Container.get_by_uuid')
def test_get_one_by_uuid(self, mock_container_get_by_uuid,
mock_container_show):
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_container_get_by_uuid.return_value = test_container_obj
mock_container_show.return_value = test_container_obj
response = self.app.get('/v1/containers/%s' % test_container['uuid'])
mock_container_get_by_uuid.assert_called_once_with(
mock.ANY,
test_container['uuid'])
self.assertEqual(200, response.status_int)
self.assertEqual(test_container['uuid'],
response.json['uuid'])
@patch('magnum.conductor.api.API.container_show')
@patch('magnum.objects.Container.get_by_name')
def test_get_one_by_name(self, mock_container_get_by_name,
mock_container_show):
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_container_get_by_name.return_value = test_container_obj
mock_container_show.return_value = test_container_obj
response = self.app.get('/v1/containers/%s' % test_container['name'])
mock_container_get_by_name.assert_called_once_with(
mock.ANY,
test_container['name'])
self.assertEqual(200, response.status_int)
self.assertEqual(test_container['uuid'],
response.json['uuid'])
def _action_test(self, container, action, ident_field):
test_container_obj = objects.Container(self.context, **container)
ident = container.get(ident_field)
get_by_ident_loc = 'magnum.objects.Container.get_by_%s' % ident_field
with patch(get_by_ident_loc) as mock_get_by_indent:
mock_get_by_indent.return_value = test_container_obj
response = self.app.put('/v1/containers/%s/%s' % (ident,
action))
self.assertEqual(200, response.status_int)
# Only PUT should work, others like GET should fail
self.assertRaises(AppError, self.app.get,
('/v1/containers/%s/%s' %
(ident, action)))
@patch('magnum.conductor.api.API.container_start')
def test_start_by_uuid(self, mock_container_start):
mock_container_start.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'start', 'uuid')
mock_container_start.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_start')
def test_start_by_name(self, mock_container_start):
mock_container_start.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'start', 'name')
mock_container_start.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_stop')
def test_stop_by_uuid(self, mock_container_stop):
mock_container_stop.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'stop', 'uuid')
mock_container_stop.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_stop')
def test_stop_by_name(self, mock_container_stop):
mock_container_stop.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'stop', 'name')
mock_container_stop.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_pause')
def test_pause_by_uuid(self, mock_container_pause):
mock_container_pause.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'pause', 'uuid')
mock_container_pause.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_pause')
def test_pause_by_name(self, mock_container_pause):
mock_container_pause.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'pause', 'name')
mock_container_pause.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_unpause')
def test_unpause_by_uuid(self, mock_container_unpause):
mock_container_unpause.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'unpause', 'uuid')
mock_container_unpause.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_unpause')
def test_unpause_by_name(self, mock_container_unpause):
mock_container_unpause.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'unpause', 'name')
mock_container_unpause.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_reboot')
def test_reboot_by_uuid(self, mock_container_reboot):
mock_container_reboot.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'reboot', 'uuid')
mock_container_reboot.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_reboot')
def test_reboot_by_name(self, mock_container_reboot):
mock_container_reboot.return_value = ""
test_container = utils.get_test_container()
self._action_test(test_container, 'reboot', 'name')
mock_container_reboot.assert_called_once_with(
test_container.get('uuid'))
@patch('magnum.conductor.api.API.container_logs')
@patch('magnum.objects.Container.get_by_uuid')
def test_get_logs_by_uuid(self, mock_get_by_uuid, mock_container_logs):
mock_container_logs.return_value = ""
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
response = self.app.get('/v1/containers/%s/logs' % container_uuid)
self.assertEqual(200, response.status_int)
mock_container_logs.assert_called_once_with(container_uuid)
@patch('magnum.conductor.api.API.container_logs')
@patch('magnum.objects.Container.get_by_name')
def test_get_logs_by_name(self, mock_get_by_name, mock_container_logs):
mock_container_logs.return_value = ""
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_get_by_name.return_value = test_container_obj
container_name = test_container.get('name')
container_uuid = test_container.get('uuid')
response = self.app.get('/v1/containers/%s/logs' % container_name)
self.assertEqual(200, response.status_int)
mock_container_logs.assert_called_once_with(container_uuid)
@patch('magnum.conductor.api.API.container_logs')
@patch('magnum.objects.Container.get_by_uuid')
def test_get_logs_put_fails(self, mock_get_by_uuid, mock_container_logs):
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
self.assertRaises(AppError, self.app.put,
'/v1/containers/%s/logs' % container_uuid)
self.assertFalse(mock_container_logs.called)
@patch('magnum.conductor.api.API.container_exec')
@patch('magnum.objects.Container.get_by_uuid')
def test_execute_command_by_uuid(self, mock_get_by_uuid,
mock_container_exec):
mock_container_exec.return_value = ""
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
url = '/v1/containers/%s/%s' % (container_uuid, 'execute')
cmd = {'command': 'ls'}
response = self.app.put(url, cmd)
self.assertEqual(200, response.status_int)
mock_container_exec.assert_called_once_with(container_uuid,
cmd['command'])
@patch('magnum.conductor.api.API.container_exec')
@patch('magnum.objects.Container.get_by_name')
def test_execute_command_by_name(self, mock_get_by_name,
mock_container_exec):
mock_container_exec.return_value = ""
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_get_by_name.return_value = test_container_obj
container_name = test_container.get('name')
container_uuid = test_container.get('uuid')
url = '/v1/containers/%s/%s' % (container_name, 'execute')
cmd = {'command': 'ls'}
response = self.app.put(url, cmd)
self.assertEqual(200, response.status_int)
mock_container_exec.assert_called_once_with(container_uuid,
cmd['command'])
@patch('magnum.conductor.api.API.container_delete')
@patch('magnum.objects.Container.get_by_uuid')
def test_delete_container_by_uuid(self, mock_get_by_uuid,
mock_container_delete):
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_get_by_uuid.return_value = test_container_obj
with patch.object(test_container_obj, 'destroy') as mock_destroy:
container_uuid = test_container.get('uuid')
response = self.app.delete('/v1/containers/%s' % container_uuid)
self.assertEqual(204, response.status_int)
mock_container_delete.assert_called_once_with(container_uuid)
mock_destroy.assert_called_once_with()
@patch('magnum.conductor.api.API.container_delete')
@patch('magnum.objects.Container.get_by_name')
def test_delete_container_by_name(self, mock_get_by_name,
mock_container_delete):
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_get_by_name.return_value = test_container_obj
with patch.object(test_container_obj, 'destroy') as mock_destroy:
container_name = test_container.get('name')
container_uuid = test_container.get('uuid')
response = self.app.delete('/v1/containers/%s' % container_name)
self.assertEqual(204, response.status_int)
mock_container_delete.assert_called_once_with(container_uuid)
mock_destroy.assert_called_once_with()
class TestContainerEnforcement(api_base.FunctionalTest):
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: 'project_id:non_fake'})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(
"Policy doesn't allow %s to be performed." % rule,
response.json['errors'][0]['detail'])
def test_policy_disallow_get_all(self):
self._common_policy_check(
'container:get_all', self.get_json, '/containers',
expect_errors=True)
def test_policy_disallow_get_one(self):
container = obj_utils.create_test_container(self.context)
self._common_policy_check(
'container:get', self.get_json,
'/containers/%s' % container.uuid,
expect_errors=True)
def test_policy_disallow_detail(self):
self._common_policy_check(
'container:detail',
self.get_json,
'/containers/%s/detail' % comm_utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_create(self):
baymodel = obj_utils.create_test_baymodel(self.context)
bay = obj_utils.create_test_bay(self.context,
baymodel_id=baymodel.uuid)
params = ('{"name": "My Docker", "image": "ubuntu",'
'"command": "env", "memory": "512m",'
'"bay_uuid": "%s"}' % bay.uuid)
self._common_policy_check(
'container:create', self.app.post, '/v1/containers', params=params,
content_type='application/json',
expect_errors=True)
def test_policy_disallow_delete(self):
bay = obj_utils.create_test_bay(self.context)
container = obj_utils.create_test_container(self.context,
bay_uuid=bay.uuid)
self._common_policy_check(
'container:delete', self.app.delete,
'/v1/containers/%s' % container.uuid,
expect_errors=True)
def _owner_check(self, rule, func, *args, **kwargs):
self.policy.set_rules({rule: "user_id:%(user_id)s"})
response = func(*args, **kwargs)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(
"Policy doesn't allow %s to be performed." % rule,
response.json['errors'][0]['detail'])
def test_policy_only_owner_get_one(self):
container = obj_utils.create_test_container(self.context,
user_id='another')
self._owner_check("container:get", self.get_json,
'/containers/%s' % container.uuid,
expect_errors=True)
def test_policy_only_owner_delete(self):
container = obj_utils.create_test_container(self.context,
user_id='another')
self._owner_check(
"container:delete", self.delete,
'/containers/%s' % container.uuid,
expect_errors=True)
def test_policy_only_owner_logs(self):
container = obj_utils.create_test_container(self.context,
user_id='another')
self._owner_check("container:logs", self.get_json,
'/containers/logs/%s' % container.uuid,
expect_errors=True)
def test_policy_only_owner_execute(self):
container = obj_utils.create_test_container(self.context,
user_id='another')
self._owner_check("container:execute", self.put_json,
'/containers/execute/%s/ls' % container.uuid,
{}, expect_errors=True)
def test_policy_only_owner_actions(self):
actions = ['start', 'stop', 'reboot', 'pause', 'unpause']
container = obj_utils.create_test_container(self.context,
user_id='another')
for action in actions:
self._owner_check('container:%s' % action, self.put_json,
'/containers/%s/%s' % (action, container.uuid),
{}, expect_errors=True)

View File

@ -1,627 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
from k8sclient.client import rest
import mock
from oslo_config import cfg
from six.moves.urllib import parse as urlparse
from wsme import types as wtypes
from magnum.api.controllers.v1 import pod as api_pod
from magnum.common import utils
from magnum.conductor import api as rpcapi
from magnum.tests import base
from magnum.tests.unit.api import base as api_base
from magnum.tests.unit.api import utils as apiutils
from magnum.tests.unit.objects import utils as obj_utils
class TestPodObject(base.TestCase):
def test_pod_init(self):
pod_dict = apiutils.pod_post_data(bay_uuid=None)
del pod_dict['desc']
pod = api_pod.Pod(**pod_dict)
self.assertEqual(wtypes.Unset, pod.desc)
class TestListPod(api_base.FunctionalTest):
def setUp(self):
super(TestListPod, self).setUp()
obj_utils.create_test_bay(self.context, coe='kubernetes')
self.pod = obj_utils.create_test_pod(self.context)
@mock.patch.object(rpcapi.API, 'pod_list')
def test_empty(self, mock_pod_list):
mock_pod_list.return_value = []
response = self.get_json('/pods?bay_ident=5d12f6fd-a196-4bf0-ae4c-'
'1f639a523a52')
self.assertEqual([], response['pods'])
def _assert_pod_fields(self, pod):
pod_fields = ['name', 'bay_uuid', 'desc', 'images', 'labels',
'status', 'host']
for field in pod_fields:
self.assertIn(field, pod)
@mock.patch.object(rpcapi.API, 'pod_show')
def test_one(self, mock_pod_show):
pod = obj_utils.create_test_pod(self.context)
mock_pod_show.return_value = pod
response = self.get_json(
'/pods/%s/%s' % (pod['uuid'], pod['bay_uuid']))
self.assertEqual(pod.uuid, response['uuid'])
self._assert_pod_fields(response)
@mock.patch.object(rpcapi.API, 'pod_show')
def test_get_one(self, mock_pod_show):
pod = obj_utils.create_test_pod(self.context)
mock_pod_show.return_value = pod
response = self.get_json(
'/pods/%s/%s' % (pod['uuid'], pod['bay_uuid']))
self.assertEqual(pod.uuid, response['uuid'])
self._assert_pod_fields(response)
@mock.patch.object(rpcapi.API, 'pod_show')
def test_get_one_by_name(self, mock_pod_show):
pod = obj_utils.create_test_pod(self.context)
mock_pod_show.return_value = pod
response = self.get_json(
'/pods/%s/%s' % (pod['name'], pod['bay_uuid']))
self.assertEqual(pod.uuid, response['uuid'])
self._assert_pod_fields(response)
@mock.patch.object(rpcapi.API, 'pod_show')
def test_get_one_by_name_not_found(self, mock_pod_show):
err = rest.ApiException(status=404)
mock_pod_show.side_effect = err
response = self.get_json(
'/pods/not_found/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'pod_show')
def test_get_one_by_name_multiple_pod(self, mock_pod_show):
obj_utils.create_test_pod(self.context, name='test_pod',
uuid=utils.generate_uuid())
obj_utils.create_test_pod(self.context, name='test_pod',
uuid=utils.generate_uuid())
err = rest.ApiException(status=500)
mock_pod_show.side_effect = err
response = self.get_json(
'/pods/test_pod/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'pod_list')
def test_get_all_with_pagination_marker(self, mock_pod_list):
pod_list = []
for id_ in range(4):
pod = obj_utils.create_test_pod(self.context, id=id_,
uuid=utils.generate_uuid())
pod_list.append(pod.uuid)
mock_pod_list.return_value = [pod]
response = self.get_json('/pods?limit=3&marker=%s&bay_ident=5d12f6fd-'
'a196-4bf0-ae4c-1f639a523a52' % pod_list[2])
self.assertEqual(1, len(response['pods']))
self.assertEqual(pod_list[-1], response['pods'][0]['uuid'])
@mock.patch.object(rpcapi.API, 'pod_list')
def test_detail(self, mock_pod_list):
pod = obj_utils.create_test_pod(self.context)
mock_pod_list.return_value = [pod]
response = self.get_json('/pods/detail?bay_ident=5d12f6fd-a196-4bf0-'
'ae4c-1f639a523a52')
self.assertEqual(pod.uuid, response['pods'][0]["uuid"])
self._assert_pod_fields(response['pods'][0])
@mock.patch.object(rpcapi.API, 'pod_list')
def test_detail_with_pagination_marker(self, mock_pod_list):
pod_list = []
for id_ in range(4):
pod = obj_utils.create_test_pod(self.context, id=id_,
uuid=utils.generate_uuid())
pod_list.append(pod.uuid)
mock_pod_list.return_value = [pod]
response = self.get_json('/pods/detail?limit=3&marker=%s&bay_ident='
'5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
% pod_list[2])
self.assertEqual(1, len(response['pods']))
self.assertEqual(pod_list[-1], response['pods'][0]['uuid'])
self._assert_pod_fields(response['pods'][0])
@mock.patch.object(rpcapi.API, 'pod_list')
def test_detail_against_single(self, mock_pod_list):
pod = obj_utils.create_test_pod(self.context)
mock_pod_list.return_value = [pod]
response = self.get_json('/pods/%s/detail' % pod['uuid'],
expect_errors=True)
self.assertEqual(404, response.status_int)
@mock.patch.object(rpcapi.API, 'pod_list')
def test_many(self, mock_pod_list):
pod_list = []
for id_ in range(1):
pod = obj_utils.create_test_pod(self.context, id=id_,
uuid=utils.generate_uuid())
pod_list.append(pod.uuid)
mock_pod_list.return_value = [pod]
response = self.get_json('/pods?bay_ident=5d12f6fd-a196-4bf0-ae4c-'
'1f639a523a52')
self.assertEqual(len(pod_list), len(response['pods']))
uuids = [p['uuid'] for p in response['pods']]
self.assertEqual(sorted(pod_list), sorted(uuids))
@mock.patch.object(rpcapi.API, 'pod_show')
def test_links(self, mock_pod_show):
uuid = utils.generate_uuid()
pod = obj_utils.create_test_pod(self.context, id=1, uuid=uuid)
mock_pod_show.return_value = pod
response = self.get_json('/pods/%s/%s' % (uuid, pod.bay_uuid))
self.assertIn('links', response.keys())
self.assertEqual(2, len(response['links']))
self.assertIn(uuid, response['links'][0]['href'])
@mock.patch.object(rpcapi.API, 'pod_list')
def test_collection_links(self, mock_pod_list):
for id_ in range(5):
pod = obj_utils.create_test_pod(self.context,
id=id_,
uuid=utils.generate_uuid())
mock_pod_list.return_value = [pod]
response = self.get_json('/pods/?limit=1&bay_ident=5d12f6fd-a196-'
'4bf0-ae4c-1f639a523a52')
self.assertEqual(1, len(response['pods']))
@mock.patch.object(rpcapi.API, 'pod_list')
def test_collection_links_default_limit(self, mock_pod_list):
cfg.CONF.set_override('max_limit', 3, 'api')
for id_ in range(5):
pod = obj_utils.create_test_pod(self.context, id=id_,
uuid=utils.generate_uuid())
mock_pod_list.return_value = [pod]
response = self.get_json('/pods?bay_ident=5d12f6fd-a196-4bf0-ae4c-'
'1f639a523a52')
self.assertEqual(1, len(response['pods']))
class TestPatch(api_base.FunctionalTest):
def setUp(self):
super(TestPatch, self).setUp()
obj_utils.create_test_bay(self.context, coe='kubernetes')
self.pod = obj_utils.create_test_pod(self.context,
desc='pod_example_A_desc',
status='Running')
def test_replace_bay_uuid(self):
self.pod.manifest = '{"key": "value"}'
another_bay = obj_utils.create_test_bay(self.context,
uuid=utils.generate_uuid())
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid),
[{'path': '/bay_uuid',
'value': another_bay.uuid,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
def test_replace_non_existent_bay_uuid(self):
self.pod.manifest = '{"key": "value"}'
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.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['errors'])
def test_replace_internal_field(self):
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid),
[{'path': '/labels', 'value': {}, 'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
self.assertTrue(response.json['errors'])
def test_replace_non_existent_pod(self):
response = self.patch_json(
'/pods/%s/%s' % (utils.generate_uuid(), self.pod.bay_uuid),
[{'path': '/desc',
'value': 'pod_example_B_desc',
'op': 'replace'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'pod_update')
@mock.patch.object(api_pod.Pod, 'parse_manifest')
def test_replace_with_manifest(self, parse_manifest, pod_update):
pod_update.return_value = self.pod
pod_update.return_value.manifest = '{"foo": "bar"}'
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.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()
self.assertTrue(pod_update.is_called)
def test_add_non_existent_property(self):
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid),
[{'path': '/foo', 'value': 'bar', 'op': 'add'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'pod_update')
@mock.patch.object(rpcapi.API, 'pod_show')
def test_remove_ok(self, mock_pod_show, mock_pod_update):
self.pod.manifest = '{"key": "value"}'
mock_pod_show.return_value = self.pod
response = self.get_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid))
self.assertIsNotNone(response['desc'])
mock_pod_update.return_value = self.pod
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid),
[{'path': '/manifest', 'op': 'remove'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
mock_pod_show.return_value = self.pod
mock_pod_show.return_value.desc = None
response = self.get_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid))
self.assertIsNone(response['desc'])
def test_remove_uuid(self):
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.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['errors'])
def test_remove_bay_uuid(self):
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid),
[{'path': '/bay_uuid', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_remove_internal_field(self):
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid),
[{'path': '/labels', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_remove_non_existent_property(self):
response = self.patch_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid),
[{'path': '/non-existent', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'pod_show')
@mock.patch.object(rpcapi.API, 'pod_update')
@mock.patch.object(api_pod.Pod, 'parse_manifest')
def test_replace_ok_by_name(self, parse_manifest,
mock_pod_update,
mock_pod_show):
self.pod.manifest = '{"foo": "bar"}'
mock_pod_update.return_value = self.pod
response = self.patch_json(
'/pods/%s/%s' % (self.pod.name, self.pod.bay_uuid),
[{'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_pod_update.is_called)
mock_pod_show.return_value = self.pod
response = self.get_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.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):
name = 'not_found'
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
response = self.patch_json(
'/pods/%s/%s' % (name, self.pod.bay_uuid),
[{'path': '/desc', 'op': 'remove'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok_by_name_multiple_pod(self, mock_utcnow):
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
obj_utils.create_test_pod(self.context, name='test_pod',
uuid=utils.generate_uuid())
obj_utils.create_test_pod(self.context, name='test_pod',
uuid=utils.generate_uuid())
response = self.patch_json(
'/pods/test_pod/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
[{'path': '/desc', 'op': 'remove'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
class TestPost(api_base.FunctionalTest):
def setUp(self):
super(TestPost, self).setUp()
self.test_bay = obj_utils.create_test_bay(self.context,
coe='kubernetes')
self.pod_obj = obj_utils.create_test_pod(self.context)
p = mock.patch.object(rpcapi.API, 'pod_create')
self.mock_pod_create = p.start()
self.mock_pod_create.return_value = self.pod_obj
self.addCleanup(p.stop)
p = mock.patch('magnum.objects.BayModel.get_by_uuid')
self.mock_baymodel_get_by_uuid = obj_utils.get_test_baymodel(
self.context,
uuid=self.test_bay.baymodel_id,
coe='kubernetes')
@mock.patch('oslo_utils.timeutils.utcnow')
@mock.patch.object(rpcapi.API, 'rc_create')
def test_create_pod(self, mock_rc_create, mock_utcnow):
pdict = apiutils.pod_post_data()
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
mock_rc_create.return_value = self.pod_obj
response = self.post_json('/pods', pdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
# Check location header
self.assertIsNotNone(response.location)
expected_location = '/v1/pods/%s' % pdict['uuid']
self.assertEqual(expected_location,
urlparse.urlparse(response.location).path)
self.assertEqual(pdict['uuid'], response.json['uuid'])
self.assertNotIn('updated_at', response.json.keys)
def test_create_pod_set_project_id_and_user_id(self):
pdict = apiutils.pod_post_data()
def _simulate_rpc_pod_create(pod):
self.assertEqual(pod.project_id, self.context.project_id)
self.assertEqual(pod.user_id, self.context.user_id)
return pod
self.mock_pod_create.side_effect = _simulate_rpc_pod_create
self.post_json('/pods', pdict)
@mock.patch.object(rpcapi.API, 'pod_create')
def test_create_pod_doesnt_contain_id(self, mock_pod_create):
pdict = apiutils.pod_post_data(desc='pod_example_A_desc')
mock_pod_create.return_value = self.pod_obj
mock_pod_create.return_value.desc = 'pod_example_A_desc'
response = self.post_json('/pods', pdict)
self.assertEqual(pdict['desc'], response.json['desc'])
@mock.patch.object(rpcapi.API, 'pod_create')
def test_create_pod_generate_uuid(self, mock_pod_create):
pdict = apiutils.pod_post_data()
del pdict['uuid']
mock_pod_create.return_value = self.pod_obj
response = self.post_json('/pods', pdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(pdict['desc'], response.json['desc'])
self.assertTrue(utils.is_uuid_like(response.json['uuid']))
def test_create_pod_no_bay_uuid(self):
pdict = apiutils.pod_post_data()
del pdict['bay_uuid']
response = self.post_json('/pods', pdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
def test_create_pod_with_non_existent_bay_uuid(self):
pdict = apiutils.pod_post_data(bay_uuid=utils.generate_uuid())
response = self.post_json('/pods', pdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_pod_with_invalid_manifest(self):
pdict = apiutils.pod_post_data()
pdict['manifest'] = 'wrong manifest'
response = self.post_json('/pods', pdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_pod_no_manifest(self):
pdict = apiutils.pod_post_data()
del pdict['manifest']
response = self.post_json('/pods', pdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_pod_no_id_in_manifest(self):
pdict = apiutils.pod_post_data()
pdict['manifest'] = {}
response = self.post_json('/pods', pdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
class TestDelete(api_base.FunctionalTest):
def setUp(self):
super(TestDelete, self).setUp()
obj_utils.create_test_bay(self.context, coe='kubernetes')
self.pod = obj_utils.create_test_pod(self.context)
@mock.patch.object(rpcapi.API, 'pod_delete')
@mock.patch.object(rpcapi.API, 'pod_show')
def test_delete_pod(self, mock_pod_show, mock_pod_delete):
self.delete('/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid))
err = rest.ApiException(status=404)
mock_pod_show.side_effect = err
response = self.get_json(
'/pods/%s/%s' % (self.pod.uuid, self.pod.bay_uuid),
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'pod_delete')
@mock.patch.object(rpcapi.API, 'pod_show')
def test_delete_pod_by_name(self,
mock_pod_show,
mock_pod_delete):
self.delete('/pods/%s/%s' % (self.pod.name, self.pod.bay_uuid),
expect_errors=True)
mock_pod_show.return_value = self.pod
response = self.get_json('/pods/%s' % self.pod.name,
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'pod_delete')
def test_delete_pod_by_name_not_found(self, mock_pod_delete):
err = rest.ApiException(status=404)
mock_pod_delete.side_effect = err
response = self.delete(
'/pods/not_found/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'pod_delete')
def test_delete_multiple_pod_by_name(self, mock_pod_delete):
obj_utils.create_test_pod(self.context, name='test_pod',
uuid=utils.generate_uuid())
obj_utils.create_test_pod(self.context, name='test_pod',
uuid=utils.generate_uuid())
err = rest.ApiException(status=400)
mock_pod_delete.side_effect = err
response = self.delete('/pods/test_pod', expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'pod_delete')
def test_delete_pod_not_found(self, mock_pod_delete):
uuid = utils.generate_uuid()
err = rest.ApiException(status=404)
mock_pod_delete.side_effect = err
response = self.delete('/pods/%s/%s' % (uuid, self.pod.bay_uuid),
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
class TestPodPolicyEnforcement(api_base.FunctionalTest):
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: 'project:non_fake'})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(
"Policy doesn't allow %s to be performed." % rule,
response.json['errors'][0]['detail'])
def test_policy_disallow_get_all(self):
self._common_policy_check(
'pod:get_all', self.get_json,
'/pods?bay_ident=%s' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_get_one(self):
self._common_policy_check(
'pod:get', self.get_json,
'/pods/?bay_ident=%s' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_detail(self):
self._common_policy_check(
'pod:detail', self.get_json,
'/pods/detail?bay_ident=%s' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_update(self):
pod = obj_utils.create_test_pod(self.context,
desc='test pod',
uuid=utils.generate_uuid())
self._common_policy_check(
'pod:update', self.patch_json,
'/pods/%s/%s' % (pod.uuid, utils.generate_uuid()),
[{'path': '/desc', 'value': 'new test pod', 'op': 'replace'}],
expect_errors=True)
def test_policy_disallow_create(self):
bay = obj_utils.create_test_bay(self.context)
pdict = apiutils.pod_post_data(bay_uuid=bay.uuid)
self._common_policy_check(
'pod:create', self.post_json, '/pods', pdict, expect_errors=True)
def test_policy_disallow_delete(self):
pod = obj_utils.create_test_pod(self.context,
name='test_pod',
uuid=utils.generate_uuid())
self._common_policy_check(
'pod:delete', self.delete,
'/pods/%s/%s' % (pod.uuid, utils.generate_uuid()),
expect_errors=True)

View File

@ -1,636 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
from k8sclient.client import rest
import mock
from oslo_config import cfg
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 import utils
from magnum.conductor import api as rpcapi
from magnum.tests import base
from magnum.tests.unit.api import base as api_base
from magnum.tests.unit.api import utils as apiutils
from magnum.tests.unit.objects import utils as obj_utils
class TestRCObject(base.TestCase):
def test_rc_init(self):
rc_dict = apiutils.rc_post_data(bay_uuid=None)
del rc_dict['images']
rc = api_rc.ReplicationController(**rc_dict)
self.assertEqual(wtypes.Unset, rc.images)
class TestListRC(api_base.FunctionalTest):
def setUp(self):
super(TestListRC, self).setUp()
obj_utils.create_test_bay(self.context, coe='kubernetes')
self.rc = obj_utils.create_test_rc(self.context)
@mock.patch.object(rpcapi.API, 'rc_list')
def test_empty(self, mock_rc_list):
mock_rc_list.return_value = []
response = self.get_json('/rcs?bay_ident=5d12f6fd-a196-4bf0-ae4c-'
'1f639a523a52')
self.assertEqual([], response['rcs'])
def _assert_rc_fields(self, rc):
rc_fields = ['name', 'bay_uuid', 'name', 'images', 'labels',
'replicas']
for field in rc_fields:
self.assertIn(field, rc)
@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)
@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)
@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(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@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(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@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&bay_ident=5d12f6fd-'
'a196-4bf0-ae4c-1f639a523a52' % rc_list[2])
self.assertEqual(1, len(response['rcs']))
self.assertEqual(rc_list[-1], response['rcs'][0]['uuid'])
@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?bay_ident=5d12f6fd-a196-4bf0-'
'ae4c-1f639a523a52')
self.assertEqual(rc.uuid, response['rcs'][0]["uuid"])
self._assert_rc_fields(response['rcs'][0])
@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&bay_ident='
'5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
% (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])
def test_detail_against_single(self):
rc = obj_utils.create_test_rc(self.context)
response = self.get_json('/rcs/%s/detail' % rc['uuid'],
expect_errors=True)
self.assertEqual(404, response.status_int)
@mock.patch.object(rpcapi.API, 'rc_list')
def test_many(self, mock_rc_list):
rc_list = []
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?bay_ident=5d12f6fd-a196-4bf0-ae4c-'
'1f639a523a52')
self.assertEqual(len(rc_list), len(response['rcs']))
uuids = [r['uuid'] for r in response['rcs']]
self.assertEqual(sorted(rc_list), sorted(uuids))
@mock.patch.object(rpcapi.API, 'rc_show')
def test_links(self, mock_rc_show):
uuid = utils.generate_uuid()
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'])
@mock.patch.object(rpcapi.API, 'rc_list')
def test_collection_links(self, mock_rc_list):
for id_ in range(5):
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&bay_ident=5d12f6fd-a196-4bf0'
'-ae4c-1f639a523a52')
self.assertEqual(1, len(response['rcs']))
@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):
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?bay_ident=5d12f6fd-a196-4bf0-ae4c-'
'1f639a523a52')
self.assertEqual(1, len(response['rcs']))
class TestPatch(api_base.FunctionalTest):
def setUp(self):
super(TestPatch, self).setUp()
obj_utils.create_test_bay(self.context, coe='kubernetes')
self.rc = obj_utils.create_test_rc(self.context,
images=['rc_example_A_image'])
self.another_bay = obj_utils.create_test_bay(
self.context,
uuid=utils.generate_uuid())
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):
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(400, response.status_code)
def test_replace_non_existent_bay_uuid(self):
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['errors'])
def test_replace_internal_field(self):
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
[{'path': '/labels', 'value': {}, 'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
self.assertTrue(response.json['errors'])
def test_replace_non_existent_rc(self):
response = self.patch_json(
'/rcs/%s/%s' % (utils.generate_uuid(),
'5d12f6fd-a196-4bf0-ae4c-1f639a523a52'),
[{'path': '/images/0',
'value': 'rc_example_B_image',
'op': 'replace'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@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):
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()
self.assertTrue(rc_update.is_called)
def test_add_non_existent_property(self):
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
[{'path': '/foo', 'value': 'bar', 'op': 'add'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
@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)
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)
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)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_remove_bay_uuid(self):
response = self.patch_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid),
[{'path': '/bay_uuid', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_remove_internal_field(self):
response = self.patch_json('/rcs/%s/%s' % (self.rc.uuid,
self.rc.bay_uuid),
[{'path': '/labels', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_remove_non_existent_property(self):
response = self.patch_json(
'/rcs/%s/%s' % (self.rc.uuid, self.rc.bay_uuid),
[{'path': '/non-existent', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@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': '/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)
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):
new_image = 'rc_example_B_image'
name = 'not_found'
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)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok_by_name_multiple_rc(self, mock_utcnow):
new_image = 'rc_example_B_image'
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
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.patch_json(
'/rcs/test_rc/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
[{'path': '/images/0',
'value': new_image,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
class TestPost(api_base.FunctionalTest):
def setUp(self):
super(TestPost, self).setUp()
self.test_bay = obj_utils.create_test_bay(self.context,
coe='kubernetes')
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.return_value = self.rc_obj
self.addCleanup(p.stop)
p = mock.patch('magnum.objects.BayModel.get_by_uuid')
self.mock_baymodel_get_by_uuid = obj_utils.get_test_baymodel(
self.context,
uuid=self.test_bay.baymodel_id,
coe='kubernetes')
@mock.patch('oslo_utils.timeutils.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)
# Check location header
self.assertIsNotNone(response.location)
expected_location = '/v1/rcs/%s' % rc_dict['uuid']
self.assertEqual(expected_location,
urlparse.urlparse(response.location).path)
self.assertEqual(rc_dict['uuid'], response.json['uuid'])
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(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)
@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']
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'])
self.assertTrue(utils.is_uuid_like(response.json['uuid']))
def test_create_rc_no_bay_uuid(self):
rc_dict = apiutils.rc_post_data()
del rc_dict['bay_uuid']
response = self.post_json('/rcs', rc_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
def test_create_rc_with_non_existent_bay_uuid(self):
rc_dict = apiutils.rc_post_data(bay_uuid=utils.generate_uuid())
response = self.post_json('/rcs', rc_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_rc_with_invalid_manifest(self):
rc_dict = apiutils.rc_post_data()
rc_dict['manifest'] = 'wrong_manifest'
response = self.post_json('/rcs', rc_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_rc_no_manifest(self):
rc_dict = apiutils.rc_post_data()
del rc_dict['manifest']
response = self.post_json('/rcs', rc_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_rc_no_id_in_manifest(self):
rc_dict = apiutils.rc_post_data()
rc_dict['manifest'] = {}
response = self.post_json('/rcs', rc_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
class TestDelete(api_base.FunctionalTest):
def setUp(self):
super(TestDelete, self).setUp()
obj_utils.create_test_bay(self.context, coe='kubernetes')
self.rc = obj_utils.create_test_rc(self.context)
@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))
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['errors'])
@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(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@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(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@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)
@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-1f639a523a52',
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
class TestRCEnforcement(api_base.FunctionalTest):
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: 'project:non_fake'})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(
"Policy doesn't allow %s to be performed." % rule,
response.json['errors'][0]['detail'])
def test_policy_disallow_get_all(self):
self._common_policy_check(
'rc:get_all', self.get_json, '/rcs', expect_errors=True)
def test_policy_disallow_get_one(self):
self._common_policy_check(
'rc:get', self.get_json,
'/rcs/?bay_ident=%s' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_detail(self):
self._common_policy_check(
'rc:detail', self.get_json,
'/rcs/detail?bay_ident=%s' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_update(self):
rc = obj_utils.create_test_rc(self.context,
desc='test rc',
uuid=utils.generate_uuid())
new_image = 'rc_example_B_image'
self._common_policy_check(
'rc:update', self.patch_json,
'/rcs/%s/%s' % (rc.uuid, utils.generate_uuid()),
[{'path': '/images/0', 'value': new_image, 'op': 'replace'}],
expect_errors=True)
def test_policy_disallow_create(self):
bay = obj_utils.create_test_bay(self.context)
pdict = apiutils.rc_post_data(bay_uuid=bay.uuid)
self._common_policy_check(
'rc:create', self.post_json, '/rcs', pdict, expect_errors=True)
def test_policy_disallow_delete(self):
rc = obj_utils.create_test_rc(self.context,
name='test_rc',
uuid=utils.generate_uuid())
self._common_policy_check(
'rc:delete', self.delete,
'/rcs/%s/%s' % (rc.uuid, utils.generate_uuid()),
expect_errors=True)

View File

@ -1,651 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
from k8sclient.client import rest
import mock
from oslo_config import cfg
from six.moves.urllib import parse as urlparse
from wsme import types as wtypes
from magnum.api.controllers.v1 import service as api_service
from magnum.common import utils
from magnum.conductor import api as rpcapi
from magnum.tests import base
from magnum.tests.unit.api import base as api_base
from magnum.tests.unit.api import utils as apiutils
from magnum.tests.unit.objects import utils as obj_utils
class TestServiceObject(base.TestCase):
def test_service_init(self):
service_dict = apiutils.service_post_data(bay_uuid=None)
del service_dict['uuid']
service = api_service.Service(**service_dict)
self.assertEqual(wtypes.Unset, service.uuid)
class TestListService(api_base.FunctionalTest):
def setUp(self):
super(TestListService, self).setUp()
obj_utils.create_test_bay(self.context, coe='kubernetes')
self.service = obj_utils.create_test_service(self.context)
@mock.patch.object(rpcapi.API, 'service_list')
def test_empty(self, mock_pod_list):
mock_pod_list.return_value = []
response = self.get_json('/services?bay_ident=5d12f6fd-a196-4bf0-ae4c-'
'1f639a523a52')
self.assertEqual([], response['services'])
def _assert_service_fields(self, service):
service_fields = ['name', 'bay_uuid', 'name', 'labels', 'selector',
'ip', 'ports']
for field in service_fields:
self.assertIn(field, service)
@mock.patch.object(rpcapi.API, 'service_show')
def test_one(self, mock_service_show):
service = obj_utils.create_test_service(self.context)
mock_service_show.return_value = service
response = self.get_json('/services/%s/%s' % (service['uuid'],
service['bay_uuid']))
self.assertEqual(service.uuid, response["uuid"])
self._assert_service_fields(response)
@mock.patch.object(rpcapi.API, 'service_show')
def test_get_one(self, mock_service_show):
service = obj_utils.create_test_service(self.context)
mock_service_show.return_value = service
response = self.get_json(
'/services/%s/%s' % (service['uuid'], service['bay_uuid']))
self.assertEqual(service.uuid, response['uuid'])
self._assert_service_fields(response)
@mock.patch.object(rpcapi.API, 'service_show')
def test_get_one_by_name(self, mock_service_show):
service = obj_utils.create_test_service(self.context)
mock_service_show.return_value = service
response = self.get_json(
'/services/%s/%s' % (service['name'], service['bay_uuid']))
self.assertEqual(service.uuid, response['uuid'])
self._assert_service_fields(response)
@mock.patch.object(rpcapi.API, 'service_show')
def test_get_one_by_name_not_found(self, mock_service_show):
err = rest.ApiException(status=404)
mock_service_show.side_effect = err
response = self.get_json(
'/services/not_found/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'service_show')
def test_get_one_by_name_multiple_service(self, mock_service_show):
obj_utils.create_test_service(
self.context, name='test_service',
uuid=utils.generate_uuid())
obj_utils.create_test_service(
self.context, name='test_service',
uuid=utils.generate_uuid())
err = rest.ApiException(status=500)
mock_service_show.side_effect = err
response = self.get_json(
'/services/test_service/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'service_list')
def test_get_all_with_pagination_marker(self, mock_service_list):
service_list = []
for id_ in range(4):
service = obj_utils.create_test_service(self.context, id=id_,
uuid=utils.generate_uuid())
service_list.append(service.uuid)
mock_service_list.return_value = [service]
response = self.get_json('/services?limit=3&marker=%s&bay_ident='
'5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
% service_list[2])
self.assertEqual(1, len(response['services']))
self.assertEqual(service_list[-1], response['services'][0]['uuid'])
@mock.patch.object(rpcapi.API, 'service_list')
def test_detail(self, mock_service_list):
service = obj_utils.create_test_service(self.context)
mock_service_list.return_value = [service]
response = self.get_json('/services/detail?bay_ident=5d12f6fd-a196-'
'4bf0-ae4c-1f639a523a52')
self.assertEqual(service.uuid, response['services'][0]["uuid"])
self._assert_service_fields(response['services'][0])
@mock.patch.object(rpcapi.API, 'service_list')
def test_detail_with_pagination_marker(self, mock_service_list):
service_list = []
for id_ in range(4):
service = obj_utils.create_test_service(self.context, id=id_,
uuid=utils.generate_uuid())
service_list.append(service.uuid)
mock_service_list.return_value = [service]
response = self.get_json('/services/detail?limit=3&marker=%s&bay_ident'
'=5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
% service_list[2])
self.assertEqual(1, len(response['services']))
self.assertEqual(service_list[-1], response['services'][0]['uuid'])
self._assert_service_fields(response['services'][0])
@mock.patch.object(rpcapi.API, 'service_list')
def test_detail_against_single(self, mock_service_list):
service = obj_utils.create_test_service(self.context)
mock_service_list.return_value = [service]
response = self.get_json('/services/%s/detail' % service['uuid'],
expect_errors=True)
self.assertEqual(404, response.status_int)
@mock.patch.object(rpcapi.API, 'service_list')
def test_many(self, mock_service_list):
service_list = []
for id_ in range(1):
service = obj_utils.create_test_service(self.context, id=id_,
uuid=utils.generate_uuid())
service_list.append(service.uuid)
mock_service_list.return_value = [service]
response = self.get_json('/services?bay_ident=5d12f6fd-a196-4bf0-ae4c-'
'1f639a523a52')
self.assertEqual(len(service_list), len(response['services']))
uuids = [s['uuid'] for s in response['services']]
self.assertEqual(sorted(service_list), sorted(uuids))
@mock.patch.object(rpcapi.API, 'service_show')
def test_links(self, mock_service_show):
uuid = utils.generate_uuid()
service = obj_utils.create_test_service(self.context, id=1, uuid=uuid)
mock_service_show.return_value = service
response = self.get_json(
'/services/%s/%s' % (uuid, '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'))
self.assertIn('links', response.keys())
self.assertEqual(2, len(response['links']))
self.assertIn(uuid, response['links'][0]['href'])
@mock.patch.object(rpcapi.API, 'service_list')
def test_collection_links(self, mock_service_list):
for id_ in range(5):
service = obj_utils.create_test_service(
self.context, id=id_,
uuid=utils.generate_uuid())
mock_service_list.return_value = [service]
response = self.get_json('/services/?limit=1&bay_ident=5d12f6fd-a196-'
'4bf0-ae4c-1f639a523a52')
self.assertEqual(1, len(response['services']))
next_marker = response['services'][-1]['uuid']
self.assertIn(next_marker, response['next'])
@mock.patch.object(rpcapi.API, 'service_list')
def test_collection_links_default_limit(self, mock_service_list):
cfg.CONF.set_override('max_limit', 3, 'api')
for id_ in range(5):
service = obj_utils.create_test_service(
self.context, id=id_,
uuid=utils.generate_uuid())
mock_service_list.return_value = [service]
response = self.get_json('/services?bay_ident=5d12f6fd-a196-4bf0-ae4c-'
'1f639a523a52')
self.assertEqual(1, len(response['services']))
class TestPatch(api_base.FunctionalTest):
def setUp(self):
super(TestPatch, self).setUp()
self.bay = obj_utils.create_test_bay(self.context,
uuid=utils.generate_uuid(),
coe='kubernetes')
self.bay2 = obj_utils.create_test_bay(self.context,
uuid=utils.generate_uuid(),
coe='kubernetes')
self.service = obj_utils.create_test_service(self.context,
bay_uuid=self.bay.uuid)
@mock.patch.object(rpcapi.API, 'service_update')
def test_replace_bay_uuid(self, mock_service_update):
self.service.manifest = '{"foo": "bar"}'
mock_service_update.return_value = self.service
mock_service_update.return_value.bay_uuid = self.bay2.uuid
response = self.patch_json(
'/services/%s/%s' % (self.service.uuid, self.service.bay_uuid),
[{'path': '/bay_uuid',
'value': self.bay2.uuid,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
@mock.patch.object(rpcapi.API, 'service_update')
def test_replace_non_existent_bay_uuid(self, mock_service_update):
err = rest.ApiException(status=400)
mock_service_update.side_effect = err
response = self.patch_json(
'/services/%s/%s' % (self.service.uuid, self.service.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['errors'])
@mock.patch.object(rpcapi.API, 'service_update')
def test_replace_internal_field(self, mock_service_update):
err = rest.ApiException(status=400)
mock_service_update.side_effect = err
response = self.patch_json(
'/services/%s/%s' % (self.service.uuid, self.service.bay_uuid),
[{'path': '/labels', 'value': {}, 'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'service_update')
def test_replace_non_existent_service(self, mock_service_update):
err = rest.ApiException(status=404)
mock_service_update.side_effect = err
response = self.patch_json(
'/services/%s?bay_ident=%s' %
(utils.generate_uuid(), '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'),
[{'path': '/bay_uuid',
'value': self.bay2.uuid,
'op': 'replace'}],
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'service_update')
@mock.patch.object(api_service.Service, 'parse_manifest')
def test_replace_with_manifest(self, parse_manifest, service_update):
service_update.return_value = self.service
service_update.return_value.manifest = '{"foo": "bar"}'
response = self.patch_json(
'/services/%s/%s' % (self.service.uuid, self.service.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()
self.assertTrue(service_update.is_called)
@mock.patch.object(rpcapi.API, 'service_update')
def test_add_non_existent_property(self, mock_service_update):
response = self.patch_json(
'/services/%s/%s' % (self.service.uuid, self.service.bay_uuid),
[{'path': '/foo', 'value': 'bar', 'op': 'add'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_remove_uuid(self):
response = self.patch_json(
'/services/%s/%s' % (self.service.uuid, self.service.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['errors'])
def test_remove_bay_uuid(self):
response = self.patch_json(
'/services/%s/%s' % (self.service.uuid, self.service.bay_uuid),
[{'path': '/bay_uuid', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_remove_internal_field(self):
response = self.patch_json(
'/services/%s/%s' % (self.service.uuid, self.service.bay_uuid),
[{'path': '/labels', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_remove_non_existent_property(self):
response = self.patch_json(
'/services/%s/%s' % (self.service.uuid, self.service.bay_uuid),
[{'path': '/non-existent', 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'service_show')
@mock.patch.object(rpcapi.API, 'service_update')
@mock.patch.object(api_service.Service, 'parse_manifest')
def test_replace_ok_by_name(self, parse_manifest,
mock_service_update,
mock_service_show):
self.service.manifest = '{"foo": "bar"}'
mock_service_update.return_value = self.service
response = self.patch_json(
'/services/%s/%s' % (self.service.name, self.service.bay_uuid),
[{'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_service_update.is_called)
mock_service_show.return_value = self.service
response = self.get_json(
'/services/%s/%s' % (self.service.uuid,
self.service.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):
name = 'not_found'
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
response = self.patch_json(
'/services/%s/%s' % (name, self.service.bay_uuid),
[{'path': '/bay_uuid',
'value': self.bay2.uuid,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok_by_name_multiple_service(self, mock_utcnow):
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
obj_utils.create_test_service(self.context, name='test_service',
uuid=utils.generate_uuid())
obj_utils.create_test_service(self.context, name='test_service',
uuid=utils.generate_uuid())
response = self.patch_json(
'/services/test_service?bay_ident=%s' % self.bay.uuid,
[{'path': '/bay_uuid',
'value': self.bay2.uuid,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
class TestPost(api_base.FunctionalTest):
def setUp(self):
super(TestPost, self).setUp()
self.test_bay = obj_utils.create_test_bay(self.context,
coe='kubernetes')
self.service_obj = obj_utils.create_test_service(self.context)
p = mock.patch.object(rpcapi.API, 'service_create')
self.mock_service_create = p.start()
self.mock_service_create.return_value = self.service_obj
self.mock_service_create.side_effect = (
self._simulate_rpc_service_create)
self.addCleanup(p.stop)
self.mock_baymodel_get_by_uuid = obj_utils.get_test_baymodel(
self.context,
uuid=self.test_bay.baymodel_id,
coe='kubernetes')
def _simulate_rpc_service_create(self, service):
service.create()
return service
@mock.patch('oslo_utils.timeutils.utcnow')
@mock.patch.object(rpcapi.API, 'service_create')
def test_create_service(self, mock_service_create,
mock_utcnow):
sdict = apiutils.service_post_data()
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
mock_service_create.return_value = self.service_obj
response = self.post_json('/services', sdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
# Check location header
self.assertIsNotNone(response.location)
expected_location = '/v1/services/%s' % sdict['uuid']
self.assertEqual(expected_location,
urlparse.urlparse(response.location).path)
self.assertEqual(sdict['uuid'], response.json['uuid'])
self.assertNotIn('updated_at', response.json.keys)
def test_create_service_set_project_id_and_user_id(self):
sdict = apiutils.service_post_data()
def _simulate_rpc_service_create(service):
self.assertEqual(service.project_id, self.context.project_id)
self.assertEqual(service.user_id, self.context.user_id)
return service
self.mock_service_create.side_effect = _simulate_rpc_service_create
self.post_json('/services', sdict)
@mock.patch.object(rpcapi.API, 'service_create')
def test_create_service_doesnt_contain_id(self, mock_service_create):
sdict = apiutils.service_post_data()
mock_service_create.return_value = self.service_obj
response = self.post_json('/services', sdict)
self.assertEqual('application/json', response.content_type)
@mock.patch.object(rpcapi.API, 'service_create')
def test_create_service_generate_uuid(self,
mock_service_create):
sdict = apiutils.service_post_data()
del sdict['uuid']
mock_service_create.return_value = self.service_obj
response = self.post_json('/services', sdict,
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertTrue(utils.is_uuid_like(response.json['uuid']))
def test_create_service_no_bay_uuid(self):
sdict = apiutils.service_post_data()
del sdict['bay_uuid']
response = self.post_json('/services', sdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
def test_create_service_with_non_existent_bay_uuid(self):
sdict = apiutils.service_post_data(bay_uuid=utils.generate_uuid())
response = self.post_json('/services', sdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_service_no_manifest(self):
sdict = apiutils.service_post_data()
del sdict['manifest']
response = self.post_json('/services', sdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_service_invalid_manifest(self):
sdict = apiutils.service_post_data()
sdict['manifest'] = 'wrong_manifest'
response = self.post_json('/services', sdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_service_no_id_in_manifest(self):
sdict = apiutils.service_post_data()
sdict['manifest'] = {}
response = self.post_json('/services', sdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
class TestDelete(api_base.FunctionalTest):
def setUp(self):
super(TestDelete, self).setUp()
obj_utils.create_test_bay(self.context, coe='kubernetes')
self.service = obj_utils.create_test_service(self.context)
@mock.patch.object(rpcapi.API, 'service_delete')
@mock.patch.object(rpcapi.API, 'service_show')
def test_delete_service(self, mock_service_show,
mock_service_delete):
mock_service_delete.return_value = {}
self.delete(
'/services/%s/%s' % (self.service.uuid, self.service.bay_uuid))
err = rest.ApiException(status=404)
mock_service_show.side_effect = err
response = self.get_json(
'/services/%s/%s' % (self.service.uuid, self.service.bay_uuid),
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'service_delete')
@mock.patch.object(rpcapi.API, 'service_show')
def test_delete_service_not_found(self, mock_service_show,
mock_service_delete):
uuid = utils.generate_uuid()
err = rest.ApiException(status=404)
mock_service_delete.side_effect = err
response = self.delete(
'/services/%s/%s' % (uuid, self.service.bay_uuid),
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'service_delete')
@mock.patch.object(rpcapi.API, 'service_show')
def test_delete_service_with_name(self, mock_service_show,
mock_service_delete):
mock_service_delete.return_value = {}
response = self.delete(
'/services/%s/%s' % (self.service.name, self.service.bay_uuid),
expect_errors=True)
err = rest.ApiException(status=204)
mock_service_show.side_effect = err
response = self.get_json(
'/services/%s/%s' % (self.service.uuid, self.service.bay_uuid),
expect_errors=True)
self.assertEqual(500, response.status_int)
@mock.patch.object(rpcapi.API, 'service_delete')
def test_delete_service_with_name_not_found(self,
mock_service_delete):
err = rest.ApiException(status=404)
mock_service_delete.side_effect = err
response = self.delete(
'/services/not_found/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'service_delete')
def test_delete_multiple_service_by_name(self, mock_service_delete):
obj_utils.create_test_service(self.context, name='test_service',
uuid=utils.generate_uuid())
obj_utils.create_test_service(self.context, name='test_service',
uuid=utils.generate_uuid())
err = rest.ApiException(status=409)
mock_service_delete.side_effect = err
response = self.delete(
'/services/test_service/5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
expect_errors=True)
self.assertEqual(500, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
class TestServiceEnforcement(api_base.FunctionalTest):
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: 'project:non_fake'})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(
"Policy doesn't allow %s to be performed." % rule,
response.json['errors'][0]['detail'])
def test_policy_disallow_get_all(self):
self._common_policy_check(
'service:get_all', self.get_json,
'/services?bay_ident=%s' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_get_one(self):
self._common_policy_check(
'service:get', self.get_json,
'/services/?bay_ident=%s' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_detail(self):
self._common_policy_check(
'service:detail', self.get_json,
'/services/detail?bay_ident=%s' % utils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_update(self):
service = obj_utils.create_test_service(self.context,
desc='test service',
uuid=utils.generate_uuid())
self._common_policy_check(
'service:update', self.patch_json,
'/services/%s/%s' % (service.uuid, utils.generate_uuid()),
[{'path': '/bay_uuid',
'value': utils.generate_uuid(),
'op': 'replace'}], expect_errors=True)
def test_policy_disallow_create(self):
bay = obj_utils.create_test_bay(self.context)
pdict = apiutils.service_post_data(bay_uuid=bay.uuid)
self._common_policy_check(
'service:create', self.post_json, '/services', pdict,
expect_errors=True)
def test_policy_disallow_delete(self):
service = obj_utils.create_test_service(self.context,
desc='test_service',
uuid=utils.generate_uuid())
self._common_policy_check(
'service:delete', self.delete,
'/services/%s/%s' % (service.uuid, utils.generate_uuid()),
expect_errors=True)

View File

@ -1,144 +0,0 @@
# Copyright 2014
# The Cloudscaling Group, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import six
import mock
from oslo_config import cfg
import oslo_messaging as messaging
from magnum.api.controllers import root
from magnum.api import hooks
from magnum.common import context as magnum_context
from magnum.tests import base
from magnum.tests import fakes
from magnum.tests.unit.api import base as api_base
class TestContextHook(base.BaseTestCase):
def setUp(self):
super(TestContextHook, self).setUp()
self.app = fakes.FakeApp()
def test_context_hook_before_method(self):
state = mock.Mock(request=fakes.FakePecanRequest())
hook = hooks.ContextHook()
hook.before(state)
ctx = state.request.context
self.assertIsInstance(ctx, magnum_context.RequestContext)
self.assertEqual(fakes.fakeAuthTokenHeaders['X-Auth-Token'],
ctx.auth_token)
self.assertEqual(fakes.fakeAuthTokenHeaders['X-Project-Id'],
ctx.project_id)
self.assertEqual(fakes.fakeAuthTokenHeaders['X-User-Name'],
ctx.user_name)
self.assertEqual(fakes.fakeAuthTokenHeaders['X-User-Id'],
ctx.user_id)
self.assertEqual(fakes.fakeAuthTokenHeaders['X-Roles'],
','.join(ctx.roles))
self.assertEqual(fakes.fakeAuthTokenHeaders['X-User-Domain-Name'],
ctx.domain_name)
self.assertEqual(fakes.fakeAuthTokenHeaders['X-User-Domain-Id'],
ctx.domain_id)
self.assertIsNone(ctx.auth_token_info)
def test_context_hook_before_method_auth_info(self):
state = mock.Mock(request=fakes.FakePecanRequest())
state.request.environ['keystone.token_info'] = 'assert_this'
hook = hooks.ContextHook()
hook.before(state)
ctx = state.request.context
self.assertIsInstance(ctx, magnum_context.RequestContext)
self.assertEqual(fakes.fakeAuthTokenHeaders['X-Auth-Token'],
ctx.auth_token)
self.assertEqual('assert_this', ctx.auth_token_info)
class TestNoExceptionTracebackHook(api_base.FunctionalTest):
TRACE = [u'Traceback (most recent call last):',
u' File "/opt/stack/magnum/magnum/openstack/common/rpc/amqp.py",'
' line 434, in _process_data\\n **args)',
u' File "/opt/stack/magnum/magnum/openstack/common/rpc/'
'dispatcher.py", line 172, in dispatch\\n result ='
' getattr(proxyobj, method)(context, **kwargs)']
MSG_WITHOUT_TRACE = "Test exception message."
MSG_WITH_TRACE = MSG_WITHOUT_TRACE + "\n" + "\n".join(TRACE)
def setUp(self):
super(TestNoExceptionTracebackHook, self).setUp()
p = mock.patch.object(root.Root, 'convert')
self.root_convert_mock = p.start()
self.addCleanup(p.stop)
def test_hook_exception_success(self):
self.root_convert_mock.side_effect = Exception(self.MSG_WITH_TRACE)
response = self.get_json('/', path_prefix='', expect_errors=True)
actual_msg = response.json['errors'][0]['detail']
self.assertEqual(self.MSG_WITHOUT_TRACE, actual_msg)
def test_hook_remote_error_success(self):
test_exc_type = 'TestException'
self.root_convert_mock.side_effect = messaging.rpc.RemoteError(
test_exc_type, self.MSG_WITHOUT_TRACE, self.TRACE)
response = self.get_json('/', path_prefix='', expect_errors=True)
# NOTE(max_lobur): For RemoteError the client message will still have
# some garbage because in RemoteError traceback is serialized as a list
# instead of'\n'.join(trace). But since RemoteError is kind of very
# rare thing (happens due to wrong deserialization settings etc.)
# we don't care about this garbage.
if six.PY2:
expected_msg = ("Remote error: %s %s"
% (test_exc_type, self.MSG_WITHOUT_TRACE)
+ "\n[u'")
else:
expected_msg = ("Remote error: %s %s"
% (test_exc_type, self.MSG_WITHOUT_TRACE) + "\n['")
actual_msg = response.json['errors'][0]['detail']
self.assertEqual(expected_msg, actual_msg)
def test_hook_without_traceback(self):
msg = "Error message without traceback \n but \n multiline"
self.root_convert_mock.side_effect = Exception(msg)
response = self.get_json('/', path_prefix='', expect_errors=True)
actual_msg = response.json['errors'][0]['detail']
self.assertEqual(msg, actual_msg)
def test_hook_server_debug_on_serverfault(self):
cfg.CONF.set_override('debug', True)
self.root_convert_mock.side_effect = Exception(self.MSG_WITH_TRACE)
response = self.get_json('/', path_prefix='', expect_errors=True)
actual_msg = response.json['errors'][0]['detail']
self.assertEqual(self.MSG_WITHOUT_TRACE, actual_msg)
def test_hook_server_debug_on_clientfault(self):
cfg.CONF.set_override('debug', True)
client_error = Exception(self.MSG_WITH_TRACE)
client_error.code = 400
self.root_convert_mock.side_effect = client_error
response = self.get_json('/', path_prefix='', expect_errors=True)
actual_msg = response.json['errors'][0]['detail']
self.assertEqual(self.MSG_WITH_TRACE, actual_msg)

View File

@ -18,9 +18,6 @@ import pytz
from magnum.api.controllers.v1 import bay as bay_controller
from magnum.api.controllers.v1 import baymodel as baymodel_controller
from magnum.api.controllers.v1 import pod as pod_controller
from magnum.api.controllers.v1 import replicationcontroller as rc_controller
from magnum.api.controllers.v1 import service as service_controller
from magnum.api.controllers.v1 import x509keypair as x509keypair_controller
from magnum.tests.unit.db import utils
@ -52,92 +49,6 @@ def cert_post_data(**kw):
}
def pod_post_data(**kw):
pod = utils.get_test_pod(**kw)
if 'manifest' not in pod:
pod['manifest'] = '''{
"metadata": {
"name": "name_of_pod"
},
"spec": {
"containers": [
{
"name": "test",
"image": "test"
}
]
}
}'''
internal = pod_controller.PodPatchType.internal_attrs()
return remove_internal(pod, internal)
def service_post_data(**kw):
service = utils.get_test_service(**kw)
if 'manifest' not in service:
service['manifest'] = '''{
"metadata": {
"name": "test",
"labels": {
"key": "value"
}
},
"spec": {
"ports": [
{
"port": 88,
"targetPort": 6379,
"protocol": "TCP"
}
],
"selector": {
"bar": "foo"
}
}
}'''
internal = service_controller.ServicePatchType.internal_attrs()
return remove_internal(service, internal)
def rc_post_data(**kw):
rc = utils.get_test_rc(**kw)
if 'manifest' not in rc:
rc['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"
}
]
}
]
}
}
}
}'''
internal = rc_controller.ReplicationControllerPatchType.internal_attrs()
return remove_internal(rc, internal)
def x509keypair_post_data(**kw):
x509keypair = utils.get_test_x509keypair(**kw)
internal = x509keypair_controller.X509KeyPairPatchType.internal_attrs()