Merge "Adding states to perform nova upgrade"
diff --git a/README.rst b/README.rst
index 1fa5f8c..42ba8db 100644
--- a/README.rst
+++ b/README.rst
@@ -1083,6 +1083,65 @@
 You can read more about it here:
     https://docs.openstack.org/security-guide/databases/database-access-control.html
 
+Upgrades
+========
+
+Each openstack formula provide set of phases (logical bloks) that will help to
+build flexible upgrade orchestration logic for particular components. The list
+of phases might and theirs descriptions are listed in table below:
+
++-------------------------------+------------------------------------------------------+
+| State                         | Description                                          |
++===============================+======================================================+
+| <app>.upgrade.service_running | Ensure that all services for particular application  |
+|                               | are enabled for autostart and running                |
++-------------------------------+------------------------------------------------------+
+| <app>.upgrade.service_stopped | Ensure that all services for particular application  |
+|                               | disabled for autostart and dead                      |
++-------------------------------+------------------------------------------------------+
+| <app>.upgrade.pkgs_latest     | Ensure that packages used by particular application  |
+|                               | are installed to latest available version.           |
+|                               | This will not upgrade data plane packages like qemu  |
+|                               | and openvswitch as usually minimal required version  |
+|                               | in openstack services is really old. The data plane  |
+|                               | packages should be upgraded separately by `apt-get   |
+|                               | upgrade` or `apt-get dist-upgrade`                   |
+|                               | Applying this state will not autostart service.      |
++-------------------------------+------------------------------------------------------+
+| <app>.upgrade.render_config   | Ensure configuration is rendered actual version.     +
++-------------------------------+------------------------------------------------------+
+| <app>.upgrade.pre             | We assume this state is applied on all nodes in the  |
+|                               | cloud before running upgrade.                        |
+|                               | Only non destructive actions will be applied during  |
+|                               | this phase. Perform service built in service check   |
+|                               | like (keystone-manage doctor and nova-status upgrade)|
++-------------------------------+------------------------------------------------------+
+| <app>.upgrade.upgrade.pre     | Mostly applicable for data plane nodes. During this  |
+|                               | phase resources will be gracefully removed from      |
+|                               | current node if it is allowed. Services for upgraded |
+|                               | application will be set to admin disabled state to   |
+|                               | make sure node will not participate in resources     |
+|                               | scheduling. For example on gtw nodes this will set   |
+|                               | all agents to admin disable state and will move all  |
+|                               | routers to other agents.                             |
++-------------------------------+------------------------------------------------------+
+| <app>.upgrade.upgrade         | This state will basically upgrade application on     |
+|                               | particular target. Stop services, render             |
+|                               | configuration, install new packages, run offline     |
+|                               | dbsync (for ctl), start services. Data plane should  |
+|                               | not be affected, only OpenStack python services.     |
++-------------------------------+------------------------------------------------------+
+| <app>.upgrade.upgrade.post    | Add services back to scheduling.                     |
++-------------------------------+------------------------------------------------------+
+| <app>.upgrade.post            | This phase should be launched only when upgrade of   |
+|                               | the cloud is completed. Cleanup temporary files,     |
+|                               | perform other post upgrade tasks.                    |
++-------------------------------+------------------------------------------------------+
+| <app>.upgrade.verify          | Here we will do basic health checks (API CRUD        |
+|                               | operations, verify do not have dead network          |
+|                               | agents/compute services)                             |
++-------------------------------+------------------------------------------------------+
+
 Documentation and Bugs
 ======================
 
diff --git a/nova/map.jinja b/nova/map.jinja
index 651293a..7e0006f 100644
--- a/nova/map.jinja
+++ b/nova/map.jinja
@@ -19,7 +19,7 @@
 
 {%- if grains.os_family == "Debian" %}
 {%- set pkgs_list = [ 'nova-common', 'nova-consoleproxy', 'novnc', 'nova-api', 'nova-conductor', 'nova-consoleauth', 'nova-doc', 'nova-scheduler', 'python-novaclient', 'python-memcache', 'gettext-base', 'python-pycadf'] %}
