gluon/gluon/particleGenerator/generator.py

411 lines
16 KiB
Python

# Copyright (c) 2015 Cisco Systems, Inc.
# 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 pkg_resources
import six
import yaml
from oslo_config import cfg
from gluon.common import exception as exc
from gluon.db.sqlalchemy import models as sql_models
class MyData(object):
pass
GenData = MyData()
GenData.DBGeneratorInstance = None
GenData.models = dict()
GenData.package_name = "gluon"
GenData.model_dir = "models"
def raise_format_error(format_str, val_tuple):
str = format_str % val_tuple
raise exc.InvalidFileFormat(str)
def raise_obj_error(obj_name, format_str, val_tuple):
str = format_str % val_tuple
raise_format_error("Object: %s, %s", (obj_name, str))
# Check if policies are defined for each object and actions are valid.
# Does not verify the content of the policy for undefined rules and cyclic
# rules. The content of policies will be verified by calling the oslo_policy's
# load_rules funtions after all policies are registered.
def validate_policies(model):
'''Each api object should have policies defined'''
allowed_actions = ['create', 'delete', 'get', 'list', 'update']
for obj_name, obj_val in model['api_objects'].items():
if 'policies' not in obj_val:
raise_obj_error(obj_name,
'%s has no policies defined.',
(obj_name))
policies = obj_val.get('policies')
for action, policy in policies.items():
if action not in allowed_actions:
raise_obj_error(obj_name,
'In %s policies, %s is not a valid action.',
(obj_name, action))
for action in allowed_actions:
if action not in policies:
raise_obj_error(obj_name,
'In %s policies, action %s is not defined.',
(obj_name, action))
def validate_attributes(obj_name, obj, model):
props = ['type', 'primary', 'description', 'required',
'length', 'values', 'format', 'min', 'max']
types = ['integer', 'number', 'string', 'boolean', 'uuid', 'enum']
formats = ['date-time', 'json', 'ipv4', 'ipv6', 'mac', 'uri', 'email']
int_formats = ['int32', 'int64']
for attr_name, attr_val in obj.get('attributes').items():
if 'type' not in attr_val:
raise_obj_error(obj_name,
'A type property is not specified for '
'attribute: %s, ',
(attr_name))
for prop_name, prop_val in attr_val.items():
if prop_name in props:
if prop_name == 'type':
if prop_val not in types and \
prop_val not in model.get('api_objects'):
raise_obj_error(
obj_name,
'Invalid type: %s for attribute: %s, '
'expected type or API Object Name',
(prop_val, attr_name))
elif prop_val == 'enum':
if 'values' not in attr_val:
raise_obj_error(
obj_name,
'No enum values specified for attribute: %s',
(attr_name))
elif prop_name == 'format':
if attr_val.get('type') != 'string' and \
attr_val.get('type') != 'integer':
raise_obj_error(
obj_name,
'Format is only valid for string or integer '
'type: %s, attribute: %s',
(prop_val, attr_name))
if attr_val.get('type') == 'string':
if prop_val not in formats:
raise_obj_error(
obj_name,
'Invalid format: %s for attribute: %s',
(prop_val, attr_name))
if attr_val.get('type') == 'integer':
if prop_val not in int_formats:
raise_obj_error(
obj_name,
'Invalid int format: %s for attribute: %s',
(prop_val, attr_name))
elif prop_name == 'values':
if attr_val.get('type') != 'enum':
raise_obj_error(
obj_name,
'Values without enum specified for attribute: %s',
(attr_name))
elif prop_name == 'length':
if not isinstance(prop_val, six.integer_types):
raise_obj_error(
obj_name,
'Integer values required for length: '
'%s, attribute: %s',
(prop_val, attr_name))
elif prop_name == 'min' or prop_name == 'max':
if attr_val.get('type') != 'integer':
raise_obj_error(
obj_name,
'Min/Max is only valid for integer '
'type: %s, attribute: %s',
(prop_val, attr_name))
if not isinstance(prop_val, six.integer_types):
raise_obj_error(
obj_name,
'Integer values required for Min/Max: '
'%s, attribute: %s',
(prop_val, attr_name))
else:
raise_obj_error(
obj_name,
'Invalid property in AttributeSchema: %s for %s',
(prop_name, attr_name))
def validate_api(obj_name, obj_val, model):
api = obj_val.get('api')
if 'name' not in api:
raise_format_error('Name is missing in API object for: %s', (obj_name))
if 'parent' in api:
if api.get('parent') not in model.get('api_objects'):
raise_obj_error(
obj_name,
'API parent: %s does not reference an API object',
(api.get('parent')))
if 'parent_key' not in api:
raise_obj_error(
obj_name,
'parent_key must be present if parent property is present',
())
elif api.get('parent_key') not in obj_val.get('attributes'):
raise_obj_error(
obj_name,
'parent_key contains unkown attribute: %s',
(api.get('parent_key')))
def verify_model(model):
valid_versions = ["1.0"]
# Verify file version is correct
if 'file_version' not in model:
raise_format_error('Missing file_version object', ())
if model['file_version'] not in valid_versions:
raise_format_error('Invalid file version: %s', (model['file_version']))
if 'info' not in model:
raise_format_error('No info object defined', ())
if 'name' not in model.get('info'):
raise_format_error('Info object missing name', ())
# Verify that BasePort, BaseInterface and BaseService have been extended
if 'api_objects' not in model or len(model['api_objects']) == 0:
raise_format_error('No API objects are defined', ())
baseport_found = False
baseinterface_found = False
baseservice_found = False
for obj_name, obj_val in model['api_objects'].items():
if obj_val.get('extends') == 'BasePort':
if baseport_found:
raise_format_error(
'Only one object can extend BasePort', ())
baseport_found = True
if obj_val.get('extends') == 'BaseInterface':
if baseinterface_found:
raise_format_error(
'Only one object can extend BaseInterface', ())
baseinterface_found = True
if obj_val.get('extends') == 'BaseService':
baseservice_found = True
if 'attributes' not in obj_val:
raise_format_error(
'No attributes specified for object: %s', (obj_name))
validate_attributes(obj_name, obj_val, model)
validate_api(obj_name, obj_val, model)
if not baseport_found:
raise_format_error(
'BasePort must be extended by an API object', ())
if not baseinterface_found:
raise_format_error(
'BaseInterface must be extended by an API object', ())
if not baseservice_found:
raise_format_error(
'BaseService must be extended by an API object', ())
def extend_object(obj, obj_dict):
name = obj.get('extends')
if name not in obj_dict:
raise_format_error('extends references unkown object: %s', (name))
ext_obj = obj_dict.get(name)
orig_attrs = obj.get('attributes', dict())
obj['attributes'] = dict()
orig_policies = obj.get('policies', dict())
obj['policies'] = dict()
if 'attributes' in ext_obj:
for attr_name, attr_val in \
ext_obj.get('attributes').items():
if attr_name not in obj['attributes']:
obj['attributes'].__setitem__(attr_name, attr_val)
else:
obj['attributes'][attr_name].update(attr_val)
for attr_name, attr_val in orig_attrs.items():
if attr_name not in obj['attributes']:
obj['attributes'].__setitem__(attr_name, attr_val)
else:
obj['attributes'][attr_name].update(attr_val)
if 'policies' in ext_obj:
for rule_name, rule_val in ext_obj.get('policies').items():
if rule_name not in obj['policies']:
obj['policies'].__setitem__(rule_name, rule_val)
else:
obj['policies'][rule_name].update(rule_val)
for rule_name, rule_val in orig_policies.items():
if rule_name not in obj['policies']:
obj['policies'].__setitem__(rule_name, rule_val)
else:
obj['policies'][rule_name].update(rule_val)
return obj
def proc_object_extensions(dicta, dictb):
moved_list = list()
for obj_name, obj_val in dicta.items():
if obj_val.get('extends') in dictb:
dictb[obj_name] = extend_object(obj_val, dictb)
moved_list.append(obj_name)
for obj_name in moved_list:
dicta.__delitem__(obj_name)
if len(dicta):
proc_object_extensions(dicta, dictb)
def extend_base_objects(model):
# First we move non-extended objects to new list
for obj_name, obj_val in model.get('base_objects').items():
if 'extends' in obj_val:
if obj_val.get('extends') not in model.get('base_objects'):
raise_format_error('extends references unkown object: %s',
(obj_val.get('extends')))
new_dict = dict()
moved_list = list()
for obj_name, obj_val in model.get('base_objects').items():
if 'extends' not in obj_val:
moved_list.append(obj_name)
new_dict.__setitem__(obj_name, obj_val)
for obj_name in moved_list:
model['base_objects'].__delitem__(obj_name)
proc_object_extensions(model['base_objects'], new_dict)
model['base_objects'] = new_dict
return model
def extend_api_objects(model):
new_dict = dict()
for obj_name, obj_val in model.get('api_objects').items():
if 'extends' in obj_val:
if obj_val.get('extends') not in model.get('base_objects'):
raise_obj_error(obj_name,
'extends references unkown object: %s',
(obj_val.get('extends')))
new_dict[obj_name] = extend_object(obj_val,
model.get('base_objects'))
model.get('api_objects').update(new_dict)
return model
def append_model(model, yaml_dict):
main_objects = ['file_version', 'imports', 'info', 'objects']
for obj_name in six.iterkeys(yaml_dict):
if obj_name not in main_objects:
raise_format_error('Invalid top level object: %s', (obj_name))
file_version = yaml_dict.get('file_version')
cur_file_version = model.get('file_version')
if file_version and cur_file_version:
if file_version != file_version:
raise_format_error('File version mismatch %s', (file_version))
else:
model['file_version'] = yaml_dict['file_version']
elif file_version:
model['file_version'] = file_version
if 'imports' in yaml_dict:
model['imports'] = yaml_dict.get('imports')
if 'info' in yaml_dict:
model['info'] = yaml_dict.get('info')
if 'api_objects' not in model:
model['api_objects'] = dict()
if 'base_objects' not in model:
model['base_objects'] = dict()
for obj_name, obj_val in yaml_dict.get('objects').items():
if 'api' in obj_val:
if 'plural_name' not in obj_val['api']:
obj_val['api']['plural_name'] = \
obj_val['api'].get('name', '') + 's'
model['api_objects'].__setitem__(obj_name, obj_val)
else:
model['base_objects'].__setitem__(obj_name, obj_val)
def load_model(package_name, model_dir, model_name):
model_path = model_dir + "/" + model_name
model = {}
for f in pkg_resources.resource_listdir(package_name, model_path):
f = model_path + '/' + f
with pkg_resources.resource_stream(package_name, f) as fd:
append_model(model, yaml.safe_load(fd))
imports_path = model.get('imports')
if imports_path:
f = model_dir + '/' + imports_path
with pkg_resources.resource_stream(package_name, f) as fd:
append_model(model, yaml.safe_load(fd))
extend_base_objects(model)
extend_api_objects(model)
return model
# Singleton generator
def load_model_for_service(service):
if GenData.models.get(service) is None:
GenData.models[service] = load_model(GenData.package_name,
GenData.model_dir,
service)
return GenData.models.get(service)
def get_model_list(package_name="gluon", model_dir="models"):
model_list = list()
for f in pkg_resources.resource_listdir(package_name, model_dir):
if f == 'base':
continue
model_list.append(f)
return model_list
def get_service_list():
service_list = list()
services = str(cfg.CONF.api.service_list).split(',')
if len(services) == 1 and services[0] == '*':
service_list = get_model_list()
else:
for api_name in services:
service_list.append(api_name.strip())
return service_list
def build_sql_models(service_list):
from gluon.particleGenerator.DataBaseModelGenerator \
import DataBaseModelProcessor
if GenData.DBGeneratorInstance is None:
GenData.DBGeneratorInstance = DataBaseModelProcessor()
base = sql_models.Base
for service in service_list:
GenData.DBGeneratorInstance.add_model(load_model_for_service(service))
GenData.DBGeneratorInstance.build_sqla_models(service, base)
def build_api(root, service_list):
from gluon.particleGenerator.ApiGenerator import APIGenerator
for service in service_list:
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, 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_version_root, service,
GenData.DBGeneratorInstance.get_db_models(service))
def get_db_gen():
return GenData.DBGeneratorInstance