#!/usr/bin/env python # # Copyright (C) 2012 Red Hat, Inc. All rights reserved. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library 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 # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Authors: Michal Minar # """ Unit tests for LMI_SoftwareFileCheck provider. """ import hashlib import os import pywbem import re import subprocess import unittest from collections import namedtuple import common RE_CHECKSUM = re.compile(r'^([0-9a-fA-F]+)\s+.*') PassedFlags = namedtuple("PassedFlags", #pylint: disable=C0103 "exists, type, size, mode, checksum, dev, ltarget, uid, gid, mtime") class TestSoftwareFileCheck(common.SoftwareBaseTestCase): #pylint: disable=R0904 """ Basic cim operations test. """ CLASS_NAME = "LMI_SoftwareFileCheck" KEYS = ( "CheckID", "Name", "SoftwareElementID", "SoftwareElementState" , "TargetOperatingSystem", "Version") hash_num2algo = { 1 : hashlib.md5, #pylint: disable=E1101 2 : hashlib.sha1, #pylint: disable=E1101 8 : hashlib.sha256, #pylint: disable=E1101 9 : hashlib.sha384, #pylint: disable=E1101 10 : hashlib.sha512, #pylint: disable=E1101 11 : hashlib.sha224 #pylint: disable=E1101 } hash_num2cmd = { 1 : "md5sum", 2 : "sha1sum", 8 : "sha256sum", 9 : "sha384sum", 10 : "sha512sum", 11 : "sha224sum" } hash_num2length = { 1 : 32, 2 : 40, 8 : 64, 9 : 96, 10 : 128, 11 : 56 } def make_op(self, pkg, filename, newer=True): """ @return object path of LMI_SoftwareFileCheck """ objpath = self.objpath.copy() objpath["Name"] = filename objpath["Version"] = getattr(pkg, "up_ver" if newer else "ver") objpath["CheckID"] = "%s#%s" % (pkg.name, filename) objpath["SoftwareElementState"] = pywbem.Uint16(2) objpath["TargetOperatingSystem"] = pywbem.Uint16(36) objpath["SoftwareElementID"] = pkg.get_nevra(newer) return objpath def assertEqualSEID(self, id1, id2, msg=None): """ This is for comparison of SoftwareElementID property values. """ if ( not isinstance(id1, basestring) or not isinstance(id2, basestring)): return common.SoftwareBaseTestCase.assertEqual(self, id1, id2, msg) match1 = common.RE_NEVRA.match(id1) match2 = common.RE_NEVRA.match(id2) if not match1 or not match2: return common.SoftwareBaseTestCase.assertEqual(self, id1, id2, msg) if any( match1.group(g) != match2.group(g) for g in ('name', 'ver', 'rel', 'arch')): return common.SoftwareBaseTestCase.assertEqual(self, id1, id2, msg) epoch1 = match1.group('epoch') epoch2 = match2.group('epoch') if not epoch1: epoch1 = '0' if not epoch2: epoch2 = '0' if epoch1 != epoch2: return common.SoftwareBaseTestCase.assertEqual(self, id1, id2, msg) return True def assertEqual(self, op1, op2, msg=None): """ This is override for object paths, that allows some differences (like missing epoch in SoftwareElementID). """ if ( not isinstance(op1, pywbem.CIMInstanceName) or not isinstance(op2, pywbem.CIMInstanceName) or op1.classname != op2.classname or op1.namespace != op2.namespace or op1.keybindings.keys() != op2.keybindings.keys() or any(op1[a] != op2[a] for a in ( 'Name', 'Version', 'CheckID', 'SoftwareElementState' , 'TargetOperatingSystem'))): return common.SoftwareBaseTestCase.assertEqual(self, op1, op2, msg) return self.assertEqualSEID( op1['SoftwareElementID'], op2['SoftwareElementID'], msg) def make_checksum_str(self, csumnum, filename): """ @return checksum of installed file """ return RE_CHECKSUM.match(subprocess.check_output([ self.hash_num2cmd[csumnum], filename])).group(1).lower() def do_check_symlink(self, pkg, filepath, inst): """ Assert some details about symlink. """ target = os.readlink(filepath) stats = os.lstat(filepath) self.assertEqual(pywbem.Uint16(3), inst["FileType"], "Unexpected file type of symlink for %s:%s"%( pkg.name, filepath)) self.assertEqual(stats.st_uid, inst["FileUserID"], "Unexpected uid of symlink for %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_gid, inst["FileGroupID"], "Unexpected gid of symlink for %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_mode, inst["FileMode"], "Unexpected mode of symlink for %s:%s" %(pkg.name, filepath)) self.assertEqual(stats.st_size, inst["FileSize"], "Unexpected size of symlink for %s:%s"%(pkg.name, filepath)) self.assertEqual(target, inst["LinkTarget"], "Unexpected size of symlink for %s:%s"%(pkg.name, filepath)) self.assertEqual("0"*self.hash_num2length[inst["FileChecksumType"]], inst["FileChecksum"], "Unexpected hash of symlink for %s:%s"%(pkg.name, filepath)) self.assertEqual(int(stats.st_mtime), inst["LastModificationTime"], "Unexpected mtime of symlink for %s:%s"%(pkg.name, filepath)) # modify owner prev_user = inst["FileUserID"] #prev_mtime = inst["LastModificationTime"] #prev_pflags = PassedFlags(*inst["PassedFlags"]) os.lchown(filepath, stats.st_uid + 1, -1) inst = self.conn.GetInstance(InstanceName=inst.path) self.assertEqual(inst["ExpectedFileUserID"] + 1, inst["FileUserID"], "Unexpected uid of modified symlink for %s:%s"%( pkg.name, filepath)) self.assertEqual(prev_user + 1, inst["FileUserID"], "Unexpected uid of modified symlink for %s:%s"%( pkg.name, filepath)) self.assertEqual(stats.st_gid, inst["FileGroupID"], "Unexpected gid of modified symlink for %s:%s"%( pkg.name, filepath)) cur_pflags = PassedFlags(*inst["PassedFlags"]) #self.assertGreater(inst["LastModificationTime"], prev_mtime) self.assertTrue(cur_pflags.exists, "Symlink %s:%s should exist." %(pkg.name, filepath)) self.assertTrue(cur_pflags.type, "File type should match for symlink %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.size, "File size should match for symlink %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.mode, "File mode should match for symlink %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.checksum, "File checksum should match for symlink %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.dev, "Device number should match for symlink %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.ltarget, "Link target should match for symlink %s:%s"%(pkg.name, filepath)) self.assertFalse(cur_pflags.uid, "Uid should not match for symlink %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.gid, "Gid shoud match for symlink %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.mtime, "Mtime should match for symlink %s:%s"%(pkg.name, filepath)) # modify link_target os.remove(filepath) os.symlink("wrong" + "*"*len(inst["ExpectedLinkTarget"]), filepath) os.lchown(filepath, inst["ExpectedFileUserID"], inst["ExpectedFileGroupID"]) inst = self.conn.GetInstance(InstanceName=inst.path) cur_pflags = PassedFlags(*inst["PassedFlags"]) self.assertGreater(len(inst["LinkTarget"]), len(inst["ExpectedLinkTarget"])) self.assertTrue(cur_pflags.exists, "File %s:%s should exist"%(pkg.name, filepath)) self.assertTrue(cur_pflags.type, "File type should match for symlink %s:%s"%(pkg.name, filepath)) # file size not checked for symlinks #self.assertFalse(cur_pflags.size, #"File size should not match for symlink %s:%s"%( #pkg.name, filepath)) self.assertTrue(cur_pflags.mode, "File mode should match for symlink %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.checksum, "Checksum should match for symlink %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.dev, "Device numbers should match for symlink %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.ltarget, "Link target should not match for symlink %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.uid, "File uid should match for symlink %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.gid, "File gid should match for symlink %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.mtime, "File mtime should match for symlink %s:%s"%(pkg.name, filepath)) def do_check_directory(self, pkg, filepath, inst): """ Assert some details about directory. """ stats = os.lstat(filepath) self.assertEqual(pywbem.Uint16(2), inst["FileType"], "Unexpected type for directory %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_uid, inst["FileUserID"], "Unexpected uid for directory %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_gid, inst["FileGroupID"], "Unexpected gid for directory %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_mode, inst["FileMode"], "Unexpected mode for directory %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_size, inst["FileSize"], "Unexpected size for directory %s:%s"%(pkg.name, filepath)) self.assertIs(inst["LinkTarget"], None) self.assertEqual("0"*self.hash_num2length[inst["FileChecksumType"]], inst["FileChecksum"], "Unexpected checksum for directory %s:%s"%(pkg.name, filepath)) self.assertEqual(int(stats.st_mtime), inst["LastModificationTime"], "Unexpected mtime for directory %s:%s"%(pkg.name, filepath)) def do_check_file(self, pkg, filepath, inst): """ Assert some details about regurar file. """ stats = os.lstat(filepath) self.assertEqual(pywbem.Uint16(1), inst["FileType"], "Unexpected file type for %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_uid, inst["FileUserID"], "Unexpected file uid for %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_gid, inst["FileGroupID"], "Unexpected gid for regular file %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_mode, inst["FileMode"], "Unexpected mode for reqular file %s:%s"%(pkg.name, filepath)) self.assertEqual(stats.st_size, inst["FileSize"], "Unexpected size for reqular file %s:%s"%(pkg.name, filepath)) self.assertIs(inst["LinkTarget"], None) csum = self.make_checksum_str(inst['FileChecksumType'], filepath) self.assertEqual(csum, inst["FileChecksum"].lower(), "Unexpected checksum for reqular file %s:%s"%(pkg.name, filepath)) self.assertEqual(inst["LastModificationTime"], inst["ExpectedLastModificationTime"], "Unexpected mtime for reqular file %s:%s"%(pkg.name, filepath)) self.assertEqual(int(stats.st_mtime), inst["LastModificationTime"], "Unexpected mtime for reqular file %s:%s"%(pkg.name, filepath)) # make it longer with open(filepath, "a+") as fobj: fobj.write("data\n") inst = self.conn.GetInstance(InstanceName=inst.path) cur_pflags = PassedFlags(*inst["PassedFlags"]) self.assertGreater(inst["FileSize"], inst["ExpectedFileSize"], "File size should be greater, then expected for reqular file" " %s:%s"%(pkg.name, filepath)) self.assertGreater(inst["LastModificationTime"], inst["ExpectedLastModificationTime"], "Unexpected mtime for reqular file %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.exists, "Regular file should exist %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.type, "Type of regular file should match for %s:%s"%(pkg.name, filepath)) self.assertFalse(cur_pflags.size, "Size should not match for regular file %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.mode, "Mode should match for regular file %s:%s"%(pkg.name, filepath)) self.assertFalse(cur_pflags.checksum, "Checksum should not match for regular file %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.dev, "Device number should match for regular file %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.ltarget, "Link target should match for regular file %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.uid, "File uid should match for %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.gid, "File gid should match for %s:%s"%(pkg.name, filepath)) self.assertFalse(cur_pflags.mtime, "File mtime should not match for %s:%s"%(pkg.name, filepath)) # change file type os.remove(filepath) os.symlink(filepath, filepath) os.lchown(filepath, inst["ExpectedFileUserID"], inst["ExpectedFileGroupID"]) inst = self.conn.GetInstance(InstanceName=inst.path) cur_pflags = PassedFlags(*inst["PassedFlags"]) self.assertNotEqual(inst["ExpectedLinkTarget"], inst["LinkTarget"], "Link target should not match for %s:%s"%(pkg.name, filepath)) self.assertNotEqual(inst["ExpectedFileSize"], inst["FileSize"], "File size should not match for %s:%s"%(pkg.name, filepath)) self.assertGreater(inst["LastModificationTime"], inst["ExpectedLastModificationTime"], "File mtime should be greater than expected for %s:%s"%( pkg.name, filepath)) self.assertNotEqual(inst["ExpectedFileType"], inst["FileType"], "File type should not match for %s:%s"%(pkg.name, filepath)) self.assertEqual(pywbem.Uint16(3), inst["FileType"], "File type should match for %s:%s"%(pkg.name, filepath)) self.assertTrue(cur_pflags.exists, "Regular file %s:%s should exist"%(pkg.name, filepath)) self.assertFalse(cur_pflags.type, "Regular file type should not match for %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.size, "Size should not match for regular file %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.mode, "File mode should not match for regular file %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.checksum, "Checksum should not match for regular file %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.dev, "Device should match for regular file %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.ltarget, "Link target should not match for regular file %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.uid, "Regular file's uid should match for %s:%s"%( pkg.name, filepath)) self.assertTrue(cur_pflags.gid, "Regular file's gid should match for %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.mtime, "Regular file's mtime should not match for %s:%s"%( pkg.name, filepath)) # remove it os.remove(filepath) inst = self.conn.GetInstance(InstanceName=inst.path) cur_pflags = PassedFlags(*inst["PassedFlags"]) self.assertEqual(inst["ExpectedLinkTarget"], inst["LinkTarget"], "Link target does not match for regular file %s:%s"%( pkg.name, filepath)) self.assertNotEqual(inst["ExpectedFileSize"], inst["FileSize"], "File size should not match for regular file %s:%s"%( pkg.name, filepath)) self.assertIsNone(inst["LastModificationTime"]) self.assertIsNone(inst["FileType"]) self.assertIsNone(inst["FileChecksum"]) self.assertIsNone(inst["FileMode"]) self.assertIsNone(inst["FileUserID"]) self.assertIsNone(inst["FileGroupID"]) self.assertFalse(cur_pflags.exists, "Regular file %s:%s should not exist"%(pkg.name, filepath)) self.assertFalse(cur_pflags.type, "Regular file's type should not match for %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.size, "Regular file's size should not match for %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.mode, "Regular file's mode should not match for %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.checksum, "Regular file's checksum should not match for %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.dev, "Regular file's dev number should not match %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.ltarget, "Regular file's link target should not match for %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.uid, "Regular file's uid should not match for %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.gid, "Regular file's guid should not match for %s:%s"%( pkg.name, filepath)) self.assertFalse(cur_pflags.mtime, "Regular file's mtime should not match for %s:%s"%( pkg.name, filepath)) @common.mark_dangerous def test_get_instance(self): """ Tests GetInstance call. """ for pkg in self.pkgdb: files = self.pkg_files[pkg.name] if ( common.is_installed(pkg) and not common.verify_pkg(pkg.name)): common.remove_pkg(pkg.name) if not common.is_installed(pkg.name): self.install_pkg(pkg) self.assertTrue(common.is_installed(pkg), "Package %s must be installed"%pkg) for filepath in files: objpath = self.make_op(pkg, filepath) inst = self.conn.GetInstance( InstanceName=objpath, LocalOnly=False) self.assertIsInstance(inst, pywbem.CIMInstance) self.assertEqual(objpath, inst.path, msg="Object paths of instance must match for %s:%s"%( pkg.name, filepath)) for key in self.KEYS: if key.lower() == "softwareelementid": self.assertEqualSEID(inst[key], objpath[key], "OP key %s values should match for %s:%s"%( key, pkg.name, filepath)) elif key.lower() == "targetoperatingsystem": self.assertIsInstance(objpath[key], (int, long)) else: self.assertEqual(objpath[key], inst[key], "OP key %s values should match for %s:%s"%( key, pkg.name, filepath)) self.assertTrue(inst["FileExists"], "File %s:%s must exist"%(pkg.name, filepath)) self.assertEqual(10, len(inst["PassedFlags"]), "PassedFlags must have constant length") for i, flag in enumerate(inst["PassedFlags"]): self.assertTrue(flag is True, "Flag \"%s\" should all match for file %s:%s"%( inst["PassedFlagsDescriptions"][i], pkg.name, filepath)) for prop in ( "FileType", "FileUserID", "FileGroupID" , "FileMode", "FileSize", "LinkTarget" , "FileChecksum", "FileModeFlags"): if ( ( os.path.islink(filepath) or (not os.path.isfile(filepath))) and prop == "FileSize"): continue self.assertEqual(inst["Expected"+prop], inst[prop], "%s should match for %s:%s"%(prop, pkg.name, filepath)) if os.path.islink(filepath): self.do_check_symlink(pkg, filepath, inst) elif os.path.isdir(filepath): self.do_check_directory(pkg, filepath, inst) elif os.path.isfile(filepath): self.do_check_file(pkg, filepath, inst) @common.mark_dangerous def test_invoke_method(self): """ Tests Invoke method invocation. """ for pkg in self.pkgdb: files = self.pkg_files[pkg.name] if common.is_installed(pkg) and not common.verify_pkg(pkg.name): common.remove_pkg(pkg.name) if not common.is_installed(pkg.name): self.install_pkg(pkg) self.assertTrue(common.is_installed(pkg), "Package %s must be installed"%pkg) for filepath in files: objpath = self.make_op(pkg, filepath) (rval, _) = self.conn.InvokeMethod( MethodName="Invoke", ObjectName=objpath) self.assertEqual(pywbem.Uint32(0), rval, msg="InvokeMethod should be successful for %s:%s"%( pkg.name, filepath)) # modify file if os.path.isfile(filepath): os.remove(filepath) else: os.lchown(filepath, os.stat(filepath).st_uid + 1, -1) (rval, _) = self.conn.InvokeMethod( MethodName="Invoke", ObjectName=objpath) self.assertEqual(pywbem.Uint32(2), rval, "InvokeMethod should not pass for modified file %s:%s"%( pkg.name, filepath)) def suite(): """For unittest loaders.""" return unittest.TestLoader().loadTestsFromTestCase( TestSoftwareFileCheck) if __name__ == "__main__": unittest.main()