#!/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 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 """ 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' ] } } 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 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()