summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPedro Alvarez <pedro.alvarez@codethink.co.uk>2015-02-18 16:40:04 +0000
committerPedro Alvarez <pedro.alvarez@codethink.co.uk>2015-03-02 16:22:32 +0000
commit2246f59e27b575cb765772d601cfb88a195747ff (patch)
treea8c96b85eaf79fe367aac0b4b01497812ccd6a96
parent89be7c20f31b6d9d5d95e2480d4dd345f622e329 (diff)
downloaddefinitions-2246f59e27b575cb765772d601cfb88a195747ff.tar.gz
Keystone configuration to ansible
-rw-r--r--openstack-keystone.configure98
-rw-r--r--openstack/etc/systemd/system/openstack-keystone-setup.service7
-rw-r--r--openstack/manifest30
-rw-r--r--openstack/usr/share/openstack/hosts1
-rw-r--r--openstack/usr/share/openstack/keystone.yml68
-rw-r--r--openstack/usr/share/openstack/keystone/keystone-paste.ini (renamed from openstack/etc/keystone/keystone-paste.ini)0
-rw-r--r--openstack/usr/share/openstack/keystone/keystone.conf (renamed from openstack/etc/keystone/keystone.conf)12
-rw-r--r--openstack/usr/share/openstack/keystone/logging.conf (renamed from openstack/etc/keystone/logging.conf)0
-rw-r--r--openstack/usr/share/openstack/keystone/policy.json (renamed from openstack/etc/keystone/policy.json)0
-rw-r--r--openstack/usr/share/openstack/modules/README3
-rw-r--r--openstack/usr/share/openstack/modules/cinder_manage105
-rw-r--r--openstack/usr/share/openstack/modules/glance176
-rw-r--r--openstack/usr/share/openstack/modules/glance_manage158
-rw-r--r--openstack/usr/share/openstack/modules/heat_manage105
-rw-r--r--openstack/usr/share/openstack/modules/keystone_manage123
-rw-r--r--openstack/usr/share/openstack/modules/keystone_service309
-rw-r--r--openstack/usr/share/openstack/modules/neutron_floating_ip274
-rw-r--r--openstack/usr/share/openstack/modules/neutron_network282
-rw-r--r--openstack/usr/share/openstack/modules/neutron_router210
-rw-r--r--openstack/usr/share/openstack/modules/neutron_router_gateway215
-rw-r--r--openstack/usr/share/openstack/modules/neutron_router_interface249
-rw-r--r--openstack/usr/share/openstack/modules/neutron_sec_group382
-rw-r--r--openstack/usr/share/openstack/modules/neutron_subnet294
-rw-r--r--openstack/usr/share/openstack/modules/nova_flavor188
-rw-r--r--openstack/usr/share/openstack/modules/nova_manage93
-rw-r--r--openstack/usr/share/openstack/openstack-keystone-setup92
26 files changed, 3339 insertions, 135 deletions
diff --git a/openstack-keystone.configure b/openstack-keystone.configure
index d2d1171e..a81b7c0a 100644
--- a/openstack-keystone.configure
+++ b/openstack-keystone.configure
@@ -20,37 +20,79 @@ set -e
ROOT="$1"
##########################################################################
-# Substitutions in configuration files
+
+ln -s "/etc/systemd/system/openstack-keystone-setup.service" \
+ "$ROOT/etc/systemd/system/multi-user.target.wants/openstack-keystone-setup.service"
+
+##########################################################################
+# Check variables
##########################################################################
-cat <<EOF > "$ROOT"/etc/openstack-keystone-setup.sed
-s/##KEYSTONE_TEMPORARY_ADMIN_TOKEN##/$KEYSTONE_TEMPORARY_ADMIN_TOKEN/g
-s/##KEYSTONE_TEMPORARY_ADMIN_PASSWORD##/$KEYSTONE_TEMPORARY_ADMIN_PASSWORD/g
-s/##KEYSTONE_PUBLIC_URL##/$KEYSTONE_PUBLIC_URL/g
-s/##KEYSTONE_INTERNAL_URL##/$KEYSTONE_INTERNAL_URL/g
-s/##KEYSTONE_ADMIN_URL##/$KEYSTONE_ADMIN_URL/g
-s/##OPENSTACK_AUTH_HOST##/$OPENSTACK_AUTH_HOST/g
-s/##OPENSTACK_AUTH_PORT##/$OPENSTACK_AUTH_PORT/g
-s/##IDENTITY_URI##/$IDENTITY_URI/g
-s/##KEYSTONE_DB_USER##/$KEYSTONE_DB_USER/g
-s/##KEYSTONE_DB_PASSWORD##/$KEYSTONE_DB_PASSWORD/g
-EOF
+if [ -z "$KEYSTONE_TEMPORARY_ADMIN_TOKEN" -a \
+ -z "$KEYSTONE_TEMPORARY_ADMIN_PASSWORD" -a \
+ -z "$KEYSTONE_PUBLIC_URL" -a \
+ -z "$KEYSTONE_INTERNAL_URL" -a \
+ -z "$KEYSTONE_ADMIN_URL" -a \
+ -z "$OPENSTACK_AUTH_HOST" -a \
+ -z "$OPENSTACK_AUTH_PORT" -a \
+ -z "$IDENTITY_URI" -a \
+ -z "$KEYSTONE_DB_USER" -a \
+ -z "$KEYSTONE_DB_PASSWORD" -a \
+ -z "$RABBITMQ_HOST" -a \
+ -z "$RABBITMQ_PORT" -a \
+ -z "$RABBITMQ_USER" -a \
+ -z "$RABBITMQ_PASSWORD" -a \
+ -z "$CONTROLLER_HOST" ]; then
+ # No Keystone options defined, do nothing.
+ exit 0
+fi
-sed -f "$ROOT"/etc/openstack-keystone-setup.sed -i \
- "$ROOT"/etc/keystone/keystone.conf \
- "$ROOT"/etc/glance/glance-api.conf \
- "$ROOT"/etc/glance/glance-registry.conf \
- "$ROOT"/etc/nova/nova.conf \
- "$ROOT"/etc/cinder/cinder.conf \
- "$ROOT"/etc/neutron/neutron.conf \
- "$ROOT"/etc/neutron/metadata_agent.ini \
- "$ROOT"/usr/share/openstack/openstack-keystone-setup \
- "$ROOT"/usr/share/openstack/openstack-glance-setup \
- "$ROOT"/usr/share/openstack/openstack-nova-setup \
- "$ROOT"/usr/share/openstack/openstack-neutron-setup \
- "$ROOT"/usr/share/openstack/openstack-cinder-setup
+if [ -z "$KEYSTONE_TEMPORARY_ADMIN_TOKEN" -o \
+ -z "$KEYSTONE_TEMPORARY_ADMIN_PASSWORD" -o \
+ -z "$KEYSTONE_PUBLIC_URL" -o \
+ -z "$KEYSTONE_INTERNAL_URL" -o \
+ -z "$KEYSTONE_ADMIN_URL" -o \
+ -z "$OPENSTACK_AUTH_HOST" -o \
+ -z "$OPENSTACK_AUTH_PORT" -o \
+ -z "$IDENTITY_URI" -a \
+ -z "$KEYSTONE_DB_USER" -o \
+ -z "$KEYSTONE_DB_PASSWORD" -o \
+ -z "$RABBITMQ_HOST" -o \
+ -z "$RABBITMQ_PORT" -o \
+ -z "$RABBITMQ_USER" -o \
+ -z "$RABBITMQ_PASSWORD" -o \
+ -z "$CONTROLLER_HOST" ]; then
+ echo Some options required for Keystone were defined, but not all.
+ exit 1
+fi
##########################################################################
+# Generate config variable shell snippet
+##########################################################################
-ln -s "/etc/systemd/system/openstack-keystone-setup.service" \
- "$ROOT/etc/systemd/system/multi-user.target.wants/openstack-keystone-setup.service"
+OPENSTACK_DATA="$ROOT/etc/openstack"
+mkdir -p "$OPENSTACK_DATA"
+
+python <<'EOF' >"$OPENSTACK_DATA/keystone.conf"
+import os, sys, yaml
+
+keystone_configuration={
+ 'KEYSTONE_TEMPORARY_ADMIN_TOKEN': os.environ['KEYSTONE_TEMPORARY_ADMIN_TOKEN'],
+ 'KEYSTONE_TEMPORARY_ADMIN_PASSWORD': os.environ['KEYSTONE_TEMPORARY_ADMIN_PASSWORD'],
+ 'KEYSTONE_PUBLIC_URL': os.environ['KEYSTONE_PUBLIC_URL'],
+ 'KEYSTONE_INTERNAL_URL': os.environ['KEYSTONE_INTERNAL_URL'],
+ 'KEYSTONE_ADMIN_URL': os.environ['KEYSTONE_ADMIN_URL'],
+ 'OPENSTACK_AUTH_HOST': os.environ['OPENSTACK_AUTH_HOST'],
+ 'OPENSTACK_AUTH_PORT': os.environ['OPENSTACK_AUTH_PORT'],
+ 'IDENTITY_URI': os.environ['IDENTITY_URI'],
+ 'KEYSTONE_DB_USER': os.environ['KEYSTONE_DB_USER'],
+ 'KEYSTONE_DB_PASSWORD': os.environ['KEYSTONE_DB_PASSWORD'],
+ 'CONTROLLER_HOST': os.environ['CONTROLLER_HOST'],
+ 'RABBITMQ_HOST': os.environ['RABBITMQ_HOST'],
+ 'RABBITMQ_PORT': os.environ['RABBITMQ_PORT'],
+ 'RABBITMQ_USER': os.environ['RABBITMQ_USER'],
+ 'RABBITMQ_PASSWORD': os.environ['RABBITMQ_PASSWORD'],
+}
+
+yaml.dump(keystone_configuration, sys.stdout, default_flow_style=False)
+EOF
diff --git a/openstack/etc/systemd/system/openstack-keystone-setup.service b/openstack/etc/systemd/system/openstack-keystone-setup.service
index fb2793bb..9ea04c1d 100644
--- a/openstack/etc/systemd/system/openstack-keystone-setup.service
+++ b/openstack/etc/systemd/system/openstack-keystone-setup.service
@@ -1,11 +1,12 @@
[Unit]
-Description=Run openstack-keystone-setup (once)
+Description=Run keystone-setup Ansible scripts
After=local-fs.target postgres-server.service
+ConditionPathExists=/etc/openstack/keystone.conf
[Service]
+# Oneshot, since others setup have to wait until this service finishes
Type=oneshot
-ExecStart=/usr/share/openstack/openstack-keystone-setup
-Restart=no
+ExecStart=/usr/bin/ansible-playbook -v -M /usr/share/ansible/ansible-openstack-modules -i /usr/share/openstack/hosts /usr/share/openstack/keystone.yml
[Install]
WantedBy=multi-user.target
diff --git a/openstack/manifest b/openstack/manifest
index b19142b5..73e8f6ee 100644
--- a/openstack/manifest
+++ b/openstack/manifest
@@ -1,11 +1,29 @@
-0040755 0 0 /etc/keystone
-0040755 0 0 /var/lib/keystone
0040755 0 0 /usr/share/openstack
+0100644 0 0 /usr/share/openstack/hosts
+0100644 0 0 /usr/share/openstack/keystone.yml
+0040755 0 0 /usr/share/openstack/keystone
0100755 0 0 /usr/share/openstack/openstack-keystone-setup
-0100644 0 0 /etc/keystone/logging.conf
-0100644 0 0 /etc/keystone/keystone.conf
-0100644 0 0 /etc/keystone/policy.json
-0100644 0 0 /etc/keystone/keystone-paste.ini
+0100644 0 0 /usr/share/openstack/keystone/logging.conf
+0100644 0 0 /usr/share/openstack/keystone/keystone.conf
+0100644 0 0 /usr/share/openstack/keystone/policy.json
+0100644 0 0 /usr/share/openstack/keystone/keystone-paste.ini
+0040755 0 0 /usr/share/openstack/modules
+0100644 0 0 /usr/share/openstack/modules/README
+0100644 0 0 /usr/share/openstack/modules/cinder_manage
+0100644 0 0 /usr/share/openstack/modules/glance
+0100644 0 0 /usr/share/openstack/modules/glance_manage
+0100644 0 0 /usr/share/openstack/modules/heat_manage
+0100644 0 0 /usr/share/openstack/modules/keystone_manage
+0100644 0 0 /usr/share/openstack/modules/keystone_service
+0100644 0 0 /usr/share/openstack/modules/neutron_floating_ip
+0100644 0 0 /usr/share/openstack/modules/neutron_network
+0100644 0 0 /usr/share/openstack/modules/neutron_router
+0100644 0 0 /usr/share/openstack/modules/neutron_router_gateway
+0100644 0 0 /usr/share/openstack/modules/neutron_router_interface
+0100644 0 0 /usr/share/openstack/modules/neutron_sec_group
+0100644 0 0 /usr/share/openstack/modules/neutron_subnet
+0100644 0 0 /usr/share/openstack/modules/nova_flavor
+0100644 0 0 /usr/share/openstack/modules/nova_manage
0100644 0 0 /etc/logrotate.d/openstack-keystone
0100644 0 0 /etc/systemd/system/openstack-keystone.service
0100644 0 0 /etc/systemd/system/openstack-keystone-setup.service
diff --git a/openstack/usr/share/openstack/hosts b/openstack/usr/share/openstack/hosts
new file mode 100644
index 00000000..5b97818d
--- /dev/null
+++ b/openstack/usr/share/openstack/hosts
@@ -0,0 +1 @@
+localhost ansible_connection=local
diff --git a/openstack/usr/share/openstack/keystone.yml b/openstack/usr/share/openstack/keystone.yml
new file mode 100644
index 00000000..69bfb834
--- /dev/null
+++ b/openstack/usr/share/openstack/keystone.yml
@@ -0,0 +1,68 @@
+---
+- hosts: localhost
+ vars_files:
+ - "/etc/openstack/keystone.conf"
+ tasks:
+ - name: Create the keystone user.
+ user: name=keystone comment="Openstack Keystone Daemons" shell=/sbin/nologin home=/var/lib/keystone
+
+ - name: Create the /var folders for keystone
+ file: path={{ item }} state=directory owner=keystone group=keystone
+ with_items:
+ - /var/run/keystone
+ - /var/lock/keystone
+ - /var/log/keystone
+ - /var/lib/keystone
+
+ - file: path=/etc/keystone state=directory
+ - name: Add the configuration needed for lorry in /etc using templates
+ template: src=/usr/share/openstack/keystone/{{ item }} dest=/etc/keystone/{{ item }}
+ with_lines:
+ - (cd /usr/share/openstack/keystone && find -type f)
+
+ - postgresql_user: name={{ KEYSTONE_DB_USER }}
+ sudo: yes
+ sudo_user: keystone
+ - postgresql_db: name=keystone owner={{ KEYSTONE_DB_USER }}
+ sudo: yes
+ sudo_user: keystone
+
+ - keystone_manage: action=dbsync
+ sudo: yes
+ sudo_user: keystone
+
+ - name: Enable and start openstack-keystone service
+ service: name=openstack-keystone.service enabled=yes state=started
+
+ - keystone_user: >
+ tenant=admin
+ tenant_description="Admin Tenant"
+ token={{ KEYSTONE_TEMPORARY_ADMIN_TOKEN }}
+
+ - keystone_user: >
+ user=temporary_admin
+ tenant=admin
+ password={{ KEYSTONE_TEMPORARY_ADMIN_PASSWORD }}
+ token={{ KEYSTONE_TEMPORARY_ADMIN_TOKEN }}
+
+ - keystone_user: >
+ role=admin
+ user=temporary_admin
+ tenant=admin
+ token={{ KEYSTONE_TEMPORARY_ADMIN_TOKEN }}
+
+ - keystone_user: >
+ tenant=service
+ tenant_description="Service Tenant"
+ token={{ KEYSTONE_TEMPORARY_ADMIN_TOKEN }}
+
+ - keystone_service: >
+ name=keystone
+ type=identity
+ description="Keystone Identity Service"
+ publicurl={{ KEYSTONE_PUBLIC_URL }}
+ internalurl={{ KEYSONTE_INTERNAL_URL | default('http://127.0.0.1:5000/v2.0') }}
+ adminurl={{ KEYSTONE_ADMIN_URL }}
+ region='RegionOne'
+ token={{ KEYSTONE_TEMPORARY_ADMIN_TOKEN }}
+
diff --git a/openstack/etc/keystone/keystone-paste.ini b/openstack/usr/share/openstack/keystone/keystone-paste.ini
index cd132971..cd132971 100644
--- a/openstack/etc/keystone/keystone-paste.ini
+++ b/openstack/usr/share/openstack/keystone/keystone-paste.ini
diff --git a/openstack/etc/keystone/keystone.conf b/openstack/usr/share/openstack/keystone/keystone.conf
index a46cc5fc..b50ae262 100644
--- a/openstack/etc/keystone/keystone.conf
+++ b/openstack/usr/share/openstack/keystone/keystone.conf
@@ -10,7 +10,7 @@
# recommended), remove AdminTokenAuthMiddleware from your
# paste application pipelines (for example, in keystone-
# paste.ini). (string value)
-admin_token=##KEYSTONE_TEMPORARY_ADMIN_TOKEN##
+admin_token={{ KEYSTONE_TEMPORARY_ADMIN_TOKEN }}
# The IP address of the network interface for the public
# service to listen on. (string value)
@@ -200,11 +200,11 @@ public_port=5000
# The RabbitMQ broker address where a single node is used.
# (string value)
-rabbit_host=##RABBITMQ_HOST##
+rabbit_host={{ RABBITMQ_HOST }}
# The RabbitMQ broker port where a single node is used.
# (integer value)
-rabbit_port=##RABBITMQ_PORT##
+rabbit_port={{ RABBITMQ_PORT }}
# RabbitMQ HA cluster host:port pairs. (list value)
rabbit_hosts=$rabbit_host:$rabbit_port
@@ -213,10 +213,10 @@ rabbit_hosts=$rabbit_host:$rabbit_port
rabbit_use_ssl=false
# The RabbitMQ userid. (string value)
-rabbit_userid=##RABBITMQ_USER##
+rabbit_userid={{ RABBITMQ_USER }}
# The RabbitMQ password. (string value)
-rabbit_password=##RABBITMQ_PASSWORD##
+rabbit_password={{ RABBITMQ_PASSWORD }}
# the RabbitMQ login method (string value)
#rabbit_login_method=AMQPLAIN
@@ -628,7 +628,7 @@ rpc_backend=rabbit
# Deprecated group/name - [DATABASE]/sql_connection
# Deprecated group/name - [sql]/connection
#connection=<None>
-connection=postgresql://##KEYSTONE_DB_USER##:##KEYSTONE_DB_PASSWORD##@onenode/keystone
+connection=postgresql://{{ KEYSTONE_DB_USER }}:{{ KEYSTONE_DB_PASSWORD }}@{{ CONTROLLER_HOST }}/keystone
# The SQLAlchemy connection string to use to connect to the
# slave database. (string value)
diff --git a/openstack/etc/keystone/logging.conf b/openstack/usr/share/openstack/keystone/logging.conf
index 21d43c8d..21d43c8d 100644
--- a/openstack/etc/keystone/logging.conf
+++ b/openstack/usr/share/openstack/keystone/logging.conf
diff --git a/openstack/etc/keystone/policy.json b/openstack/usr/share/openstack/keystone/policy.json
index 9c7e646e..9c7e646e 100644
--- a/openstack/etc/keystone/policy.json
+++ b/openstack/usr/share/openstack/keystone/policy.json
diff --git a/openstack/usr/share/openstack/modules/README b/openstack/usr/share/openstack/modules/README
new file mode 100644
index 00000000..5cabf204
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/README
@@ -0,0 +1,3 @@
+Ansible modules borrowed from:
+
+https://github.com/openstack-ansible/openstack-ansible-modules
diff --git a/openstack/usr/share/openstack/modules/cinder_manage b/openstack/usr/share/openstack/modules/cinder_manage
new file mode 100644
index 00000000..75a0b453
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/cinder_manage
@@ -0,0 +1,105 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+DOCUMENTATION = '''
+---
+module: cinder_manage
+short_description: Initialize OpenStack Block Storage (cinder) database
+description: Create the tables for the database backend used by cinder
+options:
+ action:
+ description:
+ - action to perform. Currently only dbysnc is supported
+ required: true
+ conf:
+ description:
+ - path to cinder config file.
+ required: false
+ default: /etc/cinder/cinder.conf
+requirements: [ cinder ]
+author: Lorin Hochstein
+'''
+
+EXAMPLES = '''
+cinder_manage: action=dbsync
+'''
+
+import subprocess
+
+cinder_found = True
+try:
+ from cinder.db.sqlalchemy import migration
+ try:
+ from cinder import flags
+ FLAGS = flags.FLAGS
+ except ImportError:
+ # Starting with icehouse
+ import cinder.common.config
+ FLAGS = cinder.common.config.CONF
+except ImportError:
+ cinder_found = False
+
+
+def load_config_file(conf):
+ FLAGS(args=[], project='cinder', default_config_files=[conf])
+
+def will_db_change():
+ """ Check if the database version will change after the sync.
+
+ """
+ # Load the config file options
+ current_version = migration.db_version()
+ repository = migration._find_migrate_repo()
+ repo_version = repository.latest
+ return current_version != repo_version
+
+
+def do_dbsync():
+ """Do the dbsync. Returns (returncode, stdout, stderr)"""
+ # We call cinder-manage db_sync on the shell rather than trying to
+ # do this in Python since we have no guarantees about changes to the
+ # internals.
+ args = ['cinder-manage', 'db', 'sync']
+
+ call = subprocess.Popen(args, shell=False,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = call.communicate()
+ return (call.returncode, out, err)
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ action=dict(required=True),
+ conf=dict(required=False, default="/etc/cinder/cinder.conf")
+ ),
+ supports_check_mode=True
+ )
+
+ if not cinder_found:
+ module.fail_json(msg="cinder package could not be found")
+
+ action = module.params['action']
+ conf = module.params['conf']
+
+ if action not in ['dbsync', 'db_sync']:
+ module.fail_json(msg="Only supported action is 'dbsync'")
+
+ load_config_file(conf)
+
+ changed = will_db_change()
+ if module.check_mode:
+ module.exit_json(changed=changed)
+
+ (res, stdout, stderr) = do_dbsync()
+
+ if res == 0:
+ module.exit_json(changed=changed, stdout=stdout, stderr=stderr)
+ else:
+ module.fail_json(msg="cinder-manage returned non-zero value: %d" % res,
+ stdout=stdout, stderr=stderr)
+
+# this is magic, see lib/ansible/module_common.py
+#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
+main()
diff --git a/openstack/usr/share/openstack/modules/glance b/openstack/usr/share/openstack/modules/glance
new file mode 100644
index 00000000..f62942f9
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/glance
@@ -0,0 +1,176 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+DOCUMENTATION = '''
+---
+module: glance
+short_description: Manage OpenStack virtual machine images
+description:
+ - Upload virtual machine images to OpenStack Image Service (glance)
+requirements: [ python-glanceclient ]
+options:
+ name:
+ description:
+ - name of the image
+ required: true
+ format:
+ description:
+ - disk format
+ choices: [ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, iso]
+ required: true
+ is_public:
+ description:
+ - if true, image is public
+ choices: [true, false]
+ aliases: [public]
+ required: false
+ default: false
+ file:
+ description:
+ - path to the file that contains the image
+ required: true
+ aliases: [path]
+ auth_url:
+ description:
+ - URL to Identity service (keystone) catalog endpoint
+ required: true
+ region:
+ description:
+ - OpenStack region name
+ required: false
+ aliases: [region_name]
+ username:
+ description:
+ - user name to authenticate against Identity service
+ aliases: [user, user_name, login_user]
+ password:
+ description:
+ - password to authenticate against Identity service
+ aliases: [pass, login_password]
+ tenant_name:
+ description:
+ - name of the tenant
+ endpoint_type:
+ description:
+ - endpoint URL type
+ choices: [publicURL, internalURL]
+ required: false
+ default: publicURL
+
+examples:
+ - code: 'glance: name=cirros file=/tmp/cirros.img format=qcow2 is_public=true auth_url=http://192.168.206.130:5000/v2.0/ username=admin tenant_name=demo password=secrete region=RegionOne endpoint_type=publicURL '
+'''
+
+
+try:
+ from glanceclient import Client
+ from keystoneclient.v2_0 import client as ksclient
+except ImportError:
+ glanceclient_found = False
+else:
+ glanceclient_found = True
+
+
+def get_token_and_endpoint(auth_url, username, password, tenant_name,
+ region_name, endpoint_type):
+
+ keystone = ksclient.Client(username=username,
+ password=password,
+ tenant_name=tenant_name,
+ auth_url=auth_url,
+ region_name=region_name)
+ glance_endpoint = keystone.service_catalog.url_for(
+ service_type="image",
+ endpoint_type=endpoint_type)
+ return (keystone.auth_token, glance_endpoint)
+
+
+def authenticate(auth_url, username, password, tenant_name, region,
+ endpoint_type, version='1'):
+ """Return a keystone client object"""
+
+ (token, endpoint) = get_token_and_endpoint(auth_url, username, password,
+ tenant_name, region,
+ endpoint_type)
+
+ return Client(version, endpoint=endpoint, token=token)
+
+
+def get_images(glance, name):
+ """ Retrieve all images with a certain name """
+ images = [x for x in glance.images.list() if x.name == name]
+ return images
+
+
+def create_image(glance, name, path, disk_format, is_public, check_mode):
+ """ Create a new image from a file on the path.
+
+ Return a pair. First element indicates whether a change occurred,
+ second one is the ID of the iamge """
+
+ # If the image(s) already exists, we're done
+ images = get_images(glance, name)
+ if len(images) > 0:
+ return (False, images[0].id)
+
+ if check_mode:
+ return (True, None)
+
+ image = glance.images.create(name=name, disk_format=disk_format,
+ container_format='bare',
+ is_public=is_public)
+ image.update(data=open(path, 'rb'))
+ return (True, image.id)
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ name=dict(required=True),
+ file=dict(required=True, aliases=['path']),
+ auth_url=dict(required=True),
+ region=dict(required=False, aliases=['region_name']),
+ username=dict(required=True, aliases=['user',
+ 'user_name',
+ 'login_user']),
+ password=dict(required=True, aliases=['pass', 'login_password']),
+ tenant_name=dict(required=True, aliases=['tenant']),
+ disk_format=dict(required=True,
+ choices=['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw',
+ 'qcow2', 'vdi', 'iso'],
+ aliases=['disk-format', 'format']),
+ is_public=dict(required=False,
+ default=False,
+ aliases=['public']),
+ endpoint_type=dict(required=False,
+ choices=['publicURL', 'internalURL'],
+ default='publicURL')
+ ),
+ supports_check_mode=True
+ )
+
+ name = module.params['name']
+ path = module.params['file']
+ auth_url = module.params['auth_url']
+ region = module.params['region']
+ username = module.params['username']
+ password = module.params['password']
+ tenant_name = module.params['tenant_name']
+ disk_format = module.params['disk_format']
+ is_public = module.params['is_public']
+ endpoint_type = module.params['endpoint_type']
+ check_mode = module.check_mode
+
+ glance = authenticate(auth_url, username, password, tenant_name, region,
+ endpoint_type)
+
+ (changed, id) = create_image(glance, name, path, disk_format, is_public,
+ check_mode)
+
+ module.exit_json(changed=changed, name=name, id=id)
+
+# this is magic, see lib/ansible/module_common.py
+#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
+if __name__ == '__main__':
+ main()
diff --git a/openstack/usr/share/openstack/modules/glance_manage b/openstack/usr/share/openstack/modules/glance_manage
new file mode 100644
index 00000000..b89e7bb0
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/glance_manage
@@ -0,0 +1,158 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+DOCUMENTATION = '''
+---
+module: glance_manage
+short_description: Initialize OpenStack Image (glance) database
+description: Create the tables for the database backend used by glance
+options:
+ action:
+ description:
+ - action to perform. Currently only dbsync is supported.
+ required: true
+ conf:
+ description:
+ - path to glance-registry config file.
+ required: false
+ default: /etc/glance/glance-registry.conf
+requirements: [ glance ]
+author: Lorin Hochstein
+'''
+
+EXAMPLES = '''
+glance_manage: action=dbsync
+'''
+
+import os
+import subprocess
+import sys
+
+try:
+ import glance
+ import sqlalchemy
+except ImportError:
+ print("failed=True msg='glance is not installed'")
+ sys.exit(1)
+
+from glance.version import version_info
+# this is necessary starting from havana release due to bug 885529
+# https://bugs.launchpad.net/glance/+bug/885529
+from glance.openstack.common import gettextutils
+gettextutils.install('glance')
+import glance.db.sqlalchemy.api
+
+try:
+ glance_version = version_info.version_string()
+except AttributeError:
+ glance_version = version_info.version
+
+if glance_version.startswith('2014.2'):
+ from oslo.config.cfg import CONF
+ from oslo.db.sqlalchemy import migration
+ from migrate.versioning import api as versioning_api
+ from glance.db import migration as db_migration
+ from glance.db.sqlalchemy import api as db_api
+elif glance_version.startswith('2014.1'):
+ from oslo.config.cfg import CONF
+ from glance.openstack.common.db.sqlalchemy import migration
+ from migrate.versioning import api as versioning_api
+else:
+ from glance.db.sqlalchemy import migration
+ from glance.common.exception import DatabaseMigrationError
+ from migrate.versioning import api as versioning_api
+ CONF = migration.CONF
+
+def is_under_version_control(conf):
+ """ Return true if the database is under version control"""
+ CONF(project='glance', default_config_files=[conf])
+ try:
+ migration.db_version()
+ except DatabaseMigrationError:
+ return False
+ # db_version() will fail with TypeError on icehouse. Icehouse uses db
+ # migration so we're good.
+ finally:
+ return True
+
+
+def will_db_change(conf):
+ """ Check if the database version will change after the sync """
+ # Load the config file options
+ if not is_under_version_control(conf):
+ return True
+ if glance_version.startswith('2014.2'):
+ engine = db_api.get_engine()
+ repo_path = db_migration.MIGRATE_REPO_PATH
+ current_version = migration.db_version(db_api.get_engine(),
+ repo_path,
+ db_migration.INIT_VERSION)
+ elif glance_version.startswith('2014.1'):
+ repo_path = os.path.join(os.path.dirname(glance.__file__),
+ 'db', 'sqlalchemy', 'migrate_repo')
+ engine = sqlalchemy.create_engine(CONF.database.connection)
+ current_version = migration.db_version(engine, repo_path, 0)
+ else:
+ repo_path = migration.get_migrate_repo_path()
+ current_version = migration.db_version()
+
+ repo_version = versioning_api.repository.Repository(repo_path).latest
+ return current_version != repo_version
+
+
+def put_under_version_control():
+ """ Create the initial sqlalchemy migrate database tables. """
+ args = ['glance-manage', 'version_control', '0']
+
+ call = subprocess.Popen(args, shell=False,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = call.communicate()
+ return (call.returncode, out, err)
+
+
+def do_dbsync():
+ """ Do a database migration """
+ args = ['glance-manage', 'db_sync']
+
+ call = subprocess.Popen(args, shell=False,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = call.communicate()
+ return (call.returncode, out, err)
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ action=dict(required=True),
+ conf=dict(required=False,
+ default="/etc/glance/glance-registry.conf")
+ ),
+ supports_check_mode=True
+ )
+
+ action = module.params['action']
+ if action not in ['dbsync', 'db_sync']:
+ module.fail_json(msg="Only supported action is 'dbsync'")
+
+ conf = module.params['conf']
+
+ changed = will_db_change(conf)
+ if module.check_mode:
+ module.exit_json(changed=changed)
+
+ if not is_under_version_control(conf):
+ (res, stdout, stderr) = put_under_version_control()
+ if res != 0:
+ msg = "failed to put glance db under version control"
+ module.fail_json(msg=msg, stdout=stdout, stderr=stderr)
+
+ (res, stdout, stderr) = do_dbsync()
+ if res != 0:
+ msg = "failed to synchronize glance db with repository"
+ module.fail_json(msg=msg, stdout=stdout, stderr=stderr)
+
+ module.exit_json(changed=changed)
+
+#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
+main()
diff --git a/openstack/usr/share/openstack/modules/heat_manage b/openstack/usr/share/openstack/modules/heat_manage
new file mode 100644
index 00000000..e24abbda
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/heat_manage
@@ -0,0 +1,105 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+DOCUMENTATION = '''
+---
+module: heat_manage
+short_description: Initialize Orchestration (heat) database
+description: Create the tables for the database backend used by heat
+options:
+ action:
+ description:
+ - action to perform. Currently only dbysnc is supported
+ required: true
+ conf:
+ description:
+ - path to keystone config file.
+ required: false
+ default: /etc/heat/heat.conf
+requirements: [ python-heatclient ]
+author: Gauvain Pocentek
+'''
+
+EXAMPLES = '''
+heat_manage: action=dbsync
+'''
+
+import subprocess
+
+try:
+ import heat
+ from heat.db.sqlalchemy import migration
+ from migrate.versioning import api as versioning_api
+ from heat.db import api
+ from oslo.config import cfg
+except ImportError:
+ heat_found = False
+else:
+ heat_found = True
+
+
+def will_db_change(conf):
+ """ Check if the database version will change after the sync.
+
+ conf is the path to the heat config file
+
+ """
+ # Load the config file options
+ cfg.CONF(project='heat', default_config_files=[conf])
+ try:
+ current_version = migration.db_version()
+ except TypeError: # juno
+ current_version = api.db_version(api.get_engine())
+
+ repo_path = os.path.join(os.path.dirname(heat.__file__),
+ 'db', 'sqlalchemy', 'migrate_repo')
+ repo_version = versioning_api.repository.Repository(repo_path).latest
+ return current_version != repo_version
+
+
+def do_dbsync():
+ """Do the dbsync. Returns (returncode, stdout, stderr)"""
+ # We call heat-manage db_sync on the shell rather than trying to
+ # do this in Python since we have no guarantees about changes to the
+ # internals.
+ args = ['heat-manage', 'db_sync']
+
+ call = subprocess.Popen(args, shell=False,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = call.communicate()
+ return (call.returncode, out, err)
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ action=dict(required=True),
+ conf=dict(required=False, default="/etc/heat/heat.conf")
+ ),
+ supports_check_mode=True
+ )
+
+ if not heat_found:
+ module.fail_json(msg="python-heatclient could not be found")
+
+ action = module.params['action']
+ conf = module.params['conf']
+ if action not in ['dbsync', 'db_sync']:
+ module.fail_json(msg="Only supported action is 'dbsync'")
+
+ changed = will_db_change(conf)
+ if module.check_mode:
+ module.exit_json(changed=changed)
+
+ (res, stdout, stderr) = do_dbsync()
+
+ if res == 0:
+ module.exit_json(changed=changed, stdout=stdout, stderr=stderr)
+ else:
+ module.fail_json(msg="heat-manage returned non-zero value: %d" % res,
+ stdout=stdout, stderr=stderr)
+
+
+from ansible.module_utils.basic import *
+main()
diff --git a/openstack/usr/share/openstack/modules/keystone_manage b/openstack/usr/share/openstack/modules/keystone_manage
new file mode 100644
index 00000000..780f7c9a
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/keystone_manage
@@ -0,0 +1,123 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+DOCUMENTATION = '''
+---
+module: keystone_manage
+short_description: Initialize OpenStack Identity (keystone) database
+description: Create the tables for the database backend used by keystone
+options:
+ action:
+ description:
+ - action to perform. Currently only dbysnc is supported
+ required: true
+ conf:
+ description:
+ - path to keystone config file.
+ required: false
+ default: /etc/keystone/keystone.conf
+requirements: [ keystone ]
+author: Lorin Hochstein
+'''
+
+EXAMPLES = '''
+keystone_manage: action=dbsync
+'''
+
+import subprocess
+
+try:
+ # this is necessary starting from havana release due to bug 885529
+ # https://bugs.launchpad.net/glance/+bug/885529
+ from keystone.openstack.common import gettextutils
+ gettextutils.install('keystone')
+except AttributeError:
+ # this is not havana
+ pass
+
+try:
+ from keystone.common import sql
+ from migrate.versioning import api as versioning_api
+except ImportError:
+ keystone_found = False
+else:
+ keystone_found = True
+
+try:
+ # for icehouse
+ from keystone.common.sql import migration_helpers as migration
+except ImportError:
+ pass
+
+
+def will_db_change(conf):
+ """ Check if the database version will change after the sync.
+
+ conf is the path to the keystone config file
+
+ """
+ # Load the config file options
+ try:
+ # before icehouse
+ sql.migration.CONF(project='keystone', default_config_files=[conf])
+ current_version = sql.migration.db_version()
+ except AttributeError:
+ # starting with icehouse
+ sql.core.CONF(project='keystone', default_config_files=[conf])
+ current_version = migration.get_db_version()
+
+ # in havana the method _find_migrate_repo has been renamed to find_migrate_repo
+ try:
+ repo_path = migration.find_migrate_repo()
+ except AttributeError:
+ repo_path = migration._find_migrate_repo()
+ repo_version = versioning_api.repository.Repository(repo_path).latest
+ return current_version != repo_version
+
+
+def do_dbsync():
+ """Do the dbsync. Returns (returncode, stdout, stderr)"""
+ # We call keystone-manage db_sync on the shell rather than trying to
+ # do this in Python since we have no guarantees about changes to the
+ # internals.
+ args = ['keystone-manage', 'db_sync']
+
+ call = subprocess.Popen(args, shell=False,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = call.communicate()
+ return (call.returncode, out, err)
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ action=dict(required=True),
+ conf=dict(required=False, default="/etc/keystone/keystone.conf")
+ ),
+ supports_check_mode=True
+ )
+
+ if not keystone_found:
+ module.fail_json(msg="keystone package could not be found")
+
+ action = module.params['action']
+ conf = module.params['conf']
+ if action not in ['dbsync', 'db_sync']:
+ module.fail_json(msg="Only supported action is 'dbsync'")
+
+ changed = will_db_change(conf)
+ if module.check_mode:
+ module.exit_json(changed=changed)
+
+ (res, stdout, stderr) = do_dbsync()
+
+ if res == 0:
+ module.exit_json(changed=changed, stdout=stdout, stderr=stderr)
+ else:
+ module.fail_json(msg="keystone-manage returned non-zero value: %d" % res,
+ stdout=stdout, stderr=stderr)
+
+# this is magic, see lib/ansible/module_common.py
+#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
+main()
diff --git a/openstack/usr/share/openstack/modules/keystone_service b/openstack/usr/share/openstack/modules/keystone_service
new file mode 100644
index 00000000..aa4302d1
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/keystone_service
@@ -0,0 +1,309 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+DOCUMENTATION = '''
+---
+module: keystone_service
+short_description: Manage OpenStack Identity (keystone) service endpoints
+options:
+ name:
+ description:
+ - name of service (e.g., keystone)
+ required: yes
+ type:
+ description:
+ - type of service (e.g., identity)
+ required: yes
+ description:
+ description:
+ - description of service (e.g., Identity Service)
+ required: yes
+ public_url:
+ description:
+ - public url of service.
+ - 'Alias: I(url)'
+ - 'Alias: I(publicurl)'
+ required: yes
+ internal_url:
+ description:
+ - internal url of service.
+ - 'Alias: I(internalurl)'
+ required: no
+ default: value of public_url
+ admin_url:
+ description:
+ - admin url of service.
+ - 'Alias: I(adminurl)'
+ required: no
+ default: value of public_url
+ insecure:
+ description:
+ - allow use of self-signed SSL certificates
+ required: no
+ choices: [ "yes", "no" ]
+ region:
+ description:
+ - region of service
+ required: yes
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices: ['present', 'absent']
+ default: present
+
+
+
+requirements: [ python-keystoneclient ]
+author: Lorin Hochstein
+'''
+
+EXAMPLES = '''
+examples:
+keystone_service: >
+ name=keystone
+ type=identity
+ description="Keystone Identity Service"
+ publicurl=http://192.168.206.130:5000/v2.0
+ internalurl=http://192.168.206.130:5000/v2.0
+ adminurl=http://192.168.206.130:35357/v2.0
+
+keystone_service: >
+ name=glance
+ type=image
+ description="Glance Identity Service"
+ url=http://192.168.206.130:9292
+
+'''
+
+try:
+ from keystoneclient.v2_0 import client
+except ImportError:
+ keystoneclient_found = False
+else:
+ keystoneclient_found = True
+
+import traceback
+
+
+def authenticate(endpoint, token, login_user, login_password, tenant_name,
+ insecure):
+ """Return a keystone client object"""
+
+ if token:
+ return client.Client(endpoint=endpoint, token=token, insecure=insecure)
+ else:
+ return client.Client(auth_url=endpoint, username=login_user,
+ password=login_password, tenant_name=tenant_name,
+ insecure=insecure)
+
+def get_service(keystone, name):
+ """ Retrieve a service by name """
+ services = [x for x in keystone.services.list() if x.name == name]
+ count = len(services)
+ if count == 0:
+ raise KeyError("No keystone services with name %s" % name)
+ elif count > 1:
+ raise ValueError("%d services with name %s" % (count, name))
+ else:
+ return services[0]
+
+
+def get_endpoint(keystone, name):
+ """ Retrieve a service endpoint by name """
+ service = get_service(keystone, name)
+ endpoints = [x for x in keystone.endpoints.list()
+ if x.service_id == service.id]
+ count = len(endpoints)
+ if count == 0:
+ raise KeyError("No keystone endpoints with service name %s" % name)
+ elif count > 1:
+ raise ValueError("%d endpoints with service name %s" % (count, name))
+ else:
+ return endpoints[0]
+
+
+def ensure_service_present(keystone, name, service_type, description,
+ check_mode):
+ """ Ensure the service is present and has the right values
+
+ Returns a pair, where the first element is a boolean that indicates
+ a state change, and the second element is the service uuid, or None
+ if running in check mode"""
+ service = None
+ try:
+ service = get_service(keystone, name)
+ except:
+ # Service doesn't exist yet, we'll need to create one
+ pass
+ else:
+ # See if it matches exactly
+ if service.name == name and \
+ service.type == service_type and \
+ service.description == description:
+
+ # Same, no changes needed
+ return (False, service.id)
+
+ # At this point, we know we will need to make a change
+ if check_mode:
+ return (True, None)
+
+ if service is None:
+ service = keystone.services.create(name=name,
+ service_type=service_type,
+ description=description)
+ return (True, service.id)
+ else:
+ msg = "keystone v2 API doesn't support updating services"
+ raise ValueError(msg)
+
+
+def ensure_endpoint_present(keystone, name, public_url, internal_url,
+ admin_url, region, check_mode):
+ """ Ensure the service endpoint is present and have the right values
+
+ Assumes the service object has already been created at this point"""
+
+ service = get_service(keystone, name)
+ endpoint = None
+ try:
+ endpoint = get_endpoint(keystone, name)
+ except:
+ # Endpoint doesn't exist yet, we'll need to create one
+ pass
+ else:
+ # See if it matches
+ if endpoint.publicurl == public_url and \
+ endpoint.adminurl == admin_url and \
+ endpoint.internalurl == internal_url and \
+ endpoint.region == region:
+
+ # Same, no changes needed
+ return (False, endpoint.id)
+
+ # At this point, we know we will need to make a change
+ if check_mode:
+ return (True, None)
+
+ if endpoint is None:
+ endpoint = keystone.endpoints.create(region=region,
+ service_id=service.id,
+ publicurl=public_url,
+ adminurl=admin_url,
+ internalurl=internal_url)
+ return (True, endpoint.id)
+ else:
+ msg = "keystone v2 API doesn't support updating endpoints"
+ raise ValueError(msg)
+
+
+def ensure_service_absent(keystone, name, check_mode):
+ """ Ensure the service is absent"""
+
+ raise NotImplementedError()
+
+def ensure_endpoint_absent(keystone, name, check_mode):
+ """ Ensure the service endpoint """
+ raise NotImplementedError()
+
+
+def dispatch(keystone, name, service_type, description, public_url,
+ internal_url, admin_url, region, state, check_mode):
+
+ if state == 'present':
+ (service_changed, service_id) = ensure_service_present(keystone,
+ name,
+ service_type,
+ description,
+ check_mode)
+
+ (endpoint_changed, endpoint_id) = ensure_endpoint_present(
+ keystone,
+ name,
+ public_url,
+ internal_url,
+ admin_url,
+ region,
+ check_mode)
+ return dict(changed=service_changed or endpoint_changed,
+ service_id=service_id,
+ endpoint_id=endpoint_id)
+ elif state == 'absent':
+ endpoint_changed = ensure_endpoint_absent(keystone, name, check_mode)
+ service_changed = ensure_service_absent(keystone, name, check_mode)
+ return dict(changed=service_changed or endpoint_changed)
+ else:
+ raise ValueError("Code should never reach here")
+
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ name=dict(required=True),
+ type=dict(required=True),
+ description=dict(required=False),
+ public_url=dict(required=True, aliases=['url', 'publicurl']),
+ internal_url=dict(required=False, aliases=['internalurl']),
+ admin_url=dict(required=False, aliases=['adminurl']),
+ region=dict(required=True),
+ state=dict(default='present', choices=['present', 'absent']),
+ endpoint=dict(required=False,
+ default="http://127.0.0.1:35357/v2.0",
+ aliases=['auth_url']),
+ token=dict(required=False),
+ insecure=dict(required=False, default=False, choices=BOOLEANS),
+
+ login_user=dict(required=False),
+ login_password=dict(required=False),
+ tenant_name=dict(required=False, aliases=['tenant'])
+ ),
+ supports_check_mode=True,
+ mutually_exclusive=[['token', 'login_user'],
+ ['token', 'login_password'],
+ ['token', 'tenant_name']]
+ )
+
+ endpoint = module.params['endpoint']
+ token = module.params['token']
+ login_user = module.params['login_user']
+ login_password = module.params['login_password']
+ tenant_name = module.params['tenant_name']
+ insecure = module.boolean(module.params['insecure'])
+ name = module.params['name']
+ service_type = module.params['type']
+ description = module.params['description']
+ public_url = module.params['public_url']
+ internal_url = module.params['internal_url']
+ if internal_url is None:
+ internal_url = public_url
+ admin_url = module.params['admin_url']
+ if admin_url is None:
+ admin_url = public_url
+ region = module.params['region']
+ state = module.params['state']
+
+ keystone = authenticate(endpoint, token, login_user, login_password,
+ tenant_name, insecure)
+ check_mode = module.check_mode
+
+ try:
+ d = dispatch(keystone, name, service_type, description,
+ public_url, internal_url, admin_url, region, state,
+ check_mode)
+ except Exception:
+ if check_mode:
+ # If we have a failure in check mode
+ module.exit_json(changed=True,
+ msg="exception: %s" % traceback.format_exc())
+ else:
+ module.fail_json(msg=traceback.format_exc())
+ else:
+ module.exit_json(**d)
+
+
+# this is magic, see lib/ansible/module_common.py
+#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
+if __name__ == '__main__':
+ main()
diff --git a/openstack/usr/share/openstack/modules/neutron_floating_ip b/openstack/usr/share/openstack/modules/neutron_floating_ip
new file mode 100644
index 00000000..4c141025
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/neutron_floating_ip
@@ -0,0 +1,274 @@
+#!/usr/bin/python
+#coding: utf-8 -*-
+
+# (c) 2013, Benno Joy <benno@ansibleworks.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/>.
+
+try:
+ from novaclient.v1_1 import client as nova_client
+ from neutronclient.neutron import client
+ from keystoneclient.v2_0 import client as ksclient
+ import time
+except ImportError:
+ print("failed=True msg='glanceclient,keystoneclient and neutronclient client are required'")
+
+DOCUMENTATION = '''
+---
+module: neutron_floating_ip
+version_added: "1.2"
+short_description: Add/Remove floating IP from an instance
+description:
+ - Add or Remove a floating IP to an instance
+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
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices: ['present', 'absent']
+ default: present
+ network_name:
+ description:
+ - Name of the network from which IP has to be assigned to VM. Please make sure the network is an external network
+ required: true
+ default: None
+ port_network_name:
+ description:
+ - Name of the network where the VM port lives. Useful when the VM has more than one port
+ required: false
+ default: None
+ instance_name:
+ description:
+ - The name of the instance to which the IP address should be assigned
+ required: true
+ default: None
+requirements: ["novaclient", "neutronclient", "keystoneclient"]
+'''
+
+EXAMPLES = '''
+# Assign a floating ip to the instance from an external network
+- neutron_floating_ip: state=present login_username=admin login_password=admin
+ login_tenant_name=admin network_name=external_network
+ instance_name=vm1
+'''
+
+def _get_ksclient(module, kwargs):
+ try:
+ kclient = ksclient.Client(username=kwargs.get('login_username'),
+ password=kwargs.get('login_password'),
+ tenant_name=kwargs.get('login_tenant_name'),
+ auth_url=kwargs.get('auth_url'))
+ except Exception as e:
+ module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message)
+ global _os_keystone
+ _os_keystone = kclient
+ return kclient
+
+
+def _get_endpoint(module, ksclient):
+ try:
+ endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
+ except Exception as e:
+ module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message)
+ return endpoint
+
+def _get_neutron_client(module, kwargs):
+ _ksclient = _get_ksclient(module, kwargs)
+ token = _ksclient.auth_token
+ endpoint = _get_endpoint(module, _ksclient)
+ kwargs = {
+ 'token': token,
+ 'endpoint_url': endpoint
+ }
+ try:
+ neutron = client.Client('2.0', **kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in connecting to neutron: %s " % e.message)
+ return neutron
+
+def _get_server_state(module, nova):
+ server_info = None
+ server = None
+ try:
+ for server in nova.servers.list():
+ if server:
+ info = server._info
+ if info['name'] == module.params['instance_name']:
+ if info['status'] != 'ACTIVE' and module.params['state'] == 'present':
+ module.fail_json( msg="The VM is available but not Active. state:" + info['status'])
+ server_info = info
+ break
+ except Exception as e:
+ module.fail_json(msg = "Error in getting the server list: %s" % e.message)
+ return server_info, server
+
+def _get_port_info(neutron, module, instance_id):
+ if module.params['port_network_name'] is None:
+ kwargs = {
+ 'device_id': instance_id
+ }
+ else:
+ network_id = _get_net_id(neutron, module.params['port_network_name'])
+ if not network_id:
+ module.fail_json(msg = "cannot find the network specified, please check")
+
+ kwargs = {
+ 'device_id': instance_id,
+ 'network_id': network_id
+ }
+ try:
+ ports = neutron.list_ports(**kwargs)
+ except Exception as e:
+ module.fail_json( msg = "Error in listing ports: %s" % e.message)
+ if not ports['ports']:
+ return None, None
+ return ports['ports'][0]['fixed_ips'][0]['ip_address'], ports['ports'][0]['id']
+
+def _get_floating_ip(module, neutron, fixed_ip_address):
+ kwargs = {
+ 'fixed_ip_address': fixed_ip_address
+ }
+ try:
+ ips = neutron.list_floatingips(**kwargs)
+ except Exception as e:
+ module.fail_json(msg = "error in fetching the floatingips's %s" % e.message)
+ if not ips['floatingips']:
+ return None, None
+ return ips['floatingips'][0]['id'], ips['floatingips'][0]['floating_ip_address']
+
+def _assign_floating_ip(neutron, module, port_id, net_id):
+ kwargs = {
+ 'floating_network_id': net_id
+ }
+ try:
+ ips = neutron.list_floatingips(**kwargs)
+ except Exception as e:
+ module.fail_json(msg = "error in fetching the floatingips's %s" % e.message)
+
+ fip = next((fip for fip in ips['floatingips'] if fip['port_id'] is None), None)
+
+ if fip is None:
+ _create_floating_ip(neutron, module, port_id, net_id)
+ else:
+ _update_floating_ip(neutron, module, port_id, fip['id'])
+
+def _create_floating_ip(neutron, module, port_id, net_id):
+ kwargs = {
+ 'port_id': port_id,
+ 'floating_network_id': net_id
+ }
+ try:
+ result = neutron.create_floatingip({'floatingip': kwargs})
+ except Exception as e:
+ module.fail_json(msg="There was an error in updating the floating ip address: %s" % e.message)
+ module.exit_json(changed=True, result=result, public_ip=result['floatingip']['floating_ip_address'])
+
+def _get_net_id(neutron, network_name):
+ kwargs = {
+ 'name': network_name,
+ }
+ try:
+ networks = neutron.list_networks(**kwargs)
+ except Exception as e:
+ module.fail_json("Error in listing neutron networks: %s" % e.message)
+ if not networks['networks']:
+ return None
+ return networks['networks'][0]['id']
+
+def _update_floating_ip(neutron, module, port_id, floating_ip_id):
+ kwargs = {
+ 'port_id': port_id
+ }
+ try:
+ result = neutron.update_floatingip(floating_ip_id, {'floatingip': kwargs})
+ except Exception as e:
+ module.fail_json(msg="There was an error in updating the floating ip address: %s" % e.message)
+ module.exit_json(changed=True, result=result, public_ip=result['floatingip']['floating_ip_address'])
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec = dict(
+ login_username = dict(default='admin'),
+ login_password = dict(required=True),
+ login_tenant_name = dict(required='True'),
+ auth_url = dict(default='http://127.0.0.1:35357/v2.0/'),
+ region_name = dict(default=None),
+ network_name = dict(required=True),
+ instance_name = dict(required=True),
+ port_network_name = dict(default=None),
+ state = dict(default='present', choices=['absent', 'present'])
+ ),
+ )
+
+ try:
+ nova = nova_client.Client(module.params['login_username'], module.params['login_password'],
+ module.params['login_tenant_name'], module.params['auth_url'], service_type='compute')
+ neutron = _get_neutron_client(module, module.params)
+ except Exception as e:
+ module.fail_json(msg="Error in authenticating to nova: %s" % e.message)
+
+ server_info, server_obj = _get_server_state(module, nova)
+ if not server_info:
+ module.fail_json(msg="The instance name provided cannot be found")
+
+ fixed_ip, port_id = _get_port_info(neutron, module, server_info['id'])
+ if not port_id:
+ module.fail_json(msg="Cannot find a port for this instance, maybe fixed ip is not assigned")
+
+ floating_id, floating_ip = _get_floating_ip(module, neutron, fixed_ip)
+
+ if module.params['state'] == 'present':
+ if floating_ip:
+ module.exit_json(changed = False, public_ip=floating_ip)
+ net_id = _get_net_id(neutron, module.params['network_name'])
+ if not net_id:
+ module.fail_json(msg = "cannot find the network specified, please check")
+ _assign_floating_ip(neutron, module, port_id, net_id)
+
+ if module.params['state'] == 'absent':
+ if floating_ip:
+ _update_floating_ip(neutron, module, None, floating_id)
+ module.exit_json(changed=False)
+
+# this is magic, see lib/ansible/module.params['common.py
+from ansible.module_utils.basic import *
+main()
+
+
diff --git a/openstack/usr/share/openstack/modules/neutron_network b/openstack/usr/share/openstack/modules/neutron_network
new file mode 100644
index 00000000..6dee0450
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/neutron_network
@@ -0,0 +1,282 @@
+#!/usr/bin/python
+#coding: utf-8 -*-
+
+# (c) 2013, Benno Joy <benno@ansibleworks.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/>.
+
+try:
+ from neutronclient.neutron import client
+ from keystoneclient.v2_0 import client as ksclient
+except ImportError:
+ print("failed=True msg='neutronclient and keystone client are required'")
+
+DOCUMENTATION = '''
+---
+module: neutron_network
+version_added: "1.4"
+short_description: Creates/Removes networks from OpenStack
+description:
+ - Add or Remove network from OpenStack.
+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'
+ tenant_name:
+ description:
+ - The name of the tenant for whom the network is created
+ required: false
+ default: None
+ 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
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices: ['present', 'absent']
+ default: present
+ name:
+ description:
+ - Name to be assigned to the nework
+ required: true
+ default: None
+ provider_network_type:
+ description:
+ - The type of the network to be created, gre, vxlan, vlan, local. Available types depend on the plugin. The Neutron service decides if not specified.
+ required: false
+ default: None
+ provider_physical_network:
+ description:
+ - The physical network which would realize the virtual network for flat and vlan networks.
+ required: false
+ default: None
+ provider_segmentation_id:
+ description:
+ - The id that has to be assigned to the network, in case of vlan networks that would be vlan id, for gre the tunnel id and for vxlan the VNI.
+ required: false
+ default: None
+ router_external:
+ description:
+ - If 'yes', specifies that the virtual network is a external network (public).
+ required: false
+ default: false
+ shared:
+ description:
+ - Whether this network is shared or not
+ required: false
+ default: false
+ admin_state_up:
+ description:
+ - Whether the state should be marked as up or down
+ required: false
+ default: true
+requirements: ["neutronclient", "keystoneclient"]
+
+'''
+
+EXAMPLES = '''
+# Create a GRE backed Neutron network with tunnel id 1 for tenant1
+- neutron_network: name=t1network tenant_name=tenant1 state=present
+ provider_network_type=gre provider_segmentation_id=1
+ login_username=admin login_password=admin login_tenant_name=admin
+
+# Create an external network
+- neutron_network: name=external_network state=present
+ provider_network_type=local router_external=yes
+ login_username=admin login_password=admin login_tenant_name=admin
+'''
+
+_os_keystone = None
+_os_tenant_id = None
+
+def _get_ksclient(module, kwargs):
+ try:
+ kclient = ksclient.Client(username=kwargs.get('login_username'),
+ password=kwargs.get('login_password'),
+ tenant_name=kwargs.get('login_tenant_name'),
+ auth_url=kwargs.get('auth_url'))
+ except Exception as e:
+ module.fail_json(msg = "Error authenticating to the keystone: %s" %e.message)
+ global _os_keystone
+ _os_keystone = kclient
+ return kclient
+
+
+def _get_endpoint(module, ksclient):
+ try:
+ endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
+ except Exception as e:
+ module.fail_json(msg = "Error getting endpoint for Neutron: %s " %e.message)
+ return endpoint
+
+def _get_neutron_client(module, kwargs):
+ _ksclient = _get_ksclient(module, kwargs)
+ token = _ksclient.auth_token
+ endpoint = _get_endpoint(module, _ksclient)
+ kwargs = {
+ 'token': token,
+ 'endpoint_url': endpoint
+ }
+ try:
+ neutron = client.Client('2.0', **kwargs)
+ except Exception as e:
+ module.fail_json(msg = " Error in connecting to Neutron: %s " %e.message)
+ return neutron
+
+def _set_tenant_id(module):
+ global _os_tenant_id
+ if not module.params['tenant_name']:
+ tenant_name = module.params['login_tenant_name']
+ else:
+ tenant_name = module.params['tenant_name']
+
+ for tenant in _os_keystone.tenants.list():
+ if tenant.name == tenant_name:
+ _os_tenant_id = tenant.id
+ break
+ if not _os_tenant_id:
+ module.fail_json(msg = "The tenant id cannot be found, please check the paramters")
+
+
+def _get_net_id(neutron, module):
+ kwargs = {
+ 'tenant_id': _os_tenant_id,
+ 'name': module.params['name'],
+ }
+ try:
+ networks = neutron.list_networks(**kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in listing Neutron networks: %s" % e.message)
+ if not networks['networks']:
+ return None
+ return networks['networks'][0]['id']
+
+def _create_network(module, neutron):
+
+ neutron.format = 'json'
+
+ network = {
+ 'name': module.params.get('name'),
+ 'tenant_id': _os_tenant_id,
+ 'provider:network_type': module.params.get('provider_network_type'),
+ 'provider:physical_network': module.params.get('provider_physical_network'),
+ 'provider:segmentation_id': module.params.get('provider_segmentation_id'),
+ 'router:external': module.params.get('router_external'),
+ 'shared': module.params.get('shared'),
+ 'admin_state_up': module.params.get('admin_state_up'),
+ }
+
+ if module.params['provider_network_type'] == 'local':
+ network.pop('provider:physical_network', None)
+ network.pop('provider:segmentation_id', None)
+
+ if module.params['provider_network_type'] == 'flat':
+ network.pop('provider:segmentation_id', None)
+
+ if module.params['provider_network_type'] == 'gre':
+ network.pop('provider:physical_network', None)
+
+ if module.params['provider_network_type'] == 'vxlan':
+ network.pop('provider:physical_network', None)
+
+ if module.params['provider_network_type'] is None:
+ network.pop('provider:network_type', None)
+ network.pop('provider:physical_network', None)
+ network.pop('provider:segmentation_id', None)
+
+ try:
+ net = neutron.create_network({'network':network})
+ except Exception as e:
+ module.fail_json(msg = "Error in creating network: %s" % e.message)
+ return net['network']['id']
+
+def _delete_network(module, net_id, neutron):
+
+ try:
+ id = neutron.delete_network(net_id)
+ except Exception as e:
+ module.fail_json(msg = "Error in deleting the network: %s" % e.message)
+ return True
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec = dict(
+ login_username = dict(default='admin'),
+ login_password = dict(required=True),
+ login_tenant_name = dict(required='True'),
+ auth_url = dict(default='http://127.0.0.1:35357/v2.0/'),
+ region_name = dict(default=None),
+ name = dict(required=True),
+ tenant_name = dict(default=None),
+ provider_network_type = dict(default=None, choices=['local', 'vlan', 'flat', 'gre', 'vxlan']),
+ provider_physical_network = dict(default=None),
+ provider_segmentation_id = dict(default=None),
+ router_external = dict(default=False, type='bool'),
+ shared = dict(default=False, type='bool'),
+ admin_state_up = dict(default=True, type='bool'),
+ state = dict(default='present', choices=['absent', 'present'])
+ ),
+ )
+
+ if module.params['provider_network_type'] in ['vlan' , 'flat']:
+ if not module.params['provider_physical_network']:
+ module.fail_json(msg = " for vlan and flat networks, variable provider_physical_network should be set.")
+
+ if module.params['provider_network_type'] in ['vlan', 'gre', 'vxlan']:
+ if not module.params['provider_segmentation_id']:
+ module.fail_json(msg = " for vlan, gre & vxlan networks, variable provider_segmentation_id should be set.")
+
+ neutron = _get_neutron_client(module, module.params)
+
+ _set_tenant_id(module)
+
+ if module.params['state'] == 'present':
+ network_id = _get_net_id(neutron, module)
+ if not network_id:
+ network_id = _create_network(module, neutron)
+ module.exit_json(changed = True, result = "Created", id = network_id)
+ else:
+ module.exit_json(changed = False, result = "Success", id = network_id)
+
+ if module.params['state'] == 'absent':
+ network_id = _get_net_id(neutron, module)
+ if not network_id:
+ module.exit_json(changed = False, result = "Success")
+ else:
+ _delete_network(module, network_id, neutron)
+ module.exit_json(changed = True, result = "Deleted")
+
+# this is magic, see lib/ansible/module.params['common.py
+from ansible.module_utils.basic import *
+main()
diff --git a/openstack/usr/share/openstack/modules/neutron_router b/openstack/usr/share/openstack/modules/neutron_router
new file mode 100644
index 00000000..56d384d0
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/neutron_router
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+#coding: utf-8 -*-
+
+# (c) 2013, Benno Joy <benno@ansibleworks.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/>.
+
+try:
+ from neutronclient.neutron import client
+ from keystoneclient.v2_0 import client as ksclient
+except ImportError:
+ print("failed=True msg='neutronclient and keystone client are required'")
+
+DOCUMENTATION = '''
+---
+module: neutron_router
+version_added: "1.2"
+short_description: Create or Remove router from openstack
+description:
+ - Create or Delete routers from OpenStack
+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
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices: ['present', 'absent']
+ default: present
+ name:
+ description:
+ - Name to be give to the router
+ required: true
+ default: None
+ tenant_name:
+ description:
+ - Name of the tenant for which the router has to be created, if none router would be created for the login tenant.
+ required: false
+ default: None
+ admin_state_up:
+ description:
+ - desired admin state of the created router .
+ required: false
+ default: true
+requirements: ["neutronclient", "keystoneclient"]
+'''
+
+EXAMPLES = '''
+# Creates a router for tenant admin
+- neutron_router: state=present
+ login_username=admin
+ login_password=admin
+ login_tenant_name=admin
+ name=router1"
+'''
+
+_os_keystone = None
+_os_tenant_id = None
+
+def _get_ksclient(module, kwargs):
+ try:
+ kclient = ksclient.Client(username=kwargs.get('login_username'),
+ password=kwargs.get('login_password'),
+ tenant_name=kwargs.get('login_tenant_name'),
+ auth_url=kwargs.get('auth_url'))
+ except Exception as e:
+ module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message)
+ global _os_keystone
+ _os_keystone = kclient
+ return kclient
+
+
+def _get_endpoint(module, ksclient):
+ try:
+ endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
+ except Exception as e:
+ module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message)
+ return endpoint
+
+def _get_neutron_client(module, kwargs):
+ _ksclient = _get_ksclient(module, kwargs)
+ token = _ksclient.auth_token
+ endpoint = _get_endpoint(module, _ksclient)
+ kwargs = {
+ 'token': token,
+ 'endpoint_url': endpoint
+ }
+ try:
+ neutron = client.Client('2.0', **kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in connecting to Neutron: %s " % e.message)
+ return neutron
+
+def _set_tenant_id(module):
+ global _os_tenant_id
+ if not module.params['tenant_name']:
+ login_tenant_name = module.params['login_tenant_name']
+ else:
+ login_tenant_name = module.params['tenant_name']
+
+ for tenant in _os_keystone.tenants.list():
+ if tenant.name == login_tenant_name:
+ _os_tenant_id = tenant.id
+ break
+ if not _os_tenant_id:
+ module.fail_json(msg = "The tenant id cannot be found, please check the paramters")
+
+
+def _get_router_id(module, neutron):
+ kwargs = {
+ 'name': module.params['name'],
+ 'tenant_id': _os_tenant_id,
+ }
+ try:
+ routers = neutron.list_routers(**kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in getting the router list: %s " % e.message)
+ if not routers['routers']:
+ return None
+ return routers['routers'][0]['id']
+
+def _create_router(module, neutron):
+ router = {
+ 'name': module.params['name'],
+ 'tenant_id': _os_tenant_id,
+ 'admin_state_up': module.params['admin_state_up'],
+ }
+ try:
+ new_router = neutron.create_router(dict(router=router))
+ except Exception as e:
+ module.fail_json( msg = "Error in creating router: %s" % e.message)
+ return new_router['router']['id']
+
+def _delete_router(module, neutron, router_id):
+ try:
+ neutron.delete_router(router_id)
+ except:
+ module.fail_json("Error in deleting the router")
+ return True
+
+def main():
+ module = AnsibleModule(
+ argument_spec = dict(
+ login_username = dict(default='admin'),
+ login_password = dict(required=True),
+ login_tenant_name = dict(required='True'),
+ auth_url = dict(default='http://127.0.0.1:35357/v2.0/'),
+ region_name = dict(default=None),
+ name = dict(required=True),
+ tenant_name = dict(default=None),
+ state = dict(default='present', choices=['absent', 'present']),
+ admin_state_up = dict(type='bool', default=True),
+ ),
+ )
+
+ neutron = _get_neutron_client(module, module.params)
+ _set_tenant_id(module)
+
+ if module.params['state'] == 'present':
+ router_id = _get_router_id(module, neutron)
+ if not router_id:
+ router_id = _create_router(module, neutron)
+ module.exit_json(changed=True, result="Created", id=router_id)
+ else:
+ module.exit_json(changed=False, result="success" , id=router_id)
+
+ else:
+ router_id = _get_router_id(module, neutron)
+ if not router_id:
+ module.exit_json(changed=False, result="success")
+ else:
+ _delete_router(module, neutron, router_id)
+ module.exit_json(changed=True, result="deleted")
+
+# this is magic, see lib/ansible/module.params['common.py
+from ansible.module_utils.basic import *
+main()
diff --git a/openstack/usr/share/openstack/modules/neutron_router_gateway b/openstack/usr/share/openstack/modules/neutron_router_gateway
new file mode 100644
index 00000000..93235b85
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/neutron_router_gateway
@@ -0,0 +1,215 @@
+#!/usr/bin/python
+#coding: utf-8 -*-
+
+# (c) 2013, Benno Joy <benno@ansibleworks.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/>.
+
+try:
+ from neutronclient.neutron import client
+ from keystoneclient.v2_0 import client as ksclient
+except ImportError:
+ print("failed=True msg='neutronclient and keystone client are required'")
+DOCUMENTATION = '''
+---
+module: neutron_router_gateway
+version_added: "1.2"
+short_description: set/unset a gateway interface for the router with the specified external network
+description:
+ - Creates/Removes a gateway interface from the router, used to associate a external network with a router to route external traffic.
+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
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices: ['present', 'absent']
+ default: present
+ router_name:
+ description:
+ - Name of the router to which the gateway should be attached.
+ required: true
+ default: None
+ network_name:
+ description:
+ - Name of the external network which should be attached to the router.
+ required: true
+ default: None
+requirements: ["neutronclient", "keystoneclient"]
+'''
+
+EXAMPLES = '''
+# Attach an external network with a router to allow flow of external traffic
+- neutron_router_gateway: state=present login_username=admin login_password=admin
+ login_tenant_name=admin router_name=external_router
+ network_name=external_network
+'''
+
+_os_keystone = None
+def _get_ksclient(module, kwargs):
+ try:
+ kclient = ksclient.Client(username=kwargs.get('login_username'),
+ password=kwargs.get('login_password'),
+ tenant_name=kwargs.get('login_tenant_name'),
+ auth_url=kwargs.get('auth_url'))
+ except Exception as e:
+ module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message)
+ global _os_keystone
+ _os_keystone = kclient
+ return kclient
+
+
+def _get_endpoint(module, ksclient):
+ try:
+ endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
+ except Exception as e:
+ module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message)
+ return endpoint
+
+def _get_neutron_client(module, kwargs):
+ _ksclient = _get_ksclient(module, kwargs)
+ token = _ksclient.auth_token
+ endpoint = _get_endpoint(module, _ksclient)
+ kwargs = {
+ 'token': token,
+ 'endpoint_url': endpoint
+ }
+ try:
+ neutron = client.Client('2.0', **kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in connecting to neutron: %s " % e.message)
+ return neutron
+
+def _get_router_id(module, neutron):
+ kwargs = {
+ 'name': module.params['router_name'],
+ }
+ try:
+ routers = neutron.list_routers(**kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in getting the router list: %s " % e.message)
+ if not routers['routers']:
+ return None
+ return routers['routers'][0]['id']
+
+def _get_net_id(neutron, module):
+ kwargs = {
+ 'name': module.params['network_name'],
+ 'router:external': True
+ }
+ try:
+ networks = neutron.list_networks(**kwargs)
+ except Exception as e:
+ module.fail_json("Error in listing neutron networks: %s" % e.message)
+ if not networks['networks']:
+ return None
+ return networks['networks'][0]['id']
+
+def _get_port_id(neutron, module, router_id, network_id):
+ kwargs = {
+ 'device_id': router_id,
+ 'network_id': network_id,
+ }
+ try:
+ ports = neutron.list_ports(**kwargs)
+ except Exception as e:
+ module.fail_json( msg = "Error in listing ports: %s" % e.message)
+ if not ports['ports']:
+ return None
+ return ports['ports'][0]['id']
+
+def _add_gateway_router(neutron, module, router_id, network_id):
+ kwargs = {
+ 'network_id': network_id
+ }
+ try:
+ neutron.add_gateway_router(router_id, kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in adding gateway to router: %s" % e.message)
+ return True
+
+def _remove_gateway_router(neutron, module, router_id):
+ try:
+ neutron.remove_gateway_router(router_id)
+ except Exception as e:
+ module.fail_json(msg = "Error in removing gateway to router: %s" % e.message)
+ return True
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec = dict(
+ login_username = dict(default='admin'),
+ login_password = dict(required=True),
+ login_tenant_name = dict(required='True'),
+ auth_url = dict(default='http://127.0.0.1:35357/v2.0/'),
+ region_name = dict(default=None),
+ router_name = dict(required=True),
+ network_name = dict(required=True),
+ state = dict(default='present', choices=['absent', 'present']),
+ ),
+ )
+
+ neutron = _get_neutron_client(module, module.params)
+ router_id = _get_router_id(module, neutron)
+
+ if not router_id:
+ module.fail_json(msg="failed to get the router id, please check the router name")
+
+ network_id = _get_net_id(neutron, module)
+ if not network_id:
+ module.fail_json(msg="failed to get the network id, please check the network name and make sure it is external")
+
+ if module.params['state'] == 'present':
+ port_id = _get_port_id(neutron, module, router_id, network_id)
+ if not port_id:
+ _add_gateway_router(neutron, module, router_id, network_id)
+ module.exit_json(changed=True, result="created")
+ module.exit_json(changed=False, result="success")
+
+ if module.params['state'] == 'absent':
+ port_id = _get_port_id(neutron, module, router_id, network_id)
+ if not port_id:
+ module.exit_json(changed=False, result="Success")
+ _remove_gateway_router(neutron, module, router_id)
+ module.exit_json(changed=True, result="Deleted")
+
+# this is magic, see lib/ansible/module.params['common.py
+from ansible.module_utils.basic import *
+main()
+
+
diff --git a/openstack/usr/share/openstack/modules/neutron_router_interface b/openstack/usr/share/openstack/modules/neutron_router_interface
new file mode 100644
index 00000000..8d57725c
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/neutron_router_interface
@@ -0,0 +1,249 @@
+#!/usr/bin/python
+#coding: utf-8 -*-
+
+# (c) 2013, Benno Joy <benno@ansibleworks.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/>.
+
+try:
+ from neutronclient.neutron import client
+ from keystoneclient.v2_0 import client as ksclient
+except ImportError:
+ print("failed=True msg='neutronclient and keystone client are required'")
+DOCUMENTATION = '''
+---
+module: neutron_router_interface
+version_added: "1.2"
+short_description: Attach/Dettach a subnet's interface to a router
+description:
+ - Attach/Dettach a subnet interface to a router, to provide a gateway for the subnet.
+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
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices: ['present', 'absent']
+ default: present
+ router_name:
+ description:
+ - Name of the router to which the subnet's interface should be attached.
+ required: true
+ default: None
+ subnet_name:
+ description:
+ - Name of the subnet to whose interface should be attached to the router.
+ required: true
+ default: None
+ tenant_name:
+ description:
+ - Name of the tenant whose subnet has to be attached.
+ required: false
+ default: None
+requirements: ["neutronclient", "keystoneclient"]
+'''
+
+EXAMPLES = '''
+# Attach tenant1's subnet to the external router
+- neutron_router_interface: state=present login_username=admin
+ login_password=admin
+ login_tenant_name=admin
+ tenant_name=tenant1
+ router_name=external_route
+ subnet_name=t1subnet
+'''
+
+
+_os_keystone = None
+_os_tenant_id = None
+
+def _get_ksclient(module, kwargs):
+ try:
+ kclient = ksclient.Client(username=kwargs.get('login_username'),
+ password=kwargs.get('login_password'),
+ tenant_name=kwargs.get('login_tenant_name'),
+ auth_url=kwargs.get('auth_url'))
+ except Exception as e:
+ module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message)
+ global _os_keystone
+ _os_keystone = kclient
+ return kclient
+
+
+def _get_endpoint(module, ksclient):
+ try:
+ endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
+ except Exception as e:
+ module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message)
+ return endpoint
+
+def _get_neutron_client(module, kwargs):
+ _ksclient = _get_ksclient(module, kwargs)
+ token = _ksclient.auth_token
+ endpoint = _get_endpoint(module, _ksclient)
+ kwargs = {
+ 'token': token,
+ 'endpoint_url': endpoint
+ }
+ try:
+ neutron = client.Client('2.0', **kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in connecting to neutron: %s " % e.message)
+ return neutron
+
+def _set_tenant_id(module):
+ global _os_tenant_id
+ if not module.params['tenant_name']:
+ login_tenant_name = module.params['login_tenant_name']
+ else:
+ login_tenant_name = module.params['tenant_name']
+
+ for tenant in _os_keystone.tenants.list():
+ if tenant.name == login_tenant_name:
+ _os_tenant_id = tenant.id
+ break
+ if not _os_tenant_id:
+ module.fail_json(msg = "The tenant id cannot be found, please check the paramters")
+
+
+def _get_router_id(module, neutron):
+ kwargs = {
+ 'name': module.params['router_name'],
+ }
+ try:
+ routers = neutron.list_routers(**kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in getting the router list: %s " % e.message)
+ if not routers['routers']:
+ return None
+ return routers['routers'][0]['id']
+
+
+def _get_subnet_id(module, neutron):
+ subnet_id = None
+ kwargs = {
+ 'tenant_id': _os_tenant_id,
+ 'name': module.params['subnet_name'],
+ }
+ try:
+ subnets = neutron.list_subnets(**kwargs)
+ except Exception as e:
+ module.fail_json( msg = " Error in getting the subnet list:%s " % e.message)
+ if not subnets['subnets']:
+ return None
+ return subnets['subnets'][0]['id']
+
+def _get_port_id(neutron, module, router_id, subnet_id):
+ kwargs = {
+ 'tenant_id': _os_tenant_id,
+ 'device_id': router_id,
+ }
+ try:
+ ports = neutron.list_ports(**kwargs)
+ except Exception as e:
+ module.fail_json( msg = "Error in listing ports: %s" % e.message)
+ if not ports['ports']:
+ return None
+ for port in ports['ports']:
+ for subnet in port['fixed_ips']:
+ if subnet['subnet_id'] == subnet_id:
+ return port['id']
+ return None
+
+def _add_interface_router(neutron, module, router_id, subnet_id):
+ kwargs = {
+ 'subnet_id': subnet_id
+ }
+ try:
+ neutron.add_interface_router(router_id, kwargs)
+ except Exception as e:
+ module.fail_json(msg = "Error in adding interface to router: %s" % e.message)
+ return True
+
+def _remove_interface_router(neutron, module, router_id, subnet_id):
+ kwargs = {
+ 'subnet_id': subnet_id
+ }
+ try:
+ neutron.remove_interface_router(router_id, kwargs)
+ except Exception as e:
+ module.fail_json(msg="Error in removing interface from router: %s" % e.message)
+ return True
+
+def main():
+ module = AnsibleModule(
+ argument_spec = dict(
+ login_username = dict(default='admin'),
+ login_password = dict(required=True),
+ login_tenant_name = dict(required='True'),
+ auth_url = dict(default='http://127.0.0.1:35357/v2.0/'),
+ region_name = dict(default=None),
+ router_name = dict(required=True),
+ subnet_name = dict(required=True),
+ tenant_name = dict(default=None),
+ state = dict(default='present', choices=['absent', 'present']),
+ ),
+ )
+
+ neutron = _get_neutron_client(module, module.params)
+ _set_tenant_id(module)
+
+ router_id = _get_router_id(module, neutron)
+ if not router_id:
+ module.fail_json(msg="failed to get the router id, please check the router name")
+
+ subnet_id = _get_subnet_id(module, neutron)
+ if not subnet_id:
+ module.fail_json(msg="failed to get the subnet id, please check the subnet name")
+
+ if module.params['state'] == 'present':
+ port_id = _get_port_id(neutron, module, router_id, subnet_id)
+ if not port_id:
+ _add_interface_router(neutron, module, router_id, subnet_id)
+ module.exit_json(changed=True, result="created", id=port_id)
+ module.exit_json(changed=False, result="success", id=port_id)
+
+ if module.params['state'] == 'absent':
+ port_id = _get_port_id(neutron, module, router_id, subnet_id)
+ if not port_id:
+ module.exit_json(changed = False, result = "Success")
+ _remove_interface_router(neutron, module, router_id, subnet_id)
+ module.exit_json(changed=True, result="Deleted")
+
+# this is magic, see lib/ansible/module.params['common.py
+from ansible.module_utils.basic import *
+main()
diff --git a/openstack/usr/share/openstack/modules/neutron_sec_group b/openstack/usr/share/openstack/modules/neutron_sec_group
new file mode 100644
index 00000000..518c50e7
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/neutron_sec_group
@@ -0,0 +1,382 @@
+#!/usr/bin/python
+#
+# (c) Cisco Systems, 2014
+#
+# 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/>.
+
+DOCUMENTATION = '''
+---
+module: neutron_sec_group
+short_description: Create, Remove or Update Openstack security groups
+description:
+ - Create, Remove or Update Openstack security groups
+options:
+ login_username:
+ description:
+ - login username to authenticate to keystone
+ required: true
+ login_password:
+ description:
+ - Password of login user
+ required: true
+ login_tenant_name:
+ description:
+ - The tenant name of the login user
+ required: true
+ auth_url:
+ description:
+ - The keystone url for authentication
+ required: false
+ default: 'http://127.0.0.1:5000/v2.0/'
+ region_name:
+ description:
+ - Name of the region
+ required: false
+ default: None
+ state:
+ description:
+ - Indicate desired state of the security group
+ choices: ['present', 'absent']
+ default: present
+ name:
+ description:
+ - Name to be given to the security group
+ required: true
+ default: None
+ tenant_name:
+ description:
+ - Name of the tenant for which the security group has to be created,
+ if none, the security group would be created for the login tenant.
+ required: false
+ default: None
+ rules:
+ description:
+ - "List of security group rules. Available parameters of a rule:
+ direction, port_range_min, port_range_max, ethertype, protocol,
+ remote_ip_prefix/remote_ip_group"
+ required: false
+ default: none
+requirements: ["neutronclient", "keystoneclient"]
+'''
+
+EXAMPLES = '''
+# Creates a security group with a number of rules
+neutron_sec_group:
+ login_username: "demo"
+ login_password: "password"
+ login_tenant_name: "demo"
+ auth_url: "http://127.0.0.1:5000/v2.0"
+ name: "sg-test"
+ description: "Description of the security group"
+ state: "present"
+ rules:
+ - direction: "ingress"
+ port_range_min: "80"
+ port_range_max: "80"
+ ethertype: "IPv4"
+ protocol: "tcp"
+ remote_ip_prefix: "10.0.0.1/24"
+ - direction: "ingress"
+ port_range_min: "22"
+ port_range_max: "22"
+ ethertype: "IPv4"
+ protocol: "tcp"
+ remote_ip_prefix: "10.0.0.1/24"
+'''
+
+try:
+ import neutronclient.v2_0.client
+ import keystoneclient.v2_0.client
+ from neutronclient.common import exceptions
+except ImportError:
+ print "failed=True msg='neutronclient and keystoneclient are required'"
+
+def main():
+ """
+ Main function - entry point. The magic starts here ;-)
+ """
+ module = AnsibleModule(
+ argument_spec=dict(
+ auth_url=dict(default="http://127.0.0.1:5000/v2.0/"),
+ login_username=dict(required=True),
+ login_password=dict(required=True),
+ login_tenant_name=dict(required=True),
+ name=dict(required=True),
+ description=dict(default=None),
+ region_name=dict(default=None),
+ rules=dict(default=None),
+ tenant_name=dict(required=False),
+ state=dict(default="present", choices=['present', 'absent'])
+ ),
+ supports_check_mode=True
+ )
+ network_client = _get_network_client(module.params)
+ identity_client = _get_identity_client(module.params)
+
+ try:
+ # Get id of security group (as a result check whether it exists)
+ params = {
+ 'name': module.params['name'],
+ 'tenant_id': _get_tenant_id(module, identity_client),
+ 'fields': 'id'
+ }
+ sec_groups = network_client.list_security_groups(**params)["security_groups"]
+ if len(sec_groups) > 1:
+ raise exceptions.NeutronClientNoUniqueMatch(resource='security_group',name=name)
+ elif len(sec_groups) == 0:
+ sec_group_exists = False
+ else:
+ sec_group = sec_groups[0]
+ sec_group_exists = True
+
+ # state=present -> create or update depending on whether sg exists.
+ if module.params['state'] == "present":
+ # UPDATE
+ if sec_group_exists:
+ changed, sg = _update_sg(module, network_client, sec_group)
+ if changed:
+ module.exit_json(sec_group=sg, updated=True, changed=changed)
+ else:
+ module.exit_json(sec_group=sg, changed=changed)
+ # CREATE
+ else:
+ sg = _create_sg(module, network_client, identity_client)
+ module.exit_json(sec_group=sg, created=True, changed=True)
+ # DELETE
+ elif module.params['state'] == "absent" and sec_group_exists:
+ _delete_sg(module, network_client, sec_group)
+ module.exit_json(changed=True)
+
+ module.exit_json(changed=False)
+
+ except exceptions.Unauthorized as exc:
+ module.fail_json(msg="Authentication error: %s" % str(exc))
+ except Exception as exc:
+ module.fail_json(msg="Error: %s" % str(exc))
+
+def _delete_sg(module, network_client, sec_group):
+ """
+ Deletes a security group.
+ :param module: module to get security group params from.
+ :param network_client: network client to use.
+ :param sec_group: security group to delete.
+ """
+ if module.check_mode:
+ return
+ network_client.delete_security_group(sec_group['id'])
+
+
+def _create_sg(module, network_client, identity_client):
+ """
+ Creates a security group.
+ :param module: module to get security group params from.
+ :param network_client: network client to use.
+ :param: identity_client: identity_client used if an admin performs the
+ operation for a different tenant.
+ :return: newly created security group.
+ """
+ if module.check_mode:
+ return None
+ # NOTE: we don't do explicit rule validation, the API server will take
+ # care of that for us :-)
+ rules = module.params['rules']
+
+ data = {
+ "security_group": {
+ "name": module.params['name'],
+ "description": module.params['description'],
+ 'tenant_id': _get_tenant_id(module, identity_client)
+ }
+ }
+
+ sg = network_client.create_security_group(data)
+ sg = sg["security_group"]
+
+ changed, sg = _update_sg(module, network_client, sg)
+ return sg
+
+
+def _update_sg(module, network_client, sg):
+ """
+ Updates a security group.
+ :param module: module to get updated security group param from.
+ :param network_client: network client to use.
+ :param sg: security group that needs to be updated.
+ :return: True/False, the updated security group.
+ """
+ changed = False
+ sg = network_client.show_security_group(sg['id'])
+ sg = sg['security_group']
+
+ # We only allow description updating, no name updating
+ if module.params["description"] \
+ and not module.params['description'] == sg['description'] \
+ and module.check_mode:
+
+ changed = True
+ elif module.params["description"] \
+ and not module.params['description'] == sg['description'] \
+ and not module.check_mode:
+ body = {
+ "security_group": {
+ "description": module.params["description"]
+ }
+ }
+ sg = network_client.update_security_group(sg['id'], body)
+ sg = sg['security_group']
+ changed = True
+
+ # Security rules group update
+ existing_rules = sg['security_group_rules']
+ wanted_rules = module.params['rules']
+
+ #check ok
+ ok_rules = []
+ for new_rule in wanted_rules:
+ # Ugly: define tenant also here so that matches
+ new_rule['tenant_id'] = sg['tenant_id']
+ # protocol is in lowercase
+ if 'protocol' in new_rule:
+ new_rule['protocol'] = new_rule['protocol'].lower()
+
+ matched_id = None
+ for old_rule in existing_rules:
+ clean_new_rule = new_rule.copy()
+ clean_old_rule = old_rule.copy()
+ old_id = clean_old_rule.pop('id')
+ clean_old_rule.pop('security_group_id')
+ for key in clean_old_rule.keys():
+ if key not in clean_new_rule:
+ clean_new_rule[key] = None
+ continue
+ value = clean_new_rule[key]
+ if isinstance(value, (str, unicode)) and value.isdigit():
+ clean_new_rule[key] = int(value)
+ if cmp(clean_old_rule, clean_new_rule) == 0:
+ matched_id = old_id
+ break
+
+ if matched_id:
+ new_rule['done'] = True
+ ok_rules.append(matched_id)
+
+ #apply new first
+ new_rules = [rule for rule in wanted_rules if 'done' not in rule]
+ if len(new_rules):
+ if not module.check_mode:
+ sg = _create_sg_rules(network_client, sg, new_rules)
+ changed = True
+
+ #then delete not ok
+ for rule in existing_rules:
+ if rule['id'] in ok_rules:
+ continue
+ if not module.check_mode:
+ sg = network_client.delete_security_group_rule(rule['id'])
+ changed = True
+
+ return changed, sg
+
+def _create_sg_rules(network_client, sg, rules):
+ """
+ Creates a set of security group rules in a given security group.
+ :param network_client: network client to use to create rules.
+ :param sg: security group to create rules in.
+ :param rules: rules to create.
+ :return: the updated security group.
+ """
+ if rules:
+ for rule in rules:
+ rule['tenant_id'] = sg['tenant_id']
+ rule['security_group_id'] = sg['id']
+ data = {
+ "security_group_rule": rule
+ }
+ network_client.create_security_group_rule(data)
+
+ # fetch security group again to show end result
+ return network_client.show_security_group(sg['id'])['security_group']
+ return sg
+
+
+def _get_tenant_id(module, identity_client):
+ """
+ Returns the tenant_id, given tenant_name.
+ if tenant_name is not specified in the module params uses tenant_id
+ from Keystone session
+ :param identity_client: identity_client used to get the tenant_id from its
+ name.
+ :param module_params: module parameters.
+ """
+ if not module.params['tenant_name']:
+ tenant_id = identity_client.tenant_id
+ else:
+ tenant_name = module.params['tenant_name']
+ tenant = _get_tenant(identity_client, tenant_name)
+ tenant_id = tenant.id
+
+ return tenant_id
+
+
+def _get_tenant(identity_client, tenant_name):
+ """
+ Returns the tenant, given the tenant_name.
+ :param identity_client: identity client to use to do the required requests.
+ :param tenant_name: name of the tenant.
+ :return: tenant for which the name was given.
+ """
+ tenants = identity_client.tenants.list()
+ tenant = next((t for t in tenants if t.name == tenant_name), None)
+ if not tenant:
+ raise Exception("Tenant with name '%s' not found." % tenant_name)
+
+ return tenant
+
+
+def _get_network_client(module_params):
+ """
+ :param module_params: module params containing the openstack credentials
+ used to authenticate.
+ :return: a neutron client.
+ """
+ client = neutronclient.v2_0.client.Client(
+ username=module_params.get('login_username'),
+ password=module_params.get('login_password'),
+ tenant_name=module_params.get('login_tenant_name'),
+ auth_url=module_params.get('auth_url'),
+ region_name=module_params.get('region_name'))
+
+ return client
+
+
+def _get_identity_client(module_params):
+ """
+ :param module_params: module params containing the openstack credentials
+ used to authenticate.
+ :return: a keystone client.
+ """
+ client = keystoneclient.v2_0.client.Client(
+ username=module_params.get('login_username'),
+ password=module_params.get('login_password'),
+ tenant_name=module_params.get('login_tenant_name'),
+ auth_url=module_params.get('auth_url'),
+ region_name=module_params.get('region_name'))
+
+ return client
+
+
+# Let's get the party started!
+from ansible.module_utils.basic import *
+
+main()
diff --git a/openstack/usr/share/openstack/modules/neutron_subnet b/openstack/usr/share/openstack/modules/neutron_subnet
new file mode 100644
index 00000000..8cb622b5
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/neutron_subnet
@@ -0,0 +1,294 @@
+#!/usr/bin/python
+#coding: utf-8 -*-
+
+# (c) 2013, Benno Joy <benno@ansibleworks.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/>.
+
+try:
+ from neutronclient.neutron import client
+ from keystoneclient.v2_0 import client as ksclient
+except ImportError:
+ print("failed=True msg='neutron and keystone client are required'")
+
+DOCUMENTATION = '''
+---
+module: neutron_subnet
+version_added: "1.2"
+short_description: Add/Remove floating IP from an instance
+description:
+ - Add or Remove a floating IP to an instance
+options:
+ login_username:
+ description:
+ - login username to authenticate to keystone
+ required: true
+ default: admin
+ login_password:
+ description:
+ - Password of login user
+ required: true
+ default: True
+ login_tenant_name:
+ description:
+ - The tenant name of the login user
+ required: true
+ default: 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
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices: ['present', 'absent']
+ default: present
+ network_name:
+ description:
+ - Name of the network to which the subnet should be attached
+ required: true
+ default: None
+ cidr:
+ description:
+ - The CIDR representation of the subnet that should be assigned to the subnet
+ required: true
+ default: None
+ tenant_name:
+ description:
+ - The name of the tenant for whom the subnet should be created
+ required: false
+ default: None
+ ip_version:
+ description:
+ - The IP version of the subnet 4 or 6
+ required: false
+ default: 4
+ enable_dhcp:
+ description:
+ - Whether DHCP should be enabled for this subnet.
+ required: false
+ default: true
+ gateway_ip:
+ description:
+ - The ip that would be assigned to the gateway for this subnet
+ required: false
+ default: None
+ dns_nameservers:
+ description:
+ - DNS nameservers for this subnet, comma-separated
+ required: false
+ default: None
+ allocation_pool_start:
+ description:
+ - From the subnet pool the starting address from which the IP should be allocated
+ required: false
+ default: None
+ allocation_pool_end:
+ description:
+ - From the subnet pool the last IP that should be assigned to the virtual machines
+ required: false
+ default: None
+ host_routes:
+ description:
+ - Host routes for this subnet, list of dictionaries, e.g. [{destination: 0.0.0.0/0, nexthop: 123.456.78.9}, {destination: 192.168.0.0/24, nexthop: 192.168.0.1}]
+ required: false
+ default: None
+requirements: ["neutron", "keystoneclient"]
+'''
+
+EXAMPLES = '''
+# Create a subnet for a tenant with the specified subnet
+- neutron_subnet: state=present login_username=admin login_password=admin
+ login_tenant_name=admin tenant_name=tenant1
+ network_name=network1 name=net1subnet cidr=192.168.0.0/24"
+'''
+
+_os_keystone = None
+_os_tenant_id = None
+_os_network_id = None
+
+def _get_ksclient(module, kwargs):
+ try:
+ kclient = ksclient.Client(username=kwargs.get('login_username'),
+ password=kwargs.get('login_password'),
+ tenant_name=kwargs.get('login_tenant_name'),
+ auth_url=kwargs.get('auth_url'))
+ except Exception as e:
+ module.fail_json(msg = "Error authenticating to the keystone: %s" %e.message)
+ global _os_keystone
+ _os_keystone = kclient
+ return kclient
+
+
+def _get_endpoint(module, ksclient):
+ try:
+ endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
+ except Exception as e:
+ module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message)
+ return endpoint
+
+def _get_neutron_client(module, kwargs):
+ _ksclient = _get_ksclient(module, kwargs)
+ token = _ksclient.auth_token
+ endpoint = _get_endpoint(module, _ksclient)
+ kwargs = {
+ 'token': token,
+ 'endpoint_url': endpoint
+ }
+ try:
+ neutron = client.Client('2.0', **kwargs)
+ except Exception as e:
+ module.fail_json(msg = " Error in connecting to Neutron: %s" % e.message)
+ return neutron
+
+def _set_tenant_id(module):
+ global _os_tenant_id
+ if not module.params['tenant_name']:
+ tenant_name = module.params['login_tenant_name']
+ else:
+ tenant_name = module.params['tenant_name']
+
+ for tenant in _os_keystone.tenants.list():
+ if tenant.name == tenant_name:
+ _os_tenant_id = tenant.id
+ break
+ if not _os_tenant_id:
+ module.fail_json(msg = "The tenant id cannot be found, please check the paramters")
+
+def _get_net_id(neutron, module):
+ kwargs = {
+ 'tenant_id': _os_tenant_id,
+ 'name': module.params['network_name'],
+ }
+ try:
+ networks = neutron.list_networks(**kwargs)
+ except Exception as e:
+ module.fail_json("Error in listing Neutron networks: %s" % e.message)
+ if not networks['networks']:
+ return None
+ return networks['networks'][0]['id']
+
+
+def _get_subnet_id(module, neutron):
+ global _os_network_id
+ subnet_id = None
+ _os_network_id = _get_net_id(neutron, module)
+ if not _os_network_id:
+ module.fail_json(msg = "network id of network not found.")
+ else:
+ kwargs = {
+ 'tenant_id': _os_tenant_id,
+ 'name': module.params['name'],
+ }
+ try:
+ subnets = neutron.list_subnets(**kwargs)
+ except Exception as e:
+ module.fail_json( msg = " Error in getting the subnet list:%s " % e.message)
+ if not subnets['subnets']:
+ return None
+ return subnets['subnets'][0]['id']
+
+def _create_subnet(module, neutron):
+ neutron.format = 'json'
+ subnet = {
+ 'name': module.params['name'],
+ 'ip_version': module.params['ip_version'],
+ 'enable_dhcp': module.params['enable_dhcp'],
+ 'tenant_id': _os_tenant_id,
+ 'gateway_ip': module.params['gateway_ip'],
+ 'dns_nameservers': module.params['dns_nameservers'],
+ 'network_id': _os_network_id,
+ 'cidr': module.params['cidr'],
+ 'host_routes': module.params['host_routes'],
+ }
+ if module.params['allocation_pool_start'] and module.params['allocation_pool_end']:
+ allocation_pools = [
+ {
+ 'start' : module.params['allocation_pool_start'],
+ 'end' : module.params['allocation_pool_end']
+ }
+ ]
+ subnet.update({'allocation_pools': allocation_pools})
+ if not module.params['gateway_ip']:
+ subnet.pop('gateway_ip')
+ if module.params['dns_nameservers']:
+ subnet['dns_nameservers'] = module.params['dns_nameservers'].split(',')
+ else:
+ subnet.pop('dns_nameservers')
+ if not module.params['host_routes']:
+ subnet.pop('host_routes')
+ try:
+ new_subnet = neutron.create_subnet(dict(subnet=subnet))
+ except Exception, e:
+ module.fail_json(msg = "Failure in creating subnet: %s" % e.message)
+ return new_subnet['subnet']['id']
+
+
+def _delete_subnet(module, neutron, subnet_id):
+ try:
+ neutron.delete_subnet(subnet_id)
+ except Exception as e:
+ module.fail_json( msg = "Error in deleting subnet: %s" % e.message)
+ return True
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec = dict(
+ login_username = dict(default='admin'),
+ login_password = dict(required=True),
+ login_tenant_name = dict(required='True'),
+ auth_url = dict(default='http://127.0.0.1:35357/v2.0/'),
+ region_name = dict(default=None),
+ name = dict(required=True),
+ network_name = dict(required=True),
+ cidr = dict(required=True),
+ tenant_name = dict(default=None),
+ state = dict(default='present', choices=['absent', 'present']),
+ ip_version = dict(default='4', choices=['4', '6']),
+ enable_dhcp = dict(default='true', choices=BOOLEANS),
+ gateway_ip = dict(default=None),
+ dns_nameservers = dict(default=None),
+ allocation_pool_start = dict(default=None),
+ allocation_pool_end = dict(default=None),
+ host_routes = dict(default=None),
+ ),
+ )
+ neutron = _get_neutron_client(module, module.params)
+ _set_tenant_id(module)
+ if module.params['state'] == 'present':
+ subnet_id = _get_subnet_id(module, neutron)
+ if not subnet_id:
+ subnet_id = _create_subnet(module, neutron)
+ module.exit_json(changed = True, result = "Created" , id = subnet_id)
+ else:
+ module.exit_json(changed = False, result = "success" , id = subnet_id)
+ else:
+ subnet_id = _get_subnet_id(module, neutron)
+ if not subnet_id:
+ module.exit_json(changed = False, result = "success")
+ else:
+ _delete_subnet(module, neutron, subnet_id)
+ module.exit_json(changed = True, result = "deleted")
+
+# this is magic, see lib/ansible/module.params['common.py
+from ansible.module_utils.basic import *
+main()
diff --git a/openstack/usr/share/openstack/modules/nova_flavor b/openstack/usr/share/openstack/modules/nova_flavor
new file mode 100644
index 00000000..b71d648f
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/nova_flavor
@@ -0,0 +1,188 @@
+#!/usr/bin/python
+#coding: utf-8 -*-
+
+# (c) 2014, Adam Samalik <asamalik@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/>.
+
+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_flavor
+short_description: Manage OpenStack flavours
+description:
+ - Create VM flavours 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 flavor
+ required: True
+ ram:
+ description:
+ - Memory in MB for the flavor
+ required: True
+ vcpus:
+ description:
+ - Number of VCPUs for the flavor
+ required: True
+ disk:
+ description:
+ - Size of local disk in GB
+ required: True
+ swap:
+ description:
+ - Swap space in MB
+ required: False
+ default: 0
+ id:
+ description:
+ - ID for the flavor (optional).
+ required: False
+ default: ID will be automatically generated
+ is_public:
+ description:
+ - Decide if the flavour is public
+'''
+
+EXAMPLES = '''
+ - nova_flavor:
+ login_username: admin
+ login_password: 1234
+ login_tenant_name: admin
+ name: medium
+ ram: 2048
+ vcpus: 2
+ disk: 20
+'''
+
+
+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_flavors(nova, name, id=None):
+ if not id:
+ flavors = [x for x in nova.flavors.list() if x.name == name]
+ else:
+ 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):
+ flavors = get_flavors(nova, name, id)
+ if len(flavors) >0:
+ return False, flavors[0].id
+
+ try:
+ flavor = nova.flavors.create(name, ram, vcpus, disk, swap=swap,
+ flavorid=id, is_public=is_public)
+ except Exception as e:
+ module.fail_json(msg = "Could not create a flavour: {}".format(
+ e.message))
+
+ return True, flavor.id
+
+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),
+ ram = dict(required=True),
+ vcpus = dict(required=True),
+ disk = dict(required=True),
+ swap = dict(default=0),
+ id = dict(default=None),
+ is_public = dict(default=True, choices=BOOLEANS),
+ )
+ )
+ 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']
+ ram = module.params['ram']
+ vcpus = module.params['vcpus']
+ disk = module.params['disk']
+ swap = module.params['swap']
+ id = module.params['id']
+ 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)
+
+ module.exit_json(changed=changed, name=name, id=id)
+
+# this is magic, see lib/ansible/module_common.py
+#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
+if __name__ == '__main__':
+ main()
diff --git a/openstack/usr/share/openstack/modules/nova_manage b/openstack/usr/share/openstack/modules/nova_manage
new file mode 100644
index 00000000..6b1a297e
--- /dev/null
+++ b/openstack/usr/share/openstack/modules/nova_manage
@@ -0,0 +1,93 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+DOCUMENTATION = '''
+---
+module: nova_manage
+short_description: Initialize OpenStack Compute (nova) database
+description: Create the tables for the database backend used by nova
+options:
+ action:
+ description:
+ - action to perform. Currently only dbsync is supported
+ required: true
+requirements: [ nova ]
+'''
+
+EXAMPLES = '''
+nova_manage: action=dbsync
+'''
+
+import subprocess
+
+try:
+ from nova.db.sqlalchemy import migration
+ from nova import config
+except ImportError:
+ nova_found = False
+else:
+ nova_found = True
+
+
+def load_config_file():
+ config.parse_args([])
+
+
+def will_db_change():
+ """ Check if the database version will change after the sync.
+
+ """
+ # Load the config file options
+ current_version = migration.db_version()
+ repository = migration._find_migrate_repo()
+ repo_version = repository.latest
+ return current_version != repo_version
+
+
+def do_dbsync():
+ """Do the dbsync. Returns (returncode, stdout, stderr)"""
+ # We call nova-manage db_sync on the shell rather than trying to
+ # do this in Python since we have no guarantees about changes to the
+ # internals.
+ args = ['nova-manage', 'db', 'sync']
+
+ call = subprocess.Popen(args, shell=False,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = call.communicate()
+ return (call.returncode, out, err)
+
+
+def main():
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ action=dict(required=True),
+ ),
+ supports_check_mode=True
+ )
+
+ if not nova_found:
+ module.fail_json(msg="nova package could not be found")
+
+ action = module.params['action']
+
+ if action not in ['dbsync', 'db_sync']:
+ module.fail_json(msg="Only supported action is 'dbsync'")
+
+ load_config_file()
+
+ changed = will_db_change()
+ if module.check_mode:
+ module.exit_json(changed=changed)
+
+ (res, stdout, stderr) = do_dbsync()
+
+ if res == 0:
+ module.exit_json(changed=changed, stdout=stdout, stderr=stderr)
+ else:
+ module.fail_json(msg="nova-manage returned non-zero value: %d" % res,
+ stdout=stdout, stderr=stderr)
+
+# this is magic, see lib/ansible/module_common.py
+#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
+main()
diff --git a/openstack/usr/share/openstack/openstack-keystone-setup b/openstack/usr/share/openstack/openstack-keystone-setup
deleted file mode 100644
index 9c034c5b..00000000
--- a/openstack/usr/share/openstack/openstack-keystone-setup
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2014 Codethink Limited
-#
-# This program 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; version 2 of the License.
-#
-# This program 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 program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-set -e
-
-# Create required system users and groups
-
-getent group keystone >/dev/null || groupadd -r --gid 163 keystone
-getent passwd keystone >/dev/null || \
- useradd --uid 163 -r -g keystone -d /var/lib/keystone -s /sbin/nologin \
- -c "OpenStack Keystone Daemons" keystone
-
-# Keystone compute configuration
-if [ ! -d /var/run/keystone ]; then
- mkdir -p /var/run/keystone
- chown -R keystone:keystone /var/run/keystone
-fi
-
-if [ ! -d /var/lock/keystone ]; then
- mkdir -p /var/lock/keystone
- chown -R keystone:keystone /var/lock/keystone
-fi
-
-if [ ! -d /var/log/keystone ]; then
- mkdir -p /var/log/keystone
- chown -R keystone:keystone /var/log/keystone
-fi
-
-# Setup the keystone database
-if ! sudo -u postgres psql -lqt | grep -q keystone; then
- # Create posgreSQL user
- sudo -u postgres createuser \
- --pwprompt --encrypted \
- --no-adduser --no-createdb \
- --no-password \
- ##KEYSTONE_DB_USER##
-
- sudo -u postgres createdb \
- --owner=##KEYSTONE_DB_USER## \
- keystone
-
- sudo -u keystone keystone-manage db_sync
-fi
-
-chown -R keystone:keystone /var/lib/keystone
-
-systemctl start openstack-keystone
-
-export OS_SERVICE_TOKEN=##KEYSTONE_TEMPORARY_ADMIN_TOKEN##
-export OS_SERVICE_ENDPOINT='http://onenode:35357/v2.0'
-
-# This script creates a TEMPORARY admin user, with a password that may
-# float arount on the system. Please delete this user once you have set up
-# the real admin user with a real secure password.
-
-keystone tenant-create --name admin --description "Admin Tenant"
-keystone role-create --name admin
-
-keystone user-create --name temporary_admin --pass ##KEYSTONE_TEMPORARY_ADMIN_PASSWORD##
-keystone user-role-add --tenant admin --user temporary_admin --role admin
-
-keystone tenant-create --name service --description "Service Tenant"
-
-# Define a service for the Identity Service
-keystone service-create --name keystone --type identity --description "Openstack Identity"
-
-# Specify an API endpoint for the Identity Service by using the returned service ID.
-keystone endpoint-create --service-id $(keystone service-list | awk '/ identity / {print $2}') \
- --publicurl ##KEYSTONE_PUBLIC_URL## \
- --internalurl ##KEYSTONE_INTERNAL_URL## \
- --adminurl ##KEYSTONE_ADMIN_URL##
-
-rm /etc/systemd/system/multi-user.target.wants/openstack-keystone-setup.service
-
-ln -s "/etc/systemd/system/openstack-keystone.service" \
- "/etc/systemd/system/multi-user.target.wants/openstack-keystone.service"
-
-exit 0