Merge "Fixing retry condition"
diff --git a/_modules/neutronv2/__init__.py b/_modules/neutronv2/__init__.py
index e275af6..74b6fb1 100644
--- a/_modules/neutronv2/__init__.py
+++ b/_modules/neutronv2/__init__.py
@@ -12,6 +12,7 @@
 from neutronv2 import subnets
 from neutronv2 import agents
 from neutronv2 import routers
+from neutronv2 import ports
 from neutronv2 import common
 
 
@@ -61,6 +62,13 @@
 router_interface_add = routers.router_interface_add
 router_interface_remove = routers.router_interface_remove
 
+port_list = lists.port_list
+port_create = ports.port_create
+port_delete = ports.port_delete
+port_update = ports.port_update
+port_get_details = ports.port_get_details
+
+
 wait_for_network_services = agents.wait_for_network_services
 
 wait_for_api_ready = common.wait_for_api_ready
@@ -79,6 +87,7 @@
     'dhcp_agent_network_remove', 'dhcp_agent_network_schedule',
     'router_list', 'router_create', 'router_delete', 'router_get_details',
     'router_interface_add', 'router_interface_remove', 'router_update',
+    'port_create', 'port_delete', 'port_update', 'port_list', 'port_get_details',
 )
 
 
diff --git a/_modules/neutronv2/arg_converter.py b/_modules/neutronv2/arg_converter.py
index f2bc76d..99cb8fe 100644
--- a/_modules/neutronv2/arg_converter.py
+++ b/_modules/neutronv2/arg_converter.py
@@ -18,6 +18,7 @@
     'subnetpool': lists.subnetpool_list,
     'agent': lists.agent_list,
     'router': lists.router_list,
+    'port': lists.port_list,
 }
 
 
@@ -27,6 +28,7 @@
     'subnetpool': 'subnetpools',
     'agent': 'agents',
     'router': 'routers',
+    'port': 'ports',
 }
 
 
diff --git a/_modules/neutronv2/lists.py b/_modules/neutronv2/lists.py
index 1b56392..eea4a7a 100644
--- a/_modules/neutronv2/lists.py
+++ b/_modules/neutronv2/lists.py
@@ -34,3 +34,9 @@
 def router_list(**kwargs):
     url = '/routers?{}'.format(urlencode(kwargs))
     return url, {}
+
+
+@send('get')
+def port_list(**kwargs):
+    url = '/ports?{}'.format(urlencode(kwargs))
+    return url, {}
\ No newline at end of file
diff --git a/_modules/neutronv2/ports.py b/_modules/neutronv2/ports.py
new file mode 100644
index 0000000..ab9482b
--- /dev/null
+++ b/_modules/neutronv2/ports.py
@@ -0,0 +1,40 @@
+from neutronv2.common import send
+from neutronv2.arg_converter import get_by_name_or_uuid_multiple
+
+try:
+    from urllib.parse import urlencode
+except ImportError:
+    from urllib import urlencode
+
+
+@get_by_name_or_uuid_multiple([('port', 'port_id')])
+@send('get')
+def port_get_details(port_id, **kwargs):
+    url = '/ports/{}?{}'.format(port_id, urlencode(kwargs))
+    return url, {}
+
+
+@get_by_name_or_uuid_multiple([('port', 'port_id')])
+@send('put')
+def port_update(port_id, **kwargs):
+    url = '/ports/{}'.format(port_id)
+    json = {
+        'port': kwargs,
+    }
+    return url, {'json': json}
+
+
+@get_by_name_or_uuid_multiple([('port', 'port_id')])
+@send('delete')
+def port_delete(port_id, **kwargs):
+    url = '/port/{}'.format(port_id)
+    return url, {}
+
+
+@send('post')
+def port_create(**kwargs):
+    url = '/ports'
+    json = {
+        'port': kwargs,
+    }
+    return url, {'json': json}
diff --git a/_states/neutronv2.py b/_states/neutronv2.py
index b4fd30d..81c8fa4 100644
--- a/_states/neutronv2.py
+++ b/_states/neutronv2.py
@@ -12,7 +12,7 @@
     return __salt__['neutronv2.{}'.format(fname)](*args, **kwargs)
 
 
-def _resource_present(resource, name, changeable_params, cloud_name, **kwargs):
+def _try_get_resource(resource, name, cloud_name):
     try:
         method_name = '{}_get_details'.format(resource)
         exact_resource = _neutronv2_call(
@@ -20,35 +20,58 @@
         )[resource]
     except Exception as e:
         if 'ResourceNotFound' in repr(e):
-            try:
-                method_name = '{}_create'.format(resource)
-                resp = _neutronv2_call(
-                    method_name, name=name, cloud_name=cloud_name, **kwargs
-                )
-            except Exception as e:
-                log.exception('Neutron {0} create failed with {1}'.
-                    format(resource, e))
-                return _failed('create', name, resource)
-            return _succeeded('create', name, resource, resp)
-        elif 'MultipleResourcesFound' in repr(e):
-            return _failed('find', name, resource)
+            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]
-    try:
-        method_name = '{}_update'.format(resource)
-        resp = _neutronv2_call(
-            method_name, 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', name, resource)
-    return _succeeded('update', name, resource, resp)
+    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):
@@ -237,6 +260,17 @@
     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 _succeeded(op, name, resource, changes=None):
     msg_map = {
         'create': '{0} {1} created',