Bläddra i källkod

Openstack heat (#2)

* Adding a role to invoke openstack heat

* Adding readme

* Pulling parameters out to inventory file

* start of end-to-end playbook

* More enhancements and refactoring to make dynamic inventory the driver for an openshift install

* Switching to variable substituted path to config.yaml playbook

* Changes to allow defining of number of nodes/infranodes.

* Added labels to inventory

* Start of end-to-end functionality

* Enhancements to support openstack heat provisioning

* Updating inventory sample to remove some deprecation warnings

* Working towards making the secure-registry role 'become' aware

* Fixing node labels and removing secure-registry as it's no longer needed

* No longer need insecure registry line, as installer will secure our registry

* Adjusted dynamic inventory to filter by clusterid

* Minor updates to dynamic inventory bug

* Adding a refactored sample inventory directory

* Refactoring playbooks for better directory structure, and to narrow down host groups

* Adding volume mounts to heat template

* Moving dns playbooks back to original location

* Fixing incorrect file path

* Cleaning up inventory samples

* One more hostname to clean up

* Changing var name

* changed openshift-provision to openshift-prep

* Adjusting current provision script to avoid breakage by new openstack-heat code
Eric Sauer 8 år sedan
förälder
incheckning
11b48fe4e2

+ 5 - 0
roles/common/pre_tasks/pre_tasks.yml

@@ -15,6 +15,11 @@
     env_id: "{{ env_id | default(default_env_id) }}"
   delegate_to: localhost
 
+- name: Set Dynamic Inventory Filters
+  shell: >
+    export OS_INV_FILTER_KEY=clusterid && OS_INV_FILTER_VALUE={{ env_id }}
+  delegate_to: localhost
+
 - name: Updating DNS domain to include env_id (if not empty)
   set_fact:
     full_dns_domain: "{{ (env_id|trim == '') | ternary(dns_domain, env_id + '.' + dns_domain) }}"

+ 4 - 0
roles/openshift-prep/tasks/main.yml

@@ -0,0 +1,4 @@
+---
+# Starting Point for OpenShift Installation and Configuration
+- include: prerequisites.yml
+  tags: [prerequisites]

+ 36 - 0
roles/openshift-prep/tasks/prerequisites.yml

@@ -0,0 +1,36 @@
+---
+- name: "Cleaning yum repositories"
+  command: "yum clean all"
+  
+- name: "Install required packages"
+  yum:
+    name: "{{ item }}"
+    state: latest
+  with_items:
+    - wget
+    - git
+    - net-tools
+    - bind-utils
+    - bridge-utils
+    - bash-completion
+    - atomic-openshift-utils
+    - vim-enhanced
+
+- name: "Update all packages (this can take a very long time)"
+  yum:
+    name: "*"
+    state: latest
+
+- name: "Verify hostname"
+  shell: hostnamectl status | awk "/Static hostname/"'{ print $3 }'
+  register: hostname_fqdn
+
+- name: "Set hostname if required"
+  hostname:
+    name: "{{ ansible_fqdn }}"
+  when: hostname_fqdn.stdout != ansible_fqdn
+
+- name: "Verify SELinux is enforcing"
+  fail:
+    msg: "SELinux is required for OpenShift and has been detected as '{{ ansible_selinux.config_mode }}'"
+  when: ansible_selinux.config_mode != "enforcing"

+ 9 - 0
roles/openstack-stack/README.md

@@ -0,0 +1,9 @@
+# Role openstack-stack
+
+Role for spinning up instances using OpenStack Heat.
+
+## To Test
+
+```
+ansible-playbook casl-ansible/roles/openstack-stack/test/stack-create-test.yml
+```

+ 684 - 0
roles/openstack-stack/files/heat_stack.yaml

@@ -0,0 +1,684 @@
+heat_template_version: 2014-10-16
+
+description: OpenShift cluster
+
+parameters:
+
+  cluster_env:
+    type: string
+    label: Cluster environment
+    description: Environment of the cluster
+
+  cluster_id:
+    type: string
+    label: Cluster ID
+    description: Identifier of the cluster
+
+  subnet_24_prefix:
+    type: string
+    label: subnet /24 prefix
+    description: /24 subnet prefix of the network of the cluster (dot separated number triplet)
+
+  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_public_key:
+    type: string
+    label: SSH public key
+    description: SSH public key
+    hidden: true
+
+  ssh_incoming:
+    type: string
+    label: Source of ssh connections
+    description: Source of legitimate ssh connections
+    default: 0.0.0.0/0
+
+  node_port_incoming:
+    type: string
+    label: Source of node port connections
+    description: Authorized sources targetting node ports
+    default: 0.0.0.0/0
+
+  num_etcd:
+    type: number
+    label: Number of etcd nodes
+    description: Number of etcd nodes
+
+  num_masters:
+    type: number
+    label: Number of masters
+    description: Number of masters
+
+  num_nodes:
+    type: number
+    label: Number of compute nodes
+    description: Number of compute nodes
+
+  num_infra:
+    type: number
+    label: Number of infrastructure nodes
+    description: Number of infrastructure nodes
+
+  num_dns:
+    type: number
+    label: Number of dns servers
+    description: Number of dns servers
+
+  etcd_image:
+    type: string
+    label: Etcd image
+    description: Name of the image for the etcd servers
+
+  master_image:
+    type: string
+    label: Master image
+    description: Name of the image for the master servers
+
+  node_image:
+    type: string
+    label: Node image
+    description: Name of the image for the compute node servers
+
+  infra_image:
+    type: string
+    label: Infra image
+    description: Name of the image for the infra node servers
+
+  dns_image:
+    type: string
+    label: DNS image
+    description: Name of the image for the DNS server
+
+  etcd_flavor:
+    type: string
+    label: Etcd flavor
+    description: Flavor of the etcd servers
+
+  master_flavor:
+    type: string
+    label: Master flavor
+    description: Flavor of the master servers
+
+  node_flavor:
+    type: string
+    label: Node flavor
+    description: Flavor of the compute node servers
+
+  infra_flavor:
+    type: string
+    label: Infra flavor
+    description: Flavor of the infra node servers
+
+  dns_flavor:
+    type: string
+    label: DNS flavor
+    description: Flavor of the DNS server
+
+  master_volume_size:
+    type: number
+    description: Size of the volume to be created.
+    default: 5
+    constraints:
+      - range: { min: 1, max: 1024 }
+        description: must be between 1 and 1024 Gb.
+
+  app_volume_size:
+    type: number
+    description: Size of the volume to be created.
+    default: 5
+    constraints:
+      - range: { min: 1, max: 1024 }
+        description: must be between 1 and 1024 Gb.
+
+  infra_volume_size:
+    type: number
+    description: Size of the volume to be created.
+    default: 5
+    constraints:
+      - range: { min: 1, max: 1024 }
+        description: must be between 1 and 1024 Gb.
+
+  dns_volume_size:
+    type: number
+    description: Size of the volume to be created.
+    default: 5
+    constraints:
+      - range: { min: 1, max: 1024 }
+        description: must be between 1 and 1024 Gb.
+
+  etcd_volume_size:
+    type: number
+    description: Size of the volume to be created.
+    default: 5
+    constraints:
+      - range: { min: 1, max: 1024 }
+        description: must be between 1 and 1024 Gb.
+
+outputs:
+
+  etcd_names:
+    description: Name of the etcds
+    value: { get_attr: [ etcd, name ] }
+
+  etcd_ips:
+    description: IPs of the etcds
+    value: { get_attr: [ etcd, private_ip ] }
+
+  etcd_floating_ips:
+    description: Floating IPs of the etcds
+    value: { get_attr: [ etcd, floating_ip ] }
+
+  master_names:
+    description: Name of the masters
+    value: { get_attr: [ masters, name ] }
+
+  master_ips:
+    description: IPs of the masters
+    value: { get_attr: [ masters, private_ip ] }
+
+  master_floating_ips:
+    description: Floating IPs of the masters
+    value: { get_attr: [ masters, floating_ip ] }
+
+  node_names:
+    description: Name of the nodes
+    value: { get_attr: [ compute_nodes, name ] }
+
+  node_ips:
+    description: IPs of the nodes
+    value: { get_attr: [ compute_nodes, private_ip ] }
+
+  node_floating_ips:
+    description: Floating IPs of the nodes
+    value: { get_attr: [ compute_nodes, floating_ip ] }
+
+  infra_names:
+    description: Name of the nodes
+    value: { get_attr: [ infra_nodes, name ] }
+
+  infra_ips:
+    description: IPs of the nodes
+    value: { get_attr: [ infra_nodes, private_ip ] }
+
+  infra_floating_ips:
+    description: Floating IPs of the nodes
+    value: { get_attr: [ infra_nodes, floating_ip ] }
+
+  dns_name:
+    description: Name of the DNS
+    value:
+      get_attr:
+        - dns
+        - name
+
+  dns_floating_ip:
+    description: Floating IP of the DNS
+    value:
+      get_attr:
+        - dns
+        - addresses
+        - str_replace:
+            template: openshift-ansible-cluster_id-net
+            params:
+              cluster_id: { get_param: cluster_id }
+        - 1
+        - addr
+
+resources:
+
+  net:
+    type: OS::Neutron::Net
+    properties:
+      name:
+        str_replace:
+          template: openshift-ansible-cluster_id-net
+          params:
+            cluster_id: { get_param: cluster_id }
+
+  subnet:
+    type: OS::Neutron::Subnet
+    properties:
+      name:
+        str_replace:
+          template: openshift-ansible-cluster_id-subnet
+          params:
+            cluster_id: { get_param: cluster_id }
+      network: { get_resource: net }
+      cidr:
+        str_replace:
+          template: subnet_24_prefix.0/24
+          params:
+            subnet_24_prefix: { get_param: subnet_24_prefix }
+      allocation_pools:
+        - start:
+            str_replace:
+              template: subnet_24_prefix.3
+              params:
+                subnet_24_prefix: { get_param: subnet_24_prefix }
+          end:
+            str_replace:
+              template: subnet_24_prefix.254
+              params:
+                subnet_24_prefix: { get_param: subnet_24_prefix }
+      dns_nameservers:
+        - 10.9.48.31
+#        - { get_param: dns_nameservers }
+#        repeat:
+#          for_each: 
+#            <%nameserver%>: { get_param: dns_nameservers }
+#          template: <%nameserver%>
+
+  router:
+    type: OS::Neutron::Router
+    properties:
+      name:
+        str_replace:
+          template: openshift-ansible-cluster_id-router
+          params:
+            cluster_id: { get_param: cluster_id }
+      external_gateway_info:
+        network: { get_param: external_net }
+
+  interface:
+    type: OS::Neutron::RouterInterface
+    properties:
+      router_id: { get_resource: router }
+      subnet_id: { get_resource: subnet }
+
+#  keypair:
+#    type: OS::Nova::KeyPair
+#    properties:
+#      name:
+#        str_replace:
+#          template: openshift-ansible-cluster_id-keypair
+#          params:
+#            cluster_id: { get_param: cluster_id }
+#      public_key: { get_param: ssh_public_key }
+
+  master-secgrp:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      name:
+        str_replace:
+          template: openshift-ansible-cluster_id-master-secgrp
+          params:
+            cluster_id: { get_param: cluster_id }
+      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: 8444
+          port_range_max: 8444
+        - 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: 8053
+          port_range_max: 8053
+        - direction: ingress
+          protocol: udp
+          port_range_min: 8053
+          port_range_max: 8053
+        - direction: ingress
+          protocol: tcp
+          port_range_min: 24224
+          port_range_max: 24224
+        - direction: ingress
+          protocol: udp
+          port_range_min: 24224
+          port_range_max: 24224
+        - direction: ingress
+          protocol: tcp
+          port_range_min: 2224
+          port_range_max: 2224
+        - direction: ingress
+          protocol: udp
+          port_range_min: 5404
+          port_range_max: 5404
+        - direction: ingress
+          protocol: udp
+          port_range_min: 5405
+          port_range_max: 5405
+        - direction: ingress
+          protocol: tcp
+          port_range_min: 9090
+          port_range_max: 9090
+
+  etcd-secgrp:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      name:
+        str_replace:
+          template: openshift-ansible-cluster_id-etcd-secgrp
+          params:
+            cluster_id: { get_param: cluster_id }
+      description:
+        str_replace:
+          template: Security group for cluster_id etcd cluster
+          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: 2379
+          port_range_max: 2379
+          remote_mode: remote_group_id
+          remote_group_id: { get_resource: master-secgrp }
+        - direction: ingress
+          protocol: tcp
+          port_range_min: 2380
+          port_range_max: 2380
+          remote_mode: remote_group_id
+
+  node-secgrp:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      name:
+        str_replace:
+          template: openshift-ansible-cluster_id-node-secgrp
+          params:
+            cluster_id: { get_param: cluster_id }
+      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: tcp
+          port_range_min: 10250
+          port_range_max: 10250
+          remote_mode: remote_group_id
+        - direction: ingress
+          protocol: tcp
+          port_range_min: 10255
+          port_range_max: 10255
+          remote_mode: remote_group_id
+        - direction: ingress
+          protocol: udp
+          port_range_min: 10255
+          port_range_max: 10255
+          remote_mode: remote_group_id
+        - direction: ingress
+          protocol: udp
+          port_range_min: 4789
+          port_range_max: 4789
+          remote_mode: remote_group_id
+        - direction: ingress
+          protocol: tcp
+          port_range_min: 30000
+          port_range_max: 32767
+          remote_ip_prefix: { get_param: node_port_incoming }
+
+  infra-secgrp:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      name:
+        str_replace:
+          template: openshift-ansible-cluster_id-infra-secgrp
+          params:
+            cluster_id: { get_param: cluster_id }
+      description:
+        str_replace:
+          template: Security group for cluster_id OpenShift infrastructure cluster nodes
+          params:
+            cluster_id: { get_param: cluster_id }
+      rules:
+        - direction: ingress
+          protocol: tcp
+          port_range_min: 80
+          port_range_max: 80
+        - direction: ingress
+          protocol: tcp
+          port_range_min: 443
+          port_range_max: 443
+
+  dns-secgrp:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      name:
+        str_replace:
+          template: openshift-ansible-cluster_id-dns-secgrp
+          params:
+            cluster_id: { get_param: cluster_id }
+      description:
+        str_replace:
+          template: Security group for cluster_id cluster DNS
+          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: 53
+          port_range_max: 53
+          remote_ip_prefix: { get_param: node_port_incoming }
+        - direction: ingress
+          protocol: tcp
+          port_range_min: 53
+          port_range_max: 53
+          remote_ip_prefix: { get_param: node_port_incoming }
+
+  etcd:
+    type: OS::Heat::ResourceGroup
+    properties:
+      count: { get_param: num_etcd }
+      resource_def:
+        type: heat_stack_server.yaml
+        properties:
+          name:
+            str_replace:
+              template: k8s_type-%index%.cluster_id.cluster_env
+              params:
+                cluster_id: { get_param: cluster_id }
+                k8s_type: etcd
+                cluster_env: { get_param: cluster_env }
+          cluster_env: { get_param: cluster_env }
+          cluster_id:  { get_param: cluster_id }
+          type:        etcd
+          image:       { get_param: etcd_image }
+          flavor:      { get_param: etcd_flavor }
+          key_name:    { get_param: ssh_public_key }
+          net:         { get_resource: net }
+          subnet:      { get_resource: subnet }
+          secgrp:
+            - { get_resource: etcd-secgrp }
+          floating_network: { get_param: external_net }
+          net_name:
+            str_replace:
+              template: openshift-ansible-cluster_id-net
+              params:
+                cluster_id: { get_param: cluster_id }
+          volume_size: { get_param: etcd_volume_size }
+    depends_on:
+      - interface
+
+  masters:
+    type: OS::Heat::ResourceGroup
+    properties:
+      count: { get_param: num_masters }
+      resource_def:
+        type: heat_stack_server.yaml
+        properties:
+          name:
+            str_replace:
+              template: k8s_type-%index%.cluster_id.cluster_env
+              params:
+                cluster_id: { get_param: cluster_id }
+                k8s_type: master
+                cluster_env: { get_param: cluster_env }
+          cluster_env: { get_param: cluster_env }
+          cluster_id:  { get_param: cluster_id }
+          type:        master
+          image:       { get_param: master_image }
+          flavor:      { get_param: master_flavor }
+          key_name:    { get_param: ssh_public_key }
+          net:         { get_resource: net }
+          subnet:      { get_resource: subnet }
+          secgrp:
+            - { get_resource: master-secgrp }
+            - { get_resource: node-secgrp }
+          floating_network: { get_param: external_net }
+          net_name:
+            str_replace:
+              template: openshift-ansible-cluster_id-net
+              params:
+                cluster_id: { get_param: cluster_id }
+          volume_size: { get_param: master_volume_size }
+    depends_on:
+      - interface
+
+  compute_nodes:
+    type: OS::Heat::ResourceGroup
+    properties:
+      count: { get_param: num_nodes }
+      resource_def:
+        type: heat_stack_server.yaml
+        properties:
+          name:
+            str_replace:
+              template: subtype-k8s_type-%index%.cluster_id.cluster_env
+              params:
+                cluster_id: { get_param: cluster_id }
+                k8s_type: node
+                subtype: app
+                cluster_env: { get_param: cluster_env }
+          cluster_env: { get_param: cluster_env }
+          cluster_id:  { get_param: cluster_id }
+          type:        node
+          subtype:     app
+          image:       { get_param: node_image }
+          flavor:      { get_param: node_flavor }
+          key_name:    { get_param: ssh_public_key }
+          net:         { get_resource: net }
+          subnet:      { get_resource: subnet }
+          secgrp:
+            - { get_resource: node-secgrp }
+          floating_network: { get_param: external_net }
+          net_name:
+            str_replace:
+              template: openshift-ansible-cluster_id-net
+              params:
+                cluster_id: { get_param: cluster_id }
+          volume_size: { get_param: app_volume_size }
+    depends_on:
+      - interface
+
+  infra_nodes:
+    type: OS::Heat::ResourceGroup
+    properties:
+      count: { get_param: num_infra }
+      resource_def:
+        type: heat_stack_server.yaml
+        properties:
+          name:
+            str_replace:
+              template: subtypek8s_type-%index%.cluster_id.cluster_env
+              params:
+                cluster_id: { get_param: cluster_id }
+                k8s_type: node
+                subtype: infra
+                cluster_env: { get_param: cluster_env }
+          cluster_env: { get_param: cluster_env }
+          cluster_id:  { get_param: cluster_id }
+          type:        node
+          subtype:     infra
+          image:       { get_param: infra_image }
+          flavor:      { get_param: infra_flavor }
+          key_name:    { get_param: ssh_public_key }
+          net:         { get_resource: net }
+          subnet:      { get_resource: subnet }
+          secgrp:
+            - { get_resource: node-secgrp }
+            - { get_resource: infra-secgrp }
+          floating_network: { get_param: external_net }
+          net_name:
+            str_replace:
+              template: openshift-ansible-cluster_id-net
+              params:
+                cluster_id: { get_param: cluster_id }
+          volume_size: { get_param: infra_volume_size }
+    depends_on:
+      - interface
+
+  dns:
+    type: OS::Heat::ResourceGroup
+    properties:
+      count: { get_param: num_dns }
+      resource_def:
+        type: heat_stack_server.yaml
+        properties:
+          name:
+            str_replace:
+              template: k8s_type-%index%.cluster_id.cluster_env
+              params:
+                cluster_id: { get_param: cluster_id }
+                k8s_type: dns
+                cluster_env: { get_param: cluster_env }
+          cluster_env: { get_param: cluster_env }
+          cluster_id:  { get_param: cluster_id }
+          type:        dns
+          image:       { get_param: dns_image }
+          flavor:      { get_param: dns_flavor }
+          key_name:    { get_param: ssh_public_key }
+          net:         { get_resource: net }
+          subnet:      { get_resource: subnet }
+          secgrp:
+            - { get_resource: node-secgrp }
+            - { get_resource: dns-secgrp }
+          floating_network: { get_param: external_net }
+          net_name:
+            str_replace:
+              template: openshift-ansible-cluster_id-net
+              params:
+                cluster_id: { get_param: cluster_id }
+          volume_size: { get_param: dns_volume_size }
+    depends_on:
+      - interface
+

+ 156 - 0
roles/openstack-stack/files/heat_stack_server.yaml

@@ -0,0 +1,156 @@
+heat_template_version: 2014-10-16
+
+description: OpenShift cluster server
+
+parameters:
+
+  name:
+    type: string
+    label: Name
+    description: Name
+
+  cluster_env:
+    type: string
+    label: Cluster environment
+    description: Environment of the cluster
+
+  cluster_id:
+    type: string
+    label: Cluster ID
+    description: Identifier of the cluster
+
+  type:
+    type: string
+    label: Type
+    description: Type master or node
+
+  subtype:
+    type: string
+    label: Sub-type
+    description: Sub-type compute or infra for nodes, default otherwise
+    default: default
+
+  key_name:
+    type: string
+    label: Key name
+    description: Key name of keypair
+
+  image:
+    type: string
+    label: Image
+    description: Name of the image
+
+  flavor:
+    type: string
+    label: Flavor
+    description: Name of the flavor
+
+  net:
+    type: string
+    label: Net ID
+    description: Net resource
+
+  net_name:
+    type: string
+    label: Net name
+    description: Net name
+
+  subnet:
+    type: string
+    label: Subnet ID
+    description: Subnet resource
+
+  secgrp:
+    type: comma_delimited_list
+    label: Security groups
+    description: Security group resources
+
+  floating_network:
+    type: string
+    label: Floating network
+    description: Network to allocate floating IP from
+
+  availability_zone:
+    type: string
+    description: The Availability Zone to launch the instance.
+    default: nova
+
+  volume_size:
+    type: number
+    description: Size of the volume to be created.
+    default: 1
+    constraints:
+      - range: { min: 1, max: 1024 }
+        description: must be between 1 and 1024 Gb.
+
+outputs:
+
+  name:
+    description: Name of the server
+    value: { get_attr: [ server, name ] }
+
+  private_ip:
+    description: Private IP of the server
+    value:
+      get_attr:
+        - server
+        - addresses
+        - { get_param: net_name }
+        - 0
+        - addr
+
+  floating_ip:
+    description: Floating IP of the server
+    value:
+      get_attr:
+        - server
+        - addresses
+        - { get_param: net_name }
+        - 1
+        - addr
+
+resources:
+
+  server:
+    type: OS::Nova::Server
+    properties:
+      name:      { get_param: name }
+      key_name:  { get_param: key_name }
+      image:     { get_param: image }
+      flavor:    { get_param: flavor }
+      networks:
+        - port:  { get_resource: port }
+      user_data: { get_file: user-data }
+      user_data_format: RAW
+      metadata:
+        environment: { get_param: cluster_env }
+        clusterid: { get_param: cluster_id }
+        host-type: { get_param: type }
+        sub-host-type:    { get_param: subtype }
+
+  port:
+    type: OS::Neutron::Port
+    properties:
+      network: { get_param: net }
+      fixed_ips:
+        - subnet: { get_param: subnet }
+      security_groups: { get_param: secgrp }
+
+  floating-ip:
+    type: OS::Neutron::FloatingIP
+    properties:
+      floating_network: { get_param: floating_network }
+      port_id: { get_resource: port }
+
+  cinder_volume:
+    type: OS::Cinder::Volume
+    properties:
+      size: { get_param: volume_size }
+      availability_zone: { get_param: availability_zone }
+
+  volume_attachment:
+    type: OS::Cinder::VolumeAttachment
+    properties:
+      volume_id: { get_resource: cinder_volume }
+      instance_uuid: { get_resource: server }
+      mountpoint: /dev/sdb

+ 13 - 0
roles/openstack-stack/files/user-data

@@ -0,0 +1,13 @@
+#cloud-config
+disable_root: true
+
+system_info:
+  default_user:
+    name: openshift
+    sudo: ["ALL=(ALL) NOPASSWD: ALL"]
+
+write_files:
+  - path: /etc/sudoers.d/00-openshift-no-requiretty
+    permissions: 440
+    content: |
+      Defaults:openshift !requiretty

+ 31 - 0
roles/openstack-stack/tasks/main.yml

@@ -0,0 +1,31 @@
+---
+- name: create stack
+  ignore_errors: False
+  register: stack_create
+  os_stack:
+    name: "{{ stack_name }}"
+    state: present
+    template: 'roles/openstack-stack/files/heat_stack.yaml'
+    wait: yes
+    parameters:
+      cluster_env: "{{ dns_domain }}"
+      cluster_id: "{{ stack_name }}"
+      subnet_24_prefix: "{{ subnet_prefix }}"
+      dns_nameservers: "{{ dns_nameservers }}"
+      external_net: "{{ external_network }}"
+      ssh_public_key: "{{ ssh_public_key }}"
+      num_etcd: "{{ num_etcd }}"
+      num_masters: "{{ num_masters }}"
+      num_nodes: "{{ num_nodes }}"
+      num_infra: "{{ num_infra }}"
+      num_dns: "{{ num_dns }}"
+      etcd_image: "{{ openstack_image }}"
+      master_image: "{{ openstack_image }}"
+      node_image: "{{ openstack_image }}"
+      infra_image: "{{ openstack_image }}"
+      dns_image: "{{ openstack_image }}"
+      etcd_flavor: "{{ etcd_flavor }}"
+      master_flavor: "{{ master_flavor }}"
+      node_flavor: "{{ node_flavor }}"
+      infra_flavor: "{{ infra_flavor }}"
+      dns_flavor: "{{ dns_flavor }}"

+ 1 - 0
roles/openstack-stack/test/roles

@@ -0,0 +1 @@
+../../../roles/

+ 17 - 0
roles/openstack-stack/test/stack-create-test.yml

@@ -0,0 +1,17 @@
+---
+- hosts: localhost
+  roles:
+  - role: openstack-stack
+    stack_name: test-stack
+    dns_domain: "{{ openstack_dns_domain }}"
+    dns_nameservers: "{{ openstack_nameservers }}"
+    subnet_prefix: "{{ openstack_subnet_prefix }}"
+    ssh_public_key: "{{ openstack_ssh_public_key }}"
+    openstack_image: "{{ openstack_default_image_name }}"
+    etcd_flavor: "{{ openstack_default_flavor }}"
+    master_flavor: "{{ openstack_default_flavor }}"
+    node_flavor: "{{ openstack_default_flavor }}"
+    infra_flavor: "{{ openstack_default_flavor }}"
+    dns_flavor: "{{ openstack_default_flavor }}"
+    external_network: "{{ openstack_external_network_name }}"
+