Adjust Ironic formula granularity

Change-Id: I9073326380278c71573f102d3b278be6c745696e
Related-PROD: PROD-21936
diff --git a/README.rst b/README.rst
index 4d76b85..218c333 100644
--- a/README.rst
+++ b/README.rst
@@ -229,3 +229,69 @@
           pool_size: 5
           sleep_time: 10
           timeout: 15
+
+Upgrades
+========
+
+Each openstack formula provide set of phases (logical blocks) that will help to
+build flexible upgrade orchestration logic for particular components. The list
+of phases 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)                             |
++-------------------------------+------------------------------------------------------+
+
+Upgrade pillar example:
+ironic:
+  upgrade:
+    enabled: True
+    old_release: pike
+    new_release: queens
diff --git a/ironic/db/offline_sync.sls b/ironic/db/offline_sync.sls
index 5008575..71c5498 100644
--- a/ironic/db/offline_sync.sls
+++ b/ironic/db/offline_sync.sls
@@ -1,8 +1,11 @@
 {%- from "ironic/map.jinja" import api with context %}
 
+{%- set should_run = '/bin/false' %}
+{%- if not grains.get('noservices') or api.get('role', 'secondary') == 'primary' %}
+{%- set should_run = '/bin/true' %}
+{%- endif %}
+
 ironic_install_database:
   cmd.run:
   - name: ironic-dbsync --config-file /etc/ironic/ironic.conf upgrade
-    {%- if grains.get('noservices') or ( api.api_type in ["deploy"] and api.get('role', 'primary') == 'secondary' ) %}
-  - onlyif: /bin/false
-    {%- endif %}
+  - onlyif: {{ should_run }}
diff --git a/ironic/db/online_sync.sls b/ironic/db/online_sync.sls
index 9008da9..14e0d9f 100644
--- a/ironic/db/online_sync.sls
+++ b/ironic/db/online_sync.sls
@@ -1,8 +1,12 @@
 {%- from "ironic/map.jinja" import api with context %}
 
+{%- set should_run = '/bin/false' %}
+{%- if not grains.get('noservices') and api.version not in ["juno", "kilo", "liberty", 'ocata'] and api.api_type not in ["deploy"] and api.get('role', 'primary') == 'primary' %}
+{%- set should_run = '/bin/true' %}
+{%- endif %}
+
 ironic_online_data_migrations:
   cmd.run:
   - name: ironic-dbsync online_data_migrations
