infrastructure changes for synchronizers and consistency

Change-Id: I6854feca155893356e9509d47021e42322074538
This commit is contained in:
Alexey Weyl 2016-02-16 10:14:56 +02:00
parent f2f9879d0a
commit 76ac86993d
22 changed files with 484 additions and 138 deletions

View File

@ -70,3 +70,4 @@ class EventAction(object):
CREATE = 'create' CREATE = 'create'
DELETE = 'delete' DELETE = 'delete'
UPDATE = 'update' UPDATE = 'update'
END_MESSAGE = 'end_message'

View File

@ -13,9 +13,9 @@
# under the License. # under the License.
import os import os
import yaml
from oslo_log import log from oslo_log import log
import yaml
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -35,15 +35,21 @@ def load_yaml_files(dir_path, with_exception=False):
yaml_files = [] yaml_files = []
for file in files: for file in files:
full_path = dir_path + '/' + file full_path = dir_path + '/' + file
config = load_yaml_file(full_path, with_exception)
if config:
yaml_files.append(config)
return yaml_files
def load_yaml_file(full_path, with_exception=False):
with open(full_path, 'r') as stream: with open(full_path, 'r') as stream:
try: try:
config = yaml.load(stream, Loader=yaml.BaseLoader) return yaml.load(stream, Loader=yaml.BaseLoader)
except Exception as e: except Exception as e:
if with_exception: if with_exception:
raise e raise e
else: else:
LOG.error("Fails to parse file: %s. %s" % (full_path, e)) LOG.error("Fails to parse file: %s. %s" % (full_path, e))
return None
yaml_files.append(config)
return yaml_files

View File

@ -14,6 +14,7 @@
import json import json
import eventlet
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log from oslo_log import log
import oslo_messaging import oslo_messaging
@ -28,6 +29,8 @@ from vitrage.graph import Direction
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
eventlet.monkey_patch()
class VitrageApiHandlerService(os_service.Service): class VitrageApiHandlerService(os_service.Service):
@ -61,7 +64,7 @@ class VitrageApiHandlerService(os_service.Service):
# TODO(Dany) use eventlet instead of threading # TODO(Dany) use eventlet instead of threading
server = oslo_messaging.get_rpc_server(transport, target, server = oslo_messaging.get_rpc_server(transport, target,
endpoints, executor='threading') endpoints, executor='eventlet')
server.start() server.start()

View File

@ -13,6 +13,7 @@
# under the License. # under the License.
import abc import abc
import six import six
@ -37,3 +38,7 @@ class ProcessorBase(object):
@abc.abstractmethod @abc.abstractmethod
def delete_entity(self, deleted_vertex, neighbors): def delete_entity(self, deleted_vertex, neighbors):
pass pass
@abc.abstractmethod
def handle_end_message(self, event):
pass

View File

@ -125,6 +125,9 @@ class Processor(processor.ProcessorBase):
LOG.info("Delete event arrived on invalid resource: %s", LOG.info("Delete event arrived on invalid resource: %s",
deleted_vertex) deleted_vertex)
def handle_end_message(self, vertex, neighbors):
return
def transform_entity(self, event): def transform_entity(self, event):
return self.transformer.transform(event) return self.transformer.transform(event)
@ -146,8 +149,8 @@ class Processor(processor.ProcessorBase):
neighbors, valid_edges) neighbors, valid_edges)
for (vertex, edge) in neighbors: for (vertex, edge) in neighbors:
graph_vertex = self.entity_graph.get_vertex(vertex.vertex_id) graph_vertex = self.entity_graph.get_vertex(vertex.vertex_id)
if (not graph_vertex) or self.entity_graph.check_update_validation( if not graph_vertex or \
graph_vertex, vertex): not self.entity_graph.is_vertex_deleted(graph_vertex):
if self.entity_graph.can_update_vertex(graph_vertex, vertex): if self.entity_graph.can_update_vertex(graph_vertex, vertex):
LOG.debug("Updates vertex: %s", vertex) LOG.debug("Updates vertex: %s", vertex)
self.entity_graph.update_vertex(vertex) self.entity_graph.update_vertex(vertex)
@ -215,5 +218,6 @@ class Processor(processor.ProcessorBase):
self.actions = { self.actions = {
EventAction.CREATE: self.create_entity, EventAction.CREATE: self.create_entity,
EventAction.UPDATE: self.update_entity, EventAction.UPDATE: self.update_entity,
EventAction.DELETE: self.delete_entity EventAction.DELETE: self.delete_entity,
EventAction.END_MESSAGE: self.handle_end_message
} }

View File

