diff options
-rw-r--r-- | .tito/packages/openshift-ansible | 2 | ||||
-rw-r--r-- | inventory/byo/hosts.example | 10 | ||||
-rw-r--r-- | openshift-ansible.spec | 36 | ||||
-rw-r--r-- | roles/lib_zabbix/library/zbx_action.py | 7 | ||||
-rw-r--r-- | roles/lib_zabbix/tasks/create_template.yml | 24 | ||||
-rw-r--r-- | roles/openshift_common/tasks/main.yml | 13 | ||||
-rwxr-xr-x | roles/openshift_facts/library/openshift_facts.py | 2 | ||||
-rw-r--r-- | roles/openshift_node/tasks/main.yml | 1 | ||||
-rw-r--r-- | roles/openshift_node/templates/node.yaml.v1.j2 | 2 | ||||
-rw-r--r-- | roles/os_zabbix/tasks/main.yml | 40 | ||||
-rw-r--r-- | roles/os_zabbix/vars/template_openshift_master.yml | 34 | ||||
-rw-r--r-- | roles/os_zabbix/vars/template_os_linux.yml | 12 | ||||
-rw-r--r-- | utils/src/ooinstall/cli_installer.py | 120 | ||||
-rw-r--r-- | utils/src/ooinstall/oo_config.py | 9 | ||||
-rw-r--r-- | utils/src/ooinstall/openshift_ansible.py | 82 | ||||
-rw-r--r-- | utils/test/cli_installer_tests.py | 260 |
16 files changed, 604 insertions, 50 deletions
diff --git a/.tito/packages/openshift-ansible b/.tito/packages/openshift-ansible index 71c04689a..ce566784c 100644 --- a/.tito/packages/openshift-ansible +++ b/.tito/packages/openshift-ansible @@ -1 +1 @@ -3.0.15-1 ./ +3.0.16-1 ./ diff --git a/inventory/byo/hosts.example b/inventory/byo/hosts.example index 423581281..ef0736b63 100644 --- a/inventory/byo/hosts.example +++ b/inventory/byo/hosts.example @@ -144,6 +144,16 @@ openshift_master_identity_providers=[{'name': 'htpasswd_auth', 'login': 'true', # configure how often node iptables rules are refreshed #openshift_node_iptables_sync_period=5s +# Configure nodeIP in the node config +# This is needed in cases where node traffic is desired to go over an +# interface other than the default network interface. +#openshift_node_set_node_ip=True + +# Force setting of system hostname when configuring OpenShift +# This works around issues related to installations that do not have valid dns +# entries for the interfaces attached to the host. +#openshift_set_hostname=True + # host group for masters [masters] ose3-master[1:3]-ansible.test.example.com diff --git a/openshift-ansible.spec b/openshift-ansible.spec index ea9cc91bd..09569761f 100644 --- a/openshift-ansible.spec +++ b/openshift-ansible.spec @@ -5,7 +5,7 @@ } Name: openshift-ansible -Version: 3.0.15 +Version: 3.0.16 Release: 1%{?dist} Summary: Openshift and Atomic Enterprise Ansible License: ASL 2.0 @@ -258,6 +258,40 @@ Atomic OpenShift Utilities includes %changelog +* Tue Nov 24 2015 Brenton Leanhardt <bleanhar@redhat.com> 3.0.16-1 +- Silencing pylint branch errors for now for the atomic-openshift-installer + harness (bleanhar@redhat.com) +- Properly setting scheduleability for HA Master scenarios + (bleanhar@redhat.com) +- added graphs (mwoodson@redhat.com) +- Rework setting of hostname (jdetiber@redhat.com) +- Fixed a bug in the actions. It now supports changing opconditions + (kwoodson@redhat.com) +- Conditionally set the nodeIP (jdetiber@redhat.com) +- Bug 1284991 - "atomic-openshift-installer uninstall" error when configuration + file is missing. (bleanhar@redhat.com) +- Avoid printing the master and node totals in the add-a-node scenario + (bleanhar@redhat.com) +- Fixing tests for quick_ha (bleanhar@redhat.com) +- Removing a debug line (bleanhar@redhat.com) +- atomic-openshift-installer: Fix lint issue (smunilla@redhat.com) +- Handling preconfigured load balancers (bleanhar@redhat.com) +- atomic-openshift-installer: Rename ha_proxy (smunilla@redhat.com) +- atomic-openshift-installer: Reverse version and host collection + (smunilla@redhat.com) +- cli_installer_tests: Add test for unattended quick HA (smunilla@redhat.com) +- Breakup inventory writing (smunilla@redhat.com) +- Enforce 1 or 3 masters (smunilla@redhat.com) +- Add interactive test (smunilla@redhat.com) +- atomic-openshift-installer: HA for quick installer (smunilla@redhat.com) +- Adding zbx_graph support (kwoodson@redhat.com) +- Modified step params to be in order when passed as a list + (kwoodson@redhat.com) +- Add serviceAccountConfig.masterCA during 3.1 upgrade (jdetiber@redhat.com) +- Use the identity_providers from openshift_facts instead of always using the + inventory variable (jdetiber@redhat.com) +- Refactor master identity provider configuration (jdetiber@redhat.com) + * Fri Nov 20 2015 Kenny Woodson <kwoodson@redhat.com> 3.0.15-1 - Fixing clone group functionality. Also separating extra_vars from extra_groups (kwoodson@redhat.com) diff --git a/roles/lib_zabbix/library/zbx_action.py b/roles/lib_zabbix/library/zbx_action.py index d64cebae1..24693e5db 100644 --- a/roles/lib_zabbix/library/zbx_action.py +++ b/roles/lib_zabbix/library/zbx_action.py @@ -89,6 +89,9 @@ def operation_differences(zabbix_ops, user_ops): for zab, user in zip(zabbix_ops, user_ops): for key, val in user.items(): if key == 'opconditions': + if len(zab[key]) != len(val): + rval[key] = val + break for z_cond, u_cond in zip(zab[key], user[key]): if not all([str(u_cond[op_key]) == z_cond[op_key] for op_key in \ ['conditiontype', 'operator', 'value']]): @@ -330,9 +333,9 @@ def get_action_operations(zapi, inc_operations): condition['operator'] = 0 if condition['value'] == 'acknowledged': - condition['operator'] = 1 + condition['value'] = 1 else: - condition['operator'] = 0 + condition['value'] = 0 return inc_operations diff --git a/roles/lib_zabbix/tasks/create_template.yml b/roles/lib_zabbix/tasks/create_template.yml index 2992505bf..47749389e 100644 --- a/roles/lib_zabbix/tasks/create_template.yml +++ b/roles/lib_zabbix/tasks/create_template.yml @@ -105,3 +105,27 @@ description: "{{ item.description | default('', True) }}" with_items: template.ztriggerprototypes when: template.ztriggerprototypes is defined + +- name: Create Graphs + zbx_graph: + zbx_server: "{{ server }}" + zbx_user: "{{ user }}" + zbx_password: "{{ password }}" + name: "{{ item.name }}" + height: "{{ item.height }}" + width: "{{ item.width }}" + graph_items: "{{ item.graph_items }}" + with_items: template.zgraphs + when: template.zgraphs is defined + +- name: Create Graph Prototypes + zbx_graphprototype: + zbx_server: "{{ server }}" + zbx_user: "{{ user }}" + zbx_password: "{{ password }}" + name: "{{ item.name }}" + height: "{{ item.height }}" + width: "{{ item.width }}" + graph_items: "{{ item.graph_items }}" + with_items: template.zgraphprototypes + when: template.zgraphprototypes is defined diff --git a/roles/openshift_common/tasks/main.yml b/roles/openshift_common/tasks/main.yml index e9df4e364..55065b3de 100644 --- a/roles/openshift_common/tasks/main.yml +++ b/roles/openshift_common/tasks/main.yml @@ -3,6 +3,10 @@ msg: Flannel can not be used with openshift sdn when: openshift_use_openshift_sdn | default(false) | bool and openshift_use_flannel | default(false) | bool +- fail: + msg: openshift_hostname must be 64 characters or less + when: openshift_hostname is defined and openshift_hostname | length > 64 + - name: Set common Cluster facts openshift_facts: role: common @@ -18,3 +22,12 @@ deployment_type: "{{ openshift_deployment_type }}" use_fluentd: "{{ openshift_use_fluentd | default(None) }}" use_flannel: "{{ openshift_use_flannel | default(None) }}" + + # For enterprise versions < 3.1 and origin versions < 1.1 we want to set the + # hostname by default. +- set_fact: + set_hostname_default: "{{ not openshift.common.version_greater_than_3_1_or_1_1 }}" + +- name: Set hostname + hostname: name={{ openshift.common.hostname }} + when: openshift_set_hostname | default(set_hostname_default) | bool diff --git a/roles/openshift_facts/library/openshift_facts.py b/roles/openshift_facts/library/openshift_facts.py index 6006bfa9d..b60e42c71 100755 --- a/roles/openshift_facts/library/openshift_facts.py +++ b/roles/openshift_facts/library/openshift_facts.py @@ -1074,7 +1074,7 @@ class OpenShiftFacts(object): if 'node' in roles: node = dict(labels={}, annotations={}, portal_net='172.30.0.0/16', - iptables_sync_period='5s') + iptables_sync_period='5s', set_node_ip=False) defaults['node'] = node return defaults diff --git a/roles/openshift_node/tasks/main.yml b/roles/openshift_node/tasks/main.yml index d11bc5123..42d984a09 100644 --- a/roles/openshift_node/tasks/main.yml +++ b/roles/openshift_node/tasks/main.yml @@ -34,6 +34,7 @@ schedulable: "{{ openshift_schedulable | default(openshift_scheduleable) | default(None) }}" sdn_mtu: "{{ openshift_node_sdn_mtu | default(None) }}" storage_plugin_deps: "{{ osn_storage_plugin_deps | default(None) }}" + set_node_ip: "{{ openshift_set_node_ip | default(None) }}" # We have to add tuned-profiles in the same transaction otherwise we run into depsolving # problems because the rpms don't pin the version properly. diff --git a/roles/openshift_node/templates/node.yaml.v1.j2 b/roles/openshift_node/templates/node.yaml.v1.j2 index 7d2f506e3..41a303dee 100644 --- a/roles/openshift_node/templates/node.yaml.v1.j2 +++ b/roles/openshift_node/templates/node.yaml.v1.j2 @@ -23,7 +23,9 @@ networkConfig: {% if openshift.common.use_openshift_sdn %} networkPluginName: {{ openshift.common.sdn_network_plugin_name }} {% endif %} +{% if openshift.node.set_node_ip | bool %} nodeIP: {{ openshift.common.ip }} +{% endif %} nodeName: {{ openshift.common.hostname | lower }} podManifestConfig: servingInfo: diff --git a/roles/os_zabbix/tasks/main.yml b/roles/os_zabbix/tasks/main.yml index 59c89bb02..d0b307a3d 100644 --- a/roles/os_zabbix/tasks/main.yml +++ b/roles/os_zabbix/tasks/main.yml @@ -8,15 +8,35 @@ register: templates - include_vars: template_heartbeat.yml + tags: + - heartbeat - include_vars: template_os_linux.yml + tags: + - linux - include_vars: template_docker.yml + tags: + - docker - include_vars: template_openshift_master.yml + tags: + - openshift_master - include_vars: template_openshift_node.yml + tags: + - openshift_node - include_vars: template_ops_tools.yml + tags: + - ops_tools - include_vars: template_app_zabbix_server.yml + tags: + - zabbix_server - include_vars: template_app_zabbix_agent.yml + tags: + - zabbix_agent - include_vars: template_performance_copilot.yml + tags: + - pcp - include_vars: template_aws.yml + tags: + - aws - name: Include Template Heartbeat include: ../../lib_zabbix/tasks/create_template.yml @@ -25,6 +45,8 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - heartbeat - name: Include Template os_linux include: ../../lib_zabbix/tasks/create_template.yml @@ -33,6 +55,8 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - linux - name: Include Template docker include: ../../lib_zabbix/tasks/create_template.yml @@ -41,6 +65,8 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - docker - name: Include Template Openshift Master include: ../../lib_zabbix/tasks/create_template.yml @@ -49,6 +75,8 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - openshift_master - name: Include Template Openshift Node include: ../../lib_zabbix/tasks/create_template.yml @@ -57,6 +85,8 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - openshift_node - name: Include Template Ops Tools include: ../../lib_zabbix/tasks/create_template.yml @@ -65,6 +95,8 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - ops_tools - name: Include Template App Zabbix Server include: ../../lib_zabbix/tasks/create_template.yml @@ -73,6 +105,8 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - zabbix_server - name: Include Template App Zabbix Agent include: ../../lib_zabbix/tasks/create_template.yml @@ -81,6 +115,8 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - zabbix_agent - name: Include Template Performance Copilot include: ../../lib_zabbix/tasks/create_template.yml @@ -89,6 +125,8 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - pcp - name: Include Template AWS include: ../../lib_zabbix/tasks/create_template.yml @@ -97,3 +135,5 @@ server: "{{ ozb_server }}" user: "{{ ozb_user }}" password: "{{ ozb_password }}" + tags: + - aws diff --git a/roles/os_zabbix/vars/template_openshift_master.yml b/roles/os_zabbix/vars/template_openshift_master.yml index 512adad4c..8236cf135 100644 --- a/roles/os_zabbix/vars/template_openshift_master.yml +++ b/roles/os_zabbix/vars/template_openshift_master.yml @@ -244,3 +244,37 @@ g_template_openshift_master: expression: '{Template Openshift Master:openshift.master.etcd.ping.last(#1)}=0 and {Template Openshift Master:openshift.master.etcd.ping.last(#2)}=0' url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_etcd.asciidoc' priority: high + + zgraphs: + - name: Openshift Master API Server Latency Pods LIST Quantiles + width: 900 + height: 200 + graph_items: + - item_name: openshift.master.apiserver.latency.summary.pods.quantile.list.5 + color: red + - item_name: openshift.master.apiserver.latency.summary.pods.quantile.list.9 + color: blue + - item_name: openshift.master.apiserver.latency.summary.pods.quantile.list.99 + color: orange + + - name: Openshift Master API Server Latency Pods WATCHLIST Quantiles + width: 900 + height: 200 + graph_items: + - item_name: openshift.master.apiserver.latency.summary.pods.quantile.watchlist.5 + color: red + - item_name: openshift.master.apiserver.latency.summary.pods.quantile.watchlist.9 + color: blue + - item_name: openshift.master.apiserver.latency.summary.pods.quantile.watchlist.99 + color: orange + + - name: Openshift Master Scheduler End to End Latency Quantiles + width: 900 + height: 200 + graph_items: + - item_name: openshift.master.scheduler.e2e.scheduling.latency.quantile.5 + color: red + - item_name: openshift.master.scheduler.e2e.scheduling.latency.quantile.9 + color: blue + - item_name: openshift.master.scheduler.e2e.scheduling.latency.quantile.99 + color: orange diff --git a/roles/os_zabbix/vars/template_os_linux.yml b/roles/os_zabbix/vars/template_os_linux.yml index 04665be62..79d52ef9b 100644 --- a/roles/os_zabbix/vars/template_os_linux.yml +++ b/roles/os_zabbix/vars/template_os_linux.yml @@ -304,3 +304,15 @@ g_template_os_linux: description: 'CPU is less than 10% idle' dependencies: - 'CPU idle less than 5% on {HOST.NAME}' + + zgraphprototypes: + - name: Network Interface Usage + width: 1000 + height: 400 + graph_items: + - item_name: "Bytes per second IN on network interface {#OSO_NET_INTERFACE}" + item_type: prototype + color: red + - item_name: "Bytes per second OUT on network interface {#OSO_NET_INTERFACE}" + item_type: prototype + color: blue diff --git a/utils/src/ooinstall/cli_installer.py b/utils/src/ooinstall/cli_installer.py index 84092a774..d7c06745e 100644 --- a/utils/src/ooinstall/cli_installer.py +++ b/utils/src/ooinstall/cli_installer.py @@ -72,13 +72,14 @@ def delete_hosts(hosts): click.echo("\"{}\" doesn't coorespond to any valid input.".format(del_idx)) return hosts, None -def collect_hosts(master_set=False): +def collect_hosts(version=None, masters_set=False, print_summary=True): """ Collect host information from user. This will later be filled in using ansible. Returns: a list of host information collected from the user """ + min_masters_for_ha = 3 click.clear() click.echo('***Host Configuration***') message = """ @@ -102,17 +103,20 @@ http://docs.openshift.com/enterprise/latest/architecture/infrastructure_componen hosts = [] more_hosts = True + num_masters = 0 while more_hosts: host_props = {} - hostname_or_ip = click.prompt('Enter hostname or IP address:', - default='', - value_proc=validate_prompt_hostname) - - host_props['connect_to'] = hostname_or_ip - if not master_set: - is_master = click.confirm('Will this host be an OpenShift Master?') - host_props['master'] = is_master - master_set = is_master + host_props['connect_to'] = click.prompt('Enter hostname or IP address:', + default='', + value_proc=validate_prompt_hostname) + + if not masters_set: + if click.confirm('Will this host be an OpenShift Master?'): + host_props['master'] = True + num_masters += 1 + + if num_masters >= min_masters_for_ha or version == '3.0': + masters_set = True host_props['node'] = True #TODO: Reenable this option once container installs are out of tech preview @@ -129,9 +133,51 @@ http://docs.openshift.com/enterprise/latest/architecture/infrastructure_componen hosts.append(host) - more_hosts = click.confirm('Do you want to add additional hosts?') + if print_summary: + click.echo('') + click.echo('Current Masters: {}'.format(num_masters)) + click.echo('Current Nodes: {}'.format(len(hosts))) + click.echo('Additional Masters required for HA: {}'.format(max(min_masters_for_ha - num_masters, 0))) + click.echo('') + + if num_masters <= 1 or num_masters >= min_masters_for_ha: + more_hosts = click.confirm('Do you want to add additional hosts?') + + if num_masters > 1: + hosts.append(collect_master_lb()) + return hosts +def collect_master_lb(): + """ + Get an HA proxy from the user + """ + message = """ +Setting up High Availability Masters requires a load balancing solution. +Please provide a host that will be configured as a proxy. This can either be +an existing load balancer configured to balance all masters on port 8443 or a +new host that will have HAProxy installed on it. + +If the host provided does is not yet configured a reference haproxy load +balancer will be installed. It's important to note that while the rest of the +environment will be fault tolerant this reference load balancer will not be. +It can be replaced post-installation with a load balancer with the same +hostname. +""" + click.echo(message) + host_props = {} + host_props['connect_to'] = click.prompt('Enter hostname or IP address:', + default='', + value_proc=validate_prompt_hostname) + install_haproxy = click.confirm('Should the reference haproxy load balancer be installed on this host?') + host_props['preconfigured'] = not install_haproxy + host_props['master'] = False + host_props['node'] = False + host_props['master_lb'] = True + master_lb = Host(**host_props) + + return master_lb + def confirm_hosts_facts(oo_cfg, callback_facts): hosts = oo_cfg.hosts click.clear() @@ -169,6 +215,8 @@ Notes: default_facts_lines = [] default_facts = {} for h in hosts: + if h.preconfigured == True: + continue 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"] @@ -199,7 +247,41 @@ Edit %s with the desired values and run `atomic-openshift-installer --unattended sys.exit(0) return default_facts -def get_variant_and_version(): + + +def check_hosts_config(oo_cfg): + click.clear() + masters = [host for host in oo_cfg.hosts if host.master] + if len(masters) > 1: + master_lb = [host for host in oo_cfg.hosts if host.master_lb] + if len(master_lb) > 1: + click.echo('More than one Master load balancer specified. Only one is allowed.') + sys.exit(0) + elif len(master_lb) == 1: + if master_lb[0].master or master_lb[0].node: + click.echo('The Master load balancer is configured as a master or node. Please correct this.') + sys.exit(0) + else: + message = """ +No HAProxy given in config. Either specify one or provide a load balancing solution +of your choice to balance the master API (port 8443) on all master hosts. + +https://docs.openshift.org/latest/install_config/install/advanced_install.html#multiple-masters +""" + confirm_continue(message) + + nodes = [host for host in oo_cfg.hosts if host.node] + if len(masters) == len(nodes): + message = """ +No dedicated Nodes specified. By default, colocated Masters have their Nodes +set to unscheduleable. Continuing at this point will label all nodes as +scheduleable. +""" + confirm_continue(message) + + return + +def get_variant_and_version(multi_master=False): message = "\nWhich variant would you like to install?\n\n" i = 1 @@ -211,6 +293,8 @@ def get_variant_and_version(): message = "%s\n" % message click.echo(message) + if multi_master: + click.echo('NOTE: 3.0 installations are not') response = click.prompt("Choose a variant from above: ", default=1) product, version = combos[response - 1] @@ -292,16 +376,16 @@ https://docs.openshift.com/enterprise/latest/admin_guide/install/prerequisites.h oo_cfg.settings['ansible_ssh_user'] = get_ansible_ssh_user() click.clear() - if not oo_cfg.hosts: - oo_cfg.hosts = collect_hosts() - click.clear() - if oo_cfg.settings.get('variant', '') == '': variant, version = get_variant_and_version() oo_cfg.settings['variant'] = variant.name oo_cfg.settings['variant_version'] = version.name click.clear() + if not oo_cfg.hosts: + oo_cfg.hosts = collect_hosts(version=oo_cfg.settings['variant_version']) + click.clear() + return oo_cfg @@ -312,7 +396,7 @@ def collect_new_nodes(): Add new nodes here """ click.echo(message) - return collect_hosts(True) + return collect_hosts(masters_set=True, print_summary=False) def get_installed_hosts(hosts, callback_facts): installed_hosts = [] @@ -555,6 +639,8 @@ def install(ctx, force): else: oo_cfg = get_missing_info_from_user(oo_cfg) + check_hosts_config(oo_cfg) + click.echo('Gathering information from hosts...') callback_facts, error = openshift_ansible.default_facts(oo_cfg.hosts, verbose) diff --git a/utils/src/ooinstall/oo_config.py b/utils/src/ooinstall/oo_config.py index 9c97e6e93..b6f0cdce3 100644 --- a/utils/src/ooinstall/oo_config.py +++ b/utils/src/ooinstall/oo_config.py @@ -36,19 +36,24 @@ class Host(object): self.public_ip = kwargs.get('public_ip', None) self.public_hostname = kwargs.get('public_hostname', None) self.connect_to = kwargs.get('connect_to', None) + self.preconfigured = kwargs.get('preconfigured', None) # Should this host run as an OpenShift master: self.master = kwargs.get('master', False) # Should this host run as an OpenShift node: self.node = kwargs.get('node', False) + + # Should this host run as an HAProxy: + self.master_lb = kwargs.get('master_lb', False) + self.containerized = kwargs.get('containerized', False) 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: + if self.master is False and self.node is False and self.master_lb is False: raise OOConfigInvalidHostError( "You must specify each host as either a master or a node.") @@ -62,7 +67,7 @@ class Host(object): """ Used when exporting to yaml. """ d = {} for prop in ['ip', 'hostname', 'public_ip', 'public_hostname', - 'master', 'node', 'containerized', 'connect_to']: + 'master', 'node', 'master_lb', 'containerized', 'connect_to', 'preconfigured']: # If the property is defined (not None or False), export it: if getattr(self, prop): d[prop] = getattr(self, prop) diff --git a/utils/src/ooinstall/openshift_ansible.py b/utils/src/ooinstall/openshift_ansible.py index e9402dfb1..9afc9a644 100644 --- a/utils/src/ooinstall/openshift_ansible.py +++ b/utils/src/ooinstall/openshift_ansible.py @@ -17,14 +17,17 @@ def set_config(cfg): def generate_inventory(hosts): global CFG + masters = [host for host in hosts if host.master] + nodes = [host for host in hosts if host.node] + proxy = determine_proxy_configuration(hosts) + multiple_masters = len(masters) > 1 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_become=true\n') + + write_inventory_children(base_inventory, multiple_masters, proxy) + + write_inventory_vars(base_inventory, multiple_masters, proxy) # Find the correct deployment type for ansible: ver = find_variant(CFG.settings['variant'], @@ -45,22 +48,69 @@ def generate_inventory(hosts): "'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) for master in masters: write_host(master, base_inventory) + + if len(masters) > 1: + base_inventory.write('\n[etcd]\n') + for master in masters: + write_host(master, base_inventory) + base_inventory.write('\n[nodes]\n') - nodes = (host for host in hosts if host.node) - for node in nodes: - # TODO: Until the Master can run the SDN itself we have to configure the Masters - # as Nodes too. - scheduleable = True - # If there's only one Node and it's also a Master we want it to be scheduleable: - if node in masters and len(masters) != 1: - scheduleable = False - write_host(node, base_inventory, scheduleable) + + # TODO: It would be much better to calculate the scheduleability elsewhere + # and store it on the Node object. + if set(nodes) == set(masters): + for node in nodes: + write_host(node, base_inventory) + else: + for node in nodes: + # TODO: Until the Master can run the SDN itself we have to configure the Masters + # as Nodes too. + scheduleable = True + if node in masters: + scheduleable = False + write_host(node, base_inventory, scheduleable) + + if not getattr(proxy, 'preconfigured', True): + base_inventory.write('\n[lb]\n') + write_host(proxy, base_inventory) + base_inventory.close() return base_inventory_path +def determine_proxy_configuration(hosts): + proxy = next((host for host in hosts if host.master_lb), None) + if proxy: + if proxy.hostname == None: + proxy.hostname = proxy.connect_to + proxy.public_hostname = proxy.connect_to + return proxy + + return None + +def write_inventory_children(base_inventory, multiple_masters, proxy): + global CFG + + base_inventory.write('\n[OSEv3:children]\n') + base_inventory.write('masters\n') + base_inventory.write('nodes\n') + if multiple_masters: + base_inventory.write('etcd\n') + if not getattr(proxy, 'preconfigured', True): + base_inventory.write('lb\n') + +def write_inventory_vars(base_inventory, multiple_masters, proxy): + global CFG + 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_become=true\n') + if multiple_masters: + base_inventory.write('openshift_master_cluster_method=native\n') + base_inventory.write("openshift_master_cluster_hostname={}\n".format(proxy.hostname)) + base_inventory.write("openshift_master_cluster_public_hostname={}\n".format(proxy.public_hostname)) + def write_host(host, inventory, scheduleable=True): global CFG @@ -118,6 +168,7 @@ def default_facts(hosts, verbose=False): facts_env = os.environ.copy() facts_env["OO_INSTALL_CALLBACK_FACTS_YAML"] = CFG.settings['ansible_callback_facts_yaml'] facts_env["ANSIBLE_CALLBACK_PLUGINS"] = CFG.settings['ansible_plugins_directory'] + facts_env["OPENSHIFT_MASTER_CLUSTER_METHOD"] = 'native' if 'ansible_log_path' in CFG.settings: facts_env["ANSIBLE_LOG_PATH"] = CFG.settings['ansible_log_path'] if 'ansible_config' in CFG.settings: @@ -176,4 +227,3 @@ def run_upgrade_playbook(verbose=False): if 'ansible_config' in CFG.settings: facts_env['ANSIBLE_CONFIG'] = CFG.settings['ansible_config'] return run_ansible(playbook, inventory_file, facts_env, verbose) - diff --git a/utils/test/cli_installer_tests.py b/utils/test/cli_installer_tests.py index 40a2f844d..c951b6580 100644 --- a/utils/test/cli_installer_tests.py +++ b/utils/test/cli_installer_tests.py @@ -41,6 +41,41 @@ MOCK_FACTS = { }, } +MOCK_FACTS_QUICKHA = { + '10.0.0.1': { + 'common': { + 'ip': '10.0.0.1', + 'public_ip': '10.0.0.1', + 'hostname': 'master-private.example.com', + 'public_hostname': 'master.example.com' + } + }, + '10.0.0.2': { + 'common': { + 'ip': '10.0.0.2', + 'public_ip': '10.0.0.2', + 'hostname': 'node1-private.example.com', + 'public_hostname': 'node1.example.com' + } + }, + '10.0.0.3': { + 'common': { + 'ip': '10.0.0.3', + 'public_ip': '10.0.0.3', + 'hostname': 'node2-private.example.com', + 'public_hostname': 'node2.example.com' + } + }, + '10.0.0.4': { + 'common': { + 'ip': '10.0.0.4', + 'public_ip': '10.0.0.4', + 'hostname': 'proxy-private.example.com', + 'public_hostname': 'proxy.example.com' + } + }, +} + # Substitute in a product name before use: SAMPLE_CONFIG = """ variant: %s @@ -91,6 +126,38 @@ hosts: node: true """ +QUICKHA_CONFIG = """ +variant: %s +ansible_ssh_user: root +hosts: + - connect_to: 10.0.0.1 + ip: 10.0.0.1 + hostname: master-private.example.com + public_ip: 24.222.0.1 + public_hostname: master.example.com + master: true + node: true + - connect_to: 10.0.0.2 + ip: 10.0.0.2 + hostname: node1-private.example.com + public_ip: 24.222.0.2 + public_hostname: node1.example.com + master: true + node: true + - connect_to: 10.0.0.3 + 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.4 + ip: 10.0.0.4 + hostname: proxy-private.example.com + public_ip: 24.222.0.4 + public_hostname: proxy.example.com + master_lb: true +""" + class OOCliFixture(OOInstallFixture): def setUp(self): @@ -145,11 +212,12 @@ class OOCliFixture(OOInstallFixture): print written_config['hosts'] self.assertEquals(host_count, len(written_config['hosts'])) for h in written_config['hosts']: - self.assertTrue(h['node']) - self.assertTrue('ip' in h) self.assertTrue('hostname' in h) - self.assertTrue('public_ip' in h) self.assertTrue('public_hostname' in h) + if 'preconfigured' not in h: + self.assertTrue(h['node']) + self.assertTrue('ip' in h) + self.assertTrue('public_ip' in h) #pylint: disable=too-many-arguments def _verify_get_hosts_to_run_on(self, mock_facts, load_facts_mock, @@ -504,6 +572,40 @@ class UnattendedCliTests(OOCliFixture): assert result.exit_code == 1 assert result.output == "You must specify either and 'ip' or 'hostname' to connect to.\n" + #unattended with two masters, one node, and haproxy + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') + def test_quick_ha_full_run(self, load_facts_mock, run_playbook_mock): + load_facts_mock.return_value = (MOCK_FACTS_QUICKHA, 0) + run_playbook_mock.return_value = 0 + + config_file = self.write_config(os.path.join(self.work_dir, + 'ooinstall.conf'), QUICKHA_CONFIG % 'openshift-enterprise') + + 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] + self.assertEquals(os.path.join(self.work_dir, ".ansible/hosts"), + load_facts_args[0]) + self.assertEquals(os.path.join(self.work_dir, + "playbooks/byo/openshift_facts.yml"), load_facts_args[1]) + env_vars = load_facts_args[2] + self.assertEquals(os.path.join(self.work_dir, + '.ansible/callback_facts.yaml'), + env_vars['OO_INSTALL_CALLBACK_FACTS_YAML']) + self.assertEqual('/tmp/ansible.log', env_vars['ANSIBLE_LOG_PATH']) + # If user running test has rpm installed, this might be set to default: + self.assertTrue('ANSIBLE_CONFIG' not in env_vars or + env_vars['ANSIBLE_CONFIG'] == cli.DEFAULT_ANSIBLE_CONFIG) + + # Make sure we ran on the expected masters and nodes: + hosts = run_playbook_mock.call_args[0][0] + hosts_to_run_on = run_playbook_mock.call_args[0][1] + self.assertEquals(4, len(hosts)) + self.assertEquals(4, len(hosts_to_run_on)) + class AttendedCliTests(OOCliFixture): def setUp(self): @@ -512,9 +614,10 @@ class AttendedCliTests(OOCliFixture): self.config_file = os.path.join(self.work_dir, 'config.yml') self.cli_args.extend(["-c", self.config_file]) - #pylint: disable=too-many-arguments + #pylint: disable=too-many-arguments,too-many-branches def _build_input(self, ssh_user=None, hosts=None, variant_num=None, - add_nodes=None, confirm_facts=None): + add_nodes=None, confirm_facts=None, scheduleable_masters_ok=None, + master_lb=None): """ Builds a CLI input string with newline characters to simulate the full run. @@ -527,28 +630,40 @@ class AttendedCliTests(OOCliFixture): if ssh_user: inputs.append(ssh_user) + if variant_num: + inputs.append(str(variant_num)) # Choose variant + version + + num_masters = 0 if hosts: i = 0 + min_masters_for_ha = 3 for (host, is_master) in hosts: inputs.append(host) - inputs.append('y' if is_master else 'n') + if is_master: + inputs.append('y') + num_masters += 1 + else: + inputs.append('n') #inputs.append('rpm') if i < len(hosts) - 1: - inputs.append('y') # Add more hosts + if num_masters <= 1 or num_masters >= min_masters_for_ha: + inputs.append('y') # Add more hosts else: inputs.append('n') # Done adding hosts i += 1 - if variant_num: - inputs.append(str(variant_num)) # Choose variant + version + if master_lb: + inputs.append(master_lb[0]) + inputs.append('y' if master_lb[1] else 'n') # TODO: support option 2, fresh install if add_nodes: + if scheduleable_masters_ok: + inputs.append('y') inputs.append('1') # Add more nodes i = 0 for (host, is_master) in add_nodes: inputs.append(host) - inputs.append('y' if is_master else 'n') #inputs.append('rpm') if i < len(add_nodes) - 1: inputs.append('y') # Add more hosts @@ -556,6 +671,13 @@ class AttendedCliTests(OOCliFixture): inputs.append('n') # Done adding hosts i += 1 + if add_nodes is None: + total_hosts = hosts + else: + total_hosts = hosts + add_nodes + if total_hosts is not None and num_masters == len(total_hosts): + inputs.append('y') + inputs.extend([ confirm_facts, 'y', # lets do this @@ -587,6 +709,15 @@ class AttendedCliTests(OOCliFixture): written_config = self._read_yaml(self.config_file) self._verify_config_hosts(written_config, 3) + inventory = ConfigParser.ConfigParser(allow_no_value=True) + inventory.read(os.path.join(self.work_dir, '.ansible/hosts')) + self.assertEquals('False', + inventory.get('nodes', '10.0.0.1 openshift_scheduleable')) + self.assertEquals(None, + inventory.get('nodes', '10.0.0.2')) + self.assertEquals(None, + inventory.get('nodes', '10.0.0.3')) + # interactive with config file and some installed some uninstalled hosts @patch('ooinstall.openshift_ansible.run_main_playbook') @patch('ooinstall.openshift_ansible.load_system_facts') @@ -613,6 +744,7 @@ class AttendedCliTests(OOCliFixture): result = self.runner.invoke(cli.cli, self.cli_args, input=cli_input) + print result self.assert_result(result, 0) self._verify_load_facts(load_facts_mock) @@ -658,6 +790,7 @@ class AttendedCliTests(OOCliFixture): add_nodes=[('10.0.0.2', False)], ssh_user='root', variant_num=1, + scheduleable_masters_ok=True, confirm_facts='y') self._verify_get_hosts_to_run_on(mock_facts, load_facts_mock, @@ -667,6 +800,113 @@ class AttendedCliTests(OOCliFixture): exp_hosts_to_run_on_len=2, force=False) + #interactive multimaster: one more node than master + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') + def test_quick_ha1(self, load_facts_mock, run_playbook_mock): + load_facts_mock.return_value = (MOCK_FACTS_QUICKHA, 0) + run_playbook_mock.return_value = 0 + + cli_input = self._build_input(hosts=[ + ('10.0.0.1', True), + ('10.0.0.2', True), + ('10.0.0.3', False), + ('10.0.0.4', True)], + ssh_user='root', + variant_num=1, + confirm_facts='y', + master_lb=('10.0.0.5', False)) + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args, + input=cli_input) + self.assert_result(result, 0) + + self._verify_load_facts(load_facts_mock) + self._verify_run_playbook(run_playbook_mock, 5, 5) + + written_config = self._read_yaml(self.config_file) + self._verify_config_hosts(written_config, 5) + + inventory = ConfigParser.ConfigParser(allow_no_value=True) + inventory.read(os.path.join(self.work_dir, '.ansible/hosts')) + self.assertEquals('False', + inventory.get('nodes', '10.0.0.1 openshift_scheduleable')) + self.assertEquals('False', + inventory.get('nodes', '10.0.0.2 openshift_scheduleable')) + self.assertEquals(None, + inventory.get('nodes', '10.0.0.3')) + self.assertEquals('False', + inventory.get('nodes', '10.0.0.4 openshift_scheduleable')) + + return + + #interactive multimaster: equal number masters and nodes + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') + def test_quick_ha2(self, load_facts_mock, run_playbook_mock): + load_facts_mock.return_value = (MOCK_FACTS_QUICKHA, 0) + run_playbook_mock.return_value = 0 + + cli_input = self._build_input(hosts=[ + ('10.0.0.1', True), + ('10.0.0.2', True), + ('10.0.0.3', True)], + ssh_user='root', + variant_num=1, + confirm_facts='y', + master_lb=('10.0.0.5', False)) + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args, + input=cli_input) + self.assert_result(result, 0) + + self._verify_load_facts(load_facts_mock) + self._verify_run_playbook(run_playbook_mock, 4, 4) + + written_config = self._read_yaml(self.config_file) + self._verify_config_hosts(written_config, 4) + + inventory = ConfigParser.ConfigParser(allow_no_value=True) + inventory.read(os.path.join(self.work_dir, '.ansible/hosts')) + self.assertEquals(None, + inventory.get('nodes', '10.0.0.1')) + self.assertEquals(None, + inventory.get('nodes', '10.0.0.2')) + self.assertEquals(None, + inventory.get('nodes', '10.0.0.3')) + + return + + #interactive all-in-one + @patch('ooinstall.openshift_ansible.run_main_playbook') + @patch('ooinstall.openshift_ansible.load_system_facts') + def test_all_in_one(self, load_facts_mock, run_playbook_mock): + load_facts_mock.return_value = (MOCK_FACTS, 0) + run_playbook_mock.return_value = 0 + + cli_input = self._build_input(hosts=[ + ('10.0.0.1', True)], + ssh_user='root', + variant_num=1, + confirm_facts='y') + self.cli_args.append("install") + result = self.runner.invoke(cli.cli, self.cli_args, + input=cli_input) + self.assert_result(result, 0) + + self._verify_load_facts(load_facts_mock) + self._verify_run_playbook(run_playbook_mock, 1, 1) + + written_config = self._read_yaml(self.config_file) + self._verify_config_hosts(written_config, 1) + + inventory = ConfigParser.ConfigParser(allow_no_value=True) + inventory.read(os.path.join(self.work_dir, '.ansible/hosts')) + self.assertEquals(None, + inventory.get('nodes', '10.0.0.1')) + + return + # TODO: test with config file, attended add node # TODO: test with config file, attended new node already in config file # TODO: test with config file, attended new node already in config file, plus manually added nodes |