Adding API support for magnum service

These changes implement the API level support for querying magnum
services.

Partially-Implements: blueprint magnum-service-list
Closes-bug: #1498158
Depends-On: Ia0c09222405c87cb61e5de4a43ba345ae3405b50
Change-Id: Ib816f595ba2edef29edaec40fa940570755b10aa
This commit is contained in:
Surojit Pathak 2015-09-17 21:35:53 +00:00
parent 3674ce278d
commit e360cf0be2
10 changed files with 250 additions and 8 deletions

View File

@ -35,6 +35,10 @@
# (integer value)
#periodic_interval_max = 60
# Max interval size between periodic tasks execution in seconds.
# (integer value)
#service_down_time = 180
# Name of this node. This can be an opaque identifier. It is not
# necessarily a hostname, FQDN, or IP address. However, the node name
# must be valid within an AMQP key, and if using ZeroMQ, a valid

View File

@ -54,5 +54,7 @@
"container:update": "rule:default",
"certificate:create": "rule:default",
"certificate:get": "rule:default"
"certificate:get": "rule:default",
"magnum-service:get_all": "rule:admin_api"
}

View File

@ -29,6 +29,7 @@ from magnum.api.controllers.v1 import bay
from magnum.api.controllers.v1 import baymodel
from magnum.api.controllers.v1 import certificate
from magnum.api.controllers.v1 import container
from magnum.api.controllers.v1 import magnum_services
from magnum.api.controllers.v1 import node
from magnum.api.controllers.v1 import pod
from magnum.api.controllers.v1 import replicationcontroller as rc
@ -109,6 +110,9 @@ class V1(controllers_base.APIBase):
certificates = [link.Link]
"""Links to the certificates resource"""
mservices = [link.Link]
"""Links to the magnum-services resource"""
@staticmethod
def convert():
v1 = V1()
@ -170,6 +174,12 @@ class V1(controllers_base.APIBase):
pecan.request.host_url,
'certificates', '',
bookmark=True)]
v1.mservices = [link.Link.make_link('self', pecan.request.host_url,
'mservices', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'mservices', '',
bookmark=True)]
return v1
@ -185,6 +195,7 @@ class Controller(rest.RestController):
services = service.ServicesController()
x509keypairs = x509keypair.X509KeyPairController()
certificates = certificate.CertificateController()
mservices = magnum_services.MagnumServiceController()
@expose.expose(V1)
def get(self):

View File

@ -0,0 +1,100 @@
# 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 pecan import rest
import wsme
from wsme import types as wtypes
from magnum.api.controllers import base
from magnum.api.controllers.v1 import collection
from magnum.api import expose
from magnum.api import servicegroup as svcgrp_api
from magnum.common import policy
from magnum import objects
class MagnumService(base.APIBase):
host = wtypes.StringType(min_length=1, max_length=255)
"""Name of the host """
binary = wtypes.StringType(min_length=1, max_length=255)
"""Name of the binary"""
state = wtypes.StringType(min_length=1, max_length=255)
"""State of the binary"""
id = wsme.wsattr(wtypes.IntegerType(minimum=1))
"""The id for the healthcheck record """
report_count = wsme.wsattr(wtypes.IntegerType(minimum=0))
"""The number of times the heartbeat was reported """
# disabled = wsme.wsattr(wtypes.BoolType(default=False))
"""If the service is 'disabled' administratively """
disabled_reason = wtypes.StringType(min_length=0, max_length=255)
"""Reason for disabling """
def __init__(self, state, **kwargs):
super(MagnumService, self).__init__()
self.fields = ['state']
setattr(self, 'state', state)
for field in objects.MagnumService.fields:
self.fields.append(field)
setattr(self, field, kwargs.get(field, wtypes.Unset))
class MagnumServiceCollection(collection.Collection):
mservices = [MagnumService]
"""A list containing bays objects"""
def __init__(self, **kwargs):
super(MagnumServiceCollection, self).__init__()
self._type = 'mservices'
@staticmethod
def convert_db_rec_list_to_collection(servicegroup_api,
rpc_msvcs, **kwargs):
collection = MagnumServiceCollection()
collection.mservices = []
for p in rpc_msvcs:
alive = servicegroup_api.service_is_up(p)
state = 'up' if alive else 'down'
msvc = MagnumService(state, **p.as_dict())
collection.mservices.append(msvc)
collection.next = collection.get_next(limit=None, url=None, **kwargs)
return collection
class MagnumServiceController(rest.RestController):
"""REST controller for magnum-services."""
def __init__(self, **kwargs):
super(MagnumServiceController, self).__init__()
self.servicegroup_api = svcgrp_api.ServiceGroup()
@policy.enforce_wsgi("magnum-service", "get_all")
@expose.expose(MagnumServiceCollection)
def get_all(self):
"""Retrieve a list of magnum-services.
"""
msvcs = pecan.request.rpcapi.magnum_services_list(
pecan.request.context, limit=None,
marker=None, sort_key='id',
sort_dir='asc')
return MagnumServiceCollection.convert_db_rec_list_to_collection(
self.servicegroup_api, msvcs)

View File

@ -11,14 +11,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_config import cfg
from oslo_utils import timeutils
from magnum.db.sqlalchemy import models
periodic_opts = [
cfg.IntOpt('service_down_time',
default=180,
help='Max interval size between periodic tasks execution in '
'seconds.'),
]
class API(object):
def __init__(self, conf):
self.service_down_time = 3 * conf.periodic_interval_max
CONF = cfg.CONF
CONF.register_opts(periodic_opts)
class ServiceGroup(object):
def __init__(self):
self.service_down_time = CONF.service_down_time
def service_is_up(self, member):
if not isinstance(member, models.MagnumService):
@ -28,6 +39,7 @@ class API(object):
last_heartbeat = (member.get(
'last_seen_up') or member['updated_at'] or member['created_at'])
elapsed = timeutils.delta_seconds(last_heartbeat, timeutils.utcnow())
now = timeutils.utcnow(True)
elapsed = timeutils.delta_seconds(last_heartbeat, now)
is_up = abs(elapsed) <= self.service_down_time
return is_up

