Move installation steps to YAML filex

- move installation steps to YAML files
- add fixtures and snapshots for install salt, common services
  and openstack
- fixtures in conftest.py now are included by python instead of
  using pytest plugins
diff --git a/tcp_tests/managers/common_services_manager.py b/tcp_tests/managers/common_services_manager.py
new file mode 100644
index 0000000..add159f
--- /dev/null
+++ b/tcp_tests/managers/common_services_manager.py
@@ -0,0 +1,28 @@
+#    Copyright 2016 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.
+
+class CommonServicesManager(object):
+    """docstring for CommonServicesManager"""
+
+    __config = None
+    __underlay = None
+
+    def __init__(self, config, underlay):
+        self.__config = config
+        self.__underlay = underlay
+        super(CommonServicesManager, self).__init__()
+
+    def install(self, commands):
+        self.__underlay.execute_commands(commands)
+        self.__config.common_services.installed = True
diff --git a/tcp_tests/managers/openstack_manager.py b/tcp_tests/managers/openstack_manager.py
new file mode 100644
index 0000000..9727ac4
--- /dev/null
+++ b/tcp_tests/managers/openstack_manager.py
@@ -0,0 +1,28 @@
+#    Copyright 2016 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.
+
+class OpenstackManager(object):
+    """docstring for OpenstackManager"""
+
+    __config = None
+    __underlay = None
+
+    def __init__(self, config, underlay):
+        self.__config = config
+        self.__underlay = underlay
+        super(OpenstackManager, self).__init__()
+
+    def install(self, commands):
+        self.__underlay.execute_commands(commands)
+        self.__config.openstack.installed = True
diff --git a/tcp_tests/managers/saltmanager.py b/tcp_tests/managers/saltmanager.py
new file mode 100644
index 0000000..2e539bc
--- /dev/null
+++ b/tcp_tests/managers/saltmanager.py
@@ -0,0 +1,35 @@
+#    Copyright 2016 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.
+
+class SaltManager(object):
+    """docstring for SaltManager"""
+
+    __config = None
+    __underlay = None
+
+    def __init__(self, config, underlay):
+        self.__config = config
+        self.__underlay = underlay
+
+
+        super(SaltManager, self).__init__()
+
+    def install(self, commands):
+        if self.__config.salt.salt_master_host == '0.0.0.0':
+            # Temporary workaround. Underlay should be extended with roles
+            salt_nodes = self.__underlay.node_names()
+            self.__config.salt.salt_master_host = \
+                self.__underlay.host_by_node_name(salt_nodes[0])
+
+        self.__underlay.execute_commands(commands)
diff --git a/tcp_tests/managers/tcpmanager.py b/tcp_tests/managers/tcpmanager.py
deleted file mode 100644
index c71bf89..0000000
--- a/tcp_tests/managers/tcpmanager.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#    Copyright 2016 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 copy
-import os
-
-import yaml
-
-from devops.helpers import helpers
-
-from tcp_tests.helpers import exceptions
-from tcp_tests import logger
-from tcp_tests import settings
-
-LOG = logger.logger
-
-
-class TCPManager(object):
-    """docstring for TCPManager"""
-
-    __config = None
-    __underlay = None
-
-    def __init__(self, config, underlay):
-        self.__config = config
-        self.__underlay = underlay
-        self._api_client = None
-
-        if self.__config.tcp.tcp_host == '0.0.0.0':
-            # Temporary workaround. Underlay should be extended with roles
-            tcp_nodes = self.__underlay.node_names()
-            self.__config.tcp.tcp_host = \
-                self.__underlay.host_by_node_name(tcp_nodes[0])
-
-        super(TCPManager, self).__init__()
-
-    def show_tcp_config(self):
-        cmd = 'reclass -n {0}'.format(self.__underlay.node_names()[0])
-        self.__underlay.sudo_check_call(cmd, host=self.__config.tcp.tcp_host,
-                                        verbose=True)
-
-    def install_tcp(self):
-        raise Exception("Not implemented!")
-
-    def check_salt_service(self, service_name, node_name, check_cmd,
-                           state_running='start/running'):
-        cmd = "service {0} status | grep -q '{1}'".format(
-            service_name, state_running)
-        with self.__underlay.remote(node_name=node_name) as remote:
-            result = remote.execute(cmd)
-            if result.exit_code != 0:
-                LOG.info("{0} is not in running state on the node {1},"
-                         " restarting".format(service_name, node_name))
-                cmd = ("service {0} stop;"
-                       " sleep 3; killall -9 {0};"
-                       "service {0} start; sleep 5;"
-                       .format(service_name))
-                remote.execute(cmd)
-
-                remote.execute(check_cmd)
-                remote.execute(check_cmd)
diff --git a/tcp_tests/managers/underlay_ssh_manager.py b/tcp_tests/managers/underlay_ssh_manager.py
index 578768a..90f3924 100644
--- a/tcp_tests/managers/underlay_ssh_manager.py
+++ b/tcp_tests/managers/underlay_ssh_manager.py
@@ -13,6 +13,7 @@
 #    under the License.
 
 import random