@ -16,7 +16,9 @@ from oslo_log import log as logging
from oslo_utils import importutils from oslo_utils import importutils
from vitrage.common.constants import EntityType from vitrage.common.constants import EntityType
from vitrage.common.constants import EventAction
from vitrage.common.constants import SynchronizerProperties as SyncProps from vitrage.common.constants import SynchronizerProperties as SyncProps
from vitrage.common.constants import SyncMode
from vitrage.common.exception import VitrageTransformerError from vitrage.common.exception import VitrageTransformerError
from vitrage.synchronizer.plugins.nagios.transformer import NagiosTransformer from vitrage.synchronizer.plugins.nagios.transformer import NagiosTransformer
from vitrage.synchronizer.plugins.nova.host.transformer import HostTransformer from vitrage.synchronizer.plugins.nova.host.transformer import HostTransformer
@ -24,8 +26,8 @@ from vitrage.synchronizer.plugins.nova.instance.transformer import \
InstanceTransformer InstanceTransformer
from vitrage.synchronizer.plugins.nova.zone.transformer import ZoneTransformer from vitrage.synchronizer.plugins.nova.zone.transformer import ZoneTransformer
from vitrage.synchronizer.plugins.static_physical.transformer import \ from vitrage.synchronizer.plugins.static_physical.transformer import \
StaticPhysical StaticPhysicalTransformer
from vitrage.synchronizer.plugins import transformer_base
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -54,7 +56,8 @@ class TransformerManager(object):
transformers) transformers)
transformers[EntityType.SWITCH] = importutils.import_object( transformers[EntityType.SWITCH] = importutils.import_object(
"%s.%s" % (StaticPhysical.__module__, StaticPhysical.__name__), "%s.%s" % (StaticPhysicalTransformer.__module__,
StaticPhysicalTransformer.__name__),
transformers) transformers)
transformers[EntityType.NAGIOS] = importutils.import_object( transformers[EntityType.NAGIOS] = importutils.import_object(
@ -75,7 +78,7 @@ class TransformerManager(object):
return transformer return transformer
def transform(self, entity_event): def transform(self, entity_event):
if not self._is_end_message(entity_event):
try: try:
sync_type = entity_event[SyncProps.SYNC_TYPE] sync_type = entity_event[SyncProps.SYNC_TYPE]
except KeyError: except KeyError:
@ -83,6 +86,10 @@ class TransformerManager(object):
'Entity Event must contains sync_type field.') 'Entity Event must contains sync_type field.')
return self.get_transformer(sync_type).transform(entity_event) return self.get_transformer(sync_type).transform(entity_event)
else:
return transformer_base.EntityWrapper(None,
None,
EventAction.END_MESSAGE)
def extract_key(self, entity_event): def extract_key(self, entity_event):
@ -93,3 +100,9 @@ class TransformerManager(object):
'Entity Event must contains sync_type field.') 'Entity Event must contains sync_type field.')
return self.get_transformer(sync_type).extract_key() return self.get_transformer(sync_type).extract_key()
@staticmethod
def _is_end_message(entity_event):
return entity_event[SyncProps.SYNC_MODE] == SyncMode.INIT_SNAPSHOT and\
SyncProps.EVENT_TYPE in entity_event and \
entity_event[SyncProps.EVENT_TYPE] == EventAction.END_MESSAGE

View File

@ -19,4 +19,13 @@ OPTS = [
default=600, default=600,
min=10, min=10,
help='interval between full snapshots'), help='interval between full snapshots'),
cfg.IntOpt('nagios_changes_interval',
default=30,
min=30,
help='interval between checking changes in nagios plugin'),
cfg.IntOpt('static_physical_changes_interval',
default=30,
min=30,
help='interval between checking changes in the configuration '
'files of the physical topology plugin'),
] ]

View File

