Merge "Trigger policy-rc.d only if packages are installed"
diff --git a/_modules/neutronv2/__init__.py b/_modules/neutronv2/__init__.py
index 6e37241..3626669 100644
--- a/_modules/neutronv2/__init__.py
+++ b/_modules/neutronv2/__init__.py
@@ -5,28 +5,32 @@
 except ImportError:
     REQUIREMENTS_MET = False
 
+from neutronv2 import lists
 from neutronv2 import networks
 from neutronv2 import subnetpools
 from neutronv2 import auto_alloc
 from neutronv2 import subnets
+from neutronv2 import agents
+from neutronv2 import routers
+
 
 network_get_details = networks.network_get_details
 network_update = networks.network_update
 network_delete = networks.network_delete
-network_list = networks.network_list
+network_list = lists.network_list
 network_create = networks.network_create
 network_bulk_create = networks.network_bulk_create
 
 subnetpool_get_details = subnetpools.subnetpool_get_details
 subnetpool_update = subnetpools.subnetpool_update
 subnetpool_delete = subnetpools.subnetpool_delete
-subnetpool_list = subnetpools.subnetpool_list
+subnetpool_list = lists.subnetpool_list
 subnetpool_create = subnetpools.subnetpool_create
 
 auto_alloc_get_details = auto_alloc.auto_alloc_get_details
 auto_alloc_delete = auto_alloc.auto_alloc_delete
 
-subnet_list = subnets.subnet_list
+subnet_list = lists.subnet_list
 subnet_create = subnets.subnet_create
 subnet_bulk_create = subnets.subnet_bulk_create
 subnet_get_details = subnets.subnet_get_details
@@ -34,6 +38,29 @@
 subnet_delete = subnets.subnet_delete
 
 
+agent_list = lists.agent_list
+agent_get_details = agents.agent_get_details
+agent_update = agents.agent_update
+agent_delete = agents.agent_delete
+l3_agent_router_list = agents.l3_agent_router_list
+l3_agent_router_schedule = agents.l3_agent_router_schedule
+l3_agent_router_remove = agents.l3_agent_router_remove
+l3_agent_by_router_list = agents.l3_agent_by_router_list
+dhcp_agent_list_networks = agents.dhcp_agent_list_networks
+dhcp_agent_network_schedule = agents.dhcp_agent_network_schedule
+dhcp_agent_network_remove = agents.dhcp_agent_network_remove
+dhcp_agent_by_network_list = agents.dhcp_agent_by_network_list
+
+
+router_list = lists.router_list
+router_create = routers.router_create
+router_get_details = routers.router_get_details
+router_update = routers.router_update
+router_delete = routers.router_delete
+router_interface_add = routers.router_interface_add
+router_interface_remove = routers.router_interface_remove
+
+
 __all__ = (
     'network_get_details', 'network_update', 'network_delete', 'network_list',
     'network_create', 'network_bulk_create', 'subnetpool_get_details',
@@ -41,6 +68,13 @@
     'subnetpool_create', 'auto_alloc_get_details', 'auto_alloc_delete',
     'subnet_list', 'subnet_create', 'subnet_bulk_create', 'subnet_get_details',
     'subnet_update', 'subnet_delete',
+    'agent_list', 'agent_delete', 'agent_get_details', 'agent_update',
+    'l3_agent_by_router_list', 'l3_agent_router_list',
+    'l3_agent_router_remove', 'l3_agent_router_schedule',
+    'dhcp_agent_by_network_list', 'dhcp_agent_list_networks',
+    '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',
 )
 
 
