Add role to create and delete the OVB stack

For environments where OVB is run on an Openstack
host cloud, this role contains the functionality
to manage the heat stack.
The associated playbooks are included.

Change-Id: I6270000613b1883a4f729faee3d255fd08624c13
This commit is contained in:
Ronelle Landy 2016-11-22 18:05:01 -05:00
parent dbac16dfb4
commit bdada2eca5
13 changed files with 780 additions and 0 deletions

View File

@ -0,0 +1,18 @@
---
- name: Create the OVB stack
hosts: localhost
roles:
- { role: ovb-manage-stack, ovb_manage_stack_mode: 'create' }
- name: Inventory the undercloud instance
hosts: localhost
gather_facts: yes
roles:
- { role: tripleo-inventory }
- name: Setup the undercloud
hosts: undercloud
vars:
ansible_ssh_user: root
roles:
- { role: ovb-manage-stack, ovb_manage_stack_mode: 'setup-undercloud' }

View File

@ -0,0 +1,5 @@
---
- name: clean up heat stack
hosts: localhost
roles:
- { role: ovb-manage-stack, ovb_manage_stack_mode: 'delete' }

View File

@ -0,0 +1,90 @@
Role Name
=========
Ansible roles for managing a heat stack to deploy an OpenStack cloud using OpenStack Virtual Baremetal.
Requirements
------------
These roles assume that the host cloud has already been patched as per
https://github.com/cybertron/openstack-virtual-baremetal/blob/master/README.rst#patching-the-host-cloud.
Role Variables
--------------
**Note:** Make sure to include all environment file and options from your [initial Overcloud creation](http://docs.openstack.org/developer/tripleo-docs/basic_deployment/basic_deployment_cli.html#deploy-the-overcloud)
To interact with the Openstack Virtual Baremetal host cloud, credentials are needed:
- os_username: <cloud_username>
- os_password: <user_password>
- os_tenant_name: <tenant_name>
- os_auth_url: <cloud_auth_url> # For example http://190.1.1.5:5000/v2.0
Parameters required to access the stack:
- prefix --used as in id for the image and the stack parameters
- stack_name: <'oooq-{{ prefix }}stack'> -- name for OVB heat stack
- rc_file: </home/stack/overcloudrc> -- file to reference the overcloud
- node_name: 'undercloud'
- existing_key_location: <local_working_dir> -- required to access the undercloud node
- ssh_extra_args: <'-F "{{ local_working_dir }}/ssh.config.ansible"'>
- undercoud_key: <"{{ local_working_dir }}/id_rsa_undercloud">
Parameters required for shade (See defaults/main.yml for default values):
- heat_template
- environment_list
Parameters used the env.yaml file to create the OVB heat stack (See defaults/main.yml for default values):
- bmc_flavor
- bmc_image
- bmc_prefix
- baremetal_flavor
- baremetal_image
- baremetal_prefix
- key_name
- private_net
- node_count
- public_net
- provision_net
- undercloud_name
- undercloud_image
- undercloud_flavor
- external_net
- templates_dir
- ovb_dir
- network_isolation_type: <multi-nic> -- other options are 'none' and 'public-bond'
Parameters required to setup the undercloud:
- mtu: <1350>
- mtu_interface: <eth1>
- pvt_nameserver: <8.8.8.8> -- on some internal clouds external servers are blocked
- external_interface: <eth2>
- external_interface_ip: <10.0.0.1>
- external_interface_netmask: <255.255.255.0>
- registered_releases -- releases for which images should be available for the undercloud
Dependencies
------------
This playbook depends on the shade library and https://github.com/cybertron/openstack-virtual-baremetal.
Example Playbook
----------------
Playbooks to create the strack prior to TripleO Quickstart deployments will require:
- name: Create the OVB stack
hosts: localhost
roles:
- { role: ovb-manage-stack, ovb_manage_stack_mode: 'create' }
License
-------
Apache
Author Information
------------------
RDO-CI Team

View File

@ -0,0 +1,78 @@
---
# defaults for all ovb-stack related tasks
local_working_dir: "{{ lookup('env', 'HOME') }}/.quickstart"
working_dir: /home/stack
release: mitaka
node:
prefix:
- "{{ 1000 |random }}"
- "{{ lookup('env', 'USER') }}"
- "{{ lookup('env', 'BUILD_NUMBER') }}"
tmp:
node_prefix: '{{ node.prefix | reject("none") | join("-") }}-'
os_username: admin
os_password: password
os_tenant_name: admin
os_auth_url: 'http://10.0.1.10:5000/v2.0'
cloud_name: qeos7
stack_name: 'oooq-{{ prefix }}stack'
rc_file: /home/stack/overcloudrc
node_name: 'undercloud'
ssh_extra_args: '-F "{{ local_working_dir }}/ssh.config.ansible"'
undercoud_key: "{{ local_working_dir }}/id_rsa_undercloud"
node_groups:
- 'undercloud'
- 'tester'
templates_dir: "{{ local_working_dir }}/openstack-virtual-baremetal/templates"
ovb_dir: "{{ local_working_dir }}/openstack-virtual-baremetal"
heat_template: "{{ templates_dir }}/quintupleo.yaml"
environment_list:
- "{{ templates_dir }}/resource-registry.yaml"
- "{{ local_working_dir }}/{{ prefix }}env.yaml"
existing_key_location: '{{ local_working_dir }}'
remove_image_from_host_cloud: false
bmc_flavor: m1.medium
bmc_image: 'bmc-base'
bmc_prefix: '{{ prefix }}bmc'
baremetal_flavor: m1.large
baremetal_image: 'ipxe-boot'
baremetal_prefix: '{{ prefix }}baremetal'
key_name: '{{ prefix }}key'
private_net: '{{ prefix }}private'
node_count: 2
public_net: '{{ prefix }}public'
provision_net: '{{ prefix }}provision'
# QuintupleO-specific params ignored by virtual-baremetal.yaml
undercloud_name: '{{ prefix }}undercloud'
undercloud_image: '{{ prefix }}undercloud.qcow2'
undercloud_flavor: m1.xlarge
external_net: '10.2.1.0/22'
network_isolation_type: multi-nic
setup_undercloud_connectivity_log: "{{ working_dir }}/setup_undercloud_connectivity.log"
mtu: 1350
mtu_interface:
- eth1
pvt_nameserver: 8.8.8.8
external_interface: eth2
external_interface_ip: 10.0.0.1
external_interface_netmask: 255.255.255.0
registered_releases:
- mitaka
- newton
- master
- rhos-9

View File

@ -0,0 +1,262 @@
#!/usr/bin/python
#coding: utf-8 -*-
# (c) 2016, Mathieu Bultel <mbultel@redhat.com>
# (c) 2016, Steve Baker <sbaker@redhat.com>
#
# This module 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.
#
# This software 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 this software. If not, see <http://www.gnu.org/licenses/>.
from time import sleep
from distutils.version import StrictVersion
try:
import shade
HAS_SHADE = True
except ImportError:
HAS_SHADE = False
DOCUMENTATION = '''
---
module: os_stack
short_description: Add/Remove Heat Stack
extends_documentation_fragment: openstack
version_added: "2.2"
author: "Mathieu Bultel (matbu), Steve Baker (steveb)"
description:
- Add or Remove a Stack to an OpenStack Heat
options:
state:
description:
- Indicate desired state of the resource
choices: ['present', 'absent']
required: false
default: present
name:
description:
- Name of the stack that should be created, name could be char and digit, no space
required: true
template:
description:
- Path of the template file to use for the stack creation
required: false
default: None
environment:
description:
- List of environment files that should be used for the stack creation
required: false
default: None
parameters:
description:
- Dictionary of parameters for the stack creation
required: false
default: None
rollback:
description:
- Rollback stack creation
required: false
default: false
timeout:
description:
- Maximum number of seconds to wait for the stack creation
required: false
default: 3600
requirements:
- "python >= 2.6"
- "shade"
'''
EXAMPLES = '''
---
- name: create stack
ignore_errors: True
register: stack_create
os_stack:
name: "{{ stack_name }}"
state: present
template: "/path/to/my_stack.yaml"
environment:
- /path/to/resource-registry.yaml
- /path/to/environment.yaml
parameters:
bmc_flavor: m1.medium
bmc_image: CentOS
key_name: default
private_net: {{ private_net_param }}
node_count: 2
name: undercloud
image: CentOS
my_flavor: m1.large
external_net: {{ external_net_param }}
'''
RETURN = '''
id:
description: Stack ID.
type: string
sample: "97a3f543-8136-4570-920e-fd7605c989d6"
stack:
action:
description: Action, could be Create or Update.
type: string
sample: "CREATE"
creation_time:
description: Time when the action has been made.
type: string
sample: "2016-07-05T17:38:12Z"
description:
description: Description of the Stack provided in the heat template.
type: string
sample: "HOT template to create a new instance and networks"
id:
description: Stack ID.
type: string
sample: "97a3f543-8136-4570-920e-fd7605c989d6"
name:
description: Name of the Stack
type: string
sample: "test-stack"
identifier:
description: Identifier of the current Stack action.
type: string
sample: "test-stack/97a3f543-8136-4570-920e-fd7605c989d6"
links:
description: Links to the current Stack.
type: list of dict
sample: "[{'href': 'http://foo:8004/v1/7f6a/stacks/test-stack/97a3f543-8136-4570-920e-fd7605c989d6']"
outputs:
description: Output returned by the Stack.
type: list of dict
sample: "{'description': 'IP address of server1 in private network',
'output_key': 'server1_private_ip',
'output_value': '10.1.10.103'}"
parameters:
description: Parameters of the current Stack
type: dict
sample: "{'OS::project_id': '7f6a3a3e01164a4eb4eecb2ab7742101',
'OS::stack_id': '97a3f543-8136-4570-920e-fd7605c989d6',
'OS::stack_name': 'test-stack',
'stack_status': 'CREATE_COMPLETE',
'stack_status_reason': 'Stack CREATE completed successfully',
'status': 'COMPLETE',
'template_description': 'HOT template to create a new instance and networks',
'timeout_mins': 60,
'updated_time': null}"
'''
def _create_stack(module, stack, cloud):
try:
stack = cloud.create_stack(module.params['name'],
template_file=module.params['template'],
environment_files=module.params['environment'],
timeout=module.params['timeout'],
wait=True,
rollback=module.params['rollback'],
**module.params['parameters'])
stack = cloud.get_stack(stack.id, None)
if stack.stack_status == 'CREATE_COMPLETE':
return stack
else:
return False
module.fail_json(msg = "Failure in creating stack: ".format(stack))
except shade.OpenStackCloudException as e:
module.fail_json(msg=str(e))
def _update_stack(module, stack, cloud):
try:
stack = cloud.update_stack(
module.params['name'],
template_file=module.params['template'],
environment_files=module.params['environment'],
timeout=module.params['timeout'],
rollback=module.params['rollback'],
wait=module.params['wait'])
if stack['stack_status'] == 'UPDATE_COMPLETE':
return stack
else:
module.fail_json(msg = "Failure in updating stack: %s" %
stack['stack_status_reason'])
except shade.OpenStackCloudException as e:
module.fail_json(msg=str(e))
def _system_state_change(module, stack, cloud):
state = module.params['state']
if state == 'present':
if not stack:
return True
if state == 'absent' and stack:
return True
return False
def main():
argument_spec = openstack_full_argument_spec(
name=dict(required=True),
template=dict(default=None),
environment=dict(default=None, type='list'),
parameters=dict(default={}, type='dict'),
rollback=dict(default=False, type='bool'),
timeout=dict(default=3600, type='int'),
state=dict(default='present', choices=['absent', 'present']),
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
# stack API introduced in 1.8.0
if not HAS_SHADE or (StrictVersion(shade.__version__) < StrictVersion('1.8.0')):
module.fail_json(msg='shade 1.8.0 or higher is required for this module')
state = module.params['state']
name = module.params['name']
# Check for required parameters when state == 'present'
if state == 'present':
for p in ['template']:
if not module.params[p]:
module.fail_json(msg='%s required with present state' % p)
try:
cloud = shade.openstack_cloud(**module.params)
stack = cloud.get_stack(name)
if module.check_mode:
module.exit_json(changed=_system_state_change(module, stack,
cloud))
if state == 'present':
if not stack:
stack = _create_stack(module, stack, cloud)
else:
stack = _update_stack(module, stack, cloud)
changed = True
module.exit_json(changed=changed,
stack=stack,
id=stack.id)
elif state == 'absent':
if not stack:
changed = False
else:
changed = True
if not cloud.delete_stack(name, wait=module.params['wait']):
module.fail_json(msg='delete stack failed for stack: %s' % name)
module.exit_json(changed=changed)
except shade.OpenStackCloudException as e:
module.fail_json(msg=str(e))
from ansible.module_utils.basic import *
from ansible.module_utils.openstack import *
if __name__ == '__main__':
main()

View File

@ -0,0 +1,31 @@
---
- name: Ensure local working dir exists
file:
path: "{{ local_working_dir }}"
- name: generate prefix for all run-related entities
set_fact:
prefix="{{ tmp.node_prefix }}"
- name: get the latest uploaded image
shell: >
export OS_AUTH_URL="{{ os_auth_url }}";
export OS_USERNAME="{{ os_username }}";
export OS_PASSWORD="{{ os_password }}";
export OS_TENANT_NAME="{{ os_tenant_name }}";
glance image-list --sort-key 'created_at' | grep "{{ osp_release | default(release) }}-undercloud.qcow2" | head -n1 | cut -d'|' -f3 | sed 's/ //g'
register: latest_undercloud_image_name
no_log: true
- name: set fact for undercloud image
set_fact: latest_undercloud_image="{{ latest_undercloud_image_name.stdout }}"
- name: copy key inserted in image to undercloud_key location
copy:
src: "{{ existing_key_location }}/{{ item }}"
dest: "{{ local_working_dir }}/{{ item }}"
mode: 0600
with_items:
- id_rsa_undercloud
- id_rsa_undercloud.pub

View File

@ -0,0 +1,12 @@
---
- name: Create OVB stack
include: ovb-create-stack.yml
when: ovb_manage_stack_mode == 'create'
- name: Setup OVB undercloud
include: ovb-setup-undercloud.yml
when: ovb_manage_stack_mode == 'setup-undercloud'
- name: Delete OVB stack
include: ovb-delete-stack.yml
when: ovb_manage_stack_mode == 'delete'

View File

@ -0,0 +1,114 @@
---
- include: get-undercloud-image.yml
- name: Copy deploy stack parameters template
template:
src: env.yaml.j2
dest: "{{ local_working_dir }}/{{ prefix }}env.yaml"
mode: 0755
- name: Add templates for multi-nic
blockinfile:
dest: "{{ local_working_dir }}/{{ prefix }}env.yaml"
insertafter: "## in baremetal-networks-all.yaml"
content: |
## multi-nic
OS::OVB::BaremetalNetworks: {{ templates_dir }}/baremetal-networks-all.yaml
OS::OVB::BaremetalPorts: {{ templates_dir }}/baremetal-ports-all.yaml
when: network_isolation_type == 'multi-nic'
- name: Add templates for public-bond
blockinfile:
dest: "{{ local_working_dir }}/{{ prefix }}env.yaml"
insertafter: "## in baremetal-networks-all.yaml"
content: |
## public-bond
OS::OVB::BaremetalNetworks: {{ templates_dir }}/baremetal-networks-all.yaml
OS::OVB::BaremetalPorts: {{ templates_dir }}/baremetal-ports-public-bond.yaml
when: network_isolation_type == 'public-bond'
- name: Add keypair
shell: >
export OS_USERNAME="{{ os_username }}";
export OS_PASSWORD="{{ os_password }}";
export OS_TENANT_NAME="{{ os_tenant_name }}";
export OS_AUTH_URL="{{ os_auth_url }}";
nova keypair-add --pub-key ~/.ssh/id_rsa.pub {{ prefix }}key
ignore_errors: true
no_log: true
- name: copy clouds.yaml file
template:
src: clouds.yaml.j2
dest: "{{ local_working_dir }}/clouds.yaml"
mode: 0755
- name: Deploy stack
os_stack:
name: "{{ stack_name }}"
cloud: "{{ cloud_name}}"
state: present
template: "{{ heat_template }}"
environment: "{{ environment_list }}"
register: stack_deployment
environment:
OS_CLIENT_CONFIG_FILE: "{{ local_working_dir }}/clouds.yaml"
ignore_errors: true
- name: Show stack deployment information
shell: >
export OS_USERNAME="{{ os_username }}";
export OS_PASSWORD="{{ os_password }}";
export OS_TENANT_NAME="{{ os_tenant_name }}";
export OS_AUTH_URL="{{ os_auth_url }}";
heat stack-show {{ stack_name }}
when: stack_deployment.result is not defined
no_log: true
- name: set fact for undercloud floating IP address
set_fact:
undercloud_ip="{{ stack_deployment.stack.outputs[0].output_value }}"
- name: Add provisioned undercloud host
add_host:
name: undercloud
hostname: "{{ undercloud_ip }}"
groups: "{{ node_groups| join(',') }}"
ansible_ssh_host: "{{ undercloud_ip }}"
ansible_fqdn: undercloud
ansible_ssh_user: stack
ansible_private_key_file: "{{ undercloud_key }}"
ansible_ssh_extra_args: "{{ ssh_extra_args }}"
undercloud_ip: "{{ undercloud_ip }}"
- name: Wait for provisioned host to become reachable
command:
ssh -o BatchMode=yes -o "StrictHostKeyChecking=no" root@{{ undercloud_ip }} -i "{{ undercloud_key }}"
register: result
until: result|success
retries: 300
delay: 5
- name: Build nodes.json file to be used as instackenv.json
shell: >
chdir={{ local_working_dir }}
export OS_USERNAME="{{ os_username }}";
export OS_PASSWORD="{{ os_password }}";
export OS_TENANT_NAME="{{ os_tenant_name }}";
export OS_AUTH_URL="{{ os_auth_url }}";
{{ ovb_dir }}/bin/build-nodes-json --env {{ local_working_dir }}/{{ prefix }}env.yaml
register: nodes_json
no_log: true
- name: Ensure directories exist for network-environment copy
file:
path: "{{ network_env_file_dest }}"
state: directory
mode: 0755
- name: Copy the network-environment.yaml
copy:
src: "{{ network_environment_file }}"
dest: "{{ network_env_file_dest }}"
mode: 0755

View File

@ -0,0 +1,19 @@
- name: Remove stack
os_stack:
name: "{{ stack_name }}"
cloud: "{{ cloud_name }}"
state: absent
environment:
OS_CLIENT_CONFIG_FILE: "{{ local_working_dir }}/clouds.yaml"
ignore_errors: true
- name: Remove associated keypair
shell: >
export OS_USERNAME="{{ os_username }}";
export OS_PASSWORD="{{ os_password }}";
export OS_TENANT_NAME="{{ os_tenant_name }}";
export OS_AUTH_URL="{{ os_auth_url }}";
nova keypair-delete {{ prefix }}key
ignore_errors: true
no_log: true

View File

@ -0,0 +1,28 @@
---
- name: copy both public and private ssh keys to root directory
become: yes
copy:
src: "{{ lookup('env','HOME') }}/.ssh/{{ item }}"
dest: "/root/.ssh/{{ item }}"
mode: 0600
with_items:
- id_rsa
- id_rsa.pub
- name: Copy instackenv.json to undercloud
copy:
src="{{ local_working_dir }}/nodes.json"
dest="{{ working_dir }}/instackenv.json"
- name: Copy over setup undercloud connectivity script template
template:
src: setup-undercloud-connectivity.sh.j2
dest: "{{ working_dir }}/setup-undercloud-connectivity.sh"
mode: 0755
- name: Setup interfaces, connectivity on the undercloud
become: yes
shell: >
"{{ working_dir }}"/setup-undercloud-connectivity.sh > \
{{ setup_undercloud_connectivity_log }} 2>&1

View File

@ -0,0 +1,9 @@
clouds:
{{ cloud_name }}:
auth:
username: {{ os_username }}
password: {{ os_password }}
project_name: {{ os_tenant_name }}
auth_url: {{ os_auth_url }}
region_name: regionOne

View File

@ -0,0 +1,57 @@
parameters:
os_user: {{ os_username }}
os_password: {{ os_password }}
os_tenant: {{ os_tenant_name }}
os_auth_url: {{ os_auth_url }}
bmc_flavor: m1.micro
bmc_image: 'bmc-base'
bmc_prefix: '{{ prefix }}bmc'
baremetal_flavor: m1.large
baremetal_image: 'ipxe-boot'
baremetal_prefix: '{{ prefix }}baremetal'
key_name: '{{ prefix }}key'
private_net: '{{ prefix }}private'
node_count: {{ node_count }}
public_net: '{{ prefix }}public'
provision_net: '{{ prefix }}provision'
# QuintupleO-specific params ignored by virtual-baremetal.yaml
undercloud_name: '{{ prefix }}undercloud'
undercloud_image: '{{ latest_undercloud_image }}'
undercloud_flavor: m1.xlarge
external_net: '{{ external_net }}'
undercloud_user_data: |
#!/bin/sh
sed -i "s/no-port-forwarding.*sleep 10\" //" /root/.ssh/authorized_keys
parameter_defaults:
## Uncomment and customize the following to use an existing floating ip
# undercloud_floating_ip_id: 'uuid of floating ip'
# undercloud_floating_ip: 'address of floating ip'
# Network Isolation parameters
overcloud_internal_net: '{{ prefix }}overcloud_internal_net'
overcloud_storage_net: '{{ prefix }}overcloud_storage_net'
overcloud_storage_mgmt_net: '{{ prefix }}overcloud_storage_mgmt_net'
overcloud_tenant_net: '{{ prefix }}overcloud_tenant_net'
resource_registry:
## Uncomment the following to use an existing floating ip
# OS::OVB::UndercloudFloating: templates/undercloud-floating-existing.yaml
## Uncomment the following to use no floating ip
# OS::OVB::UndercloudFloating: templates/undercloud-floating-none.yaml
## Uncomment the following to create a private network
OS::OVB::PrivateNetwork: {{ templates_dir }}/private-net-create.yaml
## Uncomment to create all networks required for network-isolation.
## parameter_defaults should be used to override default parameter values
## in baremetal-networks-all.yaml
# OS::OVB::BaremetalNetworks: {{ templates_dir }}/baremetal-networks-all.yaml
# OS::OVB::BaremetalPorts: {{ templates_dir }}/baremetal-ports-all.yaml
## Uncomment to deploy a quintupleo environment without an undercloud.
# OS::OVB::UndercloudEnvironment: OS::Heat::None

View File

@ -0,0 +1,57 @@
#! /bin/bash
set -eux
### --start_docs
## Set up the undercloud for installation
## ======================================
## * Configure external interface
## ::
sudo ifconfig {{ external_interface }} {{ external_interface_ip }} netmask {{ external_interface_netmask }}
## * Get mac address of external interface
## ::
MAC_ADDR_EXT_INTERFACE=$( ifconfig | grep -n3 {{ external_interface }} | grep -A1 ether | cut -d " " -f 10 )
## * Set up external interface
## ::
sudo bash -c 'cat <<EOF > /etc/sysconfig/network-scripts/ifcfg-{{ external_interface }}
NAME={{ external_interface }}
IPADDR={{ external_interface_ip }}
NETMASK={{ external_interface_netmask }}
NM_CONTROLLED=no
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
ONBOOT=yes
HWADDR=$MAC_ADDR_EXT_INTERFACE
PEERDNS=yes
PEERROUTES=yes
IPV6_PEERDNS=yes
IPV6_PEERROUTES=yes
EOF'
## * Set MTU values
## ::
{% for interface in (mtu_interface) %}
ip link set {{ interface }} mtu {{ mtu }}
echo "MTU={{ mtu }}" >> /etc/sysconfig/network-scripts/ifcfg-{{ interface }}
{% endfor %}
## * Add nameserver to resolv.conf
## ::
cat <<EOF >> /etc/resolv.conf
nameserver {{ pvt_nameserver }}
EOF
### --stop_docs