API to get the Cell Capacity

The total RAM and the available RAM are stored in memory of CellStateManager.
This API gets the available slots per flavor for a given cell.

Implements: blueprint get-cell-free-ram
Change-Id: I2a6dbb8835cad04f3ee058c3012490782d7c8e67
This commit is contained in:
Kaushik Chandrashekar 2013-04-22 06:26:32 -05:00
parent 09adc96f8f
commit b27a6cb399
18 changed files with 382 additions and 10 deletions

View File

@ -160,6 +160,14 @@
"namespace": "http://docs.openstack.org/compute/ext/cells/api/v1.1",
"updated": "2011-09-21T00:00:00+00:00"
},
{
"alias": "os-cell-capacities",
"description": "Adding functionality to get cell capacities.",
"links": [],
"name": "CellCapacities",
"namespace": "http://docs.openstack.org/compute/ext/cell_capacities/api/v1.1",
"updated": "2013-05-27T00:00:00+00:00"
},
{
"alias": "os-certificates",
"description": "Certificates support.",

View File

@ -68,6 +68,9 @@
listing neighbor cells, and getting the capabilities of the local cell.
</description>
</extension>
<extension alias="os-cell-capacities" updated="2013-05-27T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/cell_capacities/api/v1.1" name="CellCapacities">
<description>Adds functionality to get cell capacities.</description>
</extension>
<extension alias="os-certificates" updated="2012-01-19T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/certificates/api/v1.1" name="Certificates">
<description>Certificates support.</description>
</extension>

View File

@ -0,0 +1,18 @@
{
"cell": {
"capacities": {
"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
}
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<cell xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<capacities>
<ram_free total_mb="7680">
<unit_by_mb unit="0" mb="8192"/>
<unit_by_mb unit="13" mb="512"/>
<unit_by_mb unit="1" mb="4096"/>
<unit_by_mb unit="3" mb="2048"/>
<unit_by_mb unit="0" mb="16384"/>
</ram_free>
<disk_free total_mb="1052672">
<unit_by_mb unit="11" mb="81920"/>
<unit_by_mb unit="46" mb="20480"/>
<unit_by_mb unit="23" mb="40960"/>
<unit_by_mb unit="5" mb="163840"/>
<unit_by_mb unit="0" mb="0"/>
</disk_free>
</capacities>
</cell>

View File

@ -0,0 +1,27 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Rackspace Hosting
#
# 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 nova.api.openstack import extensions
class Cell_capacities(extensions.ExtensionDescriptor):
"""Adding functionality to get cell capacities."""
name = "CellCapacities"
alias = "os-cell-capacities"
namespace = ("http://docs.openstack.org/compute/ext/"
"cell_capacities/api/v1.1")
updated = "2013-05-27T00:00:00+00:00"

View File

@ -52,8 +52,33 @@ def make_cell(elem):
cap = xmlutil.SubTemplateElement(caps, xmlutil.Selector(0),
selector=xmlutil.get_items)
cap.text = 1
make_capacity(elem)
def make_capacity(cell):
def get_units_by_mb(capacity_info):
return capacity_info['units_by_mb'].items()
capacity = xmlutil.SubTemplateElement(cell, 'capacities',
selector='capacities')
ram_free = xmlutil.SubTemplateElement(capacity, 'ram_free',
selector='ram_free')
ram_free.set('total_mb', 'total_mb')
unit_by_mb = xmlutil.SubTemplateElement(ram_free, 'unit_by_mb',
selector=get_units_by_mb)
unit_by_mb.set('mb', 0)
unit_by_mb.set('unit', 1)
disk_free = xmlutil.SubTemplateElement(capacity, 'disk_free',
selector='disk_free')
disk_free.set('total_mb', 'total_mb')
unit_by_mb = xmlutil.SubTemplateElement(disk_free, 'unit_by_mb',
selector=get_units_by_mb)
unit_by_mb.set('mb', 0)
unit_by_mb.set('unit', 1)
cell_nsmap = {None: wsgi.XMLNS_V10}
@ -123,9 +148,10 @@ def _scrub_cell(cell, detail=False):
class Controller(object):
"""Controller for Cell resources."""
def __init__(self):
def __init__(self, ext_mgr):
self.compute_api = compute.API()
self.cells_rpcapi = cells_rpcapi.CellsAPI()
self.ext_mgr = ext_mgr
def _get_cells(self, ctxt, req, detail=False):
"""Return all cells."""
@ -167,6 +193,25 @@ class Controller(object):
'capabilities': cell_capabs}
return dict(cell=cell)
@wsgi.serializers(xml=CellTemplate)
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 v3, along with capabilities
if not self.ext_mgr.is_loaded('os-cell-capacities'):
raise exc.HTTPNotFound()
context = req.environ['nova.context']
authorize(context)
try:
capacities = self.cells_rpcapi.get_capacities(context,
cell_name=id)
except exception.CellNotFound:
msg = (_("Cell %(id)s not found.") % {'id': id})
raise exc.HTTPNotFound(explanation=msg)
return dict(cell={"capacities": capacities})
@wsgi.serializers(xml=CellTemplate)
def show(self, req, id):
"""Return data about the given cell name. 'id' is a cell name."""
@ -283,15 +328,20 @@ class Cells(extensions.ExtensionDescriptor):
name = "Cells"
alias = "os-cells"
namespace = "http://docs.openstack.org/compute/ext/cells/api/v1.1"
updated = "2011-09-21T00:00:00+00:00"
updated = "2013-05-14T00:00:00+00:00"
def get_resources(self):
coll_actions = {
'detail': 'GET',
'info': 'GET',
'sync_instances': 'POST',
}
'capacities': 'GET',
}
memb_actions = {
'capacities': 'GET',
}
res = extensions.ResourceExtension('os-cells',
Controller(), collection_actions=coll_actions)
Controller(self.ext_mgr), collection_actions=coll_actions,
member_actions=memb_actions)
return [res]

