Add support for getting Service Status
Change-Id: Iaf10d8486ac8015ecf9f394dfbf074bfb863fb78
This commit is contained in:
parent
b5448804c0
commit
7abae80c61
@ -21,6 +21,7 @@ from designate.i18n import _LI
|
||||
from designate import exceptions
|
||||
from designate import utils
|
||||
from designate import service
|
||||
from designate import service_status
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -30,6 +31,23 @@ class Service(service.WSGIService, service.Service):
|
||||
def __init__(self, threads=None):
|
||||
super(Service, self).__init__(threads=threads)
|
||||
|
||||
emitter_cls = service_status.HeartBeatEmitter.get_driver(
|
||||
cfg.CONF.heartbeat_emitter.emitter_type
|
||||
)
|
||||
self.heartbeat_emitter = emitter_cls(
|
||||
self.service_name, self.tg, status_factory=self._get_status
|
||||
)
|
||||
|
||||
def start(self):
|
||||
super(Service, self).start()
|
||||
self.heartbeat_emitter.start()
|
||||
|
||||
def _get_status(self):
|
||||
status = "UP"
|
||||
stats = {}
|
||||
capabilities = {}
|
||||
return status, stats, capabilities
|
||||
|
||||
@property
|
||||
def service_name(self):
|
||||
return 'api'
|
||||
|
@ -23,6 +23,7 @@ from designate.api.v2.controllers import tlds
|
||||
from designate.api.v2.controllers import blacklists
|
||||
from designate.api.v2.controllers import errors
|
||||
from designate.api.v2.controllers import pools
|
||||
from designate.api.v2.controllers import service_status
|
||||
from designate.api.v2.controllers import zones
|
||||
from designate.api.v2.controllers import tsigkeys
|
||||
|
||||
@ -57,4 +58,5 @@ class RootController(object):
|
||||
blacklists = blacklists.BlacklistsController()
|
||||
errors = errors.ErrorsController()
|
||||
pools = pools.PoolsController()
|
||||
service_statuses = service_status.ServiceStatusController()
|
||||
tsigkeys = tsigkeys.TsigKeysController()
|
||||
|
61
designate/api/v2/controllers/service_status.py
Normal file
61
designate/api/v2/controllers/service_status.py
Normal file
@ -0,0 +1,61 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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 pecan
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate import utils
|
||||
from designate.api.v2.controllers import rest
|
||||
from designate.objects.adapters import DesignateAdapter
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServiceStatusController(rest.RestController):
|
||||
SORT_KEYS = ['created_at', 'id', 'updated_at', 'hostname', 'service_name',
|
||||
'status']
|
||||
|
||||
@pecan.expose(template='json:', content_type='application/json')
|
||||
def get_all(self, **params):
|
||||
request = pecan.request
|
||||
context = pecan.request.environ['context']
|
||||
|
||||
marker, limit, sort_key, sort_dir = utils.get_paging_params(
|
||||
params, self.SORT_KEYS)
|
||||
|
||||
accepted_filters = ["hostname", "service_name", "status"]
|
||||
criterion = self._apply_filter_params(
|
||||
params, accepted_filters, {})
|
||||
|
||||
service_statuses = self.central_api.find_service_statuses(
|
||||
context, criterion, )
|
||||
|
||||
return DesignateAdapter.render(
|
||||
'API_v2',
|
||||
service_statuses,
|
||||
request=request)
|
||||
|
||||
@pecan.expose(template='json:', content_type='application/json')
|
||||
@utils.validate_uuid('service_id')
|
||||
def get_one(self, service_id):
|
||||
"""Get Service Status"""
|
||||
request = pecan.request
|
||||
context = request.environ['context']
|
||||
|
||||
criterion = {"id": service_id}
|
||||
service_status = self.central_api.find_service_status(
|
||||
context, criterion)
|
||||
|
||||
return DesignateAdapter.render(
|
||||
'API_v2', service_status, request=request)
|
@ -54,14 +54,15 @@ class CentralAPI(object):
|
||||
5.5 - Add deleted zone purging task
|
||||
5.6 - Changed 'purge_zones' function args
|
||||
6.0 - Renamed domains to zones
|
||||
6.1 - Add ServiceStatus methods
|
||||
"""
|
||||
RPC_API_VERSION = '6.0'
|
||||
RPC_API_VERSION = '6.1'
|
||||
|
||||
def __init__(self, topic=None):
|
||||
topic = topic if topic else cfg.CONF.central_topic
|
||||
|
||||
target = messaging.Target(topic=topic, version=self.RPC_API_VERSION)
|
||||
self.client = rpc.get_client(target, version_cap='6.0')
|
||||
self.client = rpc.get_client(target, version_cap='6.1')
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
@ -578,3 +579,24 @@ class CentralAPI(object):
|
||||
"delete_zone_export."))
|
||||
return self.client.call(context, 'delete_zone_export',
|
||||
zone_export_id=zone_export_id)
|
||||
|
||||
def find_service_status(self, context, criterion=None):
|
||||
LOG.info(_LI("find_service_status: Calling central's "
|
||||
"find_service_status."))
|
||||
return self.client.call(context, 'find_service_status',
|
||||
criterion=criterion)
|
||||
|
||||
def find_service_statuses(self, context, criterion=None, marker=None,
|
||||
limit=None, sort_key=None, sort_dir=None):
|
||||
LOG.info(_LI("find_service_statuses: Calling central's "
|
||||
"find_service_statuses."))
|
||||
return self.client.call(context, 'find_service_statuses',
|
||||
criterion=criterion, marker=marker,
|
||||
limit=limit, sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
def update_service_status(self, context, service_status):
|
||||
LOG.info(_LI("update_service_status: Calling central's "
|
||||
"update_service_status."))
|
||||
self.client.cast(context, 'update_service_status',
|
||||
service_status=service_status)
|
||||
|
@ -263,7 +263,7 @@ def notification(notification_type):
|
||||
|
||||
|
||||
class Service(service.RPCService, service.Service):
|
||||
RPC_API_VERSION = '6.0'
|
||||
RPC_API_VERSION = '6.1'
|
||||
|
||||
target = messaging.Target(version=RPC_API_VERSION)
|
||||
|
||||
@ -272,6 +272,10 @@ class Service(service.RPCService, service.Service):
|
||||
|
||||
self.network_api = network_api.get_network_api(cfg.CONF.network_api)
|
||||
|
||||
# update_service_status needs is called by the emitter so we pass
|
||||
# ourselves as the rpc_api.
|
||||
self.heartbeat_emitter.rpc_api = self
|
||||
|
||||
@property
|
||||
def scheduler(self):
|
||||
if not hasattr(self, '_scheduler'):
|
||||
@ -2880,3 +2884,38 @@ class Service(service.RPCService, service.Service):
|
||||
zone_export = self.storage.delete_zone_export(context, zone_export_id)
|
||||
|
||||
return zone_export
|
||||
|
||||
def find_service_statuses(self, context, criterion=None, marker=None,
|
||||
limit=None, sort_key=None, sort_dir=None):
|
||||
"""List service statuses.
|
||||
"""
|
||||
policy.check('find_service_statuses', context)
|
||||
|
||||
return self.storage.find_service_statuses(
|
||||
context, criterion, marker, limit, sort_key, sort_dir)
|
||||
|
||||
def find_service_status(self, context, criterion=None):
|
||||
policy.check('find_service_status', context)
|
||||
|
||||
return self.storage.find_service_status(context, criterion)
|
||||
|
||||
def update_service_status(self, context, service_status):
|
||||
policy.check('update_service_status', context)
|
||||
|
||||
criterion = {
|
||||
"service_name": service_status.service_name,
|
||||
"hostname": service_status.hostname
|
||||
}
|
||||
|
||||
if service_status.obj_attr_is_set('id'):
|
||||
criterion["id"] = service_status.id
|
||||
|
||||
try:
|
||||
db_status = self.storage.find_service_status(
|
||||
context, criterion)
|
||||
db_status.update(dict(service_status))
|
||||
|
||||
return self.storage.update_service_status(context, db_status)
|
||||
except exceptions.ServiceStatusNotFound:
|
||||
return self.storage.create_service_status(
|
||||
context, service_status)
|
||||
|
@ -247,6 +247,10 @@ class Duplicate(Base):
|
||||
error_type = 'duplicate'
|
||||
|
||||
|
||||
class DuplicateServiceStatus(Duplicate):
|
||||
error_type = 'duplicate_service_status'
|
||||
|
||||
|
||||
class DuplicateQuota(Duplicate):
|
||||
error_type = 'duplicate_quota'
|
||||
|
||||
@ -351,6 +355,10 @@ class NotFound(Base):
|
||||
error_type = 'not_found'
|
||||
|
||||
|
||||
class ServiceStatusNotFound(NotFound):
|
||||
error_type = 'service_status_not_found'
|
||||
|
||||
|
||||
class QuotaNotFound(NotFound):
|
||||
error_type = 'quota_not_found'
|
||||
|
||||
|
@ -34,6 +34,7 @@ from designate.objects.quota import Quota, QuotaList # noqa
|
||||
from designate.objects.record import Record, RecordList # noqa
|
||||
from designate.objects.recordset import RecordSet, RecordSetList # noqa
|
||||
from designate.objects.server import Server, ServerList # noqa
|
||||
from designate.objects.service_status import ServiceStatus, ServiceStatusList # noqa
|
||||
from designate.objects.tenant import Tenant, TenantList # noqa
|
||||
from designate.objects.tld import Tld, TldList # noqa
|
||||
from designate.objects.tsigkey import TsigKey, TsigKeyList # noqa
|
||||
|
@ -27,6 +27,7 @@ from designate.objects.adapters.api_v2.pool_ns_record import PoolNsRecordAPIv2Ad
|
||||
from designate.objects.adapters.api_v2.tld import TldAPIv2Adapter, TldListAPIv2Adapter # noqa
|
||||
from designate.objects.adapters.api_v2.tsigkey import TsigKeyAPIv2Adapter, TsigKeyListAPIv2Adapter # noqa
|
||||
from designate.objects.adapters.api_v2.quota import QuotaAPIv2Adapter, QuotaListAPIv2Adapter # noqa
|
||||
from designate.objects.adapters.api_v2.service_status import ServiceStatusAPIv2Adapter, ServiceStatusListAPIv2Adapter # noqa
|
||||
from designate.objects.adapters.api_v2.zone_transfer_accept import ZoneTransferAcceptAPIv2Adapter, ZoneTransferAcceptListAPIv2Adapter # noqa
|
||||
from designate.objects.adapters.api_v2.zone_transfer_request import ZoneTransferRequestAPIv2Adapter, ZoneTransferRequestListAPIv2Adapter # noqa
|
||||
from designate.objects.adapters.api_v2.validation_error import ValidationErrorAPIv2Adapter, ValidationErrorListAPIv2Adapter # noqa
|
||||
|
65
designate/objects/adapters/api_v2/service_status.py
Normal file
65
designate/objects/adapters/api_v2/service_status.py
Normal file
@ -0,0 +1,65 @@
|
||||
# Copyright 2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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_log import log as logging
|
||||
|
||||
from designate.objects.adapters.api_v2 import base
|
||||
from designate import objects
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServiceStatusAPIv2Adapter(base.APIv2Adapter):
|
||||
|
||||
ADAPTER_OBJECT = objects.ServiceStatus
|
||||
|
||||
MODIFICATIONS = {
|
||||
'fields': {
|
||||
"id": {},
|
||||
"hostname": {},
|
||||
"service_name": {},
|
||||
"status": {},
|
||||
"stats": {},
|
||||
"capabilities": {},
|
||||
"heartbeated_at": {},
|
||||
"created_at": {},
|
||||
"updated_at": {},
|
||||
},
|
||||
'options': {
|
||||
'links': True,
|
||||
'resource_name': 'service_status',
|
||||
'collection_name': 'service_statuses',
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _render_object(cls, object, *args, **kwargs):
|
||||
obj = super(ServiceStatusAPIv2Adapter, cls)._render_object(
|
||||
object, *args, **kwargs)
|
||||
|
||||
obj['links']['self'] = \
|
||||
'%s/v2/%s/%s' % (cls.BASE_URI, 'service_statuses', obj['id'])
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
class ServiceStatusListAPIv2Adapter(base.APIv2Adapter):
|
||||
|
||||
ADAPTER_OBJECT = objects.ServiceStatusList
|
||||
|
||||
MODIFICATIONS = {
|
||||
'options': {
|
||||
'links': True,
|
||||
'resource_name': 'service_status',
|
||||
'collection_name': 'service_statuses',
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import six
|
||||
from six.moves.urllib import parse
|
||||
import jsonschema
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from designate import exceptions
|
||||
from designate.schema import validators
|
||||
@ -179,6 +180,11 @@ class DesignateObject(object):
|
||||
if isinstance(value, dict) and 'designate_object.name' in value:
|
||||
setattr(instance, field, DesignateObject.from_primitive(value))
|
||||
else:
|
||||
# data typically doesn't have a schema..
|
||||
schema = cls.FIELDS[field].get("schema", None)
|
||||
if schema is not None and value is not None:
|
||||
if "format" in schema and schema["format"] == "date-time":
|
||||
value = timeutils.parse_strtime(value)
|
||||
setattr(instance, field, value)
|
||||
|
||||
instance._obj_changes = set(primitive['designate_object.changes'])
|
||||
|
61
designate/objects/service_status.py
Normal file
61
designate/objects/service_status.py
Normal file
@ -0,0 +1,61 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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 designate.objects import base
|
||||
|
||||
|
||||
class ServiceStatus(base.PersistentObjectMixin,
|
||||
base.DictObjectMixin,
|
||||
base.DesignateObject):
|
||||
FIELDS = {
|
||||
"service_name": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"hostname": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"heartbeated_at": {
|
||||
"schema": {
|
||||
'type': ['string', 'null'],
|
||||
'format': 'date-time'
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["UP", "DOWN", "WARNING"]
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
STRING_FIELDS = [
|
||||
'service_name', 'hostname', 'status'
|
||||
]
|
||||
|
||||
|
||||
class ServiceStatusList(base.ListObjectMixin, base.DesignateObject):
|
||||
LIST_ITEM_TYPE = ServiceStatus
|
@ -35,11 +35,13 @@ from designate.i18n import _
|
||||
from designate.i18n import _LE
|
||||
from designate.i18n import _LI
|
||||
from designate.i18n import _LW
|
||||
from designate import rpc
|
||||
from designate import policy
|
||||
from designate import rpc
|
||||
from designate import service_status
|
||||
from designate import version
|
||||
from designate import utils
|
||||
|
||||
|
||||
# TODO(kiall): These options have been cut+paste from the old WSGI code, and
|
||||
# should be moved into service:api etc..
|
||||
wsgi_socket_opts = [
|
||||
@ -108,6 +110,19 @@ class RPCService(object):
|
||||
messaging.Target(topic=self._rpc_topic, server=self._host),
|
||||
self._rpc_endpoints)
|
||||
|
||||
emitter_cls = service_status.HeartBeatEmitter.get_driver(
|
||||
cfg.CONF.heartbeat_emitter.emitter_type
|
||||
)
|
||||
self.heartbeat_emitter = emitter_cls(
|
||||
self.service_name, self.tg, status_factory=self._get_status
|
||||
)
|
||||
|
||||
def _get_status(self):
|
||||
status = "UP"
|
||||
stats = {}
|
||||
capabilities = {}
|
||||
return status, stats, capabilities
|
||||
|
||||
@property
|
||||
def _rpc_endpoints(self):
|
||||
return [self]
|
||||
@ -130,8 +145,11 @@ class RPCService(object):
|
||||
if e != self and hasattr(e, 'start'):
|
||||
e.start()
|
||||
|
||||
self.heartbeat_emitter.start()
|
||||
|
||||
def stop(self):
|
||||
LOG.debug("Stopping RPC server on topic '%s'" % self._rpc_topic)
|
||||
self.heartbeat_emitter.stop()
|
||||
|
||||
for e in self._rpc_endpoints:
|
||||
if e != self and hasattr(e, 'stop'):
|
||||
|
114
designate/service_status.py
Normal file
114
designate/service_status.py
Normal file
@ -0,0 +1,114 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from designate import context
|
||||
from designate import objects
|
||||
from designate import plugin
|
||||
from designate.central import rpcapi as central_rpcapi
|
||||
|
||||
heartbeat_opts = [
|
||||
cfg.FloatOpt('heartbeat_interval',
|
||||
default=5.0,
|
||||
help='Number of seconds between heartbeats for reporting '
|
||||
'state'),
|
||||
cfg.StrOpt('emitter_type', default="rpc", help="Emitter to use")
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(heartbeat_opts, group="heartbeat_emitter")
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HeartBeatEmitter(plugin.DriverPlugin):
|
||||
__plugin_ns__ = 'designate.heartbeat_emitter'
|
||||
__plugin_type__ = 'heartbeat_emitter'
|
||||
|
||||
def __init__(self, service, threadgroup, status_factory=None):
|
||||
super(HeartBeatEmitter, self).__init__()
|
||||
|
||||
self._service = service
|
||||
self._hostname = CONF.host
|
||||
|
||||
self._running = False
|
||||
self._tg = threadgroup
|
||||
self._tg.add_timer(
|
||||
cfg.CONF.heartbeat_emitter.heartbeat_interval,
|
||||
self._emit_heartbeat)
|
||||
|
||||
self._status_factory = status_factory
|
||||
|
||||
def _get_status(self):
|
||||
if self._status_factory is not None:
|
||||
return self._status_factory()
|
||||
|
||||
return True, {}, {}
|
||||
|
||||
def _emit_heartbeat(self):
|
||||
"""
|
||||
Returns Status, Stats, Capabilities
|
||||
"""
|
||||
if not self._running:
|
||||
return
|
||||
|
||||
LOG.debug("Emitting heartbeat...")
|
||||
|
||||
status, stats, capabilities = self._get_status()
|
||||
|
||||
service_status = objects.ServiceStatus(
|
||||
service_name=self._service,
|
||||
hostname=self._hostname,
|
||||
status=status,
|
||||
stats=stats,
|
||||
capabilities=capabilities,
|
||||
heartbeated_at=timeutils.utcnow()
|
||||
)
|
||||
|
||||
self._transmit(service_status)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _transmit(self, status):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
self._running = True
|
||||
|
||||
def stop(self):
|
||||
self._running = False
|
||||
|
||||
|
||||
class NoopEmitter(HeartBeatEmitter):
|
||||
__plugin_name__ = 'noop'
|
||||
|
||||
def _transmit(self, status):
|
||||
LOG.debug(status)
|
||||
|
||||
|
||||
class RpcEmitter(HeartBeatEmitter):
|
||||
__plugin_name__ = 'rpc'
|
||||
|
||||
def __init__(self, service, thread_group, rpc_api=None, *args, **kwargs):
|
||||
super(RpcEmitter, self).__init__(
|
||||
service, thread_group, *args, **kwargs)
|
||||
self.rpc_api = rpc_api
|
||||
|
||||
def _transmit(self, status):
|
||||
admin_context = context.DesignateContext.get_admin_context()
|
||||
api = self.rpc_api or central_rpcapi.CentralAPI.get_instance()
|
||||
api.update_service_status(admin_context, status)
|
@ -766,3 +766,37 @@ class Storage(DriverPlugin):
|
||||
return {
|
||||
'status': None
|
||||
}
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_service_statuses(self, context, criterion=None, marker=None,
|
||||
limit=None, sort_key=None, sort_dir=None):
|
||||
"""
|
||||
Retrieve status for services
|
||||
|
||||
:param context: RPC Context.
|
||||
:param criterion: Criteria to filter by.
|
||||
:param marker: Resource ID from which after the requested page will
|
||||
start after
|
||||
:param limit: Integer limit of objects of the page size after the
|
||||
marker
|
||||
:param sort_key: Key from which to sort after.
|
||||
:param sort_dir: Direction to sort after using sort_key.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_service_status(self, context, criterion):
|
||||
"""
|
||||
Find a single Service Status.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param criterion: Criteria to filter by.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_service_status(self, context, service_status):
|
||||
"""
|
||||
Update the Service status for a service.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param service_status: Set the status for a service.
|
||||
"""
|
||||
|
@ -1922,6 +1922,35 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
||||
|
||||
return result[0]
|
||||
|
||||
# Service Status Methods
|
||||
def _find_service_statuses(self, context, criterion, one=False,
|
||||
marker=None, limit=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
return self._find(
|
||||
context, tables.service_status, objects.ServiceStatus,
|
||||
objects.ServiceStatusList, exceptions.ServiceStatusNotFound,
|
||||
criterion, one, marker, limit, sort_key, sort_dir)
|
||||
|
||||
def find_service_status(self, context, criterion):
|
||||
return self._find_service_statuses(context, criterion, one=True)
|
||||
|
||||
def find_service_statuses(self, context, criterion=None, marker=None,
|
||||
limit=None, sort_key=None, sort_dir=None):
|
||||
return self._find_service_statuses(context, criterion, marker=marker,
|
||||
limit=limit, sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
def create_service_status(self, context, service_status):
|
||||
return self._create(
|
||||
tables.service_status, service_status,
|
||||
exceptions.DuplicateServiceStatus)
|
||||
|
||||
def update_service_status(self, context, service_status):
|
||||
return self._update(
|
||||
context, tables.service_status, service_status,
|
||||
exceptions.DuplicateServiceStatus,
|
||||
exceptions.ServiceStatusNotFound)
|
||||
|
||||
# diagnostics
|
||||
def ping(self, context):
|
||||
start_time = time.time()
|
||||
|
@ -0,0 +1,52 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Add Service Status tables"""
|
||||
|
||||
|
||||
from oslo_log import log as logging
|
||||
from sqlalchemy import String, DateTime, Enum, Text
|
||||
from sqlalchemy.schema import Table, Column, MetaData
|
||||
|
||||
from designate import utils
|
||||
from designate.sqlalchemy.types import UUID
|
||||
|
||||
LOG = logging.getLogger()
|
||||
|
||||
meta = MetaData()
|
||||
|
||||
SERVICE_STATES = [
|
||||
"UP", "DOWN", "WARNING"
|
||||
]
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta.bind = migrate_engine
|
||||
|
||||
status_enum = Enum(name='service_statuses', metadata=meta, *SERVICE_STATES)
|
||||
status_enum.create()
|
||||
|
||||
service_status_table = Table('service_statuses', meta,
|
||||
Column('id', UUID(), default=utils.generate_uuid, primary_key=True),
|
||||
Column('created_at', DateTime),
|
||||
Column('updated_at', DateTime),
|
||||
|
||||
Column('service_name', String(40), nullable=False),
|
||||
Column('hostname', String(255), nullable=False),
|
||||
Column('heartbeated_at', DateTime, nullable=True),
|
||||
Column('status', status_enum, nullable=False),
|
||||
Column('stats', Text, nullable=False),
|
||||
Column('capabilities', Text, nullable=False),
|
||||
)
|
||||
service_status_table.create()
|
@ -18,6 +18,7 @@ from sqlalchemy import (Table, MetaData, Column, String, Text, Integer,
|
||||
UniqueConstraint, ForeignKeyConstraint)
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_db.sqlalchemy import types
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from designate import utils
|
||||
@ -39,6 +40,9 @@ ACTIONS = ['CREATE', 'DELETE', 'UPDATE', 'NONE']
|
||||
ZONE_TYPES = ('PRIMARY', 'SECONDARY',)
|
||||
ZONE_TASK_TYPES = ['IMPORT', 'EXPORT']
|
||||
|
||||
SERVICE_STATES = [
|
||||
"UP", "DOWN", "WARNING"
|
||||
]
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
@ -51,6 +55,25 @@ def default_shard(context, id_col):
|
||||
return int(context.current_parameters[id_col][0:3], 16)
|
||||
|
||||
|
||||
service_status = Table("service_statuses", metadata,
|
||||
Column('id', UUID, default=utils.generate_uuid, primary_key=True),
|
||||
Column('created_at', DateTime, default=lambda: timeutils.utcnow()),
|
||||
Column('updated_at', DateTime, onupdate=lambda: timeutils.utcnow()),
|
||||
|
||||
Column('service_name', String(40), nullable=False),
|
||||
Column('hostname', String(255), nullable=False),
|
||||
Column('heartbeated_at', DateTime, nullable=True),
|
||||
|
||||
Column('status', Enum(name='service_statuses', *SERVICE_STATES),
|
||||
nullable=False),
|
||||
Column('stats', types.JsonEncodedDict, nullable=False),
|
||||
Column('capabilities', types.JsonEncodedDict, nullable=False),
|
||||
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8',
|
||||
)
|
||||
|
||||
|
||||
quotas = Table('quotas', metadata,
|
||||
Column('id', UUID, default=utils.generate_uuid, primary_key=True),
|
||||
Column('version', Integer, default=1, nullable=False),
|
||||
|
@ -62,6 +62,14 @@ class TestTimeoutError(Exception):
|
||||
|
||||
|
||||
class TestCase(base.BaseTestCase):
|
||||
service_status_fixtures = [{
|
||||
'service_name': 'foo',
|
||||
'hostname': 'bar',
|
||||
'status': "UP",
|
||||
'stats': {},
|
||||
'capabilities': {},
|
||||
}]
|
||||
|
||||
quota_fixtures = [{
|
||||
'resource': 'zones',
|
||||
'hard_limit': 5,
|
||||
@ -320,6 +328,11 @@ class TestCase(base.BaseTestCase):
|
||||
group='service:central'
|
||||
)
|
||||
|
||||
self.config(
|
||||
emitter_type="noop",
|
||||
group="heartbeat_emitter"
|
||||
)
|
||||
|
||||
self.config(
|
||||
auth_strategy='noauth',
|
||||
group='service:api'
|
||||
@ -604,6 +617,23 @@ class TestCase(base.BaseTestCase):
|
||||
_values.update(values)
|
||||
return _values
|
||||
|
||||
def get_service_status_fixture(self, fixture=0, values=None):
|
||||
values = values or {}
|
||||
|
||||
_values = copy.copy(self.service_status_fixtures[fixture])
|
||||
_values.update(values)
|
||||
return _values
|
||||
|
||||
def update_service_status(self, **kwargs):
|
||||
context = kwargs.pop('context', self.admin_context)
|
||||
fixture = kwargs.pop('fixture', 0)
|
||||
|
||||
values = self.get_service_status_fixture(
|
||||
fixture=fixture, values=kwargs)
|
||||
|
||||
return self.central_service.update_service_status(
|
||||
context, objects.ServiceStatus.from_dict(values))
|
||||
|
||||
def create_tld(self, **kwargs):
|
||||
context = kwargs.pop('context', self.admin_context)
|
||||
fixture = kwargs.pop('fixture', 0)
|
||||
|
84
designate/tests/test_api/test_v2/test_service_status.py
Normal file
84
designate/tests/test_api/test_v2/test_service_status.py
Normal file
@ -0,0 +1,84 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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_log import log as logging
|
||||
|
||||
from designate.tests.test_api.test_v2 import ApiV2TestCase
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApiV2ServiceStatusTest(ApiV2TestCase):
|
||||
def setUp(self):
|
||||
super(ApiV2ServiceStatusTest, self).setUp()
|
||||
|
||||
def test_get_service_statuses(self):
|
||||
# Set the policy file as this is an admin-only API
|
||||
self.policy({'find_service_statuses': '@'})
|
||||
|
||||
response = self.client.get('/service_statuses/')
|
||||
|
||||
# Check the headers are what we expect
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
||||
# Check the body structure is what we expect
|
||||
self.assertIn('service_statuses', response.json)
|
||||
self.assertIn('links', response.json)
|
||||
self.assertIn('self', response.json['links'])
|
||||
|
||||
# Test with 0 service_statuses
|
||||
# Seeing that Central is started there will be 1 here already..
|
||||
self.assertEqual(0, len(response.json['service_statuses']))
|
||||
|
||||
data = [self.update_service_status(
|
||||
hostname="foo%s" % i, service_name="bar") for i in range(0, 10)]
|
||||
|
||||
self._assert_paging(data, '/service_statuses', key='service_statuses')
|
||||
|
||||
def test_get_service_status(self):
|
||||
service_status = self.update_service_status(fixture=0)
|
||||
|
||||
# Set the policy file as this is an admin-only API
|
||||
self.policy({'find_service_status': '@'})
|
||||
|
||||
response = self.client.get(
|
||||
'/service_statuses/%s' % service_status['id'],
|
||||
headers=[('Accept', 'application/json')])
|
||||
|
||||
# Verify the headers
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
||||
# Verify the body structure
|
||||
self.assertIn('links', response.json)
|
||||
self.assertIn('self', response.json['links'])
|
||||
|
||||
# Verify the returned values
|
||||
self.assertIn('id', response.json)
|
||||
self.assertIn('created_at', response.json)
|
||||
self.assertIsNone(response.json['updated_at'])
|
||||
|
||||
fixture = self.get_service_status_fixture(0)
|
||||
self.assertEqual(fixture['hostname'], response.json['hostname'])
|
||||
self.assertEqual(fixture['service_name'],
|
||||
response.json['service_name'])
|
||||
self.assertEqual(fixture['capabilities'],
|
||||
response.json['capabilities'])
|
||||
self.assertEqual(fixture['stats'], response.json['stats'])
|
||||
self.assertEqual(fixture['status'], response.json['status'])
|
||||
self.assertEqual(None, response.json['heartbeated_at'])
|
||||
|
||||
def test_get_service_status_invalid_id(self):
|
||||
self.policy({'find_service_status': '@'})
|
||||
self._assert_invalid_uuid(self.client.get, '/service_statuses/%s')
|
@ -52,6 +52,7 @@ class SqlalchemyStorageTest(StorageTestCase, TestCase):
|
||||
u'quotas',
|
||||
u'records',
|
||||
u'recordsets',
|
||||
u'service_statuses',
|
||||
u'tlds',
|
||||
u'tsigkeys',
|
||||
u'zone_attributes',
|
||||
|
105
designate/tests/unit/test_service_status.py
Normal file
105
designate/tests/unit/test_service_status.py
Normal file
@ -0,0 +1,105 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# 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 mock
|
||||
import oslotest.base
|
||||
from oslo_config import cfg
|
||||
|
||||
from designate import objects
|
||||
from designate import service_status
|
||||
|
||||
|
||||
class NoopEmitterTest(oslotest.base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(NoopEmitterTest, self).setUp()
|
||||
self.mock_tg = mock.Mock()
|
||||
|
||||
def test_init(self):
|
||||
service_status.NoopEmitter("svc", self.mock_tg)
|
||||
|
||||
def test_start(self):
|
||||
emitter = service_status.NoopEmitter("svc", self.mock_tg)
|
||||
emitter.start()
|
||||
|
||||
self.mock_tg.add_timer.assert_called_once_with(
|
||||
5.0, emitter._emit_heartbeat)
|
||||
|
||||
def test_stop(self):
|
||||
mock_pulse = mock.Mock()
|
||||
self.mock_tg.add_timer.return_value = mock_pulse
|
||||
|
||||
emitter = service_status.NoopEmitter("svc", self.mock_tg)
|
||||
emitter.start()
|
||||
emitter.stop()
|
||||
|
||||
self.assertFalse(emitter._running)
|
||||
|
||||
|
||||
class RpcEmitterTest(oslotest.base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(RpcEmitterTest, self).setUp()
|
||||
self.mock_tg = mock.Mock()
|
||||
|
||||
@mock.patch.object(objects, "ServiceStatus")
|
||||
@mock.patch("designate.context.DesignateContext.get_admin_context")
|
||||
def test_emit_no_status_factory(self, mock_context, mock_service_status):
|
||||
emitter = service_status.RpcEmitter("svc", self.mock_tg)
|
||||
emitter.start()
|
||||
|
||||
central = mock.Mock()
|
||||
with mock.patch("designate.central.rpcapi.CentralAPI.get_instance",
|
||||
return_value=central):
|
||||
emitter._emit_heartbeat()
|
||||
|
||||
mock_service_status.assert_called_once_with(
|
||||
service_name="svc",
|
||||
hostname=cfg.CONF.host,
|
||||
status=True,
|
||||
stats={},
|
||||
capabilities={},
|
||||
heartbeated_at=mock.ANY
|
||||
)
|
||||
|
||||
central.update_service_status.assert_called_once_with(
|
||||
mock_context.return_value, mock_service_status.return_value
|
||||
)
|
||||
|
||||
@mock.patch.object(objects, "ServiceStatus")
|
||||
@mock.patch("designate.context.DesignateContext.get_admin_context")
|
||||
def test_emit_status_factory(self, mock_context, mock_service_status):
|
||||
status = False
|
||||
stats = {"a": 1}
|
||||
capabilities = {"b": 2}
|
||||
|
||||
status_factory = mock.Mock(return_value=(status, stats, capabilities,))
|
||||
emitter = service_status.RpcEmitter("svc", self.mock_tg,
|
||||
status_factory=status_factory)
|
||||
emitter.start()
|
||||
|
||||
central = mock.Mock()
|
||||
with mock.patch("designate.central.rpcapi.CentralAPI.get_instance",
|
||||
return_value=central):
|
||||
emitter._emit_heartbeat()
|
||||
|
||||
mock_service_status.assert_called_once_with(
|
||||
service_name="svc",
|
||||
hostname=cfg.CONF.host,
|
||||
status=status,
|
||||
stats=stats,
|
||||
capabilities=capabilities,
|
||||
heartbeated_at=mock.ANY
|
||||
)
|
||||
|
||||
central.update_service_status.assert_called_once_with(
|
||||
mock_context.return_value, mock_service_status.return_value
|
||||
)
|
43
doc/service_status.rst
Normal file
43
doc/service_status.rst
Normal file
@ -0,0 +1,43 @@
|
||||
..
|
||||
Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
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.
|
||||
|
||||
|
||||
Service Statuses
|
||||
================
|
||||
|
||||
Overview
|
||||
-----------------------
|
||||
The Service Status entries are used to track the health state of the services
|
||||
in the Designate system. Each service will report in it's health via RPC or
|
||||
using HTTP.
|
||||
|
||||
|
||||
Explanation
|
||||
-----------
|
||||
|
||||
============ ==============================================================
|
||||
Attribute Description
|
||||
============ ==============================================================
|
||||
service_name The name of the service, typically `central` or alike.
|
||||
hostname The hostname where the service is running.
|
||||
capabilities Service capabilities, used to tell a service of the same type
|
||||
apart.
|
||||
stats Statistics are optional per service metrics.
|
||||
status An enum describing the status of the service.
|
||||
UP for health and ok, DOWN for down (Ie the service hasn't
|
||||
reported in for a while) and WARNING if the service is having
|
||||
issues.
|
||||
============ ==============================================================
|
@ -68,6 +68,7 @@ Reference Documentation
|
||||
support-matrix
|
||||
pools
|
||||
memory-caching
|
||||
service_status
|
||||
|
||||
Source Documentation
|
||||
====================
|
||||
|
@ -93,6 +93,7 @@ V2 API
|
||||
rest/v2/pools
|
||||
rest/v2/limits
|
||||
rest/v2/reverse
|
||||
rest/v2/service_status
|
||||
rest/v2/tsigkeys
|
||||
|
||||
Admin API
|
||||
|
149
doc/source/rest/v2/service_status.rst
Normal file
149
doc/source/rest/v2/service_status.rst
Normal file
@ -0,0 +1,149 @@
|
||||
..
|
||||
Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
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.
|
||||
|
||||
Service Statuses
|
||||
================
|
||||
|
||||
Overview
|
||||
-----------------------
|
||||
The Service Status entries are used to track the health state of the services
|
||||
in the Designate system.
|
||||
|
||||
|
||||
Get a Service Status
|
||||
--------------------
|
||||
|
||||
.. http:get:: /service_statuses/(uuid:id)
|
||||
|
||||
Lists a particular Service Status
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /service_statuses/5abe514c-9fb5-41e8-ab73-5ed25f8a73e9 HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
{
|
||||
"capabilities": {},
|
||||
"created_at": "2016-03-08T09:20:23.000000",
|
||||
"heartbeated_at": "2016-03-08T09:26:18.000000",
|
||||
"hostname": "vagrant-ubuntu-trusty-64",
|
||||
"id": "769e8ca2-f71e-48be-8dee-631492c91e41",
|
||||
"links": {
|
||||
"self": "http://192.168.27.100:9001/v2/service_statuses/769e8ca2-f71e-48be-8dee-631492c91e41",
|
||||
"service_status": "http://192.168.27.100:9001/v2/service_statuses/769e8ca2-f71e-48be-8dee-631492c91e41"
|
||||
},
|
||||
"service_name": "pool_manager",
|
||||
"stats": {},
|
||||
"status": "UP",
|
||||
"updated_at": "2016-03-08T09:26:18.000000"
|
||||
}
|
||||
|
||||
:form created_at: timestamp
|
||||
:form updated_at: timestamp
|
||||
:form id: uuid
|
||||
:form description: UTF-8 text field
|
||||
:form links: links to traverse the list
|
||||
:form service_name: Service name
|
||||
:form hostname: Service hostname
|
||||
:form capabilities: Service capabilities - dict of capabilities
|
||||
:form stats: Service stats - dict of stats
|
||||
:form status: Service status - UP, DOWN or WARNING
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Access Denied
|
||||
:statuscode 404: Service Status not found
|
||||
|
||||
List Service Statuses
|
||||
---------------------
|
||||
|
||||
.. http:get:: /service_statuses
|
||||
|
||||
Lists all Service Statuses
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /service_statuses HTTP/1.1
|
||||
Host: example.com
|
||||
Accept: application/json
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
{
|
||||
"service_statuses":[
|
||||
{
|
||||
"capabilities": {},
|
||||
"created_at": "2016-03-08T09:20:23.000000",
|
||||
"heartbeated_at": "2016-03-08T09:26:18.000000",
|
||||
"hostname": "vagrant-ubuntu-trusty-64",
|
||||
"id": "769e8ca2-f71e-48be-8dee-631492c91e41",
|
||||
"links": {
|
||||
"self": "http://192.168.27.100:9001/v2/service_statuses/769e8ca2-f71e-48be-8dee-631492c91e41",
|
||||
"service_status": "http://192.168.27.100:9001/v2/service_statuses/769e8ca2-f71e-48be-8dee-631492c91e41"
|
||||
},
|
||||
"service_name": "pool_manager",
|
||||
"stats": {},
|
||||
"status": "UP",
|
||||
"updated_at": "2016-03-08T09:26:18.000000"
|
||||
},
|
||||
{
|
||||
"capabilities": {},
|
||||
"created_at": "2016-03-08T09:20:26.000000",
|
||||
"heartbeated_at": "2016-03-08T09:26:16.000000",
|
||||
"hostname": "vagrant-ubuntu-trusty-64",
|
||||
"id": "adcf580b-ea1c-4ebc-8a95-37ccdeed11ae",
|
||||
"links": {
|
||||
"self": "http://192.168.27.100:9001/v2/service_statuses/adcf580b-ea1c-4ebc-8a95-37ccdeed11ae",
|
||||
"service_status": "http://192.168.27.100:9001/v2/service_statuses/adcf580b-ea1c-4ebc-8a95-37ccdeed11ae"
|
||||
},
|
||||
"service_name": "zone_manager",
|
||||
"stats": {},
|
||||
"status": "UP",
|
||||
"updated_at": "2016-03-08T09:26:17.000000"
|
||||
}
|
||||
],
|
||||
"links":{
|
||||
"self":"http://127.0.0.1:9001/v2/service_statuses"
|
||||
}
|
||||
}
|
||||
|
||||
:form created_at: timestamp
|
||||
:form updated_at: timestamp
|
||||
:form id: uuid
|
||||
:form description: UTF-8 text field
|
||||
:form links: links to traverse the list
|
||||
:form service_name: Service name
|
||||
:form hostname: Service hostname
|
||||
:form capabilities: Service capabilities - dict of capabilities
|
||||
:form stats: Service stats - dict of stats
|
||||
:form status: Service status - UP, DOWN or WARNING
|
||||
:statuscode 200: OK
|
||||
:statuscode 401: Access Denied
|
@ -122,5 +122,8 @@
|
||||
"find_zone_exports": "rule:admin_or_owner",
|
||||
"get_zone_export": "rule:admin_or_owner",
|
||||
"update_zone_export": "rule:admin_or_owner",
|
||||
"delete_zone_export": "rule:admin_or_owner"
|
||||
|
||||
"find_service_status": "rule:admin",
|
||||
"find_service_statuses": "rule:admin",
|
||||
"update_service_service_status": "rule:admin"
|
||||
}
|
||||
|
@ -122,6 +122,11 @@ designate.zone_manager_tasks =
|
||||
periodic_secondary_refresh = designate.zone_manager.tasks:PeriodicSecondaryRefreshTask
|
||||
delayed_notify = designate.zone_manager.tasks:PeriodicGenerateDelayedNotifyTask
|
||||
|
||||
designate.heartbeat_emitter =
|
||||
noop = designate.service_status:NoopEmitter
|
||||
rpc = designate.service_status:RpcEmitter
|
||||
|
||||
|
||||
[build_sphinx]
|
||||
all_files = 1
|
||||
build-dir = doc/build
|
||||
|
Loading…
Reference in New Issue
Block a user