[DCAE] INFO.yaml update
[dcaegen2/platform.git] / adapter / acumos / aoconversion / dataformat_gen.py
1 # ============LICENSE_START====================================================
2 # org.onap.dcae
3 # =============================================================================
4 # Copyright (c) 2019 AT&T Intellectual Property. All rights reserved.
5 # =============================================================================
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 #      http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 # ============LICENSE_END======================================================
18
19 from subprocess import PIPE, Popen
20 import json
21 from jsonschema import validate
22 import requests
23 from aoconversion import utils, exceptions
24
25
26 def _get_js_schema():
27     res = requests.get("http://json-schema.org/draft-04/schema#")
28     return res.json()
29
30
31 def _get_dcae_df_schema():
32     res = requests.get(
33         "https://gerrit.onap.org/r/gitweb?p=dcaegen2/platform/cli.git;a=blob_plain;f=component-json-schemas/data-format/dcae-cli-v1/data-format-schema.json;hb=HEAD"
34     )
35     return res.json()
36
37
38 def _protobuf_to_js(proto_path):
39     """
40     Converts a protobuf to jsonschema and returns the generated schema as a JSON object.
41     """
42     cmd = ["protobuf-jsonschema", proto_path]
43     p = Popen(cmd, stderr=PIPE, stdout=PIPE)
44     out = p.stdout.read()
45     asjson = json.loads(out)
46
47     # change the defintion names to remove the random package name that acumos generates
48     defs = asjson["definitions"]
49     defns = list(defs.keys())
50     for defn in defns:
51         # https://stackoverflow.com/questions/16475384/rename-a-dictionary-key
52         defs[defn.split(".")[1]] = defs.pop(defn)
53
54     # make sure what we got out is a valid jsonschema
55     draft4 = _get_js_schema()
56     validate(instance=asjson, schema=draft4)
57
58     return asjson
59
60
61 def _get_needed_formats(meta):
62     """
63     Read the metadata and figure out what the principle data formats are.
64     We cannot determine this from the proto because the proto may list "submessages" in a flat namespace; some of them may not coorespond to a data format but rather a referenced defintion in another.
65     We don't want to generate a data format for submessages though; instead they should be included in definitions as part of the relevent data format
66     """
67     # we use a dict because multiple methods may reuse names
68     needed_formats = {}
69     for method in meta["methods"]:
70         needed_formats[meta["methods"][method]["input"]] = 1
71         needed_formats[meta["methods"][method]["output"]] = 1
72     return list(needed_formats.keys())
73
74
75 def _generate_dcae_data_formats(proto_path, meta, dcae_df_schema, draft_4_schema):
76     """
77     Generates a collection of data formats from the model .proto
78     This helper function is broken out for the ease of unit testing; this can be unit tested easily because all deps are parameters,
79     but generate_dcae_data_formats requires some mocking etc.
80     """
81     js = _protobuf_to_js(proto_path)
82     needed_formats = _get_needed_formats(meta)
83
84     data_formats = []
85
86     used_defns = []
87
88     # iterate over and convert
89     for nf in needed_formats:
90         defn = js["definitions"][nf]
91
92         definitions = {}
93
94         # check for the case where we have an array of other defns
95         for prop in defn["properties"]:
96             if defn["properties"][prop]["type"] == "array" and "$ref" in defn["properties"][prop]["items"]:
97                 unclean_ref_name = defn["properties"][prop]["items"]["$ref"]
98                 clean_ref_name = unclean_ref_name.split(".")[1]
99                 if clean_ref_name in js["definitions"]:
100                     defn["properties"][prop]["items"]["$ref"] = "#/definitions/{0}".format(clean_ref_name)
101                     definitions[clean_ref_name] = js["definitions"][clean_ref_name]
102                     used_defns.append(clean_ref_name)
103                 else:  # this is bad/unsupported, investigate
104                     raise exceptions.UnsupportedFormatScenario()
105
106         # the defns created by this tool do not include a schema field.
107         # I created an issue: https://github.com/devongovett/protobuf-jsonschema/issues/12
108         defn["$schema"] = "http://json-schema.org/draft-04/schema#"
109
110         # Include the definitions, which may be empty {}
111         defn["definitions"] = definitions
112
113         # Validate that our resulting jsonschema is valid jsonschema
114         validate(instance=defn, schema=draft_4_schema)
115
116         # we currently hardcode dataformatversion, since it is the latest and has been for years  https://gerrit.onap.org/r/gitweb?p=dcaegen2/platform/cli.git;a=blob_plain;f=component-json-schemas/data-format/dcae-cli-v1/data-format-schema.json;hb=HEAD
117         dcae_df = {"self": {"name": nf, "version": "1.0.0"}, "dataformatversion": "1.0.1", "jsonschema": defn}
118
119         # make sure the schema validates against the DCAE data format schema
120         validate(instance=dcae_df, schema=dcae_df_schema)
121
122         # if we've passed the validation and exc raising so far, we are good, append this to output list of dcae data formats
123         data_formats.append(dcae_df)
124
125     # make sure every definitin we got out was used. Otherwise, this requires investigation!!
126     if sorted(needed_formats + used_defns) != sorted(list(js["definitions"].keys())):
127         raise exceptions.UnsupportedFormatScenario()
128
129     return data_formats
130
131
132 # Public
133
134
135 def generate_dcae_data_formats(model_repo_path, model_name):
136     """
137     Generates a collection of data formats from the model .proto
138     Writes them to disk
139     Returns them as the return of this call so this can be fed directly into spec gen
140     """
141     data_formats = _generate_dcae_data_formats(
142         "{0}/{1}/model.proto".format(model_repo_path, model_name),
143         utils.get_metadata(model_repo_path, model_name),
144         _get_dcae_df_schema(),
145         _get_js_schema(),
146     )
147
148     # now we iterate over these and write a file to disk for each, since the dcae cli seems to want that
149     for df in data_formats:
150         # name_version seems like a reasonable filename
151         fname = "{0}_{1}_dcae_data_format.json".format(df["self"]["name"], df["self"]["version"])
152         with open("{0}/{1}".format(model_repo_path, fname), "w") as f:
153             f.write(json.dumps(df))
154
155     return data_formats