congress/congress/library_service/library_service.py

249 lines
8.4 KiB
Python

# Copyright (c) 2017 VMware, 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.
#
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import copy
import json
import jsonschema
import os
import yaml
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging
from congress.datalog import compile
from congress.db import db_library_policies
from congress.dse2 import data_service
from congress import exception
LOG = logging.getLogger(__name__)
def validate_policy_item(item):
schema_json = '''
{
"id": "PolicyProperties",
"title": "Policy Properties",
"type": "object",
"required": ["name", "rules"],
"properties": {
"name": {
"title": "Policy unique name",
"type": "string",
"minLength": 1,
"maxLength": 255
},
"description": {
"title": "Policy description",
"type": "string"
},
"kind": {
"title": "Policy kind",
"type": "string",
"enum": ["database", "nonrecursive", "action", "materialized",
"delta", "datasource"]
},
"abbreviation": {
"title": "Policy name abbreviation",
"type": "string",
"minLength": 1,
"maxLength": 5
},
"rules": {
"title": "collection of rules",
"type": "array",
"items": {
"type": "object",
"properties": {
"PolicyRule": {
"title": "Policy rule",
"type": "object",
"required": ["rule"],
"properties": {
"rule": {
"title": "Rule definition following policy grammar",
"type": "string"
},
"name": {
"title": "User-friendly name",
"type": "string"
},
"comment": {
"title": "User-friendly comment",
"type": "string"
}
}
}
}
}
}
}
}
'''
try:
jsonschema.validate(item, json.loads(schema_json))
except jsonschema.exceptions.ValidationError as ve:
raise exception.InvalidPolicyInput(data=str(ve))
class LibraryService (data_service.DataService):
def __init__(self, name):
data_service.DataService.__init__(self, name)
self.name = name
self.add_rpc_endpoint(DseLibraryServiceEndpoints(self))
def create_policy(self, policy_dict):
policy_dict = copy.deepcopy(policy_dict)
validate_policy_item(policy_dict)
policy_name = policy_dict['name']
# check name is valid
if not compile.string_is_servicename(policy_name):
raise exception.PolicyException(
'name `%s` is not a valid policy name' % policy_name)
# make defaults
if 'kind' not in policy_dict:
policy_dict['kind'] = 'nonrecursive'
if 'abbreviation' not in policy_dict:
policy_dict['abbreviation'] = policy_name[:5]
if 'description' not in policy_dict:
policy_dict['description'] = ''
try:
# Note(thread-safety): blocking call
policy = db_library_policies.add_policy(policy_dict=policy_dict)
return policy.to_dict()
except db_exc.DBError:
LOG.exception('Creating a new library policy failed.')
raise
def get_policies(self, include_rules=True):
return [p.to_dict(include_rules)
for p in db_library_policies.get_policies()]
def get_policy(self, id_, include_rules=True):
# Note(thread-safety): blocking call
policy = db_library_policies.get_policy(id_)
return policy.to_dict(include_rules)
def get_policy_by_name(self, name, include_rules=True):
# Note(thread-safety): blocking call
policy = db_library_policies.get_policy_by_name(name)
return policy.to_dict(include_rules)
def delete_all_policies(self):
# Note(thread-safety): blocking call
db_library_policies.delete_policies()
def delete_policy(self, id_):
# Note(thread-safety): blocking call
db_object = db_library_policies.get_policy(id_)
db_library_policies.delete_policy(id_)
return db_object.to_dict(include_rules=True)
def replace_policy(self, id_, policy_dict):
validate_policy_item(policy_dict)
policy_name = policy_dict['name']
# check name is valid
if not compile.string_is_servicename(policy_name):
raise exception.PolicyException(
"Policy name %s is not a valid service name" % policy_name)
# make defaults
if 'kind' not in policy_dict:
policy_dict['kind'] = 'nonrecursive'
if 'abbreviation' not in policy_dict:
policy_dict['abbreviation'] = policy_name[:5]
if 'description' not in policy_dict:
policy_dict['description'] = ''
# Note(thread-safety): blocking call
policy = db_library_policies.replace_policy(
id_, policy_dict=policy_dict)
return policy.to_dict()
def load_policies_from_files(self):
def _load_library_policy_file(full_path):
with open(full_path, "r") as stream:
policies = yaml.load_all(stream)
count = 0
doc_num_in_file = 0
for policy in policies:
try:
doc_num_in_file += 1
self.create_policy(policy)
count += 1
except db_exc.DBDuplicateEntry:
LOG.debug(
'Library policy %s (number %s in file %s) already '
'exists (likely loaded by another Congress '
'instance). Skipping.',
policy.get('name', '[no name]'),
doc_num_in_file, full_path)
except exception.CongressException:
LOG.exception(
'Library policy %s could not be loaded. Skipped. '
'YAML reproduced here %s',
policy.get('name', '[no name]'),
yaml.dumps(policy))
return count
file_count = 0
policy_count = 0
for (dirpath, dirnames, filenames) in os.walk(
cfg.CONF.policy_library_path):
for filename in filenames:
count = _load_library_policy_file(
os.path.join(dirpath, filename))
if count > 0:
file_count += 1
policy_count += count
LOG.debug(
'%s library policies from %s files successfully loaded',
policy_count, file_count)
class DseLibraryServiceEndpoints(object):
"""RPC endpoints exposed by LibraryService."""
def __init__(self, data_service):
self.data_service = data_service
def create_policy(self, context, policy_dict):
return self.data_service.create_policy(policy_dict)
def get_policies(self, context, include_rules=True):
return self.data_service.get_policies(include_rules)
def get_policy(self, context, id_, include_rules=True):
return self.data_service.get_policy(id_, include_rules)
def get_policy_by_name(self, context, name, include_rules=True):
return self.data_service.get_policy_by_name(name, include_rules)
def delete_all_policies(self, context):
return self.data_service.delete_all_policies()
def delete_policy(self, context, id_):
return self.data_service.delete_policy(id_)
def replace_policy(self, context, id_, policy_dict):
return self.data_service.replace_policy(id_, policy_dict)