blob: 85e9c3c8f6fca05dd7e699b39f41be5e592c5f17 [file] [log] [blame]
import logging
import random
log = logging.getLogger(__name__)
def __virtual__():
return 'neutronv2' if 'neutronv2.subnet_list' in __salt__ else False
def _neutronv2_call(fname, *args, **kwargs):
if __opts__.get('test') and not any(x for x in ['_get_', '_list'] if x in fname):
return {'changes': 'neutronv2 state {} to be called with {} {}'.format(
fname, args, kwargs), 'result': None}
return __salt__['neutronv2.{}'.format(fname)](*args, **kwargs)
def _try_get_resource(resource, name, cloud_name):
try:
method_name = '{}_get_details'.format(resource)
exact_resource = _neutronv2_call(
method_name, name, cloud_name=cloud_name
)[resource]
except Exception as e:
if 'ResourceNotFound' in repr(e):
return None
else:
raise
return exact_resource
def _resource_present(resource, resource_name, changeable_params, cloud_name,
**kwargs):
exact_resource = None
try:
exact_resource = _try_get_resource(resource, resource_name, cloud_name)
if exact_resource is None:
# in case of rename - check if resource was already renamed
if kwargs.get('name') is not None and kwargs.get(
'name') != resource_name:
exact_resource = _try_get_resource(resource,
kwargs.get('name'),
cloud_name)
except Exception as e:
if 'MultipleResourcesFound' in repr(e):
return _failed('find', resource_name, resource)
else:
raise
if exact_resource is None:
try:
method_name = '{}_create'.format(resource)
exact_resource_name = kwargs.pop('name', resource_name)
resp = _neutronv2_call(
method_name, name=exact_resource_name,
cloud_name=cloud_name, **kwargs)
except Exception as e:
log.exception('Neutron {0} create failed with {1}'.
format(resource, e))
return _failed('create', exact_resource_name, resource)
return _succeeded('create', exact_resource_name, resource, resp)
to_update = {}
for key in kwargs:
if key in changeable_params and (key not in exact_resource
or kwargs[key] != exact_resource[key]):
to_update[key] = kwargs[key]
if to_update:
try:
method_name = '{}_update'.format(resource)
resp = _neutronv2_call(
method_name, resource_name, cloud_name=cloud_name, **to_update
)
except Exception as e:
log.exception('Neutron {0} update failed with {1}'.format(resource, e))
return _failed('update', resource_name, resource)
return _succeeded('update', resource_name, resource, resp)
else:
return _succeeded('no_changes', resource_name, resource)
def _resource_absent(resource, name, cloud_name):
try:
method_name = '{}_get_details'.format(resource)
_neutronv2_call(
method_name, name, cloud_name=cloud_name
)[resource]
except Exception as e:
if 'ResourceNotFound' in repr(e):
return _succeeded('absent', name, resource)
if 'MultipleResourcesFound' in repr(e):
return _failed('find', name, resource)
try:
method_name = '{}_delete'.format(resource)
_neutronv2_call(
method_name, name, cloud_name=cloud_name
)
except Exception as e:
log.error('Neutron delete {0} failed with {1}'.format(resource, e))
return _failed('delete', name, resource)
return _succeeded('delete', name, resource)
def network_present(name, cloud_name, **kwargs):
changeable = (
'admin_state_up', 'dns_domain', 'mtu', 'port_security_enabled',
'provider:network_type', 'provider:physical_network',
'provider:segmentation_id', 'qos_policy_id', 'router:external',
'segments', 'shared', 'description', 'is_default'
)
return _resource_present('network', name, changeable, cloud_name, **kwargs)
def network_absent(name, cloud_name):
return _resource_absent('network', name, cloud_name)
def subnet_present(name, cloud_name, network_id, ip_version, cidr, **kwargs):
kwargs.update({'network_id': network_id,
'ip_version': ip_version,
'cidr': cidr})
changeable = (
'name', 'enable_dhcp', 'dns_nameservers', 'allocation_pools',
'host_routes', 'gateway_ip', 'description', 'service_types',
)
return _resource_present('subnet', name, changeable, cloud_name, **kwargs)
def subnet_absent(name, cloud_name):
return _resource_absent('subnet', name, cloud_name)
def subnetpool_present(name, cloud_name, prefixes, **kwargs):
kwargs.update({'prefixes': prefixes})
changeable = (
'default_quota', 'min_prefixlen', 'address_scope_id',
'default_prefixlen', 'description'
)
return _resource_present('subnetpool', name, changeable, cloud_name, **kwargs)
def subnetpool_absent(name, cloud_name):
return _resource_absent('subnetpool', name, cloud_name)
def agent_present(name, agent_type, cloud_name, **kwargs):
"""
:param name: agent host name
:param agent_type: type of the agent. i.e. 'L3 agent' or 'DHCP agent'
:param kwargs:
:param description: agent description
:param admin_state_up: administrative state of the agent
"""
agents = _neutronv2_call(
'agent_list', host=name, agent_type=agent_type,
cloud_name=cloud_name)['agents']
# Make sure we have one and only one such agent
if len(agents) == 1:
agent = agents[0]
to_update = {}
for key in kwargs:
if kwargs[key] != agent[key]:
to_update[key] = kwargs[key]
if to_update:
try:
_neutronv2_call('agent_update', agent_id=agent['id'],
cloud_name=cloud_name, **kwargs)
except Exception:
return _failed('update', name, 'agent')
return _succeeded('update', name, 'agent')
return _succeeded('no_changes', name, 'agent')
else:
return _failed('find', name, 'agent')
def agents_disabled(name, cloud_name, **kwargs):
"""
:param name: agent host name
:param kwargs:
:param description: agent description
:param admin_state_up: administrative state of the agent
"""
agents = _neutronv2_call(
'agent_list', host=name, cloud_name=cloud_name)['agents']
changes = {}
for agent in agents:
if agent['admin_state_up'] == True:
try:
changes[agent['id']] = _neutronv2_call('agent_update', agent_id=agent['id'],
cloud_name=cloud_name, admin_state_up=False)
except Exception:
return _failed('update', name, 'agent')
return _succeeded('update', name, 'agent',changes)
def agents_enabled(name, cloud_name, **kwargs):
"""
:param name: agent host name
:param kwargs:
:param description: agent description
:param admin_state_up: administrative state of the agent
"""
agents = _neutronv2_call(
'agent_list', host=name, cloud_name=cloud_name)['agents']
changes = {}
for agent in agents:
if agent['admin_state_up'] == False:
try:
changes[agent['id']] = _neutronv2_call('agent_update', agent_id=agent['id'],
cloud_name=cloud_name, admin_state_up=True)
except Exception:
return _failed('update', name, 'agent')
return _succeeded('update', name, 'agent', changes)
def l3_resources_moved(name, cloud_name, target=None):
"""
Ensure l3 resources are moved to target/other nodes
Move non-HA (legacy and DVR) routers.
:param name: agent host to remove routers from
:param target: target host to move routers to
:param cloud_name: name of cloud from os client config
"""
all_agents = _neutronv2_call(
'agent_list', agent_type='L3 agent', cloud_name=cloud_name)['agents']
current_agent_id = [x['id'] for x in all_agents if x['host'] == name][0]
if target is not None:
target_agents = [x['id'] for x in all_agents if x['host'] == target]
else:
target_agents = [x['id'] for x in all_agents
if x['host'] != name and x['alive'] and x['admin_state_up']]
if len(target_agents) == 0:
log.error("No candidate agents to move routers.")
return _failed('resources_moved', name, 'L3 agent')
routers_on_agent = _neutronv2_call(
'l3_agent_router_list', current_agent_id, cloud_name=cloud_name)['routers']
routers_on_agent = [x for x in routers_on_agent if x['ha'] == False]
try:
for router in routers_on_agent:
_neutronv2_call(
'l3_agent_router_remove', router_id=router['id'],
agent_id=current_agent_id, cloud_name=cloud_name)
_neutronv2_call(
'l3_agent_router_schedule', router_id=router['id'],
agent_id=random.choice(target_agents),
cloud_name=cloud_name)
except Exception as e:
log.exception("Failed to move router from {0}: {1}".format(name, e))
return _failed('resources_moved', name, 'L3 agent')
return _succeeded('resources_moved', name, 'L3 agent')
def port_present(port_name, cloud_name, **kwargs):
changeable = (
'name', 'description', 'device_id', 'qos_policy',
'allowed_address_pair', 'fixed_ip',
'device_owner', 'admin_state_up', 'security_group', 'extra_dhcp_opt',
)
return _resource_present('port', port_name, changeable,
cloud_name, **kwargs)
def rbac_get_rule_id(cloud_name, **kwargs):
existing_rules = _neutronv2_call('rbac_policies_list',
cloud_name=cloud_name)
match_condition_fields = ['action',
'target_tenant',
'object_id',
]
for rule in existing_rules['rbac_policies']:
match = True
for field in match_condition_fields:
if rule[field] != kwargs[field]:
match = False
break
if match: return rule['id']
def rbac_present(name, cloud_name, **kwargs):
resource = 'rbac_policies'
# Resolve network name to UID if needed
kwargs['object_id'] = __salt__['neutronv2.network_get_details'] \
(network_id=kwargs['object_id'],cloud_name=cloud_name)['network']['id']
if rbac_get_rule_id(cloud_name, **kwargs):
return _succeeded('no_changes', name, resource)
r = _neutronv2_call('{}_create'.format(resource),
cloud_name=cloud_name,
**kwargs)
if r:
return _succeeded('create', name, resource, changes=r)
else:
return _failed('create', name, kwargs)
def rbac_absent(name, cloud_name, **kwargs):
resource = 'rbac_policies'
# Resolve network name to UID if needed
kwargs['object_id'] = __salt__['neutronv2.network_get_details'] \
(network_id=kwargs['object_id'],cloud_name=cloud_name)['network']['id']
rule_id = rbac_get_rule_id(cloud_name, **kwargs)
if rule_id:
r = _neutronv2_call('{}_delete'.format(resource),
cloud_name=cloud_name,
id=rule_id)
return _succeeded('delete', name, resource, changes=r)
return _succeeded('no_changes', name, resource)
def _succeeded(op, name, resource, changes=None):
msg_map = {
'create': '{0} {1} created',
'delete': '{0} {1} removed',
'update': '{0} {1} updated',
'no_changes': '{0} {1} is in desired state',
'absent': '{0} {1} not present',
'resources_moved': '{1} resources were moved from {0}',
}
changes_dict = {
'name': name,
'result': True,
'comment': msg_map[op].format(resource, name),
'changes': changes or {},
}
return changes_dict
def _failed(op, name, resource):
msg_map = {
'create': '{0} {1} failed to create',
'delete': '{0} {1} failed to delete',
'update': '{0} {1} failed to update',
'find': '{0} {1} found multiple {0}',
'resources_moved': 'failed to move {1} from {0}',
}
changes_dict = {
'name': name,
'result': False,
'comment': msg_map[op].format(resource, name),
'changes': {},
}
return changes_dict