diff --git a/README.md b/README.md index c1268ca..afa56a5 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,29 @@ Example Playbook Generate var file mapping [demo](https://youtu.be/s1-Hq_Mk1w8)
Fail over scenario [demo](https://youtu.be/mEOgH-Tk09c) +Scripts +------- +The ovirt-dr script should provide the user a more convinient way to run +disaster recovery actions as a way to avoid using ansible playbooks directly. +There are four actions which the user can execute: + validate validate the var file mapping which is used for failover and failback + generate Generate the mapping var file based on the primary and secondary setup, to be used for failover and failback + failover Start a failover process to the target setup + failback Start a failback process from the target setup to the source setup + +Each of those actions is using a configuration file which its default location is oVirt.disaster-recovery/files/dr.conf + +Example Script +-------------- +For mapping file generation: + ./ovirt-dr generate +For mapping file validation: + ./ovirt-dr validate +For fail-over operation: + ./ovirt-dr failover +For fail-back operation: + ./ovirt-dr failback + License ------- diff --git a/examples/dr_generate_var.yml b/examples/dr_play.yml similarity index 100% rename from examples/dr_generate_var.yml rename to examples/dr_play.yml diff --git a/files/bcolors.py b/files/bcolors.py new file mode 100644 index 0000000..0c494fb --- /dev/null +++ b/files/bcolors.py @@ -0,0 +1,18 @@ +#!/usr/bin/python + + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[1;34m' + OKGREEN = '\033[0;32m' + WARNING = '\x1b[0;33m' + FAIL = '\033[0;31m' + ENDC = '\033[0m' + + def disable(self): + self.HEADER = '' + self.OKBLUE = '' + self.OKGREEN = '' + self.WARNING = '' + self.FAIL = '' + self.ENDC = '' diff --git a/files/dr.conf b/files/dr.conf new file mode 100644 index 0000000..0973f58 --- /dev/null +++ b/files/dr.conf @@ -0,0 +1,20 @@ +[generate_vars] +site=http://engine.example.com/ovirt-engine/api +username=admin@internal +password= +ca_file=/etc/pki/ovirt-engine/ca.pem +output_file=/var/lib/ovirt-ansible-disaster-recovery/mapping_vars.yml +ansible_play=../examples/dr_play.yml + +[validate_vars] +var_file=/var/lib/ovirt-ansible-disaster-recovery/mapping_vars.yml +vault=../examples/ovirt_passwords.yml + +[failover_failback] +dr_target_host=secondary +dr_source_map=primary +vault=passwords.yml +var_file=/var/lib/ovirt-ansible-disaster-recovery/mapping_vars.yml +ansible_play=../examples/dr_play.yml +dr_report_file=/tmp/report.log + diff --git a/files/fail_back.py b/files/fail_back.py new file mode 100755 index 0000000..84c9a3a --- /dev/null +++ b/files/fail_back.py @@ -0,0 +1,250 @@ +#!/usr/bin/python +from bcolors import bcolors +try: + import configparser +except ImportError: + import ConfigParser as configparser +import os.path +import shlex +import subprocess +import sys + +from subprocess import call + +INFO = bcolors.OKGREEN +INPUT = bcolors.OKGREEN +WARN = bcolors.WARNING +FAIL = bcolors.FAIL +END = bcolors.ENDC +PREFIX = "[Failback] " +PLAY_DEF = "../examples/dr_play.yml" + + +class FailBack(): + + def run(self, conf_file, log_file): + print("\n%s%sStart failback operation...%s" + % (INFO, + PREFIX, + END)) + dr_tag = "fail_back" + dr_clean_tag = "clean_engine" + target_host, source_map, var_file, vault, ansible_play = \ + self._init_vars(conf_file) + print("\n%s%starget_host: %s \n" + "%ssource_map: %s \n" + "%svar_file: %s \n" + "%svault: %s \n" + "%sansible_play: %s%s \n" + % (INFO, + PREFIX, + target_host, + PREFIX, + source_map, + PREFIX, + var_file, + PREFIX, + vault, + PREFIX, + ansible_play, + END)) + + cmd = [] + cmd.append("ansible-playbook") + cmd.append(ansible_play) + cmd.append("-t") + cmd.append(dr_clean_tag) + cmd.append("-e") + cmd.append("@" + var_file) + cmd.append("-e") + cmd.append("@" + vault) + cmd.append("-e") + cmd.append(" dr_source_map=" + target_host) + cmd.append("--vault-password-file") + cmd.append("vault_secret.sh") + cmd.append("-vvv") + + cmd_fb = [] + cmd_fb.append("ansible-playbook") + cmd_fb.append(ansible_play) + cmd_fb.append("-t") + cmd_fb.append(dr_tag) + cmd_fb.append("-e") + cmd_fb.append("@" + var_file) + cmd_fb.append("-e") + cmd_fb.append("@" + vault) + cmd_fb.append("-e") + cmd_fb.append(" dr_target_host=" + target_host + + " dr_source_map=" + source_map) + cmd_fb.append("--vault-password-file") + cmd_fb.append("vault_secret.sh") + cmd_fb.append("-vvv") + + # Setting vault password + vault_pass = raw_input( + INPUT + PREFIX + "Please enter the vault password: " + END) + os.system("export vault_password=\"" + vault_pass + "\"") + print("\n%s%sStarting cleanup process of setup '%s'" + " for oVirt ansible disaster recovery%s" + % (INFO, + PREFIX, + target_host, + END)) + with open(log_file, "w") as f: + f.write("Executing cleanup command: %s" % ' '.join(map(str, cmd))) + proc = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + for line in iter(proc.stdout.readline, ''): + # TODO: since we dont want to have log and print the + # progress in the stdout, we only filter the task names + # We should find a better way to do so. + if 'TASK [ovirt-ansible-disaster-recovery : ' in line: + sys.stdout.write("\n" + line + "\n") + f.write(line) + + print("\n%s%sFinished cleanup of setup '%s'" + " for oVirt ansible disaster recovery%s" + % (INFO, + PREFIX, + source_map, + END)) + + print("\n%s%sStarting fail-back process to setup '%s'" + " from setup '%s' for oVirt ansible disaster recovery" + % (INFO, + PREFIX, + target_host, + source_map)) + + f.write("Executing command %s" % ' '.join(map(str, cmd_fb))) + proc_fb = subprocess.Popen(cmd_fb, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + for line in iter(proc_fb.stdout.readline, ''): + # TODO: since we dont want to have log and print the + # progress in the stdout, we only filter the task names + # We should find a better way to do so. + if 'TASK [ovirt-ansible-disaster-recovery :' in line: + sys.stdout.write("\n" + line + "\n") + if "[Failback Replication Sync]" in line: + sys.stdout.write("\n" + INPUT + line + END) + f.write(line) + + call(["cat", "report.log"]) + print("\n%s%sFinished failback operation" + " for oVirt ansible disaster recovery%s" + % (INFO, + PREFIX, + END)) + + def _init_vars(self, conf_file): + """ Declare constants """ + _SECTION = "failover_failback" + _TARGET = "dr_target_host" + _SOURCE = "dr_source_map" + _VAULT = "vault" + _VAR_FILE = "var_file" + _ANSIBLE_PLAY = 'ansible_play' + setups = ['primary', 'secondary'] + + """ Declare varialbles """ + target_host, source_map, vault, var_file, ansible_play = \ + '', '', '', '', '' + settings = configparser.ConfigParser() + settings._interpolation = configparser.ExtendedInterpolation() + settings.read(conf_file) + if _SECTION not in settings.sections(): + settings.add_section(_SECTION) + if not settings.has_option(_SECTION, _TARGET): + settings.set(_SECTION, _TARGET, '') + if not settings.has_option(_SECTION, _SOURCE): + settings.set(_SECTION, _SOURCE, '') + if not settings.has_option(_SECTION, _VAULT): + settings.set(_SECTION, _VAULT, '') + if not settings.has_option(_SECTION, _VAR_FILE): + settings.set(_SECTION, _VAR_FILE, '') + if not settings.has_option(_SECTION, _ANSIBLE_PLAY): + settings.set(_SECTION, _ANSIBLE_PLAY, '') + # We fetch the source map as target host since in failback + # we do the reverse operation. + target_host = settings.get(_SECTION, _SOURCE, + vars=DefaultOption(settings, + _SECTION, + source_map=None)) + # We fetch the target host as target the source mapping for failback + # since we do the reverse operation. + source_map = settings.get(_SECTION, _TARGET, + vars=DefaultOption(settings, + _SECTION, + target_host=None)) + vault = settings.get(_SECTION, _VAULT, + vars=DefaultOption(settings, + _SECTION, + vault=None)) + var_file = settings.get(_SECTION, _VAR_FILE, + vars=DefaultOption(settings, + _SECTION, + var_file=None)) + ansible_play = settings.get(_SECTION, _ANSIBLE_PLAY, + vars=DefaultOption(settings, + _SECTION, + ansible_play=None)) + while target_host not in setups: + target_host = raw_input( + INPUT + PREFIX + "The target setup was not defined. " + "Please provide the setup which it is failback to " + "(primary or secondary): " + END) + while source_map not in setups: + source_map = raw_input( + INPUT + PREFIX + "The source mapping was not defined. " + "Please provide the source mapping " + "(primary or secondary): " + END) + while not os.path.isfile(var_file): + var_file = raw_input("%s%svar file mapping '%s' does not exist. " + "Please provide a valid mapping var file: %s" + % (INPUT, + PREFIX, + var_file, + END)) + while not os.path.isfile(vault): + vault = raw_input("%s%spassword file '%s' does not exist." + "Please provide a valid password file:%s " + % (INPUT, + PREFIX, + vault, + END)) + while (not ansible_play) or (not os.path.isfile(ansible_play)): + ansible_play = raw_input("%s%sansible play '%s' " + "is not initialized. " + "Please provide the ansible play file " + "to generate the mapping var file " + "with ('%s'):%s " + % (INPUT, + PREFIX, + str(ansible_play), + PLAY_DEF, + END) or PLAY_DEF) + return (target_host, source_map, var_file, vault, ansible_play) + + +class DefaultOption(dict): + + def __init__(self, config, section, **kv): + self._config = config + self._section = section + dict.__init__(self, **kv) + + def items(self): + _items = [] + for option in self: + if not self._config.has_option(self._section, option): + _items.append((option, self[option])) + else: + value_in_config = self._config.get(self._section, option) + _items.append((option, value_in_config)) + return _items + + +if __name__ == "__main__": + FailBack().run('dr.conf', '/var/log/ovirt-dr/ovirt-dr.log') diff --git a/files/fail_over.py b/files/fail_over.py new file mode 100755 index 0000000..2e741e1 --- /dev/null +++ b/files/fail_over.py @@ -0,0 +1,190 @@ +#!/usr/bin/python +from bcolors import bcolors +try: + import configparser +except ImportError: + import ConfigParser as configparser +import os.path +import shlex +import subprocess +import sys + +from subprocess import call + +INFO = bcolors.OKGREEN +INPUT = bcolors.OKGREEN +WARN = bcolors.WARNING +FAIL = bcolors.FAIL +END = bcolors.ENDC +PREFIX = "[Failover] " +PLAY_DEF = "../examples/dr_play.yml" + + +class FailOver(): + + def run(self, conf_file, log_file): + print("\n%s%sStart failover operation...%s" + % (INFO, + PREFIX, + END)) + dr_tag = "fail_over" + target_host, source_map, var_file, vault, ansible_play = \ + self._init_vars(conf_file) + print("\n%s%starget_host: %s \n" + "%ssource_map: %s \n" + "%svar_file: %s \n" + "%svault: %s \n" + "%sansible_play: %s%s \n" + % (INFO, + PREFIX, + target_host, + PREFIX, + source_map, + PREFIX, + var_file, + PREFIX, + vault, + PREFIX, + ansible_play, + END)) + + cmd = [] + cmd.append("ansible-playbook") + cmd.append(ansible_play) + cmd.append("-t") + cmd.append(dr_tag) + cmd.append("-e") + cmd.append("@" + var_file) + cmd.append("-e") + cmd.append("@" + vault) + cmd.append("-e") + cmd.append( + " dr_target_host=" + target_host + " dr_source_map=" + source_map) + cmd.append("--ask-vault-pass") + cmd.append("-vvvvv") + with open(log_file, "w") as f: + f.write("Executing failover command: %s" % ' '.join(map(str, cmd))) + proc = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + for line in iter(proc.stdout.readline, ''): + # TODO: since we dont want to have log and print the + # progress in the stdout, we only filter the task names + # We should find a better way to do so. + if 'TASK [ovirt-ansible-disaster-recovery : ' in line: + sys.stdout.write("\n" + line + "\n") + f.write(line) + call(['cat', '../files/report.log']) + + print("\n%s%sFinished failover operation" + " for oVirt ansible disaster recovery%s" + % (INFO, + PREFIX, + END)) + + def _init_vars(self, conf_file): + """ Declare constants """ + _SECTION = "failover_failback" + _TARGET = "dr_target_host" + _SOURCE = "dr_source_map" + _VAULT = "vault" + _VAR_FILE = "var_file" + _ANSIBLE_PLAY = 'ansible_play' + setups = ['primary', 'secondary'] + + """ Declare varialbles """ + target_host, source_map, vault, var_file, ansible_play = \ + '', '', '', '', '' + settings = configparser.ConfigParser() + settings._interpolation = configparser.ExtendedInterpolation() + settings.read(conf_file) + if _SECTION not in settings.sections(): + settings.add_section(_SECTION) + if not settings.has_option(_SECTION, _TARGET): + settings.set(_SECTION, _TARGET, '') + if not settings.has_option(_SECTION, _SOURCE): + settings.set(_SECTION, _SOURCE, '') + if not settings.has_option(_SECTION, _VAULT): + settings.set(_SECTION, _VAULT, '') + if not settings.has_option(_SECTION, _VAR_FILE): + settings.set(_SECTION, _VAR_FILE, '') + if not settings.has_option(_SECTION, _ANSIBLE_PLAY): + settings.set(_SECTION, _ANSIBLE_PLAY, '') + target_host = settings.get(_SECTION, _TARGET, + vars=DefaultOption(settings, + _SECTION, + target_host=None)) + source_map = settings.get(_SECTION, _SOURCE, + vars=DefaultOption(settings, + _SECTION, + source_map=None)) + vault = settings.get(_SECTION, _VAULT, + vars=DefaultOption(settings, + _SECTION, + vault=None)) + var_file = settings.get(_SECTION, _VAR_FILE, + vars=DefaultOption(settings, + _SECTION, + var_file=None)) + ansible_play = settings.get(_SECTION, _ANSIBLE_PLAY, + vars=DefaultOption(settings, + _SECTION, + ansible_play=None)) + while target_host not in setups: + target_host = raw_input( + INPUT + PREFIX + "target host was not defined. " + "Please provide the target host " + "(primary or secondary): " + END) + while source_map not in setups: + source_map = raw_input( + INPUT + PREFIX + "source mapping was not defined. " + "Please provide the source mapping " + "(primary or secondary): " + END) + while not os.path.isfile(var_file): + var_file = raw_input("%s%svar file mapping '%s' does not exist. " + "Please provide a valid mapping var file: %s" + % (INPUT, + PREFIX, + var_file, + END)) + while not os.path.isfile(vault): + vault = raw_input("%s%spassword file '%s' does not exist." + "Please provide a valid password file:%s " + % (INPUT, + PREFIX, + vault, + END)) + while (not ansible_play) or (not os.path.isfile(ansible_play)): + ansible_play = raw_input("%s%sansible play '%s' " + "is not initialized. " + "Please provide the ansible play file " + "to generate the mapping var file " + "with ('%s'):%s " + % (INPUT, + PREFIX, + str(ansible_play), + PLAY_DEF, + END) or PLAY_DEF) + return (target_host, source_map, var_file, vault, ansible_play) + + +class DefaultOption(dict): + + def __init__(self, config, section, **kv): + self._config = config + self._section = section + dict.__init__(self, **kv) + + def items(self): + _items = [] + for option in self: + if not self._config.has_option(self._section, option): + _items.append((option, self[option])) + else: + value_in_config = self._config.get(self._section, option) + _items.append((option, value_in_config)) + return _items + + +if __name__ == "__main__": + FailOver().run('dr.conf', '/var/log/ovirt-dr/ovirt-dr.log') diff --git a/tasks/generate_mapping.py b/files/generate_mapping.py similarity index 100% rename from tasks/generate_mapping.py rename to files/generate_mapping.py diff --git a/files/generate_vars.py b/files/generate_vars.py new file mode 100755 index 0000000..3ea531e --- /dev/null +++ b/files/generate_vars.py @@ -0,0 +1,323 @@ +#!/usr/bin/python +from bcolors import bcolors + +try: + import configparser +except ImportError: + import ConfigParser as configparser +import os.path +import ovirtsdk4 as sdk +import shlex +import subprocess +import sys + +from subprocess import call + +INFO = bcolors.OKGREEN +INPUT = bcolors.OKGREEN +WARN = bcolors.WARNING +FAIL = bcolors.FAIL +END = bcolors.ENDC +PREFIX = "[Generate Mapping File] " +CA_DEF = '/etc/pki/ovirt-engine/ca.pem' +USERNAME_DEF = 'admin@internal' +SITE_DEF = 'http://localhost:8080/ovirt-engine/api' +VAR_DEF = "/var/lib/ovirt-ansible-disaster-recovery/mapping_vars.yml" +PLAY_DEF = "../examples/dr_play.yml" + + +class GenerateMappingFile(): + + def run(self, conf_file, log_file): + print("\n%s%sStart generate variable mapping file " + "for oVirt ansible disaster recovery%s" + % (INFO, + PREFIX, + END)) + dr_tag = "generate_mapping" + site, username, password, ca_file, var_file_path, _ansible_play = \ + self._init_vars(conf_file) + print("\n%s%sSite address: %s \n" + "%susername: %s \n" + "%spassword: *******\n" + "%sca file location: %s \n" + "%soutput file location: %s \n" + "%sansible play location: %s \n%s" + % (INFO, + PREFIX, + site, + PREFIX, + username, + PREFIX, + PREFIX, + ca_file, + PREFIX, + var_file_path, + PREFIX, + _ansible_play, + END)) + if not self._validate_connection(site, username, password, ca_file): + self._print_error(log_file) + exit() + command = "site=" + site + " username=" + username + " password=" + \ + password + " ca=" + ca_file + " var_file=" + var_file_path + cmd = [] + cmd.append("ansible-playbook") + cmd.append(_ansible_play) + cmd.append("-t") + cmd.append(dr_tag) + cmd.append("-e") + cmd.append(command) + cmd.append("-vvvvv") + with open(log_file, "w") as f: + f.write("Executing command %s" % ' '.join(map(str, cmd))) + call(cmd, stdout=f) + if not os.path.isfile(var_file_path): + print("%s%scan not find output file in '%s'.%s" + % (FAIL, + PREFIX, + var_file_path, + END)) + self._print_error(log_file) + exit() + print("\n%s%sVar file location: '%s'%s" + % (INFO, + PREFIX, + var_file_path, + END)) + self._print_success() + + def _print_success(self): + print("%s%sFinished generating variable mapping file " + "for oVirt ansible disaster recovery%s" + % (INFO, + PREFIX, + END)) + + def _print_error(self, log_file): + print("%s%sFailed to generate var file." + " See log file '%s' for further details%s" + % (FAIL, + PREFIX, + log_file, + END)) + + def _connect_sdk(self, url, username, password, ca): + connection = sdk.Connection( + url=url, + username=username, + password=password, + ca_file=ca, + ) + return connection + + def _validate_connection(self, + url, + username, + password, + ca): + conn = None + try: + conn = self._connect_sdk(url, + username, + password, + ca) + dcs_service = conn.system_service().data_centers_service() + dcs_service.list() + except Exception as e: + print( + "%s%sConnection to setup has failed." + " Please check your cradentials: " + "\n%s URL: %s" + "\n%s USER: %s" + "\n%s CA file: %s%s" % + (FAIL, + PREFIX, + PREFIX, + url, + PREFIX, + username, + PREFIX, + ca, + END)) + print("Error: %s" % e) + if conn: + conn.close() + return False + return True + + def _validate_output_file_exists(self, fname): + _dir = os.path.dirname(fname) + if _dir != '' and not os.path.exists(_dir): + print("%s%sPath '%s' does not exists. Create folder%s" + % (WARN, + PREFIX, + _dir, + END)) + os.makedirs(_dir) + if os.path.isfile(fname): + valid = {"yes": True, "y": True, "ye": True, + "no": False, "n": False} + ans = raw_input("%s%sThe output file '%s' " + "already exists. " + "Would you like to override it (y,n)?%s " + % (WARN, + PREFIX, + fname, + END)) + while True: + ans = ans.lower() + if ans in valid: + if not valid[ans]: + print("%s%sFailed to create output file. " + "File could not be overriden.%s" + % (WARN, + PREFIX, + END)) + sys.exit(0) + break + else: + ans = raw_input("%s%sPlease respond with 'yes' or 'no': %s" + % (INPUT, + PREFIX, + END)) + try: + os.remove(fname) + except OSError: + print("\n\n%s%SFile %s could not be replaced.%s" + % (WARN, + PREFIX, + fname, + END)) + sys.exit(0) + + def _init_vars(self, conf_file): + """ Declare constants """ + _SECTION = "generate_vars" + _SITE = 'site' + _USERNAME = 'username' + _PASSWORD = 'password' + _CA_FILE = 'ca_file' + # TODO: Must have full path, should add relative path + _OUTPUT_FILE = '/var/lib/ovirt-ansible-disaster-recovery/mapping_vars.yml' + _ANSIBLE_PLAY = 'ansible_play' + + """ Declare varialbles """ + site, username, password, ca_file, output_file, ansible_play = '', \ + '', '', '', '', '' + settings = configparser.ConfigParser() + settings._interpolation = configparser.ExtendedInterpolation() + settings.read(conf_file) + if _SECTION not in settings.sections(): + settings.add_section(_SECTION) + if not settings.has_option(_SECTION, _SITE): + settings.set(_SECTION, _SITE, '') + if not settings.has_option(_SECTION, _USERNAME): + settings.set(_SECTION, _USERNAME, '') + if not settings.has_option(_SECTION, _PASSWORD): + settings.set(_SECTION, _PASSWORD, '') + if not settings.has_option(_SECTION, _CA_FILE): + settings.set(_SECTION, _CA_FILE, '') + if not settings.has_option(_SECTION, _OUTPUT_FILE): + settings.set(_SECTION, _OUTPUT_FILE, '') + if not settings.has_option(_SECTION, _ANSIBLE_PLAY): + settings.set(_SECTION, _ANSIBLE_PLAY, '') + site = settings.get(_SECTION, _SITE, + vars=DefaultOption(settings, + _SECTION, + site=None)) + username = settings.get(_SECTION, _USERNAME, + vars=DefaultOption(settings, + _SECTION, + username=None)) + password = settings.get(_SECTION, _PASSWORD, + vars=DefaultOption(settings, + _SECTION, + password=None)) + ca_file = settings.get(_SECTION, _CA_FILE, + vars=DefaultOption(settings, + _SECTION, + ca_file=None)) + output_file = settings.get(_SECTION, _OUTPUT_FILE, + vars=DefaultOption(settings, + _SECTION, + output_file=None)) + ansible_play = settings.get(_SECTION, _ANSIBLE_PLAY, + vars=DefaultOption(settings, + _SECTION, + ansible_play=None)) + if (not site): + site = raw_input("%s%sSite address is not initialized. " + "Please provide the site URL (%s):%s " + % (INPUT, + PREFIX, + SITE_DEF, + END)) or SITE_DEF + if (not username): + username = raw_input("%s%sUsername is not initialized. " + "Please provide username " + "(%s):%s " + % (INPUT, + PREFIX, + USERNAME_DEF, + END)) or USERNAME_DEF + while (not password): + password = raw_input("%s%sPassword is not initialized. " + "Please provide the password for " + "username %s:%s " + % (INPUT, + PREFIX, + username, + END)) + + while (not ca_file): + ca_file = raw_input("%s%sCa file is not initialized. " + "Please provide the ca file location " + "(%s):%s " + % (INPUT, + PREFIX, + CA_DEF, + END)) or CA_DEF + + while (not output_file): + output_file = raw_input("%s%sOutput file is not initialized. " + "Please provide the output file location " + "for the mapping var file (%s):%s " + % (INPUT, + PREFIX, + _OUTPUT_FILE, + END)) or _OUTPUT_FILE + self._validate_output_file_exists(output_file) + while (not ansible_play) or (not os.path.isfile(ansible_play)): + ansible_play = raw_input("%s%sAnsible play '%s' is not " + "initialized. Please provide the ansible " + "play to generate the mapping var file " + "(%s):%s " + % (INPUT, + PREFIX, + ansible_play, + PLAY_DEF, + END)) or PLAY_DEF + return (site, username, password, ca_file, output_file, ansible_play) + + +class DefaultOption(dict): + + def __init__(self, config, section, **kv): + self._config = config + self._section = section + dict.__init__(self, **kv) + + def items(self): + _items = [] + for option in self: + if not self._config.has_option(self._section, option): + _items.append((option, self[option])) + else: + value_in_config = self._config.get(self._section, option) + _items.append((option, value_in_config)) + return _items + + +if __name__ == "__main__": + GenerateMappingFile().run('dr.conf', '/var/log/ovirt-dr/ovirt-dr.log') diff --git a/files/generate_vars_test.py b/files/generate_vars_test.py new file mode 100755 index 0000000..18dbc85 --- /dev/null +++ b/files/generate_vars_test.py @@ -0,0 +1,37 @@ +import subprocess +import pexpect +import os + +from contextlib import contextmanager + +import pytest + + +@contextmanager +def generator(tmpdir): + env = dict(os.environ) + env["PYTHONUNBUFFERED"] = "x" + env["GENERATE_VARS_CONF_DIR"] = str(tmpdir) + env["GENERATE_VARS_OUT_DIR"] = str(tmpdir) + gen = pexpect.spawn('./generate-vars', env=env) + try: + yield gen + finally: + gen.terminate(force=True) + + +INITIAL_CONF = """ +[generate_vars] +""" + + +def test_initial_conf(tmpdir): + conf = tmpdir.join("dr.conf") + conf.write(INITIAL_CONF) + with generator(tmpdir) as g: + # TODO: Use regex + g.expect('override') + # Add dry run + g.sendline('y') + # "/tmp/dr_ovirt-ansible/mapping_vars.yml" + assert os.path.exists("/tmp/dr_ovirt-ansible/mapping_vars.yml") diff --git a/files/ovirt-dr b/files/ovirt-dr new file mode 100755 index 0000000..fa3329b --- /dev/null +++ b/files/ovirt-dr @@ -0,0 +1,84 @@ +#!/usr/bin/python +import fail_back +import fail_over +import errno +import os +import sys +import generate_vars +import getopt +import validator + +VALIDATE = 'validate' +GENERATE = 'generate' +FAILOVER = 'failover' +FAILBACK = 'failback' +# TODO: Use build.sh for those files +DIR = '/var/log/ovirt-dr' +LOG_FILE = DIR + '/ovirt-dr.log' +DEF_CONF_FILE = 'dr.conf' + + +def main(argv): + action, conf_file = _init_vars(argv) + while not os.path.isfile(conf_file): + conf_file = raw_input( + "Conf file '" + conf_file + "' does not exist." + " Please provide the configuration file location: ") + if action == 'validate': + validator.ValidateMappingFile().run(conf_file, LOG_FILE) + elif action == 'generate': + generate_vars.GenerateMappingFile().run(conf_file, LOG_FILE) + elif action == 'failover': + fail_over.FailOver().run(conf_file, LOG_FILE) + elif action == 'failback': + fail_back.FailBack().run(conf_file, LOG_FILE) + else: + print "\tError: action '%s' is not defined" % action + help_log() + + +def _init_vars(argv): + conf_file = DEF_CONF_FILE + if len(argv) == 0: + print("ovirt-dr: missing action operand\n" + "Try 'ovirt-dr --help' for more information.") + sys.exit(2) + action = argv[0] + try: + opts, args = \ + getopt.getopt(argv, "f:", ["file="]) + except getopt.GetoptError: + help_log() + sys.exit(2) + + if not os.path.exists(DIR): + print("Create dir file '%s'" % DIR) + os.makedirs(DIR) + + for opt, arg in opts: + if opt in ("-f", "--filename"): + conf_file = arg + return (action, conf_file) + + +def help_log(): + print( + ''' + \tusage: ovirt-dr <%s/%s/%s/%s> [-f ]\n + \tHere is a description of the following actions:\n + \t\t%s\tGenerate the mapping var file based on primary setup + \t\t%s\tValidate the var file mapping + \t\t%s\tStart a failover process to the target setup + \t\t%s\tStart a failback process to the source setup + ''' % (GENERATE, + VALIDATE, + FAILOVER, + FAILBACK, + GENERATE, + VALIDATE, + FAILOVER, + FAILBACK)) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/files/validator.py b/files/validator.py new file mode 100755 index 0000000..7ff5e57 --- /dev/null +++ b/files/validator.py @@ -0,0 +1,779 @@ +#!/usr/bin/python +from ansible_vault import Vault +from bcolors import bcolors +import collections +try: + import configparser +except ImportError: + import ConfigParser as configparser +import os.path +import ovirtsdk4 as sdk +import ovirtsdk4.types as types +import shlex +from subprocess import call +import sys +import yaml + +INFO = bcolors.OKGREEN +INPUT = bcolors.OKGREEN +WARN = bcolors.WARNING +FAIL = bcolors.FAIL +END = bcolors.ENDC +PREFIX = "[Validate Mapping File] " + + +class ValidateMappingFile(): + + def_var_file = "/var/lib/ovirt-ansible-disaster-" \ + "recovery/mapping_vars.yml" + def_vault = "/var/lib/ovirt-ansible-disaster-" \ + "recovery/passwords.yml" + var_file = "" + vault = "" + cluster_map = 'dr_cluster_mappings' + domain_map = 'dr_import_storages' + role_map = 'dr_role_mappings' + aff_group_map = 'dr_affinity_group_mappings' + aff_label_map = 'dr_affinity_label_mappings' + network_map = 'dr_network_mappings' + + def run(self, conf_file, log_file): + print("%s%sValidate variable mapping file " + "for oVirt ansible disaster recovery%s" + % (INFO, + PREFIX, + END)) + self._set_dr_conf_variables(conf_file) + print("%s%sVar File: '%s'%s" + % (INFO, + PREFIX, + self.var_file, + END)) + while not os.path.isfile(self.var_file): + self.var_file = raw_input( + "%s%sVar file '%s' does not exists. " + "Please provide the location of the var file:%s " % + (FAIL, PREFIX, self.var_file, END)) + + python_vars = self._read_var_file() + vault_password = raw_input( + "%s%sPlease provide vault password for file '%s' " + "(For plain file, please press enter):%s " % + (INPUT, + PREFIX, + self.vault_file, + END)) + + if (not self._validate_lists_in_mapping_file(python_vars) or + not self._validate_duplicate_keys(python_vars) or not + self._entity_validator(python_vars, vault_password)): + self._print_finish_error() + exit() + + if (not self._validate_hosted_engine(python_vars)): + self._print_finish_error() + exit() + self._print_finish_success() + + def _validate_lists_in_mapping_file(self, mapping_vars): + return self._is_list(mapping_vars, self.cluster_map) and self._is_list( + mapping_vars, self.domain_map) and self._is_list( + mapping_vars, self.role_map) and self._is_list( + mapping_vars, self.aff_group_map) and self._is_list( + mapping_vars, self.aff_label_map) and self._is_list( + mapping_vars, self.network_map) + + def _is_list(self, mapping_vars, mapping): + map_file = mapping_vars.get(mapping) + if not isinstance(map_file, list) and map_file is not None: + print("%s%s%s is not a list: '%s'." + " Please check your mapping file%s" + % (FAIL, + PREFIX, + mapping, + map_file, + END)) + return False + return True + + def _print_finish_error(self): + print("%s%sFailed to validate variable mapping file " + "for oVirt ansible disaster recovery%s" + % (FAIL, + PREFIX, + END)) + + def _print_finish_success(self): + print("%s%sFinished validation variable mapping file " + "for oVirt ansible disaster recovery%s" + % (INFO, + PREFIX, + END)) + + def _read_var_file(self): + with open(self.var_file, 'r') as info: + info_dict = yaml.load(info) + return info_dict + + def _set_dr_conf_variables(self, conf_file): + _SECTION = 'validate_vars' + _VAR_FILE = 'var_file' + _VAULT = 'vault' + + # Get default location of the yml var file. + settings = configparser.ConfigParser() + settings._interpolation = configparser.ExtendedInterpolation() + settings.read(conf_file) + if _SECTION not in settings.sections(): + settings.add_section(_SECTION) + if not settings.has_option(_SECTION, _VAR_FILE): + settings.set(_SECTION, _VAR_FILE, '') + var_file = settings.get(_SECTION, _VAR_FILE, + vars=DefaultOption(settings, + _SECTION, + site=self.def_var_file)) + + # If no default location exists, get the location from the user. + while not var_file: + var_file = raw_input("%s%sVar file is not initialized. " + "Please provide the location of the var file " + "(%s):%s " % (WARN, + PREFIX, + self.def_var_file, + END) or self.def_var_file) + + if not settings.has_option(_SECTION, _VAULT): + settings.set(_SECTION, _VAULT, '') + vault_file = settings.get( + _SECTION, + _VAULT, + vars=DefaultOption( + settings, + _SECTION, + site=self.def_vault)) + + # If no default location exists, get the location from the user. + while (not vault_file): + vault_file = raw_input( + "%s%sPasswords file is not initialized. " + "Please provide the location of the passwords file " + "(%s):%s " % + (WARN, PREFIX, self.def_vault, END) or self.def_vault) + self.var_file = var_file + self.vault_file = vault_file + + def _print_duplicate_keys(self, duplicates, keys): + ret_val = False + for key in keys: + if len(duplicates[key]) > 0: + print( + "%s%sFound the following duplicate keys " + "in %s: %s%s" % + (FAIL, + PREFIX, + key, + list(duplicates[key]), + END)) + ret_val = True + return ret_val + + def _entity_validator(self, python_vars, vault_password): + isValid = True + ovirt_setups = ConnectSDK( + python_vars, + self.vault_file, + vault_password) + isValid = ovirt_setups.validate_primary() and isValid + isValid = ovirt_setups.validate_secondary() and isValid + if isValid: + try: + primary_conn, second_conn = '', '' + primary_conn = ovirt_setups.connect_primary() + if primary_conn is None: + return False + isValid = self._validate_entities_in_setup( + primary_conn, 'primary', python_vars) and isValid + second_conn = ovirt_setups.connect_secondary() + if second_conn is None: + return False + isValid = self._validate_entities_in_setup( + second_conn, 'secondary', python_vars) and isValid + cluster_mapping = python_vars.get(self.cluster_map) + if isValid: + isValid = self._is_compatible_versions(primary_conn, + second_conn, + ovirt_setups, + cluster_mapping) + finally: + # Close the connections + if primary_conn: + primary_conn.close() + if second_conn: + second_conn.close() + + return isValid + + def _validate_entities_in_setup(self, conn, setup, python_vars): + isValid = True + dcs_service = conn.system_service().data_centers_service() + dcs_list = dcs_service.list() + clusters = [] + affinity_groups = set() + for dc in dcs_list: + dc_service = dcs_service.data_center_service(dc.id) + clusters_service = dc_service.clusters_service() + attached_clusters_list = clusters_service.list() + for cluster in attached_clusters_list: + clusters.append(cluster.name) + cluster_service = clusters_service.cluster_service(cluster.id) + affinity_groups.update( + self._fetch_affinity_groups(cluster_service)) + aff_labels = self._get_affinity_labels(conn) + aaa_domains = self._get_aaa_domains(conn) + # TODO: Remove once vnic prifle is validated. + networks = self._get_vnic_profile_mapping(conn) + isValid = self._validate_networks( + python_vars, + networks, + setup) and isValid + isValid = self._validate_entity_exists( + clusters, + python_vars, + self.cluster_map, + setup) and isValid + isValid = self._validate_entity_exists( + list(affinity_groups), + python_vars, + self.aff_group_map, + setup) and isValid + isValid = self._validate_entity_exists( + aff_labels, + python_vars, + self.aff_label_map, + setup) and isValid + return isValid + + def _fetch_affinity_groups(self, cluster_service): + affinity_groups = set() + affinity_groups_service = cluster_service.affinity_groups_service() + for affinity_group in affinity_groups_service.list(): + affinity_groups.add(affinity_group.name) + return list(affinity_groups) + + def _get_affinity_labels(self, conn): + affinity_labels = set() + affinity_labels_service = \ + conn.system_service().affinity_labels_service() + affinity_labels_list = affinity_labels_service.list() + for affinity_label in affinity_labels_list: + affinity_labels.add(affinity_label.name) + return list(affinity_labels) + + def _get_aaa_domains(self, conn): + domains = [] + domains_service = conn.system_service().domains_service() + domains_list = domains_service.list() + for domain in domains_list: + domains.append(domain.name) + return domains + + def _get_vnic_profile_mapping(self, conn): + networks = [] + vnic_profiles_service = conn.system_service().vnic_profiles_service() + vnic_profile_list = vnic_profiles_service.list() + for vnic_profile_item in vnic_profile_list: + mapped_network = {} + networks_list = conn.system_service().networks_service().list() + network_name = '' + for network_item in networks_list: + if network_item.id == vnic_profile_item.network.id: + network_name = network_item.name + dc_name = conn.system_service().data_centers_service(). \ + data_center_service(network_item.data_center.id). \ + get()._name + break + mapped_network['network_name'] = network_name + mapped_network['network_dc'] = dc_name + mapped_network['profile_name'] = vnic_profile_item.name + networks.append(mapped_network) + return networks + + def _key_setup(self, setup, key): + if setup == 'primary': + if key == 'dr_import_storages': + return 'dr_primary_name' + if key == 'dr_network_mappings': + return ['primary_profile_name', + 'primary_network_name', + 'primary_network_dc'] + return 'primary_name' + elif setup == 'secondary': + if key == 'dr_import_storages': + return 'dr_secondary_name' + if key == 'dr_network_mappings': + return ['secondary_profile_name', + 'secondary_network_name', + 'secondary_network_dc'] + return 'secondary_name' + + def _validate_networks(self, var_file, networks_setup, setup): + dups = self._get_network_dups(networks_setup) + _mappings = var_file.get(self.network_map) + keys = self._key_setup(setup, self.network_map) + for mapping in _mappings: + map_key = mapping[keys[0]] + "_" + mapping[keys[1]] + if (map_key in dups): + if keys[2] not in mapping: + print( + "%s%sVnic profile name '%s' and network name '%s'" + " are related to multiple data centers in the" + " %s setup. Please specify the data center name in" + " the mapping var file.%s" % + (FAIL, + PREFIX, + mapping[keys[0]], + mapping[keys[1]], + setup, + END)) + return False + # TODO: Add check whether the data center exists in the setup + print("%s%sFinished validation for 'dr_network_mappings' for " + "%s setup with success.%s" % + (INFO, + PREFIX, + setup, + END)) + return True + + def _get_network_dups(self, networks_setup): + attributes = [attr['profile_name'] + + "_" + + attr['network_name'] for attr in networks_setup] + dups = [x for n, x in enumerate(attributes) if x in attributes[:n]] + return dups + + def _validate_entity_exists(self, _list, var_file, key, setup): + isValid = True + key_setup = self._key_setup(setup, key) + _mapping = var_file.get(key) + if _mapping is None: + return isValid + for x in _mapping: + if key_setup not in x.keys(): + print( + "%s%sdictionary key '%s' is not include in %s[%s].%s" % + (FAIL, + PREFIX, + key_setup, + key, + x.keys(), + END)) + isValid = False + if isValid and x[key_setup] not in _list: + print( + "%s%s%s entity '%s':'%s' does not exist in the " + "setup.\n%sThe entities which exists in the setup " + "are: %s.%s" % + (FAIL, + PREFIX, + key, + key_setup, + x[key_setup], + PREFIX, + _list, + END)) + isValid = False + if isValid: + print( + "%s%sFinished validation for '%s' for key name " + "'%s' with success.%s" % + (INFO, + PREFIX, + key, + key_setup, + END)) + return isValid + + def _validate_hosted_engine(self, var_file): + domains = var_file[self.domain_map] + hosted = 'hosted_storage' + for domain in domains: + primary = domain['dr_primary_name'] + secondary = domain['dr_secondary_name'] + if (primary == hosted or secondary == hosted): + print("%s%sHosted storage domains are not supported.%s" + % (FAIL, + PREFIX, + END)) + return False + return True + + def _validate_duplicate_keys(self, var_file): + isValid = True + clusters = 'clusters' + domains = 'domains' + roles = 'roles' + aff_group = 'aff_groups' + aff_label = 'aff_labels' + network = 'network' + key1 = 'primary_name' + key2 = 'secondary_name' + + duplicates = self._get_dups( + var_file, [ + [clusters, self.cluster_map, key1, key2], + [domains, self.domain_map, 'dr_primary_name', + 'dr_secondary_name'], + [roles, self.role_map, key1, key2], + [aff_group, self.aff_group_map, key1, key2], + [aff_label, self.aff_label_map, key1, key2]]) + duplicates[network] = self._get_dup_network(var_file) + isValid = (not self._print_duplicate_keys( + duplicates, [clusters, domains, roles, aff_group, + aff_label, network])) and isValid + return isValid + + def _is_compatible_versions(self, + primary_conn, + second_conn, + var_file, + cluster_mapping): + """ Validate cluster versions """ + service_primary = primary_conn.system_service().clusters_service() + service_sec = second_conn.system_service().clusters_service() + for cluster_map in cluster_mapping: + search_prime = "name=%s" % cluster_map['primary_name'] + search_sec = "name=%s" % cluster_map['secondary_name'] + cluster_prime = service_primary.list(search=search_prime)[0] + cluster_sec = service_sec.list(search=search_sec)[0] + prime_ver = cluster_prime.version + sec_ver = cluster_sec.version + if (prime_ver.major != sec_ver.major or + prime_ver.minor != sec_ver.minor): + print("%s%sClusters has incompatible versions. " + "primary setup ('%s' %s.%s) not equal to " + "secondary setup ('%s' %s.%s)%s" + % (FAIL, + PREFIX, + cluster_prime.name, + prime_ver.major, + prime_ver.minor, + cluster_sec.name, + sec_ver.major, + sec_ver.minor, + END)) + return False + return True + + def _get_dups(self, var_file, mappings): + duplicates = {} + for mapping in mappings: + _return_set = set() + _mapping = var_file.get(mapping[1]) + if _mapping is None or len(_mapping) < 1: + print("%s%smapping %s is empty in var file%s" + % (WARN, + PREFIX, + mapping[1], + END)) + duplicates[mapping[0]] = _return_set + continue + _primary = set() + _second = set() + _return_set.update( + set(x[mapping[2]] + for x in _mapping + if x[mapping[2]] + in _primary or _primary.add(x[mapping[2]]))) + _return_set.update( + set(x[mapping[3]] + for x in _mapping + if x[mapping[3]] + in _second or _second.add(x[mapping[3]]))) + duplicates[mapping[0]] = _return_set + return duplicates + + def _get_dup_network(self, var_file): + _return_set = set() + # TODO: Add data center also + _mapping = var_file.get(self.network_map) + if _mapping is None or len(_mapping) < 1: + print("%s%sNetwork has not been initialized in var file%s" + % (WARN, + PREFIX, + END)) + return _return_set + + # Check for profile + network name duplicates in primary + _primary1 = set() + key1_a = 'primary_profile_name' + key1_b = 'primary_network_name' + for x in _mapping: + if (x[key1_a] is None or x[key1_b] is None): + print("%s%sNetwork '%s' is not initialized in map %s %s%s" + % (FAIL, + PREFIX, + x, + x[key1_a], + x[key1_b], + END)) + exit() + map_key = x[key1_a] + "_" + x[key1_b] + if map_key in _primary1: + _return_set.add(map_key) + else: + _primary1.add(map_key) + + # Check for profile + network name duplicates in secondary + _second1 = set() + val1_a = 'secondary_profile_name' + val1_b = 'secondary_network_name' + for x in _mapping: + if (x[val1_a] is None or x[val1_b] is None): + print("%s%sThe following network mapping is not " + "initialized in var file mapping:\n" + " %s:'%s'\n %s:'%s'%s" + % (FAIL, + PREFIX, + val1_a, + x[val1_a], + val1_b, + x[val1_b], + END)) + exit() + map_key = x[val1_a] + "_" + x[val1_b] + if map_key in _second1: + _return_set.add(map_key) + else: + _second1.add(map_key) + + # TODO: Once vnic profile will be validated, delete: + # Check for duplicates in primary_profile_id + # _primary2 = set() + # key = 'primary_profile_id' + # _return_set.update(set(x[key] + # for x in + # _mapping if x[key] + # in _primary2 or _primary2.add(x[key]))) + # + # # Check for duplicates in secondary_profile_id + # _second2 = set() + # val = 'secondary_profile_id' + # _return_set.update(set(x[val] + # for x in + # _mapping if x[val] + # in _second2 or _second2.add(x[val]))) + return _return_set + + +class DefaultOption(dict): + + def __init__(self, config, section, **kv): + self._config = config + self._section = section + dict.__init__(self, **kv) + + def items(self): + _items = [] + for option in self: + if not self._config.has_option(self._section, option): + _items.append((option, self[option])) + else: + value_in_config = self._config.get(self._section, option) + _items.append((option, value_in_config)) + return _items + + +class ConnectSDK: + primary_url, primary_user, primary_ca, primary_password = '', '', '', '' + second_url, second_user, second_ca, second_password = '', '', '', '' + prefix = '' + error_msg = "%s%s The '%s' field in the %s setup is not " \ + "initialized in var file mapping.%s" + + def __init__(self, var_file, pass_file, vault_password): + """ + --- + dr_sites_primary_url: http://xxx.xx.xx.xxx:8080/ovirt-engine/api + dr_sites_primary_username: admin@internal + dr_sites_primary_ca_file: /etc/pki/ovirt-engine/ca.pem + + # Please fill in the following properties for the secondary site: + dr_sites_secondary_url: http://yyy.yy.yy.yyy:8080/ovirt-engine/api + dr_sites_secondary_username: admin@internal + dr_sites_secondary_ca_file: /etc/pki/ovirt-engine_secondary/ca.pem + """ + self.primary_url = var_file.get('dr_sites_primary_url') + self.primary_user = var_file.get('dr_sites_primary_username') + self.primary_ca = var_file.get('dr_sites_primary_ca_file') + self.second_url = var_file.get('dr_sites_secondary_url') + self.second_user = var_file.get('dr_sites_secondary_username') + self.second_ca = var_file.get('dr_sites_secondary_ca_file') + + if (vault_password != ''): + vault = Vault(vault_password) + try: + passwords = vault.load(open(pass_file).read()) + self.primary_password = passwords['dr_sites_primary_password'] + self.second_password = passwords['dr_sites_secondary_password'] + except BaseException: + try: + print("%s%sCan not read passwords from vault." + " Will try to read as plain file.%s" + % (WARN, + PREFIX, + END)) + self._plain_read(pass_file) + except BaseException: + print("%s%sCan not read passwords from file%s" + % (FAIL, + PREFIX, + END)) + else: + try: + self._plain_read(pass_file) + except BaseException: + print("%s%sCan not read passwords from file%s" + % (FAIL, + PREFIX, + END)) + + def _plain_read(self, pass_file): + with open(pass_file) as file: + passwords = file.read() + info_dict = yaml.load(passwords) + self.primary_password = \ + info_dict['dr_sites_primary_password'] + self.second_password = \ + info_dict['dr_sites_secondary_password'] + + def validate_primary(self): + isValid = True + if self.primary_url is None: + print(self.error_msg % ( + FAIL, + PREFIX, + "url", + "primary", + END)) + isValid = False + if self.primary_user is None: + print(self.error_msg % ( + FAIL, + PREFIX, + "username", + "primary", + END)) + isValid = False + if self.primary_password is None: + print(self.error_msg % ( + FAIL, + PREFIX, + "password", + "primary", + END)) + isValid = False + if self.primary_ca is None: + print(self.error_msg % ( + FAIL, + PREFIX, + "ca", + "primary", + END)) + isValid = False + return isValid + + def validate_secondary(self): + isValid = True + if self.second_url is None: + print(self.error_msg % ( + FAIL, + PREFIX, + "url", + "secondary", + END)) + isValid = False + if self.second_user is None: + print(self.error_msg % ( + FAIL, + PREFIX, + "username", + "secondary", + END)) + isValid = False + if self.second_password is None: + print(self.error_msg % ( + FAIL, + PREFIX, + "password", + "secondary", + END)) + isValid = False + if self.second_ca is None: + print(self.error_msg % ( + FAIL, + PREFIX, + "ca", + "secondary", + END)) + isValid = False + return isValid + + def _validate_connection(self, + url, + username, + password, + ca): + conn = None + try: + conn = self._connect_sdk(url, + username, + password, + ca) + dcs_service = conn.system_service().data_centers_service() + dcs_service.list() + except Exception: + print( + "%s%sConnection to setup has failed." + " Please check your cradentials: " + "\n%s URL: %s" + "\n%s USER: %s" + "\n%s CA file: %s%s" % + (FAIL, + PREFIX, + PREFIX, + url, + PREFIX, + username, + PREFIX, + ca, + END)) + if conn: + conn.close() + return None + return conn + + def connect_primary(self): + return self._validate_connection(self.primary_url, + self.primary_user, + self.primary_password, + self.primary_ca) + + def connect_secondary(self): + return self._validate_connection(self.second_url, + self.second_user, + self.second_password, + self.second_ca) + + def _connect_sdk(self, url, username, password, ca): + connection = sdk.Connection( + url=url, + username=username, + password=password, + ca_file=ca, + ) + return connection + + +if __name__ == "__main__": + ValidateMappingFile().run('dr.conf', '/var/log/ovirt-dr/ovirt-dr.log')