diff options
Diffstat (limited to 'openstack/usr/share/openstack/modules/neutron_sec_group')
-rw-r--r-- | openstack/usr/share/openstack/modules/neutron_sec_group | 382 |
1 files changed, 382 insertions, 0 deletions
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() |