@ -1,4 +1,5 @@
# Copyright 2016 - Alcatel-Lucent # Copyright 2016 - Alcatel-Lucent
# Copyright 2016 - Nokia
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -15,6 +16,7 @@
from oslo_log import log from oslo_log import log
from oslo_service import service as os_service from oslo_service import service as os_service
from services import ChangesService
from services import SnapshotsService from services import SnapshotsService
from vitrage.synchronizer.plugins.nagios.synchronizer import NagiosSynchronizer from vitrage.synchronizer.plugins.nagios.synchronizer import NagiosSynchronizer
from vitrage.synchronizer.plugins.nova.host.synchronizer import \ from vitrage.synchronizer.plugins.nova.host.synchronizer import \
@ -24,8 +26,7 @@ from vitrage.synchronizer.plugins.nova.instance.synchronizer import \
from vitrage.synchronizer.plugins.nova.zone.synchronizer import \ from vitrage.synchronizer.plugins.nova.zone.synchronizer import \
ZoneSynchronizer ZoneSynchronizer
from vitrage.synchronizer.plugins.static_physical.synchronizer import \ from vitrage.synchronizer.plugins.static_physical.synchronizer import \
StaticPhysical StaticPhysicalSynchronizer
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -40,11 +41,10 @@ def create_send_to_queue_callback(queue):
class Launcher(object): class Launcher(object):
def __init__(self, conf, callback): def __init__(self, conf, callback):
self.conf = conf self.conf = conf
self.callback = callback self.callback = callback
self.plugins = self._init_registered_plugins() self.snapshot_plugins = self._register_snapshot_plugins()
self.services = [SnapshotsService(conf, self.plugins)] self.services = self._register_services()
def launch(self): def launch(self):
launcher = os_service.ProcessLauncher(self.conf) launcher = os_service.ProcessLauncher(self.conf)
@ -52,7 +52,7 @@ class Launcher(object):
service.set_callback(self.callback) service.set_callback(self.callback)
launcher.launch_service(service, 1) launcher.launch_service(service, 1)
def _init_registered_plugins(self): def _register_snapshot_plugins(self):
version = 2.0 version = 2.0
user = 'admin' user = 'admin'
password = 'password' password = 'password'
@ -63,6 +63,19 @@ class Launcher(object):
HostSynchronizer(version, user, password, project, auth_url), HostSynchronizer(version, user, password, project, auth_url),
InstanceSynchronizer(version, user, password, project, auth_url), InstanceSynchronizer(version, user, password, project, auth_url),
NagiosSynchronizer(self.conf), NagiosSynchronizer(self.conf),
StaticPhysical(self.conf) StaticPhysicalSynchronizer(self.conf)]
]
return registered_plugins return registered_plugins
def _register_services(self):
nagios_changes_interval = self.conf.synchronizer.\
nagios_changes_interval
static_physical_changes_interval = self.conf.synchronizer.\
static_physical_changes_interval
return [SnapshotsService(self.conf, self.snapshot_plugins),
ChangesService(self.conf,
[NagiosSynchronizer(self.conf)],
nagios_changes_interval),
ChangesService(self.conf,
[StaticPhysicalSynchronizer(self.conf)],
static_physical_changes_interval)]

View File

@ -11,7 +11,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from collections import namedtuple from collections import namedtuple
from oslo_log import log from oslo_log import log
import requests import requests
@ -19,11 +21,11 @@ from vitrage.common.constants import EntityType
from vitrage.common.constants import SynchronizerProperties as SyncProps from vitrage.common.constants import SynchronizerProperties as SyncProps
from vitrage.i18n import _LE from vitrage.i18n import _LE
from vitrage.i18n import _LW from vitrage.i18n import _LW
from vitrage.synchronizer.base import SynchronizerBase
from vitrage.synchronizer.plugins.nagios.parser import NagiosParser from vitrage.synchronizer.plugins.nagios.parser import NagiosParser
from vitrage.synchronizer.plugins.nagios.properties import NagiosProperties \ from vitrage.synchronizer.plugins.nagios.properties import NagiosProperties \
as NagiosProps as NagiosProps
from vitrage.synchronizer.plugins.nagios.properties import NagiosStatus from vitrage.synchronizer.plugins.nagios.properties import NagiosStatus
from vitrage.synchronizer.plugins.synchronizer_base import SynchronizerBase
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -36,8 +38,13 @@ class NagiosSynchronizer(SynchronizerBase):
self.conf = conf self.conf = conf
self.cache = dict() self.cache = dict()
def get_all(self): def get_all(self, sync_mode):
return self.make_pickleable(self._get_services(), EntityType.NAGIOS) return self.make_pickleable(self._get_services(),
EntityType.NAGIOS,
sync_mode)
def get_changes(self, sync_mode):
return []
def _get_services(self): def _get_services(self):
nagios_user = self.conf.synchronizer_plugins.nagios_user nagios_user = self.conf.synchronizer_plugins.nagios_user
@ -71,7 +78,8 @@ class NagiosSynchronizer(SynchronizerBase):
response.status_code) response.status_code)
return [] return []
def _enrich_services(self, nagios_services): @staticmethod
def _enrich_services(nagios_services):
for service in nagios_services: for service in nagios_services:
# TODO(ifat_afek) - add a configuration file for resource types # TODO(ifat_afek) - add a configuration file for resource types
service[NagiosProps.RESOURCE_TYPE] = EntityType.NOVA_HOST service[NagiosProps.RESOURCE_TYPE] = EntityType.NOVA_HOST

