From c698e66797bad69b4c77b26b487bf8322989beb0 Mon Sep 17 00:00:00 2001 From: Michael Hwang Date: Tue, 12 Nov 2019 16:04:20 -0500 Subject: Copy dcae-cli->onboardingapi, copy component specs Issue-ID: DCAEGEN2-1860 Change-Id: I4805398c76479fad51cbdb74470ccc8f706ce9dc Signed-off-by: Michael Hwang --- mod/onboardingapi/dcae_cli/catalog/mock/schema.py | 191 ++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 mod/onboardingapi/dcae_cli/catalog/mock/schema.py (limited to 'mod/onboardingapi/dcae_cli/catalog/mock/schema.py') diff --git a/mod/onboardingapi/dcae_cli/catalog/mock/schema.py b/mod/onboardingapi/dcae_cli/catalog/mock/schema.py new file mode 100644 index 0000000..640d125 --- /dev/null +++ b/mod/onboardingapi/dcae_cli/catalog/mock/schema.py @@ -0,0 +1,191 @@ +# ============LICENSE_START======================================================= +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017-2018 AT&T Intellectual Property. 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. +# ============LICENSE_END========================================================= +# +# ECOMP is a trademark and service mark of AT&T Intellectual Property. + +# -*- coding: utf-8 -*- +""" +Provides jsonschema +""" +import json +from functools import partial, reduce + +import six +from jsonschema import validate, ValidationError +import requests + +from dcae_cli.util import reraise_with_msg, fetch_file_from_web +from dcae_cli.util import config as cli_config +from dcae_cli.util.exc import DcaeException +from dcae_cli.util.logger import get_logger + + +log = get_logger('Schema') + +# UPDATE: This message applies to the component spec which has been moved on a +# remote server. +# +# WARNING: The below has a "oneOf" for service provides, that will validate as long as any of them are chosen. +# However, this is wrong because what we really want is something like: +# if component_type == docker +# provides = foo +# elif component_type == cdap +# provides = bar +# The unlikely but problematic case is the cdap developer gets a hold of the docker documentation, uses that, it validates, and blows up at cdap runtime + + +# TODO: The next step here is to decide how to manage the links to the schemas. Either: +# +# a) Manage the links in the dcae-cli tool here and thus need to ask if this +# belongs in the config to point to some remote server or even point to local +# machine. +# UPDATE: This item has been mostly completed where at least the path is configurable now. + +# b) Read the links to the schemas from the spec - self-describing jsons. Is +# this even feasible? + +# c) Both +# + +class FetchSchemaError(RuntimeError): + pass + +def _fetch_schema(schema_path): + try: + server_url = cli_config.get_server_url() + return fetch_file_from_web(server_url, schema_path) + except requests.HTTPError as e: + raise FetchSchemaError("HTTP error from fetching schema", e) + except Exception as e: + raise FetchSchemaError("Unexpected error from fetching schema", e) + + +def _safe_dict(obj): + '''Returns a dict from a dict or json string''' + if isinstance(obj, str): + return json.loads(obj) + else: + return obj + +def _validate(fetch_schema_func, schema_path, spec): + '''Validate the given spec + + Fetch the schema and then validate. Upon a error from fetching or validation, + a DcaeException is raised. + + Parameters + ---------- + fetch_schema_func: function that takes schema_path -> dict representation of schema + throws a FetchSchemaError upon any failure + schema_path: string - path to schema + spec: dict or string representation of JSON of schema instance + + Returns + ------- + Nothing, silence is golden + ''' + try: + schema = fetch_schema_func(schema_path) + validate(_safe_dict(spec), schema) + except ValidationError as e: + reraise_with_msg(e, as_dcae=True) + except FetchSchemaError as e: + reraise_with_msg(e, as_dcae=True) + +_validate_using_nexus = partial(_validate, _fetch_schema) + + +def apply_defaults(properties_definition, properties): + """Utility method to enforce expected defaults + + This method is used to enforce properties that are *expected* to have at least + the default if not set by a user. Expected properties are not required but + have a default set. jsonschema does not provide this. + + Parameters + ---------- + properties_definition: dict of the schema definition of the properties to use + for verifying and applying defaults + properties: dict of the target properties to verify and apply defaults to + + Return + ------ + dict - a new version of properties that has the expected default values + """ + # Recursively process all inner objects. Look for more properties and not match + # on type + for k,v in six.iteritems(properties_definition): + if "properties" in v: + properties[k] = apply_defaults(v["properties"], properties.get(k, {})) + + # Collect defaults + defaults = [ (k, v["default"]) for k, v in properties_definition.items() if "default" in v ] + + def apply_default(accumulator, default): + k, v = default + if k not in accumulator: + # Not doing data type checking and any casting. Assuming that this + # should have been taken care of in validation + accumulator[k] = v + return accumulator + + return reduce(apply_default, defaults, properties) + +def apply_defaults_docker_config(config): + """Apply expected defaults to Docker config + Parameters + ---------- + config: Docker config dict + Return + ------ + Updated Docker config dict + """ + # Apply health check defaults + healthcheck_type = config["healthcheck"]["type"] + component_spec = _fetch_schema(cli_config.get_path_component_spec()) + + if healthcheck_type in ["http", "https"]: + apply_defaults_func = partial(apply_defaults, + component_spec["definitions"]["docker_healthcheck_http"]["properties"]) + elif healthcheck_type in ["script"]: + apply_defaults_func = partial(apply_defaults, + component_spec["definitions"]["docker_healthcheck_script"]["properties"]) + else: + # You should never get here + apply_defaults_func = lambda x: x + + config["healthcheck"] = apply_defaults_func(config["healthcheck"]) + + return config + +def validate_component(spec): + _validate_using_nexus(cli_config.get_path_component_spec(), spec) + + # REVIEW: Could not determine how to do this nicely in json schema. This is + # not ideal. We want json schema to be the "it" for validation. + ctype = component_type = spec["self"]["component_type"] + + if ctype == "cdap": + invalid = [s for s in spec["streams"].get("subscribes", []) \ + if s["type"] in ["data_router", "data router"]] + if invalid: + raise DcaeException("Cdap component as data router subscriber is not supported.") + +def validate_format(spec): + path = cli_config.get_path_data_format() + _validate_using_nexus(path, spec) -- cgit 1.2.3-korg