228 lines
7.7 KiB
Python
228 lines
7.7 KiB
Python
# Copyright 2012 IBM Corp.
|
|
#
|
|
# 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.config import cfg
|
|
import webob.exc
|
|
|
|
from nova.api.openstack import extensions
|
|
from nova.api.openstack import wsgi
|
|
from nova.api.openstack import xmlutil
|
|
from nova import compute
|
|
from nova import exception
|
|
from nova.openstack.common.gettextutils import _
|
|
from nova import servicegroup
|
|
from nova import utils
|
|
|
|
authorize = extensions.extension_authorizer('compute', 'services')
|
|
CONF = cfg.CONF
|
|
CONF.import_opt('service_down_time', 'nova.service')
|
|
|
|
|
|
class ServicesIndexTemplate(xmlutil.TemplateBuilder):
|
|
def construct(self):
|
|
root = xmlutil.TemplateElement('services')
|
|
elem = xmlutil.SubTemplateElement(root, 'service', selector='services')
|
|
elem.set('id')
|
|
elem.set('binary')
|
|
elem.set('host')
|
|
elem.set('zone')
|
|
elem.set('status')
|
|
elem.set('state')
|
|
elem.set('updated_at')
|
|
elem.set('disabled_reason')
|
|
|
|
return xmlutil.MasterTemplate(root, 1)
|
|
|
|
|
|
class ServiceUpdateTemplate(xmlutil.TemplateBuilder):
|
|
def construct(self):
|
|
root = xmlutil.TemplateElement('service', selector='service')
|
|
root.set('host')
|
|
root.set('binary')
|
|
root.set('status')
|
|
root.set('disabled_reason')
|
|
|
|
return xmlutil.MasterTemplate(root, 1)
|
|
|
|
|
|
class ServiceUpdateDeserializer(wsgi.XMLDeserializer):
|
|
def default(self, string):
|
|
node = xmlutil.safe_minidom_parse_string(string)
|
|
service = {}
|
|
service_node = self.find_first_child_named(node, 'service')
|
|
if service_node is None:
|
|
return service
|
|
service['host'] = service_node.getAttribute('host')
|
|
service['binary'] = service_node.getAttribute('binary')
|
|
service['disabled_reason'] = service_node.getAttribute(
|
|
'disabled_reason')
|
|
|
|
return dict(body=service)
|
|
|
|
|
|
class ServiceController(object):
|
|
|
|
def __init__(self, ext_mgr=None, *args, **kwargs):
|
|
self.host_api = compute.HostAPI()
|
|
self.servicegroup_api = servicegroup.API()
|
|
self.ext_mgr = ext_mgr
|
|
|
|
def _get_services(self, req):
|
|
context = req.environ['nova.context']
|
|
authorize(context)
|
|
services = self.host_api.service_get_all(
|
|
context, set_zones=True)
|
|
|
|
host = ''
|
|
if 'host' in req.GET:
|
|
host = req.GET['host']
|
|
binary = ''
|
|
if 'binary' in req.GET:
|
|
binary = req.GET['binary']
|
|
if host:
|
|
services = [s for s in services if s['host'] == host]
|
|
if binary:
|
|
services = [s for s in services if s['binary'] == binary]
|
|
|
|
return services
|
|
|
|
def _get_service_detail(self, svc, detailed):
|
|
alive = self.servicegroup_api.service_is_up(svc)
|
|
state = (alive and "up") or "down"
|
|
active = 'enabled'
|
|
if svc['disabled']:
|
|
active = 'disabled'
|
|
service_detail = {'binary': svc['binary'], 'host': svc['host'],
|
|
'zone': svc['availability_zone'],
|
|
'status': active, 'state': state,
|
|
'updated_at': svc['updated_at']}
|
|
if self.ext_mgr.is_loaded('os-extended-services-delete'):
|
|
service_detail['id'] = svc['id']
|
|
if detailed:
|
|
service_detail['disabled_reason'] = svc['disabled_reason']
|
|
|
|
return service_detail
|
|
|
|
def _get_services_list(self, req, detailed):
|
|
services = self._get_services(req)
|
|
svcs = []
|
|
for svc in services:
|
|
svcs.append(self._get_service_detail(svc, detailed))
|
|
|
|
return svcs
|
|
|
|
def _is_valid_as_reason(self, reason):
|
|
try:
|
|
utils.check_string_length(reason.strip(), 'Disabled reason',
|
|
min_length=1, max_length=255)
|
|
except exception.InvalidInput:
|
|
return False
|
|
|
|
return True
|
|
|
|
@wsgi.response(204)
|
|
def delete(self, req, id):
|
|
"""Deletes the specified service."""
|
|
if not self.ext_mgr.is_loaded('os-extended-services-delete'):
|
|
raise webob.exc.HTTPMethodNotAllowed()
|
|
|
|
context = req.environ['nova.context']
|
|
authorize(context)
|
|
|
|
try:
|
|
self.host_api.service_delete(context, id)
|
|
except exception.ServiceNotFound:
|
|
explanation = _("Service %s not found.") % id
|
|
raise webob.exc.HTTPNotFound(explanation=explanation)
|
|
|
|
@wsgi.serializers(xml=ServicesIndexTemplate)
|
|
def index(self, req):
|
|
"""Return a list of all running services."""
|
|
detailed = self.ext_mgr.is_loaded('os-extended-services')
|
|
services = self._get_services_list(req, detailed)
|
|
|
|
return {'services': services}
|
|
|
|
@wsgi.deserializers(xml=ServiceUpdateDeserializer)
|
|
@wsgi.serializers(xml=ServiceUpdateTemplate)
|
|
def update(self, req, id, body):
|
|
"""Enable/Disable scheduling for a service."""
|
|
context = req.environ['nova.context']
|
|
authorize(context)
|
|
|
|
ext_loaded = self.ext_mgr.is_loaded('os-extended-services')
|
|
if id == "enable":
|
|
disabled = False
|
|
status = "enabled"
|
|
elif (id == "disable" or
|
|
(id == "disable-log-reason" and ext_loaded)):
|
|
disabled = True
|
|
status = "disabled"
|
|
else:
|
|
msg = _("Unknown action")
|
|
raise webob.exc.HTTPNotFound(explanation=msg)
|
|
try:
|
|
host = body['host']
|
|
binary = body['binary']
|
|
ret_value = {
|
|
'service': {
|
|
'host': host,
|
|
'binary': binary,
|
|
'status': status,
|
|
},
|
|
}
|
|
status_detail = {
|
|
'disabled': disabled,
|
|
'disabled_reason': None,
|
|
}
|
|
if id == "disable-log-reason":
|
|
reason = body['disabled_reason']
|
|
if not self._is_valid_as_reason(reason):
|
|
msg = _('The string containing the reason for disabling '
|
|
'the service contains invalid characters or is '
|
|
'too long.')
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
|
|
status_detail['disabled_reason'] = reason
|
|
ret_value['service']['disabled_reason'] = reason
|
|
except (TypeError, KeyError):
|
|
msg = _('Invalid attribute in the request')
|
|
if 'host' in body and 'binary' in body:
|
|
msg = _('Missing disabled reason field')
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
|
|
try:
|
|
self.host_api.service_update(context, host, binary, status_detail)
|
|
except exception.HostBinaryNotFound as e:
|
|
raise webob.exc.HTTPNotFound(explanation=e.format_message())
|
|
|
|
return ret_value
|
|
|
|
|
|
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:00Z"
|
|
|
|
def get_resources(self):
|
|
resources = []
|
|
resource = extensions.ResourceExtension('os-services',
|
|
ServiceController(self.ext_mgr))
|
|
|
|
resources.append(resource)
|
|
return resources
|