nova instance transformer and tests
Change-Id: Id3eb11592c36ac196680f6b6d21bf40f1a67aebf
This commit is contained in:
parent
c7942a5d85
commit
797240c436
@ -6,9 +6,10 @@ pbr>=1.6
|
|||||||
Babel>=1.3
|
Babel>=1.3
|
||||||
networkx>=1.10
|
networkx>=1.10
|
||||||
oslo.log>=1.12.0 # Apache-2.0
|
oslo.log>=1.12.0 # Apache-2.0
|
||||||
|
oslo.policy>=0.3.0
|
||||||
pecan>=0.8.0
|
pecan>=0.8.0
|
||||||
PasteDeploy>=1.5.0
|
PasteDeploy>=1.5.0
|
||||||
Werkzeug>=0.7
|
Werkzeug>=0.7
|
||||||
oslo.policy>=0.3.0
|
|
||||||
keystonemiddleware>=2.3.0
|
keystonemiddleware>=2.3.0
|
||||||
|
stevedore>=1.5.0 # Apache-2.0
|
||||||
exrex>=0.9.4
|
exrex>=0.9.4
|
||||||
|
@ -39,3 +39,8 @@ all_files = 1
|
|||||||
|
|
||||||
[upload_sphinx]
|
[upload_sphinx]
|
||||||
upload-dir = doc/build/html
|
upload-dir = doc/build/html
|
||||||
|
|
||||||
|
[entry_points]
|
||||||
|
vitrage.transformers =
|
||||||
|
nova.instance = vitrage.entity_graph.transformer.nova_transformer.InstanceTransformer
|
||||||
|
nova.host = vitrage.entity_graph.transformer.nova_transformer.HostTransformer
|
||||||
|
@ -15,3 +15,5 @@ testrepository>=0.0.18
|
|||||||
testscenarios>=0.4
|
testscenarios>=0.4
|
||||||
testtools>=1.4.0
|
testtools>=1.4.0
|
||||||
exrex>=0.9.4
|
exrex>=0.9.4
|
||||||
|
stevedore>=1.5.0 # Apache-2.0
|
||||||
|
|
||||||
|
1
vitrage/common/__init__.py
Normal file
1
vitrage/common/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__author__ = 'stack'
|
41
vitrage/common/constants.py
Normal file
41
vitrage/common/constants.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Copyright 2015 - Alcatel-Lucent
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
class VertexConstants(object):
|
||||||
|
TYPE = 'TYPE'
|
||||||
|
SUB_TYPE = 'SUB_TYPE'
|
||||||
|
ID = 'ID'
|
||||||
|
IS_VERTEX_DELETED = 'IS_VERTEX_DELETED'
|
||||||
|
VERTEX_DELETION_TIMESTAMP = 'VERTEX_DELETION_TIMESTAMP'
|
||||||
|
STATE = 'STATE'
|
||||||
|
PROJECT = 'PROJECT'
|
||||||
|
UPDATE_TIMESTAMP = 'UPDATE_TIMESTAMP'
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeConstants(object):
|
||||||
|
RELATION_NAME = 'RELATION_NAME'
|
||||||
|
IS_EDGE_DELETED = 'IS_EDGE_DELETED'
|
||||||
|
EDGE_DELETION_TIMESTAMP = 'EDGE_DELETION_TIMESTAMP'
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeLabels(object):
|
||||||
|
ON = 'on'
|
||||||
|
CONTAINS = 'contains'
|
||||||
|
|
||||||
|
|
||||||
|
class SynchronizerMessageMode(object):
|
||||||
|
SNAPSHOT = 'snapshot'
|
||||||
|
INIT_SNAPSHOT = 'init_snapshot'
|
||||||
|
UPDATE = 'update'
|
@ -1,30 +0,0 @@
|
|||||||
# Copyright 2015 - Alcatel-Lucent
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# Vertex properties
|
|
||||||
TYPE = 'TYPE'
|
|
||||||
SUB_TYPE = 'SUB_TYPE'
|
|
||||||
ID = 'ID'
|
|
||||||
VERTEX_ID = 'VERTEX_ID'
|
|
||||||
EVENT_TYPE = 'EVENT_TYPE'
|
|
||||||
IS_VERTEX_DELETED = 'IS_VERTEX_DELETED'
|
|
||||||
VERTEX_DELETION_TIMESTAMP = 'VERTEX_DELETION_TIMESTAMP'
|
|
||||||
STATE = 'STATE'
|
|
||||||
PROJECT = 'PROJECT'
|
|
||||||
TIMESTAMP = 'TIMESTAMP'
|
|
||||||
|
|
||||||
# Edge properties
|
|
||||||
RELATION_NAME = 'RELATION_NAME'
|
|
||||||
IS_EDGE_DELETED = 'IS_EDGE_DELETED'
|
|
||||||
EDGE_DELETION_TIMESTAMP = 'EDGE_DELETION_TIMESTAMP'
|
|
@ -13,12 +13,19 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import six
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
EntityWrapper = \
|
||||||
|
namedtuple('EntityWrapper', ['entity_vertex', 'neighbors'], 'action')
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class Transformer(object):
|
class Transformer(object):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@ -28,13 +35,14 @@ class Transformer(object):
|
|||||||
:return: An EntityWrapper. EntityWrapper is namedTuple that contains
|
:return: An EntityWrapper. EntityWrapper is namedTuple that contains
|
||||||
an entity vertex and a list of vertex and an edge pair that describe
|
an entity vertex and a list of vertex and an edge pair that describe
|
||||||
the entity's neighbors.
|
the entity's neighbors.
|
||||||
|
:rtype: EntityWrapper
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_key_fields(self):
|
def key_fields(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_key(self, entity_event):
|
def extract_key(self, entity_event):
|
||||||
pass
|
pass
|
||||||
|
47
vitrage/entity_graph/transformer/nova_transformer.py
Normal file
47
vitrage/entity_graph/transformer/nova_transformer.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Copyright 2015 - Alcatel-Lucent
|
||||||
|
#
|
||||||
|
# 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 VertexConstants as vertex_cons
|
||||||
|
from vitrage.entity_graph.transformer import base
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
KEY_SEPARATOR = ':'
|
||||||
|
|
||||||
|
ENTITY_TYPE = 'RESOURCE'
|
||||||
|
INSTANCE_SUB_TYPE = 'nova.instance'
|
||||||
|
HOST_SUB_TYPE = 'nova.host'
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceTransformer(base.Transformer):
|
||||||
|
|
||||||
|
# # Fields returned from Nova Instance snapshot
|
||||||
|
ENTITY_ID_DICT = {'snapshot': 'id',
|
||||||
|
'init_snapshot': 'id',
|
||||||
|
'update': 'instance_id'}
|
||||||
|
|
||||||
|
def transform(self, entity_event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def key_fields(self):
|
||||||
|
return [vertex_cons.TYPE, vertex_cons.SUB_TYPE, vertex_cons.ID]
|
||||||
|
|
||||||
|
def extract_key(self, entity_event):
|
||||||
|
|
||||||
|
sync_mode = entity_event['sync_mode']
|
||||||
|
return KEY_SEPARATOR.join(
|
||||||
|
[ENTITY_TYPE,
|
||||||
|
INSTANCE_SUB_TYPE,
|
||||||
|
entity_event[self.ENTITY_ID_DICT[sync_mode]]])
|
29
vitrage/entity_graph/transformer/transformer_manager.py
Normal file
29
vitrage/entity_graph/transformer/transformer_manager.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Copyright 2014 - Mirantis, Inc.
|
||||||
|
# Copyright 2014 - StackStorm, Inc.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TransformerManager(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.transformers = self.register_transformer_classes()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def register_transformer_classes():
|
||||||
|
pass
|
@ -67,7 +67,7 @@ class Edge(object):
|
|||||||
:param source_id: source vertex id
|
:param source_id: source vertex id
|
||||||
:type source_id: str
|
:type source_id: str
|
||||||
|
|
||||||
:param target_id: target vertex id
|
:param target_id: target vertex id`
|
||||||
:type target_id: str
|
:type target_id: str
|
||||||
|
|
||||||
:param label:
|
:param label:
|
||||||
|
@ -25,14 +25,14 @@ multiple instances of the same entity type.
|
|||||||
|
|
||||||
from os.path import curdir
|
from os.path import curdir
|
||||||
from os import walk
|
from os import walk
|
||||||
from random import randint
|
import random
|
||||||
|
|
||||||
from entity_model import CommonEntityModel as cem
|
from entity_model import CommonEntityModel as cem
|
||||||
|
|
||||||
|
|
||||||
def _get_filename_path(filename):
|
def _get_filename_path(filename):
|
||||||
base_dir = None
|
base_dir = None
|
||||||
for i in walk("../%s" % curdir):
|
for i in walk("../../%s" % curdir):
|
||||||
if i[0].find('resources') != -1 and filename in i[2]:
|
if i[0].find('resources') != -1 and filename in i[2]:
|
||||||
base_dir = i[0]
|
base_dir = i[0]
|
||||||
break
|
break
|
||||||
@ -46,8 +46,6 @@ class MockEventGenerator(object):
|
|||||||
"""Represents a single generator.
|
"""Represents a single generator.
|
||||||
|
|
||||||
A generator can generate events for several instances of the same type
|
A generator can generate events for several instances of the same type
|
||||||
|
|
||||||
file is expected to be in the ../resources folder
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filename, instance_num, generator_name='generator'):
|
def __init__(self, filename, instance_num, generator_name='generator'):
|
||||||
@ -75,7 +73,7 @@ class MockEventGenerator(object):
|
|||||||
param_type = line_params[1].lower()
|
param_type = line_params[1].lower()
|
||||||
params_dict[param_type][line_params[0]] = line_params[2]
|
params_dict[param_type][line_params[0]] = line_params[2]
|
||||||
except KeyError as ke:
|
except KeyError as ke:
|
||||||
print("Syntax error: {0}".format(ke.message))
|
print("Syntax error ({0}): {1}".format(ke.errno, ke.strerror))
|
||||||
|
|
||||||
def prepare_instance_models(self):
|
def prepare_instance_models(self):
|
||||||
"""Create the models for all the instances """
|
"""Create the models for all the instances """
|
||||||
@ -97,7 +95,7 @@ class MockEventGenerator(object):
|
|||||||
|
|
||||||
data_stream = []
|
data_stream = []
|
||||||
for _ in xrange(event_num):
|
for _ in xrange(event_num):
|
||||||
model = self.models[randint(0, self.instance_num - 1)]
|
model = self.models[random.randint(0, self.instance_num - 1)]
|
||||||
model.generate_dynamic_params()
|
model.generate_dynamic_params()
|
||||||
data_stream.append(model.params)
|
data_stream.append(model.params)
|
||||||
return data_stream
|
return data_stream
|
||||||
|
@ -20,7 +20,7 @@ of what can be returned
|
|||||||
|
|
||||||
usage example:
|
usage example:
|
||||||
test_entity_spec_list = [
|
test_entity_spec_list = [
|
||||||
{'filename': '../resources/mock_nova_instance_config_1.txt',
|
{'filename': '../resources/mock_nova_inst_snapshot.txt',
|
||||||
'#instances': 10,
|
'#instances': 10,
|
||||||
'name': 'Instance (vm) generator'
|
'name': 'Instance (vm) generator'
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
event_type S snapshot
|
sync_mode D snapshot|init_snapshot
|
||||||
OS-DCF:diskConfig S AUTO
|
OS-DCF:diskConfig S AUTO
|
||||||
OS-EXT-AZ:availability_zone S nova
|
OS-EXT-AZ:availability_zone S nova
|
||||||
OS-EXT-SRV-ATTR:host D [a-z]{4}-devstack
|
OS-EXT-SRV-ATTR:host D [a-z]{4}-devstack
|
@ -1,4 +1,4 @@
|
|||||||
event_type S update
|
sync_mode S update
|
||||||
u-OS-DCF:diskConfig S AUTO
|
u-OS-DCF:diskConfig S AUTO
|
||||||
u-OS-EXT-AZ:availability_zone S nova
|
u-OS-EXT-AZ:availability_zone S nova
|
||||||
u-OS-EXT-SRV-ATTR:host D [a-z]{4}-devstack
|
u-OS-EXT-SRV-ATTR:host D [a-z]{4}-devstack
|
||||||
@ -22,7 +22,7 @@ u-config_drive D True|False
|
|||||||
u-created D \d{2}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}
|
u-created D \d{2}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}
|
||||||
u-flavor D m1.nano
|
u-flavor D m1.nano
|
||||||
u-hostId D [0-9a-f]{56}
|
u-hostId D [0-9a-f]{56}
|
||||||
u-id S [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
|
instance_id S [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
|
||||||
u-image D cirros-[a-z]+ ([a-z0-9]+)
|
u-image D cirros-[a-z]+ ([a-z0-9]+)
|
||||||
u-key_name D -
|
u-key_name D -
|
||||||
u-metadata D \{\}
|
u-metadata D \{\}
|
@ -13,8 +13,16 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class BaseTest(base.BaseTestCase):
|
class BaseTest(base.BaseTestCase):
|
||||||
|
|
||||||
"""Test case base class for all unit tests."""
|
"""Test case base class for all unit tests."""
|
||||||
|
|
||||||
|
def assert_list_equal(self, l1, l2):
|
||||||
|
if tuple(sys.version_info)[0:2] < (2, 7):
|
||||||
|
# for python 2.6 compatibility
|
||||||
|
self.assertEqual(l1, l2)
|
||||||
|
else:
|
||||||
|
super(BaseTest, self).assertListEqual(l1, l2)
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
# Copyright 2015 - Alcatel-Lucent
|
|
||||||
#
|
|
||||||
# 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.tests.unit import base
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class TransformNovaInstanceTest(base.BaseTest):
|
|
||||||
|
|
||||||
def test_transform_instance(self):
|
|
||||||
|
|
||||||
LOG.debug('Test transform Nova instance into entity wrapper')
|
|
||||||
self.assertTrue(True)
|
|
83
vitrage/tests/unit/transformers/test_nova_transformers.py
Normal file
83
vitrage/tests/unit/transformers/test_nova_transformers.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Copyright 2015 - Alcatel-Lucent
|
||||||
|
#
|
||||||
|
# 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 VertexConstants as vertexCons
|
||||||
|
from vitrage.entity_graph.transformer import nova_transformer as nt
|
||||||
|
from vitrage.tests.mocks import mock_syncronizer as mock_sync
|
||||||
|
from vitrage.tests.unit import base
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_nova_instance_transformer():
|
||||||
|
return nt.InstanceTransformer()
|
||||||
|
|
||||||
|
|
||||||
|
def get_instance_entity_spec_list(config_file_path, number_of_instances):
|
||||||
|
|
||||||
|
"""Returns a list of nova instance specifications by
|
||||||
|
|
||||||
|
given specific configuration file.
|
||||||
|
|
||||||
|
:rtype : list
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'filename': config_file_path,
|
||||||
|
'#instances': number_of_instances,
|
||||||
|
'name': 'Instance generator'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NovaInstanceTransformerTest(base.BaseTest):
|
||||||
|
|
||||||
|
def test_key_fields(self):
|
||||||
|
LOG.debug('Test get key fields from nova instance transformer')
|
||||||
|
transformer = get_nova_instance_transformer()
|
||||||
|
|
||||||
|
expected_key_fields = [vertexCons.TYPE,
|
||||||
|
vertexCons.SUB_TYPE,
|
||||||
|
vertexCons.ID]
|
||||||
|
observed_key_fields = transformer.key_fields()
|
||||||
|
self.assert_list_equal(expected_key_fields, observed_key_fields)
|
||||||
|
|
||||||
|
def test_extract_key(self):
|
||||||
|
LOG.debug('Test get key from nova instance transformer')
|
||||||
|
|
||||||
|
transformer = get_nova_instance_transformer()
|
||||||
|
|
||||||
|
instance_specifications = [
|
||||||
|
get_instance_entity_spec_list('mock_nova_inst_snapshot.txt', 1),
|
||||||
|
get_instance_entity_spec_list('mock_nova_inst_update.txt', 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
spec_list = mock_sync.get_mock_generators(instance_specifications)
|
||||||
|
instance_events = mock_sync.generate_random_events_list(spec_list)
|
||||||
|
|
||||||
|
for event in instance_events:
|
||||||
|
observed_key = transformer.extract_key(event)
|
||||||
|
observed_key_fields = observed_key.split(nt.KEY_SEPARATOR)
|
||||||
|
|
||||||
|
self.assertEqual(nt.ENTITY_TYPE, observed_key_fields[0])
|
||||||
|
self.assertEqual(nt.INSTANCE_SUB_TYPE, observed_key_fields[1])
|
||||||
|
|
||||||
|
event_id = event[transformer.ENTITY_ID_DICT[event['sync_mode']]]
|
||||||
|
self.assertEqual(event_id, observed_key_fields[2])
|
||||||
|
|
||||||
|
expected_key = nt.KEY_SEPARATOR.join(
|
||||||
|
[nt.ENTITY_TYPE,
|
||||||
|
nt.INSTANCE_SUB_TYPE,
|
||||||
|
event_id])
|
||||||
|
self.assertEqual(expected_key, observed_key)
|
1
vitrage/utils/__init__.py
Normal file
1
vitrage/utils/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__author__ = 'stack'
|
Loading…
Reference in New Issue
Block a user