View File

@ -15,7 +15,7 @@
from novaclient import client from novaclient import client
from vitrage.synchronizer.base import SynchronizerBase from vitrage.synchronizer.plugins.synchronizer_base import SynchronizerBase
class NovaBase(SynchronizerBase): class NovaBase(SynchronizerBase):

View File

@ -34,6 +34,12 @@ class HostSynchronizer(NovaBase):
compute_hosts.append(host_dict) compute_hosts.append(host_dict)
return compute_hosts return compute_hosts
def get_all(self): def get_all(self, sync_mode):
return self.make_pickleable(self.filter_none_compute_hosts( return self.make_pickleable(
self.client.hosts.list()), EntityType.NOVA_HOST, ['manager']) self.filter_none_compute_hosts(self.client.hosts.list()),
EntityType.NOVA_HOST,
sync_mode,
['manager'])
def get_changes(self, sync_mode):
pass

View File

@ -31,7 +31,12 @@ class InstanceSynchronizer(NovaBase):
instances_res.append(instance.__dict__) instances_res.append(instance.__dict__)
return instances_res return instances_res
def get_all(self): def get_all(self, sync_mode):
return self.make_pickleable( return self.make_pickleable(
self.filter_instances(self.client.servers.list()), self.filter_instances(self.client.servers.list()),
EntityType.NOVA_INSTANCE, ['manager']) EntityType.NOVA_INSTANCE,
sync_mode,
['manager'])
def get_changes(self, sync_mode):
pass

View File

@ -33,7 +33,12 @@ class ZoneSynchronizer(NovaBase):
zones_res.append(zone_dict) zones_res.append(zone_dict)
return zones_res return zones_res
def get_all(self): def get_all(self, sync_mode):
return self.make_pickleable(self.filter_internal_zone( return self.make_pickleable(self.filter_internal_zone(
self.client.availability_zones.list()), self.client.availability_zones.list()),
EntityType.NOVA_ZONE, ['manager']) EntityType.NOVA_ZONE,
sync_mode,
['manager'])
def get_changes(self, sync_mode):
pass

View File

@ -14,26 +14,120 @@
import os import os
from vitrage.common.constants import EventAction
from vitrage.common.constants import SynchronizerProperties as SyncProps
from vitrage.common.constants import VertexProperties as VProps
from vitrage.common import file_utils from vitrage.common import file_utils
from vitrage.synchronizer.base import SynchronizerBase from vitrage.synchronizer.plugins.synchronizer_base import SynchronizerBase
class StaticPhysical(SynchronizerBase): class StaticPhysicalSynchronizer(SynchronizerBase):
STATIC_PHYSICAL = 'static_physical'
ENTITIES_SECTION = 'entities'
def __init__(self, conf): def __init__(self, conf):
super(StaticPhysical, self).__init__() super(StaticPhysicalSynchronizer, self).__init__()
self.cfg = conf self.cfg = conf
self.cache = {}
def get_all(self): def get_all(self, sync_mode):
return self.make_pickleable(self.get_instances(), None, []) return self.make_pickleable(self._get_all_entities(),
self.STATIC_PHYSICAL,
sync_mode)
def get_instances(self): def get_changes(self, sync_mode):
return self.make_pickleable(self._get_changes_entities(),
self.STATIC_PHYSICAL,
sync_mode)
def _get_all_entities(self):
static_entities = [] static_entities = []
if os.path.isdir(self.cfg.synchronizer_plugins.static_plugins_dir):
static_plugin_configs = file_utils.load_yaml_files(
self.cfg.synchronizer_plugins.static_plugins_dir)
for config in static_plugin_configs: if os.path.isdir(self.cfg.synchronizer_plugins.static_plugins_dir):
for entity in config['entities']: files = file_utils.load_files(
static_entities.append(entity) self.cfg.synchronizer_plugins.static_plugins_dir, '.yaml')
for file in files:
full_path = self.cfg.synchronizer_plugins.static_plugins_dir \
+ '/' + file
static_entities += self._get_entities_from_file(file,
full_path)
return static_entities return static_entities
def _get_entities_from_file(self, file, path):
static_entities = []
config = file_utils.load_yaml_file(path)
for entity in config[self.ENTITIES_SECTION]:
static_entities.append(entity.copy())
self.cache[file] = config
return static_entities
def _get_changes_entities(self):
entities_updates = []
if os.path.isdir(self.cfg.synchronizer_plugins.static_plugins_dir):
entities_updates = []
files = file_utils.load_files(
self.cfg.synchronizer_plugins.static_plugins_dir, '.yaml')
for file in files:
full_path = self.cfg.synchronizer_plugins.static_plugins_dir +\
'/' + file
config = file_utils.load_yaml_file(full_path)
if config:
if file in self.cache:
if str(config) != str(self.cache[file]):
# TODO(alexey_weyl): need also to remove deleted
# files from cache
self._update_on_existing_entities(
self.cache[file][self.ENTITIES_SECTION],
config[self.ENTITIES_SECTION],
entities_updates)
self._update_on_new_entities(
config[self.ENTITIES_SECTION],
self.cache[file][self.ENTITIES_SECTION],
entities_updates)
self.cache[file] = config
else:
self.cache[file] = config
entities_updates += \
self._get_entities_from_file(file, full_path)
return entities_updates
def _update_on_existing_entities(self, old_entities,
new_entities, updates):
for old_entity in old_entities:
if old_entity not in new_entities:
new_entity = self._find_entity(old_entity, new_entities)
if not new_entity:
self._delete_event(old_entity)
updates.append(old_entity.copy())
else:
updates.append(new_entity.copy())
@staticmethod
def _find_entity(new_entity, entities):
for entity in entities:
if entity[SyncProps.SYNC_TYPE] == new_entity[SyncProps.SYNC_TYPE] \
and entity[VProps.ID] == new_entity[VProps.ID]:
return entity
return None
@staticmethod
def _update_on_new_entities(new_entities, old_entities, updates):
for entity in new_entities:
if entity not in updates and entity not in old_entities:
updates.append(entity.copy())
@staticmethod
def _delete_event(entity):
entity[SyncProps.EVENT_TYPE] = EventAction.DELETE

