Proton Version Management

This patch impletements the Proton Version Management specifications
located here: https://review.openstack.org/#/c/456772/

Change-Id: I7581b55dbaac170439ac9274454a5e2c553fa7ca
This commit is contained in:
JinLi 2017-04-25 16:36:21 -07:00
parent b82abe1579
commit 618e637f8a
6 changed files with 201 additions and 46 deletions

View File

@ -19,24 +19,50 @@ import wsmeext.pecan as wsme_pecan
from gluon.api.baseObject import APIBase
from gluon.api import link
from gluon.api import types
from gluon.particleGenerator import generator as particle_generator
from oslo_config import cfg
class ProtonRoot(APIBase):
"""The representation of the version 1 of the API."""
class Proton(APIBase):
id = wtypes.text
"""The ID of the version, also acts as the release number"""
proton_service = wtypes.text
"""The name of Proton service"""
status = types.create_enum_type('CURRENT', 'STABLE', 'DEPRECATED')
"""Status of the API, which can be CURRENT, STABLE or DEPRECATED"""
links = [link.Link]
"""A Link that point to the root of proton"""
@staticmethod
def convert(service, status='CURRENT'):
proton = Proton()
proton.status = status
proton.proton_service = service
proton.links = [link.Link.make_link('self',
pecan.request.host_url,
'proton',
service,
bookmark=True)]
return proton
class ProtonRoot(APIBase):
protons = [Proton]
"""List of protons in their current version"""
@staticmethod
def convert():
service_list = particle_generator.get_service_list()
protons = list()
for service in service_list:
proton = Proton.convert(service)
protons.append(proton)
root = ProtonRoot()
root.id = "proton"
root.links = [link.Link.make_link('self', pecan.request.host_url,
'proton', '', bookmark=True), ]
root.protons = protons
return root

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import pecan
from pecan import rest
import six
from wsme import types as wtypes
@ -21,9 +22,11 @@ from gluon.api.baseObject import APIBase
from gluon.api.baseObject import APIBaseObject
from gluon.api.baseObject import RootObjectController
from gluon.api.baseObject import SubObjectController
from gluon.api import link
from gluon.api import types
from gluon.particleGenerator.DataBaseModelGenerator \
import DataBaseModelProcessor
from gluon.particleGenerator.generator import load_model_for_service
class MyData(object):
@ -34,26 +37,117 @@ ApiGenData = MyData()
ApiGenData.svc_controllers = {}
class ServiceRoot(APIBase):
"""The root service URL"""
id = wtypes.text
class ProtonVersion(APIBase):
"""The root of proton service URL"""
proton_service = wtypes.text
"""Name of proton service"""
version_id = wtypes.text
"""Version id of proton service, e.g. v1.0, v2.0, ..."""
status = types.create_enum_type('CURRENT', 'STABLE', 'DEPRECATED')
"""Status of the API, which can be CURRENT, STABLE or DEPRECATED"""
links = [link.Link]
"""A Link that point to a specific version of the API"""
@staticmethod
def convert(api_name):
def convert(service, version_id, status='CURRENT'):
version = ProtonVersion()
version.proton_service = service
version.version_id = version_id
version.status = status
resource_args = service + '/' + version_id
version.links = [link.Link.make_link('self',
pecan.request.host_url,
'proton',
resource_args,
bookmark=True)]
return version
class Resource(ProtonVersion):
"""Resource under a specific version of proton service"""
resource_name = wtypes.text
"""The resource name"""
@staticmethod
def convert(service, version_id, resource_name, status='CURRENT'):
resource = Resource()
resource.proton_service = service
resource.version_id = version_id
resource.status = status
resource.resource_name = resource_name
resource_args = service + '/' + version_id + '/' + resource_name
resource.links = [link.Link.make_link('self',
pecan.request.host_url,
'proton',
resource_args,
bookmark=True)]
return resource
class ServiceRoot(APIBase):
"""The root service URL of a proton"""
default_version = ProtonVersion
"""Default version of a service"""
versions = [ProtonVersion]
"""Supported versions of a service"""
# TODO(JinLi) need to handle multiple versions of a proton service.
# for now we only have one version, so we put it in
# both default_version and versions.
@staticmethod
def convert(service, version_id):
version = ProtonVersion.convert(service, version_id)
root = ServiceRoot()
root.id = api_name
root.default_version = version
root.versions = [version]
return root
class ServiceVersionRoot(APIBase):
"""The root service URL of a specific version of a proton"""
resources = [Resource]
"""Resources available for a specific version of a proton service"""
@staticmethod
def convert(service_name, version_id):
model = load_model_for_service(service_name)
root = ServiceVersionRoot()
root.resources = list()
for table_name, table_data in model['api_objects'].items():
resource_name = table_data['api']['plural_name']
resource = Resource.convert(service_name,
version_id,
resource_name)
root.resources.append(resource)
return root
class ServiceController(rest.RestController):
"""Version 1 API controller root."""
def __init__(self, api_name):
def __init__(self, api_name, version_id):
self.api_name = api_name
self.version_id = version_id
@wsme_pecan.wsexpose(ServiceRoot)
def get(self):
return ServiceRoot.convert(self.api_name)
return ServiceRoot.convert(self.api_name, self.version_id)
class ServiceVersionController(ServiceController):
"""Version 1 API controller root."""
@wsme_pecan.wsexpose(ServiceVersionRoot)
def get(self):
return ServiceVersionRoot.convert(self.api_name, self.version_id)
class APIGenerator(object):
@ -65,11 +159,16 @@ class APIGenerator(object):
def add_model(self, model):
self.data = model
def create_controller(self, service_name, root):
controller = ServiceController(service_name)
def create_controller(self, service_name, version_id, root):
controller = ServiceController(service_name, version_id)
setattr(root, service_name, controller)
return controller
def create_version_controller(self, service_name, version_id, root):
controller = ServiceVersionController(service_name, version_id)
setattr(root, version_id, controller)
return controller
def create_api(self, root, service_name, db_models):
self.db_models = db_models
self.service_name = service_name

