Merge "Add test suite for scenario "opnfv_os-ovn-nofeature-ha""
[yardstick.git] / yardstick / common / ansible_common.py
index 0cafa97..dee7044 100644 (file)
@@ -12,8 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from __future__ import absolute_import
-
 import cgitb
 import collections
 import contextlib as cl
@@ -23,17 +21,18 @@ import os
 from collections import Mapping, MutableMapping, Iterable, Callable, deque
 from functools import partial
 from itertools import chain
-from subprocess import CalledProcessError, Popen, PIPE
-from tempfile import NamedTemporaryFile
+import subprocess
+import tempfile
 
 import six
-import six.moves.configparser as ConfigParser
+from six.moves import configparser
 import yaml
 from six import StringIO
 from chainmap import ChainMap
+from oslo_serialization import jsonutils
 
 from yardstick.common.utils import Timer
-
+from yardstick.common import constants as consts
 
 cgitb.enable(format="text")
 
@@ -133,10 +132,9 @@ class CustomTemporaryFile(object):
         else:
             self.data_types = self.DEFAULT_DATA_TYPES
         # must open "w+" so unicode is encoded correctly
-        self.creator = partial(NamedTemporaryFile, mode="w+", delete=False,
-                               dir=directory,
-                               prefix=prefix,
-                               suffix=self.suffix)
+        self.creator = partial(
+            tempfile.NamedTemporaryFile, mode="w+", delete=False,
+            dir=directory, prefix=prefix, suffix=self.suffix)
 
     def make_context(self, data, write_func, descriptor='data'):
         return TempfileContext(data, write_func, descriptor, self.data_types,
@@ -190,8 +188,8 @@ class FileNameGenerator(object):
         if not prefix.endswith('_'):
             prefix += '_'
 
-        temp_file = NamedTemporaryFile(delete=False, dir=directory,
-                                       prefix=prefix, suffix=suffix)
+        temp_file = tempfile.NamedTemporaryFile(delete=False, dir=directory,
+                                                prefix=prefix, suffix=suffix)
         with cl.closing(temp_file):
             return temp_file.name
 
@@ -298,8 +296,9 @@ class AnsibleNode(MutableMapping):
     def gen_inventory_line(self):
         inventory_params = self.get_inventory_params()
         # use format to convert ints
+        # sort to ensure consistent key value ordering
         formatted_args = (u"{}={}".format(*entry) for entry in
-                          inventory_params.items())
+                          sorted(inventory_params.items()))
         line = u" ".join(chain([self['name']], formatted_args))
         return line
 
@@ -434,6 +433,7 @@ class AnsibleCommon(object):
         ansible_dict = dict(os.environ, **{
             "ANSIBLE_LOG_PATH": os.path.join(directory, log_file),
             "ANSIBLE_LOG_BASE": directory,
+            "ANSIBLE_ROLES_PATH": consts.ANSIBLE_ROLES_PATH,
             # # required for SSH to work
             # "ANSIBLE_SSH_ARGS": "-o UserKnownHostsFile=/dev/null "
             #                     "-o GSSAPIAuthentication=no "
@@ -471,7 +471,9 @@ class AnsibleCommon(object):
 
         prefix = '_'.join([self.prefix, prefix, 'inventory'])
         ini_temp_file = IniMapTemporaryFile(directory=directory, prefix=prefix)
-        inventory_config = ConfigParser.ConfigParser(allow_no_value=True)
+        inventory_config = configparser.ConfigParser(allow_no_value=True)
+        # disable default lowercasing
+        inventory_config.optionxform = str
         return ini_temp_file.make_context(self.inventory_dict, write_func,
                                           descriptor='inventory')
 
@@ -504,6 +506,58 @@ class AnsibleCommon(object):
             timeout = 1200.0
         return timeout
 
+    def _generate_ansible_cfg(self, directory):
+        parser = configparser.ConfigParser()
+        parser.add_section('defaults')
+        parser.set('defaults', 'host_key_checking', 'False')
+
+        cfg_path = os.path.join(directory, 'ansible.cfg')
+        with open(cfg_path, 'w') as f:
+            parser.write(f)
+
+    def get_sut_info(self, directory, sut_dir='sut'):
+        if not os.path.isdir(directory):
+            raise OSError('No such directory: %s' % directory)
+
+        self._generate_ansible_cfg(directory)
+
+        prefix = 'tmp'
+        self.gen_inventory_ini_dict()
+        ini_file = self._gen_ansible_inventory_file(directory, prefix=prefix)
+        with ini_file as f:
+            inventory_path = str(f)
+
+        self._exec_get_sut_info_cmd(directory, inventory_path, sut_dir)
+
+        sut_dir = os.path.join(directory, sut_dir)
+        sut_info = self._gen_sut_info_dict(sut_dir)
+
+        return sut_info
+
+    def _exec_get_sut_info_cmd(self, directory, inventory_path, sut_dir):
+        cmd = ['ansible', 'all', '-m', 'setup', '-i',
+               inventory_path, '--tree', sut_dir]
+
+        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=directory)
+        output, _ = proc.communicate()
+        retcode = proc.wait()
+        LOG.debug("exit status = %s", retcode)
+        if retcode != 0:
+            raise subprocess.CalledProcessError(retcode, cmd, output)
+
+    def _gen_sut_info_dict(self, sut_dir):
+        sut_info = {}
+
+        if os.path.isdir(sut_dir):
+            root, _, files = next(os.walk(sut_dir))
+            for filename in files:
+                abs_path = os.path.join(root, filename)
+                with open(abs_path) as f:
+                    data = jsonutils.load(f)
+                sut_info[filename] = data
+
+        return sut_info
+
     def execute_ansible(self, playbooks, directory, timeout=None,
                         extra_vars=None, ansible_check=False, prefix='tmp',
                         verbose=False):
@@ -513,7 +567,7 @@ class AnsibleCommon(object):
         #  playbook dir: use include to point to files in  consts.ANSIBLE_DIR
 
         if not os.path.isdir(directory):
-            raise OSError("Not a directory, %s", directory)
+            raise OSError("Not a directory, %s" % directory)
         timeout = self.get_timeout(timeout, self.default_timeout)
 
         self.counter += 1
@@ -560,12 +614,13 @@ class AnsibleCommon(object):
                     # 'timeout': timeout / 2,
                 })
                 with Timer() as timer:
-                    proc = Popen(cmd, stdout=PIPE, **exec_args)
+                    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                                            **exec_args)
                     output, _ = proc.communicate()
                     retcode = proc.wait()
                 LOG.debug("exit status = %s", retcode)
                 if retcode != 0:
-                    raise CalledProcessError(retcode, cmd, output)
+                    raise subprocess.CalledProcessError(retcode, cmd, output)
                 timeout -= timer.total_seconds()
 
             cmd.remove("--syntax-check")
@@ -575,10 +630,10 @@ class AnsibleCommon(object):
                 # TODO: add timeout support of use subprocess32 backport
                 # 'timeout': timeout,
             })
-            proc = Popen(cmd, stdout=PIPE, **exec_args)
+            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, **exec_args)
             output, _ = proc.communicate()
             retcode = proc.wait()
             LOG.debug("exit status = %s", retcode)
             if retcode != 0:
-                raise CalledProcessError(retcode, cmd, output)
+                raise subprocess.CalledProcessError(retcode, cmd, output)
             return output