Merge "Added TMF API 639 Datasource"
This commit is contained in:
commit
46936dc052
@ -29,6 +29,7 @@ Datasources
|
|||||||
nova-config
|
nova-config
|
||||||
prometheus-datasource
|
prometheus-datasource
|
||||||
kapacitor-datasource
|
kapacitor-datasource
|
||||||
|
tmfapi639-datasource
|
||||||
|
|
||||||
Notifiers
|
Notifiers
|
||||||
---------
|
---------
|
||||||
|
37
doc/source/contributor/tmfapi639-datasource.rst
Normal file
37
doc/source/contributor/tmfapi639-datasource.rst
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
TMF API 639 - Vitrage
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This datasource loads to Vitrage topologies exposed in TMF API 639 Resource Inventory Management.
|
||||||
|
https://www.tmforum.org/resources/specification/tmf639-resource-inventory-management-api-rest-specification-r17-0-1/
|
||||||
|
|
||||||
|
The fields used to define the topology will be:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- @type
|
||||||
|
- resourceRelationship : [resource: id]
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
|
||||||
|
1. Create file ``tmfapi639_conf.yaml`` on your vitrage folder (generally: /etc/vitrage/) according to the following template:
|
||||||
|
|
||||||
|
|
||||||
|
| -endpoint:
|
||||||
|
| snapshot: URL CONTAINING COMPLETE TOPOLOGY
|
||||||
|
| update: OPTIONAL URL CONTAINING NOTIFICATIONS FOR TOPOLOGY CHANGES
|
||||||
|
|
||||||
|
You may allow as many endpoints as you desire.
|
||||||
|
|
||||||
|
|
||||||
|
2. Add tmfapi639 to list of datasources in ``/etc/vitrage/vitrage.conf``
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
[datasources]
|
||||||
|
types = ...,tmfapi639,...
|
||||||
|
|
||||||
|
|
||||||
|
3. Restart vitrage service in devstack/openstack
|
||||||
|
|
||||||
|
**Warning:** due to limitations on TMF API definition, topology changes will require all parents all the way to the root to be defined in order to be correctly represented.
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- New TMF API 639 datasource added capable of both handling
|
||||||
|
topology snapshots and further updates. All described within
|
||||||
|
the TMF's API 639 specification.
|
51
vitrage/datasources/tmfapi639/__init__.py
Normal file
51
vitrage/datasources/tmfapi639/__init__.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Copyright 2020
|
||||||
|
#
|
||||||
|
# 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 oslo_config import cfg
|
||||||
|
from vitrage.common.constants import DatasourceOpts as DSOpts
|
||||||
|
from vitrage.common.constants import UpdateMethod
|
||||||
|
|
||||||
|
TMFAPI639_DATASOURCE = 'tmfapi639'
|
||||||
|
|
||||||
|
OPTS = [
|
||||||
|
cfg.StrOpt(DSOpts.TRANSFORMER,
|
||||||
|
default='vitrage.datasources.tmfapi639.transformer.'
|
||||||
|
'TmfApi639Transformer',
|
||||||
|
help='TmfApi639 transformer class path',
|
||||||
|
required=True),
|
||||||
|
cfg.StrOpt(DSOpts.DRIVER,
|
||||||
|
default='vitrage.datasources.tmfapi639.driver.'
|
||||||
|
'TmfApi639Driver',
|
||||||
|
help='TmfApi639 driver class path',
|
||||||
|
required=True),
|
||||||
|
cfg.StrOpt(DSOpts.UPDATE_METHOD,
|
||||||
|
default=UpdateMethod.PULL,
|
||||||
|
help='None: updates only via Vitrage periodic snapshots.'
|
||||||
|
'Pull: updates periodically.'
|
||||||
|
'Push: updates by getting notifications from the'
|
||||||
|
' datasource itself.',
|
||||||
|
required=True),
|
||||||
|
cfg.IntOpt(DSOpts.CHANGES_INTERVAL,
|
||||||
|
default=30,
|
||||||
|
min=10,
|
||||||
|
help='interval in seconds between checking changes in the'
|
||||||
|
' TmfApi 639 interface'),
|
||||||
|
cfg.StrOpt(DSOpts.CONFIG_FILE, default='/etc/vitrage/tmfapi639_conf.yaml',
|
||||||
|
help='TmfApi639 configuration file'
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
class TmfApi639Fields(object):
|
||||||
|
TYPE = 'type'
|
||||||
|
ID = 'id'
|
51
vitrage/datasources/tmfapi639/config.py
Normal file
51
vitrage/datasources/tmfapi639/config.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Copyright 2020
|
||||||
|
#
|
||||||
|
# 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 oslo_config import cfg
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from vitrage.common.constants import DatasourceOpts as DSOpts
|
||||||
|
from vitrage.utils import file as file_utils
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TmfApi639Config(object):
|
||||||
|
def __init__(self):
|
||||||
|
try:
|
||||||
|
tmfapi639_config_file = CONF.tmfapi639[DSOpts.CONFIG_FILE]
|
||||||
|
tmfapi639_config = file_utils.load_yaml_file(tmfapi639_config_file)
|
||||||
|
self.endpoints = self._create_mapping(tmfapi639_config)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Failed initialization: " + str(e))
|
||||||
|
self.endpoints = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_mapping(config):
|
||||||
|
"""Read URL list from config dictionary"""
|
||||||
|
LOG.debug(config)
|
||||||
|
endpoint_list = []
|
||||||
|
# Tuple list containing either 1 or 2 elements (Endpoint and updates)
|
||||||
|
for e in config:
|
||||||
|
snapshot_url = e["endpoint"]["snapshot"]
|
||||||
|
update_url = ""
|
||||||
|
if "update" in e["endpoint"]:
|
||||||
|
update_url = e["endpoint"]["update"]
|
||||||
|
if update_url != "":
|
||||||
|
endpoint_list.append((snapshot_url, update_url))
|
||||||
|
else:
|
||||||
|
endpoint_list.append(snapshot_url)
|
||||||
|
LOG.info("Finished reading endpoints file")
|
||||||
|
return endpoint_list
|
96
vitrage/datasources/tmfapi639/driver.py
Normal file
96
vitrage/datasources/tmfapi639/driver.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Copyright 2020
|
||||||
|
#
|
||||||
|
# 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 oslo_log import log
|
||||||
|
|
||||||
|
from vitrage.datasources.driver_base import DriverBase
|
||||||
|
from vitrage.datasources.tmfapi639 import TMFAPI639_DATASOURCE
|
||||||
|
|
||||||
|
from vitrage.datasources.tmfapi639.config import TmfApi639Config
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TmfApi639Driver(DriverBase):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(TmfApi639Driver, self).__init__()
|
||||||
|
self.config = TmfApi639Config()
|
||||||
|
self.endpoints = self.config.endpoints
|
||||||
|
self.event_lambda = 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_event_types():
|
||||||
|
return ['tmfapi639.instance.create',
|
||||||
|
'tmfapi639.instance.update',
|
||||||
|
'tmfapi639.instance.delete']
|
||||||
|
|
||||||
|
def enrich_event(self, event, event_type):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_all(self, datasource_action):
|
||||||
|
"""Query all entities and send events to the vitrage events queue.
|
||||||
|
|
||||||
|
When done for the first time, send an "end" event to inform it has
|
||||||
|
finished the get_all for the datasource (because it is done
|
||||||
|
asynchronously).
|
||||||
|
"""
|
||||||
|
return self.make_pickleable(self._get_all_entities(),
|
||||||
|
TMFAPI639_DATASOURCE,
|
||||||
|
datasource_action)
|
||||||
|
|
||||||
|
def get_changes(self, datasource_action):
|
||||||
|
"""Send an event to the vitrage events queue upon any change."""
|
||||||
|
return self.make_pickleable(self._get_changes_entities(),
|
||||||
|
TMFAPI639_DATASOURCE,
|
||||||
|
datasource_action)
|
||||||
|
|
||||||
|
def _get_all_entities(self):
|
||||||
|
total = []
|
||||||
|
for pairs in self.endpoints:
|
||||||
|
try:
|
||||||
|
if type(pairs) is tuple: # Contains an update URL
|
||||||
|
LOG.info("Connecting to " + pairs[0] +
|
||||||
|
"with updates in " + pairs[1])
|
||||||
|
r = requests.get(pairs[0])
|
||||||
|
elif type(pairs) is str: # Doesn't contain update URL
|
||||||
|
LOG.info("Connecting to " + pairs)
|
||||||
|
r = requests.get(pairs)
|
||||||
|
r_dict = json.loads(r.text)
|
||||||
|
total += r_dict
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Couldn't establish connection:" + str(e))
|
||||||
|
return total
|
||||||
|
|
||||||
|
def _get_changes_entities(self): # Called by get changes
|
||||||
|
total = []
|
||||||
|
for pairs in self.endpoints:
|
||||||
|
try:
|
||||||
|
if type(pairs) is tuple: # Contains an update URL
|
||||||
|
LOG.info("Connecting to " + pairs[0] +
|
||||||
|
"with updates in " + pairs[1])
|
||||||
|
r = requests.get(pairs[1])
|
||||||
|
r_dict = json.loads(r.text)
|
||||||
|
for e in r_dict:
|
||||||
|
if e["eventId"] < self.event_lambda:
|
||||||
|
continue
|
||||||
|
total.append(e["event"]["resource"])
|
||||||
|
self.event_lambda = e["eventId"]
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Couldn't establish connection:" + str(e))
|
||||||
|
return total
|
97
vitrage/datasources/tmfapi639/transformer.py
Normal file
97
vitrage/datasources/tmfapi639/transformer.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# Copyright 2020
|
||||||
|
#
|
||||||
|
# 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 oslo_log import log as logging
|
||||||
|
|
||||||
|
from vitrage.common.constants import EntityCategory
|
||||||
|
from vitrage.common.constants import VertexProperties as VProps
|
||||||
|
from vitrage.datasources.resource_transformer_base \
|
||||||
|
import ResourceTransformerBase
|
||||||
|
from vitrage.datasources.tmfapi639 import TMFAPI639_DATASOURCE
|
||||||
|
from vitrage.datasources import transformer_base
|
||||||
|
import vitrage.graph.utils as graph_utils
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TmfApi639Transformer(ResourceTransformerBase):
|
||||||
|
|
||||||
|
def __init__(self, transformers):
|
||||||
|
super(TmfApi639Transformer, self).__init__(transformers)
|
||||||
|
|
||||||
|
def _create_snapshot_entity_vertex(self, entity_event):
|
||||||
|
return self._create_vertex(entity_event)
|
||||||
|
|
||||||
|
def _create_update_entity_vertex(self, entity_event):
|
||||||
|
return self._create_vertex(entity_event)
|
||||||
|
|
||||||
|
def _create_snapshot_neighbors(self, entity_event):
|
||||||
|
return self._create_tmfapi639_neighbors(entity_event)
|
||||||
|
|
||||||
|
def _create_update_neighbors(self, entity_event):
|
||||||
|
return self._create_tmfapi639_neighbors(entity_event)
|
||||||
|
|
||||||
|
def _create_entity_key(self, entity_event):
|
||||||
|
"""the unique key of this entity"""
|
||||||
|
entity_id = entity_event["id"]
|
||||||
|
entity_type = TMFAPI639_DATASOURCE
|
||||||
|
key_fields = self._key_values(entity_type, entity_id)
|
||||||
|
return transformer_base.build_key(key_fields)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_vitrage_type():
|
||||||
|
return TMFAPI639_DATASOURCE
|
||||||
|
|
||||||
|
def _create_vertex(self, entity_event):
|
||||||
|
"""Camps used from the received JSON:
|
||||||
|
|
||||||
|
{id, name, @type ,resourceRelationship : [type, resource: id]}
|
||||||
|
|
||||||
|
The TMF 639 API REST Endpoint can contain more information
|
||||||
|
but we only use this one for topology.
|
||||||
|
"""
|
||||||
|
sample_timestamp = \
|
||||||
|
datetime.now().strftime(transformer_base.TIMESTAMP_FORMAT)
|
||||||
|
update_timestamp = self._format_update_timestamp(
|
||||||
|
update_timestamp=None,
|
||||||
|
sample_timestamp=sample_timestamp)
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
VProps.NAME: entity_event["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph_utils.create_vertex(
|
||||||
|
self._create_entity_key(entity_event),
|
||||||
|
vitrage_category=EntityCategory.RESOURCE,
|
||||||
|
vitrage_type=TMFAPI639_DATASOURCE,
|
||||||
|
vitrage_sample_timestamp=sample_timestamp,
|
||||||
|
entity_id=entity_event["id"],
|
||||||
|
update_timestamp=update_timestamp,
|
||||||
|
entity_state='available',
|
||||||
|
metadata=metadata)
|
||||||
|
|
||||||
|
def _create_tmfapi639_neighbors(self, entity_event):
|
||||||
|
neighbors_list = []
|
||||||
|
for n in entity_event["resourceRelationship"]:
|
||||||
|
# create placeholder vertex
|
||||||
|
neigh = self._create_neighbor(
|
||||||
|
entity_event,
|
||||||
|
n["resource"]["id"],
|
||||||
|
TMFAPI639_DATASOURCE,
|
||||||
|
n["type"],
|
||||||
|
is_entity_source=True)
|
||||||
|
neighbors_list.append(neigh)
|
||||||
|
return neighbors_list
|
@ -0,0 +1,142 @@
|
|||||||
|
# Copyright 2020
|
||||||
|
#
|
||||||
|
# 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 oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from testtools import matchers
|
||||||
|
|
||||||
|
from vitrage.common.constants import DatasourceAction
|
||||||
|
from vitrage.common.constants import DatasourceOpts as DSOpts
|
||||||
|
from vitrage.common.constants import DatasourceProperties as DSProps
|
||||||
|
from vitrage.common.constants import UpdateMethod
|
||||||
|
|
||||||
|
from vitrage.datasources.tmfapi639 import TMFAPI639_DATASOURCE
|
||||||
|
from vitrage.datasources.tmfapi639.transformer import TmfApi639Transformer
|
||||||
|
from vitrage.datasources import transformer_base
|
||||||
|
from vitrage.datasources.transformer_base import TransformerBase
|
||||||
|
|
||||||
|
from vitrage.tests.unit.datasources.test_alarm_transformer_base import \
|
||||||
|
BaseAlarmTransformerTest
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from json import loads
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
message = '[{"id":"1","name":"Host-1","@type":"Host",\
|
||||||
|
"resourceRelationship":[{"type":"parent","resource":{"id":"1"}}]},\
|
||||||
|
{"id":"2","name":"Host-2","@type":"Host",\
|
||||||
|
"resourceRelationship":[{"type":"parent","resource":{"id":"1"}}]}]'
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyProtectedMember
|
||||||
|
class TestTmfApi639Transformer(BaseAlarmTransformerTest):
|
||||||
|
|
||||||
|
OPTS = [
|
||||||
|
cfg.StrOpt(DSOpts.UPDATE_METHOD,
|
||||||
|
default=UpdateMethod.PULL),
|
||||||
|
]
|
||||||
|
|
||||||
|
# noinspection PyAttributeOutsideInit,PyPep8Naming
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(TestTmfApi639Transformer, cls).setUpClass()
|
||||||
|
cls.transformers = {}
|
||||||
|
cls.conf = cfg.ConfigOpts()
|
||||||
|
cls.conf.register_opts(cls.OPTS, group=TMFAPI639_DATASOURCE)
|
||||||
|
cls.transformer = TmfApi639Transformer(cls.transformers)
|
||||||
|
cls.transformers[TMFAPI639_DATASOURCE] = cls.transformer
|
||||||
|
|
||||||
|
# noinspection PyAttributeOutsideInit
|
||||||
|
def setUp(self):
|
||||||
|
super(TestTmfApi639Transformer, self).setUp()
|
||||||
|
# self.entity_type = TMFAPI639_DATASOURCE
|
||||||
|
# self.entity_id = '12345'
|
||||||
|
self.timestamp = datetime.utcnow()
|
||||||
|
|
||||||
|
def test_create_entity_key(self):
|
||||||
|
event = loads(message)[0]
|
||||||
|
self.assertIsNotNone(event)
|
||||||
|
|
||||||
|
transformer = TmfApi639Transformer(self.transformers)
|
||||||
|
observed_key = transformer._create_entity_key(event)
|
||||||
|
|
||||||
|
entity_type = TMFAPI639_DATASOURCE
|
||||||
|
entity_id = event["id"]
|
||||||
|
|
||||||
|
# Test assertions
|
||||||
|
observed_key_fields = observed_key.split(
|
||||||
|
TransformerBase.KEY_SEPARATOR)
|
||||||
|
|
||||||
|
self.assertEqual(entity_type, observed_key_fields[1])
|
||||||
|
self.assertEqual(entity_id, observed_key_fields[2])
|
||||||
|
|
||||||
|
# Transformer tests:
|
||||||
|
# - Vertex creation
|
||||||
|
# - Neighbor link
|
||||||
|
|
||||||
|
def test_topology(self):
|
||||||
|
|
||||||
|
sample_timestamp = \
|
||||||
|
datetime.now().strftime(transformer_base.TIMESTAMP_FORMAT)
|
||||||
|
update_timestamp = TransformerBase._format_update_timestamp(
|
||||||
|
update_timestamp=None,
|
||||||
|
sample_timestamp=sample_timestamp)
|
||||||
|
|
||||||
|
transformer = self.transformers[TMFAPI639_DATASOURCE]
|
||||||
|
|
||||||
|
# Create 1 vertex
|
||||||
|
event1 = loads(message)[0]
|
||||||
|
event1[DSProps.DATASOURCE_ACTION] = DatasourceAction.SNAPSHOT
|
||||||
|
event1[DSProps.SAMPLE_DATE] = update_timestamp
|
||||||
|
self.assertIsNotNone(event1)
|
||||||
|
|
||||||
|
# Create vertex 1
|
||||||
|
wrapper1 = transformer.transform(event1)
|
||||||
|
# Assertion
|
||||||
|
self._validate_base_vertex_props(
|
||||||
|
wrapper1.vertex,
|
||||||
|
event1["name"],
|
||||||
|
TMFAPI639_DATASOURCE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create 2nd vertex
|
||||||
|
event2 = loads(message)[1]
|
||||||
|
event2[DSProps.DATASOURCE_ACTION] = DatasourceAction.SNAPSHOT
|
||||||
|
event2[DSProps.SAMPLE_DATE] = update_timestamp
|
||||||
|
self.assertIsNotNone(event2)
|
||||||
|
|
||||||
|
# Create vertex 2
|
||||||
|
wrapper2 = transformer.transform(event2)
|
||||||
|
# Assertion
|
||||||
|
self._validate_base_vertex_props(
|
||||||
|
wrapper2.vertex,
|
||||||
|
event2["name"],
|
||||||
|
TMFAPI639_DATASOURCE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test whether they are linked
|
||||||
|
self.assertThat(wrapper2.neighbors, matchers.HasLength(1))
|
||||||
|
|
||||||
|
parent_id = transformer._create_entity_key(event1)
|
||||||
|
parent_uuid = \
|
||||||
|
transformer.uuid_from_deprecated_vitrage_id(parent_id)
|
||||||
|
|
||||||
|
child_id = transformer._create_entity_key(event2)
|
||||||
|
child_uuid = \
|
||||||
|
transformer.uuid_from_deprecated_vitrage_id(child_id)
|
||||||
|
|
||||||
|
self.assertEqual(wrapper2.neighbors[0].edge.source_id, child_uuid)
|
||||||
|
self.assertEqual(wrapper2.neighbors[0].edge.target_id, parent_uuid)
|
Loading…
Reference in New Issue
Block a user