| 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 |