diff --git a/doc/source/index.rst b/doc/source/index.rst index cfb56654..8fcee1a3 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -12,6 +12,7 @@ This project provides the Ansible modules: * keystone * memcached * name2int + * neutron * provider_networks Filters diff --git a/library/neutron b/library/neutron new file mode 100644 index 00000000..e41a6fa0 --- /dev/null +++ b/library/neutron @@ -0,0 +1,465 @@ +#!/usr/bin/env python +# Copyright 2014, Rackspace US, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import keystoneclient.v3.client as ksclient +from neutronclient.neutron import client as nclient + +# import module snippets +from ansible.module_utils.basic import * + + +DOCUMENTATION = """ +--- +module: neutron +short_description: + - Basic module for interacting with openstack neutron +description: + - Basic module for interacting with openstack neutron +options: + command: + description: + - Operation for the module to perform. Currently available + choices: + - create_network + - create_subnet + - create_router + - add_router_interface + - get_networks + required: True + openrc_path: + decription: + - Path to openrc file from which credentials and keystone endpoint + will be extracted + net_name: + description: + - Name of network + subnet_name: + description: + - Name of subnet + allocation_pools: + description: + - List of allocation pools for a subnet. Format is + startip-endip[,startip-endip]... + router_name: + description: + - Name of router + cidr: + description: + - Specify CIDR to use when creating subnet + provider_physical_network: + description: + - Specify provider:physical_network when creating network + provider_network_type: + description: + - Specify provider:network_type when creating network + provider_segmentation_id: + description: + - Specify provider:segmentation_id when creating network + router_external: + description: + - Specify router:external' when creating network + external_gateway_info: + description: + - Specify external_gateway_info when creating router + insecure: + description: + - Explicitly allow client to perform "insecure" TLS + choices: + - false + - true + default: false +author: Hugh Saunders +""" + +EXAMPLES = """ +- name: Create private network + neutron: + command: create_network + openrc_path: /root/openrc + net_name: private +- name: Create public network + neutron: + command: create_network + openrc_path: /root/openrc + net_name: public + provider_network_type: flat + provider_physical_network: vlan + router_external: true +- name: Create private subnet + neutron: + command: create_subnet + openrc_path: /root/openrc + net_name: private + subnet_name: private-subnet + cidr: "192.168.74.0/24" +- name: Create public subnet + neutron: + command: create_subnet + openrc_path: /root/openrc + net_name: public + subnet_name: public-subnet + cidr: "10.1.13.0/24" +- name: Create router + neutron: + command: create_router + openrc_path: /root/openrc + router_name: router + external_gateway_info: public +- name: Add private subnet to router + neutron: + command: add_router_interface + openrc_path: /root/openrc + router_name: router + subnet_name: private-subnet +- name: Get network information + command: get_network + openrc_path: /root/openrc + net_name: public +""" + + +COMMAND_MAP = { + 'create_network': { + 'variables': [ + 'net_name', + 'provider_physical_network', + 'provider_network_type', + 'provider_segmentation_id', + 'router_external', + 'tenant_id' + ] + }, + 'create_subnet': { + 'variables': [ + 'net_name', + 'subnet_name', + 'cidr', + 'tenant_id', + 'allocation_pools' + ] + }, + 'create_router': { + 'variables': [ + 'router_name', + 'external_gateway_info', + 'tenant_id' + ] + }, + 'add_router_interface': { + 'variables': [ + 'router_name', + 'subnet_name' + ] + }, + 'get_networks': { + 'variables': [ + 'net_name' + ] + } +} + + +class ManageNeutron(object): + def __init__(self, module): + self.state_change = False + self.neutron = None + self.keystone = None + self.module = module + + def command_router(self): + """Run the command as its provided to the module.""" + command_name = self.module.params['command'] + if command_name not in COMMAND_MAP: + self.failure( + error='No Command Found', + rc=2, + msg='Command [ %s ] was not found.' % command_name + ) + + action_command = COMMAND_MAP[command_name] + if hasattr(self, '_%s' % command_name): + action = getattr(self, '_%s' % command_name) + try: + self._keystone_authenticate() + self._init_neutron() + except Exception as e: + self.module.fail_json( + err="Initialisation Error: %s" % e, + rc=2, msg=str(e)) + facts = action(variables=action_command['variables']) + if facts is None: + self.module.exit_json(changed=self.state_change) + else: + self.module.exit_json( + changed=self.state_change, + ansible_facts=facts + ) + else: + self.failure( + error='Command not in ManageNeutron class', + rc=2, + msg='Method [ %s ] was not found.' % command_name + ) + + @staticmethod + def _facts(resource_type, resource_data): + """Return a dict for our Ansible facts.""" + key = 'neutron_%s' % resource_type + facts = {key: {}} + for f in resource_data[resource_type]: + res_name = f['name'] + del f['name'] + facts[key][res_name] = f + + return facts + + def _get_vars(self, variables, required=None): + """Return a dict of all variables as found within the module. + + :param variables: ``list`` List of all variables that are available to + use within the Neutron Command. + :param required: ``list`` Name of variables that are required. + """ + return_dict = {} + for variable in variables: + return_dict[variable] = self.module.params.get(variable) + else: + if isinstance(required, list): + for var_name in required: + check = return_dict.get(var_name) + if check is None: + self.failure( + error='Missing [ %s ] from Task or found a None' + ' value' % var_name, + rc=000, + msg='variables %s - available params [ %s ]' + % (variables, self.module.params) + ) + return return_dict + + def failure(self, error, rc, msg): + """Return a Failure when running an Ansible command. + + :param error: ``str`` Error that occurred. + :param rc: ``int`` Return code while executing an Ansible command. + :param msg: ``str`` Message to report. + """ + self.module.fail_json(msg=msg, rc=rc, err=error) + + def _parse_openrc(self): + """Get credentials from an openrc file.""" + openrc_path = self.module.params['openrc_path'] + line_re = re.compile('^export (?POS_\w*)=(?P[^\n]*)') + with open(openrc_path) as openrc: + matches = [line_re.match(l) for l in openrc] + return dict( + (g.groupdict()['key'], g.groupdict()['value']) + for g in matches if g + ) + + def _keystone_authenticate(self): + """Authenticate with Keystone.""" + openrc = self._parse_openrc() + insecure = self.module.params['insecure'] + self.keystone = ksclient.Client(insecure=insecure, + username=openrc['OS_USERNAME'], + password=openrc['OS_PASSWORD'], + project_name=openrc['OS_PROJECT_NAME'], + auth_url=openrc['OS_AUTH_URL']) + + def _init_neutron(self): + """Create neutron client object using token and url from keystone.""" + openrc = self._parse_openrc() + self.neutron = nclient.Client( + '2.0', + endpoint_url=self.keystone.service_catalog.url_for( + service_type='network', + endpoint_type=openrc['OS_ENDPOINT_TYPE']), + token=self.keystone.get_token(self.keystone.session)) + + def _get_resource_by_name(self, resource_type, resource_name): + action = getattr(self.neutron, 'list_%s' % resource_type) + resource = action(name=resource_name)[resource_type] + + if resource: + return resource[0]['id'] + else: + return None + + def _create_network(self, variables): + required_vars = ['net_name'] + variables_dict = self._get_vars(variables, required=required_vars) + net_name = variables_dict.pop('net_name') + provider_physical_network = variables_dict.pop( + 'provider_physical_network' + ) + provider_network_type = variables_dict.pop('provider_network_type') + provider_segmentation_id = variables_dict.pop( + 'provider_segmentation_id' + ) + router_external = variables_dict.pop('router_external') + tenant_id = variables_dict.pop('tenant_id') + + if not self._get_resource_by_name('networks', net_name): + n = {"name": net_name, "admin_state_up": True} + if provider_physical_network: + n['provider:physical_network'] = provider_physical_network + if provider_network_type: + n['provider:network_type'] = provider_network_type + if provider_segmentation_id: + n['provider:segmentation_id'] = str(provider_segmentation_id) + if router_external: + n['router:external'] = router_external + if tenant_id: + n['tenant_id'] = tenant_id + + self.state_change = True + self.neutron.create_network({"network": n}) + + return self._facts('networks', self.neutron.list_networks()) + + def _create_subnet(self, variables): + required_vars = ['net_name', 'subnet_name', 'cidr'] + variables_dict = self._get_vars(variables, required=required_vars) + net_name = variables_dict.pop('net_name') + subnet_name = variables_dict.pop('subnet_name') + cidr = variables_dict.pop('cidr') + network_id = self._get_resource_by_name('networks', net_name) + tenant_id = variables_dict.pop('tenant_id') + + # allocation_pools is a string with the format: + # startip-endip[,startip-endip]... + allocation_pools_str = variables_dict.get('allocation_pools', "") + allocation_pools = [] + if allocation_pools_str: + for ap in allocation_pools_str.split(','): + if "-" not in ap: + continue + start, end = ap.split('-') + allocation_pools.append({'start': start, 'end': end}) + + if not network_id: + self.failure( + error='Network not found', + rc=1, + msg='The specified network could not be found' + ) + if not self.neutron.list_subnets(cidr=cidr, + network_id=network_id)['subnets']: + self.state_change = True + s = {"name": subnet_name, "cidr": cidr, "ip_version": 4, + "network_id": network_id} + if tenant_id: + s["tenant_id"] = tenant_id + if allocation_pools: + s["allocation_pools"] = allocation_pools + + self.neutron.create_subnet({"subnet": s}) + return self._facts('subnets', self.neutron.list_subnets()) + + def _create_router(self, variables): + required_vars = ['router_name', 'external_gateway_info'] + variables_dict = self._get_vars(variables, required=required_vars) + router_name = variables_dict.pop('router_name') + external_gateway_info = variables_dict.pop('external_gateway_info') + tenant_id = variables_dict.pop('tenant_id') + + if not self._get_resource_by_name('routers', router_name): + self.state_change = True + r = {'name': router_name} + if external_gateway_info: + network_id = self._get_resource_by_name('networks', + external_gateway_info) + r['external_gateway_info'] = {'network_id': network_id} + if tenant_id: + r['tenant_id'] = tenant_id + self.neutron.create_router({'router': r}) + + return self._facts('routers', self.neutron.list_routers()) + + def _add_router_interface(self, variables): + required_vars = ['router_name', 'subnet_name'] + variables_dict = self._get_vars(variables, required=required_vars) + router_name = variables_dict.pop('router_name') + subnet_name = variables_dict.pop('subnet_name') + router_id = self._get_resource_by_name('routers', router_name) + subnet_id = self._get_resource_by_name('subnets', subnet_name) + + if not router_id: + self.failure( + error='Router not found', + rc=1, + msg='The specified router could not be found' + ) + + if not subnet_id: + self.failure( + error='Subnet not found', + rc=1, + msg='The specified subnet could not be found' + ) + + found = False + + for port in self.neutron.list_ports(device_id=router_id)['ports']: + for fixed_ips in port['fixed_ips']: + if fixed_ips['subnet_id'] == subnet_id: + found = True + if not found: + self.state_change = True + self.neutron.add_interface_router(router_id, + {'subnet_id': subnet_id}) + + def _get_networks(self, variables): + variables_dict = self._get_vars(variables) + net_name = variables_dict.pop('net_name') + if net_name: + network_id = self._get_resource_by_name('networks', net_name) + if not network_id: + self.failure( + error='Network not found', + rc=1, + msg='The specified network could not be found' + ) + + return self._facts('networks', self.neutron.list_networks()) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + command=dict(required=True, choices=COMMAND_MAP.keys()), + openrc_path=dict(required=True), + net_name=dict(required=False), + subnet_name=dict(required=False), + cidr=dict(required=False), + provider_physical_network=dict(required=False), + provider_network_type=dict(required=False), + provider_segmentation_id=dict(required=False), + router_external=dict(required=False), + router_name=dict(required=False), + external_gateway_info=dict(required=False), + tenant_id=dict(required=False), + insecure=dict(default=False, required=False, type='bool'), + allocation_pools=dict(required=False) + ), + supports_check_mode=False + ) + mn = ManageNeutron(module) + mn.command_router() + +if __name__ == '__main__': + main() diff --git a/releasenotes/notes/plugins-remove-neutron-3c87f2f8ddb420ae.yaml b/releasenotes/notes/plugins-remove-neutron-3c87f2f8ddb420ae.yaml deleted file mode 100644 index 63bc758f..00000000 --- a/releasenotes/notes/plugins-remove-neutron-3c87f2f8ddb420ae.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -upgrade: - - The ``neutron`` library has been removed from OpenStack-Ansible's - plugins. Upstream Ansible modules for managing OpenStack network - resources should be used instead.