67fe9bf5842702e2c834da383f6e0d2046122ce9
[dcaegen2/platform/cli.git] / dcae-cli / dcae_cli / catalog / mock / tests / test_schema.py
1 # ============LICENSE_START=======================================================
2 # org.onap.dcae
3 # ================================================================================
4 # Copyright (c) 2017 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 # ECOMP is a trademark and service mark of AT&T Intellectual Property.
20
21 # -*- coding: utf-8 -*-
22 '''
23 Tests the mock catalog
24 '''
25 import pytest
26 import json, copy
27
28 from dcae_cli.catalog.mock.schema import validate_component, validate_format,apply_defaults_docker_config, apply_defaults
29 from dcae_cli.catalog.mock import schema
30 from dcae_cli.util.exc import DcaeException
31
32
33 format_test = r'''
34 {
35   "self": {
36     "name": "asimov.format.integerClassification",
37     "version": "1.0.0",
38     "description": "Represents a single classification from a machine learning model - just a test version"
39   },
40   "dataformatversion": "1.0.0",
41   "jsonschema": {
42       "$schema": "http://json-schema.org/draft-04/schema#",
43       "type": "object",
44       "properties": {
45           "classification": {
46               "type": "string"
47           }
48       },
49       "additionalProperties": false
50    }
51 }
52 '''
53
54
55 component_test = r'''
56 {
57   "self": {
58     "version": "1.0.0",
59     "name": "asimov.component.kpi_anomaly",
60     "description": "Classifies VNF KPI data as anomalous",
61     "component_type": "docker"
62   },
63   "streams": {
64     "subscribes": [
65       {
66         "format": "dcae.vnf.kpi",
67         "version": "1.0.0",
68         "route": "/data",
69         "type": "http"
70       },
71       {
72         "format":"std.format_one",
73         "version":"1.0.0",
74         "config_key":"sub2",
75         "type": "message router"
76       }
77     ],
78     "publishes": [
79       {
80         "format": "asimov.format.integerClassification",
81         "version": "1.0.0",
82         "config_key": "prediction",
83         "type": "http"
84       },
85       {
86         "format":"std.format_one",
87         "version":"1.0.0",
88         "config_key":"pub2",
89         "type": "message router"
90       }
91     ]
92   },
93   "services": {
94     "calls": [],
95     "provides": [
96       {
97         "route": "/score-vnf",
98         "request": {
99           "format": "dcae.vnf.kpi",
100           "version": "1.0.0"
101         },
102         "response": {
103           "format": "asimov.format.integerClassification",
104           "version": "1.0.0"
105         }
106       }
107     ]
108   },
109   "parameters": [
110     {
111       "name": "threshold",
112       "value": 0.75,
113       "description": "Probability threshold to exceed to be anomalous"
114     }
115   ],
116   "artifacts": [
117     {
118       "uri": "somedockercontainerpath",
119       "type": "docker image"
120     }
121     ],
122   "auxilary": {
123     "healthcheck": {
124       "type": "http",
125       "endpoint": "/health"
126     }
127   }
128 }
129 '''
130
131 cdap_component_test = r'''
132 {  
133    "self":{  
134       "name":"std.cdap_comp",
135       "version":"0.0.0",
136       "description":"cdap test component",
137       "component_type":"cdap"
138    },
139    "streams":{  
140       "publishes":[  
141          {  
142             "format":"std.format_one",
143             "version":"1.0.0",
144             "config_key":"pub1",
145             "type": "http"
146          },
147          {  
148             "format":"std.format_one",
149             "version":"1.0.0",
150             "config_key":"pub2",
151             "type": "message router"
152          }
153       ],
154       "subscribes":[  
155          {  
156             "format":"std.format_two",
157             "version":"1.5.0",
158             "route":"/sub1",
159             "type": "http"
160          },
161          {  
162             "format":"std.format_one",
163             "version":"1.0.0",
164             "config_key":"sub2",
165             "type": "message router"
166          }
167       ]
168    },
169    "services":{  
170       "calls":[  
171
172       ],
173       "provides":[  
174          {  
175             "request":{  
176                "format":"std.format_one",
177                "version":"1.0.0"
178             },
179             "response":{  
180                "format":"std.format_two",
181                "version":"1.5.0"
182             },
183             "service_name":"baphomet",
184             "service_endpoint":"rises",
185             "verb":"GET"
186          }
187       ]
188    },
189    "parameters":[  
190
191    ],
192    "artifacts": [
193      {
194        "uri": "somecdapjarurl",
195        "type": "jar"
196      }
197      ],
198    "auxilary": {
199      "streamname":"who",
200      "artifact_name" : "HelloWorld",
201      "artifact_version" : "3.4.3",
202      "programs" : [
203                     {"program_type" : "flows", "program_id" : "WhoFlow"},
204                     {"program_type" : "services", "program_id" : "Greeting"}
205                   ],
206      "namespace" : "hw"
207    }
208 }
209 '''
210
211
212 def test_basic():
213     validate_component(json.loads(component_test))
214     validate_format(json.loads(format_test))
215     validate_component(json.loads(cdap_component_test))
216
217     # Test with DR publishes for cdap
218     dr_publishes = { "format":"std.format_one", "version":"1.0.0",
219             "config_key":"pub3", "type": "data router" }
220     cdap_valid = json.loads(cdap_component_test)
221     cdap_valid["streams"]["publishes"].append(dr_publishes)
222
223     # Test with DR subscribes for cdap
224     cdap_invalid = json.loads(cdap_component_test)
225     ss = cdap_invalid["streams"]["subscribes"][0]
226     ss["type"] = "data_router"
227     ss["config_key"] = "nada"
228     cdap_invalid["streams"]["subscribes"][0] = ss
229
230     with pytest.raises(DcaeException):
231         validate_component(cdap_invalid)
232
233
234
235 def test_validate_docker_config():
236
237     def compose_spec(config):
238         spec = json.loads(component_test)
239         spec["auxilary"] = config
240         return spec
241
242     good_docker_configs = [
243             {
244                 "healthcheck": {
245                     "type": "http",
246                     "endpoint": "/health",
247                     "interval": "15s",
248                     "timeout": "1s"
249                 }
250             },
251             {
252                 "healthcheck": {
253                     "type": "script",
254                     "script": "curl something"
255                     }
256             }]
257
258     for good_config in good_docker_configs:
259         spec = compose_spec(good_config)
260         assert validate_component(spec) == None
261
262     bad_docker_configs = [
263             #{},
264             {
265                 "healthcheck": {}
266             },
267             {
268                 "healthcheck": {
269                     "type": "http"
270                 }
271             },
272             {
273                 "healthcheck": {
274                     "type": "http",
275                     "script": "huh"
276                 }
277             }]
278
279     for bad_config in bad_docker_configs:
280         with pytest.raises(DcaeException):
281             spec = compose_spec(bad_config)
282             validate_component(spec)
283
284
285 def test_validate_cdap_config():
286
287     def compose_spec(config):
288         spec = json.loads(cdap_component_test)
289         spec["auxilary"] = config
290         return spec
291
292     good_cdap_configs = [
293        {
294            "streamname":"streamname",
295            "artifact_version":"6.6.6",
296            "artifact_name" : "testname",
297            "programs" : [],
298        },
299        {
300            "streamname":"streamname",
301            "artifact_version":"6.6.6",
302            "artifact_name" : "testname",
303            "programs" : [{"program_type" : "flows", "program_id" : "flow_id"}],
304            "program_preferences" : [{"program_type" : "flows", "program_id" : "flow_id", "program_pref" : {"he" : "shall rise"}}],
305            "namespace" : "this should be an optional field",
306            "app_preferences" : {"he" : "shall rise"}
307        }
308     ]
309
310     for good_config in good_cdap_configs:
311         spec = compose_spec(good_config)
312         assert validate_component(spec) == None
313
314     bad_cdap_configs = [
315             {},
316             {"YOU HAVE" : "ALWAYS FAILED ME"}
317             ]
318
319     for bad_config in bad_cdap_configs:
320         with pytest.raises(DcaeException):
321             spec = compose_spec(bad_config)
322             validate_component(bad_config)
323
324
325 def test_apply_defaults():
326     definition = { "length": { "default": 10 }, "duration": { "default": "10s" } }
327
328     # Test: Add expected properties
329     properties = {}
330     actual = apply_defaults(definition, properties)
331     assert actual == { "length": 10, "duration": "10s" }
332
333     # Test: Don't mess with existing values
334     properties = { "length": 100, "duration": "100s" }
335     actual = apply_defaults(definition, properties)
336     assert actual == properties
337
338     # Test: No defaults to apply
339     definition = { "length": {}, "duration": {} }
340     properties = { "width": 100 }
341     actual = apply_defaults(definition, properties)
342     assert actual == properties
343
344     # Test: Nested object
345     definition = { "length": { "default": 10 }, "duration": { "default": "10s" },
346             "location": { "properties": { "lat": { "default": "40" },
347                 "long": { "default": "75" }, "alt": {} } } }
348     actual = apply_defaults(definition, {})
349     assert actual == {'duration': '10s', 'length': 10,
350             'location': {'lat': '40', 'long': '75'}}
351
352
353 def test_apply_defaults_docker_config():
354     # Test: Adding of missing expected properties for http
355     dc = { "healthcheck": { "type": "http", "endpoint": "/foo" } }
356     actual = apply_defaults_docker_config(dc)
357
358     assert "interval" in actual["healthcheck"]
359     assert "timeout" in actual["healthcheck"]
360
361     # Test: Adding of missing expected properties for script
362     dc = { "healthcheck": { "type": "script", "script": "/bin/do-something" } }
363     actual = apply_defaults_docker_config(dc)
364
365     assert "interval" in actual["healthcheck"]
366     assert "timeout" in actual["healthcheck"]
367
368     # Test: Expected properties already exist
369     dc = { "healthcheck": { "type": "http", "endpoint": "/foo",
370         "interval": "10000s", "timeout": "100000s" } }
371     actual = apply_defaults_docker_config(dc)
372     assert dc == actual
373
374     # Test: Never should happen
375     dc = { "healthcheck": { "type": "bogus" } }
376     actual = apply_defaults_docker_config(dc)
377     assert dc == actual
378
379
380 def test_validate():
381     fake_schema = {
382             "$schema": "http://json-schema.org/draft-04/schema#",
383             "title": "Test schema",
384             "type": "object",
385             "properties": {
386                 "foo": { "type": "string" },
387                 "bar": { "type": "integer" }
388                 },
389             "required": ["foo", "bar"]
390             }
391
392     good_path = "/correct_path"
393
394     def fetch_schema(path):
395         if path == good_path:
396             return fake_schema
397         else:
398             raise schema.FetchSchemaError("Schema not found")
399
400     # Success case
401
402     good_instance = { "foo": "hello", "bar": 1776 }
403
404     schema._validate(fetch_schema, good_path, good_instance)
405
406     # Error from validating
407
408     bad_instance = {}
409
410     with pytest.raises(DcaeException):
411         schema._validate(fetch_schema, good_path, bad_instance)
412
413     # Error from fetching
414
415     bad_path = "/wrong_path"
416
417     with pytest.raises(DcaeException):
418         schema._validate(fetch_schema, bad_path, good_instance)