-{%- set services_list = ['nova-api', 'nova-consoleauth', 'nova-scheduler', 'nova-conductor', 'nova-novncproxy'] %}
+{%- set services_list = ['nova-conductor', 'nova-api', 'nova-consoleauth', 'nova-scheduler', 'nova-novncproxy'] %}
 {%- if pillar.nova.controller is defined and pillar.nova.controller.get('version',{}) in ["juno", "kilo", "liberty", "mitaka"] %}
 {%- do pkgs_list.append('nova-cert') %}
 {%- do services_list.append('nova-cert') %}
@@ -28,7 +28,7 @@
 
 {%- if grains.os_family == "RedHat" %}
 {%- set pkgs_list = ['openstack-nova-novncproxy', 'python-nova', 'openstack-nova-api', 'openstack-nova-console', 'openstack-nova-scheduler', 'python-novaclient', 'openstack-nova-common', 'openstack-nova-conductor', 'python-pycadf'] %}
-{%- set services_list = ['openstack-nova-api', 'openstack-nova-consoleauth', 'openstack-nova-scheduler', 'openstack-nova-conductor', 'openstack-nova-novncproxy'] %}
+{%- set services_list = ['openstack-nova-conductor', 'openstack-nova-api', 'openstack-nova-consoleauth', 'openstack-nova-scheduler', 'openstack-nova-novncproxy'] %}
 {%- if pillar.nova.controller is defined and pillar.nova.controller.get('version',{}) in ["juno", "kilo", "liberty", "mitaka", "newton", "ocata"] %}
 {%- do pkgs_list.append('openstack-nova-cert') %}
 {%- do services_list.append('openstack-nova-cert') %}
@@ -111,6 +111,40 @@
     },
 }, merge=pillar.nova.get('controller', {}), base='BaseDefaults') %}
 
