Remove '/os-cells' REST APIs
Drop support for the os-cells REST APIs, which are part of the cells v1 feature which has been deprecated since Pike. This API now returns a 410 response for all routes. Unit tests are removed and the functional API sample tests are just asserting the 410 response now. The latter are also expanded to cover APIs that weren't previously tested. The API sample docs are left intact since the API reference still builds from those and can be considered more or less branchless, so people looking at the API reference can apply it to older deployments of nova before os-cells was removed. A release note added for previous cells v1 removals is amended to note this additional change. Part of blueprint remove-cells-v1 Change-Id: Iddb519008515f591cf1d884872a5887afbe766f2 Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
parent
e25d59078e
commit
fb14f24cc3
@ -73,7 +73,6 @@ limited to some maximum microversion.
|
||||
.. include:: os-security-group-default-rules.inc
|
||||
.. include:: os-security-group-rules.inc
|
||||
.. include:: os-hosts.inc
|
||||
.. include:: os-cells.inc
|
||||
|
||||
=============
|
||||
Obsolete APIs
|
||||
@ -89,3 +88,4 @@ Compute API in the past, but no longer exist.
|
||||
.. include:: os-fixed-ips.inc
|
||||
.. include:: os-floating-ips-bulk.inc
|
||||
.. include:: os-floating-ip-dns.inc
|
||||
.. include:: os-cells.inc
|
||||
|
@ -1,8 +1,4 @@
|
||||
.. -*- rst -*-
|
||||
.. needs:parameter_verification
|
||||
.. needs:example_verification
|
||||
.. needs:body_verification
|
||||
|
||||
|
||||
==============================
|
||||
Cells (os-cells, capacities)
|
||||
@ -11,10 +7,13 @@
|
||||
Adds neighbor cells, lists neighbor cells, and shows the capabilities of
|
||||
the local cell. By default, only administrators can manage cells.
|
||||
|
||||
.. warning:: These APIs refer to a Cells v1 deployment which was deprecated
|
||||
in the 16.0.0 Pike release. These are not used with Cells v2
|
||||
which is required beginning with the 15.0.0 Ocata release where all Nova
|
||||
deployments consist of at least one Cells v2 cell.
|
||||
.. warning::
|
||||
|
||||
These APIs refer to a Cells v1 deployment which was deprecated in the 16.0.0
|
||||
Pike release. These are not used with Cells v2 which is required beginning
|
||||
with the 15.0.0 Ocata release where all Nova deployments consist of at least
|
||||
one Cells v2 cell.
|
||||
They were removed in the 20.0.0 Train release.
|
||||
|
||||
List Cells
|
||||
==========
|
||||
@ -26,7 +25,7 @@ Lists cells.
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
NotImplemented(501)
|
||||
gone(410), notImplemented(501)
|
||||
|
||||
Request
|
||||
-------
|
||||
@ -52,10 +51,10 @@ Create Cell
|
||||
|
||||
Create a new cell.
|
||||
|
||||
Normal response code: 200
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
NotImplemented(501)
|
||||
gone(410), notImplemented(501)
|
||||
|
||||
Capacities
|
||||
==========
|
||||
@ -64,8 +63,10 @@ Capacities
|
||||
|
||||
Retrieve capacities.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
NotImplemented(501)
|
||||
gone(410), notImplemented(501)
|
||||
|
||||
List Cells With Details
|
||||
=======================
|
||||
@ -77,7 +78,7 @@ Lists cells with details of capabilities.
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
NotImplemented(501)
|
||||
gone(410), notImplemented(501)
|
||||
|
||||
Request
|
||||
-------
|
||||
@ -94,10 +95,10 @@ Info For This Cell
|
||||
|
||||
Retrieve info about the current cell.
|
||||
|
||||
Normal response code: 200
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
NotImplemented(501)
|
||||
gone(410), notImplemented(501)
|
||||
|
||||
Show Cell Data
|
||||
==============
|
||||
@ -109,7 +110,7 @@ Shows data for a cell.
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
itemNotFound(404), NotImplemented(501)
|
||||
itemNotFound(404), gone(410), notImplemented(501)
|
||||
|
||||
Request
|
||||
-------
|
||||
@ -129,15 +130,15 @@ Response
|
||||
Update a Cell
|
||||
=============
|
||||
|
||||
.. rest_method:: PUT /os-cells/{cell_od}
|
||||
.. rest_method:: PUT /os-cells/{cell_id}
|
||||
|
||||
|
||||
Update an existing cell.
|
||||
|
||||
Normal response code: 200
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
itemNotFound(404), NotImplemented(501)
|
||||
itemNotFound(404), gone(410), notImplemented(501)
|
||||
|
||||
Delete a Cell
|
||||
=============
|
||||
@ -146,10 +147,10 @@ Delete a Cell
|
||||
|
||||
Remove a cell.
|
||||
|
||||
Normal response code: 200
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
itemNotFound(404), NotImplemented(501)
|
||||
itemNotFound(404), gone(410), notImplemented(501)
|
||||
|
||||
Show Cell Capacities
|
||||
====================
|
||||
@ -158,10 +159,10 @@ Show Cell Capacities
|
||||
|
||||
Shows capacities for a cell.
|
||||
|
||||
Normal response codes: 200,501
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||
itemNotFound(404), NotImplemented(501)
|
||||
itemNotFound(404), gone(410), notImplemented(501)
|
||||
|
||||
Request
|
||||
-------
|
||||
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"cells": []
|
||||
}
|
@ -14,7 +14,6 @@
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import functools
|
||||
import itertools
|
||||
import re
|
||||
|
||||
@ -495,15 +494,6 @@ def get_flavor(context, flavor_id):
|
||||
raise exc.HTTPNotFound(explanation=error.format_message())
|
||||
|
||||
|
||||
def check_cells_enabled(function):
|
||||
@functools.wraps(function)
|
||||
def inner(*args, **kwargs):
|
||||
if not CONF.cells.enable:
|
||||
raise_feature_not_supported()
|
||||
return function(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
|
||||
def is_all_tenants(search_opts):
|
||||
"""Checks to see if the all_tenants flag is in search_opts
|
||||
|
||||
|
@ -14,289 +14,49 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""The cells extension."""
|
||||
|
||||
import oslo_messaging as messaging
|
||||
from oslo_utils import strutils
|
||||
import six
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack import common
|
||||
from nova.api.openstack.compute.schemas import cells
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api import validation
|
||||
from nova.cells import rpcapi as cells_rpcapi
|
||||
import nova.conf
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.policies import cells as cells_policies
|
||||
from nova import rpc
|
||||
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
|
||||
def _filter_keys(item, keys):
|
||||
"""Filters all model attributes except for keys
|
||||
item is a dict
|
||||
"""
|
||||
return {k: v for k, v in item.items() if k in keys}
|
||||
|
||||
|
||||
def _fixup_cell_info(cell_info, keys):
|
||||
"""If the transport_url is present in the cell, derive username,
|
||||
rpc_host, and rpc_port from it.
|
||||
"""
|
||||
|
||||
if 'transport_url' not in cell_info:
|
||||
return
|
||||
|
||||
# Disassemble the transport URL
|
||||
transport_url = cell_info.pop('transport_url')
|
||||
try:
|
||||
transport_url = rpc.get_transport_url(transport_url)
|
||||
except messaging.InvalidTransportURL:
|
||||
# Just go with None's
|
||||
for key in keys:
|
||||
cell_info.setdefault(key, None)
|
||||
return
|
||||
|
||||
if not transport_url.hosts:
|
||||
return
|
||||
|
||||
transport_host = transport_url.hosts[0]
|
||||
|
||||
transport_field_map = {'rpc_host': 'hostname', 'rpc_port': 'port'}
|
||||
for key in keys:
|
||||
if key in cell_info:
|
||||
continue
|
||||
|
||||
transport_field = transport_field_map.get(key, key)
|
||||
cell_info[key] = getattr(transport_host, transport_field)
|
||||
|
||||
|
||||
def _scrub_cell(cell, detail=False):
|
||||
keys = ['name', 'username', 'rpc_host', 'rpc_port']
|
||||
if detail:
|
||||
keys.append('capabilities')
|
||||
|
||||
cell_info = _filter_keys(cell, keys + ['transport_url'])
|
||||
_fixup_cell_info(cell_info, keys)
|
||||
cell_info['type'] = 'parent' if cell['is_parent'] else 'child'
|
||||
return cell_info
|
||||
|
||||
|
||||
class CellsController(wsgi.Controller):
|
||||
"""Controller for Cell resources."""
|
||||
"""(Removed) Controller for Cell resources.
|
||||
|
||||
def __init__(self):
|
||||
self.cells_rpcapi = cells_rpcapi.CellsAPI()
|
||||
This was removed during the Train release in favour of cells v2.
|
||||
"""
|
||||
|
||||
def _get_cells(self, ctxt, req, detail=False):
|
||||
"""Return all cells."""
|
||||
# Ask the CellsManager for the most recent data
|
||||
items = self.cells_rpcapi.get_cell_info_for_neighbors(ctxt)
|
||||
items = common.limited(items, req)
|
||||
items = [_scrub_cell(item, detail=detail) for item in items]
|
||||
return dict(cells=items)
|
||||
|
||||
@wsgi.expected_errors(501)
|
||||
@common.check_cells_enabled
|
||||
@wsgi.expected_errors(410)
|
||||
def index(self, req):
|
||||
"""Return all cells in brief."""
|
||||
ctxt = req.environ['nova.context']
|
||||
ctxt.can(cells_policies.BASE_POLICY_NAME)
|
||||
return self._get_cells(ctxt, req)
|
||||
raise exc.HTTPGone()
|
||||
|
||||
@wsgi.expected_errors(501)
|
||||
@common.check_cells_enabled
|
||||
@wsgi.expected_errors(410)
|
||||
def detail(self, req):
|
||||
"""Return all cells in detail."""
|
||||
ctxt = req.environ['nova.context']
|
||||
ctxt.can(cells_policies.BASE_POLICY_NAME)
|
||||
return self._get_cells(ctxt, req, detail=True)
|
||||
raise exc.HTTPGone()
|
||||
|
||||
@wsgi.expected_errors(501)
|
||||
@common.check_cells_enabled
|
||||
@wsgi.expected_errors(410)
|
||||
def info(self, req):
|
||||
"""Return name and capabilities for this cell."""
|
||||
context = req.environ['nova.context']
|
||||
context.can(cells_policies.BASE_POLICY_NAME)
|
||||
cell_capabs = {}
|
||||
my_caps = CONF.cells.capabilities
|
||||
for cap in my_caps:
|
||||
key, value = cap.split('=')
|
||||
cell_capabs[key] = value
|
||||
cell = {'name': CONF.cells.name,
|
||||
'type': 'self',
|
||||
'rpc_host': None,
|
||||
'rpc_port': 0,
|
||||
'username': None,
|
||||
'capabilities': cell_capabs}
|
||||
return dict(cell=cell)
|
||||
raise exc.HTTPGone()
|
||||
|
||||
@wsgi.expected_errors((404, 501))
|
||||
@common.check_cells_enabled
|
||||
@wsgi.expected_errors(410)
|
||||
def capacities(self, req, id=None):
|
||||
"""Return capacities for a given cell or all cells."""
|
||||
# TODO(kaushikc): return capacities as a part of cell info and
|
||||
# cells detail calls in v2.1, along with capabilities
|
||||
context = req.environ['nova.context']
|
||||
context.can(cells_policies.BASE_POLICY_NAME)
|
||||
try:
|
||||
capacities = self.cells_rpcapi.get_capacities(context,
|
||||
cell_name=id)
|
||||
except exception.CellNotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
raise exc.HTTPGone()
|
||||
|
||||
return dict(cell={"capacities": capacities})
|
||||
|
||||
@wsgi.expected_errors((404, 501))
|
||||
@common.check_cells_enabled
|
||||
@wsgi.expected_errors(410)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given cell name. 'id' is a cell name."""
|
||||
context = req.environ['nova.context']
|
||||
context.can(cells_policies.BASE_POLICY_NAME)
|
||||
try:
|
||||
cell = self.cells_rpcapi.cell_get(context, id)
|
||||
except exception.CellNotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
return dict(cell=_scrub_cell(cell))
|
||||
raise exc.HTTPGone()
|
||||
|
||||
# NOTE(gmann): Returns 200 for backwards compatibility but should be 204
|
||||
# as this operation complete the deletion of aggregate resource and return
|
||||
# no response body.
|
||||
@wsgi.expected_errors((403, 404, 501))
|
||||
@common.check_cells_enabled
|
||||
@wsgi.expected_errors(410)
|
||||
def delete(self, req, id):
|
||||
"""Delete a child or parent cell entry. 'id' is a cell name."""
|
||||
context = req.environ['nova.context']
|
||||
raise exc.HTTPGone()
|
||||
|
||||
context.can(cells_policies.POLICY_ROOT % "delete")
|
||||
|
||||
try:
|
||||
num_deleted = self.cells_rpcapi.cell_delete(context, id)
|
||||
except exception.CellsUpdateUnsupported as e:
|
||||
raise exc.HTTPForbidden(explanation=e.format_message())
|
||||
if num_deleted == 0:
|
||||
raise exc.HTTPNotFound(
|
||||
explanation=_("Cell %s doesn't exist.") % id)
|
||||
|
||||
def _normalize_cell(self, cell, existing=None):
|
||||
"""Normalize input cell data. Normalizations include:
|
||||
|
||||
* Converting cell['type'] to is_parent boolean.
|
||||
* Merging existing transport URL with transport information.
|
||||
"""
|
||||
|
||||
if 'name' in cell:
|
||||
cell['name'] = common.normalize_name(cell['name'])
|
||||
|
||||
# Start with the cell type conversion
|
||||
if 'type' in cell:
|
||||
cell['is_parent'] = cell.pop('type') == 'parent'
|
||||
# Avoid cell type being overwritten to 'child'
|
||||
elif existing:
|
||||
cell['is_parent'] = existing['is_parent']
|
||||
else:
|
||||
cell['is_parent'] = False
|
||||
|
||||
# Now we disassemble the existing transport URL...
|
||||
transport_url = existing.get('transport_url') if existing else None
|
||||
transport_url = rpc.get_transport_url(transport_url)
|
||||
|
||||
if 'rpc_virtual_host' in cell:
|
||||
transport_url.virtual_host = cell.pop('rpc_virtual_host')
|
||||
|
||||
if not transport_url.hosts:
|
||||
transport_url.hosts.append(messaging.TransportHost())
|
||||
transport_host = transport_url.hosts[0]
|
||||
if 'rpc_port' in cell:
|
||||
cell['rpc_port'] = int(cell['rpc_port'])
|
||||
# Copy over the input fields
|
||||
transport_field_map = {
|
||||
'username': 'username',
|
||||
'password': 'password',
|
||||
'hostname': 'rpc_host',
|
||||
'port': 'rpc_port',
|
||||
}
|
||||
for key, input_field in transport_field_map.items():
|
||||
# Only override the value if we're given an override
|
||||
if input_field in cell:
|
||||
setattr(transport_host, key, cell.pop(input_field))
|
||||
|
||||
# Now set the transport URL
|
||||
cell['transport_url'] = str(transport_url)
|
||||
|
||||
# NOTE(gmann): Returns 200 for backwards compatibility but should be 201
|
||||
# as this operation complete the creation of aggregates resource when
|
||||
# returning a response.
|
||||
@wsgi.expected_errors((400, 403, 501))
|
||||
@common.check_cells_enabled
|
||||
@validation.schema(cells.create_v20, '2.0', '2.0')
|
||||
@validation.schema(cells.create, '2.1')
|
||||
@wsgi.expected_errors(410)
|
||||
def create(self, req, body):
|
||||
"""Create a child cell entry."""
|
||||
context = req.environ['nova.context']
|
||||
raise exc.HTTPGone()
|
||||
|
||||
context.can(cells_policies.POLICY_ROOT % "create")
|
||||
|
||||
cell = body['cell']
|
||||
self._normalize_cell(cell)
|
||||
try:
|
||||
cell = self.cells_rpcapi.cell_create(context, cell)
|
||||
except exception.CellsUpdateUnsupported as e:
|
||||
raise exc.HTTPForbidden(explanation=e.format_message())
|
||||
return dict(cell=_scrub_cell(cell))
|
||||
|
||||
@wsgi.expected_errors((400, 403, 404, 501))
|
||||
@common.check_cells_enabled
|
||||
@validation.schema(cells.update_v20, '2.0', '2.0')
|
||||
@validation.schema(cells.update, '2.1')
|
||||
@wsgi.expected_errors(410)
|
||||
def update(self, req, id, body):
|
||||
"""Update a child cell entry. 'id' is the cell name to update."""
|
||||
context = req.environ['nova.context']
|
||||
raise exc.HTTPGone()
|
||||
|
||||
context.can(cells_policies.POLICY_ROOT % "update")
|
||||
|
||||
cell = body['cell']
|
||||
cell.pop('id', None)
|
||||
|
||||
try:
|
||||
# NOTE(Vek): There is a race condition here if multiple
|
||||
# callers are trying to update the cell
|
||||
# information simultaneously. Since this
|
||||
# operation is administrative in nature, and
|
||||
# will be going away in the future, I don't see
|
||||
# it as much of a problem...
|
||||
existing = self.cells_rpcapi.cell_get(context, id)
|
||||
except exception.CellNotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
self._normalize_cell(cell, existing)
|
||||
try:
|
||||
cell = self.cells_rpcapi.cell_update(context, id, cell)
|
||||
except exception.CellNotFound as e:
|
||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||
except exception.CellsUpdateUnsupported as e:
|
||||
raise exc.HTTPForbidden(explanation=e.format_message())
|
||||
return dict(cell=_scrub_cell(cell))
|
||||
|
||||
# NOTE(gmann): Returns 200 for backwards compatibility but should be 204
|
||||
# as this operation complete the sync instance info and return
|
||||
# no response body.
|
||||
@wsgi.expected_errors((400, 501))
|
||||
@common.check_cells_enabled
|
||||
@validation.schema(cells.sync_instances)
|
||||
@wsgi.expected_errors(410)
|
||||
def sync_instances(self, req, body):
|
||||
"""Tell all cells to sync instance info."""
|
||||
context = req.environ['nova.context']
|
||||
|
||||
context.can(cells_policies.POLICY_ROOT % "sync_instances")
|
||||
|
||||
project_id = body.pop('project_id', None)
|
||||
deleted = body.pop('deleted', False)
|
||||
updated_since = body.pop('updated_since', None)
|
||||
if isinstance(deleted, six.string_types):
|
||||
deleted = strutils.bool_from_string(deleted, strict=True)
|
||||
self.cells_rpcapi.sync_instances(context, project_id=project_id,
|
||||
updated_since=updated_since, deleted=deleted)
|
||||
raise exc.HTTPGone()
|
||||
|
@ -1,111 +0,0 @@
|
||||
# Copyright 2014 NEC Corporation. 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 copy
|
||||
|
||||
from nova.api.validation import parameter_types
|
||||
|
||||
|
||||
create = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'cell': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': parameter_types.cell_name,
|
||||
'type': {
|
||||
'type': 'string',
|
||||
'enum': ['parent', 'child'],
|
||||
},
|
||||
|
||||
# NOTE: In unparse_transport_url(), a url consists of the
|
||||
# following parameters:
|
||||
# "qpid://<username>:<password>@<rpc_host>:<rpc_port>/"
|
||||
# or
|
||||
# "rabbit://<username>:<password>@<rpc_host>:<rpc_port>/"
|
||||
# Then the url is stored into transport_url of cells table
|
||||
# which is defined with String(255).
|
||||
'username': {
|
||||
'type': 'string', 'maxLength': 255,
|
||||
'pattern': '^[a-zA-Z0-9-_]*$'
|
||||
},
|
||||
'password': {
|
||||
# Allow to specify any string for strong password.
|
||||
'type': 'string', 'maxLength': 255,
|
||||
},
|
||||
'rpc_host': parameter_types.hostname_or_ip_address,
|
||||
'rpc_port': parameter_types.tcp_udp_port,
|
||||
'rpc_virtual_host': parameter_types.hostname_or_ip_address,
|
||||
},
|
||||
'required': ['name'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['cell'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
|
||||
create_v20 = copy.deepcopy(create)
|
||||
create_v20['properties']['cell']['properties']['name'] = (parameter_types.
|
||||
cell_name_leading_trailing_spaces)
|
||||
|
||||
|
||||
update = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'cell': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': parameter_types.cell_name,
|
||||
'type': {
|
||||
'type': 'string',
|
||||
'enum': ['parent', 'child'],
|
||||
},
|
||||
'username': {
|
||||
'type': 'string', 'maxLength': 255,
|
||||
'pattern': '^[a-zA-Z0-9-_]*$'
|
||||
},
|
||||
'password': {
|
||||
'type': 'string', 'maxLength': 255,
|
||||
},
|
||||
'rpc_host': parameter_types.hostname_or_ip_address,
|
||||
'rpc_port': parameter_types.tcp_udp_port,
|
||||
'rpc_virtual_host': parameter_types.hostname_or_ip_address,
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['cell'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
|
||||
update_v20 = copy.deepcopy(create)
|
||||
update_v20['properties']['cell']['properties']['name'] = (parameter_types.
|
||||
cell_name_leading_trailing_spaces)
|
||||
|
||||
|
||||
sync_instances = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'project_id': parameter_types.project_id,
|
||||
'deleted': parameter_types.boolean,
|
||||
'updated_since': {
|
||||
'type': 'string',
|
||||
'format': 'date-time',
|
||||
},
|
||||
},
|
||||
'additionalProperties': False,
|
||||
}
|
@ -200,24 +200,6 @@ valid_az_name_leading_trailing_spaces_regex = ValidationRegex(
|
||||
"with at least one non space character"))
|
||||
|
||||
|
||||
valid_cell_name_regex = ValidationRegex(
|
||||
valid_name_regex_base % (
|
||||
_build_regex_range(ws=False, invert=True),
|
||||
_build_regex_range(exclude=['!', '.', '@']),
|
||||
_build_regex_range(ws=False, invert=True)),
|
||||
_("printable characters except !, ., @. "
|
||||
"Can not start or end with whitespace."))
|
||||
|
||||
|
||||
# cell's name disallow '!', '.' and '@'.
|
||||
valid_cell_name_leading_trailing_spaces_regex = ValidationRegex(
|
||||
valid_name_leading_trailing_spaces_regex_base % {
|
||||
'ws': _build_regex_range(exclude=['!', '.', '@']),
|
||||
'no_ws': _build_regex_range(ws=False, exclude=['!', '.', '@'])},
|
||||
_("printable characters except !, ., @, "
|
||||
"with at least one non space character"))
|
||||
|
||||
|
||||
valid_name_leading_trailing_spaces_regex = ValidationRegex(
|
||||
valid_name_leading_trailing_spaces_regex_base % {
|
||||
'ws': _build_regex_range(),
|
||||
@ -315,18 +297,6 @@ az_name_with_leading_trailing_spaces = {
|
||||
}
|
||||
|
||||
|
||||
cell_name = {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
'format': 'cell_name'
|
||||
}
|
||||
|
||||
|
||||
cell_name_leading_trailing_spaces = {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
'format': 'cell_name_with_leading_trailing_spaces'
|
||||
}
|
||||
|
||||
|
||||
name_with_leading_trailing_spaces = {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
'format': 'name_with_leading_trailing_spaces'
|
||||
@ -339,6 +309,7 @@ description = {
|
||||
}
|
||||
|
||||
|
||||
# TODO(stephenfin): This is no longer used and should be removed
|
||||
tcp_udp_port = {
|
||||
'type': ['integer', 'string'], 'pattern': '^[0-9]*$',
|
||||
'minimum': 0, 'maximum': 65535,
|
||||
|
@ -154,33 +154,6 @@ def _validate_az_name(instance):
|
||||
raise exception.InvalidName(reason=regex.reason)
|
||||
|
||||
|
||||
@jsonschema.FormatChecker.cls_checks('cell_name_with_leading_trailing_spaces',
|
||||
exception.InvalidName)
|
||||
def _validate_cell_name_with_leading_trailing_spaces(instance):
|
||||
regex = parameter_types.valid_cell_name_leading_trailing_spaces_regex
|
||||
try:
|
||||
if re.search(regex.regex, instance):
|
||||
return True
|
||||
except TypeError:
|
||||
# The name must be string type. If instance isn't string type, the
|
||||
# TypeError will be raised at here.
|
||||
pass
|
||||
raise exception.InvalidName(reason=regex.reason)
|
||||
|
||||
|
||||
@jsonschema.FormatChecker.cls_checks('cell_name', exception.InvalidName)
|
||||
def _validate_cell_name(instance):
|
||||
regex = parameter_types.valid_cell_name_regex
|
||||
try:
|
||||
if re.search(regex.regex, instance):
|
||||
return True
|
||||
except TypeError:
|
||||
# The name must be string type. If instance isn't string type, the
|
||||
# TypeError will be raised at here.
|
||||
pass
|
||||
raise exception.InvalidName(reason=regex.reason)
|
||||
|
||||
|
||||
def _soft_validate_additional_properties(validator,
|
||||
additional_properties_value,
|
||||
instance,
|
||||
|
@ -22,7 +22,6 @@ from nova.policies import attach_interfaces
|
||||
from nova.policies import availability_zone
|
||||
from nova.policies import baremetal_nodes
|
||||
from nova.policies import base
|
||||
from nova.policies import cells
|
||||
from nova.policies import cells_scheduler
|
||||
from nova.policies import console_auth_tokens
|
||||
from nova.policies import console_output
|
||||
@ -86,7 +85,6 @@ def list_rules():
|
||||
attach_interfaces.list_rules(),
|
||||
availability_zone.list_rules(),
|
||||
baremetal_nodes.list_rules(),
|
||||
cells.list_rules(),
|
||||
cells_scheduler.list_rules(),
|
||||
console_auth_tokens.list_rules(),
|
||||
console_output.list_rules(),
|
||||
|
@ -1,96 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# 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_policy import policy
|
||||
|
||||
from nova.policies import base
|
||||
|
||||
|
||||
BASE_POLICY_NAME = 'os_compute_api:os-cells'
|
||||
POLICY_ROOT = 'os_compute_api:os-cells:%s'
|
||||
|
||||
|
||||
cells_policies = [
|
||||
policy.DocumentedRuleDefault(
|
||||
POLICY_ROOT % 'update',
|
||||
base.RULE_ADMIN_API,
|
||||
'Update an existing cell',
|
||||
[
|
||||
{
|
||||
'method': 'PUT',
|
||||
'path': '/os-cells/{cell_id}'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
POLICY_ROOT % 'create',
|
||||
base.RULE_ADMIN_API,
|
||||
'Create a new cell',
|
||||
[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/os-cells'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
BASE_POLICY_NAME,
|
||||
base.RULE_ADMIN_API,
|
||||
'List and show detailed info for a given cell or all cells',
|
||||
[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/os-cells'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/os-cells/detail'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/os-cells/info'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/os-cells/capacities'
|
||||
},
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/os-cells/{cell_id}'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
POLICY_ROOT % 'sync_instances',
|
||||
base.RULE_ADMIN_API,
|
||||
'Sync instances info in all cells',
|
||||
[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/os-cells/sync_instances'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
POLICY_ROOT % 'delete',
|
||||
base.RULE_ADMIN_API,
|
||||
'Remove a cell',
|
||||
[
|
||||
{
|
||||
'method': 'DELETE',
|
||||
'path': '/os-cells/{cell_id}'
|
||||
}
|
||||
])
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return cells_policies
|
@ -1,26 +0,0 @@
|
||||
{
|
||||
"cell": {
|
||||
"capacities": {
|
||||
"disk_free": {
|
||||
"total_mb": 1052672,
|
||||
"units_by_mb": {
|
||||
"0": 0,
|
||||
"163840": 5,
|
||||
"20480": 46,
|
||||
"40960": 23,
|
||||
"81920": 11
|
||||
}
|
||||
},
|
||||
"ram_free": {
|
||||
"total_mb": 7680,
|
||||
"units_by_mb": {
|
||||
"16384": 0,
|
||||
"2048": 3,
|
||||
"4096": 1,
|
||||
"512": 13,
|
||||
"8192": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"cell": {
|
||||
"name": "cell3",
|
||||
"rpc_host": null,
|
||||
"rpc_port": null,
|
||||
"type": "child",
|
||||
"username": "username3"
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"cells": []
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"name": "cell1",
|
||||
"rpc_host": null,
|
||||
"rpc_port": null,
|
||||
"type": "child",
|
||||
"username": "username1"
|
||||
},
|
||||
{
|
||||
"name": "cell3",
|
||||
"rpc_host": null,
|
||||
"rpc_port": null,
|
||||
"type": "child",
|
||||
"username": "username3"
|
||||
},
|
||||
{
|
||||
"name": "cell5",
|
||||
"rpc_host": null,
|
||||
"rpc_port": null,
|
||||
"type": "child",
|
||||
"username": "username5"
|
||||
},
|
||||
{
|
||||
"name": "cell2",
|
||||
"rpc_host": null,
|
||||
"rpc_port": null,
|
||||
"type": "parent",
|
||||
"username": "username2"
|
||||
},
|
||||
{
|
||||
"name": "cell4",
|
||||
"rpc_host": null,
|
||||
"rpc_port": null,
|
||||
"type": "parent",
|
||||
"username": "username4"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
# Copyright 2013 IBM Corp.
|
||||
# Copyright 2019 Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
@ -13,91 +14,47 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from six.moves import range
|
||||
|
||||
from nova.cells import state
|
||||
from nova.db.sqlalchemy import models
|
||||
from nova import exception
|
||||
from nova.tests.functional.api_sample_tests import api_sample_base
|
||||
|
||||
|
||||
class CellsSampleJsonTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
sample_dir = "os-cells"
|
||||
|
||||
def setUp(self):
|
||||
# db_check_interval < 0 makes cells manager always hit the DB
|
||||
self.flags(enable=True, db_check_interval=-1, group='cells')
|
||||
super(CellsSampleJsonTest, self).setUp()
|
||||
self.cells = self.start_service('cells',
|
||||
manager='nova.cells.manager.CellsManager')
|
||||
self._stub_cells()
|
||||
|
||||
def _stub_cells(self, num_cells=5):
|
||||
self.cell_list = []
|
||||
self.cells_next_id = 1
|
||||
|
||||
def _fake_cell_get_all(context):
|
||||
return self.cell_list
|
||||
|
||||
def _fake_cell_get(inst, context, cell_name):
|
||||
for cell in self.cell_list:
|
||||
if cell['name'] == cell_name:
|
||||
return cell
|
||||
raise exception.CellNotFound(cell_name=cell_name)
|
||||
|
||||
for x in range(num_cells):
|
||||
cell = models.Cell()
|
||||
our_id = self.cells_next_id
|
||||
self.cells_next_id += 1
|
||||
cell.update({'id': our_id,
|
||||
'name': 'cell%s' % our_id,
|
||||
'transport_url': 'rabbit://username%s@/' % our_id,
|
||||
'is_parent': our_id % 2 == 0})
|
||||
self.cell_list.append(cell)
|
||||
|
||||
self.stub_out('nova.db.api.cell_get_all', _fake_cell_get_all)
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.cell_get', _fake_cell_get)
|
||||
|
||||
def test_cells_empty_list(self):
|
||||
# Override this
|
||||
self._stub_cells(num_cells=0)
|
||||
response = self._do_get('os-cells')
|
||||
self._verify_response('cells-list-empty-resp', {}, response, 200)
|
||||
class CellsTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
|
||||
def test_cells_list(self):
|
||||
response = self._do_get('os-cells')
|
||||
self._verify_response('cells-list-resp', {}, response, 200)
|
||||
self.api.api_get('os-cells',
|
||||
check_response_status=[410])
|
||||
|
||||
def test_cells_get(self):
|
||||
response = self._do_get('os-cells/cell3')
|
||||
self._verify_response('cells-get-resp', {}, response, 200)
|
||||
def test_cells_capacity(self):
|
||||
self.api.api_get('os-cells/capacities',
|
||||
check_response_status=[410])
|
||||
|
||||
def test_get_cell_capacity(self):
|
||||
self._mock_cell_capacity()
|
||||
state_manager = state.CellStateManager()
|
||||
my_state = state_manager.get_my_state()
|
||||
response = self._do_get('os-cells/%s/capacities' %
|
||||
my_state.name)
|
||||
return self._verify_response('cells-capacities-resp',
|
||||
{}, response, 200)
|
||||
def test_cells_detail(self):
|
||||
self.api.api_get('os-cells/detail',
|
||||
check_response_status=[410])
|
||||
|
||||
def test_get_all_cells_capacity(self):
|
||||
self._mock_cell_capacity()
|
||||
response = self._do_get('os-cells/capacities')
|
||||
return self._verify_response('cells-capacities-resp',
|
||||
{}, response, 200)
|
||||
def test_cells_info(self):
|
||||
self.api.api_get('os-cells/info',
|
||||
check_response_status=[410])
|
||||
|
||||
def _mock_cell_capacity(self):
|
||||
response = {"ram_free":
|
||||
{"units_by_mb": {"8192": 0, "512": 13,
|
||||
"4096": 1, "2048": 3, "16384": 0},
|
||||
"total_mb": 7680},
|
||||
"disk_free":
|
||||
{"units_by_mb": {"81920": 11, "20480": 46,
|
||||
"40960": 23, "163840": 5, "0": 0},
|
||||
"total_mb": 1052672}
|
||||
}
|
||||
goc_mock = mock.Mock()
|
||||
goc_mock.return_value = response
|
||||
self.cells.manager.state_manager.get_our_capacities = goc_mock
|
||||
def test_cells_sync_instances(self):
|
||||
self.api.api_post('os-cells/sync_instances', {},
|
||||
check_response_status=[410])
|
||||
|
||||
def test_cell_create(self):
|
||||
self.api.api_post('os-cells', {},
|
||||
check_response_status=[410])
|
||||
|
||||
def test_cell_show(self):
|
||||
self.api.api_get('os-cells/cell3',
|
||||
check_response_status=[410])
|
||||
|
||||
def test_cell_update(self):
|
||||
self.api.api_put('os-cells/cell3', {},
|
||||
check_response_status=[410])
|
||||
|
||||
def test_cell_delete(self):
|
||||
self.api.api_delete('os-cells/cell3',
|
||||
check_response_status=[410])
|
||||
|
||||
def test_cell_capacity(self):
|
||||
self.api.api_get('os-cells/cell3/capacities',
|
||||
check_response_status=[410])
|
||||
|
@ -1,741 +0,0 @@
|
||||
# Copyright 2011-2012 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.
|
||||
|
||||
import copy
|
||||
|
||||
import mock
|
||||
from oslo_utils import timeutils
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack.compute import cells as cells_ext_v21
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova import rpc
|
||||
from nova import test
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
|
||||
|
||||
class BaseCellsTest(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(BaseCellsTest, self).setUp()
|
||||
|
||||
self.fake_cells = [
|
||||
dict(id=1, name='cell1', is_parent=True,
|
||||
weight_scale=1.0, weight_offset=0.0,
|
||||
transport_url='rabbit://bob:xxxx@r1.example.org/'),
|
||||
dict(id=2, name='cell2', is_parent=False,
|
||||
weight_scale=1.0, weight_offset=0.0,
|
||||
transport_url='rabbit://alice:qwerty@r2.example.org/')]
|
||||
|
||||
self.fake_capabilities = [
|
||||
{'cap1': '0,1', 'cap2': '2,3'},
|
||||
{'cap3': '4,5', 'cap4': '5,6'}]
|
||||
|
||||
def fake_cell_get(_self, context, cell_name):
|
||||
for cell in self.fake_cells:
|
||||
if cell_name == cell['name']:
|
||||
return cell
|
||||
else:
|
||||
raise exception.CellNotFound(cell_name=cell_name)
|
||||
|
||||
def fake_cell_create(_self, context, values):
|
||||
cell = dict(id=1)
|
||||
cell.update(values)
|
||||
return cell
|
||||
|
||||
def fake_cell_update(_self, context, cell_id, values):
|
||||
cell = fake_cell_get(_self, context, cell_id)
|
||||
cell.update(values)
|
||||
return cell
|
||||
|
||||
def fake_cells_api_get_all_cell_info(*args):
|
||||
return self._get_all_cell_info(*args)
|
||||
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.cell_get',
|
||||
fake_cell_get)
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.cell_update',
|
||||
fake_cell_update)
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.cell_create',
|
||||
fake_cell_create)
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.get_cell_info_for_neighbors',
|
||||
fake_cells_api_get_all_cell_info)
|
||||
|
||||
def _get_all_cell_info(self, *args):
|
||||
def insecure_transport_url(url):
|
||||
transport_url = rpc.get_transport_url(url)
|
||||
transport_url.hosts[0].password = None
|
||||
return str(transport_url)
|
||||
|
||||
cells = copy.deepcopy(self.fake_cells)
|
||||
cells[0]['transport_url'] = insecure_transport_url(
|
||||
cells[0]['transport_url'])
|
||||
cells[1]['transport_url'] = insecure_transport_url(
|
||||
cells[1]['transport_url'])
|
||||
for i, cell in enumerate(cells):
|
||||
cell['capabilities'] = self.fake_capabilities[i]
|
||||
return cells
|
||||
|
||||
|
||||
class CellsTestV21(BaseCellsTest):
|
||||
cell_extension = 'os_compute_api:os-cells'
|
||||
bad_request = exception.ValidationError
|
||||
|
||||
def _get_cell_controller(self):
|
||||
return cells_ext_v21.CellsController()
|
||||
|
||||
def _get_request(self, resource):
|
||||
return fakes.HTTPRequest.blank('/v2/fake/' + resource)
|
||||
|
||||
def setUp(self):
|
||||
super(CellsTestV21, self).setUp()
|
||||
self.controller = self._get_cell_controller()
|
||||
self.context = context.get_admin_context()
|
||||
self.flags(enable=True, group='cells')
|
||||
|
||||
def test_index(self):
|
||||
req = self._get_request("cells")
|
||||
res_dict = self.controller.index(req)
|
||||
|
||||
self.assertEqual(len(res_dict['cells']), 2)
|
||||
for i, cell in enumerate(res_dict['cells']):
|
||||
self.assertEqual(cell['name'], self.fake_cells[i]['name'])
|
||||
self.assertNotIn('capabilities', cell)
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def test_index_offset_and_limit(self):
|
||||
req = self._get_request('cells?offset=1&limit=1')
|
||||
res_dict = self.controller.index(req)
|
||||
|
||||
self.assertEqual(len(res_dict['cells']), 1)
|
||||
cell = res_dict['cells'][0]
|
||||
self.assertEqual(cell['name'], self.fake_cells[1]['name'])
|
||||
self.assertNotIn('capabilities', cell)
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def test_detail(self):
|
||||
req = self._get_request("cells/detail")
|
||||
res_dict = self.controller.detail(req)
|
||||
|
||||
self.assertEqual(len(res_dict['cells']), 2)
|
||||
for i, cell in enumerate(res_dict['cells']):
|
||||
self.assertEqual(cell['name'], self.fake_cells[i]['name'])
|
||||
self.assertEqual(cell['capabilities'], self.fake_capabilities[i])
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def test_detail_offset_and_limit(self):
|
||||
req = self._get_request("cells/detail?offset=1&limit=1")
|
||||
res_dict = self.controller.detail(req)
|
||||
|
||||
self.assertEqual(len(res_dict['cells']), 1)
|
||||
cell = res_dict['cells'][0]
|
||||
self.assertEqual(cell['name'], self.fake_cells[1]['name'])
|
||||
self.assertEqual(cell['capabilities'], self.fake_capabilities[1])
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def test_show_bogus_cell_raises(self):
|
||||
req = self._get_request("cells/bogus")
|
||||
self.assertRaises(exc.HTTPNotFound, self.controller.show, req, 'bogus')
|
||||
|
||||
def test_get_cell_by_name(self):
|
||||
req = self._get_request("cells/cell1")
|
||||
res_dict = self.controller.show(req, 'cell1')
|
||||
cell = res_dict['cell']
|
||||
|
||||
self.assertEqual(cell['name'], 'cell1')
|
||||
self.assertEqual(cell['rpc_host'], 'r1.example.org')
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def _cell_delete(self):
|
||||
call_info = {'delete_called': 0}
|
||||
|
||||
def fake_cell_delete(inst, context, cell_name):
|
||||
self.assertEqual(cell_name, 'cell999')
|
||||
call_info['delete_called'] += 1
|
||||
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.cell_delete',
|
||||
fake_cell_delete)
|
||||
|
||||
req = self._get_request("cells/cell999")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.controller.delete(req, 'cell999')
|
||||
self.assertEqual(call_info['delete_called'], 1)
|
||||
|
||||
def test_cell_delete(self):
|
||||
# Test cell delete with just cell policy
|
||||
rules = {"default": "is_admin:true",
|
||||
self.cell_extension: "is_admin:true"}
|
||||
self.policy.set_rules(rules)
|
||||
self._cell_delete()
|
||||
|
||||
def test_cell_delete_with_delete_policy(self):
|
||||
self._cell_delete()
|
||||
|
||||
def test_delete_bogus_cell_raises(self):
|
||||
def fake_cell_delete(inst, context, cell_name):
|
||||
return 0
|
||||
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.cell_delete',
|
||||
fake_cell_delete)
|
||||
|
||||
req = self._get_request("cells/cell999")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(exc.HTTPNotFound, self.controller.delete, req,
|
||||
'cell999')
|
||||
|
||||
def test_cell_delete_fails_for_invalid_policy(self):
|
||||
def fake_cell_delete(inst, context, cell_name):
|
||||
pass
|
||||
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.cell_delete',
|
||||
fake_cell_delete)
|
||||
|
||||
req = self._get_request("cells/cell999")
|
||||
req.environ['nova.context'] = self.context
|
||||
req.environ["nova.context"].is_admin = False
|
||||
self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self.controller.delete, req, 'cell999')
|
||||
|
||||
def _cell_create_parent(self):
|
||||
body = {'cell': {'name': 'meow',
|
||||
'username': 'fred',
|
||||
'password': 'fubar',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
cell = res_dict['cell']
|
||||
|
||||
self.assertEqual(cell['name'], 'meow')
|
||||
self.assertEqual(cell['username'], 'fred')
|
||||
self.assertEqual(cell['rpc_host'], 'r3.example.org')
|
||||
self.assertEqual(cell['type'], 'parent')
|
||||
self.assertNotIn('password', cell)
|
||||
self.assertNotIn('is_parent', cell)
|
||||
|
||||
def test_cell_create_parent(self):
|
||||
# Test create with just cells policy
|
||||
rules = {"default": "is_admin:true",
|
||||
self.cell_extension: "is_admin:true"}
|
||||
self.policy.set_rules(rules)
|
||||
self._cell_create_parent()
|
||||
|
||||
def test_cell_create_parent_with_create_policy(self):
|
||||
self._cell_create_parent()
|
||||
|
||||
def _cell_create_child(self):
|
||||
body = {'cell': {'name': 'meow',
|
||||
'username': 'fred',
|
||||
'password': 'fubar',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'child'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
res_dict = self.controller.create(req, body=body)
|
||||
cell = res_dict['cell']
|
||||
|
||||
self.assertEqual(cell['name'], 'meow')
|
||||
self.assertEqual(cell['username'], 'fred')
|
||||
self.assertEqual(cell['rpc_host'], 'r3.example.org')
|
||||
self.assertEqual(cell['type'], 'child')
|
||||
self.assertNotIn('password', cell)
|
||||
self.assertNotIn('is_parent', cell)
|
||||
|
||||
def test_cell_create_child(self):
|
||||
# Test create with just cells policy
|
||||
rules = {"default": "is_admin:true",
|
||||
self.cell_extension: "is_admin:true"}
|
||||
self.policy.set_rules(rules)
|
||||
self._cell_create_child()
|
||||
|
||||
def test_cell_create_child_with_create_policy(self):
|
||||
self._cell_create_child()
|
||||
|
||||
def test_cell_create_no_name_raises(self):
|
||||
body = {'cell': {'username': 'moocow',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def test_cell_create_name_empty_string_raises(self):
|
||||
body = {'cell': {'name': '',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def test_cell_create_name_with_invalid_character_raises(self):
|
||||
body = {'cell': {'name': 'moo\x00cow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def test_cell_create_name_with_dot_raises(self):
|
||||
body = {'cell': {'name': 'moo.cow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
def test_cell_create_name_with_exclamation_point_raises(self):
|
||||
body = {'cell': {'name': 'moo!cow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
def test_cell_create_name_with_at_raises(self):
|
||||
body = {'cell': {'name': 'moo@cow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
def test_cell_create_name_with_leading_trailing_spaces(self):
|
||||
body = {'cell': {'name': ' moocow ',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
def test_cell_create_name_with_leading_trailing_spaces_compat_mode(self):
|
||||
body = {'cell': {'name': ' moocow ',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
req.set_legacy_v2()
|
||||
resp = self.controller.create(req, body=body)
|
||||
self.assertEqual('moocow', resp['cell']['name'])
|
||||
|
||||
def test_cell_create_name_with_invalid_type_raises(self):
|
||||
body = {'cell': {'name': 'moocow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'invalid'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def test_cell_create_fails_for_invalid_policy(self):
|
||||
body = {'cell': {'name': 'fake'}}
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
req.environ['nova.context'].is_admin = False
|
||||
self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def test_cell_create_rpc_port_with_string(self):
|
||||
body = {'cell': {'name': 'fake',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'rpc_port': '123',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.controller.create(req, body=body)
|
||||
|
||||
def test_cell_create_rpc_port_with_null(self):
|
||||
body = {'cell': {'name': 'fake',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'rpc_port': None,
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def test_cell_create_rpc_port_empty_string_raises(self):
|
||||
body = {'cell': {'name': 'moocow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'rpc_port': '',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def _cell_update(self):
|
||||
body = {'cell': {'username': 'zeb',
|
||||
'password': 'sneaky'}}
|
||||
|
||||
req = self._get_request("cells/cell1")
|
||||
req.environ['nova.context'] = self.context
|
||||
res_dict = self.controller.update(req, 'cell1', body=body)
|
||||
cell = res_dict['cell']
|
||||
|
||||
self.assertEqual(cell['name'], 'cell1')
|
||||
self.assertEqual(cell['rpc_host'], 'r1.example.org')
|
||||
self.assertEqual(cell['username'], 'zeb')
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def test_cell_update(self):
|
||||
# Test cell update with just cell policy
|
||||
rules = {"default": "is_admin:true",
|
||||
self.cell_extension: "is_admin:true"}
|
||||
self.policy.set_rules(rules)
|
||||
self._cell_update()
|
||||
|
||||
def test_cell_update_with_update_policy(self):
|
||||
self._cell_update()
|
||||
|
||||
def test_cell_update_fails_for_invalid_policy(self):
|
||||
body = {'cell': {'name': 'got_changed'}}
|
||||
req = self._get_request("cells/cell1")
|
||||
req.environ['nova.context'] = self.context
|
||||
req.environ['nova.context'].is_admin = False
|
||||
self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def test_cell_update_empty_name_raises(self):
|
||||
body = {'cell': {'name': '',
|
||||
'username': 'zeb',
|
||||
'password': 'sneaky'}}
|
||||
|
||||
req = self._get_request("cells/cell1")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.update, req, 'cell1', body=body)
|
||||
|
||||
def test_cell_update_empty_rpc_port_raises(self):
|
||||
body = {'cell': {'name': 'fake',
|
||||
'username': 'zeb',
|
||||
'password': 'sneaky',
|
||||
'rpc_port': ''}}
|
||||
|
||||
req = self._get_request("cells/cell1")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.update, req, 'cell1', body=body)
|
||||
|
||||
def test_cell_update_invalid_type_raises(self):
|
||||
body = {'cell': {'username': 'zeb',
|
||||
'type': 'invalid',
|
||||
'password': 'sneaky'}}
|
||||
|
||||
req = self._get_request("cells/cell1")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.update, req, 'cell1', body=body)
|
||||
|
||||
def test_cell_update_without_type_specified(self):
|
||||
body = {'cell': {'username': 'wingwj'}}
|
||||
|
||||
req = self._get_request("cells/cell1")
|
||||
req.environ['nova.context'] = self.context
|
||||
res_dict = self.controller.update(req, 'cell1', body=body)
|
||||
cell = res_dict['cell']
|
||||
|
||||
self.assertEqual(cell['name'], 'cell1')
|
||||
self.assertEqual(cell['rpc_host'], 'r1.example.org')
|
||||
self.assertEqual(cell['username'], 'wingwj')
|
||||
self.assertEqual(cell['type'], 'parent')
|
||||
|
||||
def test_cell_update_with_type_specified(self):
|
||||
body1 = {'cell': {'username': 'wingwj', 'type': 'child'}}
|
||||
body2 = {'cell': {'username': 'wingwj', 'type': 'parent'}}
|
||||
|
||||
req1 = self._get_request("cells/cell1")
|
||||
req1.environ['nova.context'] = self.context
|
||||
res_dict1 = self.controller.update(req1, 'cell1', body=body1)
|
||||
cell1 = res_dict1['cell']
|
||||
|
||||
req2 = self._get_request("cells/cell2")
|
||||
req2.environ['nova.context'] = self.context
|
||||
res_dict2 = self.controller.update(req2, 'cell2', body=body2)
|
||||
cell2 = res_dict2['cell']
|
||||
|
||||
self.assertEqual(cell1['name'], 'cell1')
|
||||
self.assertEqual(cell1['rpc_host'], 'r1.example.org')
|
||||
self.assertEqual(cell1['username'], 'wingwj')
|
||||
self.assertEqual(cell1['type'], 'child')
|
||||
|
||||
self.assertEqual(cell2['name'], 'cell2')
|
||||
self.assertEqual(cell2['rpc_host'], 'r2.example.org')
|
||||
self.assertEqual(cell2['username'], 'wingwj')
|
||||
self.assertEqual(cell2['type'], 'parent')
|
||||
|
||||
def test_cell_update_rpc_port_with_string(self):
|
||||
body = {'cell': {'name': 'fake',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'rpc_port': '123',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.controller.update(req, 'cell1', body=body)
|
||||
|
||||
def test_cell_update_rpc_port_with_null(self):
|
||||
body = {'cell': {'name': 'fake',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'rpc_port': None,
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.update, req, 'cell1', body=body)
|
||||
|
||||
def test_cell_update_rpc_port_empty_string_raises(self):
|
||||
body = {'cell': {'name': 'moocow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'rpc_port': '',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.update, req, 'cell1', body=body)
|
||||
|
||||
def test_cell_info(self):
|
||||
caps = ['cap1=a;b', 'cap2=c;d']
|
||||
self.flags(name='darksecret', capabilities=caps, group='cells')
|
||||
|
||||
req = self._get_request("cells/info")
|
||||
res_dict = self.controller.info(req)
|
||||
cell = res_dict['cell']
|
||||
cell_caps = cell['capabilities']
|
||||
|
||||
self.assertEqual(cell['name'], 'darksecret')
|
||||
self.assertEqual(cell_caps['cap1'], 'a;b')
|
||||
self.assertEqual(cell_caps['cap2'], 'c;d')
|
||||
|
||||
def test_show_capacities(self):
|
||||
response = {"ram_free":
|
||||
{"units_by_mb": {"8192": 0, "512": 13,
|
||||
"4096": 1, "2048": 3, "16384": 0},
|
||||
"total_mb": 7680},
|
||||
"disk_free":
|
||||
{"units_by_mb": {"81920": 11, "20480": 46,
|
||||
"40960": 23, "163840": 5, "0": 0},
|
||||
"total_mb": 1052672}
|
||||
}
|
||||
|
||||
with mock.patch.object(self.controller.cells_rpcapi, 'get_capacities',
|
||||
return_value=response) as mock_get_capacities:
|
||||
req = self._get_request("cells/capacities")
|
||||
req.environ["nova.context"] = self.context
|
||||
res_dict = self.controller.capacities(req)
|
||||
self.assertEqual(response, res_dict['cell']['capacities'])
|
||||
mock_get_capacities.assert_called_once_with(
|
||||
self.context, cell_name=None)
|
||||
|
||||
def test_show_capacity_fails_with_non_admin_context(self):
|
||||
rules = {self.cell_extension: "is_admin:true"}
|
||||
self.policy.set_rules(rules)
|
||||
|
||||
req = self._get_request("cells/capacities")
|
||||
req.environ["nova.context"] = self.context
|
||||
req.environ["nova.context"].is_admin = False
|
||||
self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self.controller.capacities, req)
|
||||
|
||||
def test_show_capacities_for_invalid_cell(self):
|
||||
with mock.patch.object(self.controller.cells_rpcapi, 'get_capacities',
|
||||
side_effect=exception.CellNotFound(cell_name="invalid_cell")
|
||||
) as mock_get_capacities:
|
||||
req = self._get_request("cells/invalid_cell/capacities")
|
||||
req.environ["nova.context"] = self.context
|
||||
self.assertRaises(exc.HTTPNotFound,
|
||||
self.controller.capacities, req, "invalid_cell")
|
||||
|
||||
mock_get_capacities.assert_called_once_with(
|
||||
self.context, cell_name="invalid_cell")
|
||||
|
||||
def test_show_capacities_for_cell(self):
|
||||
response = {"ram_free":
|
||||
{"units_by_mb": {"8192": 0, "512": 13,
|
||||
"4096": 1, "2048": 3, "16384": 0},
|
||||
"total_mb": 7680},
|
||||
"disk_free":
|
||||
{"units_by_mb": {"81920": 11, "20480": 46,
|
||||
"40960": 23, "163840": 5, "0": 0},
|
||||
"total_mb": 1052672}
|
||||
}
|
||||
|
||||
with mock.patch.object(self.controller.cells_rpcapi, 'get_capacities',
|
||||
return_value=response) as mock_get_capacities:
|
||||
req = self._get_request("cells/capacities")
|
||||
req.environ["nova.context"] = self.context
|
||||
res_dict = self.controller.capacities(req, 'cell_name')
|
||||
self.assertEqual(response, res_dict['cell']['capacities'])
|
||||
|
||||
mock_get_capacities.assert_called_once_with(
|
||||
self.context, cell_name='cell_name')
|
||||
|
||||
def test_sync_instances(self):
|
||||
call_info = {}
|
||||
|
||||
def sync_instances(self, context, **kwargs):
|
||||
call_info['project_id'] = kwargs.get('project_id')
|
||||
call_info['updated_since'] = kwargs.get('updated_since')
|
||||
call_info['deleted'] = kwargs.get('deleted')
|
||||
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.sync_instances',
|
||||
sync_instances)
|
||||
|
||||
req = self._get_request("cells/sync_instances")
|
||||
req.environ['nova.context'] = self.context
|
||||
body = {}
|
||||
self.controller.sync_instances(req, body=body)
|
||||
self.assertIsNone(call_info['project_id'])
|
||||
self.assertIsNone(call_info['updated_since'])
|
||||
|
||||
body = {'project_id': 'test-project'}
|
||||
self.controller.sync_instances(req, body=body)
|
||||
self.assertEqual(call_info['project_id'], 'test-project')
|
||||
self.assertIsNone(call_info['updated_since'])
|
||||
|
||||
expected = timeutils.utcnow().isoformat()
|
||||
if not expected.endswith("+00:00"):
|
||||
expected += "+00:00"
|
||||
|
||||
body = {'updated_since': expected}
|
||||
self.controller.sync_instances(req, body=body)
|
||||
self.assertIsNone(call_info['project_id'])
|
||||
self.assertEqual(call_info['updated_since'], expected)
|
||||
|
||||
body = {'updated_since': 'skjdfkjsdkf'}
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.sync_instances, req, body=body)
|
||||
|
||||
body = {'deleted': False}
|
||||
self.controller.sync_instances(req, body=body)
|
||||
self.assertIsNone(call_info['project_id'])
|
||||
self.assertIsNone(call_info['updated_since'])
|
||||
self.assertFalse(call_info['deleted'])
|
||||
|
||||
body = {'deleted': 'False'}
|
||||
self.controller.sync_instances(req, body=body)
|
||||
self.assertIsNone(call_info['project_id'])
|
||||
self.assertIsNone(call_info['updated_since'])
|
||||
self.assertFalse(call_info['deleted'])
|
||||
|
||||
body = {'deleted': 'True'}
|
||||
self.controller.sync_instances(req, body=body)
|
||||
self.assertIsNone(call_info['project_id'])
|
||||
self.assertIsNone(call_info['updated_since'])
|
||||
self.assertTrue(call_info['deleted'])
|
||||
|
||||
body = {'deleted': 'foo'}
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.sync_instances, req, body=body)
|
||||
|
||||
body = {'foo': 'meow'}
|
||||
self.assertRaises(self.bad_request,
|
||||
self.controller.sync_instances, req, body=body)
|
||||
|
||||
def test_sync_instances_fails_for_invalid_policy(self):
|
||||
def sync_instances(self, context, **kwargs):
|
||||
pass
|
||||
|
||||
self.stub_out('nova.cells.rpcapi.CellsAPI.sync_instances',
|
||||
sync_instances)
|
||||
|
||||
req = self._get_request("cells/sync_instances")
|
||||
req.environ['nova.context'] = self.context
|
||||
req.environ['nova.context'].is_admin = False
|
||||
|
||||
body = {}
|
||||
self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self.controller.sync_instances, req, body=body)
|
||||
|
||||
def test_cells_disabled(self):
|
||||
self.flags(enable=False, group='cells')
|
||||
|
||||
req = self._get_request("cells")
|
||||
self.assertRaises(exc.HTTPNotImplemented,
|
||||
self.controller.index, req)
|
||||
|
||||
req = self._get_request("cells/detail")
|
||||
self.assertRaises(exc.HTTPNotImplemented,
|
||||
self.controller.detail, req)
|
||||
|
||||
req = self._get_request("cells/cell1")
|
||||
self.assertRaises(exc.HTTPNotImplemented,
|
||||
self.controller.show, req)
|
||||
|
||||
self.assertRaises(exc.HTTPNotImplemented,
|
||||
self.controller.delete, req, 'cell999')
|
||||
|
||||
req = self._get_request("cells/cells")
|
||||
self.assertRaises(exc.HTTPNotImplemented,
|
||||
self.controller.create, req, {})
|
||||
|
||||
req = self._get_request("cells/capacities")
|
||||
self.assertRaises(exc.HTTPNotImplemented,
|
||||
self.controller.capacities, req)
|
||||
|
||||
req = self._get_request("cells/sync_instances")
|
||||
self.assertRaises(exc.HTTPNotImplemented,
|
||||
self.controller.sync_instances, req, {})
|
@ -27,7 +27,6 @@ policy_data = """
|
||||
"os_compute_api:os-agents": "",
|
||||
"os_compute_api:os-attach-interfaces": "",
|
||||
"os_compute_api:os-baremetal-nodes": "",
|
||||
"os_compute_api:os-cells": "",
|
||||
"os_compute_api:os-console-output": "",
|
||||
"os_compute_api:os-remote-consoles": "",
|
||||
"os_compute_api:os-consoles:create": "",
|
||||
|
@ -75,15 +75,6 @@ class FakeRequest(object):
|
||||
|
||||
|
||||
class ValidationRegex(test.NoDBTestCase):
|
||||
def test_cell_names(self):
|
||||
cellre = re.compile(parameter_types.valid_cell_name_regex.regex)
|
||||
self.assertTrue(cellre.search('foo'))
|
||||
self.assertFalse(cellre.search('foo.bar'))
|
||||
self.assertFalse(cellre.search('foo@bar'))
|
||||
self.assertFalse(cellre.search('foo!bar'))
|
||||
self.assertFalse(cellre.search(' foo!bar'))
|
||||
self.assertFalse(cellre.search('\nfoo!bar'))
|
||||
|
||||
def test_build_regex_range(self):
|
||||
# this is much easier to think about if we only use the ascii
|
||||
# subset because it's a printable range we can think
|
||||
@ -183,12 +174,6 @@ class FormatCheckerTestCase(test.NoDBTestCase):
|
||||
self._format_checker("name", " ", error_message)
|
||||
self._format_checker("name", None, error_message)
|
||||
|
||||
def test_format_checker_failed_with_non_string_cell_name(self):
|
||||
error_message = ("An invalid 'name' value was provided. "
|
||||
"The name must be: printable characters except "
|
||||
"!, ., @. Can not start or end with whitespace.")
|
||||
self._format_checker("cell_name", None, error_message)
|
||||
|
||||
def test_format_checker_failed_name_with_leading_trailing_spaces(self):
|
||||
error_message = ("An invalid 'name' value was provided. "
|
||||
"The name must be: printable characters with at "
|
||||
@ -196,14 +181,6 @@ class FormatCheckerTestCase(test.NoDBTestCase):
|
||||
self._format_checker("name_with_leading_trailing_spaces",
|
||||
None, error_message)
|
||||
|
||||
def test_format_checker_failed_cell_name_with_leading_trailing_spaces(
|
||||
self):
|
||||
error_message = ("An invalid 'name' value was provided. "
|
||||
"The name must be: printable characters except"
|
||||
" !, ., @, with at least one non space character")
|
||||
self._format_checker("cell_name_with_leading_trailing_spaces",
|
||||
None, error_message)
|
||||
|
||||
|
||||
class MicroversionsSchemaTestCase(APIValidationTestCase):
|
||||
|
||||
@ -757,106 +734,6 @@ class HostnameIPaddressTestCase(APIValidationTestCase):
|
||||
expected_detail=detail)
|
||||
|
||||
|
||||
class CellNameTestCase(APIValidationTestCase):
|
||||
|
||||
post_schema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': parameter_types.cell_name,
|
||||
},
|
||||
}
|
||||
|
||||
def test_validate_name(self):
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': 'abc'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': 'my server'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': u'\u0434'}, req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': u'\u0434\u2006\ufffd'},
|
||||
req=FakeRequest()))
|
||||
|
||||
def test_validate_name_fails(self):
|
||||
error = ("An invalid 'name' value was provided. The name must be: "
|
||||
"printable characters except !, ., @. "
|
||||
"Can not start or end with whitespace.")
|
||||
|
||||
should_fail = (' ',
|
||||
' server',
|
||||
'server ',
|
||||
u'a\xa0', # trailing unicode space
|
||||
u'\uffff', # non-printable unicode
|
||||
'abc!def',
|
||||
'abc.def',
|
||||
'abc@def')
|
||||
|
||||
for item in should_fail:
|
||||
self.check_validation_error(self.post, body={'foo': item},
|
||||
expected_detail=error)
|
||||
|
||||
# four-byte unicode, if supported by this python build
|
||||
try:
|
||||
self.check_validation_error(self.post, body={'foo': u'\U00010000'},
|
||||
expected_detail=error)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
class CellNameLeadingTrailingSpacesTestCase(APIValidationTestCase):
|
||||
|
||||
post_schema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': parameter_types.cell_name_leading_trailing_spaces,
|
||||
},
|
||||
}
|
||||
|
||||
def test_validate_name(self):
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': 'abc'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': 'my server'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': u'\u0434'}, req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': u'\u0434\u2006\ufffd'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': ' my server'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': 'my server '},
|
||||
req=FakeRequest()))
|
||||
|
||||
def test_validate_name_fails(self):
|
||||
error = ("An invalid 'name' value was provided. The name must be: "
|
||||
"printable characters except !, ., @, "
|
||||
"with at least one non space character")
|
||||
|
||||
should_fail = (
|
||||
' ',
|
||||
u'\uffff', # non-printable unicode
|
||||
'abc!def',
|
||||
'abc.def',
|
||||
'abc@def')
|
||||
|
||||
for item in should_fail:
|
||||
self.check_validation_error(self.post, body={'foo': item},
|
||||
expected_detail=error)
|
||||
|
||||
# four-byte unicode, if supported by this python build
|
||||
try:
|
||||
self.check_validation_error(self.post, body={'foo': u'\U00010000'},
|
||||
expected_detail=error)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
class NameTestCase(APIValidationTestCase):
|
||||
|
||||
post_schema = {
|
||||
|
@ -296,11 +296,6 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
|
||||
"os_compute_api:os-aggregates:set_metadata",
|
||||
"os_compute_api:os-agents",
|
||||
"os_compute_api:os-baremetal-nodes",
|
||||
"os_compute_api:os-cells",
|
||||
"os_compute_api:os-cells:create",
|
||||
"os_compute_api:os-cells:delete",
|
||||
"os_compute_api:os-cells:update",
|
||||
"os_compute_api:os-cells:sync_instances",
|
||||
"os_compute_api:os-evacuate",
|
||||
"os_compute_api:os-extended-server-attributes",
|
||||
"os_compute_api:os-flavor-access:remove_tenant_access",
|
||||
|
@ -2,4 +2,18 @@
|
||||
upgrade:
|
||||
- |
|
||||
The *cells v1* feature has been deprecated since the 16.0.0 Pike release
|
||||
and has now been removed. The ``nova-cells`` service has been removed.
|
||||
and has now been removed. The ``nova-cells`` service has been removed. The
|
||||
*cells v1* specific REST APIs have been removed along with their related
|
||||
policy rules. Calling these APIs will now result in a ``410 (Gone)`` error
|
||||
response.
|
||||
|
||||
* ``GET /os-cells``
|
||||
* ``POST /os-cells``
|
||||
* ``GET /os-cells/capacities``
|
||||
* ``GET /os-cells/detail``
|
||||
* ``GET /os-cells/info``
|
||||
* ``POST /os-cells/sync_instances``
|
||||
* ``GET /os-cells/{cell_id}``
|
||||
* ``PUT /os-cells/{cell_id}``
|
||||
* ``DELETE /os-cells/{cell_id}``
|
||||
* ``GET /os-cells/{cell_id}/capacities``
|
||||
|
Loading…
x
Reference in New Issue
Block a user