+import time
 
 from devops.helpers import helpers
 from devops.helpers import ssh_client
@@ -363,3 +364,66 @@
             username=ssh_data['login'],
             password=ssh_data['password'],
             private_keys=ssh_data['keys'])
+
+    def ensure_running_service(self, service_name, node_name, check_cmd,
+                               state_running='start/running'):
+        cmd = "service {0} status | grep -q '{1}'".format(
+            service_name, state_running)
+        with self.remote(node_name=node_name) as remote:
+            result = remote.execute(cmd)
+            if result.exit_code != 0:
+                LOG.info("{0} is not in running state on the node {1},"
+                         " restarting".format(service_name, node_name))
+                cmd = ("service {0} stop;"
+                       " sleep 3; killall -9 {0};"
+                       "service {0} start; sleep 5;"
+                       .format(service_name))
+                remote.execute(cmd)
+
+                remote.execute(check_cmd)
+                remote.execute(check_cmd)
+
+    def execute_commands(self, commands):
+        for n, step in enumerate(commands):
+            LOG.info(" ####################################################")
+            LOG.info(" *** [ Command #{0} ] {1} ***"
+                     .format(n+1, step['description']))
+
+            with self.remote(node_name=step['node_name']) as remote:
+                for x in range(step['retry']['count'], 0, -1):
+                    time.sleep(3)
+                    result = remote.execute(step['cmd'], verbose=True)
+
+                    # Workaround of exit code 0 from salt in case of failures
+                    failed = 0
+                    for s in result['stdout']:
+                        if s.startswith("Failed:"):
+                            failed += int(s.split("Failed:")[1])
+
+                    if result.exit_code != 0:
+                        time.sleep(step['retry']['delay'])
+                        LOG.info(" === RETRY ({0}/{1}) ========================="
+                                 .format(x-1, step['retry']['count']))
+                    elif failed != 0:
+                        LOG.error(" === SALT returned exit code = 0 while "
+                                  "there are failed modules! ===")
+                        LOG.info(" === RETRY ({0}/{1}) ======================="
+                                 .format(x-1, step['retry']['count']))
+                    else:
+                        # Workarounds for crashed services
+                        self.ensure_running_service(
+                            "salt-master",
+                            "cfg01.mk22-lab-advanced.local",
+                            "salt-call pillar.items",
+                            'active (running)') # Hardcoded for now
+                        self.ensure_running_service(
+                            "salt-minion",
+                            "cfg01.mk22-lab-advanced.local",
+                            "salt 'cfg01*' pillar.items",
+                            "active (running)") # Hardcoded for now
+                        break
+
+                    if x == 1 and step['skip_fail'] == False:
+                        # In the last retry iteration, raise an exception
+                        raise Exception("Step '{0}' failed"
+                                        .format(step['description']))