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.
 
 # 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
 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 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
-import six.moves.configparser as ConfigParser
+from six.moves import configparser
 import yaml
 from six import StringIO
 from chainmap import ChainMap
 import yaml
 from six import StringIO
 from chainmap import ChainMap
+from oslo_serialization import jsonutils
 
 from yardstick.common.utils import Timer
 
 from yardstick.common.utils import Timer
-
+from yardstick.common import constants as consts
 
 cgitb.enable(format="text")
 
 
 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
         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,
 
     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 += '_'
 
         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
 
         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
     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
         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
 
         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_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 "
             # # 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)
 
         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')
 
         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
 
             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):
     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):
         #  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
         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:
                     # '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:
                     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")
                 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,
             })
                 # 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:
             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
             return output