diff --git a/_modules/neutronv2/agents.py b/_modules/neutronv2/agents.py
new file mode 100644
index 0000000..15703d2
--- /dev/null
+++ b/_modules/neutronv2/agents.py
@@ -0,0 +1,89 @@
+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
+
+
+@send('get')
+def agent_get_details(agent_id, **kwargs):
+    url = '/agents/{}?{}'.format(agent_id, urlencode(kwargs))
+    return url, {}
+
+
+@send('put')
+def agent_update(agent_id, **kwargs):
+    url = '/agents/{}'.format(agent_id)
+    json = {
+        'agent': kwargs,
+    }
+    return url, {'json': json}
+
+
+@send('delete')
+def agent_delete(agent_id, **kwargs):
+    url = '/agents/{}'.format(agent_id)
+    return url, {}
+
+
+@send('get')
+def l3_agent_router_list(agent_id, **kwargs):
+    url = '/agents/{}/l3-routers'.format(agent_id)
+    return url, {}
+
+
+@get_by_name_or_uuid_multiple([('router', 'router_id')])
+@send('post')
+def l3_agent_router_schedule(router_id, agent_id, **kwargs):
+    url = '/agents/{}/l3-routers'.format(agent_id)
+    json = {
+        'router_id': router_id,
+    }
+    return url, {'json': json}
+
+
+@get_by_name_or_uuid_multiple([('router', 'router_id')])
+@send('delete')
+def l3_agent_router_remove(router_id, agent_id, **kwargs):
+    url = '/agents/{}/l3-routers/{}'.format(agent_id, router_id)
+    return url, {}
+
+
+@get_by_name_or_uuid_multiple([('router', 'router_id')])
+@send('get')
+def l3_agent_by_router_list(router_id, **kwargs):
+    url = '/routers/{}/l3-agents'.format(router_id)
+    return url, {}
+
+
+@send('get')
+def dhcp_agent_list_networks(agent_id, **kwargs):
+    url = '/agents/{}/dhcp-networks'.format(agent_id)
+    return url, {}
+
+
+@get_by_name_or_uuid_multiple([('network', 'network_id')])
+@send('post')
+def dhcp_agent_network_schedule(network_id, agent_id, **kwargs):
+    url = '/agents/{}/dhcp-networks'.format(agent_id)
+    json = {
+        'network_id': network_id,
+    }
+    return url, {'json': json}
+
+
+@get_by_name_or_uuid_multiple([('network', 'network_id')])
+@send('delete')
+def dhcp_agent_network_remove(network_id, agent_id, **kwargs):
+    url = '/agents/{}/dhcp-networks/{}'.format(agent_id, network_id)
+    return url, {}
+
+
+@get_by_name_or_uuid_multiple([('network', 'network_id')])
+@send('get')
+def dhcp_agent_by_network_list(network_id, **kwargs):
+    url = '/networks/{}/dhcp-agents'.format(network_id)
+    return url, {}
diff --git a/_modules/neutronv2/arg_converter.py b/_modules/neutronv2/arg_converter.py
new file mode 100644
index 0000000..f2bc76d
--- /dev/null
+++ b/_modules/neutronv2/arg_converter.py
@@ -0,0 +1,61 @@
+from neutronv2 import lists
+from neutronv2 import common
+import functools
+import inspect
+from uuid import UUID
+
+
+def _check_uuid(val):
+    try:
+        return str(UUID(val)) == val
+    except (TypeError, ValueError, AttributeError):
+        return False
+
+
+resource_lists = {
+    'network': lists.network_list,
+    'subnet': lists.subnet_list,
+    'subnetpool': lists.subnetpool_list,
+    'agent': lists.agent_list,
+    'router': lists.router_list,
+}
+
+
+response_keys = {
+    'network': 'networks',
+    'subnet': 'subnets',
+    'subnetpool': 'subnetpools',
+    'agent': 'agents',
+    'router': 'routers',
+}
+
+
+def get_by_name_or_uuid_multiple(resource_arg_name_pairs):
+    def wrap(func):
+        @functools.wraps(func)
+        def wrapped_f(*args, **kwargs):
+            largs = list(args)
+            inspect_args = inspect.getargspec(
+                func.func_closure[0].cell_contents)
+            for (resource, arg_name) in resource_arg_name_pairs:
+                arg_index = inspect_args.args.index(arg_name)
+                if arg_name in kwargs:
+                    ref = kwargs.pop(arg_name, None)
+                else:
+                    ref = largs.pop(arg_index)
+                cloud_name = kwargs['cloud_name']
+                if _check_uuid(ref):
+                    kwargs[arg_name] = ref
+                else:
+                    # Then we have name not uuid
+                    resp_key = response_keys[resource]
+                    resp = resource_lists[resource](
+                        name=ref, cloud_name=cloud_name)[resp_key]
+                    if len(resp) == 0:
+                        raise common.ResourceNotFound(resp_key, ref)
+                    elif len(resp) > 1:
+                        raise common.MultipleResourcesFound(resp_key, ref)
+                    kwargs[arg_name] = resp[0]['id']
+            return func(*largs, **kwargs)
+        return wrapped_f
+    return wrap
diff --git a/_modules/neutronv2/common.py b/_modules/neutronv2/common.py
index adc3ff5..0dd4b8e 100644
--- a/_modules/neutronv2/common.py
+++ b/_modules/neutronv2/common.py
@@ -1,6 +1,6 @@
+import functools
 import logging
 import os_client_config
-from uuid import UUID
 
 log = logging.getLogger(__name__)
 
@@ -63,6 +63,7 @@
 
 def send(method):
     def wrap(func):
+        @functools.wraps(func)
         def wrapped_f(*args, **kwargs):
             cloud_name = kwargs.pop('cloud_name')
             if not cloud_name:
@@ -89,37 +90,3 @@
             return resp
         return wrapped_f
     return wrap
