| # -*- 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) |