From 901f0ee491efb34f9788e11dd6d572928146da91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9na=C3=AFc=20Huard?= Date: Mon, 20 Apr 2015 14:11:48 +0200 Subject: Implement OpenStack provider --- README_openstack.md | 80 ++++++++ bin/cluster | 13 +- filter_plugins/oo_filters.py | 7 + inventory/openstack/hosts/hosts | 1 + inventory/openstack/hosts/nova.ini | 45 +++++ inventory/openstack/hosts/nova.py | 224 +++++++++++++++++++++ playbooks/openstack/openshift-cluster/config.yml | 34 ++++ .../openshift-cluster/files/heat_stack.yml | 149 ++++++++++++++ .../openstack/openshift-cluster/files/user-data | 7 + .../openstack/openshift-cluster/filter_plugins | 1 + playbooks/openstack/openshift-cluster/launch.yml | 31 +++ playbooks/openstack/openshift-cluster/list.yml | 24 +++ playbooks/openstack/openshift-cluster/roles | 1 + .../tasks/configure_openstack.yml | 27 +++ .../openshift-cluster/tasks/launch_instances.yml | 48 +++++ .../openstack/openshift-cluster/terminate.yml | 43 ++++ playbooks/openstack/openshift-cluster/update.yml | 18 ++ playbooks/openstack/openshift-cluster/vars.yml | 39 ++++ 18 files changed, 790 insertions(+), 2 deletions(-) create mode 100644 README_openstack.md create mode 100644 inventory/openstack/hosts/hosts create mode 100644 inventory/openstack/hosts/nova.ini create mode 100755 inventory/openstack/hosts/nova.py create mode 100644 playbooks/openstack/openshift-cluster/config.yml create mode 100644 playbooks/openstack/openshift-cluster/files/heat_stack.yml create mode 100644 playbooks/openstack/openshift-cluster/files/user-data create mode 120000 playbooks/openstack/openshift-cluster/filter_plugins create mode 100644 playbooks/openstack/openshift-cluster/launch.yml create mode 100644 playbooks/openstack/openshift-cluster/list.yml create mode 120000 playbooks/openstack/openshift-cluster/roles create mode 100644 playbooks/openstack/openshift-cluster/tasks/configure_openstack.yml create mode 100644 playbooks/openstack/openshift-cluster/tasks/launch_instances.yml create mode 100644 playbooks/openstack/openshift-cluster/terminate.yml create mode 100644 playbooks/openstack/openshift-cluster/update.yml create mode 100644 playbooks/openstack/openshift-cluster/vars.yml 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-`): 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..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 +``` + +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 +``` + +Terminating a cluster +--------------------- + +1. To terminate the cluster + +``` + bin/cluster terminate openstack +``` diff --git a/bin/cluster b/bin/cluster index bf8198de9..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()]) ) @@ -189,13 +196,13 @@ if __name__ == '__main__': [DEFAULT] validate_cluster_ids = False cluster_ids = marketing,sales - providers = gce,aws,libvirt + providers = gce,aws,libvirt,openstack """ environment = ConfigParser.SafeConfigParser({ 'cluster_ids': 'marketing,sales', 'validate_cluster_ids': 'False', - 'providers': 'gce,aws,libvirt', + 'providers': 'gce,aws,libvirt,openstack', }) path = os.path.expanduser("~/.openshift-ansible") @@ -224,6 +231,8 @@ if __name__ == '__main__': 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/filter_plugins/oo_filters.py b/filter_plugins/oo_filters.py index 33d5e6cc3..f705b2c7f 100644 --- a/filter_plugins/oo_filters.py +++ b/filter_plugins/oo_filters.py @@ -203,6 +203,12 @@ 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 { @@ -215,4 +221,5 @@ class FilterModule(object): "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 +# +# 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 . + +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 " + sys.exit(1) 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 "/\\/ {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 -- cgit v1.2.3