Added neutronv2

Added _module and _state named neutronv2.
V2 stands for using raw REST API requests rather than
neutron python client which creates problems with not
versioned salt formulas (see related PROD).

Also created executable modules needed and states
to maintain networks, subnets, subnetpools

Change-Id: I8014b76244259218bd502b9d6722ce728413d8af
Related-Prod: PROD-16226
Related-Prod: PROD-19522
diff --git a/_states/neutronv2.py b/_states/neutronv2.py
new file mode 100644
index 0000000..06a0363
--- /dev/null
+++ b/_states/neutronv2.py
@@ -0,0 +1,150 @@
+import logging
+
+log = logging.getLogger(__name__)
+
+
+def __virtual__():
+    return 'neutronv2' if 'neutronv2.subnet_list' in __salt__ else False
+
+
+def _neutronv2_call(fname, *args, **kwargs):
+    return __salt__['neutronv2.{}'.format(fname)](*args, **kwargs)
+
+
+def _resource_present(resource, name, changeable_params, cloud_name, **kwargs):
+    try:
+        method_name = '{}_get_details'.format(resource)
+        exact_resource = _neutronv2_call(
+            method_name, name=name, cloud_name=cloud_name
+        )[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)
+        else:
+            raise
+
+    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=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)
+
+
+def _resource_absent(resource, name, cloud_name):
+    try:
+        method_name = '{}_get_details'.format(resource)
+        _neutronv2_call(
+            method_name, 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=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 _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'
+    }
+    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}'
+    }
+    changes_dict = {
+        'name': name,
+        'result': False,
+        'comment': msg_map[op].format(resource, name),
+        'changes': {},
+    }
+    return changes_dict