diff options
Diffstat (limited to 'utils')
-rw-r--r-- | utils/docs/config.md | 8 | ||||
-rw-r--r-- | utils/setup.py | 2 | ||||
-rw-r--r-- | utils/src/ooinstall/cli_installer.py | 273 | ||||
-rw-r--r-- | utils/src/ooinstall/oo_config.py | 66 | ||||
-rw-r--r-- | utils/src/ooinstall/openshift_ansible.py (renamed from utils/src/ooinstall/install_transactions.py) | 67 | ||||
-rw-r--r-- | utils/src/ooinstall/variants.py | 7 | ||||
-rw-r--r-- | utils/test/cli_installer_tests.py | 70 | ||||
-rw-r--r-- | utils/test/oo_config_tests.py | 64 |
8 files changed, 403 insertions, 154 deletions
diff --git a/utils/docs/config.md b/utils/docs/config.md index 9399409dd..2729f8d37 100644 --- a/utils/docs/config.md +++ b/utils/docs/config.md @@ -7,6 +7,7 @@ The default location this config file will be written to ~/.config/openshift/ins ## Example ``` +version: v1 variant: openshift-enterprise variant_version: 3.0 ansible_ssh_user: root @@ -18,20 +19,27 @@ hosts: master: true node: true containerized: true + connect_to: 24.222.0.1 - ip: 10.0.0.2 hostname: node1-private.example.com public_ip: 24.222.0.2 public_hostname: node1.example.com node: true + connect_to: 10.0.0.2 - ip: 10.0.0.3 hostname: node2-private.example.com public_ip: 24.222.0.3 public_hostname: node2.example.com node: true + connect_to: 10.0.0.3 ``` ## Primary Settings +### version + +Indicates the version of configuration this file was written with. Current implementation is v1. + ### variant The OpenShift variant to install. Currently valid options are: diff --git a/utils/setup.py b/utils/setup.py index 6e2fdd9c0..eac1b4b2e 100644 --- a/utils/setup.py +++ b/utils/setup.py @@ -79,7 +79,7 @@ setup( # pip to create the appropriate form of executable for the target platform. entry_points={ 'console_scripts': [ - 'oo-install=ooinstall.cli_installer:main', + 'oo-install=ooinstall.cli_installer:cli', ], }, ) diff --git a/utils/src/ooinstall/cli_installer.py b/utils/src/ooinstall/cli_installer.py index c2ae00bd1..8c2421183 100644 --- a/utils/src/ooinstall/cli_installer.py +++ b/utils/src/ooinstall/cli_installer.py @@ -6,12 +6,13 @@ import click import os import re import sys -from ooinstall import install_transactions +from ooinstall import openshift_ansible from ooinstall import OOConfig from ooinstall.oo_config import Host from ooinstall.variants import find_variant, get_variant_version_combos DEFAULT_ANSIBLE_CONFIG = '/usr/share/atomic-openshift-util/ansible.cfg' +DEFAULT_PLAYBOOK_DIR = '/usr/share/ansible/openshift-ansible/' def validate_ansible_dir(path): if not path: @@ -94,24 +95,19 @@ The OpenShift Node provides the runtime environments for containers. It will host the required services to be managed by the Master. http://docs.openshift.com/enterprise/latest/architecture/infrastructure_components/kubernetes_infrastructure.html#master -http://docs.openshift.com/enterprise/3.0/architecture/infrastructure_components/kubernetes_infrastructure.html#node +http://docs.openshift.com/enterprise/latest/architecture/infrastructure_components/kubernetes_infrastructure.html#node """ click.echo(message) hosts = [] more_hosts = True - ip_regex = re.compile(r'^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$') - while more_hosts: host_props = {} hostname_or_ip = click.prompt('Enter hostname or IP address:', default='', value_proc=validate_prompt_hostname) - if ip_regex.match(hostname_or_ip): - host_props['ip'] = hostname_or_ip - else: - host_props['hostname'] = hostname_or_ip + host_props['connect_to'] = hostname_or_ip host_props['master'] = click.confirm('Will this host be an OpenShift Master?') host_props['node'] = True @@ -149,7 +145,7 @@ Plese confirm that they are correct before moving forward. notes = """ Format: -IP,public IP,hostname,public hostname +connect_to,IP,public IP,hostname,public hostname Notes: * The installation host is the hostname from the installer's perspective. @@ -167,16 +163,15 @@ Notes: default_facts_lines = [] default_facts = {} - validated_facts = {} for h in hosts: - default_facts[h] = {} - h.ip = callback_facts[str(h)]["common"]["ip"] - h.public_ip = callback_facts[str(h)]["common"]["public_ip"] - h.hostname = callback_facts[str(h)]["common"]["hostname"] - h.public_hostname = callback_facts[str(h)]["common"]["public_hostname"] - - validated_facts[h] = {} - default_facts_lines.append(",".join([h.ip, + default_facts[h.connect_to] = {} + h.ip = callback_facts[h.connect_to]["common"]["ip"] + h.public_ip = callback_facts[h.connect_to]["common"]["public_ip"] + h.hostname = callback_facts[h.connect_to]["common"]["hostname"] + h.public_hostname = callback_facts[h.connect_to]["common"]["public_hostname"] + + default_facts_lines.append(",".join([h.connect_to, + h.ip, h.public_ip, h.hostname, h.public_hostname])) @@ -190,7 +185,7 @@ Notes: facts_confirmed = click.confirm("Do the above facts look correct?") if not facts_confirmed: message = """ -Edit %s with the desired values and rerun oo-install with --unattended . +Edit %s with the desired values and rerun atomic-openshift-installer with --unattended . """ % oo_cfg.config_path click.echo(message) # Make sure we actually write out the config file. @@ -315,10 +310,10 @@ Add new nodes here def get_installed_hosts(hosts, callback_facts): installed_hosts = [] for host in hosts: - if(host.name in callback_facts.keys() - and 'common' in callback_facts[host.name].keys() - and callback_facts[host.name]['common'].get('version', '') - and callback_facts[host.name]['common'].get('version', '') != 'None'): + if(host.connect_to in callback_facts.keys() + and 'common' in callback_facts[host.connect_to].keys() + and callback_facts[host.connect_to]['common'].get('version', '') + and callback_facts[host.connect_to]['common'].get('version', '') != 'None'): installed_hosts.append(host) return installed_hosts @@ -330,7 +325,22 @@ def get_hosts_to_run_on(oo_cfg, callback_facts, unattended, force): # Check if master or nodes already have something installed installed_hosts = get_installed_hosts(oo_cfg.hosts, callback_facts) if len(installed_hosts) > 0: - # present a message listing already installed hosts + click.echo('Installed environment detected.') + # This check has to happen before we start removing hosts later in this method + if not force: + if not unattended: + click.echo('By default the installer only adds new nodes to an installed environment.') + response = click.prompt('Do you want to (1) only add additional nodes or ' \ + '(2) perform a clean install?', type=int) + # TODO: this should be reworked with error handling. + # Click can certainly do this for us. + # This should be refactored as soon as we add a 3rd option. + if response == 1: + force = False + if response == 2: + force = True + + # present a message listing already installed hosts and remove hosts if needed for host in installed_hosts: if host.master: click.echo("{} is already an OpenShift Master".format(host)) @@ -338,100 +348,193 @@ def get_hosts_to_run_on(oo_cfg, callback_facts, unattended, force): # new nodes. elif host.node: click.echo("{} is already an OpenShift Node".format(host)) - hosts_to_run_on.remove(host) - # for unattended either continue if they force install or exit if they didn't - if unattended: - if not force: - click.echo('Installed environment detected and no additional nodes specified: ' \ - 'aborting. If you want a fresh install, use --force') - sys.exit(1) - # for attended ask the user what to do + # force is only used for reinstalls so we don't want to remove + # anything. + if not force: + hosts_to_run_on.remove(host) + + # Handle the cases where we know about uninstalled systems + new_hosts = set(hosts_to_run_on) - set(installed_hosts) + if len(new_hosts) > 0: + for new_host in new_hosts: + click.echo("{} is currently uninstalled".format(new_host)) + + # Fall through + click.echo('Adding additional nodes...') else: - click.echo('Installed environment detected and no additional nodes specified. ') - response = click.prompt('Do you want to (1) add more nodes or ' \ - '(2) perform a clean install?', type=int) - if response == 1: # add more nodes - new_nodes = collect_new_nodes() - - hosts_to_run_on.extend(new_nodes) - oo_cfg.hosts.extend(new_nodes) - - install_transactions.set_config(oo_cfg) - callback_facts, error = install_transactions.default_facts(oo_cfg.hosts) - if error: - click.echo("There was a problem fetching the required information. " \ - "See {} for details.".format(oo_cfg.settings['ansible_log_path'])) + if unattended: + if not force: + click.echo('Installed environment detected and no additional nodes specified: ' \ + 'aborting. If you want a fresh install, use ' \ + '`atomic-openshift-installer install --force`') sys.exit(1) else: - pass # proceeding as normal should do a clean install + if not force: + new_nodes = collect_new_nodes() + + hosts_to_run_on.extend(new_nodes) + oo_cfg.hosts.extend(new_nodes) + + openshift_ansible.set_config(oo_cfg) + click.echo('Gathering information from hosts...') + callback_facts, error = openshift_ansible.default_facts(oo_cfg.hosts) + if error: + click.echo("There was a problem fetching the required information. " \ + "See {} for details.".format(oo_cfg.settings['ansible_log_path'])) + sys.exit(1) + else: + pass # proceeding as normal should do a clean install return hosts_to_run_on, callback_facts -@click.command() + +@click.group() +@click.pass_context +@click.option('--unattended', '-u', is_flag=True, default=False) @click.option('--configuration', '-c', - type=click.Path(file_okay=True, - dir_okay=False, - writable=True, - readable=True), - default=None) + type=click.Path(file_okay=True, + dir_okay=False, + writable=True, + readable=True), + default=None) @click.option('--ansible-playbook-directory', '-a', type=click.Path(exists=True, file_okay=False, dir_okay=True, - writable=True, readable=True), # callback=validate_ansible_dir, + default=DEFAULT_PLAYBOOK_DIR, envvar='OO_ANSIBLE_PLAYBOOK_DIRECTORY') @click.option('--ansible-config', - type=click.Path(file_okay=True, - dir_okay=False, - writable=True, - readable=True), - default=None) + type=click.Path(file_okay=True, + dir_okay=False, + writable=True, + readable=True), + default=None) @click.option('--ansible-log-path', - type=click.Path(file_okay=True, - dir_okay=False, - writable=True, - readable=True), - default="/tmp/ansible.log") -@click.option('--unattended', '-u', is_flag=True, default=False) -@click.option('--force', '-f', is_flag=True, default=False) + type=click.Path(file_okay=True, + dir_okay=False, + writable=True, + readable=True), + default="/tmp/ansible.log") #pylint: disable=too-many-arguments # Main CLI entrypoint, not much we can do about too many arguments. -def main(configuration, ansible_playbook_directory, ansible_config, ansible_log_path, unattended, force): - oo_cfg = OOConfig(configuration) +def cli(ctx, unattended, configuration, ansible_playbook_directory, ansible_config, ansible_log_path): + """ + The main click CLI module. Responsible for handling most common CLI options, + assigning any defaults and adding to the context for the sub-commands. + """ + ctx.obj = {} + ctx.obj['unattended'] = unattended + ctx.obj['configuration'] = configuration + ctx.obj['ansible_config'] = ansible_config + ctx.obj['ansible_log_path'] = ansible_log_path + + oo_cfg = OOConfig(ctx.obj['configuration']) + # If no playbook dir on the CLI, check the config: if not ansible_playbook_directory: ansible_playbook_directory = oo_cfg.settings.get('ansible_playbook_directory', '') + # If still no playbook dir, check for the default location: + if not ansible_playbook_directory and os.path.exists(DEFAULT_PLAYBOOK_DIR): + ansible_playbook_directory = DEFAULT_PLAYBOOK_DIR + validate_ansible_dir(ansible_playbook_directory) + oo_cfg.settings['ansible_playbook_directory'] = ansible_playbook_directory + oo_cfg.ansible_playbook_directory = ansible_playbook_directory + ctx.obj['ansible_playbook_directory'] = ansible_playbook_directory - if ansible_config: - oo_cfg.settings['ansible_config'] = ansible_config + if ctx.obj['ansible_config']: + oo_cfg.settings['ansible_config'] = ctx.obj['ansible_config'] elif os.path.exists(DEFAULT_ANSIBLE_CONFIG): # If we're installed by RPM this file should exist and we can use it as our default: oo_cfg.settings['ansible_config'] = DEFAULT_ANSIBLE_CONFIG - validate_ansible_dir(ansible_playbook_directory) - oo_cfg.settings['ansible_playbook_directory'] = ansible_playbook_directory - oo_cfg.ansible_playbook_directory = ansible_playbook_directory + oo_cfg.settings['ansible_log_path'] = ctx.obj['ansible_log_path'] + + ctx.obj['oo_cfg'] = oo_cfg + openshift_ansible.set_config(oo_cfg) - oo_cfg.settings['ansible_log_path'] = ansible_log_path - install_transactions.set_config(oo_cfg) - if unattended: +@click.command() +@click.pass_context +def uninstall(ctx): + oo_cfg = ctx.obj['oo_cfg'] + + if len(oo_cfg.hosts) == 0: + click.echo("No hosts defined in: %s" % oo_cfg['configuration']) + sys.exit(1) + + click.echo("OpenShift will be uninstalled from the following hosts:\n") + if not ctx.obj['unattended']: + # Prompt interactively to confirm: + for host in oo_cfg.hosts: + click.echo(" * %s" % host.connect_to) + proceed = click.confirm("\nDo you wish to proceed?") + if not proceed: + click.echo("Uninstall cancelled.") + sys.exit(0) + + openshift_ansible.run_uninstall_playbook() + + +@click.command() +@click.pass_context +def upgrade(ctx): + oo_cfg = ctx.obj['oo_cfg'] + + if len(oo_cfg.hosts) == 0: + click.echo("No hosts defined in: %s" % oo_cfg['configuration']) + sys.exit(1) + + # Update config to reflect the version we're targetting, we'll write + # to disk once ansible completes successfully, not before. + old_variant = oo_cfg.settings['variant'] + old_version = oo_cfg.settings['variant_version'] + if oo_cfg.settings['variant'] == 'enterprise': + oo_cfg.settings['variant'] = 'openshift-enterprise' + version = find_variant(oo_cfg.settings['variant'])[1] + oo_cfg.settings['variant_version'] = version.name + click.echo("Openshift will be upgraded from %s %s to %s %s on the following hosts:\n" % ( + old_variant, old_version, oo_cfg.settings['variant'], + oo_cfg.settings['variant_version'])) + for host in oo_cfg.hosts: + click.echo(" * %s" % host.connect_to) + + if not ctx.obj['unattended']: + # Prompt interactively to confirm: + proceed = click.confirm("\nDo you wish to proceed?") + if not proceed: + click.echo("Upgrade cancelled.") + sys.exit(0) + + retcode = openshift_ansible.run_upgrade_playbook() + if retcode > 0: + click.echo("Errors encountered during upgrade, please check %s." % + oo_cfg.settings['ansible_log_path']) + else: + click.echo("Upgrade completed! Rebooting all hosts is recommended.") + + +@click.command() +@click.option('--force', '-f', is_flag=True, default=False) +@click.pass_context +def install(ctx, force): + oo_cfg = ctx.obj['oo_cfg'] + + if ctx.obj['unattended']: error_if_missing_info(oo_cfg) else: oo_cfg = get_missing_info_from_user(oo_cfg) click.echo('Gathering information from hosts...') - callback_facts, error = install_transactions.default_facts(oo_cfg.hosts) + callback_facts, error = openshift_ansible.default_facts(oo_cfg.hosts) if error: click.echo("There was a problem fetching the required information. " \ "Please see {} for details.".format(oo_cfg.settings['ansible_log_path'])) sys.exit(1) - hosts_to_run_on, callback_facts = get_hosts_to_run_on(oo_cfg, callback_facts, unattended, force) - + hosts_to_run_on, callback_facts = get_hosts_to_run_on(oo_cfg, callback_facts, ctx.obj['unattended'], force) click.echo('Writing config to: %s' % oo_cfg.config_path) @@ -449,10 +552,10 @@ def main(configuration, ansible_playbook_directory, ansible_config, ansible_log_ message = """ If changes are needed to the values recorded by the installer please update {}. """.format(oo_cfg.config_path) - if not unattended: + if not ctx.obj['unattended']: confirm_continue(message) - error = install_transactions.run_main_playbook(oo_cfg.hosts, + error = openshift_ansible.run_main_playbook(oo_cfg.hosts, hosts_to_run_on) if error: # The bootstrap script will print out the log location. @@ -475,5 +578,11 @@ http://docs.openshift.com/enterprise/latest/admin_guide/overview.html click.echo(message) click.pause() +cli.add_command(install) +cli.add_command(upgrade) +cli.add_command(uninstall) + if __name__ == '__main__': - main() + # This is expected behaviour for context passing with click library: + # pylint: disable=unexpected-keyword-arg + cli(obj={}) diff --git a/utils/src/ooinstall/oo_config.py b/utils/src/ooinstall/oo_config.py index a2f53cf78..f35a8f51b 100644 --- a/utils/src/ooinstall/oo_config.py +++ b/utils/src/ooinstall/oo_config.py @@ -12,6 +12,7 @@ PERSIST_SETTINGS = [ 'ansible_log_path', 'variant', 'variant_version', + 'version', ] REQUIRED_FACTS = ['ip', 'public_ip', 'hostname', 'public_hostname'] @@ -34,6 +35,7 @@ class Host(object): self.hostname = kwargs.get('hostname', None) self.public_ip = kwargs.get('public_ip', None) self.public_hostname = kwargs.get('public_hostname', None) + self.connect_to = kwargs.get('connect_to', None) # Should this host run as an OpenShift master: self.master = kwargs.get('master', False) @@ -42,30 +44,25 @@ class Host(object): self.node = kwargs.get('node', False) self.containerized = kwargs.get('containerized', False) - if self.ip is None and self.hostname is None: - raise OOConfigInvalidHostError("You must specify either 'ip' or 'hostname'") + if self.connect_to is None: + raise OOConfigInvalidHostError("You must specify either and 'ip' " \ + "or 'hostname' to connect to.") if self.master is False and self.node is False: raise OOConfigInvalidHostError( "You must specify each host as either a master or a node.") - # Hosts can be specified with an ip, hostname, or both. However we need - # something authoritative we can connect to and refer to the host by. - # Preference given to the IP if specified as this is more specific. - # We know one must be set by this point. - self.name = self.ip if self.ip is not None else self.hostname - def __str__(self): - return self.name + return self.connect_to def __repr__(self): - return self.name + return self.connect_to def to_dict(self): """ Used when exporting to yaml. """ d = {} for prop in ['ip', 'hostname', 'public_ip', 'public_hostname', - 'master', 'node', 'containerized']: + 'master', 'node', 'containerized', 'connect_to']: # If the property is defined (not None or False), export it: if getattr(self, prop): d[prop] = getattr(self, prop) @@ -73,7 +70,6 @@ class Host(object): class OOConfig(object): - new_config = True default_dir = os.path.normpath( os.environ.get('XDG_CONFIG_HOME', os.environ['HOME'] + '/.config/') + '/openshift/') @@ -86,19 +82,22 @@ class OOConfig(object): self.config_path = os.path.normpath(self.default_dir + self.default_file) self.settings = {} - self.read_config() - self.set_defaults() + self._read_config() + self._set_defaults() - def read_config(self, is_new=False): + def _read_config(self): self.hosts = [] try: - new_settings = None if os.path.exists(self.config_path): cfgfile = open(self.config_path, 'r') - new_settings = yaml.safe_load(cfgfile.read()) + self.settings = yaml.safe_load(cfgfile.read()) cfgfile.close() - if new_settings: - self.settings = new_settings + + # Use the presence of a Description as an indicator this is + # a legacy config file: + if 'Description' in self.settings: + self._upgrade_legacy_config() + # Parse the hosts into DTO objects: if 'hosts' in self.settings: for host in self.settings['hosts']: @@ -114,9 +113,28 @@ class OOConfig(object): ferr.strerror)) except yaml.scanner.ScannerError: raise OOConfigFileError('Config file "{}" is not a valid YAML document'.format(self.config_path)) - self.new_config = is_new - def set_defaults(self): + def _upgrade_legacy_config(self): + new_hosts = [] + if 'validated_facts' in self.settings: + for key, value in self.settings['validated_facts'].iteritems(): + if 'masters' in self.settings and key in self.settings['masters']: + value['master'] = True + if 'nodes' in self.settings and key in self.settings['nodes']: + value['node'] = True + new_hosts.append(value) + self.settings['hosts'] = new_hosts + + remove_settings = ['validated_facts', 'Description', 'Name', + 'Subscription', 'Vendor', 'Version', 'masters', 'nodes'] + for s in remove_settings: + del self.settings[s] + + # A legacy config implies openshift-enterprise 3.0: + self.settings['variant'] = 'openshift-enterprise' + self.settings['variant_version'] = '3.0' + + def _set_defaults(self): if 'ansible_inventory_directory' not in self.settings: self.settings['ansible_inventory_directory'] = \ @@ -125,6 +143,8 @@ class OOConfig(object): os.makedirs(self.settings['ansible_inventory_directory']) if 'ansible_plugins_directory' not in self.settings: self.settings['ansible_plugins_directory'] = resource_filename(__name__, 'ansible_plugins') + if 'version' not in self.settings: + self.settings['version'] = 'v1' if 'ansible_callback_facts_yaml' not in self.settings: self.settings['ansible_callback_facts_yaml'] = '%s/callback_facts.yaml' % \ @@ -158,7 +178,7 @@ class OOConfig(object): if not getattr(host, required_fact): missing_facts.append(required_fact) if len(missing_facts) > 0: - result[host.name] = missing_facts + result[host.connect_to] = missing_facts return result def save_to_disk(self): @@ -190,6 +210,6 @@ class OOConfig(object): def get_host(self, name): for host in self.hosts: - if host.name == name: + if host.connect_to == name: return host return None diff --git a/utils/src/ooinstall/install_transactions.py b/utils/src/ooinstall/openshift_ansible.py index cef6662d7..d2399df5c 100644 --- a/utils/src/ooinstall/install_transactions.py +++ b/utils/src/ooinstall/openshift_ansible.py @@ -2,7 +2,9 @@ # repo. We will work on these over time. # pylint: disable=bad-continuation,missing-docstring,no-self-use,invalid-name,global-statement,global-variable-not-assigned +import socket import subprocess +import sys import os import yaml from ooinstall.variants import find_variant @@ -14,32 +16,33 @@ def set_config(cfg): CFG = cfg def generate_inventory(hosts): - print hosts global CFG + base_inventory_path = CFG.settings['ansible_inventory_path'] base_inventory = open(base_inventory_path, 'w') base_inventory.write('\n[OSEv3:children]\nmasters\nnodes\n') base_inventory.write('\n[OSEv3:vars]\n') base_inventory.write('ansible_ssh_user={}\n'.format(CFG.settings['ansible_ssh_user'])) if CFG.settings['ansible_ssh_user'] != 'root': - base_inventory.write('ansible_sudo=true\n') + base_inventory.write('ansible_become=true\n') # Find the correct deployment type for ansible: ver = find_variant(CFG.settings['variant'], version=CFG.settings.get('variant_version', None))[1] base_inventory.write('deployment_type={}\n'.format(ver.ansible_key)) - if 'OO_INSTALL_DEVEL_REGISTRY' in os.environ: - base_inventory.write('oreg_url=rcm-img-docker01.build.eng.bos.redhat.com:' - '5001/openshift3/ose-${component}:${version}\n') - if 'OO_INSTALL_PUDDLE_REPO_ENABLE' in os.environ: - base_inventory.write("openshift_additional_repos=[{'id': 'ose-devel', " + if 'OO_INSTALL_ADDITIONAL_REGISTRIES' in os.environ: + base_inventory.write('cli_docker_additional_registries={}\n' + .format(os.environ['OO_INSTALL_ADDITIONAL_REGISTRIES'])) + if 'OO_INSTALL_INSECURE_REGISTRIES' in os.environ: + base_inventory.write('cli_docker_insecure_registries={}\n' + .format(os.environ['OO_INSTALL_INSECURE_REGISTRIES'])) + if 'OO_INSTALL_PUDDLE_REPO' in os.environ: + # We have to double the '{' here for literals + base_inventory.write("openshift_additional_repos=[{{'id': 'ose-devel', " "'name': 'ose-devel', " - "'baseurl': 'http://buildvm-devops.usersys.redhat.com" - "/puddle/build/AtomicOpenShift/3.1/latest/RH7-RHAOS-3.1/$basearch/os', " - "'enabled': 1, 'gpgcheck': 0}]\n") - if 'OO_INSTALL_STAGE_REGISTRY' in os.environ: - base_inventory.write('oreg_url=registry.access.stage.redhat.com/openshift3/ose-${component}:${version}\n') + "'baseurl': '{}', " + "'enabled': 1, 'gpgcheck': 0}}]\n".format(os.environ['OO_INSTALL_PUDDLE_REPO'])) base_inventory.write('\n[masters]\n') masters = (host for host in hosts if host.master) @@ -61,6 +64,7 @@ def generate_inventory(hosts): def write_host(host, inventory, scheduleable=True): global CFG + facts = '' if host.ip: facts += ' openshift_ip={}'.format(host.ip) @@ -74,7 +78,17 @@ def write_host(host, inventory, scheduleable=True): # Technically only nodes will ever need this. if not scheduleable: facts += ' openshift_scheduleable=False' - inventory.write('{} {}\n'.format(host, facts)) + installer_host = socket.gethostname() + if host.hostname == installer_host or host.public_hostname == installer_host: + facts += ' ansible_connection=local' + if os.geteuid() != 0: + no_pwd_sudo = subprocess.call(['sudo', '-v', '-n']) + if no_pwd_sudo == 1: + print 'The atomic-openshift-installer requires sudo access without a password.' + sys.exit(1) + facts += ' ansible_become=true' + + inventory.write('{} {}\n'.format(host.connect_to, facts)) def load_system_facts(inventory_file, os_facts_path, env_vars): @@ -126,8 +140,35 @@ def run_main_playbook(hosts, hosts_to_run_on): facts_env['ANSIBLE_CONFIG'] = CFG.settings['ansible_config'] return run_ansible(main_playbook_path, inventory_file, facts_env) + def run_ansible(playbook, inventory, env_vars): return subprocess.call(['ansible-playbook', '--inventory-file={}'.format(inventory), playbook], env=env_vars) + + +def run_uninstall_playbook(): + playbook = os.path.join(CFG.settings['ansible_playbook_directory'], + 'playbooks/adhoc/uninstall.yml') + inventory_file = generate_inventory(CFG.hosts) + facts_env = os.environ.copy() + if 'ansible_log_path' in CFG.settings: + facts_env['ANSIBLE_LOG_PATH'] = CFG.settings['ansible_log_path'] + if 'ansible_config' in CFG.settings: + facts_env['ANSIBLE_CONFIG'] = CFG.settings['ansible_config'] + return run_ansible(playbook, inventory_file, facts_env) + + +def run_upgrade_playbook(): + playbook = os.path.join(CFG.settings['ansible_playbook_directory'], + 'playbooks/adhoc/upgrades/upgrade.yml') + # TODO: Upgrade inventory for upgrade? + inventory_file = generate_inventory(CFG.hosts) + facts_env = os.environ.copy() + if 'ansible_log_path' in CFG.settings: + facts_env['ANSIBLE_LOG_PATH'] = CFG.settings['ansible_log_path'] + if 'ansible_config' in CFG.settings: + facts_env['ANSIBLE_CONFIG'] = CFG.settings['ansible_config'] + return run_ansible(playbook, inventory_file, facts_env) + diff --git a/utils/src/ooinstall/variants.py b/utils/src/ooinstall/variants.py index ed98429fc..3bb61dddb 100644 --- a/utils/src/ooinstall/variants.py +++ b/utils/src/ooinstall/variants.py @@ -29,6 +29,9 @@ class Variant(object): self.versions = versions + def latest_version(self): + return self.versions[-1] + # WARNING: Keep the versions ordered, most recent last: OSE = Variant('openshift-enterprise', 'OpenShift Enterprise', @@ -38,7 +41,7 @@ OSE = Variant('openshift-enterprise', 'OpenShift Enterprise', ] ) -AEP = Variant('atomic-enterprise', 'Atomic OpenShift Enterprise', +AEP = Variant('atomic-enterprise', 'Atomic Enterprise Platform', [ Version('3.1', 'atomic-enterprise') ] @@ -58,7 +61,7 @@ def find_variant(name, version=None): for prod in SUPPORTED_VARIANTS: if prod.name == name: if version is None: - return (prod, prod.versions[-1]) + return (prod, prod.latest_version()) for v in prod.versions: if v.name == version: return (prod, v) diff --git a/utils/test/cli_installer_tests.py b/utils/test/cli_installer_tests.py index 076fe5dc9..b183f0acb 100644 --- a/utils/test/cli_installer_tests.py +++ b/utils/test/cli_installer_tests.py @@ -76,7 +76,7 @@ class OOCliFixture(OOInstallFixture): self.cli_args = ["-a", self.work_dir] def run_cli(self): - return self.runner.invoke(cli.main, self.cli_args) + return self.runner.invoke(cli.cli, self.cli_args) def assert_result(self, result, exit_code): if result.exception is not None or result.exit_code != exit_code: @@ -102,8 +102,8 @@ class UnattendedCliTests(OOCliFixture): OOCliFixture.setUp(self) self.cli_args.append("-u") - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_cfg_full_run(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_playbook_mock.return_value = 0 @@ -111,8 +111,8 @@ class UnattendedCliTests(OOCliFixture): config_file = self.write_config(os.path.join(self.work_dir, 'ooinstall.conf'), SAMPLE_CONFIG % 'openshift-enterprise') - self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.extend(["-c", config_file, "install"]) + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) load_facts_args = load_facts_mock.call_args[0] @@ -133,8 +133,8 @@ class UnattendedCliTests(OOCliFixture): self.assertEquals(3, len(hosts)) self.assertEquals(3, len(hosts_to_run_on)) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_inventory_write(self, load_facts_mock, run_playbook_mock): # Add an ssh user so we can verify it makes it to the inventory file: @@ -146,8 +146,8 @@ class UnattendedCliTests(OOCliFixture): config_file = self.write_config(os.path.join(self.work_dir, 'ooinstall.conf'), merged_config) - self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.extend(["-c", config_file, "install"]) + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) # Check the inventory file looks as we would expect: @@ -172,8 +172,8 @@ class UnattendedCliTests(OOCliFixture): self.assertTrue('openshift_hostname' in master_line) self.assertTrue('openshift_public_hostname' in master_line) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_variant_version_latest_assumed(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) @@ -182,8 +182,8 @@ class UnattendedCliTests(OOCliFixture): config_file = self.write_config(os.path.join(self.work_dir, 'ooinstall.conf'), SAMPLE_CONFIG % 'openshift-enterprise') - self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.extend(["-c", config_file, "install"]) + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) written_config = self._read_yaml(config_file) @@ -199,8 +199,8 @@ class UnattendedCliTests(OOCliFixture): self.assertEquals('openshift-enterprise', inventory.get('OSEv3:vars', 'deployment_type')) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_variant_version_preserved(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) @@ -211,8 +211,8 @@ class UnattendedCliTests(OOCliFixture): config_file = self.write_config(os.path.join(self.work_dir, 'ooinstall.conf'), config) - self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.extend(["-c", config_file, "install"]) + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) written_config = self._read_yaml(config_file) @@ -227,8 +227,8 @@ class UnattendedCliTests(OOCliFixture): self.assertEquals('enterprise', inventory.get('OSEv3:vars', 'deployment_type')) - @patch('ooinstall.install_transactions.run_ansible') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_ansible') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_no_ansible_config_specified(self, load_facts_mock, run_ansible_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_ansible_mock.return_value = 0 @@ -238,8 +238,8 @@ class UnattendedCliTests(OOCliFixture): self._ansible_config_test(load_facts_mock, run_ansible_mock, config, None, None) - @patch('ooinstall.install_transactions.run_ansible') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_ansible') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_ansible_config_specified_cli(self, load_facts_mock, run_ansible_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_ansible_mock.return_value = 0 @@ -250,8 +250,8 @@ class UnattendedCliTests(OOCliFixture): self._ansible_config_test(load_facts_mock, run_ansible_mock, config, ansible_config, ansible_config) - @patch('ooinstall.install_transactions.run_ansible') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_ansible') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_ansible_config_specified_in_installer_config(self, load_facts_mock, run_ansible_mock): @@ -282,7 +282,8 @@ class UnattendedCliTests(OOCliFixture): self.cli_args.extend(["-c", config_file]) if ansible_config_cli: self.cli_args.extend(["--ansible-config", ansible_config_cli]) - result = self.runner.invoke(cli.main, self.cli_args) + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args) self.assert_result(result, 0) # Test the env vars for facts playbook: @@ -388,8 +389,8 @@ class AttendedCliTests(OOCliFixture): self.assertTrue('public_ip' in h) self.assertTrue('public_hostname' in h) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_full_run(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_playbook_mock.return_value = 0 @@ -401,7 +402,8 @@ class AttendedCliTests(OOCliFixture): ssh_user='root', variant_num=1, confirm_facts='y') - result = self.runner.invoke(cli.main, self.cli_args, + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args, input=cli_input) self.assert_result(result, 0) @@ -411,8 +413,8 @@ class AttendedCliTests(OOCliFixture): written_config = self._read_yaml(self.config_file) self._verify_config_hosts(written_config, 3) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_add_nodes(self, load_facts_mock, run_playbook_mock): # Modify the mock facts to return a version indicating OpenShift @@ -432,7 +434,8 @@ class AttendedCliTests(OOCliFixture): ssh_user='root', variant_num=1, confirm_facts='y') - result = self.runner.invoke(cli.main, + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args, input=cli_input) self.assert_result(result, 0) @@ -443,8 +446,8 @@ class AttendedCliTests(OOCliFixture): written_config = self._read_yaml(self.config_file) self._verify_config_hosts(written_config, 3) - @patch('ooinstall.install_transactions.run_main_playbook') - @patch('ooinstall.install_transactions.load_system_facts') + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') def test_fresh_install_with_config(self, load_facts_mock, run_playbook_mock): load_facts_mock.return_value = (MOCK_FACTS, 0) run_playbook_mock.return_value = 0 @@ -454,7 +457,8 @@ class AttendedCliTests(OOCliFixture): SAMPLE_CONFIG % 'openshift-enterprise') cli_input = self._build_input(confirm_facts='y') self.cli_args.extend(["-c", config_file]) - result = self.runner.invoke(cli.main, + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args, input=cli_input) self.assert_result(result, 0) diff --git a/utils/test/oo_config_tests.py b/utils/test/oo_config_tests.py index 01af33fd9..6dc335a0e 100644 --- a/utils/test/oo_config_tests.py +++ b/utils/test/oo_config_tests.py @@ -32,6 +32,26 @@ hosts: node: true """ +# Used to test automatic upgrading of config: +LEGACY_CONFIG = """ +Description: This is the configuration file for the OpenShift Ansible-Based Installer. +Name: OpenShift Ansible-Based Installer Configuration +Subscription: {type: none} +Vendor: OpenShift Community +Version: 0.0.1 +ansible_config: /tmp/notreal/ansible.cfg +ansible_inventory_directory: /tmp/notreal/.config/openshift/.ansible +ansible_log_path: /tmp/ansible.log +ansible_plugins_directory: /tmp/notreal/.python-eggs/ooinstall-3.0.0-py2.7.egg-tmp/ooinstall/ansible_plugins +masters: [10.0.0.1] +nodes: [10.0.0.2, 10.0.0.3] +validated_facts: + 10.0.0.1: {hostname: master-private.example.com, ip: 10.0.0.1, public_hostname: master.example.com, public_ip: 24.222.0.1} + 10.0.0.2: {hostname: node1-private.example.com, ip: 10.0.0.2, public_hostname: node1.example.com, public_ip: 24.222.0.2} + 10.0.0.3: {hostname: node2-private.example.com, ip: 10.0.0.3, public_hostname: node2.example.com, public_ip: 24.222.0.3} +""" + + CONFIG_INCOMPLETE_FACTS = """ hosts: - ip: 10.0.0.1 @@ -74,6 +94,48 @@ class OOInstallFixture(unittest.TestCase): return path +class LegacyOOConfigTests(OOInstallFixture): + + def setUp(self): + OOInstallFixture.setUp(self) + self.cfg_path = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), LEGACY_CONFIG) + self.cfg = OOConfig(self.cfg_path) + + def test_load_config_memory(self): + self.assertEquals('openshift-enterprise', self.cfg.settings['variant']) + self.assertEquals('3.0', self.cfg.settings['variant_version']) + self.assertEquals('v1', self.cfg.settings['version']) + + self.assertEquals(3, len(self.cfg.hosts)) + h1 = self.cfg.get_host('10.0.0.1') + self.assertEquals('10.0.0.1', h1.ip) + self.assertEquals('24.222.0.1', h1.public_ip) + self.assertEquals('master-private.example.com', h1.hostname) + self.assertEquals('master.example.com', h1.public_hostname) + + h2 = self.cfg.get_host('10.0.0.2') + self.assertEquals('10.0.0.2', h2.ip) + self.assertEquals('24.222.0.2', h2.public_ip) + self.assertEquals('node1-private.example.com', h2.hostname) + self.assertEquals('node1.example.com', h2.public_hostname) + + h3 = self.cfg.get_host('10.0.0.3') + self.assertEquals('10.0.0.3', h3.ip) + self.assertEquals('24.222.0.3', h3.public_ip) + self.assertEquals('node2-private.example.com', h3.hostname) + self.assertEquals('node2.example.com', h3.public_hostname) + + self.assertFalse('masters' in self.cfg.settings) + self.assertFalse('nodes' in self.cfg.settings) + self.assertFalse('Description' in self.cfg.settings) + self.assertFalse('Name' in self.cfg.settings) + self.assertFalse('Subscription' in self.cfg.settings) + self.assertFalse('Vendor' in self.cfg.settings) + self.assertFalse('Version' in self.cfg.settings) + self.assertFalse('validates_facts' in self.cfg.settings) + + class OOConfigTests(OOInstallFixture): def test_load_config(self): @@ -91,6 +153,7 @@ class OOConfigTests(OOInstallFixture): [host['ip'] for host in ooconfig.settings['hosts']]) self.assertEquals('openshift-enterprise', ooconfig.settings['variant']) + self.assertEquals('v1', ooconfig.settings['version']) def test_load_complete_facts(self): cfg_path = self.write_config(os.path.join(self.work_dir, @@ -128,6 +191,7 @@ class OOConfigTests(OOInstallFixture): self.assertTrue('ansible_ssh_user' in written_config) self.assertTrue('variant' in written_config) + self.assertEquals('v1', written_config['version']) # Some advanced settings should not get written out if they # were not specified by the user: |