Support multiple compute hosts
This patch is for supporting deploying multiple instances of zun-compute to multiple hosts. In particular, if a container is created, it will be scheduled to a host picked by a scheduler. The host was recorded at the container object. Later, container life-cycle operations will call/cast to the host, to which the container was scheduled. The list of changes of this commit is as following: * Add a basic scheduler framework. The default scheduler is a basic scheduler that randomly choose a host. * In RPC, add support for sending message to specified host. * In compute, add APIs to schedule a container. * In context, add a method to elevate to admin privilege * In Nova driver, force the nova instance to be created in the scheduled host. This requires to elevate context before calling Nova APIs. * In objects and dbapi, add a method to list Zun services with specified binary. * In setup.cfg, add a scheduler entry point. * In cmd, use hostname as the rpc server ID (instead of a generated short ID). * In conf, use hostname as default value of CONF.host. Implements: blueprint support-multiple-hosts Implements: blueprint basic-container-scheduler Change-Id: I6955881e3087c488eb9cd857cbbd19f49f6318fc
This commit is contained in:
parent
d907557d0f
commit
ddc2a81532
@ -60,5 +60,9 @@ oslo.config.opts.defaults =
|
|||||||
zun.database.migration_backend =
|
zun.database.migration_backend =
|
||||||
sqlalchemy = zun.db.sqlalchemy.migration
|
sqlalchemy = zun.db.sqlalchemy.migration
|
||||||
|
|
||||||
|
zun.scheduler.driver =
|
||||||
|
chance_scheduler = zun.scheduler.chance_scheduler:ChanceScheduler
|
||||||
|
fake_scheduler = zun.tests.unit.scheduler.fake_scheduler:FakeScheduler
|
||||||
|
|
||||||
tempest.test_plugins =
|
tempest.test_plugins =
|
||||||
zun_tests = zun.tests.tempest.plugin:ZunTempestPlugin
|
zun_tests = zun.tests.tempest.plugin:ZunTempestPlugin
|
||||||
|
@ -78,7 +78,7 @@ class Container(base.APIBase):
|
|||||||
'labels',
|
'labels',
|
||||||
'addresses',
|
'addresses',
|
||||||
'image_pull_policy',
|
'image_pull_policy',
|
||||||
'host'
|
'host',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@ -196,6 +196,7 @@ class ContainersController(rest.RestController):
|
|||||||
|
|
||||||
def _get_containers_collection(self, **kwargs):
|
def _get_containers_collection(self, **kwargs):
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
|
compute_api = pecan.request.compute_api
|
||||||
limit = api_utils.validate_limit(kwargs.get('limit'))
|
limit = api_utils.validate_limit(kwargs.get('limit'))
|
||||||
sort_dir = api_utils.validate_sort_dir(kwargs.get('sort_dir', 'asc'))
|
sort_dir = api_utils.validate_sort_dir(kwargs.get('sort_dir', 'asc'))
|
||||||
sort_key = kwargs.get('sort_key', 'id')
|
sort_key = kwargs.get('sort_key', 'id')
|
||||||
@ -217,7 +218,7 @@ class ContainersController(rest.RestController):
|
|||||||
|
|
||||||
for i, c in enumerate(containers):
|
for i, c in enumerate(containers):
|
||||||
try:
|
try:
|
||||||
containers[i] = pecan.request.rpcapi.container_show(context, c)
|
containers[i] = compute_api.container_show(context, c)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(_LE("Error while list container %(uuid)s: "
|
LOG.exception(_LE("Error while list container %(uuid)s: "
|
||||||
"%(e)s."),
|
"%(e)s."),
|
||||||
@ -240,7 +241,8 @@ class ContainersController(rest.RestController):
|
|||||||
container = _get_container(container_id)
|
container = _get_container(container_id)
|
||||||
check_policy_on_container(container.as_dict(), "container:get")
|
check_policy_on_container(container.as_dict(), "container:get")
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
container = pecan.request.rpcapi.container_show(context, container)
|
compute_api = pecan.request.compute_api
|
||||||
|
container = compute_api.container_show(context, container)
|
||||||
return Container.convert_with_links(container.as_dict())
|
return Container.convert_with_links(container.as_dict())
|
||||||
|
|
||||||
def _generate_name_for_container(self):
|
def _generate_name_for_container(self):
|
||||||
@ -254,43 +256,43 @@ class ContainersController(rest.RestController):
|
|||||||
@exception.wrap_pecan_controller_exception
|
@exception.wrap_pecan_controller_exception
|
||||||
@validation.validated(schema.container_create)
|
@validation.validated(schema.container_create)
|
||||||
def post(self, run=False, **container_dict):
|
def post(self, run=False, **container_dict):
|
||||||
"""Create a new container.
|
"""Create a new container.
|
||||||
|
|
||||||
:param run: if true, starts the container
|
:param run: if true, starts the container
|
||||||
:param container: a container within the request body.
|
:param container: a container within the request body.
|
||||||
"""
|
"""
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
policy.enforce(context, "container:create",
|
compute_api = pecan.request.compute_api
|
||||||
action="container:create")
|
policy.enforce(context, "container:create",
|
||||||
# NOTE(mkrai): Intent here is to check the existence of image
|
action="container:create")
|
||||||
# before proceeding to create container. If image is not found,
|
# NOTE(mkrai): Intent here is to check the existence of image
|
||||||
# container create will fail with 400 status.
|
# before proceeding to create container. If image is not found,
|
||||||
images = pecan.request.rpcapi.image_search(context,
|
# container create will fail with 400 status.
|
||||||
container_dict['image'],
|
images = compute_api.image_search(context, container_dict['image'],
|
||||||
exact_match=True)
|
True)
|
||||||
if not images:
|
if not images:
|
||||||
raise exception.ImageNotFound(container_dict['image'])
|
raise exception.ImageNotFound(container_dict['image'])
|
||||||
container_dict['project_id'] = context.project_id
|
container_dict['project_id'] = context.project_id
|
||||||
container_dict['user_id'] = context.user_id
|
container_dict['user_id'] = context.user_id
|
||||||
name = container_dict.get('name') or \
|
name = container_dict.get('name') or \
|
||||||
self._generate_name_for_container()
|
self._generate_name_for_container()
|
||||||
container_dict['name'] = name
|
container_dict['name'] = name
|
||||||
if container_dict.get('memory'):
|
if container_dict.get('memory'):
|
||||||
container_dict['memory'] = \
|
container_dict['memory'] = \
|
||||||
str(container_dict['memory']) + 'M'
|
str(container_dict['memory']) + 'M'
|
||||||
container_dict['status'] = fields.ContainerStatus.CREATING
|
container_dict['status'] = fields.ContainerStatus.CREATING
|
||||||
new_container = objects.Container(context, **container_dict)
|
new_container = objects.Container(context, **container_dict)
|
||||||
new_container.create(context)
|
new_container.create(context)
|
||||||
|
|
||||||
if run:
|
if run:
|
||||||
pecan.request.rpcapi.container_run(context, new_container)
|
compute_api.container_run(context, new_container)
|
||||||
else:
|
else:
|
||||||
pecan.request.rpcapi.container_create(context, new_container)
|
compute_api.container_create(context, new_container)
|
||||||
# Set the HTTP Location Header
|
# Set the HTTP Location Header
|
||||||
pecan.response.location = link.build_url('containers',
|
pecan.response.location = link.build_url('containers',
|
||||||
new_container.uuid)
|
new_container.uuid)
|
||||||
pecan.response.status = 202
|
pecan.response.status = 202
|
||||||
return Container.convert_with_links(new_container.as_dict())
|
return Container.convert_with_links(new_container.as_dict())
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
@exception.wrap_pecan_controller_exception
|
@exception.wrap_pecan_controller_exception
|
||||||
@ -336,7 +338,8 @@ class ContainersController(rest.RestController):
|
|||||||
if not force:
|
if not force:
|
||||||
utils.validate_container_state(container, 'delete')
|
utils.validate_container_state(container, 'delete')
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
pecan.request.rpcapi.container_delete(context, container, force)
|
compute_api = pecan.request.compute_api
|
||||||
|
compute_api.container_delete(context, container, force)
|
||||||
container.destroy(context)
|
container.destroy(context)
|
||||||
pecan.response.status = 204
|
pecan.response.status = 204
|
||||||
|
|
||||||
@ -349,7 +352,8 @@ class ContainersController(rest.RestController):
|
|||||||
LOG.debug('Calling compute.container_start with %s',
|
LOG.debug('Calling compute.container_start with %s',
|
||||||
container.uuid)
|
container.uuid)
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
pecan.request.rpcapi.container_start(context, container)
|
compute_api = pecan.request.compute_api
|
||||||
|
compute_api.container_start(context, container)
|
||||||
pecan.response.status = 202
|
pecan.response.status = 202
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
@ -361,7 +365,8 @@ class ContainersController(rest.RestController):
|
|||||||
LOG.debug('Calling compute.container_stop with %s' %
|
LOG.debug('Calling compute.container_stop with %s' %
|
||||||
container.uuid)
|
container.uuid)
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
pecan.request.rpcapi.container_stop(context, container, timeout)
|
compute_api = pecan.request.compute_api
|
||||||
|
compute_api.container_stop(context, container, timeout)
|
||||||
pecan.response.status = 202
|
pecan.response.status = 202
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
@ -373,7 +378,8 @@ class ContainersController(rest.RestController):
|
|||||||
LOG.debug('Calling compute.container_reboot with %s' %
|
LOG.debug('Calling compute.container_reboot with %s' %
|
||||||
container.uuid)
|
container.uuid)
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
pecan.request.rpcapi.container_reboot(context, container, timeout)
|
compute_api = pecan.request.compute_api
|
||||||
|
compute_api.container_reboot(context, container, timeout)
|
||||||
pecan.response.status = 202
|
pecan.response.status = 202
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
@ -385,7 +391,8 @@ class ContainersController(rest.RestController):
|
|||||||
LOG.debug('Calling compute.container_pause with %s' %
|
LOG.debug('Calling compute.container_pause with %s' %
|
||||||
container.uuid)
|
container.uuid)
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
pecan.request.rpcapi.container_pause(context, container)
|
compute_api = pecan.request.compute_api
|
||||||
|
compute_api.container_pause(context, container)
|
||||||
pecan.response.status = 202
|
pecan.response.status = 202
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
@ -397,7 +404,8 @@ class ContainersController(rest.RestController):
|
|||||||
LOG.debug('Calling compute.container_unpause with %s' %
|
LOG.debug('Calling compute.container_unpause with %s' %
|
||||||
container.uuid)
|
container.uuid)
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
pecan.request.rpcapi.container_unpause(context, container)
|
compute_api = pecan.request.compute_api
|
||||||
|
compute_api.container_unpause(context, container)
|
||||||
pecan.response.status = 202
|
pecan.response.status = 202
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
@ -408,7 +416,8 @@ class ContainersController(rest.RestController):
|
|||||||
LOG.debug('Calling compute.container_logs with %s' %
|
LOG.debug('Calling compute.container_logs with %s' %
|
||||||
container.uuid)
|
container.uuid)
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
return pecan.request.rpcapi.container_logs(context, container)
|
compute_api = pecan.request.compute_api
|
||||||
|
return compute_api.container_logs(context, container)
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
@exception.wrap_pecan_controller_exception
|
@exception.wrap_pecan_controller_exception
|
||||||
@ -419,8 +428,8 @@ class ContainersController(rest.RestController):
|
|||||||
LOG.debug('Calling compute.container_exec with %s command %s'
|
LOG.debug('Calling compute.container_exec with %s command %s'
|
||||||
% (container.uuid, kw['command']))
|
% (container.uuid, kw['command']))
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
return pecan.request.rpcapi.container_exec(context, container,
|
compute_api = pecan.request.compute_api
|
||||||
kw['command'])
|
return compute_api.container_exec(context, container, kw['command'])
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
@exception.wrap_pecan_controller_exception
|
@exception.wrap_pecan_controller_exception
|
||||||
@ -431,6 +440,6 @@ class ContainersController(rest.RestController):
|
|||||||
LOG.debug('Calling compute.container_kill with %s signal %s'
|
LOG.debug('Calling compute.container_kill with %s signal %s'
|
||||||
% (container.uuid, kw.get('signal', kw.get('signal'))))
|
% (container.uuid, kw.get('signal', kw.get('signal'))))
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
pecan.request.rpcapi.container_kill(context, container,
|
compute_api = pecan.request.compute_api
|
||||||
kw.get('signal', None))
|
compute_api.container_kill(context, container, kw.get('signal'))
|
||||||
pecan.response.status = 202
|
pecan.response.status = 202
|
||||||
|
@ -171,7 +171,7 @@ class ImagesController(rest.RestController):
|
|||||||
filters=filters)
|
filters=filters)
|
||||||
for i, c in enumerate(images):
|
for i, c in enumerate(images):
|
||||||
try:
|
try:
|
||||||
images[i] = pecan.request.rpcapi.image_show(context, c)
|
images[i] = pecan.request.compute_api.image_show(context, c)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.exception(_LE("Error while list image %(uuid)s: "
|
LOG.exception(_LE("Error while list image %(uuid)s: "
|
||||||
"%(e)s."), {'uuid': c.uuid, 'e': e})
|
"%(e)s."), {'uuid': c.uuid, 'e': e})
|
||||||
@ -201,7 +201,7 @@ class ImagesController(rest.RestController):
|
|||||||
repo_tag)
|
repo_tag)
|
||||||
new_image = objects.Image(context, **image_dict)
|
new_image = objects.Image(context, **image_dict)
|
||||||
new_image.pull(context)
|
new_image.pull(context)
|
||||||
pecan.request.rpcapi.image_pull(context, new_image)
|
pecan.request.compute_api.image_pull(context, new_image)
|
||||||
# Set the HTTP Location Header
|
# Set the HTTP Location Header
|
||||||
pecan.response.location = link.build_url('images', new_image.uuid)
|
pecan.response.location = link.build_url('images', new_image.uuid)
|
||||||
pecan.response.status = 202
|
pecan.response.status = 202
|
||||||
@ -217,5 +217,5 @@ class ImagesController(rest.RestController):
|
|||||||
action="image:search")
|
action="image:search")
|
||||||
LOG.debug('Calling compute.image_search with %s' %
|
LOG.debug('Calling compute.image_search with %s' %
|
||||||
image)
|
image)
|
||||||
return pecan.request.rpcapi.image_search(context, image,
|
return pecan.request.compute_api.image_search(context, image,
|
||||||
exact_match=exact_match)
|
exact_match)
|
||||||
|
@ -17,7 +17,7 @@ from oslo_config import cfg
|
|||||||
from pecan import hooks
|
from pecan import hooks
|
||||||
|
|
||||||
from zun.common import context
|
from zun.common import context
|
||||||
from zun.compute import rpcapi as compute_rpcapi
|
from zun.compute import api as compute_api
|
||||||
import zun.conf
|
import zun.conf
|
||||||
|
|
||||||
CONF = zun.conf.CONF
|
CONF = zun.conf.CONF
|
||||||
@ -80,8 +80,8 @@ class RPCHook(hooks.PecanHook):
|
|||||||
"""Attach the rpcapi object to the request so controllers can get to it."""
|
"""Attach the rpcapi object to the request so controllers can get to it."""
|
||||||
|
|
||||||
def before(self, state):
|
def before(self, state):
|
||||||
state.request.rpcapi = compute_rpcapi.API(
|
context = state.request.context
|
||||||
context=state.request.context)
|
state.request.compute_api = compute_api.API(context)
|
||||||
|
|
||||||
|
|
||||||
class NoExceptionTracebackHook(hooks.PecanHook):
|
class NoExceptionTracebackHook(hooks.PecanHook):
|
||||||
|
@ -21,7 +21,6 @@ from oslo_service import service
|
|||||||
from zun.common.i18n import _LI
|
from zun.common.i18n import _LI
|
||||||
from zun.common import rpc_service
|
from zun.common import rpc_service
|
||||||
from zun.common import service as zun_service
|
from zun.common import service as zun_service
|
||||||
from zun.common import short_id
|
|
||||||
from zun.compute import manager as compute_manager
|
from zun.compute import manager as compute_manager
|
||||||
import zun.conf
|
import zun.conf
|
||||||
|
|
||||||
@ -37,12 +36,11 @@ def main():
|
|||||||
|
|
||||||
CONF.import_opt('topic', 'zun.conf.compute', group='compute')
|
CONF.import_opt('topic', 'zun.conf.compute', group='compute')
|
||||||
|
|
||||||
compute_id = short_id.generate_id()
|
|
||||||
endpoints = [
|
endpoints = [
|
||||||
compute_manager.Manager(),
|
compute_manager.Manager(),
|
||||||
]
|
]
|
||||||
|
|
||||||
server = rpc_service.Service.create(CONF.compute.topic, compute_id,
|
server = rpc_service.Service.create(CONF.compute.topic, CONF.host,
|
||||||
endpoints, binary='zun-compute')
|
endpoints, binary='zun-compute')
|
||||||
launcher = service.launch(CONF, server)
|
launcher = service.launch(CONF, server)
|
||||||
launcher.wait()
|
launcher.wait()
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import copy
|
||||||
from eventlet.green import threading
|
from eventlet.green import threading
|
||||||
from oslo_context import context
|
from oslo_context import context
|
||||||
|
|
||||||
@ -84,6 +85,19 @@ class RequestContext(context.RequestContext):
|
|||||||
def from_dict(cls, values):
|
def from_dict(cls, values):
|
||||||
return cls(**values)
|
return cls(**values)
|
||||||
|
|
||||||
|
def elevated(self):
|
||||||
|
"""Return a version of this context with admin flag set."""
|
||||||
|
context = copy.copy(self)
|
||||||
|
# context.roles must be deepcopied to leave original roles
|
||||||
|
# without changes
|
||||||
|
context.roles = copy.deepcopy(self.roles)
|
||||||
|
context.is_admin = True
|
||||||
|
|
||||||
|
if 'admin' not in context.roles:
|
||||||
|
context.roles.append('admin')
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
def make_context(*args, **kwargs):
|
def make_context(*args, **kwargs):
|
||||||
return RequestContext(*args, **kwargs)
|
return RequestContext(*args, **kwargs)
|
||||||
|
@ -386,3 +386,7 @@ class ServerUnknownStatus(ZunException):
|
|||||||
|
|
||||||
class EntityNotFound(ZunException):
|
class EntityNotFound(ZunException):
|
||||||
message = _("The %(entity)s (%(name)s) could not be found.")
|
message = _("The %(entity)s (%(name)s) could not be found.")
|
||||||
|
|
||||||
|
|
||||||
|
class NoValidHost(ZunException):
|
||||||
|
message = _("No valid host was found. %(reason)s")
|
||||||
|
@ -79,11 +79,13 @@ class API(object):
|
|||||||
serializer=serializer,
|
serializer=serializer,
|
||||||
timeout=timeout)
|
timeout=timeout)
|
||||||
|
|
||||||
def _call(self, method, *args, **kwargs):
|
def _call(self, server, method, *args, **kwargs):
|
||||||
return self._client.call(self._context, method, *args, **kwargs)
|
cctxt = self._client.prepare(server=server)
|
||||||
|
return cctxt.call(self._context, method, *args, **kwargs)
|
||||||
|
|
||||||
def _cast(self, method, *args, **kwargs):
|
def _cast(self, server, method, *args, **kwargs):
|
||||||
self._client.cast(self._context, method, *args, **kwargs)
|
cctxt = self._client.prepare(server=server)
|
||||||
|
return cctxt.cast(self._context, method, *args, **kwargs)
|
||||||
|
|
||||||
def echo(self, message):
|
def echo(self, message):
|
||||||
self._cast('echo', message=message)
|
self._cast('echo', message=message)
|
||||||
|
94
zun/compute/api.py
Normal file
94
zun/compute/api.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Handles all requests relating to compute resources (e.g. containers,
|
||||||
|
networking and storage of containers, and compute hosts on which they run)."""
|
||||||
|
|
||||||
|
from zun.compute import rpcapi
|
||||||
|
from zun.objects import fields
|
||||||
|
from zun.scheduler import client as scheduler_client
|
||||||
|
|
||||||
|
|
||||||
|
class API(object):
|
||||||
|
"""API for interacting with the compute manager."""
|
||||||
|
|
||||||
|
def __init__(self, context):
|
||||||
|
self.rpcapi = rpcapi.API(context=context)
|
||||||
|
self.scheduler_client = scheduler_client.SchedulerClient()
|
||||||
|
super(API, self).__init__()
|
||||||
|
|
||||||
|
def container_create(self, context, new_container):
|
||||||
|
try:
|
||||||
|
self._schedule_container(context, new_container)
|
||||||
|
except Exception as exc:
|
||||||
|
new_container.status = fields.ContainerStatus.ERROR
|
||||||
|
new_container.status_reason = str(exc)
|
||||||
|
new_container.save()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.rpcapi.container_create(context, new_container)
|
||||||
|
|
||||||
|
def container_run(self, context, new_container):
|
||||||
|
try:
|
||||||
|
self._schedule_container(context, new_container)
|
||||||
|
except Exception as exc:
|
||||||
|
new_container.status = fields.ContainerStatus.ERROR
|
||||||
|
new_container.status_reason = str(exc)
|
||||||
|
new_container.save()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.rpcapi.container_run(context, new_container)
|
||||||
|
|
||||||
|
def _schedule_container(self, context, new_container):
|
||||||
|
dests = self.scheduler_client.select_destinations(context,
|
||||||
|
[new_container])
|
||||||
|
new_container.host = dests[0]['host']
|
||||||
|
new_container.save()
|
||||||
|
|
||||||
|
def container_delete(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_delete(context, container, *args)
|
||||||
|
|
||||||
|
def container_show(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_show(context, container, *args)
|
||||||
|
|
||||||
|
def container_reboot(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_reboot(context, container, *args)
|
||||||
|
|
||||||
|
def container_stop(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_stop(context, container, *args)
|
||||||
|
|
||||||
|
def container_start(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_start(context, container, *args)
|
||||||
|
|
||||||
|
def container_pause(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_pause(context, container, *args)
|
||||||
|
|
||||||
|
def container_unpause(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_unpause(context, container, *args)
|
||||||
|
|
||||||
|
def container_logs(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_logs(context, container, *args)
|
||||||
|
|
||||||
|
def container_exec(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_exec(context, container, *args)
|
||||||
|
|
||||||
|
def container_kill(self, context, container, *args):
|
||||||
|
return self.rpcapi.container_kill(context, container, *args)
|
||||||
|
|
||||||
|
def image_show(self, context, image, *args):
|
||||||
|
return self.rpcapi.image_show(context, image, *args)
|
||||||
|
|
||||||
|
def image_pull(self, context, image, *args):
|
||||||
|
return self.rpcapi.image_pull(context, image, *args)
|
||||||
|
|
||||||
|
def image_search(self, context, image, *args):
|
||||||
|
return self.rpcapi.image_search(context, image, *args)
|
@ -35,51 +35,67 @@ class API(rpc_service.API):
|
|||||||
transport, context, topic=zun.conf.CONF.compute.topic)
|
transport, context, topic=zun.conf.CONF.compute.topic)
|
||||||
|
|
||||||
def container_create(self, context, container):
|
def container_create(self, context, container):
|
||||||
self._cast('container_create', container=container)
|
self._cast(container.host, 'container_create', container=container)
|
||||||
|
|
||||||
def container_run(self, context, container):
|
def container_run(self, context, container):
|
||||||
self._cast('container_run', container=container)
|
self._cast(container.host, 'container_run', container=container)
|
||||||
|
|
||||||
def container_delete(self, context, container, force):
|
def container_delete(self, context, container, force):
|
||||||
return self._call('container_delete', container=container, force=force)
|
return self._call(container.host, 'container_delete',
|
||||||
|
container=container, force=force)
|
||||||
|
|
||||||
def container_show(self, context, container):
|
def container_show(self, context, container):
|
||||||
return self._call('container_show', container=container)
|
return self._call(container.host, 'container_show',
|
||||||
|
container=container)
|
||||||
|
|
||||||
def container_reboot(self, context, container, timeout):
|
def container_reboot(self, context, container, timeout):
|
||||||
self._cast('container_reboot', container=container,
|
self._cast(container.host, 'container_reboot', container=container,
|
||||||
timeout=timeout)
|
timeout=timeout)
|
||||||
|
|
||||||
def container_stop(self, context, container, timeout):
|
def container_stop(self, context, container, timeout):
|
||||||
self._cast('container_stop', container=container,
|
self._cast(container.host, 'container_stop', container=container,
|
||||||
timeout=timeout)
|
timeout=timeout)
|
||||||
|
|
||||||
def container_start(self, context, container):
|
def container_start(self, context, container):
|
||||||
self._cast('container_start', container=container)
|
host = container.host
|
||||||
|
self._cast(host, 'container_start', container=container)
|
||||||
|
|
||||||
def container_pause(self, context, container):
|
def container_pause(self, context, container):
|
||||||
self._cast('container_pause', container=container)
|
self._cast(container.host, 'container_pause', container=container)
|
||||||
|
|
||||||
def container_unpause(self, context, container):
|
def container_unpause(self, context, container):
|
||||||
self._cast('container_unpause', container=container)
|
self._cast(container.host, 'container_unpause', container=container)
|
||||||
|
|
||||||
def container_logs(self, context, container):
|
def container_logs(self, context, container):
|
||||||
return self._call('container_logs', container=container)
|
host = container.host
|
||||||
|
return self._call(host, 'container_logs', container=container)
|
||||||
|
|
||||||
def container_exec(self, context, container, command):
|
def container_exec(self, context, container, command):
|
||||||
return self._call('container_exec', container=container,
|
return self._call(container.host, 'container_exec',
|
||||||
command=command)
|
container=container, command=command)
|
||||||
|
|
||||||
def container_kill(self, context, container, signal):
|
def container_kill(self, context, container, signal):
|
||||||
self._cast('container_kill', container=container,
|
self._cast(container.host, 'container_kill', container=container,
|
||||||
signal=signal)
|
signal=signal)
|
||||||
|
|
||||||
def image_show(self, context, image):
|
def image_show(self, context, image):
|
||||||
return self._call('image_show', image=image)
|
# NOTE(hongbin): Image API doesn't support multiple compute nodes
|
||||||
|
# scenario yet, so we temporarily set host to None and rpc will
|
||||||
|
# choose an arbitrary host.
|
||||||
|
host = None
|
||||||
|
return self._call(host, 'image_show', image=image)
|
||||||
|
|
||||||
def image_pull(self, context, image):
|
def image_pull(self, context, image):
|
||||||
self._cast('image_pull', image=image)
|
# NOTE(hongbin): Image API doesn't support multiple compute nodes
|
||||||
|
# scenario yet, so we temporarily set host to None and rpc will
|
||||||
|
# choose an arbitrary host.
|
||||||
|
host = None
|
||||||
|
self._cast(host, 'image_pull', image=image)
|
||||||
|
|
||||||
def image_search(self, context, image, exact_match):
|
def image_search(self, context, image, exact_match):
|
||||||
return self._call('image_search', image=image,
|
# NOTE(hongbin): Image API doesn't support multiple compute nodes
|
||||||
|
# scenario yet, so we temporarily set host to None and rpc will
|
||||||
|
# choose an arbitrary host.
|
||||||
|
host = None
|
||||||
|
return self._call(host, 'image_search', image=image,
|
||||||
exact_match=exact_match)
|
exact_match=exact_match)
|
||||||
|
@ -23,6 +23,7 @@ from zun.conf import glance_client
|
|||||||
from zun.conf import image_driver
|
from zun.conf import image_driver
|
||||||
from zun.conf import nova_client
|
from zun.conf import nova_client
|
||||||
from zun.conf import path
|
from zun.conf import path
|
||||||
|
from zun.conf import scheduler
|
||||||
from zun.conf import services
|
from zun.conf import services
|
||||||
from zun.conf import zun_client
|
from zun.conf import zun_client
|
||||||
|
|
||||||
@ -37,5 +38,6 @@ glance_client.register_opts(CONF)
|
|||||||
image_driver.register_opts(CONF)
|
image_driver.register_opts(CONF)
|
||||||
nova_client.register_opts(CONF)
|
nova_client.register_opts(CONF)
|
||||||
path.register_opts(CONF)
|
path.register_opts(CONF)
|
||||||
|
scheduler.register_opts(CONF)
|
||||||
services.register_opts(CONF)
|
services.register_opts(CONF)
|
||||||
zun_client.register_opts(CONF)
|
zun_client.register_opts(CONF)
|
||||||
|
49
zun/conf/scheduler.py
Normal file
49
zun/conf/scheduler.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Copyright 2015 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
scheduler_group = cfg.OptGroup(name="scheduler",
|
||||||
|
title="Scheduler configuration")
|
||||||
|
|
||||||
|
scheduler_opts = [
|
||||||
|
cfg.StrOpt("driver",
|
||||||
|
default="chance_scheduler",
|
||||||
|
choices=("chance_scheduler", "fake_scheduler"),
|
||||||
|
help="""
|
||||||
|
The class of the driver used by the scheduler.
|
||||||
|
|
||||||
|
The options are chosen from the entry points under the namespace
|
||||||
|
'zun.scheduler.driver' in 'setup.cfg'.
|
||||||
|
|
||||||
|
Possible values:
|
||||||
|
|
||||||
|
* A string, where the string corresponds to the class name of a scheduler
|
||||||
|
driver. There are a number of options available:
|
||||||
|
** 'chance_scheduler', which simply picks a host at random
|
||||||
|
** A custom scheduler driver. In this case, you will be responsible for
|
||||||
|
creating and maintaining the entry point in your 'setup.cfg' file
|
||||||
|
"""),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def register_opts(conf):
|
||||||
|
conf.register_group(scheduler_group)
|
||||||
|
conf.register_opts(scheduler_opts, group=scheduler_group)
|
||||||
|
|
||||||
|
|
||||||
|
def list_opts():
|
||||||
|
return {scheduler_group: scheduler_opts}
|
@ -22,7 +22,8 @@ from zun.common.i18n import _
|
|||||||
|
|
||||||
service_opts = [
|
service_opts = [
|
||||||
cfg.StrOpt('host',
|
cfg.StrOpt('host',
|
||||||
default=socket.getfqdn(),
|
default=socket.gethostname(),
|
||||||
|
sample_default='localhost',
|
||||||
help=_('Name of this node. This can be an opaque identifier. '
|
help=_('Name of this node. This can be an opaque identifier. '
|
||||||
'It is not necessarily a hostname, FQDN, or IP address. '
|
'It is not necessarily a hostname, FQDN, or IP address. '
|
||||||
'However, the node name must be valid within '
|
'However, the node name must be valid within '
|
||||||
|
@ -290,11 +290,24 @@ class NovaDockerDriver(DockerDriver):
|
|||||||
def create_sandbox(self, context, container, key_name=None,
|
def create_sandbox(self, context, container, key_name=None,
|
||||||
flavor='m1.small', image='kubernetes/pause',
|
flavor='m1.small', image='kubernetes/pause',
|
||||||
nics='auto'):
|
nics='auto'):
|
||||||
|
# FIXME(hongbin): We elevate to admin privilege because the default
|
||||||
|
# policy in nova disallows non-admin users to create instance in
|
||||||
|
# specified host. This is not ideal because all nova instances will
|
||||||
|
# be created at service admin tenant now, which breaks the
|
||||||
|
# multi-tenancy model. We need to fix it.
|
||||||
|
elevated = context.elevated()
|
||||||
|
novaclient = nova.NovaClient(elevated)
|
||||||
name = self.get_sandbox_name(container)
|
name = self.get_sandbox_name(container)
|
||||||
novaclient = nova.NovaClient(context)
|
if container.host != CONF.host:
|
||||||
|
raise exception.ZunException(_(
|
||||||
|
"Host mismatch: container should be created at host '%s'.") %
|
||||||
|
container.host)
|
||||||
|
# NOTE(hongbin): The format of availability zone is ZONE:HOST:NODE
|
||||||
|
# However, we just want to specify host, so it is ':HOST:'
|
||||||
|
az = ':%s:' % container.host
|
||||||
sandbox = novaclient.create_server(name=name, image=image,
|
sandbox = novaclient.create_server(name=name, image=image,
|
||||||
flavor=flavor, key_name=key_name,
|
flavor=flavor, key_name=key_name,
|
||||||
nics=nics)
|
nics=nics, availability_zone=az)
|
||||||
self._ensure_active(novaclient, sandbox)
|
self._ensure_active(novaclient, sandbox)
|
||||||
sandbox_id = self._find_container_by_server_name(name)
|
sandbox_id = self._find_container_by_server_name(name)
|
||||||
return sandbox_id
|
return sandbox_id
|
||||||
@ -313,7 +326,8 @@ class NovaDockerDriver(DockerDriver):
|
|||||||
success_msg=success_msg, timeout_msg=timeout_msg)
|
success_msg=success_msg, timeout_msg=timeout_msg)
|
||||||
|
|
||||||
def delete_sandbox(self, context, sandbox_id):
|
def delete_sandbox(self, context, sandbox_id):
|
||||||
novaclient = nova.NovaClient(context)
|
elevated = context.elevated()
|
||||||
|
novaclient = nova.NovaClient(elevated)
|
||||||
server_name = self._find_server_by_container_id(sandbox_id)
|
server_name = self._find_server_by_container_id(sandbox_id)
|
||||||
if not server_name:
|
if not server_name:
|
||||||
LOG.warning(_LW("Cannot find server name for sandbox %s") %
|
LOG.warning(_LW("Cannot find server name for sandbox %s") %
|
||||||
@ -324,7 +338,8 @@ class NovaDockerDriver(DockerDriver):
|
|||||||
self._ensure_deleted(novaclient, server_id)
|
self._ensure_deleted(novaclient, server_id)
|
||||||
|
|
||||||
def stop_sandbox(self, context, sandbox_id):
|
def stop_sandbox(self, context, sandbox_id):
|
||||||
novaclient = nova.NovaClient(context)
|
elevated = context.elevated()
|
||||||
|
novaclient = nova.NovaClient(elevated)
|
||||||
server_name = self._find_server_by_container_id(sandbox_id)
|
server_name = self._find_server_by_container_id(sandbox_id)
|
||||||
if not server_name:
|
if not server_name:
|
||||||
LOG.warning(_LW("Cannot find server name for sandbox %s") %
|
LOG.warning(_LW("Cannot find server name for sandbox %s") %
|
||||||
@ -346,7 +361,8 @@ class NovaDockerDriver(DockerDriver):
|
|||||||
success_msg=success_msg, timeout_msg=timeout_msg)
|
success_msg=success_msg, timeout_msg=timeout_msg)
|
||||||
|
|
||||||
def get_addresses(self, context, container):
|
def get_addresses(self, context, container):
|
||||||
novaclient = nova.NovaClient(context)
|
elevated = context.elevated()
|
||||||
|
novaclient = nova.NovaClient(elevated)
|
||||||
sandbox_id = self.get_sandbox_id(container)
|
sandbox_id = self.get_sandbox_id(container)
|
||||||
if sandbox_id:
|
if sandbox_id:
|
||||||
server_name = self._find_server_by_container_id(sandbox_id)
|
server_name = self._find_server_by_container_id(sandbox_id)
|
||||||
|
@ -58,7 +58,7 @@ class Connection(object):
|
|||||||
def list_container(cls, context, filters=None,
|
def list_container(cls, context, filters=None,
|
||||||
limit=None, marker=None,
|
limit=None, marker=None,
|
||||||
sort_key=None, sort_dir=None):
|
sort_key=None, sort_dir=None):
|
||||||
"""Get matching containers.
|
"""List matching containers.
|
||||||
|
|
||||||
Return a list of the specified columns for all containers that match
|
Return a list of the specified columns for all containers that match
|
||||||
the specified filters.
|
the specified filters.
|
||||||
@ -219,6 +219,18 @@ class Connection(object):
|
|||||||
return dbdriver.get_zun_service_list(disabled, limit,
|
return dbdriver.get_zun_service_list(disabled, limit,
|
||||||
marker, sort_key, sort_dir)
|
marker, sort_key, sort_dir)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_zun_service_by_binary(cls, context, binary):
|
||||||
|
"""List matching zun services.
|
||||||
|
|
||||||
|
Return a list of the specified binary.
|
||||||
|
:param context: The security context
|
||||||
|
:param binary: The name of the binary.
|
||||||
|
:returns: A list of tuples of the specified binary.
|
||||||
|
"""
|
||||||
|
dbdriver = get_instance()
|
||||||
|
return dbdriver.list_zun_service_by_binary(binary)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pull_image(cls, context, values):
|
def pull_image(cls, context, values):
|
||||||
"""Create a new image.
|
"""Create a new image.
|
||||||
|
@ -293,6 +293,11 @@ class Connection(api.Connection):
|
|||||||
return _paginate_query(models.ZunService, limit, marker,
|
return _paginate_query(models.ZunService, limit, marker,
|
||||||
sort_key, sort_dir, query)
|
sort_key, sort_dir, query)
|
||||||
|
|
||||||
|
def list_zun_service_by_binary(cls, binary):
|
||||||
|
query = model_query(models.ZunService)
|
||||||
|
query = query.filter_by(binary=binary)
|
||||||
|
return _paginate_query(models.ZunService, query=query)
|
||||||
|
|
||||||
def pull_image(self, context, values):
|
def pull_image(self, context, values):
|
||||||
# ensure defaults are present for new containers
|
# ensure defaults are present for new containers
|
||||||
if not values.get('uuid'):
|
if not values.get('uuid'):
|
||||||
|
@ -84,6 +84,12 @@ class ZunService(base.ZunPersistentObject, base.ZunObject):
|
|||||||
return ZunService._from_db_object_list(db_zun_services, cls,
|
return ZunService._from_db_object_list(db_zun_services, cls,
|
||||||
context)
|
context)
|
||||||
|
|
||||||
|
@base.remotable_classmethod
|
||||||
|
def list_by_binary(cls, context, binary):
|
||||||
|
db_zun_services = dbapi.Connection.list_zun_service_by_binary(
|
||||||
|
context, binary)
|
||||||
|
return ZunService._from_db_object_list(db_zun_services, cls, context)
|
||||||
|
|
||||||
@base.remotable
|
@base.remotable
|
||||||
def create(self, context=None):
|
def create(self, context=None):
|
||||||
"""Create a ZunService record in the DB.
|
"""Create a ZunService record in the DB.
|
||||||
|
0
zun/scheduler/__init__.py
Normal file
0
zun/scheduler/__init__.py
Normal file
48
zun/scheduler/chance_scheduler.py
Normal file
48
zun/scheduler/chance_scheduler.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Chance (Random) Scheduler implementation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
from zun.common import exception
|
||||||
|
from zun.common.i18n import _
|
||||||
|
from zun.scheduler import driver
|
||||||
|
|
||||||
|
|
||||||
|
class ChanceScheduler(driver.Scheduler):
|
||||||
|
"""Implements Scheduler as a random node selector."""
|
||||||
|
|
||||||
|
def _schedule(self, context, container):
|
||||||
|
"""Picks a host that is up at random."""
|
||||||
|
hosts = self.hosts_up(context)
|
||||||
|
if not hosts:
|
||||||
|
msg = _("Is the appropriate service running?")
|
||||||
|
raise exception.NoValidHost(reason=msg)
|
||||||
|
|
||||||
|
return random.choice(hosts)
|
||||||
|
|
||||||
|
def select_destinations(self, context, containers):
|
||||||
|
"""Selects random destinations."""
|
||||||
|
dests = []
|
||||||
|
for container in containers:
|
||||||
|
host = self._schedule(context, container)
|
||||||
|
host_state = dict(host=host, nodename=None, limits=None)
|
||||||
|
dests.append(host_state)
|
||||||
|
|
||||||
|
if len(dests) < 1:
|
||||||
|
reason = _('There are not enough hosts available.')
|
||||||
|
raise exception.NoValidHost(reason=reason)
|
||||||
|
|
||||||
|
return dests
|
33
zun/scheduler/client.py
Normal file
33
zun/scheduler/client.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Copyright (c) 2014 Red Hat, 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 stevedore import driver
|
||||||
|
import zun.conf
|
||||||
|
|
||||||
|
CONF = zun.conf.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class SchedulerClient(object):
|
||||||
|
"""Client library for placing calls to the scheduler."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
scheduler_driver = CONF.scheduler.driver
|
||||||
|
self.driver = driver.DriverManager(
|
||||||
|
"zun.scheduler.driver",
|
||||||
|
scheduler_driver,
|
||||||
|
invoke_on_load=True).driver
|
||||||
|
|
||||||
|
def select_destinations(self, context, containers):
|
||||||
|
return self.driver.select_destinations(context, containers)
|
55
zun/scheduler/driver.py
Normal file
55
zun/scheduler/driver.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Copyright (c) 2010 OpenStack Foundation
|
||||||
|
# Copyright 2010 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Scheduler base class that all Schedulers should inherit from
|
||||||
|
"""
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from zun.api import servicegroup
|
||||||
|
import zun.conf
|
||||||
|
from zun import objects
|
||||||
|
|
||||||
|
CONF = zun.conf.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class Scheduler(object):
|
||||||
|
"""The base class that all Scheduler classes should inherit from."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.servicegroup_api = servicegroup.ServiceGroup()
|
||||||
|
|
||||||
|
def hosts_up(self, context):
|
||||||
|
"""Return the list of hosts that have a running service."""
|
||||||
|
|
||||||
|
services = objects.ZunService.list_by_binary(context, 'zun-compute')
|
||||||
|
return [service.host
|
||||||
|
for service in services
|
||||||
|
if self.servicegroup_api.service_is_up(service)]
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def select_destinations(self, context, containers):
|
||||||
|
"""Must override select_destinations method.
|
||||||
|
|
||||||
|
:return: A list of dicts with 'host', 'nodename' and 'limits' as keys
|
||||||
|
that satisfies the request_spec and filter_properties.
|
||||||
|
"""
|
||||||
|
return []
|
@ -25,8 +25,8 @@ from zun.tests.unit.objects import utils as obj_utils
|
|||||||
|
|
||||||
|
|
||||||
class TestContainerController(api_base.FunctionalTest):
|
class TestContainerController(api_base.FunctionalTest):
|
||||||
@patch('zun.compute.rpcapi.API.container_run')
|
@patch('zun.compute.api.API.container_run')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_run_container(self, mock_search, mock_container_run):
|
def test_run_container(self, mock_search, mock_container_run):
|
||||||
mock_container_run.side_effect = lambda x, y: y
|
mock_container_run.side_effect = lambda x, y: y
|
||||||
|
|
||||||
@ -40,8 +40,8 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(202, response.status_int)
|
self.assertEqual(202, response.status_int)
|
||||||
self.assertTrue(mock_container_run.called)
|
self.assertTrue(mock_container_run.called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_create_container(self, mock_search, mock_container_create):
|
def test_create_container(self, mock_search, mock_container_create):
|
||||||
mock_container_create.side_effect = lambda x, y: y
|
mock_container_create.side_effect = lambda x, y: y
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(202, response.status_int)
|
self.assertEqual(202, response.status_int)
|
||||||
self.assertTrue(mock_container_create.called)
|
self.assertTrue(mock_container_create.called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
def test_create_container_image_not_specified(self, mock_container_create):
|
def test_create_container_image_not_specified(self, mock_container_create):
|
||||||
|
|
||||||
params = ('{"name": "MyDocker",'
|
params = ('{"name": "MyDocker",'
|
||||||
@ -68,8 +68,8 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
content_type='application/json')
|
content_type='application/json')
|
||||||
self.assertTrue(mock_container_create.not_called)
|
self.assertTrue(mock_container_create.not_called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_create_container_image_not_found(self, mock_search,
|
def test_create_container_image_not_found(self, mock_search,
|
||||||
mock_container_create):
|
mock_container_create):
|
||||||
mock_container_create.side_effect = lambda x, y: y
|
mock_container_create.side_effect = lambda x, y: y
|
||||||
@ -81,8 +81,8 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(404, response.status_int)
|
self.assertEqual(404, response.status_int)
|
||||||
self.assertFalse(mock_container_create.called)
|
self.assertFalse(mock_container_create.called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_create_container_set_project_id_and_user_id(
|
def test_create_container_set_project_id_and_user_id(
|
||||||
self, mock_search, mock_container_create):
|
self, mock_search, mock_container_create):
|
||||||
def _create_side_effect(cnxt, container):
|
def _create_side_effect(cnxt, container):
|
||||||
@ -98,8 +98,8 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
params=params,
|
params=params,
|
||||||
content_type='application/json')
|
content_type='application/json')
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_create_container_resp_has_status_reason(self, mock_search,
|
def test_create_container_resp_has_status_reason(self, mock_search,
|
||||||
mock_container_create):
|
mock_container_create):
|
||||||
mock_container_create.side_effect = lambda x, y: y
|
mock_container_create.side_effect = lambda x, y: y
|
||||||
@ -113,10 +113,10 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(202, response.status_int)
|
self.assertEqual(202, response.status_int)
|
||||||
self.assertIn('status_reason', response.json.keys())
|
self.assertIn('status_reason', response.json.keys())
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.rpcapi.API.container_delete')
|
@patch('zun.compute.api.API.container_delete')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_create_container_with_command(self, mock_search,
|
def test_create_container_with_command(self, mock_search,
|
||||||
mock_container_delete,
|
mock_container_delete,
|
||||||
mock_container_create,
|
mock_container_create,
|
||||||
@ -156,9 +156,9 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(0, len(c))
|
self.assertEqual(0, len(c))
|
||||||
self.assertTrue(mock_container_create.called)
|
self.assertTrue(mock_container_create.called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_create_container_without_memory(self, mock_search,
|
def test_create_container_without_memory(self, mock_search,
|
||||||
mock_container_create,
|
mock_container_create,
|
||||||
mock_container_show):
|
mock_container_show):
|
||||||
@ -187,9 +187,9 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual({"key1": "val1", "key2": "val2"},
|
self.assertEqual({"key1": "val1", "key2": "val2"},
|
||||||
c.get('environment'))
|
c.get('environment'))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_create_container_without_environment(self, mock_search,
|
def test_create_container_without_environment(self, mock_search,
|
||||||
mock_container_create,
|
mock_container_create,
|
||||||
mock_container_show):
|
mock_container_show):
|
||||||
@ -216,9 +216,9 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual('512M', c.get('memory'))
|
self.assertEqual('512M', c.get('memory'))
|
||||||
self.assertEqual({}, c.get('environment'))
|
self.assertEqual({}, c.get('environment'))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_create_container_without_name(self, mock_search,
|
def test_create_container_without_name(self, mock_search,
|
||||||
mock_container_create,
|
mock_container_create,
|
||||||
mock_container_show):
|
mock_container_show):
|
||||||
@ -246,8 +246,8 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual({"key1": "val1", "key2": "val2"},
|
self.assertEqual({"key1": "val1", "key2": "val2"},
|
||||||
c.get('environment'))
|
c.get('environment'))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_create')
|
@patch('zun.compute.api.API.container_create')
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_create_container_invalid_long_name(self, mock_search,
|
def test_create_container_invalid_long_name(self, mock_search,
|
||||||
mock_container_create):
|
mock_container_create):
|
||||||
# Long name
|
# Long name
|
||||||
@ -257,7 +257,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
params=params, content_type='application/json')
|
params=params, content_type='application/json')
|
||||||
self.assertTrue(mock_container_create.not_called)
|
self.assertTrue(mock_container_create.not_called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.objects.Container.list')
|
@patch('zun.objects.Container.list')
|
||||||
def test_get_all_containers(self, mock_container_list,
|
def test_get_all_containers(self, mock_container_list,
|
||||||
mock_container_show):
|
mock_container_show):
|
||||||
@ -277,7 +277,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(test_container['uuid'],
|
self.assertEqual(test_container['uuid'],
|
||||||
actual_containers[0].get('uuid'))
|
actual_containers[0].get('uuid'))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.objects.Container.list')
|
@patch('zun.objects.Container.list')
|
||||||
def test_get_all_has_status_reason_and_image_pull_policy(
|
def test_get_all_has_status_reason_and_image_pull_policy(
|
||||||
self, mock_container_list, mock_container_show):
|
self, mock_container_list, mock_container_show):
|
||||||
@ -295,7 +295,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertIn('status_reason', actual_containers[0].keys())
|
self.assertIn('status_reason', actual_containers[0].keys())
|
||||||
self.assertIn('image_pull_policy', actual_containers[0].keys())
|
self.assertIn('image_pull_policy', actual_containers[0].keys())
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.objects.Container.list')
|
@patch('zun.objects.Container.list')
|
||||||
def test_get_all_containers_with_pagination_marker(self,
|
def test_get_all_containers_with_pagination_marker(self,
|
||||||
mock_container_list,
|
mock_container_list,
|
||||||
@ -318,7 +318,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(container_list[-1].uuid,
|
self.assertEqual(container_list[-1].uuid,
|
||||||
actual_containers[0].get('uuid'))
|
actual_containers[0].get('uuid'))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.objects.Container.list')
|
@patch('zun.objects.Container.list')
|
||||||
def test_get_all_containers_with_exception(self, mock_container_list,
|
def test_get_all_containers_with_exception(self, mock_container_list,
|
||||||
mock_container_show):
|
mock_container_show):
|
||||||
@ -341,7 +341,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(fields.ContainerStatus.UNKNOWN,
|
self.assertEqual(fields.ContainerStatus.UNKNOWN,
|
||||||
actual_containers[0].get('status'))
|
actual_containers[0].get('status'))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.objects.Container.get_by_uuid')
|
@patch('zun.objects.Container.get_by_uuid')
|
||||||
def test_get_one_by_uuid(self, mock_container_get_by_uuid,
|
def test_get_one_by_uuid(self, mock_container_get_by_uuid,
|
||||||
mock_container_show):
|
mock_container_show):
|
||||||
@ -359,7 +359,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(test_container['uuid'],
|
self.assertEqual(test_container['uuid'],
|
||||||
response.json['uuid'])
|
response.json['uuid'])
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.objects.Container.get_by_name')
|
@patch('zun.objects.Container.get_by_name')
|
||||||
def test_get_one_by_name(self, mock_container_get_by_name,
|
def test_get_one_by_name(self, mock_container_get_by_name,
|
||||||
mock_container_show):
|
mock_container_show):
|
||||||
@ -439,7 +439,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock.ANY, test_container_obj)
|
mock.ANY, test_container_obj)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_start')
|
@patch('zun.compute.api.API.container_start')
|
||||||
def test_start_by_uuid(self, mock_container_start, mock_validate):
|
def test_start_by_uuid(self, mock_container_start, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -458,7 +458,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
'start'))
|
'start'))
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_start')
|
@patch('zun.compute.api.API.container_start')
|
||||||
def test_start_by_name(self, mock_container_start, mock_validate):
|
def test_start_by_name(self, mock_container_start, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -468,7 +468,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock_container_start, 202)
|
mock_container_start, 202)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_stop')
|
@patch('zun.compute.api.API.container_stop')
|
||||||
def test_stop_by_uuid(self, mock_container_stop, mock_validate):
|
def test_stop_by_uuid(self, mock_container_stop, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -479,7 +479,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
query_param='timeout=10')
|
query_param='timeout=10')
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_stop')
|
@patch('zun.compute.api.API.container_stop')
|
||||||
def test_stop_by_name(self, mock_container_stop, mock_validate):
|
def test_stop_by_name(self, mock_container_stop, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -499,7 +499,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
'stop'))
|
'stop'))
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_pause')
|
@patch('zun.compute.api.API.container_pause')
|
||||||
def test_pause_by_uuid(self, mock_container_pause, mock_validate):
|
def test_pause_by_uuid(self, mock_container_pause, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -518,7 +518,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
'pause'))
|
'pause'))
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_pause')
|
@patch('zun.compute.api.API.container_pause')
|
||||||
def test_pause_by_name(self, mock_container_pause, mock_validate):
|
def test_pause_by_name(self, mock_container_pause, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -528,7 +528,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock_container_pause, 202)
|
mock_container_pause, 202)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_unpause')
|
@patch('zun.compute.api.API.container_unpause')
|
||||||
def test_unpause_by_uuid(self, mock_container_unpause, mock_validate):
|
def test_unpause_by_uuid(self, mock_container_unpause, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -548,7 +548,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
'unpause'))
|
'unpause'))
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_unpause')
|
@patch('zun.compute.api.API.container_unpause')
|
||||||
def test_unpause_by_name(self, mock_container_unpause, mock_validate):
|
def test_unpause_by_name(self, mock_container_unpause, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -558,7 +558,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock_container_unpause, 202)
|
mock_container_unpause, 202)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_reboot')
|
@patch('zun.compute.api.API.container_reboot')
|
||||||
def test_reboot_by_uuid(self, mock_container_reboot, mock_validate):
|
def test_reboot_by_uuid(self, mock_container_reboot, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -578,7 +578,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
'reboot'))
|
'reboot'))
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_reboot')
|
@patch('zun.compute.api.API.container_reboot')
|
||||||
def test_reboot_by_name(self, mock_container_reboot, mock_validate):
|
def test_reboot_by_name(self, mock_container_reboot, mock_validate):
|
||||||
test_container_obj = objects.Container(self.context,
|
test_container_obj = objects.Container(self.context,
|
||||||
**utils.get_test_container())
|
**utils.get_test_container())
|
||||||
@ -588,7 +588,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock_container_reboot, 202,
|
mock_container_reboot, 202,
|
||||||
query_param='timeout=10')
|
query_param='timeout=10')
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_logs')
|
@patch('zun.compute.api.API.container_logs')
|
||||||
@patch('zun.objects.Container.get_by_uuid')
|
@patch('zun.objects.Container.get_by_uuid')
|
||||||
def test_get_logs_by_uuid(self, mock_get_by_uuid, mock_container_logs):
|
def test_get_logs_by_uuid(self, mock_get_by_uuid, mock_container_logs):
|
||||||
mock_container_logs.return_value = "test"
|
mock_container_logs.return_value = "test"
|
||||||
@ -603,7 +603,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock_container_logs.assert_called_once_with(
|
mock_container_logs.assert_called_once_with(
|
||||||
mock.ANY, test_container_obj)
|
mock.ANY, test_container_obj)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_logs')
|
@patch('zun.compute.api.API.container_logs')
|
||||||
@patch('zun.objects.Container.get_by_name')
|
@patch('zun.objects.Container.get_by_name')
|
||||||
def test_get_logs_by_name(self, mock_get_by_name, mock_container_logs):
|
def test_get_logs_by_name(self, mock_get_by_name, mock_container_logs):
|
||||||
mock_container_logs.return_value = "test logs"
|
mock_container_logs.return_value = "test logs"
|
||||||
@ -618,7 +618,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock_container_logs.assert_called_once_with(
|
mock_container_logs.assert_called_once_with(
|
||||||
mock.ANY, test_container_obj)
|
mock.ANY, test_container_obj)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_logs')
|
@patch('zun.compute.api.API.container_logs')
|
||||||
@patch('zun.objects.Container.get_by_uuid')
|
@patch('zun.objects.Container.get_by_uuid')
|
||||||
def test_get_logs_put_fails(self, mock_get_by_uuid, mock_container_logs):
|
def test_get_logs_put_fails(self, mock_get_by_uuid, mock_container_logs):
|
||||||
test_container = utils.get_test_container()
|
test_container = utils.get_test_container()
|
||||||
@ -631,7 +631,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertFalse(mock_container_logs.called)
|
self.assertFalse(mock_container_logs.called)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_exec')
|
@patch('zun.compute.api.API.container_exec')
|
||||||
@patch('zun.objects.Container.get_by_uuid')
|
@patch('zun.objects.Container.get_by_uuid')
|
||||||
def test_execute_command_by_uuid(self, mock_get_by_uuid,
|
def test_execute_command_by_uuid(self, mock_get_by_uuid,
|
||||||
mock_container_exec, mock_validate):
|
mock_container_exec, mock_validate):
|
||||||
@ -660,7 +660,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
'execute'), cmd)
|
'execute'), cmd)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_exec')
|
@patch('zun.compute.api.API.container_exec')
|
||||||
@patch('zun.objects.Container.get_by_name')
|
@patch('zun.objects.Container.get_by_name')
|
||||||
def test_execute_command_by_name(self, mock_get_by_name,
|
def test_execute_command_by_name(self, mock_get_by_name,
|
||||||
mock_container_exec, mock_validate):
|
mock_container_exec, mock_validate):
|
||||||
@ -678,7 +678,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock.ANY, test_container_obj, cmd['command'])
|
mock.ANY, test_container_obj, cmd['command'])
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_delete')
|
@patch('zun.compute.api.API.container_delete')
|
||||||
@patch('zun.objects.Container.get_by_uuid')
|
@patch('zun.objects.Container.get_by_uuid')
|
||||||
def test_delete_container_by_uuid(self, mock_get_by_uuid,
|
def test_delete_container_by_uuid(self, mock_get_by_uuid,
|
||||||
mock_container_delete, mock_validate):
|
mock_container_delete, mock_validate):
|
||||||
@ -704,7 +704,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
"Cannot delete container %s in Running state" % uuid):
|
"Cannot delete container %s in Running state" % uuid):
|
||||||
self.app.delete('/v1/containers/%s' % (test_object.uuid))
|
self.app.delete('/v1/containers/%s' % (test_object.uuid))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.container_delete')
|
@patch('zun.compute.api.API.container_delete')
|
||||||
def test_delete_by_uuid_invalid_state_force_true(self, mock_delete):
|
def test_delete_by_uuid_invalid_state_force_true(self, mock_delete):
|
||||||
uuid = uuidutils.generate_uuid()
|
uuid = uuidutils.generate_uuid()
|
||||||
test_object = utils.create_test_container(context=self.context,
|
test_object = utils.create_test_container(context=self.context,
|
||||||
@ -714,7 +714,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(204, response.status_int)
|
self.assertEqual(204, response.status_int)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_delete')
|
@patch('zun.compute.api.API.container_delete')
|
||||||
@patch('zun.objects.Container.get_by_name')
|
@patch('zun.objects.Container.get_by_name')
|
||||||
def test_delete_container_by_name(self, mock_get_by_name,
|
def test_delete_container_by_name(self, mock_get_by_name,
|
||||||
mock_container_delete, mock_validate):
|
mock_container_delete, mock_validate):
|
||||||
@ -732,7 +732,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock_destroy.assert_called_once_with(mock.ANY)
|
mock_destroy.assert_called_once_with(mock.ANY)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_kill')
|
@patch('zun.compute.api.API.container_kill')
|
||||||
@patch('zun.objects.Container.get_by_uuid')
|
@patch('zun.objects.Container.get_by_uuid')
|
||||||
def test_kill_container_by_uuid(self,
|
def test_kill_container_by_uuid(self,
|
||||||
mock_get_by_uuid, mock_container_kill,
|
mock_get_by_uuid, mock_container_kill,
|
||||||
@ -763,7 +763,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
'kill'), body)
|
'kill'), body)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_kill')
|
@patch('zun.compute.api.API.container_kill')
|
||||||
@patch('zun.objects.Container.get_by_name')
|
@patch('zun.objects.Container.get_by_name')
|
||||||
def test_kill_container_by_name(self,
|
def test_kill_container_by_name(self,
|
||||||
mock_get_by_name, mock_container_kill,
|
mock_get_by_name, mock_container_kill,
|
||||||
@ -784,7 +784,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
mock.ANY, test_container_obj, cmd['signal'])
|
mock.ANY, test_container_obj, cmd['signal'])
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_kill')
|
@patch('zun.compute.api.API.container_kill')
|
||||||
@patch('zun.objects.Container.get_by_uuid')
|
@patch('zun.objects.Container.get_by_uuid')
|
||||||
def test_kill_container_which_not_exist(self,
|
def test_kill_container_which_not_exist(self,
|
||||||
mock_get_by_uuid,
|
mock_get_by_uuid,
|
||||||
@ -802,7 +802,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertTrue(mock_container_kill.called)
|
self.assertTrue(mock_container_kill.called)
|
||||||
|
|
||||||
@patch('zun.common.utils.validate_container_state')
|
@patch('zun.common.utils.validate_container_state')
|
||||||
@patch('zun.compute.rpcapi.API.container_kill')
|
@patch('zun.compute.api.API.container_kill')
|
||||||
@patch('zun.objects.Container.get_by_uuid')
|
@patch('zun.objects.Container.get_by_uuid')
|
||||||
def test_kill_container_with_exception(self,
|
def test_kill_container_with_exception(self,
|
||||||
mock_get_by_uuid,
|
mock_get_by_uuid,
|
||||||
|
@ -23,7 +23,7 @@ from zun.tests.unit.db import utils
|
|||||||
|
|
||||||
|
|
||||||
class TestImageController(api_base.FunctionalTest):
|
class TestImageController(api_base.FunctionalTest):
|
||||||
@patch('zun.compute.rpcapi.API.image_pull')
|
@patch('zun.compute.api.API.image_pull')
|
||||||
def test_image_pull(self, mock_image_pull):
|
def test_image_pull(self, mock_image_pull):
|
||||||
mock_image_pull.side_effect = lambda x, y: y
|
mock_image_pull.side_effect = lambda x, y: y
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ class TestImageController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(202, response.status_int)
|
self.assertEqual(202, response.status_int)
|
||||||
self.assertTrue(mock_image_pull.called)
|
self.assertTrue(mock_image_pull.called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_pull')
|
@patch('zun.compute.api.API.image_pull')
|
||||||
def test_image_pull_with_no_repo(self, mock_image_pull):
|
def test_image_pull_with_no_repo(self, mock_image_pull):
|
||||||
params = {}
|
params = {}
|
||||||
with self.assertRaisesRegexp(AppError,
|
with self.assertRaisesRegexp(AppError,
|
||||||
@ -53,7 +53,7 @@ class TestImageController(api_base.FunctionalTest):
|
|||||||
content_type='application/json')
|
content_type='application/json')
|
||||||
self.assertTrue(mock_image_pull.not_called)
|
self.assertTrue(mock_image_pull.not_called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_pull')
|
@patch('zun.compute.api.API.image_pull')
|
||||||
def test_image_pull_conflict(self, mock_image_pull):
|
def test_image_pull_conflict(self, mock_image_pull):
|
||||||
mock_image_pull.side_effect = lambda x, y: y
|
mock_image_pull.side_effect = lambda x, y: y
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ class TestImageController(api_base.FunctionalTest):
|
|||||||
params=params, content_type='application/json')
|
params=params, content_type='application/json')
|
||||||
self.assertTrue(mock_image_pull.not_called)
|
self.assertTrue(mock_image_pull.not_called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_pull')
|
@patch('zun.compute.api.API.image_pull')
|
||||||
def test_pull_image_set_project_id_and_user_id(
|
def test_pull_image_set_project_id_and_user_id(
|
||||||
self, mock_image_pull):
|
self, mock_image_pull):
|
||||||
def _create_side_effect(cnxt, image):
|
def _create_side_effect(cnxt, image):
|
||||||
@ -82,7 +82,7 @@ class TestImageController(api_base.FunctionalTest):
|
|||||||
params=params,
|
params=params,
|
||||||
content_type='application/json')
|
content_type='application/json')
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_pull')
|
@patch('zun.compute.api.API.image_pull')
|
||||||
def test_image_pull_with_tag(self, mock_image_pull):
|
def test_image_pull_with_tag(self, mock_image_pull):
|
||||||
mock_image_pull.side_effect = lambda x, y: y
|
mock_image_pull.side_effect = lambda x, y: y
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ class TestImageController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(202, response.status_int)
|
self.assertEqual(202, response.status_int)
|
||||||
self.assertTrue(mock_image_pull.called)
|
self.assertTrue(mock_image_pull.called)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_show')
|
@patch('zun.compute.api.API.image_show')
|
||||||
@patch('zun.objects.Image.list')
|
@patch('zun.objects.Image.list')
|
||||||
def test_get_all_images(self, mock_image_list, mock_image_show):
|
def test_get_all_images(self, mock_image_list, mock_image_show):
|
||||||
test_image = utils.get_test_image()
|
test_image = utils.get_test_image()
|
||||||
@ -113,7 +113,7 @@ class TestImageController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(test_image['uuid'],
|
self.assertEqual(test_image['uuid'],
|
||||||
actual_images[0].get('uuid'))
|
actual_images[0].get('uuid'))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_show')
|
@patch('zun.compute.api.API.image_show')
|
||||||
@patch('zun.objects.Image.list')
|
@patch('zun.objects.Image.list')
|
||||||
def test_get_all_images_with_pagination_marker(self, mock_image_list,
|
def test_get_all_images_with_pagination_marker(self, mock_image_list,
|
||||||
mock_image_show):
|
mock_image_show):
|
||||||
@ -136,7 +136,7 @@ class TestImageController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(image_list[-1].uuid,
|
self.assertEqual(image_list[-1].uuid,
|
||||||
actual_images[0].get('uuid'))
|
actual_images[0].get('uuid'))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_show')
|
@patch('zun.compute.api.API.image_show')
|
||||||
@patch('zun.objects.Image.list')
|
@patch('zun.objects.Image.list')
|
||||||
def test_get_all_images_with_exception(self, mock_image_list,
|
def test_get_all_images_with_exception(self, mock_image_list,
|
||||||
mock_image_show):
|
mock_image_show):
|
||||||
@ -156,28 +156,28 @@ class TestImageController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(test_image['uuid'],
|
self.assertEqual(test_image['uuid'],
|
||||||
actual_images[0].get('uuid'))
|
actual_images[0].get('uuid'))
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_search_image(self, mock_image_search):
|
def test_search_image(self, mock_image_search):
|
||||||
mock_image_search.return_value = {'name': 'redis', 'stars': 2000}
|
mock_image_search.return_value = {'name': 'redis', 'stars': 2000}
|
||||||
response = self.app.get('/v1/images/redis/search/')
|
response = self.app.get('/v1/images/redis/search/')
|
||||||
self.assertEqual(200, response.status_int)
|
self.assertEqual(200, response.status_int)
|
||||||
mock_image_search.assert_called_once_with(
|
mock_image_search.assert_called_once_with(
|
||||||
mock.ANY, 'redis', exact_match=False)
|
mock.ANY, 'redis', False)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_search_image_with_tag(self, mock_image_search):
|
def test_search_image_with_tag(self, mock_image_search):
|
||||||
mock_image_search.return_value = {'name': 'redis', 'stars': 2000}
|
mock_image_search.return_value = {'name': 'redis', 'stars': 2000}
|
||||||
response = self.app.get('/v1/images/redis:test/search/')
|
response = self.app.get('/v1/images/redis:test/search/')
|
||||||
self.assertEqual(200, response.status_int)
|
self.assertEqual(200, response.status_int)
|
||||||
mock_image_search.assert_called_once_with(
|
mock_image_search.assert_called_once_with(
|
||||||
mock.ANY, 'redis:test', exact_match=False)
|
mock.ANY, 'redis:test', False)
|
||||||
|
|
||||||
@patch('zun.compute.rpcapi.API.image_search')
|
@patch('zun.compute.api.API.image_search')
|
||||||
def test_search_image_not_found(self, mock_image_search):
|
def test_search_image_not_found(self, mock_image_search):
|
||||||
mock_image_search.side_effect = exception.ImageNotFound
|
mock_image_search.side_effect = exception.ImageNotFound
|
||||||
self.assertRaises(AppError, self.app.get, '/v1/images/redis/search/')
|
self.assertRaises(AppError, self.app.get, '/v1/images/redis/search/')
|
||||||
mock_image_search.assert_called_once_with(
|
mock_image_search.assert_called_once_with(
|
||||||
mock.ANY, 'redis', exact_match=False)
|
mock.ANY, 'redis', False)
|
||||||
|
|
||||||
|
|
||||||
class TestImageEnforcement(api_base.FunctionalTest):
|
class TestImageEnforcement(api_base.FunctionalTest):
|
||||||
|
@ -96,3 +96,13 @@ class ContextTestCase(base.TestCase):
|
|||||||
def test_request_context_sets_is_admin(self):
|
def test_request_context_sets_is_admin(self):
|
||||||
ctxt = zun_context.get_admin_context()
|
ctxt = zun_context.get_admin_context()
|
||||||
self.assertTrue(ctxt.is_admin)
|
self.assertTrue(ctxt.is_admin)
|
||||||
|
|
||||||
|
def test_request_context_elevated(self):
|
||||||
|
ctx = self._create_context(is_admin=False, roles=['Member'])
|
||||||
|
|
||||||
|
self.assertFalse(ctx.is_admin)
|
||||||
|
admin_ctxt = ctx.elevated()
|
||||||
|
self.assertTrue(admin_ctxt.is_admin)
|
||||||
|
self.assertIn('admin', admin_ctxt.roles)
|
||||||
|
self.assertFalse(ctx.is_admin)
|
||||||
|
self.assertNotIn('admin', ctx.roles)
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
from docker import errors
|
from docker import errors
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from zun import conf
|
||||||
from zun.container.docker.driver import DockerDriver
|
from zun.container.docker.driver import DockerDriver
|
||||||
from zun.container.docker.driver import NovaDockerDriver
|
from zun.container.docker.driver import NovaDockerDriver
|
||||||
from zun.container.docker import utils as docker_utils
|
from zun.container.docker import utils as docker_utils
|
||||||
@ -432,14 +433,16 @@ class TestNovaDockerDriver(base.DriverTestCase):
|
|||||||
mock_ensure_active.return_value = True
|
mock_ensure_active.return_value = True
|
||||||
mock_find_container_by_server_name.return_value = \
|
mock_find_container_by_server_name.return_value = \
|
||||||
'test_container_name_id'
|
'test_container_name_id'
|
||||||
mock_container = mock.MagicMock()
|
db_container = db_utils.create_test_container(context=self.context,
|
||||||
|
host=conf.CONF.host)
|
||||||
|
mock_container = mock.MagicMock(**db_container)
|
||||||
result_sandbox_id = self.driver.create_sandbox(self.context,
|
result_sandbox_id = self.driver.create_sandbox(self.context,
|
||||||
mock_container)
|
mock_container)
|
||||||
mock_get_sandbox_name.assert_called_once_with(mock_container)
|
mock_get_sandbox_name.assert_called_once_with(mock_container)
|
||||||
nova_client_instance.create_server.assert_called_once_with(
|
nova_client_instance.create_server.assert_called_once_with(
|
||||||
name='test_sanbox_name', image='kubernetes/pause',
|
name='test_sanbox_name', image='kubernetes/pause',
|
||||||
flavor='m1.small', key_name=None,
|
flavor='m1.small', key_name=None,
|
||||||
nics='auto')
|
nics='auto', availability_zone=':{0}:'.format(conf.CONF.host))
|
||||||
mock_ensure_active.assert_called_once_with(nova_client_instance,
|
mock_ensure_active.assert_called_once_with(nova_client_instance,
|
||||||
'server_instance')
|
'server_instance')
|
||||||
mock_find_container_by_server_name.assert_called_once_with(
|
mock_find_container_by_server_name.assert_called_once_with(
|
||||||
|
@ -27,6 +27,105 @@ from zun.tests.unit.db.utils import FakeEtcdMultipleResult
|
|||||||
from zun.tests.unit.db.utils import FakeEtcdResult
|
from zun.tests.unit.db.utils import FakeEtcdResult
|
||||||
|
|
||||||
|
|
||||||
|
class DbZunServiceTestCase(base.DbTestCase):
|
||||||
|
|
||||||
|
def test_create_zun_service(self):
|
||||||
|
utils.create_test_zun_service()
|
||||||
|
|
||||||
|
def test_create_zun_service_failure_for_dup(self):
|
||||||
|
utils.create_test_zun_service()
|
||||||
|
self.assertRaises(exception.ZunServiceAlreadyExists,
|
||||||
|
utils.create_test_zun_service)
|
||||||
|
|
||||||
|
def test_get_zun_service(self):
|
||||||
|
ms = utils.create_test_zun_service()
|
||||||
|
res = self.dbapi.get_zun_service(
|
||||||
|
ms['host'], ms['binary'])
|
||||||
|
self.assertEqual(ms.id, res.id)
|
||||||
|
|
||||||
|
def test_get_zun_service_failure(self):
|
||||||
|
utils.create_test_zun_service()
|
||||||
|
res = self.dbapi.get_zun_service(
|
||||||
|
'fakehost1', 'fake-bin1')
|
||||||
|
self.assertIsNone(res)
|
||||||
|
|
||||||
|
def test_update_zun_service(self):
|
||||||
|
ms = utils.create_test_zun_service()
|
||||||
|
d2 = True
|
||||||
|
update = {'disabled': d2}
|
||||||
|
ms1 = self.dbapi.update_zun_service(ms['host'], ms['binary'], update)
|
||||||
|
self.assertEqual(ms['id'], ms1['id'])
|
||||||
|
self.assertEqual(d2, ms1['disabled'])
|
||||||
|
res = self.dbapi.get_zun_service(
|
||||||
|
'fakehost', 'fake-bin')
|
||||||
|
self.assertEqual(ms1['id'], res['id'])
|
||||||
|
self.assertEqual(d2, res['disabled'])
|
||||||
|
|
||||||
|
def test_update_zun_service_failure(self):
|
||||||
|
fake_update = {'fake_field': 'fake_value'}
|
||||||
|
self.assertRaises(exception.ZunServiceNotFound,
|
||||||
|
self.dbapi.update_zun_service,
|
||||||
|
'fakehost1', 'fake-bin1', fake_update)
|
||||||
|
|
||||||
|
def test_destroy_zun_service(self):
|
||||||
|
ms = utils.create_test_zun_service()
|
||||||
|
res = self.dbapi.get_zun_service(
|
||||||
|
'fakehost', 'fake-bin')
|
||||||
|
self.assertEqual(res['id'], ms['id'])
|
||||||
|
self.dbapi.destroy_zun_service(ms['host'], ms['binary'])
|
||||||
|
res = self.dbapi.get_zun_service(
|
||||||
|
'fakehost', 'fake-bin')
|
||||||
|
self.assertIsNone(res)
|
||||||
|
|
||||||
|
def test_destroy_zun_service_failure(self):
|
||||||
|
self.assertRaises(exception.ZunServiceNotFound,
|
||||||
|
self.dbapi.destroy_zun_service,
|
||||||
|
'fakehostsssss', 'fakessss-bin1')
|
||||||
|
|
||||||
|
def test_get_zun_service_list(self):
|
||||||
|
fake_ms_params = {
|
||||||
|
'report_count': 1010,
|
||||||
|
'host': 'FakeHost',
|
||||||
|
'binary': 'FakeBin',
|
||||||
|
'disabled': False,
|
||||||
|
'disabled_reason': 'FakeReason'
|
||||||
|
}
|
||||||
|
utils.create_test_zun_service(**fake_ms_params)
|
||||||
|
res = self.dbapi.get_zun_service_list()
|
||||||
|
self.assertEqual(1, len(res))
|
||||||
|
res = res[0]
|
||||||
|
for k, v in fake_ms_params.items():
|
||||||
|
self.assertEqual(res[k], v)
|
||||||
|
|
||||||
|
fake_ms_params['binary'] = 'FakeBin1'
|
||||||
|
fake_ms_params['disabled'] = True
|
||||||
|
utils.create_test_zun_service(**fake_ms_params)
|
||||||
|
res = self.dbapi.get_zun_service_list(disabled=True)
|
||||||
|
self.assertEqual(1, len(res))
|
||||||
|
res = res[0]
|
||||||
|
for k, v in fake_ms_params.items():
|
||||||
|
self.assertEqual(res[k], v)
|
||||||
|
|
||||||
|
def test_list_zun_service_by_binary(self):
|
||||||
|
fake_ms_params = {
|
||||||
|
'report_count': 1010,
|
||||||
|
'host': 'FakeHost',
|
||||||
|
'binary': 'FakeBin',
|
||||||
|
'disabled': False,
|
||||||
|
'disabled_reason': 'FakeReason'
|
||||||
|
}
|
||||||
|
utils.create_test_zun_service(**fake_ms_params)
|
||||||
|
res = self.dbapi.list_zun_service_by_binary(
|
||||||
|
binary=fake_ms_params['binary'])
|
||||||
|
self.assertEqual(1, len(res))
|
||||||
|
res = res[0]
|
||||||
|
for k, v in fake_ms_params.items():
|
||||||
|
self.assertEqual(res[k], v)
|
||||||
|
|
||||||
|
res = self.dbapi.list_zun_service_by_binary(binary='none')
|
||||||
|
self.assertEqual(0, len(res))
|
||||||
|
|
||||||
|
|
||||||
class EtcdDbZunServiceTestCase(base.DbTestCase):
|
class EtcdDbZunServiceTestCase(base.DbTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -56,6 +56,16 @@ class TestZunServiceObject(base.DbTestCase):
|
|||||||
self.assertIsInstance(services[0], objects.ZunService)
|
self.assertIsInstance(services[0], objects.ZunService)
|
||||||
self.assertEqual(self.context, services[0]._context)
|
self.assertEqual(self.context, services[0]._context)
|
||||||
|
|
||||||
|
def test_list_by_binary(self):
|
||||||
|
with mock.patch.object(self.dbapi, 'list_zun_service_by_binary',
|
||||||
|
autospec=True) as mock_service_list:
|
||||||
|
mock_service_list.return_value = [self.fake_zun_service]
|
||||||
|
services = objects.ZunService.list_by_binary(self.context, 'bin')
|
||||||
|
self.assertEqual(1, mock_service_list.call_count)
|
||||||
|
self.assertThat(services, HasLength(1))
|
||||||
|
self.assertIsInstance(services[0], objects.ZunService)
|
||||||
|
self.assertEqual(self.context, services[0]._context)
|
||||||
|
|
||||||
def test_create(self):
|
def test_create(self):
|
||||||
with mock.patch.object(self.dbapi, 'create_zun_service',
|
with mock.patch.object(self.dbapi, 'create_zun_service',
|
||||||
autospec=True) as mock_create_zun_service:
|
autospec=True) as mock_create_zun_service:
|
||||||
|
0
zun/tests/unit/scheduler/__init__.py
Normal file
0
zun/tests/unit/scheduler/__init__.py
Normal file
19
zun/tests/unit/scheduler/fake_scheduler.py
Normal file
19
zun/tests/unit/scheduler/fake_scheduler.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# 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 zun.scheduler import driver
|
||||||
|
|
||||||
|
|
||||||
|
class FakeScheduler(driver.Scheduler):
|
||||||
|
|
||||||
|
def select_destinations(self, context, containers):
|
||||||
|
return []
|
61
zun/tests/unit/scheduler/test_chance_scheduler.py
Normal file
61
zun/tests/unit/scheduler/test_chance_scheduler.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# 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 zun.common import exception
|
||||||
|
from zun import objects
|
||||||
|
from zun.scheduler import chance_scheduler
|
||||||
|
from zun.tests import base
|
||||||
|
from zun.tests.unit.db import utils
|
||||||
|
|
||||||
|
|
||||||
|
class ChanceSchedulerTestCase(base.TestCase):
|
||||||
|
"""Test case for Chance Scheduler."""
|
||||||
|
|
||||||
|
driver_cls = chance_scheduler.ChanceScheduler
|
||||||
|
|
||||||
|
@mock.patch.object(driver_cls, 'hosts_up')
|
||||||
|
@mock.patch('random.choice')
|
||||||
|
def test_select_destinations(self, mock_random_choice, mock_hosts_up):
|
||||||
|
all_hosts = ['host1', 'host2', 'host3', 'host4']
|
||||||
|
|
||||||
|
def _return_hosts(*args, **kwargs):
|
||||||
|
return all_hosts
|
||||||
|
|
||||||
|
mock_random_choice.side_effect = ['host3']
|
||||||
|
mock_hosts_up.side_effect = _return_hosts
|
||||||
|
|
||||||
|
test_container = utils.get_test_container()
|
||||||
|
containers = [objects.Container(self.context, **test_container)]
|
||||||
|
dests = self.driver_cls().select_destinations(self.context, containers)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(dests))
|
||||||
|
(host, node) = (dests[0]['host'], dests[0]['nodename'])
|
||||||
|
self.assertEqual('host3', host)
|
||||||
|
self.assertIsNone(node)
|
||||||
|
|
||||||
|
calls = [mock.call(all_hosts)]
|
||||||
|
self.assertEqual(calls, mock_random_choice.call_args_list)
|
||||||
|
|
||||||
|
@mock.patch.object(driver_cls, 'hosts_up')
|
||||||
|
def test_select_destinations_no_valid_host(self, mock_hosts_up):
|
||||||
|
|
||||||
|
def _return_no_host(*args, **kwargs):
|
||||||
|
return []
|
||||||
|
|
||||||
|
mock_hosts_up.side_effect = _return_no_host
|
||||||
|
test_container = utils.get_test_container()
|
||||||
|
containers = [objects.Container(self.context, **test_container)]
|
||||||
|
self.assertRaises(exception.NoValidHost,
|
||||||
|
self.driver_cls().select_destinations, self.context,
|
||||||
|
containers)
|
47
zun/tests/unit/scheduler/test_client.py
Normal file
47
zun/tests/unit/scheduler/test_client.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# 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 oslo_config import cfg
|
||||||
|
|
||||||
|
from zun.scheduler import chance_scheduler
|
||||||
|
from zun.scheduler import client as scheduler_client
|
||||||
|
from zun.tests import base
|
||||||
|
from zun.tests.unit.scheduler import fake_scheduler
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class SchedulerClientTestCase(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(SchedulerClientTestCase, self).setUp()
|
||||||
|
self.client_cls = scheduler_client.SchedulerClient
|
||||||
|
self.client = self.client_cls()
|
||||||
|
|
||||||
|
def test_init_using_default_schedulerdriver(self):
|
||||||
|
driver = self.client_cls().driver
|
||||||
|
self.assertIsInstance(driver, chance_scheduler.ChanceScheduler)
|
||||||
|
|
||||||
|
def test_init_using_custom_schedulerdriver(self):
|
||||||
|
CONF.set_override('driver', 'fake_scheduler', group='scheduler')
|
||||||
|
driver = self.client_cls().driver
|
||||||
|
self.assertIsInstance(driver, fake_scheduler.FakeScheduler)
|
||||||
|
|
||||||
|
@mock.patch('zun.scheduler.chance_scheduler.ChanceScheduler'
|
||||||
|
'.select_destinations')
|
||||||
|
def test_select_destinations(self, mock_select_destinations):
|
||||||
|
fake_args = ['ctxt', 'fake_containers']
|
||||||
|
self.client.select_destinations(*fake_args)
|
||||||
|
mock_select_destinations.assert_called_once_with(*fake_args)
|
48
zun/tests/unit/scheduler/test_scheduler.py
Normal file
48
zun/tests/unit/scheduler/test_scheduler.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
"""
|
||||||
|
Tests For Scheduler
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from zun import objects
|
||||||
|
from zun.tests import base
|
||||||
|
from zun.tests.unit.scheduler import fake_scheduler
|
||||||
|
|
||||||
|
|
||||||
|
class SchedulerTestCase(base.TestCase):
|
||||||
|
"""Test case for base scheduler driver class."""
|
||||||
|
|
||||||
|
driver_cls = fake_scheduler.FakeScheduler
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(SchedulerTestCase, self).setUp()
|
||||||
|
self.driver = self.driver_cls()
|
||||||
|
|
||||||
|
@mock.patch('zun.objects.ZunService.list_by_binary')
|
||||||
|
@mock.patch('zun.api.servicegroup.ServiceGroup.service_is_up')
|
||||||
|
def test_hosts_up(self, mock_service_is_up, mock_list_by_binary):
|
||||||
|
service1 = objects.ZunService(host='host1')
|
||||||
|
service2 = objects.ZunService(host='host2')
|
||||||
|
services = [service1, service2]
|
||||||
|
|
||||||
|
mock_list_by_binary.return_value = services
|
||||||
|
mock_service_is_up.side_effect = [False, True]
|
||||||
|
|
||||||
|
result = self.driver.hosts_up(self.context)
|
||||||
|
self.assertEqual(result, ['host2'])
|
||||||
|
|
||||||
|
mock_list_by_binary.assert_called_once_with(self.context,
|
||||||
|
'zun-compute')
|
||||||
|
calls = [mock.call(service1), mock.call(service2)]
|
||||||
|
self.assertEqual(calls, mock_service_is_up.call_args_list)
|
Loading…
Reference in New Issue
Block a user