Initial commit with fixtures

- add fixtures for hardware and underlay
- add fuel-devops template tcpcloud-default.yaml

* Migration of fixtures is not finished yet
diff --git a/tcp_tests/helpers/env_config.py b/tcp_tests/helpers/env_config.py
new file mode 100644
index 0000000..3ad9a36
--- /dev/null
+++ b/tcp_tests/helpers/env_config.py
@@ -0,0 +1,318 @@
+#    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.
+
+# TODO(slebedev): implement unit tests
+
+import copy
+import json
+import re
+
+from devops.helpers import templates
+import yaml
+
+from tcp_tests.helpers import exceptions
+from tcp_tests import logger
+
+
+LOG = logger.logger
+
+
+class DevopsConfigMissingKey(KeyError):
+    def __init__(self, key, keypath):
+        super(DevopsConfigMissingKey, self).__init__()
+        self.key = key
+        self.keypath
+
+    def __str__(self):
+        return "Key '{0}' by keypath '{1}' is missing".format(
+            self.key,
+            self.keypath
+        )
+
+
+def fail_if_obj(x):
+    if not isinstance(x, int):
+        raise TypeError("Expecting int value!")
+
+
+def fix_devops_config(config):
+    """Function for get correct structure of config
+
+    :param config: dict
+    :returns: config dict
+    """
+    if not isinstance(config, dict):
+        raise exceptions.DevopsConfigTypeError(
+            type_name=type(config).__name__
+        )
+    if 'template' in config:
+        return copy.deepcopy(config)
+    else:
+        return {
+            "template": {
+                "devops_settings": copy.deepcopy(config)
+            }
+        }
+
+
+def list_update(obj, indexes, value):
+    """Procedure for setting value into list (nested too), need
+    in some functions where we are not able to set value directly.
+
+    e.g.: we want to change element in nested list.
+
+    obj = [12, 34, [3, 5, [0, 4], 3], 85]
+    list_update(obj, [2, 2, 1], 50) => obj[2][2][1] = 50
+    print(obj) => [12, 34, [3, 5, [0, 50], 3], 85]
+
+    :param obj: source list
+    :param indexes: list with indexes for recursive process
+    :param value: some value for setting
+    """
+    def check_obj(obj):
+        if not isinstance(obj, list):
+            raise TypeError("obj must be a list instance!")
+    check_obj(obj)
+    if len(indexes) > 0:
+        cur = obj
+        last_index = indexes[-1]
+        fail_if_obj(last_index)
+        for i in indexes[:-1]:
+            fail_if_obj(i)
+            check_obj(cur[i])
+            cur = cur[i]
+        cur[last_index] = value
+
+
+def return_obj(indexes=[]):
+    """Function returns dict() or list() object given nesting, it needs by
+    set_value_for_dict_by_keypath().
+
+    Examples:
+        return_obj() => {}
+        return_obj([0]) => [{}]
+        return_obj([-1]) => [{}]
+        return_obj([-1, 1, -2]) => [[None, [{}, None]]]
+        return_obj([2]) => [None, None, {}]
+        return_obj([1,3]) => [None, [None, None, None, {}]]
+    """
+    if not isinstance(indexes, list):
+        raise TypeError("indexes must be a list!")
+    if len(indexes) > 0:
+        # Create resulting initial object with 1 element
+        result = [None]
+        # And save it's ref
+        cur = result
+        # lambda for extending list elements
+        li = (lambda x: [None] * x)
+        # lambda for nesting of list
+        nesting = (lambda x: x if x >= 0 else abs(x) - 1)
+        # save last index
+        last_index = indexes[-1]
+        fail_if_obj(last_index)
+        # loop from first till penultimate elements of indexes
+        # we must create nesting list and set current position to
+        # element at next index in indexes list
+        for i in indexes[:-1]:
+            fail_if_obj(i)
+            cur.extend(li(nesting(i)))
+            cur[i] = [None]
+            cur = cur[i]
+        # Perform last index
+        cur.extend(li(nesting(last_index)))
+        cur[last_index] = {}
+        return result
+    else:
+        return dict()
+
+
+def keypath(paths):
+    """Function to make string keypath from list of paths"""
+    return ".".join(list(paths))
+
+
+def disassemble_path(path):
+    """Func for disassembling path into key and indexes list (if needed)
+
+    :param path: string
+    :returns: key string, indexes list
+    """
+    pattern = re.compile("\[([0-9]*)\]")
+    # find all indexes of possible list object in path
+    indexes = (lambda x: [int(r) for r in pattern.findall(x)]
+               if pattern.search(x) else [])
+    # get key
+    base_key = (lambda x: re.sub(pattern, '', x))
+    return base_key(path), indexes(path)
+
+
+def set_value_for_dict_by_keypath(source, paths, value, new_on_missing=True):
+    """Procedure for setting specific value by keypath in dict
+
+    :param source: dict
+    :param paths: string
+    :param value: value to set by keypath
+    """
+    paths = paths.lstrip(".").split(".")
+    walked_paths = []
+    # Store the last path
+    last_path = paths.pop()
+    data = source
+    # loop to go through dict
+    while len(paths) > 0:
+        path = paths.pop(0)
+        key, indexes = disassemble_path(path)
+        walked_paths.append(key)
+        if key not in data:
+            if new_on_missing:
+                # if object is missing, we create new one
+                data[key] = return_obj(indexes)
+            else:
+                raise DevopsConfigMissingKey(key, keypath(walked_paths[:-1]))
+
+        data = data[key]
+
+        # if we can not get element in list, we should
+        # throw an exception with walked path
+        for i in indexes:
+            try:
+                tmp = data[i]
+            except IndexError as err:
+                LOG.error(
+                    "Couldn't access {0} element of '{1}' keypath".format(
+                        i, keypath(walked_paths)
+                    )
+                )
+                LOG.error(
+                    "Dump of '{0}':\n{1}".format(
+                        keypath(walked_paths),
+                        json.dumps(data)
+                    )
+                )
+                raise type(err)(
+                    "Can't access '{0}' element of '{1}' object! "
+                    "'{2}' object found!".format(
+                        i,
+                        keypath(walked_paths),
+                        data
+                    )
+                )
+            data = tmp
+            walked_paths[-1] += "[{0}]".format(i)
+
+    key, indexes = disassemble_path(last_path)
+    i_count = len(indexes)
+    if key not in data:
+        if new_on_missing:
+            data[key] = return_obj(indexes)
+        else:
+            raise DevopsConfigMissingKey(key, keypath(walked_paths))
+    elif i_count > 0 and not isinstance(data[key], list):
+        raise TypeError(
+            ("Key '{0}' by '{1}' keypath expected as list "
+             "but '{3}' obj found").format(
+                 key, keypath(walked_paths), type(data[key]).__name__
+            )
+        )
+    if i_count == 0:
+        data[key] = value
+    else:
+        try:
+            list_update(data[key], indexes, value)
+        except (IndexError, TypeError) as err:
+            LOG.error(
+                "Error while setting by '{0}' key of '{1}' keypath".format(
+                    last_path,
+                    keypath(walked_paths)
+                )
+            )
+            LOG.error(
+                "Dump of object by '{0}' keypath:\n{1}".format(
+                    keypath(walked_paths),
+                    json.dumps(data)
+                )
+            )
+            raise type(err)(
+                "Couldn't set value by '{0}' key of '{1}' keypath'".format(
+                    last_path,
+                    keypath(walked_paths)
+                )
+            )
+
+
+class EnvironmentConfig(object):
+    def __init__(self):
+        super(EnvironmentConfig, self).__init__()
+        self._config = None
+
+    @property
+    def config(self):
+        return self._config
+
+    @config.setter
+    def config(self, config):
+        """Setter for config
+
+        :param config: dict
+        """
+        self._config = fix_devops_config(config)
+
+    def __getitem__(self, key):
+        if self._config is not None:
+            conf = self._config['template']['devops_settings']
+            return copy.deepcopy(conf.get(key, None))
+        else:
+            return None
+
+    @logger.logwrap
+    def set_value_by_keypath(self, keypath, value):
+        """Function for set value of devops settings by keypath.
+
+        It's forbidden to set value of self.config directly, so
+        it's possible simply set value by keypath
+        """
+        if self.config is None:
+            raise exceptions.DevopsConfigIsNone()
+        conf = self._config['template']['devops_settings']
+        set_value_for_dict_by_keypath(conf, keypath, value)
+
+    def save(self, filename):
+        """Dump current config into given file
+
+        :param filename: string
+        """
+        if self._config is None:
+            raise exceptions.DevopsConfigIsNone()
+        with open(filename, 'w') as f:
+            f.write(
+                yaml.dump(
+                    self._config, default_flow_style=False
+                )
+            )
+
+    def load_template(self, filename):
+        """Method for reading file with devops config
+
+        :param filename: string
+        """
+        if filename is not None:
+            LOG.debug(
+                "Preparing to load config from template '{0}'".format(
+                    filename
+                )
+            )
+            self.config = templates.yaml_template_load(filename)
+        else:
+            LOG.error("Template filename is not set, loading config " +
+                      "from template aborted.")