View File

@ -64,7 +64,7 @@ class CellsManager(manager.Manager):
Scheduling requests get passed to the scheduler class.
"""
RPC_API_VERSION = '1.8'
RPC_API_VERSION = '1.9'
def __init__(self, *args, **kwargs):
# Mostly for tests.
@ -387,3 +387,6 @@ class CellsManager(manager.Manager):
instance['cell_name'], instance_uuid, console_port,
console_type)
return response.value_or_raise()
def get_capacities(self, ctxt, cell_name):
return self.state_manager.get_capacities(cell_name)

View File

@ -51,6 +51,7 @@ class CellsAPI(rpc_proxy.RpcProxy):
1.6 - Adds consoleauth_delete_tokens() and validate_console_port()
1.7 - Adds service_update()
1.8 - Adds build_instances(), deprecates schedule_run_instance()
1.9 - Adds get_capacities()
'''
BASE_RPC_API_VERSION = '1.0'
@ -292,3 +293,8 @@ class CellsAPI(rpc_proxy.RpcProxy):
console_port=console_port,
console_type=console_type),
version='1.6')
def get_capacities(self, ctxt, cell_name=None):
return self.call(ctxt,
self.make_msg('get_capacities', cell_name=cell_name),
version='1.9')

View File

@ -25,6 +25,7 @@ from oslo.config import cfg
from nova.cells import rpc_driver
from nova import context
from nova.db import base
from nova import exception
from nova.openstack.common import log as logging
from nova.openstack.common import timeutils
from nova import utils
@ -361,3 +362,11 @@ class CellStateManager(base.Base):
for cell in self.child_cells.values():
self._add_to_dict(capacities, cell.capacities)
return capacities
@sync_from_db
def get_capacities(self, cell_name=None):
if not cell_name or cell_name == self.my_cell_state.name:
return self.get_our_capacities()
if cell_name in self.child_cells:
return self.child_cells[cell_name].capacities
raise exception.CellNotFound(cell_name=cell_name)

View File

@ -19,6 +19,7 @@ from lxml import etree
from webob import exc
from nova.api.openstack.compute.contrib import cells as cells_ext
from nova.api.openstack import extensions
from nova.api.openstack import xmlutil
from nova.cells import rpcapi as cells_rpcapi
from nova import context
@ -87,7 +88,8 @@ class CellsTest(test.TestCase):
self.stubs.Set(cells_rpcapi.CellsAPI, 'get_cell_info_for_neighbors',
fake_cells_api_get_all_cell_info)
self.controller = cells_ext.Controller()
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
self.controller = cells_ext.Controller(self.ext_mgr)
self.context = context.get_admin_context()
def _get_request(self, resource):
@ -281,6 +283,74 @@ class CellsTest(test.TestCase):
self.assertEqual(cell_caps['cap1'], 'a;b')
self.assertEqual(cell_caps['cap2'], 'c;d')
def test_show_capacities(self):
self.ext_mgr.is_loaded('os-cell-capacities').AndReturn(True)
self.mox.StubOutWithMock(self.controller.cells_rpcapi,
'get_capacities')
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}
}
self.controller.cells_rpcapi.\
get_capacities(self.context, cell_name=None).AndReturn(response)
self.mox.ReplayAll()
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'])
def test_show_capacity_fails_with_non_admin_context(self):
self.ext_mgr.is_loaded('os-cell-capacities').AndReturn(True)
rules = {"compute_extension:cells": "is_admin:true"}
self.policy.set_rules(rules)
self.mox.ReplayAll()
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):
self.ext_mgr.is_loaded('os-cell-capacities').AndReturn(True)
self.mox.StubOutWithMock(self.controller.cells_rpcapi,
'get_capacities')
self.controller.cells_rpcapi. \
get_capacities(self.context, cell_name="invalid_cell").AndRaise(
exception.CellNotFound(cell_name="invalid_cell"))
self.mox.ReplayAll()
req = self._get_request("cells/invalid_cell/capacities")
req.environ["nova.context"] = self.context
self.assertRaises(exc.HTTPNotFound,
self.controller.capacities, req, "invalid_cell")
def test_show_capacities_for_cell(self):
self.ext_mgr.is_loaded('os-cell-capacities').AndReturn(True)
self.mox.StubOutWithMock(self.controller.cells_rpcapi,
'get_capacities')
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}
}
self.controller.cells_rpcapi.\
get_capacities(self.context, cell_name='cell_name').\
AndReturn(response)
self.mox.ReplayAll()
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'])
def test_sync_instances(self):
call_info = {}

View File

@ -53,6 +53,7 @@ class CellsManagerClassTestCase(test.TestCase):
self.our_cell = 'grandchild-cell1'
self.cells_manager = fakes.get_cells_manager(self.our_cell)
self.msg_runner = self.cells_manager.msg_runner
self.state_manager = fakes.get_state_manager(self.our_cell)
self.driver = self.cells_manager.driver
self.ctxt = 'fake_context'
@ -513,6 +514,17 @@ class CellsManagerClassTestCase(test.TestCase):
self.cells_manager.consoleauth_delete_tokens(self.ctxt,
instance_uuid=instance_uuid)
def test_get_capacities(self):
cell_name = 'cell_name'
response = {"ram_free":
{"units_by_mb": {"64": 20, "128": 10}, "total_mb": 1491}}
self.mox.StubOutWithMock(self.state_manager,
'get_capacities')
self.state_manager.get_capacities(cell_name).AndReturn(response)
self.mox.ReplayAll()
self.assertEqual(response,
self.cells_manager.get_capacities(self.ctxt, cell_name))
def test_validate_console_port(self):
instance_uuid = 'fake-instance-uuid'
cell_name = 'fake-cell-name'

View File

@ -129,6 +129,16 @@ class CellsAPITestCase(test.TestCase):
self._check_result(call_info, 'build_instances',
expected_args, version=1.8)
def test_get_capacities(self):
capacity_info = {"capacity": "info"}
call_info = self._stub_rpc_method('call',
result=capacity_info)
result = self.cells_rpcapi.get_capacities(self.fake_context,
cell_name="name")
self._check_result(call_info, 'get_capacities',
{'cell_name': 'name'}, version='1.9')
self.assertEqual(capacity_info, result)
def test_instance_update_at_top(self):
fake_info_cache = {'id': 1,
'instance': 'fake_instance',

View File

@ -13,11 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Tests For CellsStateManager
Tests For CellStateManager
"""
from nova.cells import state
from nova import db
from nova.db.sqlalchemy import models
from nova import exception
from nova import test
@ -120,9 +122,47 @@ class TestCellsStateManager(test.TestCase):
units = 2 # 2 on host 3
self.assertEqual(units, cap['disk_free']['units_by_mb'][str(sz)])
def _capacity(self, reserve_percent):
def _get_state_manager(self, reserve_percent=0.0):
self.flags(reserve_percent=reserve_percent, group='cells')
return state.CellStateManager()
mgr = state.CellStateManager()
my_state = mgr.get_my_state()
def _capacity(self, reserve_percent):
state_manager = self._get_state_manager(reserve_percent)
my_state = state_manager.get_my_state()
return my_state.capacities
class TestCellsGetCapacity(TestCellsStateManager):
def setUp(self):
super(TestCellsGetCapacity, self).setUp()
self.capacities = {"ram_free": 1234}
self.state_manager = self._get_state_manager()
cell = models.Cell(name="cell_name")
other_cell = models.Cell(name="other_cell_name")
cell.capacities = self.capacities
other_cell.capacities = self.capacities
self.stubs.Set(self.state_manager, 'child_cells',
{"cell_name": cell,
"other_cell_name": other_cell})
def test_get_cell_capacity_for_all_cells(self):
self.stubs.Set(self.state_manager.my_cell_state, 'capacities',
self.capacities)
capacities = self.state_manager.get_capacities()
self.assertEqual({"ram_free": 3702}, capacities)
def test_get_cell_capacity_for_the_parent_cell(self):
self.stubs.Set(self.state_manager.my_cell_state, 'capacities',
self.capacities)
capacities = self.state_manager.\
get_capacities(self.state_manager.my_cell_state.name)
self.assertEqual({"ram_free": 3702}, capacities)
def test_get_cell_capacity_for_a_cell(self):
self.assertEqual(self.capacities,
self.state_manager.get_capacities(cell_name="cell_name"))
def test_get_cell_capacity_for_non_existing_cell(self):
self.assertRaises(exception.CellNotFound,
self.state_manager.get_capacities,
cell_name="invalid_cell_name")

