def find_and_configure_plugins(env_deployments): """Find and configure plugins that may have been declared. If the option 'plugins' is not defined, this function is a no-op. If the plugins value is not a string or list, the an error is logged and the function just returns; i.e. misconfiguring the plugins option in the yaml file doesn't end the test. :param env_deployments: The enviroments that are doing to be run. :type env_deployments: List[EnvironmentDeploy] :raises Exception: a plugin may explode, which means this function can fail. """ plugins = get_option("plugins") if plugins is None: return if isinstance(plugins, str): plugins = (plugins, ) if not isinstance(plugins, Iterable): logger.error("Couldn't load plugins as it doesn't seem to be a list?") return for plugin_func in plugins: logger.info("Running plugin func %s()", plugin_func) try: utils.get_class(plugin_func)(env_deployments) except Exception as e: logger.error("Plugin func: '%s' failed with %s", plugin_func, str(e)) import traceback logger.error(traceback.format_exc()) raise
def upload_collection_by_config(collection, context=None): """Upload a collection's events using a configured set of uploaders. :param collection: the collection whose logs to upload. :type collection: zaza.events.collection.Collection :param context: a context of dictionary keys for filling in values :type context: Optional[Dict[str, str]] """ uploads = get_option('zaza-events.upload', []) if isinstance(uploads, str) or not isinstance(uploads, Iterable): logger.error("Config to upload logs is misconfigured? %s", uploads) return for upload in uploads: if not isinstance(upload, Mapping): logger.error( "Ignoring upload; it doesn't seem correcly formatted?", upload) continue try: upload_type = upload['type'] except KeyError: logger.error("No type provided for upload, ignoring: %s", upload) continue if not isinstance(upload_type, str): logger.error("upload type is not a str, ignoring: %s", upload_type) continue upload_type = upload_type.lower() # TODO: this would be nicer as a dict lookup to make it more # flexible, but for the moment, we only support InfluxDB if upload_type == "influxdb": upload_influxdb(upload, collection, context) else: logger.error("Unknown type %s for uploading; ignoring", upload_type)
def __init__(self, env_deployments): """Initialise the Events Plugin. :param env_deployments: the deployments/tests that will happen. :type env_deployments: List[EnvironmentDeploy] """ self.env_deployments = env_deployments keep_logs = get_option('zaza-events.keep-logs', False) if keep_logs: self.logs_dir_base = os.path.join(tempfile.gettempdir(), "zaza-events") Path(self.logs_dir_base).mkdir(parents=True, exist_ok=True) logger.debug("Ensuring logs base dir as: %s", self.logs_dir_base) else: # get a directory that will disappear at the end of the program. self.logs_dir_base = tempfile.TemporaryDirectory("-zaza-events") # Handle all notifications and turn them into zaza.events for the # timeseries event logging. subscribe(self.handle_notifications, event=None, when=NotifyType.BOTH) # Handle the BEFORE and AFTER bundle notifications to actually create # and finalise the events logger after each deployment. subscribe(self.handle_before_bundle, event=NotifyEvents.BUNDLE, when=NotifyType.BEFORE) subscribe(self.handle_after_bundle, event=NotifyEvents.BUNDLE, when=NotifyType.AFTER) logger.info("Configured EventsPlugin.")
def test_get_option(self): options = { 'key1': 'value1', 'key2': 2, 'key3': { 'key3-1': 3, 'key3-2': 4, }, 'key4': ['a', 'b', 'c', { "key4-3-1": 5 }], } self.patch_object(g, '_options', new=options) self.assertEqual(g.get_option("key1"), "value1") self.assertEqual(g.get_option("key2"), 2) self.assertEqual(g.get_option("key3.key3-1"), 3) self.assertEqual(g.get_option("key3.key3-2"), 4) self.assertEqual(g.get_option("key4.3.key4-3-1"), 5)
def get_global_events_logging_manager(): """Return the events logging manager (for the collection). This returns the logging manager for logging time-series events from within zaza tests. The advantage of using this is that it taps into the global_options to get the 'right' one for the test. Note: if there are no zaza-events options configured the 'DEFAULT' event logging manager will be returned, which if nothing is configured, won't do anything with logs if a logger is requested and then used. :returns: the configured logging manager. :rtype: zaza.events.plugins.logging.LoggerPluginManager """ name = get_option("zaza-events.modules.logging.logger-name", None) if name is None: name = get_option("zaza-events.collection-name", "DEFAULT") return get_plugin_manager(name)
def get_conncheck_manager(): """Get the conncheck manager that's most like to be useful. If zaza-events.modules.conncheck.manager-name is defined, then use that to return the manager, otherwise, use the DEFAULT. :returns: a conncheck manager :rtype: ConnCheckPluginManager """ name = get_option('zaza-events.modules.conncheck.manager-name', "DEFAULT") return get_plugin_manager(name)
def configure(env_deployments): """Configure the event plugin for events. If the 'zaza-events' config option is available, this configures the EventsPlugin that then glues zaza.notifications published events into zaza.events time-series notifications. :param env_deployments: the deployments/tests that will happen. :type env_deployments: List[EnvironmentDeploy] """ config = get_option("zaza-events", raise_exception=False) if config is None: logger.error( "zaza.events.configure called, but no configuration found") return # Note that the subscriptions in the __init__() will keep events_plugin # around until the end of the program run. EventsPlugin(env_deployments)
def get_collection(name=None): """Return a collection by name. Collections are available globally, and typically only one will be needed per test. If the global options 'zaza-events.collection-name' is defined, then that collection name is used for the collection. This is so a global collection can be used within tests if wanted. Obviously, overriding :param:`name` will force a particular collection to be returned. This returns a named collection (a.k.a logging) for use in a module. :returns: the colection named, or creates a new one of that name. :rtype: Collection """ global _collections if name is None: name = get_option("zaza-events.collection-name", "DEFAULT") try: return _collections[name] except KeyError: pass _collections[name] = Collection(name=name) return _collections[name]
def handle_before_bundle(self, event, when, *args, env_deployment=None, **kwargs): """Handle when a new bundle is about to be performed. This resets/configures the collection. This allows a complete test run be a bundle that gets all its logs collected and then saved "somewhere" according to the config. :param event: This must be NotifyEvents.BUNDLE :type event: NotifyEvents :param when: This must be NotifyType.BEFORE :type when: NotifyType :param *args: Any additional args passed; these are ignored. :type *args: Tuple(ANY) :param env_deployment: The deployment details for this model. :type env_deployment: EnvironmentDeploy :param **kwargs: Any additional kwargs; these are ignored. :type **kwargs: Dict[str, ANY] """ logger.info("handle_before_bundle() called for env_deployment:%s", env_deployment) assert event is NotifyEvents.BUNDLE assert when is NotifyType.BEFORE assert env_deployment is not None # Note that get_collection gets a zaza-events.* configured collection # if the collection-name key is present. collection = ze_collection.get_collection() collection.reset() context = event_context_vars(env_deployment) log_collection_name = expand_vars( context, get_option('zaza-events.log-collection-name', env_deployment.name)) description = get_option('zaza-events.collection-description') logs_dir_name = expand_vars(context, "{bundle}-{date}") try: logs_dir_base = self.logs_dir_base.name except AttributeError: logs_dir_base = self.logs_dir_base logs_dir = os.path.join(logs_dir_base, logs_dir_name) Path(logs_dir).mkdir(parents=True, exist_ok=True) collection.configure(collection=log_collection_name, description=description, logs_dir=logs_dir) # Now configure any additional modules automagically modules = get_option('zaza-events.modules', []) if isinstance(modules, str) or not isinstance(modules, Iterable): logger.error("Option zaza-events.module isn't a list? %s", modules) return for module_spec in modules: # if module is a dictionary then it's a key: spec, otherwise there # is no spec for the module. if isinstance(module_spec, dict): if len(module_spec.keys()) != 1: logger.error( "Module key %s is not formatted as a single-key " "dictionary", module_spec) continue module, config = list(module_spec.items())[0] if not isinstance(config, dict): logger.error("Module key %s has invalid config %s", module, config) continue elif isinstance(module_spec, str): module, config = (module_spec, {}) else: logger.error("Can configure with %s.", module_spec) continue configure_func = ( "zaza.events.plugins.{}.auto_configure_with_collection".format( module)) try: logger.info("Running autoconfigure for zaza-events func %s()", configure_func) utils.get_class(configure_func)(collection, config) except Exception as e: logger.error( "Error running autoconfigure for zaza-events %s: %s", configure_func, str(e)) if get_option('zaza-events.raise-exceptions', False): raise # events = logging_manager.get_global_event_logger_instance() events = get_global_event_logger_instance() events.log(Events.START_TEST, comment="Starting {}".format(log_collection_name))