Improve ironic module

Add volume_connectors states and fix port_present ability to update

Change-Id: I110ca0d2aa16304cb429f662168c73536b997e3c
Closes-bug: PROD-25368 (PROD:25368)
diff --git a/_states/ironicv1.py b/_states/ironicv1.py
index b6cd7df..2505f68 100644
--- a/_states/ironicv1.py
+++ b/_states/ironicv1.py
@@ -138,7 +138,7 @@
             try:
                 method_name = '{}_update'.format(resource)
                 resp = _ironicv1_call(
-                    method_name, name, properties=to_change,
+                    method_name, exact_resource['uuid'], properties=to_change,
                     microversion=microversion, cloud_name=cloud_name,
                 )
             except Exception as e:
@@ -177,6 +177,111 @@
         return _failed('find', name, resource)
 
 
+def volume_connector_present(name, node, volume_type, cloud_name,
+                             **kwargs):
+    """
+
+    :param name: alias for connector_id because of how salt works
+    :param node: node_ident
+    :param volume_type: type of volume
+    """
+    resource = 'volume_connector'
+    microversion = kwargs.pop('microversion', '1.32')
+    method_name = '{}_list'.format(resource)
+    exact_resource = filter(
+        lambda data: data['connector_id'] == name,
+        _ironicv1_call(method_name, node=node,
+                       cloud_name=cloud_name,
+                       microversion=microversion)['connectors'])
+    if len(exact_resource) == 0:
+        try:
+            method_name = 'node_get_details'
+            node_uuid = _ironicv1_call(
+                method_name, node, cloud_name=cloud_name,
+                microversion=microversion
+            )['uuid']
+        except Exception as e:
+            if 'Not Found' in str(e):
+                return _failed('not_found', node, 'node')
+            raise
+        try:
+            method_name = '{}_create'.format(resource)
+            resp = _ironicv1_call(
+                method_name, node_uuid, volume_type, name,
+                cloud_name=cloud_name, microversion=microversion, **kwargs)
+        except Exception as e:
+            log.exception('Ironic {0} create failed with {1}'.
+                          format('node', e))
+            return _failed('create', name, resource)
+        return _succeeded('create', name, resource, resp)
+    elif len(exact_resource) == 1:
+        exact_resource = exact_resource[0]
+        to_change = []
+        for prop in kwargs:
+            path = prop.replace('~', '~0').replace('/', '~1')
+            if prop in exact_resource:
+                if exact_resource[prop] != kwargs[prop]:
+                    to_change.append({
+                        'op': 'replace',
+                        'path': '/{}'.format(path),
+                        'value': kwargs[prop],
+                    })
+            else:
+                to_change.append({
+                    'op': 'add',
+                    'path': '/{}'.format(path),
+                    'value': kwargs[prop],
+                })
+        if to_change:
+            try:
+                method_name = '{}_update'.format(resource)
+                resp = _ironicv1_call(
+                    method_name, exact_resource['uuid'], properties=to_change,
+                    microversion=microversion, cloud_name=cloud_name,
+                )
+            except Exception as e:
+                log.exception(
+                    'Ironic {0} update failed with {1}'.format(resource,
+                                                               e))
+                return _failed('update', name, resource)
+            return _succeeded('update', name, resource, resp)
+        return _succeeded('no_changes', name, resource)
+    else:
+        return _failed('find', name, resource)
+
+
+def volume_connector_absent(name, cloud_name, node, **kwargs):
+    """
+
+    :param name: alias for connector_id because of how salt works
+    :param node: node ident
+    """
+    resource = 'volume_connector'
+    microversion = kwargs.pop('microversion', '1.32')
+    method_name = '{}_list'.format(resource)
+    exact_resource = filter(
+        lambda data: data['connector_id'] == name,
+        _ironicv1_call(method_name, node=node,
+                       cloud_name=cloud_name,
+                       microversion=microversion)['connectors'])
+    if len(exact_resource) == 0:
+            return _succeeded('absent', name, resource)
+    elif len(exact_resource) == 1:
+        connector_uuid = exact_resource[0]['uuid']
+        try:
+            method_name = '{}_delete'.format(resource)
+            _ironicv1_call(
+                method_name, connector_uuid, cloud_name=cloud_name,
+                microversion=microversion
+            )
+        except Exception as e:
+            log.error('Ironic delete {0} failed with {1}'.format(resource, e))
+            return _failed('delete', name, resource)
+        return _succeeded('delete', name, resource)
+    else:
+        return _failed('find', name, resource)
+
+
 def _succeeded(op, name, resource, changes=None):
     msg_map = {
         'create': '{0} {1} created',
@@ -199,7 +304,8 @@
         'create': '{0} {1} failed to create',
         'delete': '{0} {1} failed to delete',
         'update': '{0} {1} failed to update',
-        'find': '{0} {1} found multiple {0}'
+        'find': '{0} {1} found multiple {0}',
+        'not found': '{0} {1} not found',
     }
     changes_dict = {
         'name': name,