summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Wiest <twiest@users.noreply.github.com>2015-06-08 10:57:35 -0400
committerThomas Wiest <twiest@users.noreply.github.com>2015-06-08 10:57:35 -0400
commitd30276dac07b3df32aa02711e27e94e90d925c89 (patch)
tree66074483c40a232a00c4cd3b87633d3431bd2ed6
parente03efb3d8570981ab818bf1b18c01e3573977ba3 (diff)
parent901f0ee491efb34f9788e11dd6d572928146da91 (diff)
downloadopenshift-d30276dac07b3df32aa02711e27e94e90d925c89.tar.gz
openshift-d30276dac07b3df32aa02711e27e94e90d925c89.tar.bz2
openshift-d30276dac07b3df32aa02711e27e94e90d925c89.tar.xz
openshift-d30276dac07b3df32aa02711e27e94e90d925c89.zip
Merge pull request #186 from lhuard1A/os
Implement OpenStack provider for openshift-ansible
-rw-r--r--README_openstack.md80
-rwxr-xr-xbin/cluster13
-rw-r--r--filter_plugins/oo_filters.py7
-rw-r--r--inventory/openstack/hosts/hosts1
-rw-r--r--inventory/openstack/hosts/nova.ini45
-rwxr-xr-xinventory/openstack/hosts/nova.py224
-rw-r--r--playbooks/openstack/openshift-cluster/config.yml34
-rw-r--r--playbooks/openstack/openshift-cluster/files/heat_stack.yml149
-rw-r--r--playbooks/openstack/openshift-cluster/files/user-data7
l---------playbooks/openstack/openshift-cluster/filter_plugins1
-rw-r--r--playbooks/openstack/openshift-cluster/launch.yml31
-rw-r--r--playbooks/openstack/openshift-cluster/list.yml24
l---------playbooks/openstack/openshift-cluster/roles1
-rw-r--r--playbooks/openstack/openshift-cluster/tasks/configure_openstack.yml27
-rw-r--r--playbooks/openstack/openshift-cluster/tasks/launch_instances.yml48
-rw-r--r--playbooks/openstack/openshift-cluster/terminate.yml43
-rw-r--r--playbooks/openstack/openshift-cluster/update.yml18
-rw-r--r--playbooks/openstack/openshift-cluster/vars.yml39
18 files changed, 790 insertions, 2 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 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 <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/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