SEBA-405 Convert synchronizer framework to library
Change-Id: If8562f23dc15c7d18d7a8b040b33756708b3c5ec
diff --git a/lib/xos-synchronizer/xossynchronizer/ansible_runner.py b/lib/xos-synchronizer/xossynchronizer/ansible_runner.py
new file mode 100644
index 0000000..d20feb5
--- /dev/null
+++ b/lib/xos-synchronizer/xossynchronizer/ansible_runner.py
@@ -0,0 +1,388 @@
+#!/usr/bin/env python
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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 multistructlog import create_logger
+from xosconfig import Config
+from ansible.plugins.callback import CallbackBase
+from ansible.utils.display import Display
+from ansible.executor import playbook_executor
+from ansible.parsing.dataloader import DataLoader
+from ansible.vars.manager import VariableManager
+from ansible.inventory.manager import InventoryManager
+from tempfile import NamedTemporaryFile
+import os
+import sys
+import pdb
+import json
+import uuid
+
+from ansible import constants
+
+constants = reload(constants)
+
+
+log = create_logger(Config().get("logging"))
+
+
+class ResultCallback(CallbackBase):
+
+ CALLBACK_VERSION = 2.0
+ CALLBACK_NAME = "resultcallback"
+ CALLBACK_TYPE = "programmatic"
+
+ def __init__(self):
+ super(ResultCallback, self).__init__()
+ self.results = []
+ self.uuid = str(uuid.uuid1())
+ self.playbook_status = "OK"
+
+ def v2_playbook_on_start(self, playbook):
+ self.playbook = playbook._file_name
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "playbook start",
+ "ansible_status": "OK",
+ "ansible_playbook": self.playbook,
+ }
+ log.info("PLAYBOOK START", playbook=self.playbook, **log_extra)
+
+ def v2_playbook_on_stats(self, stats):
+ host_stats = {}
+ for host in stats.processed.keys():
+ host_stats[host] = stats.summarize(host)
+
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "playbook stats",
+ "ansible_status": self.playbook_status,
+ "ansible_playbook": self.playbook,
+ "ansible_result": json.dumps(host_stats),
+ }
+
+ if self.playbook_status == "OK":
+ log.info("PLAYBOOK END", playbook=self.playbook, **log_extra)
+ else:
+ log.error("PLAYBOOK END", playbook=self.playbook, **log_extra)
+
+ def v2_playbook_on_play_start(self, play):
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "play start",
+ "ansible_status": self.playbook_status,
+ "ansible_playbook": self.playbook,
+ }
+ log.debug("PLAY START", play_name=play.name, **log_extra)
+
+ def v2_runner_on_ok(self, result, **kwargs):
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "task",
+ "ansible_status": "OK",
+ "ansible_result": json.dumps(result._result),
+ "ansible_task": result._task,
+ "ansible_playbook": self.playbook,
+ "ansible_host": result._host.get_name(),
+ }
+ log.debug("OK", task=str(result._task), **log_extra)
+ self.results.append(result)
+
+ def v2_runner_on_failed(self, result, **kwargs):
+ self.playbook_status = "FAILED"
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "task",
+ "ansible_status": "FAILED",
+ "ansible_result": json.dumps(result._result),
+ "ansible_task": result._task,
+ "ansible_playbook": self.playbook,
+ "ansible_host": result._host.get_name(),
+ }
+ log.error("FAILED", task=str(result._task), **log_extra)
+ self.results.append(result)
+
+ def v2_runner_on_async_failed(self, result, **kwargs):
+ self.playbook_status = "FAILED"
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "task",
+ "ansible_status": "ASYNC FAILED",
+ "ansible_result": json.dumps(result._result),
+ "ansible_task": result._task,
+ "ansible_playbook": self.playbook,
+ "ansible_host": result._host.get_name(),
+ }
+ log.error("ASYNC FAILED", task=str(result._task), **log_extra)
+
+ def v2_runner_on_skipped(self, result, **kwargs):
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "task",
+ "ansible_status": "SKIPPED",
+ "ansible_result": json.dumps(result._result),
+ "ansible_task": result._task,
+ "ansible_playbook": self.playbook,
+ "ansible_host": result._host.get_name(),
+ }
+ log.debug("SKIPPED", task=str(result._task), **log_extra)
+ self.results.append(result)
+
+ def v2_runner_on_unreachable(self, result, **kwargs):
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "task",
+ "ansible_status": "UNREACHABLE",
+ "ansible_result": json.dumps(result._result),
+ "ansible_task": result._task,
+ "ansible_playbook": self.playbook,
+ "ansible_host": result._host.get_name(),
+ }
+ log.error("UNREACHABLE", task=str(result._task), **log_extra)
+ self.results.append(result)
+
+ def v2_runner_retry(self, result, **kwargs):
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "task",
+ "ansible_status": "RETRY",
+ "ansible_result": json.dumps(result._result),
+ "ansible_task": result._task,
+ "ansible_playbook": self.playbook,
+ "ansible_host": result._host.get_name(),
+ }
+ log.warning(
+ "RETRYING - attempt",
+ task=str(result._task),
+ attempt=result._result["attempts"],
+ **log_extra
+ )
+ self.results.append(result)
+
+ def v2_playbook_on_handler_task_start(self, task, **kwargs):
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "task",
+ "ansible_status": "HANDLER",
+ "ansible_task": task.get_name().strip(),
+ "ansible_playbook": self.playbook,
+ # 'ansible_host': result._host.get_name()
+ }
+ log.debug("HANDLER", task=task.get_name().strip(), **log_extra)
+
+ def v2_playbook_on_import_for_host(self, result, imported_file):
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "import",
+ "ansible_status": "IMPORT",
+ "ansible_result": json.dumps(result._result),
+ "ansible_playbook": self.playbook,
+ "ansible_host": result._host.get_name(),
+ }
+ log.debug("IMPORT", imported_file=imported_file, **log_extra)
+ self.results.append(result)
+
+ def v2_playbook_on_not_import_for_host(self, result, missing_file):
+ log_extra = {
+ "xos_type": "ansible",
+ "ansible_uuid": self.uuid,
+ "ansible_type": "import",
+ "ansible_status": "MISSING IMPORT",
+ "ansible_result": json.dumps(result._result),
+ "ansible_playbook": self.playbook,
+ "ansible_host": result._host.get_name(),
+ }
+ log.debug("MISSING IMPORT", missing=missing_file, **log_extra)
+ self.results.append(result)
+
+
+class Options(object):
+ """
+ Options class to replace Ansible OptParser
+ """
+
+ def __init__(
+ self,
+ ask_pass=None,
+ ask_su_pass=None,
+ ask_sudo_pass=None,
+ become=None,
+ become_ask_pass=None,
+ become_method=None,
+ become_user=None,
+ check=None,
+ connection=None,
+ diff=None,
+ flush_cache=None,
+ force_handlers=None,
+ forks=1,
+ listtags=None,
+ listtasks=None,
+ module_path=None,
+ new_vault_password_file=None,
+ one_line=None,
+ output_file=None,
+ poll_interval=None,
+ private_key_file=None,
+ remote_user=None,
+ scp_extra_args=None,
+ seconds=None,
+ sftp_extra_args=None,
+ skip_tags=None,
+ ssh_common_args=None,
+ ssh_extra_args=None,
+ sudo=None,
+ sudo_user=None,
+ syntax=None,
+ tags=None,
+ timeout=None,
+ tree=None,
+ vault_password_files=None,
+ ask_vault_pass=None,
+ extra_vars=None,
+ inventory=None,
+ listhosts=None,
+ module_paths=None,
+ subset=None,
+ verbosity=None,
+ ):
+
+ if tags:
+ self.tags = tags
+
+ if skip_tags:
+ self.skip_tags = skip_tags
+
+ self.ask_pass = ask_pass
+ self.ask_su_pass = ask_su_pass
+ self.ask_sudo_pass = ask_sudo_pass
+ self.ask_vault_pass = ask_vault_pass
+ self.become = become
+ self.become_ask_pass = become_ask_pass
+ self.become_method = become_method
+ self.become_user = become_user
+ self.check = check
+ self.connection = connection
+ self.diff = diff
+ self.extra_vars = extra_vars
+ self.flush_cache = flush_cache
+ self.force_handlers = force_handlers
+ self.forks = forks
+ self.inventory = inventory
+ self.listhosts = listhosts
+ self.listtags = listtags
+ self.listtasks = listtasks
+ self.module_path = module_path
+ self.module_paths = module_paths
+ self.new_vault_password_file = new_vault_password_file
+ self.one_line = one_line
+ self.output_file = output_file
+ self.poll_interval = poll_interval
+ self.private_key_file = private_key_file
+ self.remote_user = remote_user
+ self.scp_extra_args = scp_extra_args
+ self.seconds = seconds
+ self.sftp_extra_args = sftp_extra_args
+ self.ssh_common_args = ssh_common_args
+ self.ssh_extra_args = ssh_extra_args
+ self.subset = subset
+ self.sudo = sudo
+ self.sudo_user = sudo_user
+ self.syntax = syntax
+ self.timeout = timeout
+ self.tree = tree
+ self.vault_password_files = vault_password_files
+ self.verbosity = verbosity
+
+
+class Runner(object):
+ def __init__(
+ self, playbook, run_data, private_key_file=None, verbosity=0, host_file=None
+ ):
+
+ self.playbook = playbook
+ self.run_data = run_data
+
+ self.options = Options()
+ self.options.output_file = playbook + ".result"
+ self.options.private_key_file = private_key_file
+ self.options.verbosity = verbosity
+ self.options.connection = "ssh" # Need a connection type "smart" or "ssh"
+ # self.options.become = True
+ self.options.become_method = "sudo"
+ self.options.become_user = "root"
+
+ # Set global verbosity
+ self.display = Display()
+ self.display.verbosity = self.options.verbosity
+ # Executor appears to have it's own
+ # verbosity object/setting as well
+ playbook_executor.verbosity = self.options.verbosity
+
+ # Become Pass Needed if not logging in as user root
+ # passwords = {'become_pass': become_pass}
+
+ # Gets data from YAML/JSON files
+ self.loader = DataLoader()
+ try:
+ self.loader.set_vault_password(os.environ["VAULT_PASS"])
+ except AttributeError:
+ pass
+
+ # Set inventory, using most of above objects
+ if host_file:
+ self.inventory = InventoryManager(loader=self.loader, sources=host_file)
+ else:
+ self.inventory = InventoryManager(loader=self.loader)
+
+ # All the variables from all the various places
+ self.variable_manager = VariableManager(
+ loader=self.loader, inventory=self.inventory
+ )
+ self.variable_manager.extra_vars = {} # self.run_data
+
+ # Setup playbook executor, but don't run until run() called
+ self.pbex = playbook_executor.PlaybookExecutor(
+ playbooks=[playbook],
+ inventory=self.inventory,
+ variable_manager=self.variable_manager,
+ loader=self.loader,
+ options=self.options,
+ passwords={},
+ )
+
+ def run(self):
+ os.environ[
+ "REQUESTS_CA_BUNDLE"
+ ] = "/usr/local/share/ca-certificates/local_certs.crt"
+ callback = ResultCallback()
+ self.pbex._tqm._stdout_callback = callback
+
+ self.pbex.run()
+ stats = self.pbex._tqm._stats
+
+ # os.remove(self.hosts.name)
+
+ return stats, callback.results