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:
parent
45e394131e
commit
82f477bb90
|
@ -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()
|
||||
|
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/',
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue