1 # ============LICENSE_START=======================================================
3 # ================================================================================
4 # Copyright (c) 2017-2018 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
10 # http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
19 # ECOMP is a trademark and service mark of AT&T Intellectual Property.
21 # -*- coding: utf-8 -*-
23 Tests the mock catalog
26 from copy import deepcopy
27 from functools import partial
31 from sqlalchemy.exc import IntegrityError
33 from dcae_cli.catalog.mock.catalog import MockCatalog, MissingEntry, DuplicateEntry, _get_unique_format_things
34 from dcae_cli.catalog.mock import catalog
37 _c1_spec = {'self': {'name': 'std.comp_one',
39 'description': 'comp1',
40 'component_type': 'docker'},
41 'streams': {'publishes': [{'format': 'std.format_one',
45 'subscribes': [{'format': 'std.format_one',
49 'services': {'calls': [{'request': {'format': 'std.format_one',
51 'response': {'format': 'std.format_one',
53 'config_key': 'call1'}],
54 'provides': [{'request': {'format': 'std.format_one',
56 'response': {'format': 'std.format_one',
59 'parameters': [{"name": "foo",
61 "description": "the foo thing"},
64 "description": "the bar thing"}
66 'artifacts': [{ "uri": "foo-image", "type": "docker image" }],
70 "endpoint": "/health",
77 _c2_spec = {'self': {'name': 'std.comp_two',
79 'description': 'comp2',
80 'component_type': 'docker'},
81 'streams': {'publishes': [],
82 'subscribes': [{'format': 'std.format_one',
86 'services': {'calls': [],
87 'provides': [{'request': {'format': 'std.format_one',
89 'response': {'format': 'std.format_one',
93 'artifacts': [{ "uri": "bar-image", "type": "docker image" }],
97 "endpoint": "/health",
105 _c2v2_spec = {'self': {'name': 'std.comp_two',
107 'description': 'comp2',
108 'component_type': 'docker'},
109 'streams': {'publishes': [],
110 'subscribes': [{'format': 'std.format_one',
114 'services': {'calls': [],
115 'provides': [{'request': {'format': 'std.format_one',
117 'response': {'format': 'std.format_one',
119 'route': '/prov1'}]},
121 'artifacts': [{ "uri": "baz-image", "type": "docker image" }],
125 "endpoint": "/health",
133 _c3_spec = {'self': {'name': 'std.comp_three',
135 'description': 'comp3',
136 'component_type': 'docker'},
137 'streams': {'publishes': [],
138 'subscribes': [{'format': 'std.format_two',
142 'services': {'calls': [],
143 'provides': [{'request': {'format': 'std.format_one',
145 'response': {'format': 'std.format_two',
147 'route': '/prov1'}]},
149 'artifacts': [{ "uri": "bazinga-image", "type": "docker image" }],
153 "endpoint": "/health",
163 "name": "std.format_one",
167 "dataformatversion": "1.0.0",
169 "$schema": "http://json-schema.org/draft-04/schema#",
176 "required": ["raw-text"],
177 "additionalProperties": False
182 "name": "std.format_two",
186 "dataformatversion": "1.0.0",
188 "$schema": "http://json-schema.org/draft-04/schema#",
195 "required": ["raw-text"],
196 "additionalProperties": False
201 "name": "std.format_two",
205 "dataformatversion": "1.0.0",
207 "$schema": "http://json-schema.org/draft-04/schema#",
214 "required": ["raw-text"],
215 "additionalProperties": False
221 "name":"std.cdap_comp",
223 "description":"cdap test component",
224 "component_type":"cdap"
229 "format":"std.format_one",
237 "format":"std.format_two",
251 "format":"std.format_one",
255 "format":"std.format_two",
258 "service_name":"baphomet",
259 "service_endpoint":"rises",
266 "app_preferences" : [],
267 "program_preferences" : []
269 "artifacts": [{"uri": "bahpomet.com", "type": "jar"}],
271 "streamname":"streamname",
272 "artifact_version":"6.6.6",
273 "artifact_name": "test_name",
274 "programs" : [{"program_type" : "flows", "program_id" : "flow_id"}]
280 def test_component_basic(mock_cli_config, mock_db_url, catalog=None):
281 '''Tests basic component usage of MockCatalog'''
283 mc = MockCatalog(db_name='dcae_cli.test.db', purge_existing=True,
284 enforce_image=False, db_url=mock_db_url)
288 c1_spec = deepcopy(_c1_spec)
289 df1_spec = deepcopy(_df1_spec)
290 df2_spec = deepcopy(_df2_spec)
292 user = "test_component_basic"
295 mc.add_format(df2_spec, user)
298 with pytest.raises(DuplicateEntry):
299 mc.add_format(df2_spec, user)
301 # component relies on df1_spec which hasn't been added
302 with pytest.raises(MissingEntry):
303 mc.add_component(user, c1_spec)
306 mc.add_format(df1_spec, user)
307 mc.add_component(user, c1_spec)
309 with pytest.raises(DuplicateEntry):
310 mc.add_component(user, c1_spec)
312 cname, cver = mc.verify_component('std.comp_one', version=None)
313 assert cver == '1.0.0'
316 def test_format_basic(mock_cli_config, mock_db_url, catalog=None):
317 '''Tests basic data format usage of MockCatalog'''
319 mc = MockCatalog(db_name='dcae_cli.test.db', purge_existing=True,
324 user = "test_format_basic"
326 df1_spec = deepcopy(_df1_spec)
327 df2_spec = deepcopy(_df2_spec)
330 mc.add_format(df1_spec, user)
333 with pytest.raises(DuplicateEntry):
334 mc.add_format(df1_spec, user)
336 # allow update of same version
337 new_descr = 'a new description'
338 df1_spec['self']['description'] = new_descr
339 mc.add_format(df1_spec, user, update=True)
341 # adding a new version is kosher
343 df1_spec['self']['version'] = new_ver
344 mc.add_format(df1_spec, user)
346 # can't update a format that doesn't exist
347 with pytest.raises(MissingEntry):
348 mc.add_format(df2_spec, user, update=True)
350 # get spec and make sure it's updated
351 spec = mc.get_format_spec(df1_spec['self']['name'], version=None)
352 assert spec['self']['version'] == new_ver
353 assert spec['self']['description'] == new_descr
356 def test_discovery(mock_cli_config, mock_db_url, catalog=None):
357 '''Tests creation of discovery objects'''
359 mc = MockCatalog(db_name='dcae_cli.test.db', purge_existing=True,
360 enforce_image=False, db_url=mock_db_url)
364 user = "test_discovery"
366 c1_spec = deepcopy(_c1_spec)
367 df1_spec = deepcopy(_df1_spec)
368 c2_spec = deepcopy(_c2_spec)
370 mc.add_format(df1_spec, user)
371 mc.add_component(user, c1_spec)
372 mc.add_component(user, c2_spec)
374 params, interfaces = mc.get_discovery_for_docker(c1_spec['self']['name'], c1_spec['self']['version'])
375 assert params == {'bar': 2, 'foo': 1}
376 assert interfaces == {'call1': [('std.comp_two', '1.0.0')], 'pub1': [('std.comp_two', '1.0.0')]}
380 '''Returns a (name, version, component type) tuple from a given component spec dict'''
381 return dd['self']['name'], dd['self']['version'], dd['self']['component_type']
384 def _comp_tuple_set(*dds):
385 '''Runs a set of component spec tuples'''
386 return set(map(_spec_tuple, dds))
389 def _format_tuple(dd):
390 '''Returns a (name, version) tuple from a given data format spec dict'''
391 return dd['self']['name'], dd['self']['version']
394 def _format_tuple_set(*dds):
395 '''Runs a set of data format spec tuples'''
396 return set(map(_format_tuple, dds))
399 def test_comp_list(mock_cli_config, mock_db_url, catalog=None):
400 '''Tests the list functionality of the catalog'''
402 mc = MockCatalog(db_name='dcae_cli.test.db', purge_existing=True,
403 enforce_image=False, db_url=mock_db_url)
407 user = "test_comp_list"
409 df1_spec = deepcopy(_df1_spec)
410 df2_spec = deepcopy(_df2_spec)
411 df2v2_spec = deepcopy(_df2v2_spec)
413 c1_spec = deepcopy(_c1_spec)
414 c2_spec = deepcopy(_c2_spec)
415 c2v2_spec = deepcopy(_c2v2_spec)
416 c3_spec = deepcopy(_c3_spec)
418 mc.add_format(df1_spec, user)
419 mc.add_format(df2_spec, user)
420 mc.add_format(df2v2_spec, user)
421 mc.add_component(user, c1_spec)
422 mc.add_component(user, c2_spec)
423 mc.add_component(user, c2v2_spec)
424 mc.add_component(user, c3_spec)
426 mc.add_component(user,_cdap_spec)
428 def components_to_specs(components):
429 return [ json.loads(c["spec"]) for c in components ]
431 # latest by default. only v2 of c2
432 components = mc.list_components()
433 specs = components_to_specs(components)
434 assert _comp_tuple_set(*specs) == _comp_tuple_set(c1_spec, c2v2_spec, c3_spec, _cdap_spec)
437 components = mc.list_components(latest=False)
438 specs = components_to_specs(components)
439 assert _comp_tuple_set(*specs) == _comp_tuple_set(c1_spec, c2_spec, c2v2_spec, c3_spec, _cdap_spec)
441 components = mc.list_components(subscribes=[('std.format_one', None)])
442 specs = components_to_specs(components)
443 assert _comp_tuple_set(*specs) == _comp_tuple_set(c1_spec, c2v2_spec)
445 # no comps subscribe to latest std.format_two
446 components = mc.list_components(subscribes=[('std.format_two', None)])
447 assert not components
449 components = mc.list_components(subscribes=[('std.format_two', '1.5.0')])
450 specs = components_to_specs(components)
451 assert _comp_tuple_set(*specs) == _comp_tuple_set(c3_spec, _cdap_spec)
453 # raise if format doesn't exist
454 with pytest.raises(MissingEntry):
455 mc.list_components(subscribes=[('std.format_two', '5.0.0')])
457 components = mc.list_components(publishes=[('std.format_one', None)])
458 specs = components_to_specs(components)
459 assert _comp_tuple_set(*specs) == _comp_tuple_set(c1_spec, _cdap_spec)
461 components = mc.list_components(calls=[(('std.format_one', None), ('std.format_one', None)), ])
462 specs = components_to_specs(components)
463 assert _comp_tuple_set(*specs) == _comp_tuple_set(c1_spec)
465 # raise if format doesn't exist
466 with pytest.raises(MissingEntry):
467 mc.list_components(calls=[(('std.format_one', '5.0.0'), ('std.format_one', None)), ])
469 components = mc.list_components(provides=[(('std.format_one', '1.0.0'), ('std.format_two', '1.5.0')), ])
470 specs = components_to_specs(components)
471 assert _comp_tuple_set(*specs) == _comp_tuple_set(c3_spec, _cdap_spec)
473 # test for listing published components
475 name_pub = c1_spec["self"]["name"]
476 version_pub = c1_spec["self"]["version"]
477 mc.publish_component(user, name_pub, version_pub)
478 components = mc.list_components(only_published=True)
479 specs = components_to_specs(components)
480 assert _comp_tuple_set(*specs) == _comp_tuple_set(c1_spec)
482 components = mc.list_components(only_published=False)
483 assert len(components) == 4
486 def test_format_list(mock_cli_config, mock_db_url, catalog=None):
487 '''Tests the list functionality of the catalog'''
489 mc = MockCatalog(db_name='dcae_cli.test.db', purge_existing=True,
490 enforce_image=False, db_url=mock_db_url)
494 user = "test_format_list"
496 df1_spec = deepcopy(_df1_spec)
497 df2_spec = deepcopy(_df2_spec)
498 df2v2_spec = deepcopy(_df2v2_spec)
500 mc.add_format(df1_spec, user)
501 mc.add_format(df2_spec, user)
502 mc.add_format(df2v2_spec, user)
504 def formats_to_specs(components):
505 return [ json.loads(c["spec"]) for c in components ]
507 # latest by default. ensure only v2 of df2 makes it
508 formats = mc.list_formats()
509 specs = formats_to_specs(formats)
510 assert _format_tuple_set(*specs) == _format_tuple_set(df1_spec, df2v2_spec)
513 formats = mc.list_formats(latest=False)
514 specs = formats_to_specs(formats)
515 assert _format_tuple_set(*specs) == _format_tuple_set(df1_spec, df2_spec, df2v2_spec)
517 # test listing of published formats
519 name_pub = df1_spec["self"]["name"]
520 version_pub = df1_spec["self"]["version"]
522 mc.publish_format(user, name_pub, version_pub)
523 formats = mc.list_formats(only_published=True)
524 specs = formats_to_specs(formats)
525 assert _format_tuple_set(*specs) == _format_tuple_set(df1_spec)
527 formats = mc.list_formats(only_published=False)
528 assert len(formats) == 2
531 def test_component_add_cdap(mock_cli_config, mock_db_url, catalog=None):
532 '''Adds a mock CDAP application'''
534 mc = MockCatalog(db_name='dcae_cli.test.db', purge_existing=True,
539 user = "test_component_add_cdap"
541 df1_spec = deepcopy(_df1_spec)
542 df2_spec = deepcopy(_df2_spec)
544 mc.add_format(df1_spec, user)
545 mc.add_format(df2_spec, user)
547 mc.add_component(user, _cdap_spec)
549 name, version, _ = _spec_tuple(_cdap_spec)
550 jar_out, cdap_config_out, spec_out = mc.get_cdap(name, version)
552 assert _cdap_spec["artifacts"][0]["uri"] == jar_out
553 assert _cdap_spec["auxilary"] == cdap_config_out
554 assert _cdap_spec == spec_out
557 def test_get_discovery_from_spec(mock_cli_config, mock_db_url):
558 mc = MockCatalog(db_name='dcae_cli.test.db', purge_existing=True,
559 enforce_image=False, db_url=mock_db_url)
561 user = "test_get_discovery_from_spec"
563 c1_spec_updated = deepcopy(_c1_spec)
564 c1_spec_updated["streams"]["publishes"][0] = {
565 'format': 'std.format_one',
567 'config_key': 'pub1',
570 c1_spec_updated["streams"]["subscribes"][0] = {
571 'format': 'std.format_one',
577 # Case when c1 doesn't exist
579 mc.add_format(_df1_spec, user)
580 mc.add_component(user, _c2_spec)
581 actual_params, actual_interface_map, actual_dmaap_config_keys \
582 = mc.get_discovery_from_spec(user, c1_spec_updated, None)
584 assert actual_params == {'bar': 2, 'foo': 1}
585 assert actual_interface_map == { 'pub1': [('std.comp_two', '1.0.0')],
586 'call1': [('std.comp_two', '1.0.0')] }
587 assert actual_dmaap_config_keys == ([], [])
589 # Case when c1 already exist
591 mc.add_component(user,_c1_spec)
593 c1_spec_updated["services"]["calls"][0]["config_key"] = "callme"
594 actual_params, actual_interface_map, actual_dmaap_config_keys \
595 = mc.get_discovery_from_spec(user, c1_spec_updated, None)
597 assert actual_params == {'bar': 2, 'foo': 1}
598 assert actual_interface_map == { 'pub1': [('std.comp_two', '1.0.0')],
599 'callme': [('std.comp_two', '1.0.0')] }
600 assert actual_dmaap_config_keys == ([], [])
602 # Case where add in dmaap streams
603 # TODO: Add in subscribes test case after spec gets pushed
605 c1_spec_updated["streams"]["publishes"][0] = {
606 'format': 'std.format_one',
608 'config_key': 'pub1',
609 'type': 'message router'
612 actual_params, actual_interface_map, actual_dmaap_config_keys \
613 = mc.get_discovery_from_spec(user, c1_spec_updated, None)
615 assert actual_params == {'bar': 2, 'foo': 1}
616 assert actual_interface_map == { 'callme': [('std.comp_two', '1.0.0')] }
617 assert actual_dmaap_config_keys == (["pub1"], [])
619 # Case when cdap spec doesn't exist
621 cdap_spec = deepcopy(_cdap_spec)
622 cdap_spec["streams"]["publishes"][0] = {
623 'format': 'std.format_one',
625 'config_key': 'pub1',
628 cdap_spec["streams"]["subscribes"][0] = {
629 'format': 'std.format_two',
635 mc.add_format(_df2_spec, user)
636 actual_params, actual_interface_map, actual_dmaap_config_keys \
637 = mc.get_discovery_from_spec(user, cdap_spec, None)
639 assert actual_params == {'program_preferences': [], 'app_config': {}, 'app_preferences': {}}
640 assert actual_interface_map == {'pub1': [('std.comp_two', '1.0.0'), ('std.comp_one', '1.0.0')]}
641 assert actual_dmaap_config_keys == ([], [])
644 def test_get_unpublished_formats(mock_cli_config, mock_db_url, catalog=None):
646 mc = MockCatalog(db_name='dcae_cli.test.db', purge_existing=True,
647 enforce_image=False, db_url=mock_db_url)
651 user = "test_get_unpublished_formats"
653 mc.add_format(_df1_spec, user)
654 mc.add_component(user, _c1_spec)
656 # detect unpublished formats
658 name_to_pub = _c1_spec["self"]["name"]
659 version_to_pub = _c1_spec["self"]["version"]
660 formats = mc.get_unpublished_formats(name_to_pub, version_to_pub)
661 assert [('std.format_one', '1.0.0')] == formats
663 # all formats published
665 mc.publish_format(user, _df1_spec["self"]["name"], _df1_spec["self"]["version"])
666 formats = mc.get_unpublished_formats(name_to_pub, version_to_pub)
667 assert len(formats) == 0
670 def test_get_unique_format_things():
671 def create_tuple(entry):
672 return (entry["name"], entry["version"])
674 def get_orm(name, version):
675 return ("ORM", name, version)
677 entries = [{"name": "abc", "version": 123},
678 {"name": "abc", "version": 123},
679 {"name": "abc", "version": 123},
680 {"name": "def", "version": 456},
681 {"name": "def", "version": 456}]
683 get_unique_fake_format = partial(_get_unique_format_things, create_tuple,
685 expected = [("ORM", "abc", 123), ("ORM", "def", 456)]
687 assert sorted(expected) == sorted(get_unique_fake_format(entries))
690 def test_filter_latest():
691 orms = [('std.empty.get', '1.0.0'), ('std.unknown', '1.0.0'),
692 ('std.unknown', '1.0.1'), ('std.empty.get', '1.0.1')]
694 assert list(catalog._filter_latest(orms)) == [('std.empty.get', '1.0.1'), \
695 ('std.unknown', '1.0.1')]
698 def test_raise_if_duplicate():
699 class FakeOrig(object):
700 args = ["unique", "duplicate"]
704 error = IntegrityError("Error about uniqueness", None, orig)
706 with pytest.raises(catalog.DuplicateEntry):
707 catalog._raise_if_duplicate(url, error)
709 # Couldn't find psycopg2.IntegrityError constructor nor way
710 # to set pgcode so decided to mock it.
711 class FakeOrigPostgres(object):
715 orig = FakeOrigPostgres()
716 error = IntegrityError("Error about uniqueness", None, orig)
718 with pytest.raises(catalog.DuplicateEntry):
719 catalog._raise_if_duplicate(url, error)
722 def test_get_docker_image_from_spec():
723 assert "foo-image" == catalog._get_docker_image_from_spec(_c1_spec)
725 def test_get_cdap_jar_from_spec():
726 assert "bahpomet.com" == catalog._get_cdap_jar_from_spec(_cdap_spec)
729 def test_build_config_keys_map():
733 {'format': 'std.format_one', 'version': '1.0.0',
734 'config_key': 'pub1', 'type': 'http'},
735 {'format': 'std.format_one', 'version': '1.0.0',
736 'config_key': 'pub2', 'type': 'message_router'}
739 {'format': 'std.format_one', 'version': '1.0.0', 'route': '/sub1',
741 {'format': 'std.format_one', 'version': '1.0.0',
742 'config_key': 'sub2', 'type': 'message_router'}
747 {'request': {'format': 'std.format_one', 'version': '1.0.0'},
748 'response': {'format': 'std.format_one', 'version': '1.0.0'},
749 'config_key': 'call1'}
752 {'request': {'format': 'std.format_one', 'version': '1.0.0'},
753 'response': {'format': 'std.format_one', 'version': '1.0.0'},
759 grouping = catalog.build_config_keys_map(stub_spec)
760 expected = {'call1': {'group': 'services_calls'}, 'pub1': {'type': 'http', 'group': 'streams_publishes'}, 'sub2': {'type': 'message_router', 'group': 'streams_subscribes'}, 'pub2': {'type': 'message_router', 'group': 'streams_publishes'}}
761 assert expected == grouping
764 def test_get_data_router_subscriber_route():
765 spec = {"streams": {"subscribes": [ { "type": "data_router", "config_key":
766 "alpha", "route": "/alpha" }, { "type": "message_router", "config_key":
769 assert "/alpha" == catalog.get_data_router_subscriber_route(spec, "alpha")
771 with pytest.raises(catalog.MissingEntry):
772 catalog.get_data_router_subscriber_route(spec, "beta")
774 with pytest.raises(catalog.MissingEntry):
775 catalog.get_data_router_subscriber_route(spec, "gamma")
778 if __name__ == '__main__':
780 pytest.main([__file__, ])