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')