
* Make test-setup.sh compatible with mysql8. Starting from mysql version 8 it's not possible to create a user implictly when using GRANT. * Fix lower constraint for python3.8. See: https://bugs.launchpad.net/nova/+bug/1886298 * Fix pep8 failure Change-Id: I46a8c67a4d5ff97d03db097c17864061befe501c
1333 lines
58 KiB
Python
1333 lines
58 KiB
Python
# Copyright 2013 UnitedStack Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import shlex
|
|
|
|
from neutronclient.common import exceptions as n_exc
|
|
from oslo_log import log as logging
|
|
from oslo_utils import strutils
|
|
from oslo_utils import uuidutils
|
|
import pecan
|
|
|
|
from zun.api.controllers import base
|
|
from zun.api.controllers import link
|
|
from zun.api.controllers.v1 import collection
|
|
from zun.api.controllers.v1.schemas import containers as schema
|
|
from zun.api.controllers.v1.views import actions_view
|
|
from zun.api.controllers.v1.views import containers_view as view
|
|
from zun.api import utils as api_utils
|
|
from zun.api import validation
|
|
from zun.common import consts
|
|
from zun.common import context as zun_context
|
|
from zun.common import exception
|
|
from zun.common.i18n import _
|
|
from zun.common import name_generator
|
|
from zun.common.policies import container as policies
|
|
from zun.common import policy
|
|
from zun.common import quota
|
|
from zun.common import utils
|
|
import zun.conf
|
|
from zun.network import model as network_model
|
|
from zun.network import neutron
|
|
from zun import objects
|
|
from zun.pci import request as pci_request
|
|
from zun.volume import cinder_api as cinder
|
|
|
|
CONF = zun.conf.CONF
|
|
LOG = logging.getLogger(__name__)
|
|
QUOTAS = quota.QUOTAS
|
|
|
|
|
|
def check_policy_on_container(container, action):
|
|
context = pecan.request.context
|
|
policy.enforce(context, action, container, action=action)
|
|
|
|
|
|
class ContainerCollection(collection.Collection):
|
|
"""API representation of a collection of containers."""
|
|
|
|
fields = {
|
|
'containers',
|
|
'next'
|
|
}
|
|
|
|
"""A list containing containers objects"""
|
|
|
|
def __init__(self, **kwargs):
|
|
super(ContainerCollection, self).__init__(**kwargs)
|
|
self._type = 'containers'
|
|
|
|
@staticmethod
|
|
def convert_with_links(rpc_containers, limit, url=None,
|
|
expand=False, **kwargs):
|
|
context = pecan.request.context
|
|
collection = ContainerCollection()
|
|
collection.containers = \
|
|
[view.format_container(context, url, p)
|
|
for p in rpc_containers]
|
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
|
return collection
|
|
|
|
|
|
class ContainersActionsController(base.Controller):
|
|
"""Controller for Container Actions."""
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def get_all(self, container_ident, **kwargs):
|
|
"""Retrieve a list of container actions."""
|
|
context = pecan.request.context
|
|
policy.enforce(context, "container:actions",
|
|
action="container:actions")
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
actions_raw = objects.ContainerAction.get_by_container_uuid(
|
|
context, container.uuid)
|
|
actions = [actions_view.format_action(a) for a in actions_raw]
|
|
|
|
return {"containerActions": actions}
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def get_one(self, container_ident, request_ident, **kwargs):
|
|
"""Retrieve information about the action."""
|
|
|
|
context = pecan.request.context
|
|
policy.enforce(context, "container:actions",
|
|
action="container:actions")
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
action = objects.ContainerAction.get_by_request_id(
|
|
context, container.uuid, request_ident)
|
|
|
|
if action is None:
|
|
raise exception.ResourceNotFound(name="Action", id=request_ident)
|
|
|
|
action_id = action.id
|
|
action = actions_view.format_action(action)
|
|
show_traceback = False
|
|
if policy.enforce(context, "container:action:events",
|
|
do_raise=False, action="container:action:events"):
|
|
show_traceback = True
|
|
|
|
events_raw = objects.ContainerActionEvent.get_by_action(context,
|
|
action_id)
|
|
action['events'] = [actions_view.format_event(evt, show_traceback)
|
|
for evt in events_raw]
|
|
return action
|
|
|
|
|
|
class ContainersController(base.Controller):
|
|
"""Controller for Containers."""
|
|
|
|
_custom_actions = {
|
|
'start': ['POST'],
|
|
'stop': ['POST'],
|
|
'reboot': ['POST'],
|
|
'rebuild': ['POST'],
|
|
'pause': ['POST'],
|
|
'unpause': ['POST'],
|
|
'logs': ['GET'],
|
|
'execute': ['POST'],
|
|
'execute_resize': ['POST'],
|
|
'kill': ['POST'],
|
|
'rename': ['POST'],
|
|
'attach': ['GET'],
|
|
'resize': ['POST'],
|
|
'resize_container': ['POST'],
|
|
'top': ['GET'],
|
|
'get_archive': ['GET'],
|
|
'put_archive': ['POST'],
|
|
'stats': ['GET'],
|
|
'commit': ['POST'],
|
|
'add_security_group': ['POST'],
|
|
'network_detach': ['POST'],
|
|
'network_attach': ['POST'],
|
|
'network_list': ['GET'],
|
|
'remove_security_group': ['POST']
|
|
}
|
|
|
|
container_actions = ContainersActionsController()
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def get_all(self, **kwargs):
|
|
"""Retrieve a list of containers.
|
|
|
|
"""
|
|
context = pecan.request.context
|
|
policy.enforce(context, "container:get_all",
|
|
action="container:get_all")
|
|
return self._get_containers_collection(**kwargs)
|
|
|
|
def _get_containers_collection(self, **kwargs):
|
|
context = pecan.request.context
|
|
if utils.is_all_projects(kwargs):
|
|
policy.enforce(context, "container:get_all_all_projects",
|
|
action="container:get_all_all_projects")
|
|
context.all_projects = True
|
|
kwargs.pop('all_projects', None)
|
|
limit = api_utils.validate_limit(kwargs.pop('limit', None))
|
|
sort_dir = api_utils.validate_sort_dir(kwargs.pop('sort_dir', 'asc'))
|
|
sort_key = kwargs.pop('sort_key', 'id')
|
|
resource_url = kwargs.pop('resource_url', None)
|
|
expand = kwargs.pop('expand', None)
|
|
|
|
container_allowed_filters = ['name', 'image', 'project_id', 'user_id',
|
|
'memory', 'host', 'task_state', 'status',
|
|
'auto_remove']
|
|
filters = {}
|
|
for filter_key in container_allowed_filters:
|
|
if filter_key in kwargs:
|
|
policy_action = policies.CONTAINER % ('get_one:' + filter_key)
|
|
context.can(policy_action, might_not_exist=True)
|
|
filter_value = kwargs.pop(filter_key)
|
|
filters[filter_key] = filter_value
|
|
marker_obj = None
|
|
marker = kwargs.pop('marker', None)
|
|
if marker:
|
|
marker_obj = objects.Container.get_by_uuid(context,
|
|
marker)
|
|
if kwargs:
|
|
unknown_params = [str(k) for k in kwargs]
|
|
msg = _("Unknown parameters: %s") % ", ".join(unknown_params)
|
|
raise exception.InvalidValue(msg)
|
|
|
|
containers = objects.Container.list(context,
|
|
limit,
|
|
marker_obj,
|
|
sort_key,
|
|
sort_dir,
|
|
filters=filters)
|
|
return ContainerCollection.convert_with_links(containers, limit,
|
|
url=resource_url,
|
|
expand=expand,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def get_one(self, container_ident, **kwargs):
|
|
"""Retrieve information about the given container.
|
|
|
|
:param container_ident: UUID or name of a container.
|
|
"""
|
|
context = pecan.request.context
|
|
if utils.is_all_projects(kwargs):
|
|
policy.enforce(context, "container:get_one_all_projects",
|
|
action="container:get_one_all_projects")
|
|
context.all_projects = True
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:get_one")
|
|
if container.host:
|
|
compute_api = pecan.request.compute_api
|
|
try:
|
|
container = compute_api.container_show(context, container)
|
|
except exception.ContainerHostNotUp as e:
|
|
LOG.error("Failed to get container details: %s", str(e))
|
|
|
|
return view.format_container(context, pecan.request.host_url,
|
|
container)
|
|
|
|
def _generate_name_for_container(self):
|
|
"""Generate a random name like: zeta-22-container."""
|
|
name_gen = name_generator.NameGenerator()
|
|
name = name_gen.generate()
|
|
return name + '-container'
|
|
|
|
@base.Controller.api_version("1.1", "1.19")
|
|
@pecan.expose('json')
|
|
@api_utils.enforce_content_types(['application/json'])
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_create)
|
|
@validation.validated(schema.legacy_container_create)
|
|
def post(self, run=False, **container_dict):
|
|
# NOTE(hongbin): We convert the representation of 'command' from
|
|
# string to list. For example:
|
|
# '"nginx" "-g" "daemon off;"' -> ["nginx", "-g", "daemon off;"]
|
|
command = container_dict.pop('command', None)
|
|
if command is not None:
|
|
if isinstance(command, str):
|
|
command = shlex.split(command)
|
|
container_dict['command'] = command
|
|
|
|
return self._do_post(run, **container_dict)
|
|
|
|
@base.Controller.api_version("1.20", "1.35") # noqa
|
|
@pecan.expose('json')
|
|
@api_utils.enforce_content_types(['application/json'])
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_create)
|
|
@validation.validated(schema.container_create)
|
|
def post(self, run=False, **container_dict): # noqa
|
|
interactive = container_dict.get('interactive')
|
|
if interactive is not None:
|
|
container_dict['tty'] = interactive
|
|
return self._do_post(run, **container_dict)
|
|
|
|
@base.Controller.api_version("1.36", "1.38") # noqa
|
|
@pecan.expose('json')
|
|
@api_utils.enforce_content_types(['application/json'])
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_create)
|
|
@validation.validated(schema.container_create)
|
|
def post(self, run=False, **container_dict): # noqa
|
|
return self._do_post(run, **container_dict)
|
|
|
|
@base.Controller.api_version("1.39", "1.39") # noqa
|
|
@pecan.expose('json')
|
|
@api_utils.enforce_content_types(['application/json'])
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_create)
|
|
@validation.validated(schema.container_create_v139)
|
|
def post(self, run=False, **container_dict): # noqa
|
|
return self._do_post(run, **container_dict)
|
|
|
|
@base.Controller.api_version("1.40") # noqa
|
|
@pecan.expose('json')
|
|
@api_utils.enforce_content_types(['application/json'])
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_create)
|
|
@validation.validated(schema.container_create_v140)
|
|
def post(self, run=False, **container_dict): # noqa
|
|
return self._do_post(run, **container_dict)
|
|
|
|
def _do_post(self, run=False, **container_dict):
|
|
"""Create or run a new container.
|
|
|
|
:param run: if true, starts the container
|
|
:param container_dict: a container within the request body.
|
|
"""
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
policy.enforce(context, "container:create",
|
|
action="container:create")
|
|
|
|
if container_dict.get('security_groups'):
|
|
# remove duplicate security_groups from list
|
|
container_dict['security_groups'] = list(set(
|
|
container_dict.get('security_groups')))
|
|
for index, sg in enumerate(container_dict['security_groups']):
|
|
security_group_id = self._check_security_group(context,
|
|
{'name': sg})
|
|
container_dict['security_groups'][index] = security_group_id
|
|
|
|
try:
|
|
run = strutils.bool_from_string(run, strict=True)
|
|
container_dict['interactive'] = strutils.bool_from_string(
|
|
container_dict.get('interactive', False), strict=True)
|
|
except ValueError:
|
|
bools = ', '.join(strutils.TRUE_STRINGS + strutils.FALSE_STRINGS)
|
|
raise exception.InvalidValue(_('Valid run or interactive '
|
|
'values are: %s') % bools)
|
|
|
|
# Check container quotas
|
|
self._check_container_quotas(context, container_dict)
|
|
|
|
auto_remove = container_dict.pop('auto_remove', None)
|
|
if auto_remove is not None:
|
|
api_utils.version_check('auto_remove', '1.3')
|
|
try:
|
|
container_dict['auto_remove'] = strutils.bool_from_string(
|
|
auto_remove, strict=True)
|
|
except ValueError:
|
|
bools = ', '.join(strutils.TRUE_STRINGS +
|
|
strutils.FALSE_STRINGS)
|
|
raise exception.InvalidValue(_('Valid auto_remove '
|
|
'values are: %s') % bools)
|
|
|
|
runtime = container_dict.pop('runtime', None)
|
|
if runtime is not None:
|
|
api_utils.version_check('runtime', '1.5')
|
|
policy.enforce(context, "container:create:runtime",
|
|
action="container:create:runtime")
|
|
container_dict['runtime'] = runtime
|
|
|
|
hostname = container_dict.pop('hostname', None)
|
|
if hostname is not None:
|
|
api_utils.version_check('hostname', '1.9')
|
|
container_dict['hostname'] = hostname
|
|
|
|
nets = container_dict.get('nets', [])
|
|
requested_networks = utils.build_requested_networks(context, nets)
|
|
pci_req = self._create_pci_requests_for_sriov_ports(context,
|
|
requested_networks)
|
|
|
|
healthcheck = container_dict.pop('healthcheck', {})
|
|
if healthcheck:
|
|
api_utils.version_check('healthcheck', '1.22')
|
|
healthcheck['test'] = healthcheck.pop('cmd', '')
|
|
container_dict['healthcheck'] = healthcheck
|
|
|
|
mounts = container_dict.pop('mounts', [])
|
|
if mounts:
|
|
api_utils.version_check('mounts', '1.11')
|
|
|
|
cpu_policy = container_dict.pop('cpu_policy', None)
|
|
container_dict['cpu_policy'] = cpu_policy
|
|
|
|
privileged = container_dict.pop('privileged', None)
|
|
if privileged is not None:
|
|
api_utils.version_check('privileged', '1.21')
|
|
policy.enforce(context, "container:create:privileged",
|
|
action="container:create:privileged")
|
|
try:
|
|
container_dict['privileged'] = strutils.bool_from_string(
|
|
privileged, strict=True)
|
|
except ValueError:
|
|
bools = ', '.join(strutils.TRUE_STRINGS +
|
|
strutils.FALSE_STRINGS)
|
|
raise exception.InvalidValue(_('Valid privileged values '
|
|
'are: %s') % bools)
|
|
|
|
# Valiadtion accepts 'None' so need to convert it to None
|
|
container_dict['image_driver'] = api_utils.string_or_none(
|
|
container_dict.get('image_driver'))
|
|
if not container_dict['image_driver']:
|
|
container_dict['image_driver'] = CONF.default_image_driver
|
|
if container_dict.get('image_pull_policy'):
|
|
policy.enforce(context, "container:create:image_pull_policy",
|
|
action="container:create:image_pull_policy")
|
|
|
|
container_dict['project_id'] = context.project_id
|
|
container_dict['user_id'] = context.user_id
|
|
name = container_dict.get('name') or \
|
|
self._generate_name_for_container()
|
|
container_dict['name'] = name
|
|
self._set_default_resource_limit(container_dict)
|
|
if container_dict.get('restart_policy'):
|
|
utils.check_for_restart_policy(container_dict)
|
|
|
|
exposed_ports = container_dict.pop('exposed_ports', None)
|
|
if exposed_ports is not None:
|
|
api_utils.version_check('exposed_ports', '1.24')
|
|
exposed_ports = utils.build_exposed_ports(exposed_ports)
|
|
container_dict['exposed_ports'] = exposed_ports
|
|
|
|
registry = container_dict.pop('registry', None)
|
|
if registry:
|
|
api_utils.version_check('registry', '1.31')
|
|
registry = utils.get_registry(registry)
|
|
container_dict['registry_id'] = registry.id
|
|
|
|
requested_host = container_dict.pop('host', None)
|
|
if requested_host:
|
|
policy.enforce(context, "container:create:requested_destination",
|
|
action="container:create:requested_destination")
|
|
|
|
container_dict['status'] = consts.CREATING
|
|
extra_spec = {}
|
|
extra_spec['hints'] = container_dict.get('hints', None)
|
|
extra_spec['pci_requests'] = pci_req
|
|
extra_spec['availability_zone'] = container_dict.get(
|
|
'availability_zone')
|
|
extra_spec['requested_host'] = requested_host
|
|
new_container = objects.Container(context, **container_dict)
|
|
new_container.create(context)
|
|
|
|
kwargs = {}
|
|
kwargs['extra_spec'] = extra_spec
|
|
kwargs['requested_networks'] = requested_networks
|
|
kwargs['requested_volumes'] = (
|
|
self._build_requested_volumes(context, new_container, mounts))
|
|
if pci_req.requests:
|
|
kwargs['pci_requests'] = pci_req
|
|
kwargs['run'] = run
|
|
compute_api.container_create(context, new_container, **kwargs)
|
|
# Set the HTTP Location Header
|
|
pecan.response.location = link.build_url('containers',
|
|
new_container.uuid)
|
|
pecan.response.status = 202
|
|
return view.format_container(context, pecan.request.host_url,
|
|
new_container)
|
|
|
|
def _check_container_quotas(self, context, container_delta_dict,
|
|
update_container=False):
|
|
deltas = {
|
|
'containers': 0 if update_container else 1,
|
|
'cpu': container_delta_dict.get('cpu', 0),
|
|
'memory': container_delta_dict.get('memory', 0),
|
|
'disk': container_delta_dict.get('disk', 0)
|
|
}
|
|
|
|
def _check_deltas(context, deltas):
|
|
"""Check usage deltas against quota limits.
|
|
|
|
This does QUOTAS.count() followed by a QUOTAS.limit_check()
|
|
using the provided deltas.
|
|
|
|
:param context: The request context, for access check.
|
|
:param deltas: A dict of {resource_name: delta, ...} to check
|
|
against the quota limits.
|
|
"""
|
|
check_kwargs = {}
|
|
count_as_dict = {}
|
|
project_id = context.project_id
|
|
for res_name, res_delta in deltas.items():
|
|
# TODO(kiennt): Apply count_as_dict method, query count usage
|
|
# once rather than count each resource.
|
|
count_as_dict[res_name] = QUOTAS.count(context, res_name,
|
|
project_id)
|
|
total = None
|
|
try:
|
|
if isinstance(count_as_dict[res_name], int):
|
|
total = count_as_dict[res_name] + int(res_delta)
|
|
else:
|
|
total = float(count_as_dict[res_name]) + \
|
|
float(res_delta)
|
|
except TypeError as e:
|
|
raise e
|
|
check_kwargs[res_name] = total
|
|
QUOTAS.limit_check(context, project_id, **check_kwargs)
|
|
|
|
_check_deltas(context, deltas)
|
|
|
|
def _set_default_resource_limit(self, container_dict):
|
|
# NOTE(kiennt): Default disk size will be set later.
|
|
container_dict['disk'] = container_dict.get('disk')
|
|
container_dict['memory'] = container_dict.get(
|
|
'memory', CONF.default_memory)
|
|
container_dict['memory'] = str(container_dict['memory'])
|
|
container_dict['cpu'] = container_dict.get(
|
|
'cpu', CONF.default_cpu)
|
|
|
|
def _create_pci_requests_for_sriov_ports(self, context,
|
|
requested_networks):
|
|
pci_requests = objects.ContainerPCIRequests(requests=[])
|
|
if not requested_networks:
|
|
return pci_requests
|
|
|
|
neutron_api = neutron.NeutronAPI(context)
|
|
for request_net in requested_networks:
|
|
phynet_name = None
|
|
vnic_type = network_model.VNIC_TYPE_NORMAL
|
|
|
|
if request_net.get('port'):
|
|
vnic_type, phynet_name = self._get_port_vnic_info(
|
|
context, neutron_api, request_net['port'])
|
|
pci_request_id = None
|
|
if vnic_type in network_model.VNIC_TYPES_SRIOV:
|
|
spec = {pci_request.PCI_NET_TAG: phynet_name}
|
|
dev_type = pci_request.DEVICE_TYPE_FOR_VNIC_TYPE.get(vnic_type)
|
|
if dev_type:
|
|
spec[pci_request.PCI_DEVICE_TYPE_TAG] = dev_type
|
|
request = objects.ContainerPCIRequest(
|
|
count=1,
|
|
spec=[spec],
|
|
request_id=uuidutils.generate_uuid())
|
|
pci_requests.requests.append(request)
|
|
pci_request_id = request.request_id
|
|
request_net['pci_request_id'] = pci_request_id
|
|
return pci_requests
|
|
|
|
def _get_port_vnic_info(self, context, neutron, port_id):
|
|
"""Retrieve port vnic info
|
|
|
|
Invoked with a valid port_id.
|
|
Return vnic type and the attached physical network name.
|
|
"""
|
|
phynet_name = None
|
|
port = self._show_port(context, port_id, neutron_client=neutron,
|
|
fields=['binding:vnic_type', 'network_id'])
|
|
vnic_type = port.get('binding:vnic_type',
|
|
network_model.VNIC_TYPE_NORMAL)
|
|
if vnic_type in network_model.VNIC_TYPES_SRIOV:
|
|
net_id = port['network_id']
|
|
phynet_name = self._get_phynet_info(context, net_id)
|
|
return vnic_type, phynet_name
|
|
|
|
def _show_port(self, context, port_id, neutron_client=None, fields=None):
|
|
"""Return the port for the client given the port id.
|
|
|
|
:param context: Request context.
|
|
:param port_id: The id of port to be queried.
|
|
:param neutron_client: A neutron client.
|
|
:param fields: The condition fields to query port data.
|
|
:returns: A dict of port data.
|
|
e.g. {'port_id': 'abcd', 'fixed_ip_address': '1.2.3.4'}
|
|
"""
|
|
if not neutron_client:
|
|
neutron_client = neutron.NeutronAPI(context)
|
|
if fields:
|
|
result = neutron_client.show_port(port_id, fields=fields)
|
|
else:
|
|
result = neutron_client.show_port(port_id)
|
|
return result.get('port')
|
|
|
|
def _get_phynet_info(self, context, net_id):
|
|
# NOTE(hongbin): Use admin context here because non-admin users are
|
|
# unable to retrieve provider:* attributes.
|
|
admin_context = zun_context.get_admin_context()
|
|
neutron_api = neutron.NeutronAPI(admin_context)
|
|
network = neutron_api.show_network(
|
|
net_id, fields='provider:physical_network')
|
|
net = network.get('network')
|
|
phynet_name = net.get('provider:physical_network')
|
|
return phynet_name
|
|
|
|
def _build_requested_volumes(self, context, container, mounts):
|
|
cinder_api = cinder.CinderAPI(context)
|
|
requested_volumes = {container.uuid: []}
|
|
for mount in mounts:
|
|
volume_dict = {
|
|
'cinder_volume_id': None,
|
|
'auto_remove': False,
|
|
'contents': None,
|
|
'user_id': context.user_id,
|
|
'project_id': context.project_id,
|
|
}
|
|
volume_type = mount.get('type', 'volume')
|
|
if volume_type == 'volume':
|
|
if mount.get('source'):
|
|
volume = cinder_api.search_volume(mount['source'])
|
|
cinder_api.ensure_volume_usable(volume)
|
|
volume_dict['cinder_volume_id'] = volume.id
|
|
volume_dict['volume_provider'] = 'cinder'
|
|
elif mount.get('size'):
|
|
volume = cinder_api.create_volume(mount['size'])
|
|
cinder_api.ensure_volume_usable(volume)
|
|
volume_dict['cinder_volume_id'] = volume.id
|
|
volume_dict['volume_provider'] = 'cinder'
|
|
volume_dict['auto_remove'] = True
|
|
elif volume_type == 'bind':
|
|
volume_dict['contents'] = mount.pop('source', '')
|
|
volume_dict['volume_provider'] = 'local'
|
|
|
|
volume_object = objects.Volume(context, **volume_dict)
|
|
volume_object.create(context)
|
|
volume_dict['volume_id'] = volume_object.id
|
|
volume_dict['container_path'] = mount['destination']
|
|
|
|
volmapp = objects.VolumeMapping(context, **volume_dict)
|
|
requested_volumes[container.uuid].append(volmapp)
|
|
|
|
return requested_volumes
|
|
|
|
def _check_security_group(self, context, security_group):
|
|
neutron_api = neutron.NeutronAPI(context)
|
|
try:
|
|
return neutron_api.find_resourceid_by_name_or_id(
|
|
'security_group', security_group['name'], context.project_id)
|
|
except n_exc.NeutronClientNoUniqueMatch:
|
|
msg = _("Multiple security group matches found for name "
|
|
"%(name)s, use an ID to be more specific.") % {
|
|
'name': security_group['name']}
|
|
raise exception.Conflict(msg)
|
|
except n_exc.NeutronClientException as e:
|
|
if e.status_code == 404:
|
|
msg = _("Security group %(name)s not found.") % {
|
|
'name': security_group['name']}
|
|
raise exception.InvalidValue(msg)
|
|
else:
|
|
raise
|
|
|
|
@base.Controller.api_version("1.1", "1.14")
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validated(schema.add_security_group)
|
|
def add_security_group(self, container_ident, **security_group):
|
|
"""Add security group to an existing container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
:param security_group: security_group to be added to container.
|
|
"""
|
|
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(
|
|
container.as_dict(), "container:add_security_group")
|
|
utils.validate_container_state(container, 'add_security_group')
|
|
|
|
# check if security group already presnt in container
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
security_group_id = self._check_security_group(context, security_group)
|
|
if security_group_id in container.security_groups:
|
|
msg = _("Security group %(id)s has been added to container.") % {
|
|
'id': security_group_id}
|
|
raise exception.InvalidValue(msg)
|
|
compute_api.add_security_group(context, container,
|
|
security_group_id)
|
|
pecan.response.status = 202
|
|
|
|
@base.Controller.api_version("1.1", "1.14")
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validated(schema.remove_security_group)
|
|
def remove_security_group(self, container_ident, **security_group):
|
|
"""Remove security group from an existing container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
:param security_group: security_group to be removed from container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(
|
|
container.as_dict(), "container:remove_security_group")
|
|
utils.validate_container_state(container, 'remove_security_group')
|
|
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
security_group_id = self._check_security_group(context, security_group)
|
|
if security_group_id not in container.security_groups:
|
|
msg = _("Security group %(id)s was not added to container.") % {
|
|
'id': security_group_id}
|
|
raise exception.InvalidValue(msg)
|
|
compute_api.remove_security_group(context, container,
|
|
security_group_id)
|
|
pecan.response.status = 202
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validated(schema.container_update)
|
|
def patch(self, container_ident, **patch):
|
|
"""Update an existing container.
|
|
|
|
:param container_ident: UUID or name of a container.
|
|
:param patch: a json PATCH document to apply to this container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
context = pecan.request.context
|
|
container_deltas = {}
|
|
check_policy_on_container(container.as_dict(), "container:update")
|
|
utils.validate_container_state(container, 'update')
|
|
if 'memory' in patch:
|
|
container_deltas['memory'] = \
|
|
int(patch['memory']) - int(container.memory)
|
|
patch['memory'] = str(patch['memory'])
|
|
if 'cpu' in patch:
|
|
patch['cpu'] = float(patch['cpu'])
|
|
container_deltas['cpu'] = patch['cpu'] - container.cpu
|
|
if 'name' in patch:
|
|
patch['name'] = str(patch['name'])
|
|
|
|
if 'memory' not in patch and 'cpu' not in patch:
|
|
for field, patch_val in patch.items():
|
|
if getattr(container, field) != patch_val:
|
|
setattr(container, field, patch_val)
|
|
container.save(context)
|
|
else:
|
|
# Check container quotas
|
|
self._check_container_quotas(context, container_deltas,
|
|
update_container=True)
|
|
compute_api = pecan.request.compute_api
|
|
container = compute_api.container_update(context, container, patch)
|
|
return view.format_container(context, pecan.request.host_url,
|
|
container)
|
|
|
|
@base.Controller.api_version("1.1", "1.13")
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_rename)
|
|
def rename(self, container_ident, name):
|
|
"""Rename an existing container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
:param name: a new name for this container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:rename")
|
|
if container.name == name:
|
|
raise exception.Conflict('The new name for the container is the '
|
|
'same as the old name.')
|
|
container.name = name
|
|
context = pecan.request.context
|
|
container.save(context)
|
|
return view.format_container(context, pecan.request.host_url,
|
|
container)
|
|
|
|
@base.Controller.api_version("1.19")
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validated(schema.container_update)
|
|
def resize_container(self, container_ident, **kwargs):
|
|
"""Resize an existing container.
|
|
|
|
:param container_ident: UUID or name of a container.
|
|
:param kwargs: cpu/memory to be updated.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(),
|
|
"container:resize_container")
|
|
utils.validate_container_state(container, 'resize_container')
|
|
if 'memory' in kwargs:
|
|
kwargs['memory'] = str(kwargs['memory'])
|
|
if 'cpu' in kwargs:
|
|
kwargs['cpu'] = float(kwargs['cpu'])
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.resize_container(context, container, kwargs)
|
|
pecan.response.status = 202
|
|
return view.format_container(context, pecan.request.host_url,
|
|
container)
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_delete)
|
|
def delete(self, container_ident, force=False, **kwargs):
|
|
"""Delete a container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
:param force: If True, allow to force delete the container.
|
|
"""
|
|
context = pecan.request.context
|
|
if utils.is_all_projects(kwargs):
|
|
policy.enforce(context, "container:delete_all_projects",
|
|
action="container:delete_all_projects")
|
|
context.all_projects = True
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:delete")
|
|
stop = kwargs.pop('stop', False)
|
|
try:
|
|
force = strutils.bool_from_string(force, strict=True)
|
|
stop = strutils.bool_from_string(stop, strict=True)
|
|
except ValueError:
|
|
bools = ', '.join(strutils.TRUE_STRINGS + strutils.FALSE_STRINGS)
|
|
raise exception.InvalidValue(_('Valid force or stop values '
|
|
'are: %s') % bools)
|
|
compute_api = pecan.request.compute_api
|
|
if not force and not stop:
|
|
utils.validate_container_state(container, 'delete')
|
|
elif force and not stop:
|
|
api_utils.version_check('force', '1.7')
|
|
policy.enforce(context, "container:delete_force",
|
|
action="container:delete_force")
|
|
utils.validate_container_state(container, 'delete_force')
|
|
elif stop:
|
|
api_utils.version_check('stop', '1.12')
|
|
utils.validate_container_state(container,
|
|
'delete_after_stop')
|
|
if container.status == consts.RUNNING:
|
|
check_policy_on_container(container.as_dict(),
|
|
"container:stop")
|
|
LOG.debug('Calling compute.container_stop with %s '
|
|
'before delete', container.uuid)
|
|
compute_api.container_stop(context, container, 10)
|
|
container.status = consts.DELETING
|
|
if container.host:
|
|
compute_api.container_delete(context, container, force)
|
|
else:
|
|
container.destroy(context)
|
|
pecan.response.status = 204
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def rebuild(self, container_ident, **kwargs):
|
|
"""Rebuild container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:rebuild")
|
|
utils.validate_container_state(container, 'rebuild')
|
|
if kwargs.get('image'):
|
|
container.image = kwargs.get('image')
|
|
if kwargs.get('image_driver'):
|
|
utils.validate_image_driver(kwargs.get('image_driver'))
|
|
container.image_driver = kwargs.get('image_driver')
|
|
LOG.debug('Calling compute.container_rebuild with %s',
|
|
container.uuid)
|
|
run = True if container.status == consts.RUNNING else False
|
|
context = pecan.request.context
|
|
container.status = consts.REBUILDING
|
|
container.save(context)
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.container_rebuild(context, container, run)
|
|
pecan.response.status = 202
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def start(self, container_ident, **kwargs):
|
|
"""Start container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:start")
|
|
utils.validate_container_state(container, 'start')
|
|
LOG.debug('Calling compute.container_start with %s',
|
|
container.uuid)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.container_start(context, container)
|
|
pecan.response.status = 202
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_stop)
|
|
def stop(self, container_ident, timeout=None, **kwargs):
|
|
"""Stop container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:stop")
|
|
utils.validate_container_state(container, 'stop')
|
|
LOG.debug('Calling compute.container_stop with %s',
|
|
container.uuid)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.container_stop(context, container, timeout)
|
|
pecan.response.status = 202
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_reboot)
|
|
def reboot(self, container_ident, timeout=None, **kwargs):
|
|
"""Reboot container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:reboot")
|
|
utils.validate_container_state(container, 'reboot')
|
|
LOG.debug('Calling compute.container_reboot with %s',
|
|
container.uuid)
|
|
context = pecan.request.context
|
|
container.status = consts.RESTARTING
|
|
container.save(context)
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.container_reboot(context, container, timeout)
|
|
pecan.response.status = 202
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def pause(self, container_ident, **kwargs):
|
|
"""Pause container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:pause")
|
|
utils.validate_container_state(container, 'pause')
|
|
LOG.debug('Calling compute.container_pause with %s',
|
|
container.uuid)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.container_pause(context, container)
|
|
pecan.response.status = 202
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def unpause(self, container_ident, **kwargs):
|
|
"""Unpause container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:unpause")
|
|
utils.validate_container_state(container, 'unpause')
|
|
LOG.debug('Calling compute.container_unpause with %s',
|
|
container.uuid)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.container_unpause(context, container)
|
|
pecan.response.status = 202
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_logs)
|
|
def logs(self, container_ident, stdout=True, stderr=True,
|
|
timestamps=False, tail='all', since=None):
|
|
"""Get logs of the given container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
:param stdout: Get standard output if True.
|
|
:param stderr: Get standard error if True.
|
|
:param timestamps: Show timestamps.
|
|
:param tail: Number of lines to show from the end of the logs.
|
|
(default: get all logs)
|
|
:param since: Show logs since a given datetime or
|
|
integer epoch (in seconds).
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:logs")
|
|
utils.validate_container_state(container, 'logs')
|
|
try:
|
|
stdout = strutils.bool_from_string(stdout, strict=True)
|
|
stderr = strutils.bool_from_string(stderr, strict=True)
|
|
timestamps = strutils.bool_from_string(timestamps, strict=True)
|
|
except ValueError:
|
|
bools = ', '.join(strutils.TRUE_STRINGS + strutils.FALSE_STRINGS)
|
|
raise exception.InvalidValue(_('Valid stdout, stderr and '
|
|
'timestamps values are: %s')
|
|
% bools)
|
|
LOG.debug('Calling compute.container_logs with %s', container.uuid)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
return compute_api.container_logs(context, container, stdout, stderr,
|
|
timestamps, tail, since)
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request,
|
|
schema.query_param_execute_command)
|
|
def execute(self, container_ident, run=True, interactive=False, **kwargs):
|
|
"""Execute command in a running container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
:param run: If True, execute run.
|
|
:param interactive: Keep STDIN open and allocate a
|
|
pseudo-TTY for interactive.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:execute")
|
|
utils.validate_container_state(container, 'execute')
|
|
try:
|
|
run = strutils.bool_from_string(run, strict=True)
|
|
interactive = strutils.bool_from_string(interactive, strict=True)
|
|
except ValueError:
|
|
bools = ', '.join(strutils.TRUE_STRINGS + strutils.FALSE_STRINGS)
|
|
raise exception.InvalidValue(_('Valid run or interactive '
|
|
'values are: %s') % bools)
|
|
LOG.debug('Calling compute.container_exec with %(uuid)s command '
|
|
'%(command)s',
|
|
{'uuid': container.uuid, 'command': kwargs['command']})
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
return compute_api.container_exec(context, container,
|
|
kwargs['command'],
|
|
run, interactive)
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request,
|
|
schema.query_param_execute_resize)
|
|
def execute_resize(self, container_ident, exec_id, **kwargs):
|
|
"""Resize the tty session used by the exec
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
:param exec_id: ID of a exec.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(),
|
|
"container:execute_resize")
|
|
utils.validate_container_state(container, 'execute_resize')
|
|
LOG.debug('Calling tty resize used by exec %s', exec_id)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
return compute_api.container_exec_resize(
|
|
context, container, exec_id, kwargs.get('h', None),
|
|
kwargs.get('w', None))
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validated(schema.query_param_signal)
|
|
def kill(self, container_ident, **kwargs):
|
|
"""Kill a running container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:kill")
|
|
utils.validate_container_state(container, 'kill')
|
|
LOG.debug('Calling compute.container_kill with %(uuid)s '
|
|
'signal %(signal)s',
|
|
{'uuid': container.uuid,
|
|
'signal': kwargs.get('signal')})
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.container_kill(context, container, kwargs.get('signal'))
|
|
pecan.response.status = 202
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def attach(self, container_ident):
|
|
"""Attach to a running container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:attach")
|
|
utils.validate_container_state(container, 'attach')
|
|
LOG.debug('Checking the status for attach with %s', container.uuid)
|
|
if container.interactive:
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
url = compute_api.container_attach(context, container)
|
|
return url
|
|
msg = _("Container doesn't support to be attached, "
|
|
"please check the interactive set properly")
|
|
raise exception.NoInteractiveFlag(msg=msg)
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_resize)
|
|
def resize(self, container_ident, **kwargs):
|
|
"""Resize container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:resize")
|
|
utils.validate_container_state(container, 'resize')
|
|
LOG.debug('Calling tty resize with %s ', container.uuid)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.container_resize(context, container, kwargs.get('h', None),
|
|
kwargs.get('w', None))
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_top)
|
|
def top(self, container_ident, ps_args=None):
|
|
"""Display the running processes inside the container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
:param ps_args: The args of the ps command.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:top")
|
|
utils.validate_container_state(container, 'top')
|
|
LOG.debug('Calling compute.container_top with %s', container.uuid)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
return compute_api.container_top(context, container, ps_args)
|
|
|
|
@base.Controller.api_version("1.1", "1.24")
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def get_archive(self, container_ident, **kwargs):
|
|
"""Retrieve a file/folder from a container
|
|
|
|
Retrieve a file or folder from a container in the
|
|
form of a tar archive.
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
kwargs['encode_data'] = False
|
|
return self._get_archive(container_ident, **kwargs)
|
|
|
|
@base.Controller.api_version("1.25") # noqa
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def get_archive(self, container_ident, **kwargs): # noqa
|
|
"""Retrieve a file/folder from a container
|
|
|
|
Retrieve a file or folder from a container in the
|
|
form of a tar archive.
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
kwargs['encode_data'] = True
|
|
return self._get_archive(container_ident, **kwargs)
|
|
|
|
def _get_archive(self, container_ident, **kwargs):
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:get_archive")
|
|
utils.validate_container_state(container, 'get_archive')
|
|
LOG.debug('Calling compute.container_get_archive with %(uuid)s '
|
|
'path %(path)s',
|
|
{'uuid': container.uuid, 'path': kwargs['path']})
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
data, stat = compute_api.container_get_archive(
|
|
context, container, kwargs['path'], kwargs['encode_data'])
|
|
return {"data": data, "stat": stat}
|
|
|
|
@base.Controller.api_version("1.1", "1.24")
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def put_archive(self, container_ident, **kwargs):
|
|
"""Insert a file/folder to container.
|
|
|
|
Insert a file or folder to an existing container using
|
|
a tar archive as source.
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
kwargs['decode_data'] = False
|
|
self._put_archive(container_ident, **kwargs)
|
|
|
|
@base.Controller.api_version("1.25") # noqa
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def put_archive(self, container_ident, **kwargs): # noqa
|
|
"""Insert a file/folder to container.
|
|
|
|
Insert a file or folder to an existing container using
|
|
a tar archive as source.
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
kwargs['decode_data'] = True
|
|
self._put_archive(container_ident, **kwargs)
|
|
|
|
def _put_archive(self, container_ident, **kwargs):
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:put_archive")
|
|
utils.validate_container_state(container, 'put_archive')
|
|
LOG.debug('Calling compute.container_put_archive with %(uuid)s '
|
|
'path %(path)s',
|
|
{'uuid': container.uuid, 'path': kwargs['path']})
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
compute_api.container_put_archive(
|
|
context, container, kwargs['path'], kwargs['data'],
|
|
kwargs['decode_data'])
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def stats(self, container_ident):
|
|
"""Display stats snapshot of the container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:stats")
|
|
utils.validate_container_state(container, 'stats')
|
|
LOG.debug('Calling compute.container_stats with %s', container.uuid)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
return compute_api.container_stats(context, container)
|
|
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.query_param_commit)
|
|
def commit(self, container_ident, **kwargs):
|
|
"""Create a new image from a container's changes.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(), "container:commit")
|
|
utils.validate_container_state(container, 'commit')
|
|
LOG.debug('Calling compute.container_commit %s ', container.uuid)
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
pecan.response.status = 202
|
|
return compute_api.container_commit(context, container,
|
|
kwargs.get('repository', None),
|
|
kwargs.get('tag', None))
|
|
|
|
@base.Controller.api_version("1.6")
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.network_detach)
|
|
def network_detach(self, container_ident, **kwargs):
|
|
"""Detach a network from the container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(),
|
|
"container:network_detach")
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
neutron_api = neutron.NeutronAPI(context)
|
|
if kwargs.get('port'):
|
|
port = neutron_api.get_neutron_port(kwargs['port'])
|
|
net_id = port['network_id']
|
|
if (net_id not in container.addresses or
|
|
port['id'] not in [a['port']
|
|
for a in container.addresses[net_id]]):
|
|
raise exception.Invalid(_(
|
|
"Port '%(port)s' is not attached to container "
|
|
"'%(container)s'.") %
|
|
{"port": kwargs.get('port'),
|
|
"container": container_ident})
|
|
else:
|
|
network = neutron_api.get_neutron_network(kwargs.get('network'))
|
|
net_id = network['id']
|
|
if net_id not in container.addresses:
|
|
raise exception.Invalid(_(
|
|
"Network '%(network)s' is not attached to container "
|
|
"'%(container)s'.") %
|
|
{"network": kwargs.get('network'),
|
|
"container": container_ident})
|
|
compute_api.network_detach(context, container, net_id)
|
|
pecan.response.status = 202
|
|
|
|
@base.Controller.api_version("1.8")
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
@validation.validate_query_param(pecan.request, schema.network_attach)
|
|
@validation.validated(schema.network_attach)
|
|
def network_attach(self, container_ident, **kwargs):
|
|
"""Attach a network to the container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
check_policy_on_container(container.as_dict(),
|
|
"container:network_attach")
|
|
context = pecan.request.context
|
|
compute_api = pecan.request.compute_api
|
|
requested_networks = utils.build_requested_networks(context, [kwargs])
|
|
if requested_networks[0]['network'] in container.addresses:
|
|
if kwargs.get('port'):
|
|
raise exception.Invalid(_(
|
|
"Cannot attach port '%(port)s' to container "
|
|
"'%(container)s' because another port on the "
|
|
"same network is already attached to this container.") %
|
|
{"port": kwargs.get('port'),
|
|
"container": container_ident})
|
|
else:
|
|
raise exception.Invalid(_(
|
|
"Network '%(network)s' is already connected to "
|
|
"container '%(container)s'.") %
|
|
{"network": kwargs.get('network'),
|
|
"container": container_ident})
|
|
compute_api.network_attach(context, container, requested_networks[0])
|
|
|
|
@base.Controller.api_version("1.13", "1.17")
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def network_list(self, container_ident):
|
|
"""Retrieve a list of networks of the container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
container_networks = self._get_container_networks_legacy(container)
|
|
return {'networks': container_networks}
|
|
|
|
def _get_container_networks_legacy(self, container):
|
|
container_networks = []
|
|
for net_id, net_infos in container.addresses.items():
|
|
for net_info in net_infos:
|
|
container_networks.append({
|
|
'net_id': net_id,
|
|
'subnet_id': net_info.get("subnet_id"),
|
|
'port_id': net_info.get("port"),
|
|
'version': net_info.get("version"),
|
|
'ip_address': net_info.get("addr")
|
|
})
|
|
return container_networks
|
|
|
|
@base.Controller.api_version("1.18") # noqa
|
|
@pecan.expose('json')
|
|
@exception.wrap_pecan_controller_exception
|
|
def network_list(self, container_ident): # noqa
|
|
"""Retrieve a list of networks of the container.
|
|
|
|
:param container_ident: UUID or Name of a container.
|
|
"""
|
|
container = api_utils.get_resource('Container', container_ident)
|
|
container_networks = self._get_container_networks(container)
|
|
return {'networks': container_networks}
|
|
|
|
def _get_container_networks(self, container):
|
|
container_networks = []
|
|
for net_id, net_infos in container.addresses.items():
|
|
addresses = {}
|
|
for net_info in net_infos:
|
|
port_id = net_info["port"]
|
|
addresses.setdefault(port_id, [])
|
|
addresses[port_id].append({
|
|
'subnet_id': net_info.get("subnet_id"),
|
|
'version': net_info.get("version"),
|
|
'ip_address': net_info.get("addr")
|
|
})
|
|
for port_id, fixed_ips in addresses.items():
|
|
container_networks.append({
|
|
'net_id': net_id,
|
|
'port_id': port_id,
|
|
'fixed_ips': fixed_ips,
|
|
})
|
|
return container_networks
|