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:
Stephen Finucane 2019-04-04 11:47:11 +01:00
parent e25d59078e
commit fb14f24cc3
20 changed files with 98 additions and 1591 deletions

View File

@ -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

View File

@ -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
-------

View File

@ -1,3 +0,0 @@
{
"cells": []
}

View File

@ -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

View File

@ -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()

View File

@ -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,
}

View File

@ -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,

View File

@ -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,

View File

@ -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(),

View File

@ -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

View File

@ -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
}
}
}
}
}

View File

@ -1,9 +0,0 @@
{
"cell": {
"name": "cell3",
"rpc_host": null,
"rpc_port": null,
"type": "child",
"username": "username3"
}
}

View File

@ -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"
}
]
}

View File

@ -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])

View File

@ -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, {})

View File

@ -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": "",

View File

@ -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 = {

View File

@ -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",

View File

@ -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``