From 251b64ef25f92b10c3123886920b950ebdf841c1 Mon Sep 17 00:00:00 2001 From: czunker Date: Fri, 23 Oct 2015 08:59:37 +0200 Subject: Extended nova_flavor with state, ephemeral and extra_specs --- nova_flavor | 102 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/nova_flavor b/nova_flavor index b71d648..2f632d8 100644 --- a/nova_flavor +++ b/nova_flavor @@ -1,6 +1,8 @@ #!/usr/bin/python #coding: utf-8 -*- +# https://github.com/openstack-ansible/openstack-ansible-modules/blob/master/nova_flavor + # (c) 2014, Adam Samalik # # This module is free software: you can redistribute it and/or modify @@ -25,9 +27,9 @@ except ImportError: DOCUMENTATION = ''' --- module: nova_flavor -short_description: Manage OpenStack flavours +short_description: Manage OpenStack flavors description: - - Create VM flavours with OpenStack Nova service + - Create VM flavors with OpenStack Nova service requirements: [ python-novaclient ] options: login_username: @@ -62,15 +64,19 @@ options: ram: description: - Memory in MB for the flavor - required: True + required: False vcpus: description: - Number of VCPUs for the flavor - required: True - disk: + required: False + root: description: - - Size of local disk in GB - required: True + - Size of root disk in GB + required: False + ephemeral: + description: + - Size of ephemeral space in GB + required: False swap: description: - Swap space in MB @@ -83,7 +89,19 @@ options: default: ID will be automatically generated is_public: description: - - Decide if the flavour is public + - Decide if the flavor is public + choices: ['true', 'false'] + default: 'true' + extra_specs: + description: + - Metadata used by scheduling to select correct host aggregate + default: None + state: + description: + - Create or delete flavor + required: False + choices: ['present', 'absent'] + default: 'present' ''' EXAMPLES = ''' @@ -94,10 +112,10 @@ EXAMPLES = ''' name: medium ram: 2048 vcpus: 2 - disk: 20 + root: 0 + ephemeral: 20 ''' - def authenticate(module, auth_url, username, password, tenant_name, region): """ Return a Nova client object. @@ -131,20 +149,43 @@ def get_flavors(nova, name, id=None): flavors = [x for x in nova.flavors.list() if x.name == name and x.id == str(id)] return flavors -def create_flavor(module, nova, name, ram, vcpus, disk, swap, id, is_public): +def create_flavor(module, nova, name, ram, vcpus, root, ephemeral, swap, id, is_public, extra_specs): flavors = get_flavors(nova, name, id) if len(flavors) >0: - return False, flavors[0].id + present_flavor = flavors[0] + # When flavor is created with 0 swap, nova will use an empty value + if present_flavor.swap == "": + present_flavor.swap = 0 + # flavor create expects lower case is_public values, but converts them into capitalized value + if present_flavor.ram == int(ram) and present_flavor.vcpus == int(vcpus) and present_flavor.disk == int(root) and present_flavor.ephemeral == int(ephemeral) and present_flavor.swap == int(swap) and str(present_flavor.is_public) == is_public.capitalize(): + if present_flavor.get_keys() == extra_specs: + return False, flavors[0].id + else: + present_flavor.set_keys(extra_specs) + return True, flavors[0].id + else: + module.fail_json(msg = "Flavor {0} already present, but specs have changed!".format(name)) try: - flavor = nova.flavors.create(name, ram, vcpus, disk, swap=swap, - flavorid=id, is_public=is_public) + flavor = nova.flavors.create(name=name, ram=ram, vcpus=vcpus, disk=root, ephemeral=ephemeral, flavorid=id, swap=swap, is_public=is_public) + flavor.set_keys(extra_specs) except Exception as e: - module.fail_json(msg = "Could not create a flavour: {}".format( - e.message)) + module.fail_json(msg = "Could not create a flavor: {0}".format(e.message)) return True, flavor.id +def delete_flavor(module, nova, name): + flavors = get_flavors(nova, name) + if len(flavors) == 0: + return False + for flavor in flavors: + try: + nova.flavors.delete(flavor.id); + except Exception as e: + module.fail_json(msg = "Could not delete flavor {0}: {1}".format(name, e.message)) + + return True + def main(): module = AnsibleModule( argument_spec = dict( @@ -154,12 +195,15 @@ def main(): auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), region_name = dict(default=None), name = dict(required=True), - ram = dict(required=True), - vcpus = dict(required=True), - disk = dict(required=True), + ram = dict(required=False), + vcpus = dict(required=False), + root = dict(required=False), + ephemeral = dict(required=False), + extra_specs = dict(required=False,default=None), swap = dict(default=0), id = dict(default=None), - is_public = dict(default=True, choices=BOOLEANS), + is_public = dict(default='true'), + state = dict(default='present'), ) ) auth_url = module.params['auth_url'] @@ -170,17 +214,27 @@ def main(): name = module.params['name'] ram = module.params['ram'] vcpus = module.params['vcpus'] - disk = module.params['disk'] + root = module.params['root'] + ephemeral = module.params['ephemeral'] + extra_specs = module.params['extra_specs'] swap = module.params['swap'] id = module.params['id'] + state = module.params['state'] is_public = module.params['is_public'] nova = authenticate(module, auth_url, username, password, tenant_name, region) - changed, id = create_flavor(module, nova, name, ram, vcpus, disk, swap, id, - is_public) + if state == 'present': + changed, id = create_flavor(module, nova, name, ram, vcpus, root, ephemeral, swap, id, + is_public, extra_specs) + module.exit_json(changed=changed, name=name, id=id) + elif state == 'absent': + changed = delete_flavor(module, nova, name) + module.exit_json(changed=changed, name=name) + else: + raise ValueError("Code should never reach here") + - module.exit_json(changed=changed, name=name, id=id) # this is magic, see lib/ansible/module_common.py #<> -- cgit v1.2.1 From 5112eb8861da5ce0f9ac3fc9ec3078d305b48097 Mon Sep 17 00:00:00 2001 From: czunker Date: Fri, 23 Oct 2015 09:04:04 +0200 Subject: Added module to manage Host Aggregates --- nova_aggregate | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 nova_aggregate diff --git a/nova_aggregate b/nova_aggregate new file mode 100644 index 0000000..711ea05 --- /dev/null +++ b/nova_aggregate @@ -0,0 +1,204 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (c) 2015, Christian Zunker +# +# 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 . + +try: + from novaclient import client + from keystoneclient.v2_0 import client as ksclient +except ImportError: + print("failed=True msg='novaclient and keystone client are required'") + +DOCUMENTATION = ''' +--- +module: nova_aggregate +short_description: Manage OpenStack host aggregates +description: + - Create host aggregates with OpenStack Nova service +requirements: [ python-novaclient ] +options: + login_username: + description: + - user name to authenticate against Identity service + required: True + aliases: [username] + login_password: + description: + - password to authenticate against Identity service + aliases: [password] + required: True + login_tenant_name: + description: + - tenant name of the login user + aliases: [tenant_name] + required: True + 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: + - Descriptive name of the aggregate + required: True + availability_zone: + description: + - Availability Zone of the aggregate + required: False + hosts: + description: + - Hosts asigned to the aggregate + required: False + metadata: + description: + - Metadata for the aggregate, used as reference inside flavors + required: False + state: + description: + - Create or delete aggregate + required: False + choices: ['present', 'absent'] + default: 'present' +''' + +EXAMPLES = ''' + - nova_aggregate: + login_username: admin + login_password: 1234 + login_tenant_name: admin + name: medium +''' + +def authenticate(module, auth_url, username, password, tenant_name, region): + """ + Return a Nova client object. + """ + try: + keystone = ksclient.Client(auth_url=auth_url, + username=username, + password=password, + tenant_name=tenant_name, + region=region) + except Exception as e: + module.fail_json( + msg = "Could not authenticate with Keystone: {}".format( + e.message)) + + try: + nova = client.Client('2', keystone.username, + keystone.password, + keystone.tenant_name, + keystone.auth_url) + except Exception as e: + module.fail_json(msg = "Could not get Nova client: {}".format( + e.message)) + + return nova + +def get_aggregates(nova, name, id=None): + if not id: + aggregates = [x for x in nova.aggregates.list() if x.name == name] + else: + aggregates = [x for x in nova.aggregates.list() if x.name == name and x.id == str(id)] + return aggregates + +def create_aggregate(module, nova, name, availability_zone, hosts, metadata, id): + aggregate_name = name + "-" + availability_zone + aggregates = get_aggregates(nova, aggregate_name, id) + if len(aggregates) >0: + present_aggregate = aggregates[0] + changed = False + for host in hosts: + if not host in present_aggregate.hosts: + nova.aggregates.add_host(present_aggregate, host) + changed = True + if metadata != present_aggregate.metadata: + nova.aggregates.set_metadata(present_aggregate, metadata) + changed = True + return changed, aggregates[0].id + + try: + aggregate = nova.aggregates.create(name=aggregate_name, availability_zone=availability_zone) + for host in hosts: + nova.aggregates.add_host(aggregate, host) + nova.aggregates.set_metadata(aggregate, metadata) + except Exception as e: + module.fail_json(msg = "Could not create aggregate: {0}".format(e.message)) + + return True, aggregate.id + +def delete_aggregate(module, nova, name): + aggregates = get_aggregates(nova, name) + if len(aggregates) == 0: + return False + for aggregate in aggregates: + try: + nova.aggregates.delete(aggregate); + except Exception as e: + module.fail_json(msg = "Could not delete aggregate {0}: {1}".format(name, e.message)) + + return True + +def main(): + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin', aliases=["username"]), + login_password = dict(required=True, aliases=["password"]), + login_tenant_name = dict(required='True', aliases=["tenant_name"]), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + name = dict(required=True), + id = dict(default=None), + availability_zone = dict(required=False), + hosts = dict(required=False), + metadata = dict(required=False, default=None), + state = dict(default='present'), + ) + ) + auth_url = module.params['auth_url'] + region = module.params['region_name'] + username = module.params['login_username'] + password = module.params['login_password'] + tenant_name = module.params['login_tenant_name'] + name = module.params['name'] + availability_zone = module.params['availability_zone'] + hosts = module.params['hosts'] + metadata = module.params['metadata'] + id = module.params['id'] + state = module.params['state'] + + nova = authenticate(module, auth_url, username, password, tenant_name, region) + + if state == 'present': + changed, id = create_aggregate(module, nova, name, availability_zone, hosts, metadata, id) + module.exit_json(changed=changed, name=name, id=id) + elif state == 'absent': + changed = delete_aggregate(module, nova, name) + module.exit_json(changed=changed, name=name) + else: + raise ValueError("Code should never reach here") + + + +# this is magic, see lib/ansible/module_common.py +#<> +if __name__ == '__main__': + main() -- cgit v1.2.1