View File

@ -160,9 +160,10 @@ def make_url(host, port, *args):
return url
def make_list_func(api_model, tablename):
def make_list_func(api_model, version, tablename):
def list_func(**kwargs):
url = make_url(kwargs["host"], kwargs["port"], api_model, tablename)
url = make_url(kwargs["host"], kwargs["port"], api_model,
version, tablename)
result = json_get(url)
print(json.dumps(result, indent=4))
return result
@ -170,10 +171,10 @@ def make_list_func(api_model, tablename):
return list_func
def make_show_func(api_model, tablename, primary_key):
def make_show_func(api_model, version, tablename, primary_key):
def show_func(**kwargs):
url = make_url(kwargs["host"], kwargs["port"], api_model, tablename,
kwargs[primary_key])
url = make_url(kwargs["host"], kwargs["port"], api_model,
version, tablename, kwargs[primary_key])
result = json_get(url)
print(json.dumps(result, indent=4))
return result
@ -181,9 +182,10 @@ def make_show_func(api_model, tablename, primary_key):
return show_func
def make_create_func(api_model, tablename):
def make_create_func(api_model, version, tablename):
def create_func(**kwargs):
url = make_url(kwargs["host"], kwargs["port"], api_model, tablename)
url = make_url(kwargs["host"], kwargs["port"], api_model,
version, tablename)
del kwargs["host"]
del kwargs["port"]
data = {}
@ -196,10 +198,10 @@ def make_create_func(api_model, tablename):
return create_func
def make_update_func(api_model, tablename, primary_key):
def make_update_func(api_model, version, tablename, primary_key):
def update_func(**kwargs):
url = make_url(kwargs["host"], kwargs["port"], api_model, tablename,
kwargs[primary_key])
url = make_url(kwargs["host"], kwargs["port"], api_model,
version, tablename, kwargs[primary_key])
del kwargs["host"]
del kwargs["port"]
del kwargs[primary_key]
@ -213,15 +215,21 @@ def make_update_func(api_model, tablename, primary_key):
return update_func
def make_delete_func(api_model, tablename, primary_key):
def make_delete_func(api_model, version, tablename, primary_key):
def delete_func(**kwargs):
url = make_url(kwargs["host"], kwargs["port"], api_model, tablename,
kwargs[primary_key])
url = make_url(kwargs["host"], kwargs["port"], api_model,
version, tablename, kwargs[primary_key])
do_delete(url)
return delete_func
def get_version(model):
version = str(model['info']['version'])
version = 'v' + version
return version
def get_primary_key(table_data):
primary = []
for k, v in table_data['attributes'].items():
@ -261,6 +269,7 @@ def proc_model(cli, package_name="unknown",
portdefault=0):
# print("loading model")
model = load_model(package_name, model_dir, api_model)
version = get_version(model)
for table_name, table_data in model['api_objects'].items():
get_primary_key(table_data)
for table_name, table_data in model['api_objects'].items():
@ -300,7 +309,7 @@ def proc_model(cli, package_name="unknown",
#
hosthelp = "Host of endpoint (%s) " % hostenv
porthelp = "Port of endpoint (%s) " % portenv
list = make_list_func(api_model, attrs['__tablename__'])
list = make_list_func(api_model, version, attrs['__tablename__'])
list.func_name = "%s-list" % (attrs['__objname__'])
list = click.option("--host", envvar=hostenv,
default=hostdefault, help=hosthelp)(list)
@ -308,7 +317,7 @@ def proc_model(cli, package_name="unknown",
default=portdefault, help=porthelp)(list)
cli.command()(list)
show = make_show_func(api_model, attrs['__tablename__'],
show = make_show_func(api_model, version, attrs['__tablename__'],
attrs['_primary_key'])
show.func_name = "%s-show" % (attrs['__objname__'])
show = click.option("--host", envvar=hostenv,
@ -318,7 +327,8 @@ def proc_model(cli, package_name="unknown",
show = click.argument(attrs['_primary_key'])(show)
cli.command()(show)
create = make_create_func(api_model, attrs['__tablename__'])
create = make_create_func(api_model, version,
attrs['__tablename__'])
create.func_name = "%s-create" % (attrs['__objname__'])
create = click.option("--host", envvar=hostenv,
default=hostdefault, help=hosthelp)(create)
@ -336,7 +346,8 @@ def proc_model(cli, package_name="unknown",
create = click.option(option_name, **kwargs)(create)
cli.command()(create)
update = make_update_func(api_model, attrs['__tablename__'],
update = make_update_func(api_model, version,
attrs['__tablename__'],
attrs['_primary_key'])
update.func_name = "%s-update" % (attrs['__objname__'])
update = click.option("--host", envvar=hostenv,
@ -355,7 +366,8 @@ def proc_model(cli, package_name="unknown",
update = click.argument(attrs['_primary_key'])(update)
cli.command()(update)
del_func = make_delete_func(api_model, attrs['__tablename__'],
del_func = make_delete_func(api_model, version,
attrs['__tablename__'],
attrs['_primary_key'])
del_func.func_name = "%s-delete" % (attrs['__objname__'])
del_func = click.option("--host", envvar=hostenv,

View File

@ -367,11 +367,16 @@ def build_sql_models(service_list):
def build_api(root, service_list):
from gluon.particleGenerator.ApiGenerator import APIGenerator
for service in service_list:
load_model_for_service(service)
model = load_model_for_service(service)
version_id = str(model['info']['version'])
version_id = 'v' + version_id
api_gen = APIGenerator()
service_root = api_gen.create_controller(service, root)
service_root = api_gen.create_controller(service, version_id, root)
service_version_root = \
api_gen.create_version_controller(service, version_id,
service_root)
api_gen.add_model(load_model_for_service(service))
api_gen.create_api(service_root, service,
api_gen.create_api(service_version_root, service,
GenData.DBGeneratorInstance.get_db_models(service))

View File

@ -212,9 +212,10 @@ class CliTestCase(partgen_base.ParticleGeneratorTestCase):
def test_make_list_func(self, mock_json_get):
kwargs = {"host": "gluonURL.net", "port": 8080}
api_model = "apimodel"
version = "v1"
tablename = "tablename"
mock_json_get.return_value = {"result": "successful"}
list_func = cli.make_list_func(api_model, tablename)
list_func = cli.make_list_func(api_model, version, tablename)
observed = list_func(**kwargs)
# self.assertIsNone(observed)
expected = {"result": "successful"}
@ -227,10 +228,12 @@ class CliTestCase(partgen_base.ParticleGeneratorTestCase):
def test_make_show_func(self, mock_json_get):
kwargs = {"host": "gluonURL.net", "port": 8080, "id": 1}
api_model = "apimodel"
version = "v1"
tablename = "tablename"
primary_key = "id"
mock_json_get.return_value = {"result": "successful"}
show_func = cli.make_show_func(api_model, tablename, primary_key)
show_func = cli.make_show_func(api_model, version,
tablename, primary_key)
observed = show_func(**kwargs)
# self.assertIsNone(observed)
expected = {"result": "successful"}
@ -245,9 +248,10 @@ class CliTestCase(partgen_base.ParticleGeneratorTestCase):
kwargs = {"host": "gluonURL.net", "port": 8080, "id": 1,
"firstname": "Jane", "lastName": "Doe"}
api_model = "apiModel"
version = "v1"
tablename = "user"
mock_do_post.return_value = '{"result": "successful"}'
create_func = cli.make_create_func(api_model, tablename)
create_func = cli.make_create_func(api_model, version, tablename)
observed = create_func(**kwargs)
self.assertIsNone(observed)
@ -261,9 +265,11 @@ class CliTestCase(partgen_base.ParticleGeneratorTestCase):
"firstname": "Jane", "lastName": "Doe"}
api_model = "apiModel"
tablename = "user"
version = "v1"
primary_key = "id"
mock_do_put.return_value = '{"result": "successful"}'
update_func = cli.make_update_func(api_model, tablename, primary_key)
update_func = cli.make_update_func(api_model, version,
tablename, primary_key)
observed = update_func(**kwargs)
self.assertIsNone(observed)
@ -275,10 +281,12 @@ class CliTestCase(partgen_base.ParticleGeneratorTestCase):
def test_make_delete_func(self, mock_do_delete):
kwargs = {"host": "gluonURL.net", "port": 8080, "id": 1}
api_model = "apiModel"
version = "v1"
tablename = "user"
primary_key = "id"
mock_do_delete.return_value = '{"result": "successful"}'
delete_func = cli.make_delete_func(api_model, tablename, primary_key)
delete_func = cli.make_delete_func(api_model, version,
tablename, primary_key)
observed = delete_func(**kwargs)
self.assertIsNone(observed)

View File

@ -74,6 +74,7 @@ class GeneratorTestCase(partgen_base.ParticleGeneratorTestCase):
"""
@mock.patch('gluon.particleGenerator.generator.load_model')
@mock.patch.object(APIGenerator, 'create_controller')
@mock.patch.object(APIGenerator, 'create_version_controller')
@mock.patch.object(APIGenerator, 'add_model')
@mock.patch.object(APIGenerator, 'create_api')
@mock.patch('gluon.particleGenerator.generator.GenData.DBGeneratorInstance'
@ -82,24 +83,28 @@ class GeneratorTestCase(partgen_base.ParticleGeneratorTestCase):
mock_DBGeneratorInstance,
mock_create_api,
mock_add_model,
mock_create_version_controller,
mock_create_controller,
mock_load_model):
root = object()
service_list = ['test']
mock_service = {'foo': 'bar'}
mock_service = {'foo': 'bar', 'info': {'version': '1.0'}}
mock_load_model.return_value = mock_service
db_models = mock.Mock()
mock_DBGeneratorInstance.get_db_models.return_value = db_models
service_root = mock.Mock()
version_root = mock.Mock()
mock_create_controller.return_value = service_root
mock_create_version_controller.return_value = version_root
generator.build_api(root, service_list)
mock_load_model.assert_called_with('gluon', 'models', 'test')
mock_create_controller.assert_called_with('test', root)
mock_create_controller.assert_called_with('test', 'v1.0', root)
mock_create_version_controller.assert_called_with('test', 'v1.0',
service_root)
mock_add_model.assert_called_with(mock_service)
mock_DBGeneratorInstance.get_db_models.assert_called_with('test')
mock_create_api.assert_called_with(service_root,
mock_create_api.assert_called_with(version_root,
'test',
db_models)
"""