+{% set upgrade = pillar.get('nova', {}).get('upgrade', {}) %}
+{% set pin_level = 'auto' %}
+
+{% set upgrade_levels = salt['grains.filter_by']({
+    'Debian': {
+        'upgrade_levels': {
+          'compute': pin_level,
+          'cells': pin_level,
+          'intercell': pin_level,
+          'cert': pin_level,
+          'scheduler': pin_level,
+          'conductor': pin_level,
+          'console': pin_level,
+          'consoleauth': pin_level,
+          'network': pin_level,
+          'baseapi': pin_level,
+        },
+     },
+    'RedHat': {
+        'upgrade_levels': {
+          'compute': pin_level,
+          'cells': pin_level,
+          'intercell': pin_level,
+          'cert': pin_level,
+          'scheduler': pin_level,
+          'conductor': pin_level,
+          'console': pin_level,
+          'consoleauth': pin_level,
+          'network': pin_level,
+          'baseapi': pin_level,
+        },
+    },
+}) %}
+
 {% set client = salt['grains.filter_by']({
     'Debian': {
         'pkgs': ['python-novaclient']
@@ -250,6 +284,11 @@
 {%- endload %}
 {% set compute = salt["grains.filter_by"](compute_defaults, merge=pillar.nova.get("compute", {}), base='BaseDefaults') %}
 
+{%- if pillar.nova.get('upgrade',{}).get('upgrade_enabled',False) %}
+  {% do compute.update(upgrade_levels) %}
+  {% do controller.update(upgrade_levels) %}
+{%- endif %}
+
 {% set monitoring = salt['grains.filter_by']({
     'default': {
         'disk': {
@@ -272,3 +311,4 @@
         'endpoint_failed_major_threshold': 0.5,
     },
 }, grain='os_family', merge=salt['pillar.get']('nova:monitoring')) %}
+
diff --git a/nova/meta/salt.yml b/nova/meta/salt.yml
index ab7047e..f5d1f33 100644
--- a/nova/meta/salt.yml
+++ b/nova/meta/salt.yml
@@ -9,3 +9,8 @@
     require:
     - salt: keystone.server
 
+orchestration:
+  upgrade:
+    applications:
+      nova:
+        priority: 1100
diff --git a/nova/upgrade/pkgs_latest.sls b/nova/upgrade/pkgs_latest.sls
new file mode 100644
index 0000000..2c702ad
--- /dev/null
+++ b/nova/upgrade/pkgs_latest.sls
@@ -0,0 +1,44 @@
+{%- from "nova/map.jinja" import controller, compute, client, upgrade with context %}
+
+nova_task_pkgs_latest:
+  test.show_notification:
+    - name: "dump_message_pkgs_latest"
+    - text: "Running nova.upgrade.pkg_latest"
+
+policy-rc.d_present:
+  file.managed:
+    - name: /usr/sbin/policy-rc.d
+    - mode: 755
+    - contents: |
+        #!/bin/sh
+        exit 101
+
+{%- set pkgs = [] %}
+{%- if compute.get('enabled', false) %}
+  {%- do pkgs.extend(compute.pkgs) %}
+{%- endif %}
+
+{%- if client.get('enabled', false) %}
+  {%- do pkgs.extend(client.pkgs) %}
+{%- endif %}
+
+{%- if controller.get('enabled', false) %}
+  {%- do pkgs.extend(controller.pkgs) %}
+
+{%- if controller.get('version',{}) not in ["juno", "kilo", "liberty", "mitaka", "newton"] %}
+  {%- do pkgs.append('nova-placement-api') %}
+{%- endif %}
+
+{%- endif %}
+
+nova_packages:
+  pkg.latest:
+  - names: {{ pkgs|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/nova/upgrade/post/init.sls b/nova/upgrade/post/init.sls
new file mode 100644
index 0000000..a29a06d
--- /dev/null
+++ b/nova/upgrade/post/init.sls
@@ -0,0 +1,21 @@
+{%- from "nova/map.jinja" import controller, compute, client, upgrade with context %}
+
+nova_post:
+  test.show_notification:
+    - name: "dump_message_post-upgrade"
+    - text: "Running nova.upgrade.post"
+
+{%- if controller.get('enabled') %}
+
+nova_status:
+  cmd.run:
+    - name: nova-status upgrade check
+  {%- if grains.get('noservices') or controller.get('role', 'primary') == 'secondary' %}
+    - onlyif: /bin/false
+  {%- endif %}
+
+{%- endif %}
+
+keystone_os_client_config_absent:
+  file.absent:
+    - name: /etc/openstack/clouds.yml
diff --git a/nova/upgrade/pre/init.sls b/nova/upgrade/pre/init.sls
new file mode 100644
index 0000000..9ed049a
--- /dev/null
+++ b/nova/upgrade/pre/init.sls
@@ -0,0 +1,44 @@
+{%- from "nova/map.jinja" import controller, compute, upgrade with context %}
+
+nova_pre:
+  test.show_notification:
+    - name: "dump_message_pre-upgrade_nova"
+    - text: "Running nova.upgrade.pre"
+
+{%- if controller.get('enabled', false) %}
+  {%- set _data = controller %}
+  {%- set type = 'controller' %}
+{%- elif compute.get('enabled', false) %}
+  {%- set _data = compute %}
+  {%- set type = 'compute' %}
+{%- endif %}
+
+/etc/nova/nova.conf:
+  file.managed:
+  - name: /etc/nova/nova.conf
+  - source: salt://nova/files/{{ _data.version }}/nova-{{ type }}.conf.{{ grains.os_family }}
+  - template: jinja
+
+{%- if controller.get('enabled') %}
+
+include:
+ - nova.db.online_sync
+
+nova_status:
+  cmd.run:
+    - name: nova-status upgrade check
+  {%- if grains.get('noservices') or controller.get('role', 'primary') == 'secondary' %}
+    - onlyif: /bin/false
+  {%- endif %}
+{%- endif %}
+
+{%- 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
+    - unless: test -f /etc/openstack/clouds.yml
diff --git a/nova/upgrade/render_config.sls b/nova/upgrade/render_config.sls
new file mode 100644
index 0000000..d963b42
--- /dev/null
+++ b/nova/upgrade/render_config.sls
@@ -0,0 +1,51 @@
+{%- from "nova/map.jinja" import controller, compute, client, upgrade with context %}
+
+nova_render_config:
+  test.show_notification:
+    - name: "dump_message_render_config_nova"
+    - text: "Running nova.upgrade.render_config"
+
+{%- if controller.get('enabled', false) %}
+  {%- set _data = controller %}
+  {%- set type = 'controller' %}
+{%- elif compute.get('enabled', false) %}
+  {%- set _data = compute %}
+  {%- set type = 'compute' %}
+{%- endif %}
+
+/etc/nova/nova.conf:
+  file.managed:
+  - source: salt://nova/files/{{ _data.version }}/nova-{{ type }}.conf.{{ grains.os_family }}
+  - template: jinja
+
+/etc/nova/api-paste.ini:
+  file.managed:
+  - source: salt://nova/files/{{ _data.version }}/api-paste.ini.{{ grains.os_family }}
+  - template: jinja
+
+{%- if controller.get('enabled', False) %}
+
+  {% for service_name in controller.services %}
+{{ service_name }}_default:
+  file.managed:
+    - name: /etc/default/{{ service_name }}
+    - source: salt://nova/files/default
+    - template: jinja
+    - defaults:
+        service_name: {{ service_name }}
+        values: {{ controller }}
+  {% endfor %}
+
+  {% if controller.get('policy', {}) and controller.version not in ['liberty', 'mitaka', 'newton'] %}
+{# nova no longer ships with a default policy.json #}
+
+/etc/nova/policy.json:
+  file.managed:
+    - contents: '{}'
+    - replace: False
+    - user: nova
+    - group: nova
+
+  {% endif %}
+
+{%- endif %}
diff --git a/nova/upgrade/service_running.sls b/nova/upgrade/service_running.sls
new file mode 100644
index 0000000..e2f2177
--- /dev/null
+++ b/nova/upgrade/service_running.sls
@@ -0,0 +1,33 @@
+{%- from "nova/map.jinja" import controller, compute, upgrade with context %}
+
+nova_task_service_running:
+  test.show_notification:
+    - name: "dump_message_service_running_nova"
+    - text: "Running nova.upgrade.service_running"
+
+{%- if controller.get('enabled', false) %}
+  {%- set _data = controller %}
+{%- elif compute.get('enabled', false) %}
+  {%- set _data = compute %}
+{%- endif %}
+
+{%- set nservices = [] %}
+{%- do nservices.extend(_data.services) %}
+
+{%- if controller.get('enabled') %}
+  {%- if controller.version not in ["juno", "kilo", "liberty", "mitaka", "newton"] %}
+    {%- do nservices.append('apache2') %}
+  {%- endif %}
+{%- endif %}
+
+{%- if nservices|unique|length > 0 %}
+  {%- for service in nservices|unique %}
+nova_service_running_{{ service }}:
+  service.running:
+  - enable: true
+  - name: {{ service }}
+  {%- if grains.get('noservices') %}
+  - onlyif: /bin/false
+  {%- endif %}
+  {%- endfor %}
+{%- endif %}
diff --git a/nova/upgrade/service_stopped.sls b/nova/upgrade/service_stopped.sls
new file mode 100644
index 0000000..ca5fd5e
--- /dev/null
+++ b/nova/upgrade/service_stopped.sls
@@ -0,0 +1,46 @@
+{%- from "nova/map.jinja" import controller, compute, upgrade with context %}
+
+nova_task_service_stopped:
+  test.show_notification:
+    - name: "dump_message_service_stopped_nova"
+    - text: "Running nova.upgrade.service_stopped"
+
+{%- if controller.get('enabled', false) %}
+  {%- set _data = controller %}
+{%- elif compute.get('enabled', false) %}
+  {%- set _data = compute %}
+{%- endif %}
+
+{%- set nservices = [] %}
+{%- do nservices.extend(_data.services) %}
+
+{%- if controller.get('enabled') %}
+
+  {%- if upgrade.get('upgrade_enabled',false) %}
+    {%- do controller.update({'version': upgrade.old_release}) %}
+  {%- endif %}
+
+  {%- if controller.version in ["juno", "kilo", "liberty", "mitaka"] %}
+    {%- do nservices.append('nova-cert') %}
+  {%- endif %}
+
+{%- endif %}
+
+{%- if controller.get('enabled') %}
+  {%- if controller.version not in ["juno", "kilo", "liberty", "mitaka", "newton"] %}
+    {%- do nservices.append('apache2') %}
+  {%- endif %}
+{%- endif %}
+
+{%- if nservices|unique|length > 0 %}
+{%- for service in nservices|unique %}
+nova_service_stopped_{{ service }}:
+  service.dead:
+  - enable: false
+  - name: {{ service }}
+  {%- if grains.get('noservices') %}
+  - onlyif: /bin/false
+  {%- endif %}
+{%- endfor %}
+{%- endif %}
+
diff --git a/nova/upgrade/upgrade/init.sls b/nova/upgrade/upgrade/init.sls
new file mode 100644
index 0000000..31f23f8
--- /dev/null
+++ b/nova/upgrade/upgrade/init.sls
@@ -0,0 +1,13 @@
+{%- from "nova/map.jinja" import controller, compute, compute_driver_mapping with context %}
+
+nova_upgrade:
+  test.show_notification:
+    - name: "dump_message_upgrade_nova"
+    - text: "Running nova.upgrade.upgrade"
+
+include:
+ - nova.upgrade.service_stopped
+ - nova.upgrade.pkgs_latest
+ - nova.upgrade.render_config
+ - nova.db.offline_sync
+ - nova.upgrade.service_running
diff --git a/nova/upgrade/upgrade/post.sls b/nova/upgrade/upgrade/post.sls
new file mode 100644
index 0000000..92dc005
--- /dev/null
+++ b/nova/upgrade/upgrade/post.sls
@@ -0,0 +1,7 @@
+{%- from "nova/map.jinja" import controller, compute, compute_driver_mapping with context %}
+
+nova_upgrade_post:
+  test.show_notification:
+    - name: "dump_message_upgrade_nova_post"
+    - text: "Running nova.upgrade.upgrade.post"
+
diff --git a/nova/upgrade/upgrade/pre.sls b/nova/upgrade/upgrade/pre.sls
new file mode 100644
index 0000000..31be4d5
--- /dev/null
+++ b/nova/upgrade/upgrade/pre.sls
@@ -0,0 +1,7 @@
+{%- from "nova/map.jinja" import controller, compute, compute_driver_mapping with context %}
+
+nova_upgrade_pre:
+  test.show_notification:
+    - name: "dump_message_upgrade_nova_pre"
+    - text: "Running nova.upgrade.upgrade.pre"
+
diff --git a/nova/upgrade/verify/_api.sls b/nova/upgrade/verify/_api.sls
new file mode 100644
index 0000000..0cf99fd
--- /dev/null
+++ b/nova/upgrade/verify/_api.sls
@@ -0,0 +1,48 @@
+{%- from "keystone/map.jinja" import client as kclient with context %}
+
+nova_upgrade_verify_api:
+  test.show_notification:
+    - name: "dump_message_verify_api_nova"
+    - text: "Running nova.upgrade.verify.api"
+
+{%- if kclient.enabled and kclient.get('os_client_config', {}).get('enabled', False)  %}
+  {%- set Nova_Test_Flavor_ID = 'random' | uuid %}
+
+novav21_flavor_create:
+  module.run:
+    - name: novav21.flavor_create
+    - kwargs:
+        cloud_name: admin_identity
+        id: {{ Nova_Test_Flavor_ID }}
+        name: {{ Nova_Test_Flavor_ID }}
+        vcpus: 2
+        ram: 1
+        disk: 2
+
+novav21_flavor_list:
+  module.run:
+    - name: novav21.flavor_list
+    - kwargs:
+        cloud_name: admin_identity
+
+novav21_quota_list:
+  module.run:
+    - name: novav21.quota_list
+    - kwargs:
+        cloud_name: admin_identity
+        project_id: default
+
+novav21_flavor_absent:
+  novav21.flavor_absent:
+  - cloud_name: admin_identity
+  - name: {{ Nova_Test_Flavor_ID }}
+  - require:
+    - novav21_flavor_create
+
+novav21_server_list:
+  module.run:
+    - name: novav21.server_list
+    - kwargs:
+        cloud_name: admin_identity
+
+{%- endif %}
diff --git a/nova/upgrade/verify/init.sls b/nova/upgrade/verify/init.sls
new file mode 100644
index 0000000..a377c18
--- /dev/null
+++ b/nova/upgrade/verify/init.sls
@@ -0,0 +1,7 @@
+nova_upgrade_verify:
+  test.show_notification:
+    - name: "dump_message_upgrade_nova_verify"
+    - text: "Running nova.upgrade.verify"
+
+include:
+ - nova.upgrade.verify._api