summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlois Mahdal <amahdal@redhat.com>2014-05-06 22:54:40 +0200
committerAlois Mahdal <amahdal@redhat.com>2014-05-07 14:09:10 +0200
commit691e41026477298b00d3a9cbd41ae9620601e888 (patch)
treecc337f09e1d8264073989e88a43b0c4345b30c6b
parent77185c85eeaf915e8b3892ed8f89307545b492a9 (diff)
downloadopenlmi-providers-691e41026477298b00d3a9cbd41ae9620601e888.tar.gz
openlmi-providers-691e41026477298b00d3a9cbd41ae9620601e888.tar.xz
openlmi-providers-691e41026477298b00d3a9cbd41ae9620601e888.zip
Move generic code to lmi.test.util
-rw-r--r--src/account/test/TestAccountInvalidEtc.py7
-rw-r--r--src/account/test/TestAccountRaceConditions.py8
-rw-r--r--src/account/test/common.py200
-rw-r--r--src/account/test/methods.py13
-rw-r--r--src/python/lmi/test/util.py214
5 files changed, 224 insertions, 218 deletions
diff --git a/src/account/test/TestAccountInvalidEtc.py b/src/account/test/TestAccountInvalidEtc.py
index 7ed77c3..05d797f 100644
--- a/src/account/test/TestAccountInvalidEtc.py
+++ b/src/account/test/TestAccountInvalidEtc.py
@@ -19,7 +19,8 @@
import sys
-from common import AccountBase, BackupStorage, BaseCrippler
+import lmi.test.util
+from common import AccountBase
from lmi.shell import LMIInstance
@@ -48,7 +49,7 @@ def gen_cripple(*args):
## Test data (later "converted" to test methods)
## ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ##
-class PasswdCrippler(BaseCrippler):
+class PasswdCrippler(lmi.test.util.BaseCrippler):
"""
Cripple files related to user management
"""
@@ -417,7 +418,7 @@ class TestAccountInvalidEtc(AccountBase):
CLASS_NAME = "LMI_Account"
def setUp(self):
- self.bs = BackupStorage()
+ self.bs = lmi.test.util.BackupStorage()
self.bs.add_file("/etc/passwd")
self.bs.add_file("/etc/shadow")
self.bs.add_file("/etc/group")
diff --git a/src/account/test/TestAccountRaceConditions.py b/src/account/test/TestAccountRaceConditions.py
index 61785c8..cd13d61 100644
--- a/src/account/test/TestAccountRaceConditions.py
+++ b/src/account/test/TestAccountRaceConditions.py
@@ -17,8 +17,9 @@
#
# Authors: Alois Mahdal <amahdal@redhat.com>
+import lmi.test.util
import methods
-from common import AccountBase, BackupStorage, PasswdFile
+from common import AccountBase, PasswdFile
from pywbem import CIMError
import threading
@@ -123,10 +124,11 @@ class TestAccountRaceConditions(AccountBase):
def setUp(self):
self.user_count = 20 # = thread count
self.prefix = "user"
- self.names = [methods.random_string(strength=8, prefix=self.prefix)
+ self.names = [lmi.test.util.random_string(strength=8,
+ prefix=self.prefix)
for i in xrange(self.user_count)]
- self.bs = BackupStorage()
+ self.bs = lmi.test.util.BackupStorage()
self.bs.add_file("/etc/passwd")
self.bs.add_file("/etc/shadow")
self.bs.add_file("/etc/group")
diff --git a/src/account/test/common.py b/src/account/test/common.py
index 8f3eeba..639fb61 100644
--- a/src/account/test/common.py
+++ b/src/account/test/common.py
@@ -30,6 +30,7 @@ from collections import Counter
from collections import OrderedDict
import methods
+import lmi.test.util
from lmi.test import lmibase
@@ -120,202 +121,3 @@ class PasswdFile():
"""
return [u['name'] for u in self.users]
-
-## ......................................................................... ##
-## Other helpers
-## ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ##
-
-class BackupStorage():
- """
- Simple file backup storage.
-
- * Only supports files.
- * Only supports absolute paths.
- * Consecutive backups rewrite each other.
- * Does not autodestroy the backup.
- """
-
- def __init__(self):
- self.root = tempfile.mkdtemp(prefix=self.__class__.__name__ + ".")
- self.backups = OrderedDict()
- subprocess.check_call(["mkdir", "-p", self.root])
-
- def _copy(self, src, dest):
- """
- Copy src to dst --- force, keep meta, no questions asked
- """
- subprocess.check_call(["cp", "-a", "-f", src, dest])
-
- def _get_bpath(self, path):
- """
- Take original path and return path to backup.
- """
- if not path.startswith("/"):
- raise ValueError("only absolute paths are supported")
- digest = hashlib.sha1(path).hexdigest()
- return self.root + "/" + digest
-
- def _update_index(self):
- """
- Create/update an index file to help in case of backup investigation
-
- For convenience, index file is sorted by real path.
- """
- paths = sorted(self.backups.keys())
- with open(self.root + "/index", "w+") as fh:
- for path in paths:
- fh.write("%s %s\n" % (self.backups[path], path))
-
- def add_files(self, paths):
- """
- Add list of tiles to backup storage
- """
- for path in paths:
- self.add_file(path)
-
- def add_file(self, path):
- """
- Add a file to backup storage
- """
- bpath = self._get_bpath(path)
- self._copy(path, bpath)
- self.backups[path] = bpath
- self._update_index()
-
- def restore(self, path):
- """
- Restore particular path
- """
- try:
- self._copy(self.backups[path], path)
- except KeyError:
- raise ValueError("path not stored: %s" % path)
-
- def restore_all(self):
- """
- Restore all stored paths in same order as they were stored
- """
- for key in self.backups.keys():
- self.restore(key)
-
- def destroy_backup(self):
- """
- Destroy the temporary backup
- """
- subprocess.call(["rm", "-rf", self.root])
-
-
-class BaseCrippler:
- """
- Helper class for crippling system files.
-
- To use the class, you need to sub-class it and implement
- _define_cases method.
- """
-
- LINE_LENGTH = 500
- LINE_COUNT = 50
- BINARY_LENGTH = 10 * 1024 * 1024
-
- ## virtual
- #
-
- def _define_cases(self):
- """
- Define cases per file supported
-
- This function must return a dict with one set of cases per
- file: key is path and value is another dict defining cases
- as pairs of name ([a-zA-Z_]) and content.
-
- Quick example:
-
- {
- '/etc/file1': {
- 'case1': "some triggering content",
- 'case2': "some other triggering content",
- 'case3': "some funny triggering content",
- },
- '/etc/file2': {
- 'case1': "some triggering content",
- 'case2': "some other triggering content",
- 'case3': "some funny triggering content",
- },
- }
-
- Note that trailing newline is added automatically to each content
- string. Also, whether content will be appended or replaced is decided
- by caller of the BaseCrippler.cripple method.
- """
- pass
-
- ## internal
- #
-
- def __init__(self):
- self.autocases = {
- 'empty': lambda: '',
- 'random_line': self._random_line,
- 'random_lines': self._random_lines,
- 'random_binary': self._random_binary,
- }
- self.cases = self._define_cases()
-
- def _append_to(self, path, content):
- with open(path, 'a+') as fh:
- fh.write(content)
-
- def _clobber(self, path, content):
- with open(path, 'w+') as fh:
- fh.write(content)
-
- def _random_binary(self, size=BINARY_LENGTH):
- chars = ''.join([chr(i) for i in xrange(256)])
- return methods.random_string(strength=size, chars=chars)
-
- def _random_line(self, size=LINE_LENGTH):
- chars = string.letters + string.punctuation + " \t"
- return methods.random_string(strength=size, chars=chars) + "\n"
-
- def _random_lines(self, size=LINE_LENGTH, count=LINE_COUNT):
- return "".join([self._random_line(size) for i in xrange(count)])
-
- def _get_content(self, path, case):
-
- try:
- content = self.autocases[case]()
- except KeyError:
- try:
- content = self.cases[path][case] + "\n"
- except KeyError:
- raise ValueError("unknown case: %s for: %s" % (case, path))
- return content
-
- ## public
- #
-
- def all_cases_for(self, path):
- """
- Return list of cases available for path
- """
- return self.cases[path].keys() + self.autocases.keys()
-
- def all_paths(self):
- """
- Return list of paths served by this implementation
- """
- return self.cases.keys()
-
- def cripple(self, path, case, op="replace"):
- """
- Cripple file according to selected case.
-
- op is either "append" or "replace" and means that the content will
- be appended to the file, otherwise it will replace it.
- """
- if op == 'replace':
- self._clobber(path, self._get_content(path, case))
- elif op == 'append':
- self._append_to(path, self._get_content(path, case))
- else:
- raise ValueError("unknown op: %s" % op)
diff --git a/src/account/test/methods.py b/src/account/test/methods.py
index 4043ddf..9659d4f 100644
--- a/src/account/test/methods.py
+++ b/src/account/test/methods.py
@@ -97,19 +97,6 @@ def create_group(group_name):
if not group_exists(group_name):
subprocess.check_call(["groupadd", group_name])
-def random_string(strength=6, chars=None, prefix=""):
- """
- Generate a random string, e.g. usable as UID/GID
-
- strength is count of random characters in the final string. chars
- is sequence of characters to choose from, and prefix can be provided
- to prepend it to final string.
- """
- if chars is None:
- chars = string.ascii_uppercase + string.digits
- salt = ''.join([random.choice(chars) for x in range(strength)])
- return prefix + salt
-
def random_shell():
"""
Make up a funny shell
diff --git a/src/python/lmi/test/util.py b/src/python/lmi/test/util.py
index 241e16a..699507c 100644
--- a/src/python/lmi/test/util.py
+++ b/src/python/lmi/test/util.py
@@ -21,8 +21,14 @@ LMI test utilities.
"""
import os
+import hashlib
import pywbem
+import random
+import tempfile
import socket
+import string
+import subprocess
+from collections import OrderedDict
from lmi.test import unittest
def is_this_system(system_name):
@@ -137,3 +143,211 @@ def check_inames_equal(fst, snd):
return True
+def random_string(strength=6, chars=None, prefix=""):
+ """
+ Generate a random string, e.g. usable as UID/GID
+
+ strength is count of random characters in the final string. chars
+ is sequence of characters to choose from, and prefix can be provided
+ to prepend it to final string.
+ """
+ if chars is None:
+ chars = string.ascii_uppercase + string.digits
+ salt = ''.join([random.choice(chars) for x in range(strength)])
+ return prefix + salt
+
+
+class BackupStorage():
+ """
+ Simple file backup storage.
+
+ * Only supports files.
+ * Only supports absolute paths.
+ * Consecutive backups rewrite each other.
+ * Does not autodestroy the backup.
+ """
+
+ def __init__(self):
+ self.root = tempfile.mkdtemp(prefix=self.__class__.__name__ + ".")
+ self.backups = OrderedDict()
+ subprocess.check_call(["mkdir", "-p", self.root])
+
+ def _copy(self, src, dest):
+ """
+ Copy src to dst --- force, keep meta, no questions asked
+ """
+ subprocess.check_call(["cp", "-a", "-f", src, dest])
+
+ def _get_bpath(self, path):
+ """
+ Take original path and return path to backup.
+ """
+ if not path.startswith("/"):
+ raise ValueError("only absolute paths are supported")
+ digest = hashlib.sha1(path).hexdigest()
+ return self.root + "/" + digest
+
+ def _update_index(self):
+ """
+ Create/update an index file to help in case of backup investigation
+
+ For convenience, index file is sorted by real path.
+ """
+ paths = sorted(self.backups.keys())
+ with open(self.root + "/index", "w+") as fh:
+ for path in paths:
+ fh.write("%s %s\n" % (self.backups[path], path))
+
+ def add_files(self, paths):
+ """
+ Add list of tiles to backup storage
+ """
+ for path in paths:
+ self.add_file(path)
+
+ def add_file(self, path):
+ """
+ Add a file to backup storage
+ """
+ bpath = self._get_bpath(path)
+ self._copy(path, bpath)
+ self.backups[path] = bpath
+ self._update_index()
+
+ def restore(self, path):
+ """
+ Restore particular path
+ """
+ try:
+ self._copy(self.backups[path], path)
+ except KeyError:
+ raise ValueError("path not stored: %s" % path)
+
+ def restore_all(self):
+ """
+ Restore all stored paths in same order as they were stored
+ """
+ for key in self.backups.keys():
+ self.restore(key)
+
+ def destroy_backup(self):
+ """
+ Destroy the temporary backup
+ """
+ subprocess.call(["rm", "-rf", self.root])
+
+
+class BaseCrippler:
+ """
+ Helper class for crippling system files.
+
+ To use the class, you need to sub-class it and implement
+ _define_cases method.
+ """
+
+ LINE_LENGTH = 500
+ LINE_COUNT = 50
+ BINARY_LENGTH = 10 * 1024 * 1024
+
+ ## virtual
+ #
+
+ def _define_cases(self):
+ """
+ Define cases per file supported
+
+ This function must return a dict with one set of cases per
+ file: key is path and value is another dict defining cases
+ as pairs of name ([a-zA-Z_]) and content.
+
+ Quick example:
+
+ {
+ '/etc/file1': {
+ 'case1': "some triggering content",
+ 'case2': "some other triggering content",
+ 'case3': "some funny triggering content",
+ },
+ '/etc/file2': {
+ 'case1': "some triggering content",
+ 'case2': "some other triggering content",
+ 'case3': "some funny triggering content",
+ },
+ }
+
+ Note that trailing newline is added automatically to each content
+ string. Also, whether content will be appended or replaced is decided
+ by caller of the BaseCrippler.cripple method.
+ """
+ pass
+
+ ## internal
+ #
+
+ def __init__(self):
+ self.autocases = {
+ 'empty': lambda: '',
+ 'random_line': self._random_line,
+ 'random_lines': self._random_lines,
+ 'random_binary': self._random_binary,
+ }
+ self.cases = self._define_cases()
+
+ def _append_to(self, path, content):
+ with open(path, 'a+') as fh:
+ fh.write(content)
+
+ def _clobber(self, path, content):
+ with open(path, 'w+') as fh:
+ fh.write(content)
+
+ def _random_binary(self, size=BINARY_LENGTH):
+ chars = ''.join([chr(i) for i in xrange(256)])
+ return random_string(strength=size, chars=chars)
+
+ def _random_line(self, size=LINE_LENGTH):
+ chars = string.letters + string.punctuation + " \t"
+ return random_string(strength=size, chars=chars) + "\n"
+
+ def _random_lines(self, size=LINE_LENGTH, count=LINE_COUNT):
+ return "".join([self._random_line(size) for i in xrange(count)])
+
+ def _get_content(self, path, case):
+
+ try:
+ content = self.autocases[case]()
+ except KeyError:
+ try:
+ content = self.cases[path][case] + "\n"
+ except KeyError:
+ raise ValueError("unknown case: %s for: %s" % (case, path))
+ return content
+
+ ## public
+ #
+
+ def all_cases_for(self, path):
+ """
+ Return list of cases available for path
+ """
+ return self.cases[path].keys() + self.autocases.keys()
+
+ def all_paths(self):
+ """
+ Return list of paths served by this implementation
+ """
+ return self.cases.keys()
+
+ def cripple(self, path, case, op="replace"):
+ """
+ Cripple file according to selected case.
+
+ op is either "append" or "replace" and means that the content will
+ be appended to the file, otherwise it will replace it.
+ """
+ if op == 'replace':
+ self._clobber(path, self._get_content(path, case))
+ elif op == 'append':
+ self._append_to(path, self._get_content(path, case))
+ else:
+ raise ValueError("unknown op: %s" % op)