View File

@ -24,9 +24,10 @@ from vitrage.synchronizer.plugins import transformer_base
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class StaticPhysical(transformer_base.TransformerBase): class StaticPhysicalTransformer(transformer_base.TransformerBase):
RELATION_TYPE = 'relation_type' RELATION_TYPE = 'relation_type'
RELATIONSHIPS_SECTION = 'relationships'
def __init__(self, transformers): def __init__(self, transformers):
self.transformers = transformers self.transformers = transformers
@ -55,7 +56,7 @@ class StaticPhysical(transformer_base.TransformerBase):
entity_key = self.extract_key(entity_event) entity_key = self.extract_key(entity_event)
timestamp = entity_event[SyncProps.SAMPLE_DATE] timestamp = entity_event[SyncProps.SAMPLE_DATE]
for neighbor_details in entity_event['relationships']: for neighbor_details in entity_event[self.RELATIONSHIPS_SECTION]:
# TODO(alexey): need to decide what to do if one of the entities # TODO(alexey): need to decide what to do if one of the entities
# fails # fails
neighbor = self._create_neighbor(neighbor_details, entity_type, neighbor = self._create_neighbor(neighbor_details, entity_type,

View File

@ -16,7 +16,10 @@ import abc
import six import six
from vitrage.common.constants import EventAction
from vitrage.common.constants import SynchronizerProperties as SyncProps from vitrage.common.constants import SynchronizerProperties as SyncProps
from vitrage.common.constants import SyncMode
import vitrage.common.datetime_utils import vitrage.common.datetime_utils
@ -27,11 +30,24 @@ class SynchronizerBase(object):
pass pass
@abc.abstractmethod @abc.abstractmethod
def get_all(self): def get_all(self, sync_mode):
pass pass
def make_pickleable(self, entities, sync_type, fields_to_remove=[]): @staticmethod
def _get_end_message(sync_type):
end_message = {
SyncProps.SYNC_TYPE: sync_type,
SyncProps.SYNC_MODE: SyncMode.INIT_SNAPSHOT,
SyncProps.EVENT_TYPE: EventAction.END_MESSAGE
}
return end_message
@abc.abstractmethod
def get_changes(self, sync_mode):
pass
def make_pickleable(self, entities, sync_type,
sync_mode, fields_to_remove=[]):
pickleable_entities = [] pickleable_entities = []
for entity in entities: for entity in entities:
@ -39,17 +55,25 @@ class SynchronizerBase(object):
entity.pop(field) entity.pop(field)
self._add_sync_type(entity, sync_type) self._add_sync_type(entity, sync_type)
self._add_sync_mode(entity, sync_mode)
self._add_sampling_time(entity) self._add_sampling_time(entity)
pickleable_entities.append(entity) pickleable_entities.append(entity)
if sync_mode == SyncMode.INIT_SNAPSHOT:
pickleable_entities.append(self._get_end_message(sync_type))
return pickleable_entities return pickleable_entities
@staticmethod @staticmethod
def _add_sync_type(entity, sync_type): def _add_sync_type(entity, sync_type):
if sync_type: if SyncProps.SYNC_TYPE not in entity:
entity[SyncProps.SYNC_TYPE] = sync_type entity[SyncProps.SYNC_TYPE] = sync_type
@staticmethod @staticmethod
def _add_sampling_time(entity): def _add_sampling_time(entity):
entity[SyncProps.SAMPLE_DATE] = str( entity[SyncProps.SAMPLE_DATE] = str(
vitrage.common.datetime_utils.utcnow()) vitrage.common.datetime_utils.utcnow())
@staticmethod
def _add_sync_mode(entity, sync_mode):
entity[SyncProps.SYNC_MODE] = sync_mode

View File

@ -1,4 +1,5 @@
# Copyright 2016 - Alcatel-Lucent # Copyright 2016 - Alcatel-Lucent
# Copyright 2016 - Nokia
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -15,7 +16,6 @@
from oslo_log import log from oslo_log import log
from oslo_service import service as os_service from oslo_service import service as os_service
from vitrage.common.constants import SynchronizerProperties as SyncProps
from vitrage.common.constants import SyncMode from vitrage.common.constants import SyncMode
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -40,17 +40,19 @@ class SnapshotsService(SynchronizerService):
def start(self): def start(self):
LOG.info("Start VitrageSnapshotsService") LOG.info("Start VitrageSnapshotsService")
super(SnapshotsService, self).start() super(SnapshotsService, self).start()
interval = self.conf.synchronizer.snapshots_interval interval = self.conf.synchronizer.snapshots_interval
self.tg.add_timer(interval, self._get_all) self.tg.add_timer(interval, self._get_all)
LOG.info("Finish start VitrageSnapshotsService") LOG.info("Finish start VitrageSnapshotsService")
def stop(self): def stop(self):
LOG.info("Stop VitrageSynchronizerService") LOG.info("Stop VitrageSnapshotsService")
super(SnapshotsService, self).stop() super(SnapshotsService, self).stop()
LOG.info("Finish stop VitrageSynchronizerService") LOG.info("Finish stop VitrageSnapshotsService")
def _get_all(self): def _get_all(self):
sync_mode = SyncMode.INIT_SNAPSHOT \ sync_mode = SyncMode.INIT_SNAPSHOT \
@ -58,15 +60,44 @@ class SnapshotsService(SynchronizerService):
LOG.debug("start get all with sync mode %s" % sync_mode) LOG.debug("start get all with sync mode %s" % sync_mode)
for plugin in self.registered_plugins: for plugin in self.registered_plugins:
entities_dictionaries = \ entities_dictionaries = plugin.get_all(sync_mode)
self._mark_snapshot_entities(plugin.get_all(), sync_mode)
for entity in entities_dictionaries: for entity in entities_dictionaries:
self.callback_function(entity) self.callback_function(entity)
LOG.debug("end get all with sync mode %s" % sync_mode) LOG.debug("end get all with sync mode %s" % sync_mode)
self.first_time = False self.first_time = False
@staticmethod
def _mark_snapshot_entities(dicts, sync_mode): class ChangesService(SynchronizerService):
[x.setdefault(SyncProps.SYNC_MODE, sync_mode) for x in dicts]
return dicts def __init__(self, conf, registered_plugins, changes_interval):
super(ChangesService, self).__init__(conf, registered_plugins)
self.changes_interval = changes_interval
def start(self):
LOG.info("Start VitrageChangesService - %s",
self.registered_plugins[0].__class__.__name__)
super(ChangesService, self).start()
self.tg.add_timer(self.changes_interval, self._get_changes)
LOG.info("Finish start VitrageChangesService - %s",
self.registered_plugins[0].__class__.__name__)
def stop(self):
LOG.info("Stop VitrageChangesService - %s",
self.registered_plugins[0].__class__.__name__)
super(ChangesService, self).stop()
LOG.info("Finish stop VitrageChangesService - %s",
self.registered_plugins[0].__class__.__name__)
def _get_changes(self):
LOG.debug("start get changes")
for plugin in self.registered_plugins:
for entity in plugin.get_changes(SyncMode.UPDATE):
self.callback_function(entity)
LOG.debug("end get changes")

View File

@ -0,0 +1,41 @@
entities:
- name: switch-1
id: 12345
sync_type: switch
state: available
relationships:
- sync_type: nova.host
name: host-1
id: 1
relation_type: contains
- sync_type: nova.host
name: host-2
id: 2
relation_type: contains
- name: switch-3
id: 34567
sync_type: switch
state: available
relationships:
- sync_type: nova.host
name: host-4
id: 4
relation_type: contains
- name: switch-4
id: 45678
sync_type: switch
state: error
relationships:
- sync_type: nova.host
name: host-2
id: 2
relation_type: contains
- name: switch-5
id: 56789
sync_type: switch
state: error
relationships:
- sync_type: nova.host
name: host-3
id: 3
relation_type: contains

View File

@ -0,0 +1,19 @@
entities:
- name: switch-1
id: 12345
sync_type: switch
state: available
relationships:
- sync_type: switch
name: switch-2
id: 23456
relation_type: contains
- name: switch-2
id: 23456
sync_type: switch
state: available
relationships:
- sync_type: switch
name: switch-3
id: 34567
relation_type: contains

View File

@ -0,0 +1,120 @@
# Copyright 2016 - Nokia
#
# 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.
import os
from oslo_config import cfg
from oslo_log import log as logging
from vitrage.common.constants import EntityType
from vitrage.common.constants import EventAction
from vitrage.common.constants import SynchronizerProperties as SyncProps
from vitrage.common.constants import SyncMode
from vitrage.common.constants import VertexProperties as VProps
from vitrage.common import file_utils
from vitrage.synchronizer.plugins.static_physical import synchronizer
from vitrage.tests import base
from vitrage.tests.mocks import utils
LOG = logging.getLogger(__name__)
class TestStaticPhysicalSynchronizer(base.BaseTest):
OPTS = [
cfg.StrOpt('static_plugins_dir',
default=utils.get_resources_dir() + '/static_plugins',
),
]
CHANGES_OPTS = [
cfg.StrOpt('static_plugins_dir',
default=utils.get_resources_dir() + '/static_plugins/'
'changes_plugins',
),
]
def setUp(self):
super(TestStaticPhysicalSynchronizer, self).setUp()
self.conf = cfg.ConfigOpts()
self.conf.register_opts(self.OPTS, group='synchronizer_plugins')
self.static_physical_synchronizer = \
synchronizer.StaticPhysicalSynchronizer(self.conf)
def test_static_plugins_loader(self):
# Setup
total_static_plugins = \
os.listdir(self.conf.synchronizer_plugins.static_plugins_dir)
# Action
static_configs = file_utils.load_yaml_files(
self.conf.synchronizer_plugins.static_plugins_dir)
# Test assertions
# -1 is because there are 2 files and a folder in static_plugins_dir
self.assertEqual(len(total_static_plugins) - 1, len(static_configs))
def test_get_all(self):
# Action
static_entities = self.static_physical_synchronizer.get_all(
SyncMode.UPDATE)
# Test assertions
self.assertEqual(5, len(static_entities))
def test_get_changes(self):
# Setup
entities = self.static_physical_synchronizer.get_all(SyncMode.UPDATE)
self.assertEqual(5, len(entities))
self.conf = cfg.ConfigOpts()
self.conf.register_opts(self.CHANGES_OPTS,
group='synchronizer_plugins')
self.static_physical_synchronizer.cfg = self.conf
# Action
changes = self.static_physical_synchronizer.get_changes(
EventAction.UPDATE)
# Test Assertions
status = any(change[SyncProps.SYNC_TYPE] == EntityType.SWITCH and
change[VProps.ID] == '12345' for change in changes)
self.assertEqual(False, status)
status = any(change[SyncProps.SYNC_TYPE] == EntityType.SWITCH and
change[VProps.ID] == '23456' and
change[SyncProps.EVENT_TYPE] == 'delete'
for change in changes)
self.assertEqual(True, status)
status = any(change[SyncProps.SYNC_TYPE] == EntityType.SWITCH and
change[VProps.ID] == '34567' for change in changes)
self.assertEqual(True, status)
status = any(change[SyncProps.SYNC_TYPE] == EntityType.SWITCH and
change[VProps.ID] == '45678' for change in changes)
self.assertEqual(True, status)
status = any(change[SyncProps.SYNC_TYPE] == EntityType.SWITCH and
change[VProps.ID] == '56789' for change in changes)
self.assertEqual(True, status)
self.assertEqual(4, len(changes))
# Action
changes = self.static_physical_synchronizer.get_changes(
EventAction.UPDATE)
# Test Assertions
self.assertEqual(0, len(changes))

View File

@ -23,7 +23,7 @@ from vitrage.common.constants import SynchronizerProperties as SyncProps
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.synchronizer.plugins.nova.host.transformer import HostTransformer from vitrage.synchronizer.plugins.nova.host.transformer import HostTransformer
from vitrage.synchronizer.plugins.static_physical.transformer \ from vitrage.synchronizer.plugins.static_physical.transformer \
import StaticPhysical import StaticPhysicalTransformer
from vitrage.synchronizer.plugins.transformer_base import TransformerBase from vitrage.synchronizer.plugins.transformer_base import TransformerBase
from vitrage.tests import base from vitrage.tests import base
from vitrage.tests.mocks import mock_syncronizer as mock_sync from vitrage.tests.mocks import mock_syncronizer as mock_sync
@ -38,7 +38,7 @@ class TestStaticPhysicalTransformer(base.BaseTest):
self.transformers = {} self.transformers = {}
host_transformer = HostTransformer(self.transformers) host_transformer = HostTransformer(self.transformers)
static_transformer = StaticPhysical(self.transformers) static_transformer = StaticPhysicalTransformer(self.transformers)
self.transformers[EntityType.NOVA_HOST] = host_transformer self.transformers[EntityType.NOVA_HOST] = host_transformer
self.transformers[EntityType.SWITCH] = static_transformer self.transformers[EntityType.SWITCH] = static_transformer
@ -51,7 +51,7 @@ class TestStaticPhysicalTransformer(base.BaseTest):
switch_type = EntityType.SWITCH switch_type = EntityType.SWITCH
switch_name = 'switch-1' switch_name = 'switch-1'
timestamp = datetime.datetime.utcnow() timestamp = datetime.datetime.utcnow()
static_transformer = StaticPhysical(self.transformers) static_transformer = StaticPhysicalTransformer(self.transformers)
# Test action # Test action
properties = { properties = {
@ -65,7 +65,7 @@ class TestStaticPhysicalTransformer(base.BaseTest):
observed_id_values = placeholder.vertex_id.split( observed_id_values = placeholder.vertex_id.split(
TransformerBase.KEY_SEPARATOR) TransformerBase.KEY_SEPARATOR)
expected_id_values = \ expected_id_values = \
StaticPhysical(self.transformers).key_values( StaticPhysicalTransformer(self.transformers).key_values(
[switch_type, switch_name]) [switch_type, switch_name])
self.assertEqual(observed_id_values, expected_id_values) self.assertEqual(observed_id_values, expected_id_values)
@ -90,7 +90,7 @@ class TestStaticPhysicalTransformer(base.BaseTest):
# Test setup # Test setup
switch_type = EntityType.SWITCH switch_type = EntityType.SWITCH
switch_name = 'switch-1' switch_name = 'switch-1'
static_transformer = StaticPhysical(self.transformers) static_transformer = StaticPhysicalTransformer(self.transformers)
# Test action # Test action
observed_key_fields = static_transformer.key_values( observed_key_fields = static_transformer.key_values(
@ -112,7 +112,8 @@ class TestStaticPhysicalTransformer(base.BaseTest):
for event in static_events: for event in static_events:
# Test action # Test action
wrapper = StaticPhysical(self.transformers).transform(event) wrapper = StaticPhysicalTransformer(self.transformers).\
transform(event)
# Test assertions # Test assertions
vertex = wrapper.vertex vertex = wrapper.vertex

View File

@ -1,63 +0,0 @@
# Copyright 2016 - Nokia
#
# 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.
import os
from oslo_config import cfg
from oslo_log import log as logging
from vitrage.common import file_utils
from vitrage.tests import base
from vitrage.tests.mocks import utils
LOG = logging.getLogger(__name__)
class TestStaticPlugin(base.BaseTest):
OPTS = [
cfg.StrOpt('static_plugins_dir',
default=utils.get_resources_dir() + '/static_plugins',
),
]
def setUp(self):
super(TestStaticPlugin, self).setUp()
self.static_dir_path = utils.get_resources_dir() + '/static_plugins'
self.conf = cfg.ConfigOpts()
self.conf.register_opts(self.OPTS, group='synchronizer_plugins')
def test_static_plugins_loader(self):
# Setup
total_static_plugins = os.listdir(self.static_dir_path)
# Action
static_configs = file_utils.load_yaml_files(
self.conf.synchronizer_plugins.static_plugins_dir)
# Test assertions
self.assertEqual(len(total_static_plugins), len(static_configs))
def test_number_of_entities(self):
static_entities = []
static_plugin_configs = file_utils.load_yaml_files(
self.conf.synchronizer_plugins.static_plugins_dir)
for config in static_plugin_configs:
for entity in config['entities']:
static_entities.append(entity)
# Test assertions
self.assertEqual(5, len(static_entities))