diff options
29 files changed, 917 insertions, 28 deletions
diff --git a/README_openstack.md b/README_openstack.md new file mode 100644 index 000000000..57977d1f5 --- /dev/null +++ b/README_openstack.md @@ -0,0 +1,80 @@ +OPENSTACK Setup instructions +============================ + +Requirements +------------ + +The OpenStack instance must have Neutron and Heat enabled. + +Install Dependencies +-------------------- + +1. The OpenStack python clients for Nova, Neutron and Heat are required: + +* `python-novaclient` +* `python-neutronclient` +* `python-heatclient` + +On RHEL / CentOS / Fedora: +``` + yum install -y ansible python-novaclient python-neutronclient python-heatclient +``` + +Configuration +------------- + +The following options can be passed via the `-o` flag of the `create` command: + +* `image_name`: Name of the image to use to spawn VMs +* `keypair` (default to `${LOGNAME}_key`): Name of the ssh key +* `public_key` (default to `~/.ssh/id_rsa.pub`): filename of the ssh public key +* `master_flavor_ram` (default to `2048`): VM flavor for the master (by amount of RAM) +* `master_flavor_id`: VM flavor for the master (by ID) +* `master_flavor_include`: VM flavor for the master (by name) +* `node_flavor_ram` (default to `4096`): VM flavor for the nodes (by amount of RAM) +* `node_flavor_id`: VM flavor for the nodes (by ID) +* `node_flavor_include`: VM flavor for the nodes (by name) +* `infra_heat_stack` (default to `playbooks/openstack/openshift-cluster/files/heat_stack.yml`): filename of the HEAT template to use to create the cluster infrastructure + +The following options are used only by `heat_stack.yml`. They are so used only if the `infra_heat_stack` option is left with its default value. + +* `network_prefix` (default to `openshift-ansible-<cluster_id>`): prefix prepended to all network objects (net, subnet, router, security groups) +* `dns` (default to `8.8.8.8,8.8.4.4`): comma separated list of DNS to use +* `net_cidr` (default to `192.168.<rand()>.0/24`): CIDR of the network created by `heat_stack.yml` +* `external_net` (default to `external`): Name of the external network to connect to +* `floating_ip_pools` (default to `external`): comma separated list of floating IP pools +* `ssh_from` (default to `0.0.0.0/0`): IPs authorized to connect to the VMs via ssh + + +Creating a cluster +------------------ + +1. To create a cluster with one master and two nodes + +``` + bin/cluster create openstack <cluster-id> +``` + +2. To create a cluster with one master and three nodes, a custom VM image and custom DNS: + +``` + bin/cluster create -n 3 -o image_name=rhel-7.1-openshift-2015.05.21 -o dns=172.16.50.210,172.16.50.250 openstack lenaic +``` + +Updating a cluster +------------------ + +1. To update the cluster + +``` + bin/cluster update openstack <cluster-id> +``` + +Terminating a cluster +--------------------- + +1. To terminate the cluster + +``` + bin/cluster terminate openstack <cluster-id> +``` diff --git a/bin/cluster b/bin/cluster index 2a6cb4b58..2ea389523 100755 --- a/bin/cluster +++ b/bin/cluster @@ -143,6 +143,8 @@ class Cluster(object): inventory = '-i inventory/aws/hosts' elif 'libvirt' == provider: inventory = '-i inventory/libvirt/hosts' + elif 'openstack' == provider: + inventory = '-i inventory/openstack/hosts' else: # this code should never be reached raise ValueError("invalid PROVIDER {}".format(provider)) @@ -163,6 +165,11 @@ class Cluster(object): if args.verbose > 0: verbose = '-{}'.format('v' * args.verbose) + if args.option: + for opt in args.option: + k, v = opt.split('=', 1) + env['opt_'+k] = v + ansible_env = '-e \'{}\''.format( ' '.join(['%s=%s' % (key, value) for (key, value) in env.items()]) ) @@ -184,24 +191,48 @@ class Cluster(object): if __name__ == '__main__': """ User command to invoke ansible playbooks in a "known" environment + + Reads ~/.openshift-ansible for default configuration items + [DEFAULT] + validate_cluster_ids = False + cluster_ids = marketing,sales + providers = gce,aws,libvirt,openstack """ + environment = ConfigParser.SafeConfigParser({ + 'cluster_ids': 'marketing,sales', + 'validate_cluster_ids': 'False', + 'providers': 'gce,aws,libvirt,openstack', + }) + + path = os.path.expanduser("~/.openshift-ansible") + if os.path.isfile(path): + environment.read(path) + cluster = Cluster() - providers = ['gce', 'aws', 'libvirt'] parser = argparse.ArgumentParser( description='Python wrapper to ensure proper environment for OpenShift ansible playbooks', ) parser.add_argument('-v', '--verbose', action='count', help='Multiple -v options increase the verbosity') - parser.add_argument('--version', action='version', version='%(prog)s 0.2') + parser.add_argument('--version', action='version', version='%(prog)s 0.3') meta_parser = argparse.ArgumentParser(add_help=False) + providers = environment.get('DEFAULT', 'providers').split(',') meta_parser.add_argument('provider', choices=providers, help='provider') - meta_parser.add_argument('cluster_id', help='prefix for cluster VM names') + + if environment.get('DEFAULT', 'validate_cluster_ids').lower() in ("yes", "true", "1"): + meta_parser.add_argument('cluster_id', choices=environment.get('DEFAULT', 'cluster_ids').split(','), + help='prefix for cluster VM names') + else: + meta_parser.add_argument('cluster_id', help='prefix for cluster VM names') + meta_parser.add_argument('-t', '--deployment-type', choices=['origin', 'online', 'enterprise'], help='Deployment type. (default: origin)') + meta_parser.add_argument('-o', '--option', action='append', + help='options') action_parser = parser.add_subparsers(dest='action', title='actions', description='Choose from valid actions') diff --git a/docs/best_practices_guide.adoc b/docs/best_practices_guide.adoc index 301c6ccda..841f6e72c 100644 --- a/docs/best_practices_guide.adoc +++ b/docs/best_practices_guide.adoc @@ -19,7 +19,6 @@ This guide complies with https://www.ietf.org/rfc/rfc2119.txt[RFC2119]. | All pull requests MUST pass the build bot *before* they are merged. |=== - The purpose of this rule is to avoid cases where the build bot will fail pull requests for code modified in a previous pull request. The tooling is flexible enough that exceptions can be made so that the tool the build bot is running will ignore certain areas or certain checks, but the build bot itself must pass for the pull request to be merged. @@ -50,6 +49,7 @@ Instead, http://docs.pylint.org/faq.html#is-it-possible-to-locally-disable-a-par .Exceptions: 1. When PyLint fails because of a dependency that can't be installed on the build bot 1. When PyLint fails because of including a module that is outside of control (like Ansible) +1. When PyLint fails, but the code makes more sense the way it is formatted (stylistic exception). For this exception, the description of the PyLint disable MUST state why the code is more clear, AND the person reviewing the PR will decide if they agree or not. The reviewer may reject the PR if they disagree with the reason for the disable. ''' [cols="2v,v"] @@ -78,9 +78,71 @@ metadata[line] = results.pop() == Ansible -=== Roles +=== Yaml Files (Playbooks, Roles, Vars, etc) + +''' +[cols="2v,v"] +|=== +| **Rule** +| Ansible files SHOULD NOT use JSON (use pure YAML instead). +|=== + +YAML is a superset of JSON, which means that Ansible allows JSON syntax to be interspersed. Even though YAML (and by extension Ansible) allows for this, JSON SHOULD NOT be used. + +.Reasons: +* Ansible is able to give clearer error messages when the files are pure YAML +* YAML reads nicer (preference held by several team members) +* YAML makes for nicer diffs as YAML tends to be multi-line, whereas JSON tends to be more concise + +.Exceptions: +* Ansible static inventory files are INI files. To pass in variables for specific hosts, Ansible allows for these variables to be put inside of the static inventory files. These variables can be in JSON format, but can't be in YAML format. This is an acceptable use of JSON, as YAML is not allowed in this case. + +Every effort should be made to keep our Ansible YAML files in pure YAML. + +=== Defensive Programming + .Context -* http://docs.ansible.com/playbooks_best_practices.html#directory-layout[Ansible Suggested Directory Layout] +* http://docs.ansible.com/fail_module.html[Ansible Fail Module] + +''' +[cols="2v,v"] +|=== +| **Rule** +| Ansible playbooks MUST begin with checks for any variables that they require. +|=== + +If an Ansible playbook requires certain variables to be set, it's best to check for these up front before any other actions have been performed. In this way, the user knows exactly what needs to be passed into the playbook. + +.Example: +[source,yaml] +---- +--- +- hosts: localhost + gather_facts: no + tasks: + - fail: msg="This playbook requires g_environment to be set and non empty" + when: g_environment is not defined or g_environment == '' +---- + +''' +[cols="2v,v"] +|=== +| **Rule** +| Ansible roles tasks/main.yml file MUST begin with checks for any variables that they require. +|=== + +If an Ansible role requires certain variables to be set, it's best to check for these up front before any other actions have been performed. In this way, the user knows exactly what needs to be passed into the role. + +.Example: +[source,yaml] +---- +--- +# tasks/main.yml +- fail: msg="This role requires arl_environment to be set and non empty" + when: arl_environment is not defined or arl_environment == '' +---- + +=== Roles ''' [cols="2v,v"] @@ -89,6 +151,9 @@ metadata[line] = results.pop() | The Ansible roles directory MUST maintain a flat structure. |=== +.Context +* http://docs.ansible.com/playbooks_best_practices.html#directory-layout[Ansible Suggested Directory Layout] + .The purpose of this rule is to: * Comply with the upstream best practices * Make it familiar for new contributors @@ -101,7 +166,7 @@ metadata[line] = results.pop() | Ansible Roles SHOULD be named like technology_component[_subcomponent]. |=== -For clarity, it is suggested to follow a pattern when naming roles. It is important to note that this is a recommendation for role naming, and follows the pattern used by upstream. +For consistency, role names SHOULD follow the above naming pattern. It is important to note that this is a recommendation for role naming, and follows the pattern used by upstream. Many times the `technology` portion of the pattern will line up with a package name. It is advised that whenever possible, the package name should be used. diff --git a/filter_plugins/oo_filters.py b/filter_plugins/oo_filters.py index 33d5e6cc3..476761715 100644 --- a/filter_plugins/oo_filters.py +++ b/filter_plugins/oo_filters.py @@ -23,13 +23,6 @@ class FilterModule(object): return arg @staticmethod - def oo_len(arg): - ''' This returns the length of the argument - Ex: "{{ hostvars | oo_len }}" - ''' - return len(arg) - - @staticmethod def get_attr(data, attribute=None): ''' This looks up dictionary attributes of the form a.b.c and returns the value. @@ -203,16 +196,22 @@ class FilterModule(object): return [root_vol, docker_vol] return [root_vol] + @staticmethod + def oo_split(string, separator=','): + ''' This splits the input string into a list + ''' + return string.split(separator) + def filters(self): ''' returns a mapping of filters to methods ''' return { "oo_select_keys": self.oo_select_keys, "oo_collect": self.oo_collect, "oo_flatten": self.oo_flatten, - "oo_len": self.oo_len, "oo_pdb": self.oo_pdb, "oo_prepend_strings_in_list": self.oo_prepend_strings_in_list, "oo_ami_selector": self.oo_ami_selector, "oo_ec2_volume_definition": self.oo_ec2_volume_definition, "oo_combine_key_value": self.oo_combine_key_value, + "oo_split": self.oo_split, } diff --git a/inventory/openstack/hosts/hosts b/inventory/openstack/hosts/hosts new file mode 100644 index 000000000..9cdc31449 --- /dev/null +++ b/inventory/openstack/hosts/hosts @@ -0,0 +1 @@ +localhost ansible_sudo=no ansible_python_interpreter=/usr/bin/python2 connection=local diff --git a/inventory/openstack/hosts/nova.ini b/inventory/openstack/hosts/nova.ini new file mode 100644 index 000000000..4900c4965 --- /dev/null +++ b/inventory/openstack/hosts/nova.ini @@ -0,0 +1,45 @@ +# Ansible OpenStack external inventory script + +[openstack] + +#------------------------------------------------------------------------- +# Required settings +#------------------------------------------------------------------------- + +# API version +version = 2 + +# OpenStack nova username +username = + +# OpenStack nova api_key or password +api_key = + +# OpenStack nova auth_url +auth_url = + +# OpenStack nova project_id or tenant name +project_id = + +#------------------------------------------------------------------------- +# Optional settings +#------------------------------------------------------------------------- + +# Authentication system +# auth_system = keystone + +# Serverarm region name to use +# region_name = + +# Specify a preference for public or private IPs (public is default) +# prefer_private = False + +# What service type (required for newer nova client) +# service_type = compute + + +# TODO: Some other options +# insecure = +# endpoint_type = +# extensions = +# service_name = diff --git a/inventory/openstack/hosts/nova.py b/inventory/openstack/hosts/nova.py new file mode 100755 index 000000000..d5bd8d1ee --- /dev/null +++ b/inventory/openstack/hosts/nova.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python2 + +# pylint: skip-file + +# (c) 2012, Marco Vito Moscaritolo <marco@agavee.com> +# +# This file is part of Ansible, +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +import sys +import re +import os +import ConfigParser +from novaclient import client as nova_client + +try: + import json +except ImportError: + import simplejson as json + +################################################### +# executed with no parameters, return the list of +# all groups and hosts + +NOVA_CONFIG_FILES = [os.getcwd() + "/nova.ini", + os.path.expanduser(os.environ.get('ANSIBLE_CONFIG', "~/nova.ini")), + "/etc/ansible/nova.ini"] + +NOVA_DEFAULTS = { + 'auth_system': None, + 'region_name': None, + 'service_type': 'compute', +} + + +def nova_load_config_file(): + p = ConfigParser.SafeConfigParser(NOVA_DEFAULTS) + + for path in NOVA_CONFIG_FILES: + if os.path.exists(path): + p.read(path) + return p + + return None + + +def get_fallback(config, value, section="openstack"): + """ + Get value from config object and return the value + or false + """ + try: + return config.get(section, value) + except ConfigParser.NoOptionError: + return False + + +def push(data, key, element): + """ + Assist in items to a dictionary of lists + """ + if (not element) or (not key): + return + + if key in data: + data[key].append(element) + else: + data[key] = [element] + + +def to_safe(word): + ''' + Converts 'bad' characters in a string to underscores so they can + be used as Ansible groups + ''' + return re.sub(r"[^A-Za-z0-9\-]", "_", word) + + +def get_ips(server, access_ip=True): + """ + Returns a list of the server's IPs, or the preferred + access IP + """ + private = [] + public = [] + address_list = [] + # Iterate through each servers network(s), get addresses and get type + addresses = getattr(server, 'addresses', {}) + if len(addresses) > 0: + for network in addresses.itervalues(): + for address in network: + if address.get('OS-EXT-IPS:type', False) == 'fixed': + private.append(address['addr']) + elif address.get('OS-EXT-IPS:type', False) == 'floating': + public.append(address['addr']) + + if not access_ip: + address_list.append(server.accessIPv4) + address_list.extend(private) + address_list.extend(public) + return address_list + + access_ip = None + # Append group to list + if server.accessIPv4: + access_ip = server.accessIPv4 + if (not access_ip) and public and not (private and prefer_private): + access_ip = public[0] + if private and not access_ip: + access_ip = private[0] + + return access_ip + + +def get_metadata(server): + """Returns dictionary of all host metadata""" + get_ips(server, False) + results = {} + for key in vars(server): + # Extract value + value = getattr(server, key) + + # Generate sanitized key + key = 'os_' + re.sub(r"[^A-Za-z0-9\-]", "_", key).lower() + + # Att value to instance result (exclude manager class) + #TODO: maybe use value.__class__ or similar inside of key_name + if key != 'os_manager': + results[key] = value + return results + +config = nova_load_config_file() +if not config: + sys.exit('Unable to find configfile in %s' % ', '.join(NOVA_CONFIG_FILES)) + +# Load up connections info based on config and then environment +# variables +username = (get_fallback(config, 'username') or + os.environ.get('OS_USERNAME', None)) +api_key = (get_fallback(config, 'api_key') or + os.environ.get('OS_PASSWORD', None)) +auth_url = (get_fallback(config, 'auth_url') or + os.environ.get('OS_AUTH_URL', None)) +project_id = (get_fallback(config, 'project_id') or + os.environ.get('OS_TENANT_NAME', None)) +region_name = (get_fallback(config, 'region_name') or + os.environ.get('OS_REGION_NAME', None)) +auth_system = (get_fallback(config, 'auth_system') or + os.environ.get('OS_AUTH_SYSTEM', None)) + +# Determine what type of IP is preferred to return +prefer_private = False +try: + prefer_private = config.getboolean('openstack', 'prefer_private') +except ConfigParser.NoOptionError: + pass + +client = nova_client.Client( + version=config.get('openstack', 'version'), + username=username, + api_key=api_key, + auth_url=auth_url, + region_name=region_name, + project_id=project_id, + auth_system=auth_system, + service_type=config.get('openstack', 'service_type'), +) + +# Default or added list option +if (len(sys.argv) == 2 and sys.argv[1] == '--list') or len(sys.argv) == 1: + groups = {'_meta': {'hostvars': {}}} + # Cycle on servers + for server in client.servers.list(): + access_ip = get_ips(server) + + # Push to name group of 1 + push(groups, server.name, access_ip) + + # Run through each metadata item and add instance to it + for key, value in server.metadata.iteritems(): + composed_key = to_safe('tag_{0}_{1}'.format(key, value)) + push(groups, composed_key, access_ip) + + # Do special handling of group for backwards compat + # inventory groups + group = server.metadata['group'] if 'group' in server.metadata else 'undefined' + push(groups, group, access_ip) + + # Add vars to _meta key for performance optimization in + # Ansible 1.3+ + groups['_meta']['hostvars'][access_ip] = get_metadata(server) + + # Return server list + print(json.dumps(groups, sort_keys=True, indent=2)) + sys.exit(0) + +##################################################### +# executed with a hostname as a parameter, return the +# variables for that host + +elif len(sys.argv) == 3 and (sys.argv[1] == '--host'): + results = {} + ips = [] + for server in client.servers.list(): + if sys.argv[2] in (get_ips(server) or []): + results = get_metadata(server) + print(json.dumps(results, sort_keys=True, indent=2)) + sys.exit(0) + +else: + print "usage: --list ..OR.. --host <hostname>" + sys.exit(1) diff --git a/playbooks/aws/ansible-tower/launch.yml b/playbooks/aws/ansible-tower/launch.yml index c23bda3a0..4bcc8b8dc 100644 --- a/playbooks/aws/ansible-tower/launch.yml +++ b/playbooks/aws/ansible-tower/launch.yml @@ -22,7 +22,7 @@ group_id: "{{ oo_security_group_ids }}" instance_type: c4.xlarge image: "{{ rhel7_ami }}" - count: "{{ oo_new_inst_names | oo_len }}" + count: "{{ oo_new_inst_names | length }}" user_data: "{{ lookup('file', user_data_file) }}" wait: yes assign_public_ip: "{{ oo_assign_public_ip }}" diff --git a/playbooks/aws/openshift-cluster/tasks/launch_instances.yml b/playbooks/aws/openshift-cluster/tasks/launch_instances.yml index 666a8d1fb..4f594e2ed 100644 --- a/playbooks/aws/openshift-cluster/tasks/launch_instances.yml +++ b/playbooks/aws/openshift-cluster/tasks/launch_instances.yml @@ -79,7 +79,7 @@ group: "{{ ec2_security_groups }}" instance_type: "{{ ec2_instance_type }}" image: "{{ latest_ami }}" - count: "{{ instances | oo_len }}" + count: "{{ instances | length }}" vpc_subnet_id: "{{ ec2_vpc_subnet | default(omit, true) }}" assign_public_ip: "{{ ec2_assign_public_ip | default(omit, true) }}" user_data: "{{ user_data }}" diff --git a/playbooks/aws/openshift-master/launch.yml b/playbooks/aws/openshift-master/launch.yml index 51a0258f0..1cefad492 100644 --- a/playbooks/aws/openshift-master/launch.yml +++ b/playbooks/aws/openshift-master/launch.yml @@ -19,7 +19,7 @@ group: ['public'] instance_type: m3.large image: "{{ g_ami }}" - count: "{{ oo_new_inst_names | oo_len }}" + count: "{{ oo_new_inst_names | length }}" user_data: "{{ lookup('file', user_data_file) }}" wait: yes register: ec2 diff --git a/playbooks/aws/openshift-node/launch.yml b/playbooks/aws/openshift-node/launch.yml index d6024a020..e7d1f7310 100644 --- a/playbooks/aws/openshift-node/launch.yml +++ b/playbooks/aws/openshift-node/launch.yml @@ -19,7 +19,7 @@ group: ['public'] instance_type: m3.large image: "{{ g_ami }}" - count: "{{ oo_new_inst_names | oo_len }}" + count: "{{ oo_new_inst_names | length }}" user_data: "{{ lookup('file', user_data_file) }}" wait: yes register: ec2 diff --git a/playbooks/common/openshift-master/config.yml b/playbooks/common/openshift-master/config.yml index 4df64e95f..a9a4e6afc 100644 --- a/playbooks/common/openshift-master/config.yml +++ b/playbooks/common/openshift-master/config.yml @@ -5,8 +5,10 @@ openshift_sdn_master_url: https://{{ openshift.common.hostname }}:4001 roles: - openshift_master - - { role: openshift_sdn_master, when: openshift.common.use_openshift_sdn | bool } - - { role: fluentd_master, when openshift.common.use_fluentd | bool } + - role: openshift_sdn_master + when: openshift.common.use_openshift_sdn | bool + - role: fluentd_master + when: openshift.common.use_fluentd | bool tasks: - name: Create group for deployment type group_by: key=oo_masters_deployment_type_{{ openshift.common.deployment_type }} diff --git a/playbooks/common/openshift-node/config.yml b/playbooks/common/openshift-node/config.yml index 70711e39b..55abedfe7 100644 --- a/playbooks/common/openshift-node/config.yml +++ b/playbooks/common/openshift-node/config.yml @@ -95,8 +95,10 @@ dest: "{{ openshift_node_cert_dir }}" roles: - openshift_node - - { role: openshift_sdn_node, when: openshift.common.use_openshift_sdn | bool } - - { role: fluentd_node, when: openshift.common.use_fluentd | bool } + - role: openshift_sdn_node + when: openshift.common.use_openshift_sdn | bool + - role: fluentd_node + when: openshift.common.use_fluentd | bool tasks: - name: Create group for deployment type group_by: key=oo_nodes_deployment_type_{{ openshift.common.deployment_type }} diff --git a/playbooks/openstack/openshift-cluster/config.yml b/playbooks/openstack/openshift-cluster/config.yml new file mode 100644 index 000000000..1c0644e04 --- /dev/null +++ b/playbooks/openstack/openshift-cluster/config.yml @@ -0,0 +1,34 @@ +- name: Populate oo_masters_to_config host group + hosts: localhost + gather_facts: no + vars_files: + - vars.yml + tasks: + - name: Evaluate oo_masters_to_config + add_host: + name: "{{ item }}" + ansible_ssh_user: "{{ deployment_vars[deployment_type].ssh_user }}" + ansible_sudo: "{{ deployment_vars[deployment_type].sudo }}" + groups: oo_masters_to_config + with_items: groups["tag_env-host-type_{{ cluster_id }}-openshift-master"] | default([]) + - name: Evaluate oo_nodes_to_config + add_host: + name: "{{ item }}" + ansible_ssh_user: "{{ deployment_vars[deployment_type].ssh_user }}" + ansible_sudo: "{{ deployment_vars[deployment_type].sudo }}" + groups: oo_nodes_to_config + with_items: groups["tag_env-host-type_{{ cluster_id }}-openshift-node"] | default([]) + - name: Evaluate oo_first_master + add_host: + name: "{{ groups['tag_env-host-type_' ~ cluster_id ~ '-openshift-master'][0] }}" + ansible_ssh_user: "{{ deployment_vars[deployment_type].ssh_user }}" + ansible_sudo: "{{ deployment_vars[deployment_type].sudo }}" + groups: oo_first_master + when: "'tag_env-host-type_{{ cluster_id }}-openshift-master' in groups" + +- include: ../../common/openshift-cluster/config.yml + vars: + openshift_cluster_id: "{{ cluster_id }}" + openshift_debug_level: 4 + openshift_deployment_type: "{{ deployment_type }}" + openshift_hostname: "{{ ansible_default_ipv4.address }}" diff --git a/playbooks/openstack/openshift-cluster/files/heat_stack.yml b/playbooks/openstack/openshift-cluster/files/heat_stack.yml new file mode 100644 index 000000000..c5f95d87d --- /dev/null +++ b/playbooks/openstack/openshift-cluster/files/heat_stack.yml @@ -0,0 +1,149 @@ +heat_template_version: 2014-10-16 + +description: OpenShift cluster + +parameters: + cluster-id: + type: string + label: Cluster ID + description: Identifier of the cluster + + network-prefix: + type: string + label: Network prefix + description: Prefix of the network objects + + cidr: + type: string + label: CIDR + description: CIDR of the network of the cluster + + dns-nameservers: + type: comma_delimited_list + label: DNS nameservers list + description: List of DNS nameservers + + external-net: + type: string + label: External network + description: Name of the external network + default: external + + ssh-incoming: + type: string + label: Source of ssh connections + description: Source of legitimate ssh connections + +resources: + net: + type: OS::Neutron::Net + properties: + name: + str_replace: + template: network-prefix-net + params: + network-prefix: { get_param: network-prefix } + + subnet: + type: OS::Neutron::Subnet + properties: + name: + str_replace: + template: network-prefix-subnet + params: + network-prefix: { get_param: network-prefix } + network: { get_resource: net } + cidr: { get_param: cidr } + dns_nameservers: { get_param: dns-nameservers } + + router: + type: OS::Neutron::Router + properties: + name: + str_replace: + template: network-prefix-router + params: + network-prefix: { get_param: network-prefix } + external_gateway_info: + network: { get_param: external-net } + + interface: + type: OS::Neutron::RouterInterface + properties: + router_id: { get_resource: router } + subnet_id: { get_resource: subnet } + + node-secgrp: + type: OS::Neutron::SecurityGroup + properties: + name: + str_replace: + template: network-prefix-node-secgrp + params: + network-prefix: { get_param: network-prefix } + description: + str_replace: + template: Security group for cluster-id OpenShift cluster nodes + params: + cluster-id: { get_param: cluster-id } + rules: + - direction: ingress + protocol: tcp + port_range_min: 22 + port_range_max: 22 + remote_ip_prefix: { get_param: ssh-incoming } + - direction: ingress + protocol: udp + port_range_min: 4789 + port_range_max: 4789 + remote_mode: remote_group_id + - direction: ingress + protocol: tcp + port_range_min: 10250 + port_range_max: 10250 + remote_mode: remote_group_id + remote_group_id: { get_resource: master-secgrp } + + master-secgrp: + type: OS::Neutron::SecurityGroup + properties: + name: + str_replace: + template: network-prefix-master-secgrp + params: + network-prefix: { get_param: network-prefix } + description: + str_replace: + template: Security group for cluster-id OpenShift cluster master + params: + cluster-id: { get_param: cluster-id } + rules: + - direction: ingress + protocol: tcp + port_range_min: 22 + port_range_max: 22 + remote_ip_prefix: { get_param: ssh-incoming } + - direction: ingress + protocol: tcp + port_range_min: 4001 + port_range_max: 4001 + - direction: ingress + protocol: tcp + port_range_min: 8443 + port_range_max: 8443 + - direction: ingress + protocol: tcp + port_range_min: 53 + port_range_max: 53 + - direction: ingress + protocol: udp + port_range_min: 53 + port_range_max: 53 + - direction: ingress + protocol: tcp + port_range_min: 24224 + port_range_max: 24224 + - direction: ingress + protocol: udp + port_range_min: 24224 + port_range_max: 24224 diff --git a/playbooks/openstack/openshift-cluster/files/user-data b/playbooks/openstack/openshift-cluster/files/user-data new file mode 100644 index 000000000..e789a5b69 --- /dev/null +++ b/playbooks/openstack/openshift-cluster/files/user-data @@ -0,0 +1,7 @@ +#cloud-config +disable_root: true + +system_info: + default_user: + name: openshift + sudo: ["ALL=(ALL) NOPASSWD: ALL"] diff --git a/playbooks/openstack/openshift-cluster/filter_plugins b/playbooks/openstack/openshift-cluster/filter_plugins new file mode 120000 index 000000000..99a95e4ca --- /dev/null +++ b/playbooks/openstack/openshift-cluster/filter_plugins @@ -0,0 +1 @@ +../../../filter_plugins
\ No newline at end of file diff --git a/playbooks/openstack/openshift-cluster/launch.yml b/playbooks/openstack/openshift-cluster/launch.yml new file mode 100644 index 000000000..5c86ade3f --- /dev/null +++ b/playbooks/openstack/openshift-cluster/launch.yml @@ -0,0 +1,31 @@ +--- +- name: Launch instance(s) + hosts: localhost + connection: local + gather_facts: no + vars_files: + - vars.yml + tasks: + - fail: + msg: "Deployment type not supported for OpenStack provider yet" + when: deployment_type in ['online', 'enterprise'] + + - include: tasks/configure_openstack.yml + + - include: ../../common/openshift-cluster/set_master_launch_facts_tasks.yml + - include: tasks/launch_instances.yml + vars: + instances: "{{ master_names }}" + cluster: "{{ cluster_id }}" + type: "{{ k8s_type }}" + + - include: ../../common/openshift-cluster/set_node_launch_facts_tasks.yml + - include: tasks/launch_instances.yml + vars: + instances: "{{ node_names }}" + cluster: "{{ cluster_id }}" + type: "{{ k8s_type }}" + +- include: update.yml + +- include: list.yml diff --git a/playbooks/openstack/openshift-cluster/list.yml b/playbooks/openstack/openshift-cluster/list.yml new file mode 100644 index 000000000..a75e350c7 --- /dev/null +++ b/playbooks/openstack/openshift-cluster/list.yml @@ -0,0 +1,24 @@ +--- +- name: Generate oo_list_hosts group + hosts: localhost + gather_facts: no + vars_files: + - vars.yml + tasks: + - set_fact: scratch_group=tag_env_{{ cluster_id }} + when: cluster_id != '' + - set_fact: scratch_group=all + when: cluster_id == '' + - add_host: + name: "{{ item }}" + groups: oo_list_hosts + ansible_ssh_user: "{{ deployment_vars[deployment_type].ssh_user }}" + ansible_ssh_host: "{{ hostvars[item].ansible_ssh_host | default(item) }}" + ansible_sudo: "{{ deployment_vars[deployment_type].sudo }}" + with_items: groups[scratch_group] | default([]) | difference(['localhost']) + +- name: List Hosts + hosts: oo_list_hosts + tasks: + - debug: + msg: 'public:{{ansible_ssh_host}} private:{{ansible_default_ipv4.address}}' diff --git a/playbooks/openstack/openshift-cluster/roles b/playbooks/openstack/openshift-cluster/roles new file mode 120000 index 000000000..20c4c58cf --- /dev/null +++ b/playbooks/openstack/openshift-cluster/roles @@ -0,0 +1 @@ +../../../roles
\ No newline at end of file diff --git a/playbooks/openstack/openshift-cluster/tasks/configure_openstack.yml b/playbooks/openstack/openshift-cluster/tasks/configure_openstack.yml new file mode 100644 index 000000000..2cbdb4805 --- /dev/null +++ b/playbooks/openstack/openshift-cluster/tasks/configure_openstack.yml @@ -0,0 +1,27 @@ +--- +- name: Check infra + command: 'heat stack-show {{ openstack_network_prefix }}-stack' + register: stack_show_result + changed_when: false + failed_when: stack_show_result.rc != 0 and 'Stack not found' not in stack_show_result.stderr + +- name: Create infra + command: 'heat stack-create -f {{ openstack_infra_heat_stack }} -P cluster-id={{ cluster_id }} -P network-prefix={{ openstack_network_prefix }} -P dns-nameservers={{ openstack_network_dns | join(",") }} -P cidr={{ openstack_network_cidr }} -P ssh-incoming={{ openstack_ssh_access_from }} {{ openstack_network_prefix }}-stack' + when: stack_show_result.rc == 1 + +- name: Update infra + command: 'heat stack-update -f {{ openstack_infra_heat_stack }} -P cluster-id={{ cluster_id }} -P network-prefix={{ openstack_network_prefix }} -P dns-nameservers={{ openstack_network_dns | join(",") }} -P cidr={{ openstack_network_cidr }} -P ssh-incoming={{ openstack_ssh_access_from }} {{ openstack_network_prefix }}-stack' + when: stack_show_result.rc == 0 + +- name: Wait for infra readiness + shell: 'heat stack-show {{ openstack_network_prefix }}-stack | awk ''$2 == "stack_status" {print $4}''' + register: stack_show_status_result + until: stack_show_status_result.stdout not in ['CREATE_IN_PROGRESS', 'UPDATE_IN_PROGRESS'] + retries: 30 + delay: 1 + failed_when: stack_show_status_result.stdout not in ['CREATE_COMPLETE', 'UPDATE_COMPLETE'] + +- name: Create ssh keypair + nova_keypair: + name: "{{ openstack_ssh_keypair }}" + public_key: "{{ openstack_ssh_public_key }}" diff --git a/playbooks/openstack/openshift-cluster/tasks/launch_instances.yml b/playbooks/openstack/openshift-cluster/tasks/launch_instances.yml new file mode 100644 index 000000000..1b9696aac --- /dev/null +++ b/playbooks/openstack/openshift-cluster/tasks/launch_instances.yml @@ -0,0 +1,48 @@ +--- +- name: Get net id + shell: 'neutron net-show {{ openstack_network_prefix }}-net | awk "/\\<id\\>/ {print \$4}"' + register: net_id_result + +- name: Launch instance(s) + nova_compute: + name: '{{ item }}' + image_name: '{{ deployment_vars[deployment_type].image.name | default(omit, true) }}' + image_id: '{{ deployment_vars[deployment_type].image.id | default(omit, true) }}' + flavor_ram: '{{ openstack_flavor[k8s_type].ram | default(omit, true) }}' + flavor_id: '{{ openstack_flavor[k8s_type].id | default(omit, true) }}' + flavor_include: '{{ openstack_flavor[k8s_type].include | default(omit, true) }}' + key_name: '{{ openstack_ssh_keypair }}' + security_groups: '{{ openstack_network_prefix }}-{{ k8s_type }}-secgrp' + nics: + - net-id: '{{ net_id_result.stdout }}' + user_data: "{{ lookup('file','files/user-data') }}" + meta: + env: '{{ cluster }}' + host-type: '{{ type }}' + env-host-type: '{{ cluster }}-openshift-{{ type }}' + floating_ip_pools: '{{ openstack_floating_ip_pools }}' + with_items: instances + register: nova_compute_result + +- name: Add new instances groups and variables + add_host: + hostname: '{{ item.item }}' + ansible_ssh_host: '{{ item.public_ip }}' + ansible_ssh_user: "{{ deployment_vars[deployment_type].ssh_user }}" + ansible_sudo: "{{ deployment_vars[deployment_type].sudo }}" + groups: 'tag_env_{{ cluster }}, tag_host-type_{{ type }}, tag_env-host-type_{{ cluster }}-openshift-{{ type }}' + with_items: nova_compute_result.results + +- name: Wait for ssh + wait_for: + host: '{{ item.public_ip }}' + port: 22 + with_items: nova_compute_result.results + +- name: Wait for user setup + command: 'ssh -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null {{ hostvars[item.item].ansible_ssh_user }}@{{ item.public_ip }} echo {{ hostvars[item.item].ansible_ssh_user }} user is setup' + register: result + until: result.rc == 0 + retries: 30 + delay: 1 + with_items: nova_compute_result.results diff --git a/playbooks/openstack/openshift-cluster/terminate.yml b/playbooks/openstack/openshift-cluster/terminate.yml new file mode 100644 index 000000000..2f05f0992 --- /dev/null +++ b/playbooks/openstack/openshift-cluster/terminate.yml @@ -0,0 +1,43 @@ +- name: Terminate instance(s) + hosts: localhost + connection: local + gather_facts: no + vars_files: + - vars.yml + tasks: + - set_fact: cluster_group=tag_env_{{ cluster_id }} + - add_host: + name: "{{ item }}" + groups: oo_hosts_to_terminate + ansible_ssh_user: "{{ deployment_vars[deployment_type].ssh_user }}" + ansible_sudo: "{{ deployment_vars[deployment_type].sudo }}" + with_items: groups[cluster_group] | default([]) + +- hosts: oo_hosts_to_terminate + +- hosts: localhost + connection: local + gather_facts: no + vars_files: + - vars.yml + tasks: + - name: Retrieve the floating IPs + shell: "neutron floatingip-list | awk '/{{ hostvars[item].ansible_default_ipv4.address }}/ {print $2}'" + with_items: groups['oo_hosts_to_terminate'] | default([]) + register: floating_ips_to_delete + + - name: Terminate instance(s) + nova_compute: + name: "{{ hostvars[item].os_name }}" + state: absent + with_items: groups['oo_hosts_to_terminate'] | default([]) + + - name: Delete floating IPs + command: "neutron floatingip-delete {{ item.stdout }}" + with_items: floating_ips_to_delete.results | default([]) + + - name: Destroy the network + command: "heat stack-delete {{ openstack_network_prefix }}-stack" + register: stack_delete_result + changed_when: stack_delete_result.rc == 0 + failed_when: stack_delete_result.rc != 0 and 'could not be found' not in stack_delete_result.stdout diff --git a/playbooks/openstack/openshift-cluster/update.yml b/playbooks/openstack/openshift-cluster/update.yml new file mode 100644 index 000000000..5e7ab4e58 --- /dev/null +++ b/playbooks/openstack/openshift-cluster/update.yml @@ -0,0 +1,18 @@ +--- +- name: Populate oo_hosts_to_update group + hosts: localhost + gather_facts: no + vars_files: + - vars.yml + tasks: + - name: Evaluate oo_hosts_to_update + add_host: + name: "{{ item }}" + groups: oo_hosts_to_update + ansible_ssh_user: "{{ deployment_vars[deployment_type].ssh_user }}" + ansible_sudo: "{{ deployment_vars[deployment_type].sudo }}" + with_items: groups["tag_env-host-type_{{ cluster_id }}-openshift-master"] | union(groups["tag_env-host-type_{{ cluster_id }}-openshift-node"]) | default([]) + +- include: ../../common/openshift-cluster/update_repos_and_packages.yml + +- include: config.yml diff --git a/playbooks/openstack/openshift-cluster/vars.yml b/playbooks/openstack/openshift-cluster/vars.yml new file mode 100644 index 000000000..c754f19fc --- /dev/null +++ b/playbooks/openstack/openshift-cluster/vars.yml @@ -0,0 +1,39 @@ +--- +openstack_infra_heat_stack: "{{ opt_infra_heat_stack | default('files/heat_stack.yml') }}" +openstack_network_prefix: "{{ opt_network_prefix | default('openshift-ansible-'+cluster_id) }}" +openstack_network_cidr: "{{ opt_net_cidr | default('192.168.' + ( ( 1048576 | random % 256 ) | string() ) + '.0/24') }}" +openstack_network_external_net: "{{ opt_external_net | default('external') }}" +openstack_floating_ip_pools: "{{ opt_floating_ip_pools | default('external') | oo_split() }}" +openstack_network_dns: "{{ opt_dns | default('8.8.8.8,8.8.4.4') | oo_split() }}" +openstack_ssh_keypair: "{{ opt_keypair | default(lookup('env', 'LOGNAME')+'_key') }}" +openstack_ssh_public_key: "{{ lookup('file', opt_public_key | default('~/.ssh/id_rsa.pub')) }}" +openstack_ssh_access_from: "{{ opt_ssh_from | default('0.0.0.0/0') }}" +openstack_flavor: + master: + ram: "{{ opt_master_flavor_ram | default(2048) }}" + id: "{{ opt_master_flavor_id | default() }}" + include: "{{ opt_master_flavor_include | default() }}" + node: + ram: "{{ opt_node_flavor_ram | default(4096) }}" + id: "{{ opt_node_flavor_id | default() }}" + include: "{{ opt_node_flavor_include | default() }}" + +deployment_vars: + origin: + image: + name: "{{ opt_image_name | default('centos-70-raw') }}" + id: + ssh_user: openshift + sudo: yes + online: + image: + name: + id: + ssh_user: root + sudo: no + enterprise: + image: + name: "{{ opt_image_name | default('centos-70-raw') }}" + id: + ssh_user: openshift + sudo: yes diff --git a/roles/openshift_common/tasks/main.yml b/roles/openshift_common/tasks/main.yml index 5bd8690a7..c55677c3f 100644 --- a/roles/openshift_common/tasks/main.yml +++ b/roles/openshift_common/tasks/main.yml @@ -10,7 +10,6 @@ public_hostname: "{{ openshift_public_hostname | default(None) }}" public_ip: "{{ openshift_public_ip | default(None) }}" use_openshift_sdn: "{{ openshift_use_openshift_sdn | default(None) }}" - use_fluentd: "{{ openshift_use_fluentd | default(True) }}" deployment_type: "{{ openshift_deployment_type }}" - name: Set hostname hostname: name={{ openshift.common.hostname }} diff --git a/roles/openshift_facts/library/openshift_facts.py b/roles/openshift_facts/library/openshift_facts.py index 9c2657ff2..aff7d723e 100755 --- a/roles/openshift_facts/library/openshift_facts.py +++ b/roles/openshift_facts/library/openshift_facts.py @@ -288,6 +288,21 @@ def normalize_provider_facts(provider, metadata): facts = normalize_openstack_facts(metadata, facts) return facts +def set_fluentd_facts_if_unset(facts): + """ Set fluentd facts if not already present in facts dict + + Args: + facts (dict): existing facts + Returns: + dict: the facts dict updated with the generated fluentd facts if + missing + """ + if 'common' in facts: + deployment_type = facts['common']['deployment_type'] + if 'use_fluentd' not in facts['common']: + use_fluentd = True if deployment_type == 'online' else False + facts['common']['use_fluentd'] = use_fluentd + return facts def set_url_facts_if_unset(facts): """ Set url facts if not already present in facts dict @@ -560,6 +575,7 @@ class OpenShiftFacts(object): facts = merge_facts(facts, local_facts) facts['current_config'] = get_current_config(facts) facts = set_url_facts_if_unset(facts) + facts = set_fluentd_facts_if_unset(facts) return dict(openshift=facts) def get_defaults(self, roles): diff --git a/roles/openshift_node/tasks/main.yml b/roles/openshift_node/tasks/main.yml index 3d56bdd67..8af41b732 100644 --- a/roles/openshift_node/tasks/main.yml +++ b/roles/openshift_node/tasks/main.yml @@ -41,6 +41,9 @@ notify: - restart openshift-node +- name: Allow NFS access for VMs + seboolean: name=virt_use_nfs state=yes persistent=yes + - name: Start and enable openshift-node service: name=openshift-node enabled=yes state=started when: not openshift.common.use_openshift_sdn|bool diff --git a/roles/openshift_repos/files/online/repos/enterprise-v3.repo b/roles/openshift_repos/files/online/repos/enterprise-v3.repo index d324c142a..69c480f0a 100644 --- a/roles/openshift_repos/files/online/repos/enterprise-v3.repo +++ b/roles/openshift_repos/files/online/repos/enterprise-v3.repo @@ -1,10 +1,10 @@ [enterprise-v3] -name=OpenShift Enterprise Beta3 -baseurl=https://gce-mirror1.ops.rhcloud.com/libra/libra-7-ose-beta3/ - https://mirror.ops.rhcloud.com/libra/libra-7-ose-beta3/ +name=OpenShift Enterprise Beta4 +baseurl=https://mirror.ops.rhcloud.com/libra/libra-7-ose-beta4/ + https://gce-mirror1.ops.rhcloud.com/libra/libra-7-ose-beta4/ enabled=1 gpgcheck=0 failovermethod=priority sslverify=False sslclientcert=/var/lib/yum/client-cert.pem -sslclientkey=/var/lib/yum/client-key.pem
\ No newline at end of file +sslclientkey=/var/lib/yum/client-key.pem |