compass-core/compass/db/api/metadata.py
xiaodongwang 14ad281057 move adapter from db to memory
Change-Id: I366052e23d72dd94229513d6a0992338d0d44638
2015-08-04 10:45:24 -07:00

518 lines
19 KiB
Python

# Copyright 2014 Huawei Technologies Co. Ltd
#
# 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.
"""Metadata related database operations."""
import copy
import logging
import string
from compass.db.api import adapter as adapter_api
from compass.db.api import database
from compass.db.api import utils
from compass.db import callback as metadata_callback
from compass.db import exception
from compass.db import models
from compass.db import validator as metadata_validator
from compass.utils import setting_wrapper as setting
from compass.utils import util
OS_FIELDS = None
PACKAGE_FIELDS = None
FLAVOR_FIELDS = None
OSES_METADATA = None
PACKAGES_METADATA = None
FLAVORS_METADATA = None
OSES_METADATA_UI_CONVERTERS = None
FLAVORS_METADATA_UI_CONVERTERS = None
def _get_field_from_configuration(configs):
"""Get fields from configurations."""
fields = {}
for config in configs:
if not isinstance(config, dict):
raise exception.InvalidParameter(
'config %s is not dict' % config
)
field_name = config['NAME']
fields[field_name] = {
'name': field_name,
'id': field_name,
'field_type': config.get('FIELD_TYPE', basestring),
'display_type': config.get('DISPLAY_TYPE', 'text'),
'validator': config.get('VALIDATOR', None),
'js_validator': config.get('JS_VALIDATOR', None),
'description': config.get('DESCRIPTION', field_name)
}
return fields
def _get_os_fields_from_configuration():
"""Get os fields from os field config dir."""
env_locals = {}
env_locals.update(metadata_validator.VALIDATOR_LOCALS)
env_locals.update(metadata_callback.CALLBACK_LOCALS)
configs = util.load_configs(
setting.OS_FIELD_DIR,
env_locals=env_locals
)
return _get_field_from_configuration(
configs
)
def _get_package_fields_from_configuration():
"""Get package fields from package field config dir."""
env_locals = {}
env_locals.update(metadata_validator.VALIDATOR_LOCALS)
env_locals.update(metadata_callback.CALLBACK_LOCALS)
configs = util.load_configs(
setting.PACKAGE_FIELD_DIR,
env_locals=env_locals
)
return _get_field_from_configuration(
configs
)
def _get_flavor_fields_from_configuration():
"""Get flavor fields from flavor field config dir."""
env_locals = {}
env_locals.update(metadata_validator.VALIDATOR_LOCALS)
env_locals.update(metadata_callback.CALLBACK_LOCALS)
configs = util.load_configs(
setting.FLAVOR_FIELD_DIR,
env_locals=env_locals
)
return _get_field_from_configuration(
configs
)
def _get_metadata_from_configuration(
path, name, config,
fields, **kwargs
):
"""Recursively get metadata from configuration.
Args:
path: used to indicate the path to the root element.
mainly for trouble shooting.
name: the key of the metadata section.
config: the value of the metadata section.
fields: all fields defined in os fields or package fields dir.
"""
if not isinstance(config, dict):
raise exception.InvalidParameter(
'%s config %s is not dict' % (path, config)
)
metadata_self = config.get('_self', {})
if 'field' in metadata_self:
field_name = metadata_self['field']
field = fields[field_name]
else:
field = {}
# mapping to may contain $ like $partition. Here we replace the
# $partition to the key of the correspendent config. The backend then
# can use this kind of feature to support multi partitions when we
# only declare the partition metadata in one place.
mapping_to_template = metadata_self.get('mapping_to', None)
if mapping_to_template:
mapping_to = string.Template(
mapping_to_template
).safe_substitute(
**kwargs
)
else:
mapping_to = None
self_metadata = {
'name': name,
'display_name': metadata_self.get('display_name', name),
'field_type': field.get('field_type', dict),
'display_type': field.get('display_type', None),
'description': metadata_self.get(
'description', field.get('description', None)
),
'is_required': metadata_self.get('is_required', False),
'required_in_whole_config': metadata_self.get(
'required_in_whole_config', False),
'mapping_to': mapping_to,
'validator': metadata_self.get(
'validator', field.get('validator', None)
),
'js_validator': metadata_self.get(
'js_validator', field.get('js_validator', None)
),
'default_value': metadata_self.get('default_value', None),
'default_callback': metadata_self.get('default_callback', None),
'default_callback_params': metadata_self.get(
'default_callback_params', {}),
'options': metadata_self.get('options', None),
'options_callback': metadata_self.get('options_callback', None),
'options_callback_params': metadata_self.get(
'options_callback_params', {}),
'autofill_callback': metadata_self.get(
'autofill_callback', None),
'autofill_callback_params': metadata_self.get(
'autofill_callback_params', {}),
'required_in_options': metadata_self.get(
'required_in_options', False)
}
self_metadata.update(kwargs)
metadata = {'_self': self_metadata}
# Key extension used to do two things:
# one is to return the extended metadata that $<something>
# will be replace to possible extensions.
# The other is to record the $<something> to extended value
# and used in future mapping_to subsititution.
# TODO(grace): select proper name instead of key_extensions if
# you think it is better.
# Suppose key_extension is {'$partition': ['/var', '/']} for $partition
# the metadata for $partition will be mapped to {
# '/var': ..., '/': ...} and kwargs={'partition': '/var'} and
# kwargs={'partition': '/'} will be parsed to recursive metadata parsing
# for sub metadata under '/var' and '/'. Then in the metadata parsing
# for the sub metadata, this kwargs will be used to substitute mapping_to.
key_extensions = metadata_self.get('key_extensions', {})
general_keys = []
for key, value in config.items():
if key.startswith('_'):
continue
if key in key_extensions:
if not key.startswith('$'):
raise exception.InvalidParameter(
'%s subkey %s should start with $' % (
path, key
)
)
extended_keys = key_extensions[key]
for extended_key in extended_keys:
if extended_key.startswith('$'):
raise exception.InvalidParameter(
'%s extended key %s should not start with $' % (
path, extended_key
)
)
sub_kwargs = dict(kwargs)
sub_kwargs[key[1:]] = extended_key
metadata[extended_key] = _get_metadata_from_configuration(
'%s/%s' % (path, extended_key), extended_key, value,
fields, **sub_kwargs
)
else:
if key.startswith('$'):
general_keys.append(key)
metadata[key] = _get_metadata_from_configuration(
'%s/%s' % (path, key), key, value,
fields, **kwargs
)
if len(general_keys) > 1:
raise exception.InvalidParameter(
'foud multi general keys in %s: %s' % (
path, general_keys
)
)
return metadata
def _get_oses_metadata_from_configuration():
"""Get os metadata from os metadata config dir."""
oses_metadata = {}
env_locals = {}
env_locals.update(metadata_validator.VALIDATOR_LOCALS)
env_locals.update(metadata_callback.CALLBACK_LOCALS)
configs = util.load_configs(
setting.OS_METADATA_DIR,
env_locals=env_locals
)
for config in configs:
os_name = config['OS']
os_metadata = oses_metadata.setdefault(os_name, {})
for key, value in config['METADATA'].items():
os_metadata[key] = _get_metadata_from_configuration(
key, key, value, OS_FIELDS
)
oses = adapter_api.OSES
parents = {}
for os_name, os in oses.items():
parent = os.get('parent', None)
parents[os_name] = parent
for os_name, os in oses.items():
oses_metadata[os_name] = util.recursive_merge_dict(
os_name, oses_metadata, parents
)
return oses_metadata
def _get_packages_metadata_from_configuration():
"""Get package metadata from package metadata config dir."""
packages_metadata = {}
env_locals = {}
env_locals.update(metadata_validator.VALIDATOR_LOCALS)
env_locals.update(metadata_callback.CALLBACK_LOCALS)
configs = util.load_configs(
setting.PACKAGE_METADATA_DIR,
env_locals=env_locals
)
for config in configs:
adapter_name = config['ADAPTER']
package_metadata = packages_metadata.setdefault(adapter_name, {})
for key, value in config['METADATA'].items():
package_metadata[key] = _get_metadata_from_configuration(
key, key, value, PACKAGE_FIELDS
)
adapters = adapter_api.ADAPTERS
parents = {}
for adapter_name, adapter in adapters.items():
parent = adapter.get('parent', None)
parents[adapter_name] = parent
for adapter_name, adapter in adapters.items():
packages_metadata[adapter_name] = util.recursive_merge_dict(
adapter_name, packages_metadata, parents
)
return packages_metadata
def _get_flavors_metadata_from_configuration():
"""Get flavor metadata from flavor metadata config dir."""
flavors_metadata = {}
env_locals = {}
env_locals.update(metadata_validator.VALIDATOR_LOCALS)
env_locals.update(metadata_callback.CALLBACK_LOCALS)
configs = util.load_configs(
setting.FLAVOR_METADATA_DIR,
env_locals=env_locals
)
for config in configs:
adapter_name = config['ADAPTER']
flavor_name = config['FLAVOR']
flavor_metadata = flavors_metadata.setdefault(
adapter_name, {}
).setdefault(flavor_name, {})
for key, value in config['METADATA'].items():
flavor_metadata[key] = _get_metadata_from_configuration(
key, key, value, FLAVOR_FIELDS
)
packages_metadata = PACKAGES_METADATA
adapters_flavors = adapter_api.ADAPTERS_FLAVORS
for adapter_name, adapter_flavors in adapters_flavors.items():
package_metadata = packages_metadata.get(adapter_name, {})
for flavor_name, flavor in adapter_flavors.items():
flavor_metadata = flavors_metadata.setdefault(
adapter_name, {}
).setdefault(flavor_name, {})
util.merge_dict(flavor_metadata, package_metadata, override=False)
return flavors_metadata
def _filter_metadata(metadata, **kwargs):
if not isinstance(metadata, dict):
return metadata
filtered_metadata = {}
for key, value in metadata.items():
if key == '_self':
default_value = value.get('default_value', None)
if default_value is None:
default_callback_params = value.get(
'default_callback_params', {}
)
callback_params = dict(kwargs)
if default_callback_params:
callback_params.update(default_callback_params)
default_callback = value.get('default_callback', None)
if default_callback:
default_value = default_callback(key, **callback_params)
options = value.get('options', None)
if options is None:
options_callback_params = value.get(
'options_callback_params', {}
)
callback_params = dict(kwargs)
if options_callback_params:
callback_params.update(options_callback_params)
options_callback = value.get('options_callback', None)
if options_callback:
options = options_callback(key, **callback_params)
filtered_metadata[key] = value
if default_value is not None:
filtered_metadata[key]['default_value'] = default_value
if options is not None:
filtered_metadata[key]['options'] = options
else:
filtered_metadata[key] = _filter_metadata(value, **kwargs)
return filtered_metadata
def _load_metadata(force_reload=False):
"""Load metadata information into memory.
If force_reload, the metadata information will be reloaded
even if the metadata is already loaded.
"""
adapter_api.load_adapters_internal(force_reload=force_reload)
global OS_FIELDS
if force_reload or OS_FIELDS is None:
OS_FIELDS = _get_os_fields_from_configuration()
global PACKAGE_FIELDS
if force_reload or PACKAGE_FIELDS is None:
PACKAGE_FIELDS = _get_package_fields_from_configuration()
global FLAVOR_FIELDS
if force_reload or FLAVOR_FIELDS is None:
FLAVOR_FIELDS = _get_flavor_fields_from_configuration()
global OSES_METADATA
if force_reload or OSES_METADATA is None:
OSES_METADATA = _get_oses_metadata_from_configuration()
global PACKAGES_METADATA
if force_reload or PACKAGES_METADATA is None:
PACKAGES_METADATA = _get_packages_metadata_from_configuration()
global FLAVORS_METADATA
if force_reload or FLAVORS_METADATA is None:
FLAVORS_METADATA = _get_flavors_metadata_from_configuration()
global OSES_METADATA_UI_CONVERTERS
if force_reload or OSES_METADATA_UI_CONVERTERS is None:
OSES_METADATA_UI_CONVERTERS = (
_get_oses_metadata_ui_converters_from_configuration()
)
global FLAVORS_METADATA_UI_CONVERTERS
if force_reload or FLAVORS_METADATA_UI_CONVERTERS is None:
FLAVORS_METADATA_UI_CONVERTERS = (
_get_flavors_metadata_ui_converters_from_configuration()
)
def _get_oses_metadata_ui_converters_from_configuration():
"""Get os metadata ui converters from os metadata mapping config dir.
os metadata ui converter is used to convert os metadata to
the format UI can understand and show.
"""
oses_metadata_ui_converters = {}
configs = util.load_configs(setting.OS_MAPPING_DIR)
for config in configs:
os_name = config['OS']
oses_metadata_ui_converters[os_name] = config.get('CONFIG_MAPPING', {})
oses = adapter_api.OSES
parents = {}
for os_name, os in oses.items():
parent = os.get('parent', None)
parents[os_name] = parent
for os_name, os in oses.items():
oses_metadata_ui_converters[os_name] = util.recursive_merge_dict(
os_name, oses_metadata_ui_converters, parents
)
return oses_metadata_ui_converters
def _get_flavors_metadata_ui_converters_from_configuration():
"""Get flavor metadata ui converters from flavor mapping config dir."""
flavors_metadata_ui_converters = {}
configs = util.load_configs(setting.FLAVOR_MAPPING_DIR)
for config in configs:
adapter_name = config['ADAPTER']
flavor_name = config['FLAVOR']
flavors_metadata_ui_converters.setdefault(
adapter_name, {}
)[flavor_name] = config.get('CONFIG_MAPPING', {})
adapters = adapter_api.ADAPTERS
parents = {}
for adapter_name, adapter in adapters.items():
parent = adapter.get('parent', None)
parents[adapter_name] = parent
for adapter_name, adapter in adapters.items():
flavors_metadata_ui_converters[adapter_name] = (
util.recursive_merge_dict(
adapter_name, flavors_metadata_ui_converters, parents
)
)
return flavors_metadata_ui_converters
def get_packages_metadata_internal(force_reload=False):
"""Get deployable package metadata."""
_load_metadata(force_reload=force_reload)
metadata_mapping = {}
adapters = adapter_api.ADAPTERS
for adapter_name, adapter in adapters.items():
if adapter.get('deployable'):
metadata_mapping[adapter_name] = _filter_metadata(
PACKAGES_METADATA.get(adapter_name, {})
)
else:
logging.info(
'ignore metadata since its adapter %s is not deployable',
adapter_name
)
return metadata_mapping
def get_flavors_metadata_internal(force_reload=False):
"""Get deployable flavor metadata."""
_load_metadata(force_reload=force_reload)
metadata_mapping = {}
adapters_flavors = adapter_api.ADAPTERS_FLAVORS
for adapter_name, adapter_flavors in adapters_flavors.items():
adapter = adapter_api.ADAPTERS[adapter_name]
if not adapter.get('deployable'):
logging.info(
'ignore metadata since its adapter %s is not deployable',
adapter_name
)
continue
for flavor_name, flavor in adapter_flavors.items():
flavor_metadata = FLAVORS_METADATA.get(
adapter_name, {}
).get(flavor_name, {})
metadata = _filter_metadata(flavor_metadata)
metadata_mapping.setdefault(
adapter_name, {}
)[flavor_name] = metadata
return metadata_mapping
def get_flavors_metadata_ui_converters_internal(force_reload=False):
"""Get usable flavor metadata ui converters."""
_load_metadata(force_reload=force_reload)
return FLAVORS_METADATA_UI_CONVERTERS
def get_oses_metadata_internal(force_reload=False):
"""Get deployable os metadata."""
_load_metadata(force_reload=force_reload)
metadata_mapping = {}
oses = adapter_api.OSES
for os_name, os in oses.items():
if os.get('deployable'):
metadata_mapping[os_name] = _filter_metadata(
OSES_METADATA.get(os_name, {})
)
else:
logging.info(
'ignore metadata since its os %s is not deployable',
os_name
)
return metadata_mapping
def get_oses_metadata_ui_converters_internal(force_reload=False):
"""Get usable os metadata ui converters."""
_load_metadata(force_reload=force_reload)
return OSES_METADATA_UI_CONVERTERS