-
-
-def _check_uuid(val):
-    try:
-        return str(UUID(val)) == val
-    except (TypeError, ValueError, AttributeError):
-        return False
-
-
-def get_by_name_or_uuid(resource_list, resp_key,
-                        res_id_key='name'):
-    def wrap(func):
-        def wrapped_f(*args, **kwargs):
-            if res_id_key in kwargs:
-                ref = kwargs.pop(res_id_key)
-                start_arg = 0
-            else:
-                start_arg = 1
-                ref = args[0]
-            cloud_name = kwargs['cloud_name']
-            if _check_uuid(ref):
-                uuid = ref
-            else:
-                # Then we have name not uuid
-                resp = resource_list(
-                    name=ref, cloud_name=cloud_name)[resp_key]
-                if len(resp) == 0:
-                    raise ResourceNotFound(resp_key, ref)
-                elif len(resp) > 1:
-                    raise MultipleResourcesFound(resp_key, ref)
-                uuid = resp[0]['id']
-            return func(uuid, *args[start_arg:], **kwargs)
-        return wrapped_f
-    return wrap
diff --git a/_modules/neutronv2/lists.py b/_modules/neutronv2/lists.py
new file mode 100644
index 0000000..1b56392
--- /dev/null
+++ b/_modules/neutronv2/lists.py
@@ -0,0 +1,36 @@
+from neutronv2.common import send
+
+try:
+    from urllib.parse import urlencode
+except ImportError:
+    from urllib import urlencode
+
+
+@send('get')
+def subnet_list(**kwargs):
+    url = '/subnets?{}'.format(urlencode(kwargs))
+    return url, {}
+
+
+@send('get')
+def subnetpool_list(**kwargs):
+    url = '/subnetpools?{}'.format(urlencode(kwargs))
+    return url, {}
+
+
+@send('get')
+def agent_list(**kwargs):
+    url = '/agents?{}'.format(urlencode(kwargs))
+    return url, {}
+
+
+@send('get')
+def network_list(**kwargs):
+    url = '/networks?{}'.format(urlencode(kwargs))
+    return url, {}
+
+
+@send('get')
+def router_list(**kwargs):
+    url = '/routers?{}'.format(urlencode(kwargs))
+    return url, {}
diff --git a/_modules/neutronv2/networks.py b/_modules/neutronv2/networks.py
index d0e85f8..bf0bf6c 100644
--- a/_modules/neutronv2/networks.py
+++ b/_modules/neutronv2/networks.py
@@ -1,26 +1,20 @@
-from neutronv2.common import send, get_by_name_or_uuid
+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
 
-RESOURCE_LIST_KEY = 'networks'
 
-
-@send('get')
-def network_list(**kwargs):
-    url = '/networks?{}'.format(urlencode(kwargs))
-    return url, {}
-
-
-@get_by_name_or_uuid(network_list, RESOURCE_LIST_KEY)
+@get_by_name_or_uuid_multiple([('network', 'network_id')])
 @send('get')
 def network_get_details(network_id, **kwargs):
     url = '/networks/{}?{}'.format(network_id, urlencode(kwargs))
     return url, {}
 
 
-@get_by_name_or_uuid(network_list, RESOURCE_LIST_KEY)
+@get_by_name_or_uuid_multiple([('network', 'network_id')])
 @send('put')
 def network_update(network_id, **kwargs):
     url = '/networks/{}'.format(network_id)
@@ -30,7 +24,7 @@
     return url, {'json': json}
 
 
-@get_by_name_or_uuid(network_list, RESOURCE_LIST_KEY)
+@get_by_name_or_uuid_multiple([('network', 'network_id')])
 @send('delete')
 def network_delete(network_id, **kwargs):
     url = '/networks/{}'.format(network_id)
diff --git a/_modules/neutronv2/routers.py b/_modules/neutronv2/routers.py
new file mode 100644
index 0000000..5c240aa
--- /dev/null
+++ b/_modules/neutronv2/routers.py
@@ -0,0 +1,56 @@
+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
+
+
+@send('post')
+def router_create(name, **kwargs):
+    url = '/routers'
+    json = {
+        'router': {
+            'name': name,
+        }
+    }
+    json['router'].update(kwargs)
+    return url, {'json': json}
+
+
+@get_by_name_or_uuid_multiple([('router', 'router_id')])
+@send('get')
+def router_get_details(router_id, **kwargs):
+    url = '/routers/{}?{}'.format(router_id, urlencode(kwargs))
+    return url, {}
+
+
+@get_by_name_or_uuid_multiple([('router', 'router_id')])
+@send('put')
+def router_update(router_id, **kwargs):
+    url = '/routers/{}'.format(router_id)
+    return url, {'json': {'router': kwargs}}
+
+
+@get_by_name_or_uuid_multiple([('router', 'router_id')])
+@send('delete')
+def router_delete(router_id, **kwargs):
+    url = '/routers/{}'.format(router_id)
+    return url, {}
+
+
+@get_by_name_or_uuid_multiple([('router', 'router_id')])
+@send('put')
+def router_interface_add(router_id, **kwargs):
+    url = '/routers/{}/add_role_interface'.format(router_id)
+    json = kwargs
+    return url, {'json': json}
+
+
+@get_by_name_or_uuid_multiple([('router', 'router_id')])
+@send('put')
+def router_interface_remove(router_id, **kwargs):
+    url = '/routers/{}/remove_role_interface'.format(router_id)
+    json = kwargs
+    return url, {'json': json}
diff --git a/_modules/neutronv2/subnetpools.py b/_modules/neutronv2/subnetpools.py
index fb1912b..5e55393 100644
--- a/_modules/neutronv2/subnetpools.py
+++ b/_modules/neutronv2/subnetpools.py
@@ -1,19 +1,13 @@
-from neutronv2.common import send, get_by_name_or_uuid
+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
 
-RESOURCE_LIST_KEY = 'subnetpools'
 
