Merge "Add opecontrail.upgade.verify state"
diff --git a/README.rst b/README.rst
index 827ddc1..f90f2fb 100644
--- a/README.rst
+++ b/README.rst
@@ -1349,6 +1349,29 @@
         tcp_tw_recycle: 0
         ....
 
+Define extra states for contrail services health check
+------------------------------------------------------
+
+Service health check procedure verifies that all available contrail services are in ``active``
+state.
+Additional states could be defined for every service as expected states for validation procedure.
+
+.. code-block:: yaml
+
+    config:
+      ....
+      services_extra_states:
+        contrail-schema:
+          - backup
+        contrail-device-manager
+          - backup
+        contrail-svc-monitor:
+          - backup
+      ....
+
+``contrail-schema``, ``contrail-device-manager`` and ``contrail-svc-monitor`` config services already
+have additional ``backup`` state by default.
+
 Usage
 =====
 
diff --git a/_modules/contrail_health.py b/_modules/contrail_health.py
new file mode 100644
index 0000000..59530e6
--- /dev/null
+++ b/_modules/contrail_health.py
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+# Copyright 2018 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import os
+import subprocess
+
+
+def __virtual__():
+    '''
+    Only load this module if contrail-status or doctrail utility
+    (in case of containerized contrail version) is available.
+    '''
+    if _is_cmd_available('contrail-status') or _is_cmd_available('doctrail'):
+        return 'contrail_health'
+    return False
+
+
+def _is_cmd_available(cmd_name):
+    try:
+        with open(os.devnull) as devnull:
+            subprocess.Popen(
+                [cmd_name], stdout=devnull, stderr=devnull
+            ).communicate()
+    except OSError as e:
+        if e.errno == os.errno.ENOENT:
+            return False
+    return True
+
+
+def get_services_status():
+
+    if _is_cmd_available('contrail-status'):
+        status_cmd_list = ['contrail-status']
+    else:
+        status_cmd_list = ['doctrail', 'all', 'contrail-status']
+
+    cs_out = str(subprocess.check_output(status_cmd_list))
+    status_map = {}
+
+    for line in cs_out.split('\n'):
+        line_list = line.split()
+        if (not line.startswith("==") and "FOR NODE" not in line and
+                len(line_list) >= 2):
+            status_map[line_list[0].split(":")[0]] = line_list[1]
+
+    return status_map
diff --git a/_states/contrail_health.py b/_states/contrail_health.py
new file mode 100644
index 0000000..fdd1f48
--- /dev/null
+++ b/_states/contrail_health.py
@@ -0,0 +1,58 @@
+#!/usr/bin/python
+# Copyright 2018 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+'''
+Verification of Contrail services states
+'''
+
+
+def __virtual__():
+    '''
+    Load Contrail Health state module
+    '''
+    return 'contrail_health'
+
+
+def services_health(name, healthy_states=None, extra_states_map=None):
+    services_states = __salt__['contrail_health.get_services_status']()
+
+    healthy_states = healthy_states or ['active']
+    extra_states_map = extra_states_map or {}
+
+    if services_states:
+
+        nonhealthy_services = []
+
+        for service_name, state in services_states.items():
+            if (state not in healthy_states and
+                    state not in extra_states_map.get(service_name, [])):
+                nonhealthy_services.append(service_name)
+
+        if nonhealthy_services:
+            comment = ("The following services didn't pass health check:\n" +
+                       "\n".join(nonhealthy_services))
+            result = False
+        else:
+            comment = ("All contrail services have health state:\n" +
+                       "\n".join(services_states.keys()))
+            result = True
+
+    else:
+        comment = ("Contrail services were not found.\n"
+                   "If contrail services are working inside container(s) "
+                   "(starting from version 4.0), please check "
+                   "that related container(s) is (are) started.")
+        result = False
+
+    return {'name': name, 'changes': {}, 'comment': comment, 'result': result}
diff --git a/metadata/service/control/control.yml b/metadata/service/control/control.yml
index c3bb5d1..7cb3521 100644
--- a/metadata/service/control/control.yml
+++ b/metadata/service/control/control.yml
@@ -68,6 +68,13 @@
         id: 2
       - host: ${_param:cluster_node03_address}
         id: 3
+      services_extra_states:
+        contrail-schema:
+        - backup
+        contrail-svc-monitor:
+        - backup
+        contrail-device-manager:
+        - backup
     control:
       version: ${_param:opencontrail_version}
       enabled: true
diff --git a/opencontrail/upgrade/verify/_service.sls b/opencontrail/upgrade/verify/_service.sls
new file mode 100644
index 0000000..9151bd1
--- /dev/null
+++ b/opencontrail/upgrade/verify/_service.sls
@@ -0,0 +1,18 @@
+{%- from "opencontrail/map.jinja" import control, config, collector, web, database, compute with context %}
+
+{%- set extra_states_map = {} %}
+
+{%- for data in [control, config, collector, web, database, compute] %}
+
+{%- if data.get('enabled', False) %}
+  {%- if data.get('services_extra_states', False) %}
+    {% do extra_states_map.update(data.get('services_extra_states')) %}
+  {%- endif %}
+{%- endif %}
+
+{%- endfor %}
+
+contrail_services_health_check:
+  contrail_health.services_health:
+    - healthy_states: ['active']
+    - extra_states_map: {{ extra_states_map }}
diff --git a/opencontrail/upgrade/verify/init.sls b/opencontrail/upgrade/verify/init.sls
new file mode 100644
index 0000000..c40bb57
--- /dev/null
+++ b/opencontrail/upgrade/verify/init.sls
@@ -0,0 +1,2 @@
+include:
+- opencontrail.upgrade.verify._service