View File

@ -160,6 +160,14 @@
"namespace": "http://docs.openstack.org/compute/ext/cells/api/v1.1",
"updated": "%(timestamp)s"
},
{
"alias": "os-cell-capacities",
"description": "%(text)s",
"links": [],
"name": "CellCapacities",
"namespace": "http://docs.openstack.org/compute/ext/cell_capacities/api/v1.1",
"updated": "%(timestamp)s"
},
{
"alias": "os-certificates",
"description": "%(text)s",

View File

@ -60,6 +60,9 @@
<extension alias="os-cells" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/cells/api/v1.1" name="Cells">
<description>%(text)s</description>
</extension>
<extension alias="os-cell-capacities" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/cell_capacities/api/v1.1" name="CellCapacities">
<description>%(text)s</description>
</extension>
<extension alias="os-certificates" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/certificates/api/v1.1" name="Certificates">
<description>%(text)s</description>
</extension>

View File

@ -0,0 +1,18 @@
{
"cell": {
"capacities": {
"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
}
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version='1.0' encoding='UTF-8'?>
<cell xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<capacities>
<ram_free total_mb="7680">
<unit_by_mb unit="0" mb="8192"/>
<unit_by_mb unit="13" mb="512"/>
<unit_by_mb unit="1" mb="4096"/>
<unit_by_mb unit="3" mb="2048"/>
<unit_by_mb unit="0" mb="16384"/>
</ram_free>
<disk_free total_mb="1052672">
<unit_by_mb unit="11" mb="81920"/>
<unit_by_mb unit="46" mb="20480"/>
<unit_by_mb unit="23" mb="40960"/>
<unit_by_mb unit="5" mb="163840"/>
<unit_by_mb unit="0" mb="0"/>
</disk_free>
</capacities>
</cell>

View File

@ -31,6 +31,7 @@ from nova.api.metadata import password
from nova.api.openstack.compute.contrib import coverage_ext
from nova.api.openstack.compute.contrib import fping
# Import extensions to pull in osapi_compute_extension CONF option used below.
from nova.cells import state
from nova.cloudpipe import pipelib
from nova.compute import api as compute_api
from nova.compute import manager as compute_manager
@ -2808,6 +2809,54 @@ class CellsSampleXmlTest(CellsSampleJsonTest):
ctype = 'xml'
class CellsCapacitySampleJsonTest(ApiSampleTestBase):
extends_name = ("nova.api.openstack.compute.contrib.cells.Cells")
extension_name = ("nova.api.openstack.compute.contrib."
"cell_capacities.Cell_capacities")
def setUp(self):
self.flags(enable=True, db_check_interval=-1, group='cells')
super(CellsCapacitySampleJsonTest, self).setUp()
# (navneetk/kaushikc) : Mock cell capacity to avoid the capacity
# being calculated from the compute nodes in the environment
self._mock_cell_capacity()
def test_get_cell_capacity(self):
state_manager = state.CellStateManager()
my_state = state_manager.get_my_state()
response = self._do_get('os-cells/%s/capacities' %
my_state.name)
subs = self._get_regexes()
return self._verify_response('cells-capacities-resp',
subs, response, 200)
def test_get_all_cells_capacity(self):
response = self._do_get('os-cells/capacities')
subs = self._get_regexes()
return self._verify_response('cells-capacities-resp',
subs, response, 200)
def _mock_cell_capacity(self):
self.mox.StubOutWithMock(self.cells.manager.state_manager,
'get_our_capacities')
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}
}
self.cells.manager.state_manager.get_our_capacities(). \
AndReturn(response)
self.mox.ReplayAll()
class CellsCapacitySampleXmlTest(CellsCapacitySampleJsonTest):
ctype = 'xml'
class BareMetalNodesJsonTest(ApiSampleTestBase, bm_db_base.BMDBTestCase):
extension_name = ('nova.api.openstack.compute.contrib.baremetal_nodes.'
'Baremetal_nodes')