View File

@ -171,6 +171,11 @@ class API(rpc_service.API):
def get_ca_certificate(self, bay):
return self._call('get_ca_certificate', bay=bay)
# magnum-services
def magnum_services_list(self, context, limit, marker, sort_key, sort_dir):
return objects.MagnumService.list(context, limit, marker, sort_key,
sort_dir)
# Versioned Objects indirection API
def object_class_action(self, context, objname, objmethod, objver,

View File

@ -770,7 +770,7 @@ class Connection(object):
"""
@abc.abstractmethod
def get_magnum_service_list(self, context, filters=None, limit=None,
def get_magnum_service_list(self, context, disabled=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get matching magnum_service records.
@ -778,7 +778,7 @@ class Connection(object):
those match the specified filters.
:param context: The security context
:param filters: Filters to apply. Defaults to None.
:param disabled: Filters disbaled services. Defaults to None.
:param limit: Maximum number of magnum_services to return.
:param marker: the last item of the previous page; we return the next
result set.

View File

@ -77,7 +77,11 @@ class TestRootController(api_base.FunctionalTest):
u'certificates': [{u'href': u'http://localhost/v1/certificates/',
u'rel': u'self'},
{u'href': u'http://localhost/certificates/',
u'rel': u'bookmark'}]}
u'rel': u'bookmark'}],
u'mservices': [{u'href': u'http://localhost/v1/mservices/',
u'rel': u'self'},
{u'href': u'http://localhost/mservices/',
u'rel': u'bookmark'}]}
response = self.app.get('/v1/')
self.assertEqual(expected, response.json)

View File

@ -0,0 +1,85 @@
# 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
from magnum.api.controllers.v1 import magnum_services as mservice
from magnum.api import servicegroup as servicegroup
from magnum.conductor import api as rpcapi
from magnum.tests import base
from magnum.tests.unit.api import base as api_base
from magnum.tests.unit.api import utils as apiutils
class TestMagnumServiceObject(base.TestCase):
def setUp(self):
super(TestMagnumServiceObject, self).setUp()
self.rpc_dict = apiutils.mservice_get_data()
def test_msvc_obj_fields_filtering(self):
"""Test that it does filtering fields """
self.rpc_dict['fake-key'] = 'fake-value'
msvco = mservice.MagnumService("up", **self.rpc_dict)
self.assertNotIn('fake-key', msvco.fields)
class db_rec(object):
def __init__(self, d):
self.rec_as_dict = d
def as_dict(self):
return self.rec_as_dict
class TestMagnumServiceController(api_base.FunctionalTest):
def setUp(self):
super(TestMagnumServiceController, self).setUp()
def test_empty(self):
response = self.get_json('/mservices')
self.assertEqual([], response['mservices'])
def _rpc_api_reply(self, count=1):
reclist = []
for i in range(count):
elem = apiutils.mservice_get_data()
elem['id'] = i + 1
rec = db_rec(elem)
reclist.append(rec)
return reclist
@mock.patch.object(rpcapi.API, 'magnum_services_list')
@mock.patch.object(servicegroup.ServiceGroup, 'service_is_up')
def test_get_one(self, svc_up, rpc_patcher):
rpc_patcher.return_value = self._rpc_api_reply()
svc_up.return_value = "up"
response = self.get_json('/mservices')
self.assertEqual(len(response['mservices']), 1)
self.assertEqual(response['mservices'][0]['id'], 1)
@mock.patch.object(rpcapi.API, 'magnum_services_list')
@mock.patch.object(servicegroup.ServiceGroup, 'service_is_up')
def test_get_many(self, svc_up, rpc_patcher):
svc_num = 5
rpc_patcher.return_value = self._rpc_api_reply(svc_num)
svc_up.return_value = "up"
response = self.get_json('/mservices')
self.assertEqual(len(response['mservices']), svc_num)
for i in range(svc_num):
elem = response['mservices'][i]
self.assertEqual(elem['id'], i + 1)

View File

@ -12,6 +12,8 @@
"""
Utils for testing the API service.
"""
import datetime
import pytz
from magnum.api.controllers.v1 import bay as bay_controller
from magnum.api.controllers.v1 import baymodel as baymodel_controller
@ -148,3 +150,20 @@ def x509keypair_post_data(**kw):
x509keypair = utils.get_test_x509keypair(**kw)
internal = x509keypair_controller.X509KeyPairPatchType.internal_attrs()
return remove_internal(x509keypair, internal)
def mservice_get_data(**kw):
"""Simulate what the RPC layer will get from DB """
faketime = datetime.datetime(2001, 1, 1, tzinfo=pytz.UTC)
return {
'binary': kw.get('binary', 'fake-binary'),
'host': kw.get('host', 'fake-host'),
'id': kw.get('id', '13'),
'report_count': kw.get('report_count', '13'),
'disabled': kw.get('disabled', False),
'disabled_reason': kw.get('disabled_reason', None),
'forced_down': kw.get('forced_down', False),
'last_seen_at': kw.get('last_seen_at', faketime),
'created_at': kw.get('created_at', faketime),
'updated_at': kw.get('updated_at', faketime),
}