# 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)) 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