-
-@send('get')
-def subnetpool_list(**kwargs):
-    url = '/subnetpools?{}'.format(urlencode(kwargs))
-    return url, {}
-
-
-@get_by_name_or_uuid(subnetpool_list, RESOURCE_LIST_KEY)
+@get_by_name_or_uuid_multiple([('subnetpool', 'subnetpool_id')])
 @send('get')
 def subnetpool_get_details(subnetpool_id, **kwargs):
     url = '/subnetpools/{}?{}'.format(
@@ -22,7 +16,7 @@
     return url, {}
 
 
-@get_by_name_or_uuid(subnetpool_list, RESOURCE_LIST_KEY)
+@get_by_name_or_uuid_multiple([('subnetpool', 'subnetpool_id')])
 @send('put')
 def subnetpool_update(subnetpool_id, **kwargs):
     url = '/subnetpools/{}'.format(subnetpool_id)
@@ -32,7 +26,7 @@
     return url, {'json': json}
 
 
-@get_by_name_or_uuid(subnetpool_list, RESOURCE_LIST_KEY)
+@get_by_name_or_uuid_multiple([('subnetpool', 'subnetpool_id')])
 @send('delete')
 def subnetpool_delete(subnetpool_id, **kwargs):
     url = '/subnetpools/{}'.format(subnetpool_id)
diff --git a/_modules/neutronv2/subnets.py b/_modules/neutronv2/subnets.py
index 3a29969..7388584 100644
--- a/_modules/neutronv2/subnets.py
+++ b/_modules/neutronv2/subnets.py
@@ -1,21 +1,13 @@
-from neutronv2.common import send, get_by_name_or_uuid
-from neutronv2 import networks
+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
 
-RESOURCE_LIST_KEY = 'subnets'
 
-
-@send('get')
-def subnet_list(**kwargs):
-    url = '/subnets?{}'.format(urlencode(kwargs))
-    return url, {}
-
-
-@get_by_name_or_uuid(networks.network_list, networks.RESOURCE_LIST_KEY,
-                     res_id_key='network_id')
+@get_by_name_or_uuid_multiple([('network', 'network_id')])
 @send('post')
 def subnet_create(network_id, ip_version, cidr, **kwargs):
     url = '/subnets'
@@ -39,14 +31,14 @@
     return url, {'json': json}
 
 
-@get_by_name_or_uuid(subnet_list, RESOURCE_LIST_KEY)
+@get_by_name_or_uuid_multiple([('subnet', 'subnet_id')])
 @send('get')
 def subnet_get_details(subnet_id, **kwargs):
     url = '/subnets/{}'.format(subnet_id)
     return url, {}
 
 
-@get_by_name_or_uuid(subnet_list, RESOURCE_LIST_KEY)
+@get_by_name_or_uuid_multiple([('subnet', 'subnet_id')])
 @send('put')
 def subnet_update(subnet_id, **kwargs):
     url = '/subnets/{}'.format(subnet_id)
@@ -56,7 +48,7 @@
     return url, {'json': json}
 
 
-@get_by_name_or_uuid(subnet_list, RESOURCE_LIST_KEY)
+@get_by_name_or_uuid_multiple([('subnet', 'subnet_id')])
 @send('delete')
 def subnet_delete(subnet_id, **kwargs):
     url = '/subnets/{}'.format(subnet_id)
diff --git a/_states/neutronv2.py b/_states/neutronv2.py
index 06a0363..0c00c22 100644
--- a/_states/neutronv2.py
+++ b/_states/neutronv2.py
@@ -15,7 +15,7 @@
     try:
         method_name = '{}_get_details'.format(resource)
         exact_resource = _neutronv2_call(
-            method_name, name=name, cloud_name=cloud_name
+            method_name, name, cloud_name=cloud_name
         )[resource]
     except Exception as e:
         if 'ResourceNotFound' in repr(e):
@@ -54,7 +54,7 @@
     try:
         method_name = '{}_get_details'.format(resource)
         _neutronv2_call(
-            method_name, name=name, cloud_name=cloud_name
+            method_name, name, cloud_name=cloud_name
         )[resource]
     except Exception as e:
         if 'ResourceNotFound' in repr(e):
@@ -64,7 +64,7 @@
     try:
         method_name = '{}_delete'.format(resource)
         _neutronv2_call(
-            method_name, name=name, cloud_name=cloud_name
+            method_name, name, cloud_name=cloud_name
         )
     except Exception as e:
         log.error('Neutron delete {0} failed with {1}'.format(resource, e))
@@ -117,6 +117,79 @@
     return _resource_absent('subnetpool', name, cloud_name)
 
 
+def agent_present(name, agent_type, cloud_name, **kwargs):
+    """
+    :param name: agent host name
+    :param agent_type: type of the agent. i.e. 'L3 agent' or 'DHCP agent'
+    :param kwargs:
+        :param description: agent description
+        :param admin_state_up: administrative state of the agent
+    """
+    agents = _neutronv2_call(
+        'agent_list', host=name, agent_type=agent_type,
+        cloud_name=cloud_name)['agents']
+    # Make sure we have one and only one such agent
+    if len(agents) == 1:
+        agent = agents[0]
+        to_update = {}
+        for key in kwargs:
+            if kwargs[key] != agent[key]:
+                to_update[key] = kwargs[key]
+        if to_update:
+            try:
+                _neutronv2_call('agent_update', agent_id=agent['id'],
+                                cloud_name=cloud_name, **kwargs)
+            except Exception:
+                return _failed('update', name, 'agent')
+            return _succeeded('update', name, 'agent')
+        return _succeeded('no_changes', name, 'agent')
+    else:
+        return _failed('find', name, 'agent')
+
+
+def agents_disabled(name, cloud_name, **kwargs):
+    """
+    :param name: agent host name
+    :param kwargs:
+        :param description: agent description
+        :param admin_state_up: administrative state of the agent
+    """
+    agents = _neutronv2_call(
+        'agent_list', host=name, cloud_name=cloud_name)['agents']
+
+    changes = {}
+    for agent in agents:
+      if agent['admin_state_up'] == True:
+        try:
+          changes[agent['id']] = _neutronv2_call('agent_update', agent_id=agent['id'],
+                                                 cloud_name=cloud_name, admin_state_up=False)
+        except Exception:
+          return _failed('update', name, 'agent')
+    return _succeeded('update', name, 'agent',changes)
+
+
+def agents_enabled(name, cloud_name, **kwargs):
+    """
+    :param name: agent host name
+    :param kwargs:
+        :param description: agent description
+        :param admin_state_up: administrative state of the agent
+    """
+    agents = _neutronv2_call(
+        'agent_list', host=name, cloud_name=cloud_name)['agents']
+
+    changes = {}
+    for agent in agents:
+      if agent['admin_state_up'] == False:
+        try:
+          changes[agent['id']] = _neutronv2_call('agent_update', agent_id=agent['id'],
+                                                 cloud_name=cloud_name, admin_state_up=True)
+        except Exception:
+          return _failed('update', name, 'agent')
+
+    return _succeeded('update', name, 'agent', changes)
+
+
 def _succeeded(op, name, resource, changes=None):
     msg_map = {
         'create': '{0} {1} created',
diff --git a/neutron/map.jinja b/neutron/map.jinja
index e8becfc..13b4bd6 100644
--- a/neutron/map.jinja
+++ b/neutron/map.jinja
@@ -2,7 +2,8 @@
     'cacert_file': salt['grains.filter_by']({
         'Debian': '/etc/ssl/certs/ca-certificates.crt',
         'RedHat': '/etc/pki/tls/certs/ca-bundle.crt'
-    })}
+    }),
+    'enabled': false }
 %}
 
 {% set compute = salt['grains.filter_by']({
@@ -151,10 +152,12 @@
 
 {% set client = salt['grains.filter_by']({
     'Debian': {
-        'pkgs': ['python-neutronclient']
+        'pkgs': ['python-neutronclient'],
+        'enabled': false
     },
     'RedHat': {
-        'pkgs': ['python-neutronclient']
+        'pkgs': ['python-neutronclient'],
+        'enabled': false
     },
 }, merge=pillar.neutron.get('client', {})) %}
 
diff --git a/neutron/meta/salt.yml b/neutron/meta/salt.yml
index ca0502a..a6fba47 100644
--- a/neutron/meta/salt.yml
+++ b/neutron/meta/salt.yml
@@ -1,11 +1,5 @@
-orchestrate:
-  server:
-    priority: 580
-    batch: 1
-    require:
-    - salt: keystone.server
-  compute:
-    priority: 590
-    require:
-    - salt: neutron.server
-
+orchestration:
+  upgrade:
+    applications:
+      neutron:
+        priority: 1150
diff --git a/neutron/upgrade/pkgs_latest.sls b/neutron/upgrade/pkgs_latest.sls
new file mode 100644
index 0000000..6023128
--- /dev/null
+++ b/neutron/upgrade/pkgs_latest.sls
@@ -0,0 +1,62 @@
+{%- from "neutron/map.jinja" import server,client,gateway,compute,fwaas with context %}
+
+neutron_task_pkg_latest:
+  test.show_notification:
+    - text: "Running neutron.upgrade.pkg_latest"
+
+policy-rc.d_present:
+  file.managed:
+    - name: /usr/sbin/policy-rc.d
+    - mode: 755
+    - contents: |
+        #!/bin/sh
+        exit 101
+
+{%- set npkgs = [] %}
+{%- if server.enabled %}
+  {%- do npkgs.extend(server.pkgs) %}
+  {% if server.backend.engine == "contrail" %}
+    {%- do npkgs.append('neutron-plugin-contrail') %}
+  {% elif server.backend.engine == "ml2" %}
+    {%- do npkgs.extend(server.pkgs_ml2) %}
+  {%- elif server.backend.get('opendaylight', False) %}
+    {%- do npkgs.append('python-networking-odl') %}
+  {%- elif server.backend.engine == "ovn" %}
+    {%- do npkgs.extend(server.pkgs_ovn) %}
+  {%- elif server.backend.engine == "midonet" %}
+    {%- if server.version == "kilo" %}
+      {%- do npkgs.extend(['python-neutron-plugin-midonet', 'python-neutron-lbaas']) %}
+    {%- else %}
+      {%- do npkgs.extend(['python-networking-midonet', 'python-neutron-lbaas', 'python-neutron-fwaas']) %}
+    {%- endif %}
+  {% elif server.backend.engine == "vmware" %}
+    {%- do npkgs.append('python-vmware-nsx') %}
+  {%- endif %}
+  {% if server.get('bgp_vpn', {}).get('enabled', False) %}
+    {%- do npkgs.extend(server.pkgs_bagpipe) %}
+  {%- endif %}
+  {%- if fwaas.get('enabled', False) %}
+    {%- do npkgs.extend(fwaas.pkgs) %}
+  {%- endif %}
+{%- endif %}
+{%- if gateway.enabled is defined and gateway.enabled %}
+  {%- do npkgs.extend(gateway.pkgs) %}
+{%- endif %}
+{%- if compute.enabled is defined and compute.enabled %}
+  {%- do npkgs.extend(compute.pkgs) %}
+{%- endif %}
+{%- if client.enabled is defined and client.enabled %}
+  {%- do npkgs.extend(client.pkgs) %}
+{%- endif %}
+
+neutron_pkg_latest:
+  pkg.latest:
+    - names: {{ npkgs|unique }}
+    - require:
+      - file: policy-rc.d_present
+    - require_in:
+      - file: policy-rc.d_absent
+
+policy-rc.d_absent:
+  file.absent:
+    - name: /usr/sbin/policy-rc.d
diff --git a/neutron/upgrade/post/init.sls b/neutron/upgrade/post/init.sls
new file mode 100644
index 0000000..4f06e22
--- /dev/null
+++ b/neutron/upgrade/post/init.sls
@@ -0,0 +1,11 @@
+{%- from "neutron/map.jinja" import server,gateway with context %}
+
+neutron_post:
+  test.show_notification:
+    - text: "Running neutron.upgrade.post"
+
+{%- if gateway.get('enabled') %}
+keystone_os_client_config_absent:
+  file.absent:
+    - name: /etc/openstack/clouds.yml
+{%- endif %}
diff --git a/neutron/upgrade/pre/init.sls b/neutron/upgrade/pre/init.sls
new file mode 100644
index 0000000..55e654d
--- /dev/null
+++ b/neutron/upgrade/pre/init.sls
@@ -0,0 +1,24 @@
+{%- from "neutron/map.jinja" import server,gateway with context %}
+
+include:
+ - neutron.upgrade.verify.api
+
+neutron_pre:
+  test.show_notification:
+    - text: "Running neutron.upgrade.pre"
+
+{%- if gateway.get('enabled') %}
+{# Get os client config from mine #}
+
+{%- set os_content = salt['mine.get']('I@keystone:client:os_client_config:enabled:true', 'keystone_os_client_config', 'compound').values()[0] %}
+
+keystone_os_client_config:
+  file.managed:
+    - name: /etc/openstack/clouds.yml
+    - contents: |
+        {{ os_content |yaml(False)|indent(8) }}
+    - user: 'root'
+    - group: 'root'
+    - makedirs: True
+
+{%- endif %}
diff --git a/neutron/upgrade/render_config.sls b/neutron/upgrade/render_config.sls
new file mode 100644
index 0000000..1f5acaa
--- /dev/null
+++ b/neutron/upgrade/render_config.sls
@@ -0,0 +1,61 @@
+{%- from "neutron/map.jinja" import server,gateway,compute,fwaas with context %}
+
+neutron_render_config:
+  test.show_notification:
+    - text: "Running neutron.upgrade.render_config"
+
+{%- if server.enabled %}
+  {%- set conf_mapping = [['/etc/neutron/neutron.conf', 'salt://neutron/files/' + server.version + '/neutron-server.conf'],
+                          ['/etc/neutron/api-paste.ini','salt://neutron/files/' + server.version + '/api-paste.ini']] %}
+{%- elif gateway.enabled %}
+  {%- set conf_mapping = [['/etc/neutron/neutron.conf', 'salt://neutron/files/' + gateway.version + '/neutron-generic.conf']] %}
+{%- elif compute.enabled %}
+  {%- set conf_mapping = [['/etc/neutron/neutron.conf', 'salt://neutron/files/' + compute.version + '/neutron-generic.conf']] %}
+{%- endif %}
+
+{%- if server.enabled %}
+  {% if server.backend.engine == "contrail" %}
+    {%- do conf_mapping.append(['/etc/neutron/plugins/opencontrail/ContrailPlugin.ini', "salt://neutron/files/" + server.version + "/ContrailPlugin.ini"]) %}
+  {% elif server.backend.engine == "ml2" %}
+    {%- do conf_mapping.append(['/etc/neutron/plugins/ml2/ml2_conf.ini', "salt://neutron/files/" + server.version + "/ml2_conf.ini"]) %}
+  {%- elif server.backend.engine == "midonet" %}
+    {%- do conf_mapping.append(['/etc/neutron/plugins/midonet/midonet.ini', "salt://neutron/files/" + server.version + "/midonet.ini"]) %}
+  {% elif server.backend.engine == "vmware" %}
+    {%- do conf_mapping.append(['/etc/neutron/plugins/vmware/nsx.ini', "salt://neutron/files/" + server.version + "/plugins/nsx.ini"]) %}
+  {%- endif %}
+  {%- if fwaas.get('enabled', False) %}
+    {%- do conf_mapping.append(['/etc/neutron/fwaas_driver.ini', "salt://neutron/files/" + fwaas.version + "/fwaas_driver.ini"]) %}
+  {%- endif %}
+  {%- if server.get('l2gw', {}).get('enabled', False) %}
+    {%- do conf_mapping.append(['/etc/neutron/l2gw_plugin.ini', "salt://neutron/files/" + server.version + "/l2gw/l2gw_plugin.ini"]) %}
+  {%- endif %}
+{%- elif gateway.enabled %}
+  {%- do conf_mapping.extend([['/etc/neutron/l3_agent.ini', "salt://neutron/files/" + gateway.version + "/l3_agent.ini"],
+                             ['/etc/neutron/plugins/ml2/openvswitch_agent.ini', "salt://neutron/files/" + gateway.version + "/openvswitch_agent.ini"],
+                             ['/etc/neutron/dhcp_agent.ini', "salt://neutron/files/" + gateway.version + "/dhcp_agent.ini"],
+                             ['/etc/neutron/metadata_agent.ini',"salt://neutron/files/" + gateway.version + "/metadata_agent.ini"]]) %}
+{%- elif compute.enabled %}
+  {% if compute.get('bgp_vpn', {}).get('enabled', False) and server.bgp_vpn.driver == "bagpipe" %}
+    {%- do conf_mapping.append(['/etc/bagpipe-bgp/bgp.conf', "salt://neutron/files/" + compute.version + "/bagpipe-bgp.conf"]) %}
+  {%- endif %}
+  {% if compute.backend.engine == "ml2" %}
+    {%- if compute.dvr %}
+      {%- do conf_mapping.extend([['/etc/neutron/l3_agent.ini', "salt://neutron/files/" + compute.version + "/l3_agent.ini"],
+                                 ['/etc/neutron/metadata_agent.ini',"salt://neutron/files/" + compute.version + "/metadata_agent.ini"]]) %}
+    {%- endif %}
+    {% if compute.get('dhcp_agent_enabled', False) %}
+      {%- do conf_mapping.extend([['/etc/neutron/dhcp_agent.ini', "salt://neutron/files/" + compute.version + "/dhcp_agent.ini"]]) %}
+    {%- endif %}
+    {% if compute.backend.sriov is defined %}
+      {%- do conf_mapping.extend([['/etc/neutron/plugins/ml2/sriov_agent.ini', "salt://neutron/files/" + compute.version + "/sriov_agent.ini"]]) %}
+    {%- endif %}
+  {%- endif %}
+{%- endif %}
+
+
+{%- for file, source in conf_mapping %}
+{{ file }}:
+  file.managed:
+  - source: {{ source }}
+  - template: jinja
+{%- endfor %}
diff --git a/neutron/upgrade/service_running.sls b/neutron/upgrade/service_running.sls
new file mode 100644
index 0000000..d2ac334
--- /dev/null
+++ b/neutron/upgrade/service_running.sls
@@ -0,0 +1,36 @@
+{%- from "neutron/map.jinja" import server,gateway,compute with context %}
+
+neutron_service_running:
+  test.show_notification:
+    - text: "Running neutron.upgrade.service_running"
+
+{%- set nservices = [] %}
+
+{%- if server.enabled %}
+  {%- do nservices.extend(server.services) %}
+  {% if server.backend.engine == "contrail" %}
+    {%- do nservices.append('neutron-server') %}
+  {%- endif %}
+{%- endif %}
+{%- if gateway.enabled is defined and gateway.enabled%}
+  {%- do nservices.extend(gateway.services) %}
+{%- endif %}
+{%- if compute.enabled is defined and compute.enabled%}
+  {%- do nservices.extend(compute.services) %}
+  {%- if compute.dvr %}
+    {%- do nservices.extend(['neutron-l3-agent', 'neutron-metadata-agent']) %}
+  {%- endif %}
+  {% if compute.get('dhcp_agent_enabled', False) %}
+    {%- do nservices.append('neutron-dhcp-agent') %}
+  {%- endif %}
+  {% if compute.backend.sriov is defined %}
+    {%- do nservices.append('neutron-sriov-agent') %}
+  {%- endif %}
+{%- endif %}
+
+{%- for nservice in nservices|unique %}
+neutron_service_{{ nservice }}_running:
+  service.running:
+  - enable: True
+  - name: {{ nservice }}
+{%- endfor %}
diff --git a/neutron/upgrade/service_stopped.sls b/neutron/upgrade/service_stopped.sls
new file mode 100644
index 0000000..6e33e5c
--- /dev/null
+++ b/neutron/upgrade/service_stopped.sls
@@ -0,0 +1,37 @@
+{%- from "neutron/map.jinja" import server,gateway,compute with context %}
+
+neutron_service_stopped:
+  test.show_notification:
+    - text: "Running neutron.upgrade.service_stopped"
+
+{%- set nservices = [] %}
+
+{%- if server.enabled %}
+  {%- do nservices.extend(server.services) %}
+  {% if server.backend.engine == "contrail" %}
+    {%- do nservices.append('neutron-server') %}
+  {%- endif %}
+{%- endif %}
+{%- if gateway.enabled is defined and gateway.enabled %}
+  {%- do nservices.extend(gateway.services) %}
+{%- endif %}
+{%- if compute.enabled is defined and compute.enabled %}
+  {%- do nservices.extend(compute.services) %}
+  {%- if compute.dvr %}
+    {%- do nservices.extend(['neutron-l3-agent', 'neutron-metadata-agent']) %}
+  {%- endif %}
+  {% if compute.get('dhcp_agent_enabled', False) %}
+    {%- do nservices.append('neutron-dhcp-agent') %}
+  {%- endif %}
+  {% if compute.backend.sriov is defined %}
+    {%- do nservices.append('neutron-sriov-agent') %}
+  {%- endif %}
+{%- endif %}
+
+
+{%- for nservice in nservices|unique %}
+neutron_service_{{ nservice }}_stopped:
+  service.dead:
+  - name: {{ nservice }}
+  - enable: false
+{%- endfor %}
diff --git a/neutron/upgrade/upgrade/init.sls b/neutron/upgrade/upgrade/init.sls
new file mode 100644
index 0000000..4ca8dea
--- /dev/null
+++ b/neutron/upgrade/upgrade/init.sls
@@ -0,0 +1,16 @@
+{%- from "neutron/map.jinja" import server with context %}
+
+neutron_upgrade:
+  test.show_notification:
+    - text: "Running neutron.upgrade.upgrade"
+
+include:
+ - neutron.upgrade.upgrade.pre
+ - neutron.upgrade.service_stopped
+ - neutron.upgrade.pkgs_latest
+ - neutron.upgrade.render_config
+{%- if server.get('enabled') %}
+ - neutron.db.offline_sync
+{%- endif %}
+ - neutron.upgrade.service_running
+ - neutron.upgrade.upgrade.post
diff --git a/neutron/upgrade/upgrade/post.sls b/neutron/upgrade/upgrade/post.sls
new file mode 100644
index 0000000..9c1d20f
--- /dev/null
+++ b/neutron/upgrade/upgrade/post.sls
@@ -0,0 +1,10 @@
+{%- from "neutron/map.jinja" import server,gateway with context %}
+
+{%- if gateway.get('enabled') %}
+{% set host_id = salt['network.get_hostname']() %}
+
+neutron_agent_enabled:
+  neutronv2.agents_enabled:
+    - name: {{ host_id }}
+    - cloud_name: admin_identity
+{%- endif %}
diff --git a/neutron/upgrade/upgrade/pre.sls b/neutron/upgrade/upgrade/pre.sls
new file mode 100644
index 0000000..9a4a105
--- /dev/null
+++ b/neutron/upgrade/upgrade/pre.sls
@@ -0,0 +1,11 @@
+{%- from "neutron/map.jinja" import server,gateway with context %}
+
+{%- if gateway.get('enabled') %}
+{% set host_id = salt['network.get_hostname']() %}
+
+neutron_agent_disable:
+  neutronv2.agents_disabled:
+    - name: {{ host_id }}
+    - cloud_name: admin_identity
+
+{%- endif %}
diff --git a/neutron/upgrade/verify/api.sls b/neutron/upgrade/verify/api.sls
new file mode 100644
index 0000000..e45df66
--- /dev/null
+++ b/neutron/upgrade/verify/api.sls
@@ -0,0 +1,55 @@
+{%- from "neutron/map.jinja" import server with context %}
+{%- from "keystone/map.jinja" import client as kclient with context %}
+
+neutron_upgrade_verify_api:
+  test.show_notification:
+    - text: "Running neutron.upgrade.verify.api"
+
+{%- if kclient.enabled and kclient.get('os_client_config', {}).get('enabled', False)  %}
+  {%- if server.enabled %}
+    {%- set neutron_test_network = 'Upgrade_TestNetwork' %}
+    {%- set neutron_test_subnet = 'Upgrade_TestSubnet' %}
+
+neutronv2_subnet_list:
+  module.run:
+    - name: neutronv2.subnet_list
+    - kwargs:
+        cloud_name: admin_identity
+
+neutronv2_network_list:
+  module.run:
+    - name: neutronv2.network_list
+    - kwargs:
+        cloud_name: admin_identity
+
+neutronv2_network_present:
+  neutronv2.network_present:
+  - cloud_name: admin_identity
+  - name: {{ neutron_test_network }}
+
+neutronv2_subnet_present:
+  neutronv2.subnet_present:
+  - name: {{ neutron_test_subnet }}
+  - cloud_name: admin_identity
+  - network_id: {{ neutron_test_network }}
+  - ip_version: 4
+  - cidr: 192.168.89.0/24
+  - require:
+    - neutronv2_network_present
+
+neutronv2_subnet_absent:
+  neutronv2.subnet_absent:
+  - cloud_name: admin_identity
+  - name: {{ neutron_test_subnet }}
+  - require:
+    - neutronv2_subnet_present
+
+neutronv2_network_absent:
+  neutronv2.network_absent:
+  - cloud_name: admin_identity
+  - name: {{ neutron_test_network }}
+  - require:
+    - neutronv2_subnet_absent
+
+  {%- endif %}
+{%- endif %}