blob: 351b1d12ff971cd6a5a2d41ec66fa7b9317bcb0f [file] [log] [blame]
# -*- coding: utf-8 -*-
'''
Management of Neutron resources
===============================
:depends: - neutronclient Python module
:configuration: See :py:mod:`salt.modules.neutron` for setup instructions.
.. code-block:: yaml
neutronng network present:
neutronng.network_present:
- name: Netone
- provider_physical_network: PHysnet1
- provider_network_type: vlan
'''
import logging
from functools import wraps
LOG = logging.getLogger(__name__)
def __virtual__():
'''
Only load if neutron module is present in __salt__
'''
return 'neutronng' if 'neutron.list_networks' in __salt__ else False
def _test_call(method):
(resource, functionality) = method.func_name.split('_')
if functionality == 'present':
functionality = 'updated'
else:
functionality = 'removed'
@wraps(method)
def check_for_testing(name, *args, **kwargs):
if __opts__.get('test', None):
return _no_change(name, resource, test=functionality)
return method(name, *args, **kwargs)
return check_for_testing
def _neutron_module_call(method, *args, **kwargs):
return __salt__['neutronng.{0}'.format(method)](*args, **kwargs)
def _get_tenant_id(tenant_name, *args, **kwargs):
try:
tenant_id = __salt__['keystoneng.tenant_get'](
name=tenant_name, **kwargs)[tenant_name]['id']
except:
tenant_id = None
LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
kwargs.get('connection_user')))
return tenant_id
def _auth(profile=None, endpoint_type=None):
'''
Set up neutron credentials
'''
if profile:
credentials = __salt__['config.option'](profile)
user = credentials['keystone.user']
password = credentials['keystone.password']
tenant = credentials['keystone.tenant']
auth_url = credentials['keystone.auth_url']
kwargs = {
'connection_user': user,
'connection_password': password,
'connection_tenant': tenant,
'connection_auth_url': auth_url,
'connection_endpoint_type': endpoint_type,
'profile': profile
}
return kwargs
@_test_call
def network_present(name=None,
network_id=None,
tenant=None,
provider_network_type=None,
provider_physical_network=None,
router_external=None,
admin_state_up=None,
shared=None,
provider_segmentation_id=None,
profile=None,
endpoint_type=None,
dns_domain=None,
is_default=None):
'''
Ensure that the neutron network is present with the specified properties.
name
The name of the network to manage
'''
connection_args = _auth(profile, endpoint_type)
tenant_id = _get_tenant_id(tenant_name=tenant, **connection_args)
existing_networks = _neutron_module_call(
'list_networks', name=name, tenant_id=tenant_id,
**connection_args)['networks']
network_arguments = _get_non_null_args(
name=name,
provider_network_type=provider_network_type,
provider_physical_network=provider_physical_network,
router_external=router_external,
admin_state_up=admin_state_up,
shared=shared,
tenant_id=tenant_id,
provider_segmentation_id=provider_segmentation_id,
dns_domain=dns_domain,
is_default=is_default)
if len(existing_networks) == 0:
network_arguments.update(connection_args)
res = _neutron_module_call(
'create_network', **network_arguments)['network']
if res.get('name') == name:
return _created(name, 'network', res['name'])
elif len(existing_networks) > 1:
LOG.error("More than one network found with the name: {0}".format(
name))
elif len(existing_networks) == 1:
existing_network = existing_networks[0]
LOG.info('CONNECTION STRINGS' + str(connection_args))
LOG.info('existing ' + str(existing_network))
LOG.info('new ' + str(network_arguments))
existing_network = dict((key.replace(':', '_', 1), value)
for key, value in
existing_network.iteritems())
# generate differential
diff = dict((key, value) for key, value in network_arguments.iteritems()
if existing_network.get(key, None) != value)
if diff:
# update the changes
network_arguments = diff.copy()
network_arguments.update(connection_args)
try:
LOG.debug('updating network {0} with changes {1}'.format(
name, str(diff)))
_neutron_module_call('update_network',
existing_network['id'],
**network_arguments)
changes_dict = _created(name, 'network', diff)
changes_dict['comment'] = '{1} {0} updated'.format(name, 'network')
return changes_dict
except:
LOG.error('Could not update network {0}'.format(name))
return _update_failed(name, 'network')
return _no_change(name, 'network')
return _create_failed(name, 'network')
@_test_call
def network_absent(name, network_id=None, profile=None, endpoint_type=None):
connection_args = _auth(profile, endpoint_type)
identifier = network_id or name
_neutron_module_call(
'delete_network', identifier, **connection_args)
return _absent(identifier, 'network')
@_test_call
def subnet_present(name,
network_name=None,
network_id=None,
tenant=None,
cidr=None,
ip_version=4,
enable_dhcp=True,
allocation_pools=None,
gateway_ip=None,
dns_nameservers=None,
host_routes=None,
profile=None,
endpoint_type=None):
'''
Ensure that the neutron subnet is present with the specified properties.
name
The name of the subnet to manage
'''
if network_name is None and network_id is None:
LOG.error("Network identificator name or uuid should be provided.")
return _create_failed(name, 'subnet')
connection_args = _auth(profile, endpoint_type)
tenant_id = _get_tenant_id(tenant_name=tenant, **connection_args)
existing_subnets = _neutron_module_call(
'list_subnets', tenant_id=tenant_id, name=name,
**connection_args)['subnets']
subnet_arguments = _get_non_null_args(
name=name,
cidr=cidr,
ip_version=ip_version,
enable_dhcp=enable_dhcp,
allocation_pools=allocation_pools,
gateway_ip=gateway_ip,
dns_nameservers=dns_nameservers,
host_routes=host_routes)
if network_id is None and network_name:
existing_networks = _neutron_module_call(
'list_networks', tenant_id=tenant_id, name=network_name,
**connection_args)['networks']
if len(existing_networks) == 0:
LOG.error("Can't find network with name: {0}".format(network_name))
elif len(existing_networks) == 1:
network_id = existing_networks[0]['id']
elif len(existing_networks) > 1:
LOG.error("Multiple networks with name: {0} found.".format(
network_name))
if network_id is None:
return _create_failed(name, 'subnet')
subnet_arguments['network_id'] = network_id
if len(existing_subnets) == 0:
subnet_arguments.update(connection_args)
res = _neutron_module_call('create_subnet', tenant_id=tenant_id,
**subnet_arguments)['subnet']
if res.get('name') == name:
return _created(name, 'subnet', res)
return _create_failed(name, 'subnet')
elif len(existing_subnets) == 1:
existing_subnet = existing_subnets[0]
# create differential
LOG.error('existing ' + str(existing_subnet))
LOG.error('new ' + str(subnet_arguments))
diff = dict((key, value) for key, value in subnet_arguments.iteritems()
if existing_subnet.get(key, None) != value)
if not diff:
return _no_change(name, 'subnet')
# update the changes
subnet_arguments = diff.copy()
subnet_arguments.update(connection_args)
try:
LOG.debug('updating subnet {0} with changes {1}'.format(
name, str(diff)))
_neutron_module_call('update_subnet',
existing_subnet['id'],
**subnet_arguments)
changes_dict = _created(name, 'subnet', diff)
changes_dict['comment'] = '{1} {0} updated'.format(name, 'subnet')
return changes_dict
except:
LOG.error('Could not update subnet {0}'.format(name))
return _update_failed(name, 'subnet')
elif len(existing_subnets) > 1:
LOG.error("Multiple subnets with name: {0} found".format(
name))
return _create_failed(name, 'network')
@_test_call
def subnet_absent(name, subnet_id=None, profile=None, endpoint_type=None):
connection_args = _auth(profile, endpoint_type)
identifier = subnet_id or name
_neutron_module_call(
'delete_subnet', identifier, **connection_args)
return _absent(name, 'subnet')
@_test_call
def router_present(name=None,
tenant=None,
gateway_network=None,
interfaces=None,
admin_state_up=True,
profile=None,
endpoint_type=None):
'''
Ensure that the neutron router is present with the specified properties.
name
The name of the subnet to manage
gateway_network
The network that would be the router's default gateway
interfaces
list of subnets the router attaches to
'''
connection_args = _auth(profile, endpoint_type)
tenant_name = tenant
try:
tenant_id = __salt__['keystoneng.tenant_get'](
name=tenant_name, **connection_args)[tenant_name]['id']
except:
tenant_id = None
LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
connection_args['connection_user']))
existing_router = _neutron_module_call(
'list_routers', name=name, **connection_args)
if not existing_router:
_neutron_module_call('create_router', name=name, tenant_id=tenant_id, admin_state_up=admin_state_up, **connection_args)
created_router = _neutron_module_call(
'list_routers', name=name, **connection_args)
if created_router:
router_id = created_router[name]['id']
network = _neutron_module_call(
'list_networks', name=gateway_network, **connection_args)["networks"]
#TODO test for more networks
gateway_network_id = network[0]['id']
_neutron_module_call('router_gateway_set',
router_id=router_id,
external_gateway=gateway_network_id,
**connection_args)
for interface in interfaces:
subnet = _neutron_module_call(
'list_subnets', name=interface, **connection_args)["subnets"]
subnet_id = subnet[0]['id']
_neutron_module_call('router_add_interface',
router_id=router_id,
subnet_id=subnet_id,
**connection_args)
return _created(name,
'router',
_neutron_module_call('list_routers',
name=name,
**connection_args))
return _create_failed(name, 'router')
router_id = existing_router[name]['id']
existing_router = existing_router[name]
diff = {}
if ( admin_state_up == True or admin_state_up == False ) and existing_router['admin_state_up'] != admin_state_up:
diff.update({'admin_state_up': admin_state_up})
if gateway_network:
network = _neutron_module_call(
'list_networks', name=gateway_network, **connection_args)["networks"]
gateway_network_id = network[0]['id']
if not existing_router['external_gateway_info'] and not existing_router['external_gateway_info'] == None:
if existing_router['external_gateway_info']['network_id'] != gateway_network_id:
diff.update({'external_gateway_info': {'network_id': gateway_network_id}})
elif not existing_router['external_gateway_info'] == None:
if not 'network_id' in existing_router['external_gateway_info'] or existing_router['external_gateway_info']['network_id'] != gateway_network_id:
diff.update({'external_gateway_info': {'network_id': gateway_network_id}})
if diff:
# update the changes
router_args = diff.copy()
router_args.update(connection_args)
try:
_neutron_module_call('update_router', existing_router['id'], **router_args)
changes_dict = _created(name, 'router', diff)
changes_dict['comment'] = 'Router {0} updated'.format(name)
return changes_dict
except:
LOG.exception('Router {0} could not be updated'.format(name))
return _update_failed(name, 'router')
return _no_change(name, 'router')
def floatingip_present(name=None,
tenant_name=None,
subnet=None,
tenant=None,
network=None,
port_id=None,
fip_exists=False,
profile=None,
endpoint_type=None):
'''
Ensure that the floating ip address is present for an instance
'''
instance_id = __salt__['novang.server_get'](name=name, tenant_name=tenant_name, profile=profile)
subnet_name = subnet
connection_args = _auth(profile, endpoint_type)
existing_subnet = _neutron_module_call(
'list_subnets', name=subnet_name, **connection_args)["subnets"]
subnet_id = existing_subnet[0]['id']
ret = {}
existing_ports = _neutron_module_call(
'list_ports', **connection_args)
existing_floatingips = _neutron_module_call(
'list_floatingips', **connection_args)
tenant = __salt__['keystoneng.tenant_get'](name=tenant_name, **connection_args)
tenant_id = tenant[tenant_name]['id']
existing_network = _neutron_module_call(
'list_networks', name=network, **connection_args)["networks"]
floating_network_id = existing_network[0]['id']
for key, value in existing_ports.iteritems():
try:
if value['fixed_ips'][0]['subnet_id'] == subnet_id and value['device_id'] == instance_id:
port_id=value['id']
except:
pass
for key, value in existing_floatingips.iteritems():
try:
if value['floating_network_id'] == floating_network_id and value['port_id'] == port_id and value['tenant_id'] == tenant_id:
fip_exists = True
break
except:
pass
if fip_exists == False:
for key, value in existing_ports.iteritems():
try:
if value['fixed_ips'][0]['subnet_id'] == subnet_id and value['device_id'] == instance_id:
ret = _neutron_module_call('create_floatingip', floating_network_id=floating_network_id, port_id=value['id'], tenant_id=tenant_id, **connection_args)
except:
pass
return _created('port', 'floatingip', ret)
else:
return _no_change('for instance {0}'.format(name), 'floatingip')
def security_group_present(name=None,
tenant=None,
description='',
rules=[],
profile=None,
endpoint_type=None):
'''
Ensure that the security group is present with the specified properties.
name
The name of the security group
description
The description of the security group
rules
list of rules to be added to the given security group
'''
# If the user is an admin, he's able to see the security groups from
# other tenants. In this case, we'll use the tenant id to get an existing
# security group.
connection_args = _auth(profile, endpoint_type)
tenant_name = tenant
try:
tenant_id = __salt__['keystoneng.tenant_get'](
name=tenant_name, **connection_args)[tenant_name]['id']
except:
tenant_id = None
LOG.debug('Cannot get the tenant id. User {0} is not an admin.'.format(
connection_args['connection_user']))
if tenant_id:
security_group = _neutron_module_call(
'list_security_groups', name=name, tenant_id=tenant_id,
**connection_args)
else:
security_group = _neutron_module_call(
'list_security_groups', name=name, **connection_args)
if not security_group:
# Create the security group as it doesn't exist already.
security_group_id = _neutron_module_call('create_security_group',
name=name,
description=description,
tenant_id=tenant_id,
**connection_args)
else:
security_group_id = security_group[name]['id']
# Set the missing rules attributes (in case the user didn't specify them
# in pillar) to some default values.
rules_attributes_defaults = {
'direction': 'ingress',
'ethertype': 'IPv4',
'protocol': 'TCP',
'port_range_min': None,
'port_range_max': None,
'remote_ip_prefix': None
}
for rule in rules:
for attribute in rules_attributes_defaults.keys():
if not rule.has_key(attribute):
rule[attribute] = rules_attributes_defaults[attribute]
# Remove all the duplicates rules given by the user in pillar.
unique_rules = []
for rule in rules:
if rule not in unique_rules:
unique_rules.append(rule)
# Get the existing security group rules.
existing_rules = _neutron_module_call(
'list_security_groups',
id=security_group_id,
**connection_args)[name]['security_group_rules']
new_rules = {}
for rule in unique_rules:
rule_found = False
for existing_rule in existing_rules:
attributes_match = True
# Compare the attributes of the existing security group rule with
# the attributes of the rule that we want to add.
for attribute in rules_attributes_defaults.keys():
existing_attribute = '' if not existing_rule[attribute] \
else str(existing_rule[attribute]).lower()
attribute = '' if not rule[attribute] \
else str(rule[attribute]).lower()
if existing_attribute != attribute:
attributes_match = False
break
if attributes_match:
rule_found = True
break
if rule_found:
# Skip adding the rule as it already exists.
continue
rule_index = len(new_rules) + 1
new_rules.update({'Rule {0}'.format(rule_index): rule})
_neutron_module_call('create_security_group_rule',
security_group_id=security_group_id,
direction=rule['direction'],
ethertype=rule['ethertype'],
protocol=rule['protocol'],
port_range_min=rule['port_range_min'],
port_range_max=rule['port_range_max'],
remote_ip_prefix=rule['remote_ip_prefix'],
tenant_id=tenant_id,
**connection_args)
if not security_group:
# The security group didn't exist. It was created and specified
# rules were added to it.
security_group = _neutron_module_call('list_security_groups',
id=security_group_id,
**connection_args)[name]
return _created(name, 'security_group', security_group)
if len(new_rules) == 0:
# Security group already exists and specified rules are already
# present.
return _no_change(name, 'security_group')
# Security group already exists, but the specified rules were added to it.
return _updated(name, 'security_group', {'New Rules': new_rules})
def port_present(network_name, profile=None, endpoint_type=None, name=None,
tenant=None, description='', fixed_ips=None, device_id=None,
device_owner=None, binding_host_id=None, admin_state_up=True,
mac_address=None, vnic_type=None, binding_profile=None,
security_groups=None, extra_dhcp_opt=None, qos_policy=None,
allowed_address_pair=None, dns_name=None):
"""
Ensure the port is present with specified parameters.
:param network_name: Name of the network to create port in
:param profile: Authentication profile
:param endpoint_type: Endpoint type
:param name: Name of this port
:param tenant: Tenant in which the port should be created, avaiable for
admin only.
:param description: Port description
:param fixed_ips: Desired IP and/or subnet for this port:
subnet_id=<name_or_id>,ip_address=<ip>.
:param device_id: Device ID of this port
:param device_owner: Device owner of this port
:param binding_host_id: he ID of the host where the port resides.
:param admin_state_up: Admin state of this port
:param mac_address: MAC address of this port
:param vnic_type: VNIC type for this port
:param binding_profile: Custom data to be passed as binding:profile
:param security_groups: Security group associated with the port
:param extra_dhcp_opt: Extra dhcp options to be assigned to this port:
opt_na me=<dhcp_option_name>,opt_value=<value>,
ip_version={4, 6}
:param qos_policy: ID or name of the QoS policy that shouldbe attached to
the resource
:param allowed_address_pair: ip_address=IP_ADDR|CIDR[,mac_address=MAC_ADDR]
Allowed address pair associated with the port.
"ip_address" parameter is required. IP address
or CIDR can be specified for "ip_address".
"mac_address" parameter is optional.
:param dns_name: Assign DNS name to the port (requires DNS integration
extension)
"""
connection_args = _auth(profile, endpoint_type)
tenant_id = _get_tenant_id(tenant_name=tenant, **connection_args)
network_id = None
port_exists = False
port_arguments = _get_non_null_args(
name=name, tenant_id=tenant_id, description=description,
fixed_ips=fixed_ips, device_id=device_id, device_owner=device_owner,
admin_state_up=admin_state_up,
mac_address=mac_address, vnic_type=vnic_type,
binding_profile=binding_profile,
extra_dhcp_opt=extra_dhcp_opt, qos_policy=qos_policy,
allowed_address_pair=allowed_address_pair, dns_name=dns_name)
if binding_host_id:
port_arguments['binding:host_id'] = binding_host_id
if security_groups:
sec_group_list = []
for sec_group_name in security_groups:
security_group = _neutron_module_call(
'list_security_groups', name=sec_group_name, **connection_args)
if security_group:
sec_group_list.append(security_group[sec_group_name]['id'])
port_arguments['security_groups'] = sec_group_list
existing_networks = _neutron_module_call(
'list_networks', tenant_id=tenant_id, name=network_name,
**connection_args)['networks']
if len(existing_networks) == 0:
LOG.error("Can't find network with name: {0}".format(network_name))
elif len(existing_networks) == 1:
network_id = existing_networks[0]['id']
elif len(existing_networks) > 1:
LOG.error("Multiple networks with name: {0} found.".format(network_name))
if network_id is None:
return _create_failed(name, 'port')
port_arguments['network_id'] = network_id
existing_ports = _neutron_module_call(
'list_ports', network_id=network_id, tenant_id=tenant_id,
**connection_args)
if name:
for key, value in existing_ports.iteritems():
try:
if value['name'] == name and value['tenant_id'] == tenant_id:
port_exists = True
break
except KeyError:
pass
if not port_exists:
port_arguments.update(connection_args)
res = _neutron_module_call('create_port', **port_arguments)['port']
if res['name'] == name:
return _created(name, 'port', res)
return _create_failed(name, 'port')
else:
return _no_change('for instance {0}'.format(name), 'port')
def _created(name, resource, resource_definition):
changes_dict = {'name': name,
'changes': resource_definition,
'result': True,
'comment': '{0} {1} created'.format(resource, name)}
return changes_dict
def _updated(name, resource, resource_definition):
changes_dict = {'name': name,
'changes': resource_definition,
'result': True,
'comment': '{0} {1} updated'.format(resource, name)}
return changes_dict
def _no_change(name, resource, test=False):
changes_dict = {'name': name,
'changes': {},
'result': True}
if test:
changes_dict['comment'] = \
'{0} {1} will be {2}'.format(resource, name, test)
else:
changes_dict['comment'] = \
'{0} {1} is in correct state'.format(resource, name)
return changes_dict
def _deleted(name, resource, resource_definition):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} removed'.format(resource, name),
'result': True}
return changes_dict
def _absent(name, resource):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} not present'.format(resource, name),
'result': True}
return changes_dict
def _delete_failed(name, resource):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} failed to delete'.format(resource,
name),
'result': False}
return changes_dict
def _create_failed(name, resource):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} failed to create'.format(resource,
name),
'result': False}
return changes_dict
def _update_failed(name, resource):
changes_dict = {'name': name,
'changes': {},
'comment': '{0} {1} failed to update'.format(resource,
name),
'result': False}
return changes_dict
def _get_non_null_args(**kwargs):
'''
Return those kwargs which are not null
'''
return dict((key, value,) for key, value in kwargs.iteritems()
if value is not None)