Merge "Add REST API support for list/enable/disable nova services"
This commit is contained in:
commit
4262020dd3
@ -59,6 +59,7 @@
|
||||
"compute_extension:rescue": "",
|
||||
"compute_extension:security_groups": "",
|
||||
"compute_extension:server_diagnostics": "rule:admin_api",
|
||||
"compute_extension:services": "rule:admin_api",
|
||||
"compute_extension:simple_tenant_usage:show": "rule:admin_or_owner",
|
||||
"compute_extension:simple_tenant_usage:list": "rule:admin_api",
|
||||
"compute_extension:users": "rule:admin_api",
|
||||
|
141
nova/api/openstack/compute/contrib/services.py
Normal file
141
nova/api/openstack/compute/contrib/services.py
Normal file
@ -0,0 +1,141 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 IBM
|
||||
# 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 webob.exc
|
||||
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api.openstack import xmlutil
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import timeutils
|
||||
from nova import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
authorize = extensions.extension_authorizer('compute', 'services')
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class ServicesIndexTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('services')
|
||||
elem = xmlutil.SubTemplateElement(root, 'service', selector='services')
|
||||
elem.set('binary')
|
||||
elem.set('host')
|
||||
elem.set('zone')
|
||||
elem.set('status')
|
||||
elem.set('state')
|
||||
elem.set('update_at')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class ServicesUpdateTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('host')
|
||||
root.set('host')
|
||||
root.set('service')
|
||||
root.set('disabled')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class ServiceController(object):
|
||||
@wsgi.serializers(xml=ServicesIndexTemplate)
|
||||
def index(self, req):
|
||||
"""
|
||||
Return a list of all running services. Filter by host & service name.
|
||||
"""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
now = timeutils.utcnow()
|
||||
services = db.service_get_all(context)
|
||||
|
||||
host = ''
|
||||
if 'host' in req.GET:
|
||||
host = req.GET['host']
|
||||
service = ''
|
||||
if 'service' in req.GET:
|
||||
service = req.GET['service']
|
||||
if host:
|
||||
services = [s for s in services if s['host'] == host]
|
||||
if service:
|
||||
services = [s for s in services if s['binary'] == service]
|
||||
|
||||
svcs = []
|
||||
for svc in services:
|
||||
delta = now - (svc['updated_at'] or svc['created_at'])
|
||||
alive = abs(utils.total_seconds(delta)) <= FLAGS.service_down_time
|
||||
art = (alive and "up") or "down"
|
||||
active = 'enabled'
|
||||
if svc['disabled']:
|
||||
active = 'disabled'
|
||||
svcs.append({"binary": svc['binary'], 'host': svc['host'],
|
||||
'zone': svc['availability_zone'],
|
||||
'status': active, 'state': art,
|
||||
'updated_at': svc['updated_at']})
|
||||
return {'services': svcs}
|
||||
|
||||
@wsgi.serializers(xml=ServicesUpdateTemplate)
|
||||
def update(self, req, id, body):
|
||||
"""Enable/Disable scheduling for a service"""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
if id == "enable":
|
||||
disabled = False
|
||||
elif id == "disable":
|
||||
disabled = True
|
||||
else:
|
||||
raise webob.exc.HTTPNotFound("Unknown action")
|
||||
|
||||
try:
|
||||
host = body['host']
|
||||
service = body['service']
|
||||
except (TypeError, KeyError):
|
||||
raise webob.exc.HTTPUnprocessableEntity()
|
||||
|
||||
try:
|
||||
svc = db.service_get_by_args(context, host, service)
|
||||
if not svc:
|
||||
raise webob.exc.HTTPNotFound('Unknown service')
|
||||
|
||||
db.service_update(context, svc['id'], {'disabled': disabled})
|
||||
except exception.ServiceNotFound:
|
||||
raise webob.exc.HTTPNotFound("service not found")
|
||||
|
||||
return {'host': host, 'service': service, 'disabled': disabled}
|
||||
|
||||
|
||||
class Services(extensions.ExtensionDescriptor):
|
||||
"""Services support"""
|
||||
|
||||
name = "Services"
|
||||
alias = "os-services"
|
||||
namespace = "http://docs.openstack.org/compute/ext/services/api/v2"
|
||||
updated = "2012-10-28T00:00:00-00:00"
|
||||
|
||||
def get_resources(self):
|
||||
resources = []
|
||||
resource = extensions.ResourceExtension('os-services',
|
||||
ServiceController())
|
||||
resources.append(resource)
|
||||
return resources
|
198
nova/tests/api/openstack/compute/contrib/test_services.py
Normal file
198
nova/tests/api/openstack/compute/contrib/test_services.py
Normal file
@ -0,0 +1,198 @@
|
||||
# Copyright 2012 IBM
|
||||
# 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 datetime import datetime
|
||||
from nova.api.openstack.compute.contrib import services
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova.openstack.common import timeutils
|
||||
from nova import test
|
||||
from nova.tests.api.openstack import fakes
|
||||
|
||||
|
||||
fake_services_list = [{'binary': 'nova-scheduler',
|
||||
'host': 'host1',
|
||||
'availability_zone': 'nova',
|
||||
'id': 1,
|
||||
'disabled': True,
|
||||
'updated_at': datetime(2012, 10, 29, 13, 42, 2),
|
||||
'created_at': datetime(2012, 9, 18, 2, 46, 27)},
|
||||
{'binary': 'nova-compute',
|
||||
'host': 'host1',
|
||||
'availability_zone': 'nova',
|
||||
'id': 2,
|
||||
'disabled': True,
|
||||
'updated_at': datetime(2012, 10, 29, 13, 42, 5),
|
||||
'created_at': datetime(2012, 9, 18, 2, 46, 27)},
|
||||
{'binary': 'nova-scheduler',
|
||||
'host': 'host2',
|
||||
'availability_zone': 'nova',
|
||||
'id': 3,
|
||||
'disabled': False,
|
||||
'updated_at': datetime(2012, 9, 19, 6, 55, 34),
|
||||
'created_at': datetime(2012, 9, 18, 2, 46, 28)},
|
||||
{'binary': 'nova-compute',
|
||||
'host': 'host2',
|
||||
'availability_zone': 'nova',
|
||||
'id': 4,
|
||||
'disabled': True,
|
||||
'updated_at': datetime(2012, 9, 18, 8, 3, 38),
|
||||
'created_at': datetime(2012, 9, 18, 2, 46, 28)},
|
||||
]
|
||||
|
||||
|
||||
class FakeRequest(object):
|
||||
environ = {"nova.context": context.get_admin_context()}
|
||||
GET = {}
|
||||
|
||||
|
||||
class FakeRequestWithSevice(object):
|
||||
environ = {"nova.context": context.get_admin_context()}
|
||||
GET = {"service": "nova-compute"}
|
||||
|
||||
|
||||
class FakeRequestWithHost(object):
|
||||
environ = {"nova.context": context.get_admin_context()}
|
||||
GET = {"host": "host1"}
|
||||
|
||||
|
||||
class FakeRequestWithHostService(object):
|
||||
environ = {"nova.context": context.get_admin_context()}
|
||||
GET = {"host": "host1", "service": "nova-compute"}
|
||||
|
||||
|
||||
def fake_servcie_get_all(context):
|
||||
return fake_services_list
|
||||
|
||||
|
||||
def fake_service_get_by_host_binary(context, host, binary):
|
||||
for service in fake_services_list:
|
||||
if service['host'] == host and service['binary'] == binary:
|
||||
return service
|
||||
return None
|
||||
|
||||
|
||||
def fake_service_get_by_id(value):
|
||||
for service in fake_services_list:
|
||||
if service['id'] == value:
|
||||
return service
|
||||
return None
|
||||
|
||||
|
||||
def fake_service_update(context, service_id, values):
|
||||
service = fake_service_get_by_id(service_id)
|
||||
if service is None:
|
||||
raise exception.ServiceNotFound(service_id=service_id)
|
||||
else:
|
||||
{'host': 'host1', 'service': 'nova-compute',
|
||||
'disabled': values['disabled']}
|
||||
|
||||
|
||||
def fake_utcnow():
|
||||
return datetime(2012, 10, 29, 13, 42, 11)
|
||||
|
||||
|
||||
class ServicesTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ServicesTest, self).setUp()
|
||||
|
||||
self.stubs.Set(db, "service_get_all", fake_servcie_get_all)
|
||||
self.stubs.Set(timeutils, "utcnow", fake_utcnow)
|
||||
self.stubs.Set(db, "service_get_by_args",
|
||||
fake_service_get_by_host_binary)
|
||||
self.stubs.Set(db, "service_update", fake_service_update)
|
||||
|
||||
self.context = context.get_admin_context()
|
||||
self.controller = services.ServiceController()
|
||||
|
||||
def tearDown(self):
|
||||
super(ServicesTest, self).tearDown()
|
||||
|
||||
def test_services_list(self):
|
||||
req = FakeRequest()
|
||||
res_dict = self.controller.index(req)
|
||||
|
||||
response = {'services': [{'binary': 'nova-scheduler',
|
||||
'host': 'host1', 'zone': 'nova',
|
||||
'status': 'disabled', 'state': 'up',
|
||||
'updated_at': datetime(2012, 10, 29, 13, 42, 2)},
|
||||
{'binary': 'nova-compute',
|
||||
'host': 'host1', 'zone': 'nova',
|
||||
'status': 'disabled', 'state': 'up',
|
||||
'updated_at': datetime(2012, 10, 29, 13, 42, 5)},
|
||||
{'binary': 'nova-scheduler', 'host': 'host2',
|
||||
'zone': 'nova',
|
||||
'status': 'enabled', 'state': 'down',
|
||||
'updated_at': datetime(2012, 9, 19, 6, 55, 34)},
|
||||
{'binary': 'nova-compute', 'host': 'host2',
|
||||
'zone': 'nova',
|
||||
'status': 'disabled', 'state': 'down',
|
||||
'updated_at': datetime(2012, 9, 18, 8, 3, 38)}]}
|
||||
self.assertEqual(res_dict, response)
|
||||
|
||||
def test_services_list_with_host(self):
|
||||
req = FakeRequestWithHost()
|
||||
res_dict = self.controller.index(req)
|
||||
|
||||
response = {'services': [{'binary': 'nova-scheduler', 'host': 'host1',
|
||||
'zone': 'nova',
|
||||
'status': 'disabled', 'state': 'up',
|
||||
'updated_at': datetime(2012, 10, 29, 13, 42, 2)},
|
||||
{'binary': 'nova-compute', 'host': 'host1',
|
||||
'zone': 'nova',
|
||||
'status': 'disabled', 'state': 'up',
|
||||
'updated_at': datetime(2012, 10, 29, 13, 42, 5)}]}
|
||||
self.assertEqual(res_dict, response)
|
||||
|
||||
def test_services_list_with_service(self):
|
||||
req = FakeRequestWithSevice()
|
||||
res_dict = self.controller.index(req)
|
||||
|
||||
response = {'services': [{'binary': 'nova-compute', 'host': 'host1',
|
||||
'zone': 'nova',
|
||||
'status': 'disabled', 'state': 'up',
|
||||
'updated_at': datetime(2012, 10, 29, 13, 42, 5)},
|
||||
{'binary': 'nova-compute', 'host': 'host2',
|
||||
'zone': 'nova',
|
||||
'status': 'disabled', 'state': 'down',
|
||||
'updated_at': datetime(2012, 9, 18, 8, 3, 38)}]}
|
||||
self.assertEqual(res_dict, response)
|
||||
|
||||
def test_services_list_with_host_service(self):
|
||||
req = FakeRequestWithHostService()
|
||||
res_dict = self.controller.index(req)
|
||||
|
||||
response = {'services': [{'binary': 'nova-compute', 'host': 'host1',
|
||||
'zone': 'nova',
|
||||
'status': 'disabled', 'state': 'up',
|
||||
'updated_at': datetime(2012, 10, 29, 13, 42, 5)}]}
|
||||
self.assertEqual(res_dict, response)
|
||||
|
||||
def test_services_enable(self):
|
||||
body = {'host': 'host1', 'service': 'nova-compute'}
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/os-services/enable')
|
||||
res_dict = self.controller.update(req, "enable", body)
|
||||
|
||||
self.assertEqual(res_dict['disabled'], False)
|
||||
|
||||
def test_services_disable(self):
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/os-services/disable')
|
||||
body = {'host': 'host1', 'service': 'nova-compute'}
|
||||
res_dict = self.controller.update(req, "disable", body)
|
||||
|
||||
self.assertEqual(res_dict['disabled'], True)
|
@ -189,6 +189,7 @@ class ExtensionControllerTest(ExtensionTestCase):
|
||||
"SecurityGroups",
|
||||
"ServerDiagnostics",
|
||||
"ServerStartStop",
|
||||
"Services",
|
||||
"SimpleTenantUsage",
|
||||
"UsedLimits",
|
||||
"UserData",
|
||||
|
@ -208,6 +208,14 @@
|
||||
"namespace": "http://docs.openstack.org/compute/ext/hosts/api/v1.1",
|
||||
"updated": "%(timestamp)s"
|
||||
},
|
||||
{
|
||||
"alias": "os-services",
|
||||
"description": "%(text)s",
|
||||
"links": [],
|
||||
"name": "Services",
|
||||
"namespace": "http://docs.openstack.org/compute/ext/services/api/v2",
|
||||
"updated": "%(timestamp)s"
|
||||
},
|
||||
{
|
||||
"alias": "os-hypervisors",
|
||||
"description": "%(text)s",
|
||||
|
@ -78,6 +78,9 @@
|
||||
<extension alias="os-hosts" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/hosts/api/v1.1" name="Hosts">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
<extension alias="os-services" name="Services" namespace="http://docs.openstack.org/compute/ext/services/api/v2" updated="%(timestamp)s">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
<extension alias="os-hypervisors" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/hypervisors/api/v1.1" name="Hypervisors">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
|
@ -117,6 +117,7 @@
|
||||
"compute_extension:rescue": "",
|
||||
"compute_extension:security_groups": "",
|
||||
"compute_extension:server_diagnostics": "",
|
||||
"compute_extension:services": "",
|
||||
"compute_extension:simple_tenant_usage:show": "",
|
||||
"compute_extension:simple_tenant_usage:list": "",
|
||||
"compute_extension:users": "",
|
||||
|
Loading…
x
Reference in New Issue
Block a user