diff --git a/library/cloud/nova_facts b/library/cloud/nova_facts new file mode 100644 index 0000000..d6ea1d3 --- /dev/null +++ b/library/cloud/nova_facts @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os + +try: + from novaclient.v1_1 import client as nova_client + from novaclient import exceptions + import time +except ImportError: + print("failed=True msg='novaclient is required for this module'") + +DOCUMENTATION = ''' +--- +module: nova_facts +short_description: Return facts from Nova +description: + - Returns instance state and metadata from Openstack Nova. +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: 'yes' + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: 'yes' + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + name: + description: + - Name of the instance for which facts will be retrieved. + default: None + instance_id: + description: + - Instance ID of the instance for which facts will be retrieved. + default: None +requirements: ["novaclient"] +''' + +EXAMPLES = ''' +# Rebuilds an existing VM with a new image +- nova_facts: + login_username: admin + login_password: admin + login_tenant_name: admin + name: vm1 + image_id: 4f905f38-e52a-43d2-b6ec-754a13ffb529 +''' + + +# The following openstack_ is a copy paste from an upcoming +# core module "lib/ansible/module_utils/openstack.py" Once that's landed, +# these should be replaced with a line at the bottom of the file: +# from ansible.module_utils.openstack import * +def openstack_argument_spec(): + # Consume standard OpenStack environment variables. + # This is mainly only useful for ad-hoc command line operation as + # in playbooks one would assume variables would be used appropriately + OS_AUTH_URL = os.environ.get('OS_AUTH_URL', 'http://127.0.0.1:35357/v2.0/') + OS_PASSWORD = os.environ.get('OS_PASSWORD', None) + OS_REGION_NAME = os.environ.get('OS_REGION_NAME', None) + OS_USERNAME = os.environ.get('OS_USERNAME', 'admin') + OS_TENANT_NAME = os.environ.get('OS_TENANT_NAME', OS_USERNAME) + + spec = dict( + login_username=dict(default=OS_USERNAME), + auth_url=dict(default=OS_AUTH_URL), + region_name=dict(default=OS_REGION_NAME), + availability_zone=dict(default=None), + ) + if OS_PASSWORD: + spec['login_password'] = dict(default=OS_PASSWORD) + else: + spec['login_password'] = dict(required=True) + if OS_TENANT_NAME: + spec['login_tenant_name'] = dict(default=OS_TENANT_NAME) + else: + spec['login_tenant_name'] = dict(required=True) + return spec + + +def _nova_facts(module, nova): + server = None + try: + if module.params.get('instance_id') is None: + servers = nova.servers.list(True, {'name': module.params['name']}) + if servers: + # the {'name': module.params['name']} will also return servers + # with names that partially match the server name, so we have to + # strictly filter here + servers = [ + x for x in servers if x.name == module.params['name'] + ] + if servers: + server = servers[0] + else: + server = nova.servers.get(module.params['instance_id']) + except Exception, e: + module.fail_json( + msg="Error in getting the server list: %s" % e.message + ) + if not server: + module.exit_json(changed=False, result="not present") + + try: + server = nova.servers.get(server.id) + except Exception, e: + module.fail_json(msg="not present") + + facts = {'ansible_facts': {}} + facts['ansible_facts']['instance_status'] = server.status + facts['ansible_facts']['instance_metadata'] = server.metadata + + module.exit_json(**facts) + + +def main(): + argument_spec = openstack_argument_spec() + argument_spec.update(dict( + name=dict(), + instance_id=dict(), + )) + module = AnsibleModule(argument_spec=argument_spec) + + nova = nova_client.Client(module.params['login_username'], + module.params['login_password'], + module.params['login_tenant_name'], + module.params['auth_url'], + region_name=module.params['region_name'], + service_type='compute') + try: + nova.authenticate() + except exceptions.Unauthorized, e: + module.fail_json( + msg="Invalid OpenStack Nova credentials.: %s" % e.message + ) + except exceptions.AuthorizationFailure, e: + module.fail_json(msg="Unable to authorize user: %s" % e.message) + + _nova_facts(module, nova) +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +main() diff --git a/playbooks/rebuild.yml b/playbooks/rebuild.yml index a9efeb4..dcda72d 100644 --- a/playbooks/rebuild.yml +++ b/playbooks/rebuild.yml @@ -13,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. --- -- name: "Change /mnt to be a read-only mount-point" - sudo: yes - command: mount -o remount,ro /mnt - name: "Call nova_rebuild" nova_rebuild: instance_id: "{{ instance_id }}" diff --git a/playbooks/remount-ro.yml b/playbooks/remount-ro.yml new file mode 100644 index 0000000..67ba6ff --- /dev/null +++ b/playbooks/remount-ro.yml @@ -0,0 +1,18 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +- name: "Change /mnt to be a read-only mount-point" + sudo: yes + command: mount -o remount,ro /mnt diff --git a/playbooks/update_cloud.yml b/playbooks/update_cloud.yml index fc178f3..2810453 100644 --- a/playbooks/update_cloud.yml +++ b/playbooks/update_cloud.yml @@ -14,52 +14,62 @@ # limitations under the License. --- - hosts: all + gather_facts: no tasks: - include: disable_os_collect_config.yml + when: instance_status == "ACTIVE" - hosts: undercloud name: Disable Undercloud sudo: yes + gather_facts: no tasks: - service: name={{ item }} enabled=no state=stopped with_items: helion_undercloud_services - when: helion is defined + when: helion is defined and instance_status == "ACTIVE" - service: name={{ item }} enabled=no state=stopped with_items: undercloud_services - when: helion is not defined + when: helion is not defined and instance_status == "ACTIVE" - hosts: nova-compute name: Disable Overcloud Compute sudo: yes + gather_facts: no tasks: - service: name={{ item }} enabled=no state=stopped with_items: helion_overcloud_compute_services - when: helion is defined + when: helion is defined and instance_status == "ACTIVE" - service: name={{ item }} enabled=no state=stopped - with_items: overcloud_compute_services + with_items: overcloud_compute_services and instance_status == "ACTIVE" when: helion is not defined - hosts: swift-storage name: swift-storage sudo: yes + gather_facts: no tasks: - service: name={{ item }} enabled=no state=stopped with_items: helion_overcloud_swift_services - when: helion is defined + when: helion is defined and instance_status == "ACTIVE" + - service: name={{ item }} enabled=no state=stopped + with_items: overcloud_swift_services + when: helion is not defined and instance_status == "ACTIVE" - hosts: controller name: Disable Overcloud Controller sudo: yes + gather_facts: no tasks: - service: name={{ item }} enabled=no state=stopped with_items: helion_overcloud_controller_services - when: helion is defined + when: helion is defined and instance_status == "ACTIVE" - service: name={{ item }} enabled=no state=stopped with_items: overcloud_controller_services - when: helion is not defined + when: helion is not defined and instance_status == "ACTIVE" - hosts: controllermgmt name: Disable Overcloud Controller Mgmt node sudo: yes + gather_facts: no tasks: - service: name={{ item }} enabled=no state=stopped with_items: helion_overcloudmgmt_controller_services - when: helion is defined + when: helion is defined and instance_status == "ACTIVE" # Critically, we need to select a single node of the galera cluster to # be the 'last'. So controllermgmt fits that bill for now. We will have # to select one to be the "special" node eventually, we can do that with @@ -68,26 +78,36 @@ - hosts: controller name: Stop MySQL on controller nodes sudo: yes + gather_facts: no tasks: - include: galera_status.yml + when: instance_status == "ACTIVE" - service: name=mysql enabled=no state=stopped - when: galera_status == 'Synced' + when: galera_status == 'Synced' and instance_status == "ACTIVE" - fail: msg="Galera Replication is out of sync - cannot safely proceed" - when: galera_status != 'Synced' + when: galera_status != 'Synced' and instance_status == "ACTIVE" - hosts: controllermgmt name: Stop MySQL on Overcloud Controller Mgmt node sudo: yes + gather_facts: no tasks: - include: galera_status.yml + when: instance_status == "ACTIVE" - fail: msg="Galera Replication is out of sync - cannot safely proceed" - when: galera_status == 'Out of Sync' + when: galera_status == 'Out of Sync' and instance_status == "ACTIVE" - service: name=mysql enabled=no state=stopped + when: instance_status == "ACTIVE" - hosts: undercloud name: Rebuild and Refresh Undercloud + gather_facts: no tasks: - include: preserve_ssh_host_keys.yml + when: instance_status == "ACTIVE" - include: cleanup_cinder_volumes.yml - - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ undercloud_rebuild_image_id }}" } + when: instance_status == "ACTIVE" + - include: remount-ro.yml + when: instance_status == "ACTIVE" + - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ undercloud_rebuild_image_id }}", when: instance_status != "REBUILD" } - local_action: wait_for port=22 timeout=600 host="{{ inventory_hostname }}" search_regex=OpenSSH delay=10 - include: refresh_config.yml - hosts: undercloud @@ -99,10 +119,13 @@ with_items: undercloud_services - hosts: controllermgmt name: Rebuild and Refresh ControllerMgmt + gather_facts: no tasks: - include: preserve_ssh_host_keys.yml + when: instance_status == "ACTIVE" - include: cleanup_cinder_volumes.yml - - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ controllermgmt_rebuild_image_id }}" } + when: instance_status == "ACTIVE" + - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ controllermgmt_rebuild_image_id }}", when: instance_status != "REBUILD" } - local_action: wait_for port=22 timeout=600 host="{{ inventory_hostname }}" search_regex=OpenSSH delay=10 - hosts: controllermgmt name: Start initial Galera cluster node @@ -110,10 +133,13 @@ - service: name=mysql enabled=yes state=started arguments=--wsrep_cluster_address="gcomm://" - hosts: controller name: Rebuild and Refresh Controller + gather_facts: no tasks: - include: preserve_ssh_host_keys.yml + when: instance_status == "ACTIVE" - include: cleanup_cinder_volumes.yml - - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ controller_rebuild_image_id }}" } + when: instance_status == "ACTIVE" + - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ controller_rebuild_image_id }}", when: instance_status != "REBUILD" } - local_action: wait_for port=22 timeout=600 host="{{ inventory_hostname }}" search_regex=OpenSSH delay=10 - hosts: controller:controllermgmt name: Re-Refresh Controller @@ -146,9 +172,11 @@ with_items: overcloud_controller_services - hosts: swift-storage name: Rebuild and Refresh swift-storage + gather_facts: no tasks: - include: preserve_ssh_host_keys.yml - - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ swift_storage_rebuild_image_id }}" } + when: instance_status == "ACTIVE" + - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ swift_storage_rebuild_image_id }}", when: instance_status != "REBUILD" } - local_action: wait_for port=22 timeout=600 host="{{ inventory_hostname }}" search_regex=OpenSSH delay=10 - include: refresh_config.yml - hosts: swift-storage @@ -163,9 +191,11 @@ with_items: overcloud_swift_services - hosts: nova-compute name: Rebuild and Refresh Nova Compute + gather_facts: no tasks: - include: preserve_ssh_host_keys.yml - - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ nova_compute_rebuild_image_id }}" } + when: instance_status == "ACTIVE" + - { include: rebuild.yml, instance_id: "{{ instance_id }}", rebuild_image_id: "{{ nova_compute_rebuild_image_id }}", when: instance_status != "REBUILD" } - local_action: wait_for port=22 timeout=600 host="{{ inventory_hostname }}" search_regex=OpenSSH delay=10 - include: refresh_config.yml - hosts: nova-compute diff --git a/plugins/inventory/heat.py b/plugins/inventory/heat.py index 5c159e3..e87253b 100755 --- a/plugins/inventory/heat.py +++ b/plugins/inventory/heat.py @@ -169,6 +169,7 @@ class HeatInventory(object): res.resource_name ) } + hostvars[addr]['instance_status'] = server.status hostvars[addr]['instance_id'] = res.physical_resource_id inventory = {'_meta': {'hostvars': hostvars}} inventory.update(groups) @@ -195,6 +196,7 @@ class HeatInventory(object): res.resource_name ) } + hostvars['instance_status'] = server.status hostvars['instance_id'] = res.physical_resource_id print(json.dumps(hostvars, indent=2)) break