Add port_present client state

This state allows to create port with desired parameters in
a given network.

Related prod: PROD-11917

Change-Id: Iff637a7085a9e83a906c68fb1e608645d1536b61
diff --git a/_modules/neutronng.py b/_modules/neutronng.py
index 6a3eaf8..06f273e 100644
--- a/_modules/neutronng.py
+++ b/_modules/neutronng.py
@@ -328,9 +328,7 @@
     .. code-block:: bash
         salt '*' neutronng.create_port network_id='openstack-network-id'
     '''
-    response = neutron_interface.create_port({'port': port_params})
-    if 'port' in response and 'id' in response['port']:
-        return response['port']['id']
+    return neutron_interface.create_port({'port': port_params})
 
 
 @_autheticate
diff --git a/_states/neutronng.py b/_states/neutronng.py
index afca39b..92afb7a 100644
--- a/_states/neutronng.py
+++ b/_states/neutronng.py
@@ -527,6 +527,109 @@
     # 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,
diff --git a/neutron/client.sls b/neutron/client.sls
index 0976115..44b03f7 100644
--- a/neutron/client.sls
+++ b/neutron/client.sls
@@ -7,6 +7,26 @@
 
 
 {%- for identity_name, identity in client.server.iteritems() %}
+
+{%- if identity.security_group is defined %}
+
+{%- for security_group_name, security_group in identity.security_group.iteritems() %}
+openstack_security_group_{{ security_group_name }}:
+  neutronng.security_group_present:
+    - name: {{ security_group_name }}
+    {%- if security_group.description is defined %}
+    - description: {{ security_group.description }}
+    {%- endif %}
+    - rules: {{ security_group.rules }}
+    - profile: {{ identity_name }}
+    - tenant: {{ security_group.tenant }}
+    {%- if identity.endpoint_type is defined %}
+    - endpoint_type: {{ identity.endpoint_type }}
+    {%- endif %}
+{%- endfor %}
+
+{%- endif %}
+
 {%- if identity.network is defined %}
 
 {%- for network_name, network in identity.network.iteritems() %}
@@ -80,6 +100,66 @@
 
 {%- endif %}
 
+{%- if network.port is defined %}
+
+{%- for port_name, port in network.port.iteritems() %}
+neutron_openstack_port_{{ port_name }}:
+  neutronng.port_present:
+    - network_name: {{ network_name }}
+    - name: {{ port_name }}
+    - profile: {{ identity_name }}
+    - tenant: {{ network.tenant }}
+    {%- if identity.endpoint_type is defined %}
+    - endpoint_type: {{ identity.endpoint_type }}
+    {%- endif %}
+    {%- if port.description is defined %}
+    - description: {{ port.description  }}
+    {%- endif %}
+    {%- if port.fixed_ips is defined %}
+    - fixed_ips: {{ port.fixed_ips }}
+    {%- endif %}
+    {%- if port.device_id is defined %}
+    - device_id: {{ port.device_id }}
+    {%- endif %}
+    {%- if port.device_owner is defined %}
+    - device_owner: {{ port.device_owner }}
+    {%- endif %}
+    {%- if port.binding_host_id is defined %}
+    - binding_host_id: {{ port.binding_host_id }}
+    {%- endif %}
+    {%- if port.admin_state_up is defined %}
+    - admin_state_up: {{ port.admin_state_up }}
+    {%- endif %}
+    {%- if port.mac_address is defined %}
+    - mac_address: {{ port.mac_address }}
+    {%- endif %}
+    {%- if port.vnic_type is defined %}
+    - vnic_type: {{ port.vnic_type }}
+    {%- endif %}
+    {%- if port.binding_profile is defined %}
+    - binding_profile: {{ port.binding_profile }}
+    {%- endif %}
+    {%- if port.security_groups is defined %}
+    - security_groups: {{ port.security_groups }}
+    {%- endif %}
+    {%- if port.extra_dhcp_opt is defined %}
+    - extra_dhcp_opt: {{ port.extra_dhcp_opt }}
+    {%- endif %}
+    {%- if port.qos_policy is defined %}
+    - qos_policy: {{ port.qos_policy }}
+    {%- endif %}
+    {%- if port.allowed_address_pair is defined %}
+    - allowed_address_pair: {{ port.allowed_address_pair }}
+    {%- endif %}
+    {%- if port.dns_name is defined %}
+    - dns_name: {{ port.dns_name }}
+    {%- endif %}
+    - require:
+      - neutronng: neutron_openstack_network_{{ network_name }}
+{%- endfor %}
+
+{%- endif %}
+
 {%- endfor %}
 
 {%- endif %}
@@ -102,25 +182,6 @@
 
 {%- endif %}
 
-{%- if identity.security_group is defined %}
-
-{%- for security_group_name, security_group in identity.security_group.iteritems() %}
-openstack_security_group_{{ security_group_name }}:
-  neutronng.security_group_present:
-    - name: {{ security_group_name }}
-    {%- if security_group.description is defined %}
-    - description: {{ security_group.description }}
-    {%- endif %}
-    - rules: {{ security_group.rules }}
-    - profile: {{ identity_name }}
-    - tenant: {{ security_group.tenant }}
-    {%- if identity.endpoint_type is defined %}
-    - endpoint_type: {{ identity.endpoint_type }}
-    {%- endif %}
-{%- endfor %}
-
-{%- endif %}
-
 {%- if identity.floating_ip is defined %}
 
 {%- for instance_name, instance in identity.floating_ip.iteritems() %}