Change in vdsm[master]: tests: add tests for sampling.VMBulkSampler

fromani at redhat.com fromani at redhat.com
Fri Apr 8 10:02:44 UTC 2016


Francesco Romani has uploaded a new change for review.

Change subject: tests: add tests for sampling.VMBulkSampler
......................................................................

tests: add tests for sampling.VMBulkSampler

The VMBulkSampler class is responsible of the bulk stats sampling,
and includes all the logic to deal with blocked domains.
This patch add the initial batch of tests to exercise this code.

Change-Id: I3ec7d5f2fc3b9b5c3efbffe874ad6ceefe7a3406
Signed-off-by: Francesco Romani <fromani at redhat.com>
---
M tests/Makefile.am
A tests/bulk_sampling_test.py
2 files changed, 289 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/56/55856/1

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1fda1ce..1cb1cc3 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -72,6 +72,7 @@
 	blocksdTests.py \
 	blockVolumeTests.py \
 	bridgeTests.py \
+	bulk_sampling_test.py \
 	cPopenTests.py \
 	capsTests.py \
 	cpuinfo_test.py \
diff --git a/tests/bulk_sampling_test.py b/tests/bulk_sampling_test.py
new file mode 100644
index 0000000..df9ed9b
--- /dev/null
+++ b/tests/bulk_sampling_test.py
@@ -0,0 +1,288 @@
+#
+# Copyright 2015-2016 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+from __future__ import absolute_import
+
+import collections
+import contextlib
+import threading
+import time
+
+from vdsm import executor
+from vdsm import schedule
+from vdsm.utils import monotonic_time
+
+from vdsm.virt import sampling
+
+from testValidation import slowtest
+from testlib import VdsmTestCase as TestCaseBase
+from testlib import recorded
+
+
+CacheSample = collections.namedtuple('CacheSample', ['stats', 'timestamp'])
+
+
+class TestVMBulkSampling(TestCaseBase):
+
+    CALL_TIMEOUT = 0.5  # seconds
+
+    TIMEOUT = 2  # seconds
+
+    def setUp(self):
+        self.sched = schedule.Scheduler(name="test.Scheduler",
+                                        clock=monotonic_time)
+        self.sched.start()
+
+        self.exc = executor.Executor(name="test.Executor",
+                                     workers_count=1,
+                                     max_tasks=100,
+                                     scheduler=self.sched)
+        self.exc.start()
+
+    def tearDown(self):
+        self.exc.stop()
+        self.exc = None
+
+        self.sched.stop()
+        self.sched = None
+
+    def test_collect_fast_path_as_default(self):
+        vms = make_vms(num=3)
+        conn = FakeConnection(vms=vms)
+        cache = FakeStatsCache()
+
+        sampler = sampling.VMBulkSampler(conn, conn.getVMs, cache)
+
+        with cache.wait_for(self.TIMEOUT):
+            self.exc.dispatch(sampler, self.CALL_TIMEOUT)
+
+        self.assertCallSequence(
+            conn.__calls__,
+            ['getAllDomainStats']
+        )
+
+    @slowtest
+    def test_collect_slow_path_after_blocked(self):
+        vms = make_vms(num=3)
+        conn = FakeConnection(vms=vms)
+        cache = FakeStatsCache()
+
+        sampler = sampling.VMBulkSampler(conn, conn.getVMs, cache)
+
+        with conn.stuck(self.TIMEOUT * 2):
+            with cache.wait_for(self.TIMEOUT):
+                self.exc.dispatch(sampler, self.CALL_TIMEOUT)
+                self.exc.dispatch(sampler, self.CALL_TIMEOUT)
+
+        self.assertCallSequence(
+            conn.__calls__,
+            ['getAllDomainStats', 'domainListGetStats']
+        )
+
+    @slowtest
+    def test_collect_vm_unresponsive(self):
+        vms = make_vms(num=3)
+        conn = FakeConnection(vms=vms)
+        cache = FakeStatsCache()
+
+        sampler = sampling.VMBulkSampler(conn, conn.getVMs, cache)
+
+        with conn.stuck(self.TIMEOUT * 2):
+            # only the two slow completions are expected to succeed
+            with cache.wait_for(self.TIMEOUT, expected=2):
+                self.exc.dispatch(sampler, self.CALL_TIMEOUT)
+                vms['1'].ready = False
+                self.exc.dispatch(sampler, self.CALL_TIMEOUT)
+                self.exc.dispatch(sampler, self.CALL_TIMEOUT)
+
+        self.assertCallSequence(
+            conn.__calls__,
+            ['getAllDomainStats', 'domainListGetStats', 'domainListGetStats']
+        )
+
+    @slowtest
+    def test_slow_collect_while_vm_unresponsive(self):
+        vms = make_vms(num=3)
+        conn = FakeConnection(vms=vms)
+        cache = FakeStatsCache()
+
+        sampler = sampling.VMBulkSampler(conn, conn.getVMs, cache)
+
+        with conn.stuck(self.TIMEOUT * 2):
+            # only the two slow completions are expected to succeed
+            with cache.wait_for(self.TIMEOUT):
+                self.exc.dispatch(sampler, self.CALL_TIMEOUT)
+                vms['1'].ready = False
+                self.exc.dispatch(sampler, self.CALL_TIMEOUT)
+            # now we succesfully waited_for the second (slow) call:
+            # call #1 (fast) recorded, not yet completed
+            # call #2 (slow) recorded, completed, waited
+            # now we need to be able to wait for the still pending call,
+            # hence we re-prepare to wait
+            cache.prepare_wait()
+
+        # so we check indeed we recorded the right call. call #1 (fast)
+        # may complete any moment, asynchronously to this thread
+        expected = ['getAllDomainStats', 'domainListGetStats']
+        self.assertCallSequence(conn.__calls__, expected)
+        # now we make sure the call #1 (fast) is completed.
+        # we expect NOT to wait here, timeout added just in case
+        assert(cache.sync.wait(self.TIMEOUT))
+
+        # reset fake environment to pristine state
+        vms['1'].ready = True
+        sampler._skip_doms.clear()
+
+        expected.append('getAllDomainStats')
+        with cache.wait_for(self.TIMEOUT):
+            self.exc.dispatch(sampler, self.CALL_TIMEOUT)
+
+        self.assertCallSequence(conn.__calls__, expected)
+
+    def assertVmsInBulkStats(self, vm_ids, bulk_stats):
+        """
+        checks only for the presence of stats. Stats data is fake,
+        so ignore it as meaningless.
+        """
+        self.assertEqual(len(vm_ids), len(bulk_stats))
+        for vm_id in vm_ids:
+            self.assertIn(vm_id, bulk_stats)
+
+    def assertVmStuck(self, sampler, vm_id):
+        self.assertTrue(sampler._skip_doms.get(vm_id))
+
+    def assertCallSequence(self, actual_calls, expected_calls):
+        for actual, expected in zip(actual_calls, expected_calls):
+            # we don't care about the arguments
+            self.assertEquals(actual[0], expected)
+        self.assertEquals(len(actual_calls), len(expected_calls))
+
+
+class FakeStatsCache(object):
+    def __init__(self, clock=monotonic_time):
+        self.data = []
+        self.clock = clock
+        self.sync = threading.Event()
+        self.expected = 1
+        self._count = 0
+
+    def put(self, bulk_stats, timestamp):
+        self.data.append(CacheSample(bulk_stats, timestamp))
+        self._count += 1
+        if self._count >= self.expected:
+            self.sync.set()
+
+    def prepare_wait(self):
+        self._count = 0
+        self.sync.clear()
+
+    @property
+    def received(self):
+        return self._count
+
+    @contextlib.contextmanager
+    def wait_for(self, timeout, expected=1):
+        self.expected = expected
+        self.prepare_wait()
+        try:
+            yield self
+        finally:
+            assert(self.sync.wait(timeout))
+
+
+class FakeDomain(object):
+    def __init__(self, name):
+        self._name = name
+        self._dom = self  # yep, this is an ugly hack
+
+    def UUIDString(self):
+        # yes this is cheating
+        return self._name
+
+
+class FakeVM(object):
+    def __init__(self, vmid):
+        self.id = vmid
+        self._dom = FakeDomain(vmid)
+        self.ready = True
+
+    def isDomainReadyForCommands(self):
+        return self.ready
+
+
+def make_vms(num=1):
+    vms = {}
+    for idx in range(1, num+1):
+        vm = FakeVM(str(idx))
+        vms[vm.id] = vm
+    return vms
+
+
+class FakeConnection(object):
+    def __init__(self, vms):
+        self.vms = vms
+        self._delay = 0
+        self._block = threading.Event()
+        self.__calls__ = []
+
+    def getVMs(self):
+        return self.vms
+
+    def sleep(self, delay):
+        self._delay = delay
+
+    def prepare_block(self):
+        self._delay = 0
+
+    @property
+    def sleeping(self):
+        return self._delay > 0
+
+    def wakeup(self):
+        self.prepare_block()
+        self._block.set()
+
+    @contextlib.contextmanager
+    def stuck(self, delay):
+        self.sleep(delay)
+        try:
+            yield self
+        finally:
+            self.wakeup()
+
+    @recorded
+    def getAllDomainStats(self, flags=0):
+        if not self._block.wait(self._delay):
+            return []
+        return [
+            (vm._dom, {
+                'vmid': vm._dom.UUIDString()
+            })
+            for vm in self.vms.values()
+        ]
+
+    @recorded
+    def domainListGetStats(self, doms, flags=0):
+        return [
+            (dom, {
+                'vmid': dom.UUIDString()
+            })
+            for dom in doms
+            if dom.UUIDString() in self.vms
+        ]


-- 
To view, visit https://gerrit.ovirt.org/55856
To unsubscribe, visit https://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I3ec7d5f2fc3b9b5c3efbffe874ad6ceefe7a3406
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Francesco Romani <fromani at redhat.com>


More information about the vdsm-patches mailing list