-    {%- if grains.get('noservices') or ( api.api_type in ["deploy"] and api.get('role', 'primary') == 'secondary' ) %}
-  - onlyif: /bin/false
-    {%- endif %}
+  - onlyif: {{ should_run }}
+  - runas: 'ironic'
diff --git a/ironic/meta/salt.yml b/ironic/meta/salt.yml
new file mode 100644
index 0000000..3e358ec
--- /dev/null
+++ b/ironic/meta/salt.yml
@@ -0,0 +1,5 @@
+orchestration:
+  upgrade:
+    applications:
+      ironic:
+        priority: 1070
diff --git a/ironic/upgrade/pkgs_latest.sls b/ironic/upgrade/pkgs_latest.sls
new file mode 100644
index 0000000..bc9d8ee
--- /dev/null
+++ b/ironic/upgrade/pkgs_latest.sls
@@ -0,0 +1,37 @@
+{%- from "ironic/map.jinja" import api,conductor,client with context %}
+
+ironic_task_pkg_latest:
+  test.show_notification:
+    - text: "Running ironic.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 api.get('enabled', False) %}
+  {%- do pkgs.extend(api.pkgs) %}
+{%- endif %}
+{%- if conductor.get('enabled', False) %}
+  {%- do pkgs.extend(['ironic-conductor']) %}
+{%- endif %}
+{%- if client.get('enabled', False) %}
+  {%- do pkgs.extend(client.pkgs) %}
+{%- endif %}
+
+ironic_pkg_latest:
+  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/ironic/upgrade/post/init.sls b/ironic/upgrade/post/init.sls
new file mode 100644
index 0000000..1a3737c
--- /dev/null
+++ b/ironic/upgrade/post/init.sls
@@ -0,0 +1,8 @@
+ironic_post:
+  test.show_notification:
+    - text: "Running ironic.upgrade.post"
+
+include:
+ - ironic.upgrade.render_config
+ - ironic.upgrade.service_stopped
+ - ironic.upgrade.service_running
diff --git a/ironic/upgrade/pre/init.sls b/ironic/upgrade/pre/init.sls
new file mode 100644
index 0000000..59c7e14
--- /dev/null
+++ b/ironic/upgrade/pre/init.sls
@@ -0,0 +1,71 @@
+{%- from "ironic/map.jinja" import api,conductor, client with context %}
+
+{%- if api.get("enabled", False) %}
+  {%- set ironic, service_name = api, 'api' %}
+{%- elif conductor.get('enabled', False) %}
+  {%- set ironic, service_name = conductor, 'conductor' %}
+{%- endif %}
+
+ironic_pre:
+  test.show_notification:
+    - text: "Running ironic.upgrade.pre"
+
+/etc/ironic/ironic.conf:
+  file.managed:
+  - source: salt://ironic/files/{{ ironic.version }}/ironic.conf
+  - template: jinja
+
+include:
+ - ironic.db.online_sync
+
+{%- 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
+
+{%- if conductor.get('enabled', false) %}
+  {%- if conductor.http_images is defined %}
+    {%- for image in conductor.http_images %}
+
+image_{{ image.name }}:
+  file.managed:
+    - name: {{ conductor.http_root }}/{{ image.name }}
+    - source: {{ image.source }}
+    {%- if image.md5summ is defined %}
+    - source_hash: md5={{ image.md5summ }}
+    {%- else %}
+    - source_hash: {{ image.hash_file }}
+    {%- endif %}
+    - user: 'ironic'
+    - group: 'ironic'
+    {%- endfor %}
+  {%- endif %}
+  {%- if client.get('enabled', false) %}
+    {%- for identity_name, nodes in client.nodes.iteritems() %}
+      {%- for node in nodes %}
+node_{{ node.name }}_present:
+  ironicv1.node_present:
+    - name: {{ node.name }}
+    - driver: {{ node.driver }}
+    - driver_info: {{ node.driver_info|default({}) }}
+    - cloud_name: {{ client.cloud_name }}
+    - properties: {{ node.properties|default({}) }}
+    {%- if node.network_interface is defined %}
+    - network_interface: {{ node.network_interface }}
+    {%- endif %}
+    {%- if node.storage_interface is defined %}
+    - storage_interface: {{ node.storage_interface }}
+    {%- endif %}
+    {%- if node.microversion is defined %}
+    - microversion: "{{ node.microversion }}"
+    {%- endif %}
+      {%- endfor %} # end for nodes
+    {%- endfor %} # end client.nodes.iteritems
+  {%- endif %}
+{%- endif %}
diff --git a/ironic/upgrade/render_config.sls b/ironic/upgrade/render_config.sls
new file mode 100644
index 0000000..d8ddac4
--- /dev/null
+++ b/ironic/upgrade/render_config.sls
@@ -0,0 +1,16 @@
+{%- from "ironic/map.jinja" import api,conductor with context %}
+
+{%- if api.get("enabled", False) %}
+  {%- set ironic, service_name = api, 'api' %}
+{%- elif conductor.get('enabled', False) %}
+  {%- set ironic, service_name = conductor, 'conductor' %}
+{%- endif %}
+
+ironic_render_config:
+  test.show_notification:
+    - text: "Running ironic.upgrade.render_config"
+
+/etc/ironic/ironic.conf:
+  file.managed:
+  - source: salt://ironic/files/{{ ironic.version }}/ironic.conf
+  - template: jinja
diff --git a/ironic/upgrade/service_running.sls b/ironic/upgrade/service_running.sls
new file mode 100644
index 0000000..b965cf3
--- /dev/null
+++ b/ironic/upgrade/service_running.sls
@@ -0,0 +1,19 @@
+{%- from "ironic/map.jinja" import api,conductor with context %}
+
+ironic_task_service_running:
+  test.show_notification:
+    - text: "Running ironic.upgrade.service_running"
+
+{%- if api.get('enabled', False) %}
+ironic_start_{{ api.service }}:
+  service.running:
+    - name: {{ api.service }}
+    - enable: True
+{%- endif %}
+
+{%- if conductor.get('enabled', False) %}
+ironic_start_{{ conductor.service }}:
+  service.running:
+    - name: {{ conductor.service }}
+    - enable: True
+{%- endif %}
diff --git a/ironic/upgrade/service_stopped.sls b/ironic/upgrade/service_stopped.sls
new file mode 100644
index 0000000..f0a8a18
--- /dev/null
+++ b/ironic/upgrade/service_stopped.sls
@@ -0,0 +1,19 @@
+{%- from "ironic/map.jinja" import api,conductor with context %}
+
+ironic_task_service_stopped:
+  test.show_notification:
+    - text: "Running ironic.upgrade.service_stopped"
+
+{%- if api.get('enabled', False) %}
+ironic_stop_{{ api.service }}:
+  service.dead:
+    - name: {{ api.service }}
+    - enable: False
+{%- endif %}
+
+{%- if conductor.get('enabled', False) %}
+ironic_stop_{{ conductor.service }}:
+  service.dead:
+    - name: {{ conductor.service }}
+    - enable: False
+{%- endif %}
diff --git a/ironic/upgrade/upgrade/init.sls b/ironic/upgrade/upgrade/init.sls
new file mode 100644
index 0000000..deb652b
--- /dev/null
+++ b/ironic/upgrade/upgrade/init.sls
@@ -0,0 +1,10 @@
+{%- from "ironic/map.jinja" import api with context %}
+
+include:
+ - ironic.upgrade.service_stopped
+ - ironic.upgrade.pkgs_latest
+ - ironic.upgrade.render_config
+{%- if api.get('enabled', False) %}
+ - ironic.db.offline_sync
+{%- endif %}
+ - ironic.upgrade.service_running
diff --git a/ironic/upgrade/upgrade/post.sls b/ironic/upgrade/upgrade/post.sls
new file mode 100644
index 0000000..48b7618
--- /dev/null
+++ b/ironic/upgrade/upgrade/post.sls
@@ -0,0 +1,4 @@
+ironic_upgrade_uprade_post:
+  test.show_notification:
+    - name: "dump_message_upgrade_ironic_post"
+    - text: "Running ironic.upgrade.upgrade.post"
diff --git a/ironic/upgrade/upgrade/pre.sls b/ironic/upgrade/upgrade/pre.sls
new file mode 100644
index 0000000..6d43585
--- /dev/null
+++ b/ironic/upgrade/upgrade/pre.sls
@@ -0,0 +1,4 @@
+ironic_upgrade_upgrade_pre:
+  test.show_notification:
+     - name: "dump_message_upgrade_ironic_pre"
+     - text: "Running ironic.upgrade.upgrade.pre"
diff --git a/ironic/upgrade/verify/_api.sls b/ironic/upgrade/verify/_api.sls
new file mode 100644
index 0000000..0628188
--- /dev/null
+++ b/ironic/upgrade/verify/_api.sls
@@ -0,0 +1,36 @@
+{%- from "ironic/map.jinja" import api with context %}
+{%- set Ironic_Test_Node_ID = salt['cmd.run']('cat /proc/sys/kernel/random/uuid') %}
+
+ironic_upgrade_verify_api:
+  test.show_notification:
+    - text: "Running ironic.upgrade.verify.api"
+
+{%- if api.api_type not in ["deploy"] and api.get('role', 'secondary') == 'primary' %}
+ironicv1_node_present_update:
+  ironicv1.node_present:
+  - name: test
+  - cloud_name: admin_identity
+  - driver: fake
+  - uuid: {{ Ironic_Test_Node_ID }}
+  - extra:
+      a: b
+  - microversion: "1.34"
+
+ironicv1_port_present:
+  ironicv1.port_present:
+  - cloud_name: admin_identity
+  - node: test
+  - address: "11:11:11:11:11:11"
+
+ironicv1_port_absent:
+  ironicv1.port_absent:
+  - cloud_name: admin_identity
+  - node: test
+  - address: "11:11:11:11:11:11"
+
+ironicv1_node_absent:
+  ironicv1.node_absent:
+  - cloud_name: admin_identity
+  - name: test
+
+{%- endif %}
diff --git a/ironic/upgrade/verify/init.sls b/ironic/upgrade/verify/init.sls
new file mode 100644
index 0000000..1f15e84
--- /dev/null
+++ b/ironic/upgrade/verify/init.sls
@@ -0,0 +1,2 @@
+include:
+  - ironic.upgrade.verify._api