Change in vdsm[master]: tests: added NFS support to functional tests
by ykleinbe@redhat.com
Yoav Kleinberger has uploaded a new change for review.
Change subject: tests: added NFS support to functional tests
......................................................................
tests: added NFS support to functional tests
In this patch, I introduce a new NFS storage context for the storage
functional tests. Since there is much shared logic between NFS and
LocalFS storage context, I introduce a common superclass.
Change-Id: I1781fc400c0604855d3143dde22ccb29e6cc8013
Signed-off-by: Yoav Kleinberger <ykleinbe(a)redhat.com>
---
M tests/functional/basicStorageTest.py
M tests/functional/testlib/storagecontexts/base.py
A tests/functional/testlib/storagecontexts/filebased.py
M tests/functional/testlib/storagecontexts/iscsi.py
M tests/functional/testlib/storagecontexts/localfs.py
A tests/functional/testlib/storagecontexts/nfs.py
6 files changed, 249 insertions(+), 114 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/90/32990/1
diff --git a/tests/functional/basicStorageTest.py b/tests/functional/basicStorageTest.py
index c01d14b..438f8c8 100644
--- a/tests/functional/basicStorageTest.py
+++ b/tests/functional/basicStorageTest.py
@@ -4,6 +4,7 @@
from functional.testlib import controlvdsm
from functional.testlib.storagecontexts import localfs
from functional.testlib.storagecontexts import iscsi
+from functional.testlib.storagecontexts import nfs
class TestBasicStorageDomain(object):
@@ -23,6 +24,9 @@
def testCreateVolumeISCSI(self):
self._testCreateVolume(iscsi.ISCSI)
+ def testCreateVolumeNFS(self):
+ self._testCreateVolume(nfs.NFS)
+
def _testCreateVolume(self, storageBackend):
with storageBackend() as (vdsm, verify):
storageServerID = vdsm.connectStorageServer()
diff --git a/tests/functional/testlib/storagecontexts/base.py b/tests/functional/testlib/storagecontexts/base.py
index 7179694..2e308ad 100644
--- a/tests/functional/testlib/storagecontexts/base.py
+++ b/tests/functional/testlib/storagecontexts/base.py
@@ -73,16 +73,47 @@
class StorageBackend(object):
def __init__(self):
self._vdsmCaller = vdsmcaller.VDSMCaller()
+ self._domainID = self._newUUID()
+ self._poolID = self._newUUID()
+ self._imageID = self._newUUID()
+ self._volumeID = self._newUUID()
+ self._connectionID = self._newUUID()
+
+ def connectionID(self):
+ return self._connectionID
+
+ def volumeID(self):
+ return self._volumeID
+
+ def imageID(self):
+ return self._imageID
+
+ def poolID(self):
+ return self._poolID
+
+ def domainID(self):
+ return self._domainID
def vdsm(self):
return self._vdsmCaller
- def newUUID(self):
+ def _newUUID(self):
return str(uuid.uuid4())
def randomName(self, base):
return "%s_%04d" % (base, random.randint(1, 10000))
+ def createStoragePool(self):
+ POOL_TYPE_DEPRECATED = 0
+ self.vdsm().createStoragePool(
+ POOL_TYPE_DEPRECATED,
+ self.poolID(),
+ self.randomName('pool'),
+ self.domainID(),
+ [self.domainID()],
+ 1)
+ return self.poolID()
+
def connectStoragePool(self, poolID, masterDomainID):
SCSI_KEY_DEPRECATED = 0
self.vdsm().connectStoragePool(
diff --git a/tests/functional/testlib/storagecontexts/filebased.py b/tests/functional/testlib/storagecontexts/filebased.py
new file mode 100644
index 0000000..bdb15d7
--- /dev/null
+++ b/tests/functional/testlib/storagecontexts/filebased.py
@@ -0,0 +1,69 @@
+import os
+import storage.volume
+import storage.image
+from . import base
+import logging
+
+
+class Verify(base.Verify):
+
+ def rhevMountPoint(self):
+ raise Exception('you must override this function')
+
+ def storageServerConnected(self):
+ self.sleepWhileVDSMCompletesTask(duration=2)
+ assert os.path.exists(self.rhevMountPoint())
+
+ def storageDomainCreated(self, domainID):
+ self.sleepWhileVDSMCompletesTask(duration=1)
+ self._directIOTestFileExists()
+ domainRoot = os.path.join(self.rhevMountPoint(), domainID)
+ expectedFiles = [
+ 'images',
+ 'dom_md',
+ 'dom_md/leases',
+ 'dom_md/inbox',
+ 'dom_md/outbox',
+ 'dom_md/metadata',
+ 'dom_md/ids']
+ expectedFullPaths =\
+ [os.path.join(domainRoot, path) for path in expectedFiles]
+ for path in expectedFullPaths:
+ logging.info('verifying path: %s' % path)
+ assert os.path.exists(path)
+
+ def _directIOTestFileExists(self):
+ directIOTestFile = os.path.join(
+ self.rhevMountPoint(),
+ '__DIRECT_IO_TEST__')
+ assert os.path.exists(directIOTestFile)
+
+ def volumeCreated(self, volumeInfo):
+ taskID, domainID, imageID, volumeID = volumeInfo
+ self.waitUntilVDSMTaskFinished(taskID, 20)
+ domainRoot = os.path.join(self.rhevMountPoint(), domainID)
+ imageDirectory = os.path.join(domainRoot, 'images', imageID)
+ assert os.path.exists(imageDirectory)
+ volumeFile = os.path.join(imageDirectory, volumeID)
+ volumeLease = '%s.lease' % volumeFile
+ volumeMeta = '%s.meta' % volumeFile
+ for path in volumeFile, volumeLease, volumeMeta:
+ logging.info('verifying path: %s' % path)
+ assert os.path.exists(path)
+
+
+class FileBased(base.StorageBackend):
+ def createVolume(self, size):
+ PREALLOCATE = 1
+ result = self.vdsm().createVolume(
+ self.domainID(),
+ self.poolID(),
+ self.imageID(),
+ self.largeIntegerXMLRPCWorkaround(size),
+ storage.volume.RAW_FORMAT,
+ PREALLOCATE,
+ storage.image.DATA_DISK_TYPE,
+ self.volumeID(),
+ self.randomName('volume_description'))
+ taskID = result['uuid']
+ return taskID, self.domainID(), self.imageID(), self.volumeID()
diff --git a/tests/functional/testlib/storagecontexts/iscsi.py b/tests/functional/testlib/storagecontexts/iscsi.py
index 0e891ee..eb19715 100644
--- a/tests/functional/testlib/storagecontexts/iscsi.py
+++ b/tests/functional/testlib/storagecontexts/iscsi.py
@@ -60,11 +60,7 @@
base.StorageBackend.__init__(self)
self._iqn = 'iqn.1970-01.functional.test:%04d' %\
random.randint(1, 10000)
- self._volumeGroup = {'uuid': self.newUUID(), 'vgs_uuid': None}
- self._poolID = self.newUUID()
- self._connectionID = self.newUUID()
- self._imageID = self.newUUID()
- self._volumeID = self.newUUID()
+ self._volumeGroup = {'uuid': self.domainID(), 'vgs_uuid': None}
def _targetcli(self, command):
commandAsList = command.split()
@@ -77,6 +73,16 @@
self._fileioBackstore = self.randomName('backfile')
logging.info('using %s, %s' %
(self._fileioBackstore, self._storageFile))
+ self._setupISCSITarget()
+ return (self,
+ Verify(
+ self._iqn,
+ self._volumeGroup,
+ self.vdsm,
+ self.volumeID())
+ )
+
+ def _setupISCSITarget(self):
self._targetcli('/backstores/fileio create %s %s 10G' %
(self._fileioBackstore, self._storageFile))
self._targetcli('/iscsi create %s' % self._iqn)
@@ -88,13 +94,6 @@
self._targetcli('/iscsi/%s/tpg1 set attribute '
'generate_node_acls=1 '
'cache_dynamic_acls=1' % self._iqn)
- return (self,
- Verify(
- self._iqn,
- self._volumeGroup,
- self.vdsm,
- self._volumeID)
- )
def __exit__(self, *args):
doubleDashed = self._volumeGroup['uuid'].replace('-', '--')
@@ -145,27 +144,13 @@
'user': '',
'tpgt': '1',
'password': '',
- 'id': self._connectionID,
+ 'id': self.connectionID(),
'port': '3260'
}
self.vdsm().disconnectStorageServer(
storage.sd.ISCSI_DOMAIN,
- self._poolID,
+ self.poolID(),
[connection])
-
- def createStoragePool(self):
- POOL_TYPE_DEPRECATED = 0
- self.vdsm().createStoragePool(
- POOL_TYPE_DEPRECATED,
- self._poolID,
- self.randomName('pool'),
- self._domainID(),
- [self._domainID()],
- 1)
- return self._poolID
-
- def _domainID(self):
- return self._volumeGroup['uuid']
def _createVG(self):
lun = self._findLUN()
@@ -194,14 +179,14 @@
def createVolume(self, size):
PREALLOCATE = 1
result = self.vdsm().createVolume(
- self._domainID(),
- self._poolID,
- self._imageID,
+ self.domainID(),
+ self.poolID(),
+ self.imageID(),
self.largeIntegerXMLRPCWorkaround(size),
storage.volume.RAW_FORMAT,
PREALLOCATE,
storage.image.DATA_DISK_TYPE,
- self._volumeID,
+ self.volumeID(),
self.randomName('iscsi_description'))
logging.info('createVolume result: %s' % result)
return result['uuid']
diff --git a/tests/functional/testlib/storagecontexts/localfs.py b/tests/functional/testlib/storagecontexts/localfs.py
index ef341a0..6ba2914 100644
--- a/tests/functional/testlib/storagecontexts/localfs.py
+++ b/tests/functional/testlib/storagecontexts/localfs.py
@@ -5,67 +5,24 @@
import storage.sd
import storage.volume
import storage.image
-from . import base
+from . import filebased
-class Verify(base.Verify):
+class Verify(filebased.Verify):
def __init__(self, directory, vdsm):
- base.Verify.__init__(self, vdsm)
+ filebased.Verify.__init__(self, vdsm)
self._directory = directory
- def storageServerConnected(self):
- self.sleepWhileVDSMCompletesTask(duration=2)
+ def rhevMountPoint(self):
transformedDirectory = self._directory.replace('/', '_')
- expectedSymlink = os.path.join(
+ result = os.path.join(
'/rhev/data-center/mnt/',
transformedDirectory)
- assert os.path.lexists(expectedSymlink)
-
- def storageDomainCreated(self, domainID):
- self.sleepWhileVDSMCompletesTask(duration=1)
- self._directIOTestFileExists()
- domainRoot = os.path.join(self._directory, domainID)
- expectedFiles = [
- 'images',
- 'dom_md',
- 'dom_md/leases',
- 'dom_md/inbox',
- 'dom_md/outbox',
- 'dom_md/metadata',
- 'dom_md/ids']
- expectedFullPaths =\
- [os.path.join(domainRoot, path) for path in expectedFiles]
- for path in expectedFullPaths:
- assert os.path.exists(path)
-
- def _directIOTestFileExists(self):
- self.sleepWhileVDSMCompletesTask(duration=1)
- directIOTestFile = os.path.join(self._directory, '__DIRECT_IO_TEST__')
- assert os.path.exists(directIOTestFile)
-
- def volumeCreated(self, volumeInfo):
- taskID, domainID, imageID, volumeID = volumeInfo
- self.waitUntilVDSMTaskFinished(taskID, 20)
- domain_directory = os.path.join(self._directory, domainID)
- image_directory = os.path.join(domain_directory, 'images', imageID)
- assert os.path.exists(image_directory)
- volume_file = os.path.join(image_directory, volumeID)
- volume_lease = '%s.lease' % volume_file
- volume_meta = '%s.meta' % volume_file
- for path in volume_file, volume_lease, volume_meta:
- assert os.path.exists(path)
+ return result
-class LocalFS(base.StorageBackend):
+class LocalFS(filebased.FileBased):
_NON_EXISTANT_POOL = '00000000-0000-0000-0000-000000000000'
-
- def __init__(self):
- base.StorageBackend.__init__(self)
- self._domainID = self.newUUID()
- self._poolID = self.newUUID()
- self._imageID = self.newUUID()
- self._volumeID = self.newUUID()
- self._connectionID = self.newUUID()
def __enter__(self):
self._createDirectoryForLocalFSStorage()
@@ -87,13 +44,13 @@
'user': '',
'tpgt': '1',
'password': '******',
- 'id': self._connectionID,
+ 'id': self.connectionID(),
'port': ''}
result = self.vdsm().connectStorageServer(
storage.sd.LOCALFS_DOMAIN,
self._NON_EXISTANT_POOL,
[localFilesystemConnection])
- assert result['statuslist'][0]['id'] == self._connectionID
+ assert result['statuslist'][0]['id'] == self.connectionID()
def disconnectStorageServer(self):
localFilesystemConnection = {
@@ -102,46 +59,20 @@
'user': '',
'tpgt': '1',
'password': '******',
- 'id': self._connectionID,
+ 'id': self.connectionID(),
'port': ''}
self.vdsm().disconnectStorageServer(
storage.sd.LOCALFS_DOMAIN,
self._NON_EXISTANT_POOL,
[localFilesystemConnection])
- def createStoragePool(self):
- POOL_TYPE_DEPRECATED = 0
- self.vdsm().createStoragePool(
- POOL_TYPE_DEPRECATED,
- self._poolID,
- self.randomName('pool'),
- self._domainID,
- [self._domainID],
- 1)
- return self._poolID
-
def createStorageDomain(self):
DOMAIN_VERSION = 3
self.vdsm().createStorageDomain(
storage.sd.LOCALFS_DOMAIN,
- self._domainID,
+ self.domainID(),
'some_name',
self._directory,
storage.sd.DATA_DOMAIN,
DOMAIN_VERSION)
- return self._domainID
-
- def createVolume(self, size):
- PREALLOCATE = 1
- result = self.vdsm().createVolume(
- self._domainID,
- self._poolID,
- self._imageID,
- self.largeIntegerXMLRPCWorkaround(size),
- storage.volume.RAW_FORMAT,
- PREALLOCATE,
- storage.image.DATA_DISK_TYPE,
- self._volumeID,
- self.randomName('localfs_description'))
- taskID = result['uuid']
- return taskID, self._domainID, self._imageID, self._volumeID
+ return self.domainID()
diff --git a/tests/functional/testlib/storagecontexts/nfs.py b/tests/functional/testlib/storagecontexts/nfs.py
new file mode 100644
index 0000000..cdde3a1
--- /dev/null
+++ b/tests/functional/testlib/storagecontexts/nfs.py
@@ -0,0 +1,115 @@
+import subprocess
+import tempfile
+import pwd
+import shutil
+import os
+import storage.sd
+from . import filebased
+
+
+class Verify(filebased.Verify):
+ def __init__(self, directory, vdsm):
+ filebased.Verify.__init__(self, vdsm)
+ self._directory = directory
+
+ def rhevMountPoint(self):
+ transformedDirectory = self._directory.replace('/', '_')
+ result = os.path.join(
+ '/rhev/data-center/mnt/',
+ '127.0.0.1:%s' % transformedDirectory)
+ return result
+
+
+class NFS(filebased.FileBased):
+ _NON_EXISTANT_POOL = '00000000-0000-0000-0000-000000000000'
+
+ def __enter__(self):
+ self._setupNFSExport()
+ return self, Verify(self._directory, self.vdsm)
+
+ def _setupNFSExport(self):
+ self._export()
+ self._restartNFSServer()
+ mountPoint = tempfile.mkdtemp()
+ COMMAND = 'sudo --non-interactive mount -t nfs localhost:%s %s' %\
+ (self._directory, mountPoint)
+ subprocess.check_call(COMMAND, shell=True)
+ self._setPermissions(mountPoint)
+ subprocess.check_call(
+ 'sudo --non-interactive umount %s' % mountPoint, shell=True)
+ os.rmdir(mountPoint)
+
+ def _export(self):
+ self._directory = tempfile.mkdtemp('', 'nfstest', '/var/tmp')
+ self._previousExports = open('/etc/exports').read()
+ with open('/etc/exports', 'a') as f:
+ line = '%s 127.0.0.1(rw,sync,no_root_squash,no_all_squash,fsid=0)\n'\
+ % self._directory
+ f.write(line)
+
+ def _unexport(self):
+ self._umount()
+ with open('/etc/exports', 'w') as f:
+ f.write(self._previousExports)
+ self._restartNFSServer()
+
+ def _umount(self):
+ COMMAND = 'sudo --non-interactive umount 127.0.0.1:%s'\
+ % self._directory
+ code = subprocess.call(COMMAND, shell=True)
+
+ def _setPermissions(self, directory):
+ vdsmUser = pwd.getpwnam('vdsm')
+ os.chown(directory, vdsmUser.pw_uid, vdsmUser.pw_gid)
+ os.chmod(directory, 0755)
+
+ def _restartNFSServer(self):
+ subprocess.check_call(
+ 'sudo --non-interactive service nfs-server restart', shell=True)
+
+ def __exit__(self, *args):
+ self._unexport()
+ shutil.rmtree(self._directory)
+
+ def connectStorageServer(self):
+ connection = {
+ 'connection': self._nfsPath(),
+ 'iqn': '',
+ 'user': '',
+ 'tpgt': '1',
+ 'password': '******',
+ 'id': self.connectionID(),
+ 'port': ''}
+ result = self.vdsm().connectStorageServer(
+ storage.sd.NFS_DOMAIN,
+ self._NON_EXISTANT_POOL,
+ [connection])
+ assert result['statuslist'][0]['id'] == self.connectionID()
+
+ def disconnectStorageServer(self):
+ connection = {
+ 'connection': self._nfsPath(),
+ 'iqn': '',
+ 'user': '',
+ 'tpgt': '1',
+ 'password': '******',
+ 'id': self.connectionID(),
+ 'port': ''}
+ self.vdsm().disconnectStorageServer(
+ storage.sd.LOCALFS_DOMAIN,
+ self._NON_EXISTANT_POOL,
+ [connection])
+
+ def createStorageDomain(self):
+ DOMAIN_VERSION = 3
+ self.vdsm().createStorageDomain(
+ storage.sd.NFS_DOMAIN,
+ self.domainID(),
+ 'some_name',
+ self._nfsPath(),
+ storage.sd.DATA_DOMAIN,
+ DOMAIN_VERSION)
+ return self.domainID()
+
+ def _nfsPath(self):
+ return '127.0.0.1:%s' % self._directory
--
To view, visit http://gerrit.ovirt.org/32990
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I1781fc400c0604855d3143dde22ccb29e6cc8013
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Yoav Kleinberger <ykleinbe(a)redhat.com>
8 years, 7 months
Change in vdsm[master]: tests: new functional tests for vdsm storage
by ykleinbe@redhat.com
Yoav Kleinberger has uploaded a new change for review.
Change subject: tests: new functional tests for vdsm storage
......................................................................
tests: new functional tests for vdsm storage
Ultimately, the purpose of this patch is to replace the existing
tests/functional/storageTests.py, henceforth "the old test".
The old test does not, in fact, verify VDSM behaviour. It only checks
for the return codes that VDSM returns to its caller.
This patch introduces a framework of "test contexts" that is extensible to various
storage backends. Each test context, be it iscsi, nfs or some other
storage type, knows how to tell VDSM to create its particular type of
storage domain, and also knows how to verify that observable actions
(e.g. the creation of a logical volume in an LVM volume group) have
actually been performed.
Currently, only localfs and iscsi are supported. Other storage backends
will be added later.
Change-Id: I1703e7c1dc223ff707775865cd14c7dd62314caf
Bug-Url: https://bugzilla.redhat.com/??????
Signed-off-by: Yoav Kleinberger <ykleinbe(a)redhat.com>
---
A tests/functional/basicStorageTest.py
A tests/functional/testlib/__init__.py
A tests/functional/testlib/controlvdsm.py
A tests/functional/testlib/testcontexts/__init__.py
A tests/functional/testlib/testcontexts/base.py
A tests/functional/testlib/testcontexts/iscsi.py
A tests/functional/testlib/testcontexts/localfs.py
A tests/run_functional_storage_tests.sh
8 files changed, 459 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/96/32496/1
diff --git a/tests/functional/basicStorageTest.py b/tests/functional/basicStorageTest.py
new file mode 100644
index 0000000..c78e4b2
--- /dev/null
+++ b/tests/functional/basicStorageTest.py
@@ -0,0 +1,42 @@
+import storage.volume
+import storage.image
+import logging
+from testlib import controlvdsm
+from testlib.testcontexts import localfs
+from testlib.testcontexts import iscsi
+
+class TestBasicStorageDomain:
+ @classmethod
+ def setup_class(cls):
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s TEST %(levelname)s: %(message)s')
+
+ def setup(self):
+ controlVDSM = controlvdsm.ControlVDSM()
+ controlVDSM.cleanup()
+
+ def testCreateVolumeLocalFS(self):
+ self._testCreateVolume(localfs.LocalFS)
+
+ def testCreateVolumeISCSI(self):
+ self._testCreateVolume(iscsi.ISCSI)
+
+ def _testCreateVolume(self, storageContext):
+ with storageContext() as (vdsm, verify):
+ storageServerID = vdsm.connectStorageServer()
+ verify.storageServerConnected()
+
+ domainID = vdsm.createStorageDomain()
+ verify.storageDomainCreated(domainID)
+
+ poolID = vdsm.createStoragePool()
+ verify.storagePoolCreated(poolID, masterDomainID = domainID)
+
+ vdsm.connectStoragePool(poolID, masterDomainID = domainID)
+ vdsm.spmStart(poolID)
+ verify.spmStarted(poolID)
+
+ vdsm.activateStorageDomain(domainID, poolID)
+ GIGABYTE = 1024 ** 3
+ volumeInfo = vdsm.createVolume(1 * GIGABYTE)
+ verify.volumeCreated(volumeInfo)
+ vdsm.disconnectStorageServer()
diff --git a/tests/functional/testlib/__init__.py b/tests/functional/testlib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/functional/testlib/__init__.py
diff --git a/tests/functional/testlib/controlvdsm.py b/tests/functional/testlib/controlvdsm.py
new file mode 100644
index 0000000..a983bf3
--- /dev/null
+++ b/tests/functional/testlib/controlvdsm.py
@@ -0,0 +1,53 @@
+import subprocess
+import logging
+import vdsm.vdscli
+import socket
+import vdsm.config
+import time
+
+class ControlVDSM:
+ def cleanup(self):
+ self._stopService()
+ assert not self._serviceRunning()
+ self._brutallyCleanFiles()
+ self._restartService()
+ return self._checkConnection()
+
+ def _checkConnection(self):
+ useSSL = vdsm.config.config.getboolean('vars', 'ssl')
+ vdsmClient = vdsm.vdscli.connect(useSSL=useSSL)
+ RETRIES = 5
+ for _ in range(RETRIES):
+ try:
+ vdsmClient.getStorageDomainsList()
+ logging.info('connection to VDSM succeeded')
+ return
+ except socket.error as e:
+ logging.warning('could not talk to VDSM: %s' % e)
+ time.sleep(1)
+
+ raise Exception('could not connect to VDSM')
+
+ def _stopService(self):
+ self._run("sudo service vdsmd stop")
+
+ def _serviceRunning(self):
+ returnCode = subprocess.call('sudo service vdsmd status', shell=True, stdout=open('/dev/null','w'), stderr=open('/dev/null','w'))
+ logging.info('vdsm running: %s' % (returnCode == 0))
+ return returnCode == 0
+
+ def _restartService(self):
+ self._run("sudo vdsm-tool configure --force")
+ self._run("sudo service vdsmd start")
+
+ def _run(self, command):
+ logging.info('running: %s' % command)
+ returnCode = subprocess.call(command, shell=True, close_fds=True, stdout=open('/dev/null','w'), stderr=open('/dev/null','w'))
+ if returnCode != 0:
+ logging.warning('failure! command was: %s' % command)
+ else:
+ logging.info('finished.')
+
+ def _brutallyCleanFiles(self):
+ logging.warning('removing /rhev/data-center without asking too many questions')
+ self._run('sudo rm -fr /rhev/data-center/*')
diff --git a/tests/functional/testlib/testcontexts/__init__.py b/tests/functional/testlib/testcontexts/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/functional/testlib/testcontexts/__init__.py
diff --git a/tests/functional/testlib/testcontexts/base.py b/tests/functional/testlib/testcontexts/base.py
new file mode 100644
index 0000000..bccebdf
--- /dev/null
+++ b/tests/functional/testlib/testcontexts/base.py
@@ -0,0 +1,101 @@
+import os
+import vdsm.vdscli
+import vdsm.config
+import random
+import uuid
+import logging
+import time
+
+class Verify:
+ def __init__(self, vdsm):
+ self._vdsm = vdsm
+
+ def assertPathExists(self, path, link = False):
+ logging.info('verifying path: %s' % path)
+ if link:
+ assert os.path.lexists(path)
+ else:
+ assert os.path.exists(path)
+
+ def assertPathDoesNotExist(self, path, link = False):
+ if link:
+ assert not os.path.lexists(path)
+ else:
+ assert not os.path.exists(path)
+
+ def waitForVDSMToFinishTask(self, duration = 1):
+ time.sleep(duration)
+
+ def storagePoolCreated(self, poolID, masterDomainID):
+ self.waitForVDSMToFinishTask()
+ linkToDomain = os.path.join('/rhev/data-center', poolID, masterDomainID)
+ masterAliasLink = os.path.join('/rhev/data-center', poolID, 'mastersd')
+ logging.info('verifying domain %s in pool %s' % (masterDomainID, poolID))
+ assert os.path.lexists(linkToDomain)
+ logging.info('verifying symlink to master domain')
+ assert os.path.lexists(masterAliasLink)
+
+ def waitFor(self, timeout, description, predicate, *args, **kwargs):
+ logging.info('waiting for "%s"' % description)
+ start = time.time()
+ for _ in xrange(timeout):
+ if predicate(*args, **kwargs):
+ logging.info('it took %s seconds' % (time.time() - start))
+ return
+ time.sleep(1)
+
+ assert False, 'waited %s seconds for "%s" but it did not happen' % (timeout, description)
+
+ def spmStarted(self, poolID):
+ self.waitForVDSMToFinishTask()
+ masterDomainDirectory = '/rhev/data-center/%s/mastersd' % poolID
+ master = os.path.join(masterDomainDirectory, 'master')
+ tasks = os.path.join(master, 'tasks')
+ vms = os.path.join(master, 'vms')
+ allExist = lambda: ( os.path.exists(master) and os.path.exists(tasks) and os.path.exists(vms) )
+ self.waitFor(60, 'SPM related subdirectories exist', allExist)
+
+ def waitOnVDSMTask(self, taskID, timeout):
+ taskFinished = lambda: (self._taskStatus(taskID)['taskState'] == 'finished')
+ self.waitFor(timeout, 'vdsm task to be finished', taskFinished)
+ taskStatus = self._taskStatus(taskID)
+ assert taskStatus['code'] == 0, taskStatus['message']
+
+ def _taskStatus(self, taskID):
+ result = self._vdsm.getTaskStatus(taskID)
+ assert result['status']['code'] == 0
+ taskStatus = result['taskStatus']
+ return taskStatus
+
+class StorageBackend:
+ def __init__(self):
+ useSSL = vdsm.config.config.getboolean('vars', 'ssl')
+ self._vdsm = vdsm.vdscli.connect(useSSL=useSSL)
+
+ def newUUID(self):
+ return str(uuid.uuid4())
+
+ def randomName(self, base):
+ return "%s_%04d" % (base, random.randint(1,10000))
+
+ def connectStoragePool(self, poolID, masterDomainID):
+ SCSI_KEY_DEPRECATED = 0
+ result = self._vdsm.connectStoragePool(poolID, 1, SCSI_KEY_DEPRECATED, masterDomainID, 1)
+ self.verifyVDSMSuccess(result)
+
+ def spmStart(self, poolID):
+ RECOVERY_MODE_DEPRECATED = 0
+ SCSI_FENCING_DEPRECATED = 0
+ result = self._vdsm.spmStart(poolID, -1, '-1', SCSI_FENCING_DEPRECATED, RECOVERY_MODE_DEPRECATED)
+ self.verifyVDSMSuccess(result)
+
+ def activateStorageDomain(self, domainID, poolID):
+ result = self._vdsm.activateStorageDomain(domainID,poolID)
+ self.verifyVDSMSuccess(result)
+
+ def stringForXMLRPC(self, number):
+ return str(number)
+
+ def verifyVDSMSuccess(self, result):
+ if result[ 'status' ][ 'code' ] != 0:
+ raise Exception('expected OK result from VDSM, got "%s" instead' % str(result))
diff --git a/tests/functional/testlib/testcontexts/iscsi.py b/tests/functional/testlib/testcontexts/iscsi.py
new file mode 100644
index 0000000..96d6958
--- /dev/null
+++ b/tests/functional/testlib/testcontexts/iscsi.py
@@ -0,0 +1,150 @@
+import os
+import random
+import logging
+import tempfile
+import shutil
+import subprocess
+import glob
+import storage.sd
+import storage.volume
+import storage.image
+from . import base
+
+class Verify(base.Verify):
+ def __init__(self, iqn, volumeGroup, vdsm, volumeID):
+ base.Verify.__init__(self, vdsm)
+ self._iqn = iqn
+ self._volumeGroup = volumeGroup
+ self._volumeID = volumeID
+
+ def storageServerConnected(self):
+ targetNameFiles = glob.glob('/sys/devices/platform/host*/session*/iscsi_session/*/targetname')
+ targetNames = [ open(file).read().strip() for file in targetNameFiles ]
+ assert self._iqn in targetNames
+
+ def storageDomainCreated(self, domainID):
+ self.waitFor(10, 'storage domain exists', self._storageDomainVolumesExist, domainID)
+
+ def _storageDomainVolumesExist(self, domainID):
+ for name in [ 'ids', 'inbox', 'leases', 'master', 'metadata', 'outbox' ]:
+ doubleDashed = domainID.replace('-', '--')
+ expectedPath = os.path.join('/dev/mapper', '%s-%s' % (doubleDashed, name))
+ logging.info('verifying: %s' % expectedPath)
+ if not os.path.lexists(expectedPath):
+ return False
+
+ return True
+
+ def volumeCreated(self, taskID):
+ self.waitOnVDSMTask(taskID, 10)
+ result = subprocess.call('sudo lvs %s | grep %s' % (self._volumeGroup['uuid'], self._volumeID), shell = True)
+ assert result == 0, "did not find logical volume in volume group"
+
+class ISCSI(base.StorageBackend):
+ _NULL_UUID = '00000000-0000-0000-0000-000000000000'
+ def __init__(self):
+ base.StorageBackend.__init__(self)
+ self._iqn = 'iqn.1970-01.functional.test:%04d' % random.randint(1,10000)
+ self._volumeGroup = { 'uuid': self.newUUID(), 'vgs_uuid': None }
+ self._poolID = self.newUUID()
+ self._connectionID = self.newUUID()
+ self._imageID = self.newUUID()
+ self._volumeID = self.newUUID()
+ logging.info( 'using the following attributes:' )
+ for name in [ '_volumeGroup', '_iqn', '_poolID', '_connectionID', '_imageID', '_volumeID' ]:
+ logging.info( '%s => %s' % ( name, getattr(self,name) ) )
+
+ def _targetcli(self, command):
+ commandAsList = command.split()
+ logging.info('running targetcli: targetcli %s' % commandAsList)
+ subprocess.check_call( [ 'targetcli' ] + commandAsList, close_fds = True )
+
+ def __enter__(self):
+ self._testDirectory = tempfile.mkdtemp()
+ self._storageFile = os.path.join(self._testDirectory, 'testfile')
+ self._fileioBackstore = self.randomName('backfile')
+ logging.info('using %s, %s' % (self._fileioBackstore, self._storageFile))
+ self._targetcli('/backstores/fileio create %s %s 10G' % (self._fileioBackstore, self._storageFile))
+ self._targetcli('/iscsi create %s' % self._iqn)
+ self._targetcli('/iscsi/%s/tpg1/luns create /backstores/fileio/%s' % (self._iqn, self._fileioBackstore))
+ self._targetcli('/iscsi/%s/tpg1 set attribute authentication=0 demo_mode_write_protect=0' % self._iqn)
+ self._targetcli('/iscsi/%s/tpg1 set attribute generate_node_acls=1 cache_dynamic_acls=1' % self._iqn)
+ return self, Verify(self._iqn, self._volumeGroup, self._vdsm, self._volumeID)
+
+ def __exit__(self, *args):
+ doubleDashed = self._volumeGroup['uuid'].replace('-', '--')
+ mapperDevices = glob.glob('/dev/mapper/%s*' % doubleDashed ) + [ '/dev/mapper/%s' % self._lunGUID ]
+ for device in mapperDevices:
+ logging.info('removing %s' % device)
+ result = subprocess.call('sudo dmsetup remove %s' % device, shell=True)
+ if result != 0:
+ logging.warning('could not remove %s' % device)
+ self._targetcli( '/iscsi delete %s' % self._iqn )
+ self._targetcli( '/backstores/fileio delete %s' % self._fileioBackstore )
+ shutil.rmtree(self._testDirectory)
+
+ def connectStorageServer(self):
+ connection = {'connection': '127.0.0.1', 'iqn': self._iqn, 'user': '', 'tpgt': '1', 'password': '', 'id': self._NULL_UUID, 'port': '3260'}
+ result = self._vdsm.connectStorageServer(
+ storage.sd.ISCSI_DOMAIN,
+ self._NULL_UUID,
+ [ connection ])
+ self.verifyVDSMSuccess(result)
+ connectionDetails = result['statuslist'][0]
+ assert connectionDetails['status'] == 0
+ logging.info( 'got id %s', connectionDetails['id'] )
+
+ def _findLUN(self):
+ result = self._vdsm.getDeviceList(storage.sd.ISCSI_DOMAIN)
+ for device in result['devList']:
+ for path in device['pathlist']:
+ if path['iqn'] == self._iqn:
+ logging.info( 'vdsm reports our LUN %s' % device )
+ return device
+
+ raise Exception("LUN not found!")
+
+ def disconnectStorageServer(self):
+ connection = {'connection': '127.0.0.1', 'iqn': self._iqn, 'user': '', 'tpgt': '1', 'password': '', 'id': self._connectionID, 'port': '3260'}
+ result = self._vdsm.disconnectStorageServer(
+ storage.sd.ISCSI_DOMAIN,
+ self._poolID,
+ [ connection ])
+ self.verifyVDSMSuccess(result)
+
+ def createStoragePool(self):
+ POOL_TYPE_DEPRECATED = 0
+ result = self._vdsm.createStoragePool(POOL_TYPE_DEPRECATED, self._poolID, self.randomName('pool'), self._domainID(), [ self._domainID() ], 1)
+ self.verifyVDSMSuccess(result)
+ return self._poolID
+
+ def _domainID(self):
+ return self._volumeGroup['uuid']
+
+ def _createVG(self):
+ lun = self._findLUN()
+ result = self._vdsm.createVG(self._volumeGroup['uuid'], [ lun['GUID'] ])
+ logging.info('createVG returned %s' % result)
+ self.verifyVDSMSuccess(result)
+ self._volumeGroup[ 'vgs_uuid' ] = result[ 'uuid' ]
+ self._lunGUID = lun['GUID']
+
+ def createStorageDomain(self):
+ self._createVG()
+ self._createStorageDomain()
+ return self._volumeGroup['uuid']
+
+ def _createStorageDomain(self):
+ domainID = self._volumeGroup['uuid']
+ DOMAIN_VERSION = 3
+ domainName = self.randomName( 'some_name' )
+ result = self._vdsm.createStorageDomain(storage.sd.ISCSI_DOMAIN, domainID, domainName, self._volumeGroup['vgs_uuid'], storage.sd.DATA_DOMAIN, DOMAIN_VERSION)
+ self.verifyVDSMSuccess(result)
+
+ def createVolume(self, size):
+ PREALLOCATE = 1
+ result = self._vdsm.createVolume(self._domainID(), self._poolID, self._imageID,
+ self.stringForXMLRPC(size), storage.volume.RAW_FORMAT, PREALLOCATE, storage.image.DATA_DISK_TYPE, self._volumeID, self.randomName('iscsi_description'))
+ logging.info( 'createVolume result: %s' % result)
+ self.verifyVDSMSuccess(result)
+ return result[ 'uuid' ]
diff --git a/tests/functional/testlib/testcontexts/localfs.py b/tests/functional/testlib/testcontexts/localfs.py
new file mode 100644
index 0000000..b498b6e
--- /dev/null
+++ b/tests/functional/testlib/testcontexts/localfs.py
@@ -0,0 +1,104 @@
+import tempfile
+import pwd
+import shutil
+import os
+import storage.sd
+import storage.volume
+import storage.image
+from . import base
+
+class Verify(base.Verify):
+ def __init__(self, directory, vdsm):
+ base.Verify.__init__(self, vdsm)
+ self._directory = directory
+
+ def storageServerConnected(self):
+ self.waitForVDSMToFinishTask(2)
+ transformedDirectory = self._directory.replace( '/', '_' )
+ expectedSymlink = os.path.join('/rhev/data-center/mnt/', transformedDirectory)
+ self.assertPathExists(expectedSymlink, link = True)
+
+ def storageDomainCreated(self, domainID):
+ self.waitForVDSMToFinishTask()
+ self._directIOTestFileExists()
+ domainRoot = os.path.join(self._directory, domainID)
+ expectedFiles = [ 'images', 'dom_md', 'dom_md/leases', 'dom_md/inbox', 'dom_md/outbox', 'dom_md/metadata', 'dom_md/ids' ]
+ expectedFullPaths = [ os.path.join(domainRoot, path) for path in expectedFiles ]
+ for path in expectedFullPaths:
+ assert os.path.exists(path)
+
+ def _directIOTestFileExists(self):
+ self.waitForVDSMToFinishTask()
+ directIOTestFile = os.path.join(self._directory, '__DIRECT_IO_TEST__')
+ self.assertPathExists(directIOTestFile)
+
+ def volumeCreated(self, volumeInfo):
+ taskID, domainID, imageID, volumeID = volumeInfo
+ self.waitOnVDSMTask(taskID, 20)
+ domain_directory = os.path.join(self._directory, domainID)
+ image_directory = os.path.join(domain_directory, 'images', imageID)
+ self.assertPathExists(image_directory)
+ volume_file = os.path.join(image_directory, volumeID)
+ volume_lease = '%s.lease' % volume_file
+ volume_meta = '%s.meta' % volume_file
+ for path in volume_file, volume_lease, volume_meta:
+ self.assertPathExists(path)
+
+class LocalFS(base.StorageBackend):
+ _NON_EXISTANT_POOL = '00000000-0000-0000-0000-000000000000'
+ def __init__(self):
+ base.StorageBackend.__init__(self)
+ self._domainID = self.newUUID()
+ self._poolID = self.newUUID()
+ self._imageID = self.newUUID()
+ self._volumeID = self.newUUID()
+ self._connectionID = self.newUUID()
+
+ def __enter__(self):
+ self._createDirectoryForLocalFSStorage()
+ return self, Verify(self._directory, self._vdsm)
+
+ def _createDirectoryForLocalFSStorage(self):
+ self._directory = tempfile.mkdtemp('', 'localfstest', '/var/tmp')
+ vdsm_user = pwd.getpwnam('vdsm')
+ os.chown(self._directory, vdsm_user.pw_uid, vdsm_user.pw_gid)
+ os.chmod(self._directory, 0755)
+
+ def __exit__(self, *args):
+ shutil.rmtree(self._directory)
+
+ def connectStorageServer(self):
+ localFilesystemConnection = {'connection': self._directory, 'iqn': '', 'user': '', 'tpgt': '1', 'password': '******', 'id': self._connectionID, 'port': ''}
+ result = self._vdsm.connectStorageServer(
+ storage.sd.LOCALFS_DOMAIN,
+ self._NON_EXISTANT_POOL,
+ [ localFilesystemConnection ])
+ self.verifyVDSMSuccess(result)
+ assert result[ 'statuslist' ][ 0 ][ 'id' ] == self._connectionID
+
+ def disconnectStorageServer(self):
+ localFilesystemConnection = {'connection': self._directory, 'iqn': '', 'user': '', 'tpgt': '1', 'password': '******', 'id': self._connectionID, 'port': ''}
+ result = self._vdsm.disconnectStorageServer(
+ storage.sd.LOCALFS_DOMAIN,
+ self._NON_EXISTANT_POOL,
+ [ localFilesystemConnection ])
+ self.verifyVDSMSuccess(result)
+
+ def createStoragePool(self):
+ POOL_TYPE_DEPRECATED = 0
+ result = self._vdsm.createStoragePool(POOL_TYPE_DEPRECATED, self._poolID, self.randomName('pool'), self._domainID, [ self._domainID ], 1)
+ self.verifyVDSMSuccess(result)
+ return self._poolID
+
+ def createStorageDomain(self):
+ DOMAIN_VERSION = 3
+ result = self._vdsm.createStorageDomain(storage.sd.LOCALFS_DOMAIN, self._domainID, 'some_name', self._directory, storage.sd.DATA_DOMAIN, DOMAIN_VERSION)
+ self.verifyVDSMSuccess(result)
+ return self._domainID
+
+ def createVolume(self, size):
+ PREALLOCATE = 1
+ result = self._vdsm.createVolume(self._domainID, self._poolID, self._imageID, self.stringForXMLRPC(size), storage.volume.RAW_FORMAT, PREALLOCATE, storage.image.DATA_DISK_TYPE, self._volumeID, self.randomName('localfs_description'))
+ self.verifyVDSMSuccess(result)
+ taskID = result[ 'uuid' ]
+ return taskID, self._domainID, self._imageID, self._volumeID
diff --git a/tests/run_functional_storage_tests.sh b/tests/run_functional_storage_tests.sh
new file mode 100755
index 0000000..cb544ee
--- /dev/null
+++ b/tests/run_functional_storage_tests.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+if [[ "$1" = "--help" ]]; then
+ echo "run_functional_storage_tests.sh [--verbose]"
+ exit -1
+fi
+if [[ "$1" = "--verbose" ]]; then
+ verbose="--nologcapture"
+fi
+sudo PYTHONPATH=../lib:../vdsm:functional nosetests -s $verbose functional/basicStorageTest.py
--
To view, visit http://gerrit.ovirt.org/32496
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I1703e7c1dc223ff707775865cd14c7dd62314caf
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Yoav Kleinberger <ykleinbe(a)redhat.com>
8 years, 7 months
Change in vdsm[master]: tests: alignmentScanTests.py is a functional test
by ykleinbe@redhat.com
Yoav Kleinberger has uploaded a new change for review.
Change subject: tests: alignmentScanTests.py is a functional test
......................................................................
tests: alignmentScanTests.py is a functional test
Since alignmentScanTests invokes actual storage code, and does not mock
it out, it should be categorized as a functional test.
Change-Id: I4e20d17c3ebee1203bb5a721ce44d5867570ce8e
Signed-off-by: Yoav Kleinberger <ykleinbe(a)redhat.com>
---
M tests/Makefile.am
M tests/functional/Makefile.am
R tests/functional/alignmentScanTests.py
3 files changed, 1 insertion(+), 1 deletion(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/45/29745/1
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4ef8f7d..f7f4f97 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -23,7 +23,6 @@
SUBDIRS = functional
test_modules = \
- alignmentScanTests.py \
blocksdTests.py \
bridgeTests.py \
cPopenTests.py \
diff --git a/tests/functional/Makefile.am b/tests/functional/Makefile.am
index b87fdf0..4fc2cc8 100644
--- a/tests/functional/Makefile.am
+++ b/tests/functional/Makefile.am
@@ -33,6 +33,7 @@
vmRecoveryTests.py \
storageTests.py \
veth.py \
+ alignmentScanTests.py \
$(NULL)
dist_vdsmfunctests_DATA = \
diff --git a/tests/alignmentScanTests.py b/tests/functional/alignmentScanTests.py
similarity index 100%
rename from tests/alignmentScanTests.py
rename to tests/functional/alignmentScanTests.py
--
To view, visit http://gerrit.ovirt.org/29745
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I4e20d17c3ebee1203bb5a721ce44d5867570ce8e
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Yoav Kleinberger <ykleinbe(a)redhat.com>
8 years, 7 months
Change in vdsm[master]: tests: use 'localhost' explicitly in test
by ykleinbe@redhat.com
Yoav Kleinberger has uploaded a new change for review.
Change subject: tests: use 'localhost' explicitly in test
......................................................................
tests: use 'localhost' explicitly in test
Previously tests could on some machines (in case the machine has a
non-default hostname). Now, since we use 'localhost' explicitly, this
will not happen.
Change-Id: I89990cff46e64120262e250eee9238b49c4edee4
Signed-off-by: Yoav Kleinberger <ykleinbe(a)redhat.com>
---
M tests/functional/storageTests.py
1 file changed, 2 insertions(+), 1 deletion(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/07/28107/1
diff --git a/tests/functional/storageTests.py b/tests/functional/storageTests.py
index 80ba312..76ca91d 100644
--- a/tests/functional/storageTests.py
+++ b/tests/functional/storageTests.py
@@ -79,7 +79,8 @@
isSSL = config.getboolean('vars', 'ssl')
if isSSL and os.geteuid() != 0:
raise SkipTest("Must be root to use SSL connection to server")
- self.s = vdscli.connect(useSSL=isSSL)
+ address = 'localhost:%s' % config.get('addresses', 'management_port')
+ self.s = vdscli.connect(hostPort=address, useSSL=isSSL)
def assertVdsOK(self, vdsResult):
# code == 0 means OK
--
To view, visit http://gerrit.ovirt.org/28107
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I89990cff46e64120262e250eee9238b49c4edee4
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Yoav Kleinberger <ykleinbe(a)redhat.com>
8 years, 7 months
Change in vdsm[master]: network: Inexistence of a network in libvirt should not be f...
by osvoboda@redhat.com
Ondřej Svoboda has uploaded a new change for review.
Change subject: network: Inexistence of a network in libvirt should not be fatal to us
......................................................................
network: Inexistence of a network in libvirt should not be fatal to us
vdsm-restore-net-config (with unified persistence) is called often during
functional network tests. This is a rollback scenario and connectivity
is at stake.
A network missing in libvirt is a sign of a different failure
(and we are after you, traffic control!) but it must not harm the restoration
of networking (not only) after failed tests.
Change-Id: Ia6ae4b359d543b6e3ee560a5cb021e31d037c035
Signed-off-by: Ondřej Svoboda <osvoboda(a)redhat.com>
---
M vdsm/network/api.py
1 file changed, 3 insertions(+), 3 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/88/34988/1
diff --git a/vdsm/network/api.py b/vdsm/network/api.py
index 1b09c6c..4a2b0c5 100755
--- a/vdsm/network/api.py
+++ b/vdsm/network/api.py
@@ -691,9 +691,9 @@
del libvirt_nets[network]
_netinfo.updateDevices()
elif 'remove' in networkAttrs:
- raise ConfigNetworkError(ne.ERR_BAD_BRIDGE, "Cannot delete "
- "network %r: It doesn't exist in the "
- "system" % network)
+ logger.warning("Not deleting network {0!r}: it is unknown to "
+ "libvirtd".format(network))
+ del networks[network]
else:
networksAdded.add(network)
--
To view, visit http://gerrit.ovirt.org/34988
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia6ae4b359d543b6e3ee560a5cb021e31d037c035
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Ondřej Svoboda <osvoboda(a)redhat.com>
8 years, 7 months
Change in vdsm[master]: netinfo: Replace getVlanDevice/ID in bootstrap with ipwrappe...
by osvoboda@redhat.com
Ondřej Svoboda has uploaded a new change for review.
Change subject: netinfo: Replace getVlanDevice/ID in bootstrap with ipwrapper.getLink
......................................................................
netinfo: Replace getVlanDevice/ID in bootstrap with ipwrapper.getLink
Change-Id: I4bfb0fa3c341a517bf444832d0531b05393ae4a9
Signed-off-by: Ondřej Svoboda <osvoboda(a)redhat.com>
---
M lib/vdsm/netinfo.py
M vdsm_reg/deployUtil.py.in
2 files changed, 16 insertions(+), 16 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/60/35360/1
diff --git a/lib/vdsm/netinfo.py b/lib/vdsm/netinfo.py
index 10bbecb..aaae19f 100644
--- a/lib/vdsm/netinfo.py
+++ b/lib/vdsm/netinfo.py
@@ -223,7 +223,7 @@
def vlanSpeed(vlanName):
"""Returns the vlan's underlying device speed."""
- vlanDevName = getVlanDevice(vlanName)
+ vlanDevName = _getVlanDevice(vlanName)
vlanDev = getLink(vlanDevName)
if vlanDev.isNIC():
speed = nicSpeed(vlanDevName)
@@ -328,8 +328,8 @@
nics = []
for iface in ports(bridge):
if iface in vlans():
- iface = getVlanDevice(iface)
- vlan = getVlanID(iface)
+ iface = _getVlanDevice(iface)
+ vlan = _getVlanID(iface)
if iface in bondings():
bonding = iface
nics = slaves(iface)
@@ -690,13 +690,13 @@
return any(vlan.startswith(dev + '.') for vlan in vlans())
-def getVlanDevice(vlan):
+def _getVlanDevice(vlan):
""" Return the device of the given VLAN. """
vlanLink = getLink(vlan)
return vlanLink.device
-def getVlanID(vlan):
+def _getVlanID(vlan):
""" Return the ID of the given VLAN. """
vlanLink = getLink(vlan)
return int(vlanLink.vlanid)
@@ -785,7 +785,7 @@
if iface == interface:
yield (network, None)
elif interface.startswith(iface + '.'):
- yield (network, getVlanID(interface))
+ yield (network, _getVlanID(interface))
def getBridgelessNetworksAndVlansForIface(self, iface):
""" Returns tuples of (network, vlan) connected to nic/bond """
@@ -794,11 +794,11 @@
if iface == netdict['iface']:
yield (network, None)
elif netdict['iface'].startswith(iface + '.'):
- yield (network, getVlanID(netdict['iface']))
+ yield (network, _getVlanID(netdict['iface']))
def getVlansForIface(self, iface):
for vlanDevName in self.getVlanDevsForIface(iface):
- yield getVlanID(vlanDevName)
+ yield _getVlanID(vlanDevName)
def getVlanDevsForIface(self, iface):
for v, vdict in self.vlans.iteritems():
@@ -852,8 +852,8 @@
for port in ports:
if port in self.vlans:
assert vlan is None
- nic = getVlanDevice(port)
- vlan = getVlanID(port)
+ nic = _getVlanDevice(port)
+ vlan = _getVlanID(port)
assert self.vlans[port]['iface'] == nic
port = nic
if port in self.bondings:
diff --git a/vdsm_reg/deployUtil.py.in b/vdsm_reg/deployUtil.py.in
index 2899411..5910bd7 100644
--- a/vdsm_reg/deployUtil.py.in
+++ b/vdsm_reg/deployUtil.py.in
@@ -972,21 +972,21 @@
This helper function extract the relevant parameters of the existing
RHEL interface, in order to create a managment bridge.
"""
- netinfo = vdsmImport("netinfo")
+ ipwrapper = vdsmImport("ipwrapper")
vlan = ''
bonding = ''
nic = None
try:
- vlans = netinfo.vlans()
- if mgtIface in vlans:
- nic = netinfo.getVlanDevice(mgtIface)
- vlan = netinfo.getVlanID(mgtIface)
+ link = ipwrapper.getLink(mgtIface)
+ if link.isVLAN():
+ nic = link.device
+ vlan = link.vlanid
else:
nic = mgtIface
- if nic in netinfo.bondings():
+ if ipwrapper.getLink(nic).isBOND():
logging.error(
(
"_getRHELBridgeParams Found bonding: %s."
--
To view, visit http://gerrit.ovirt.org/35360
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I4bfb0fa3c341a517bf444832d0531b05393ae4a9
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Ondřej Svoboda <osvoboda(a)redhat.com>
8 years, 7 months
Change in vdsm[master]: zombiereaper: Add ability to wait on process
by smizrahi@redhat.com
Saggi Mizrahi has uploaded a new change for review.
Change subject: zombiereaper: Add ability to wait on process
......................................................................
zombiereaper: Add ability to wait on process
This adds the ability to wait on a process with timeout in an efficient
manner.
When the process receives SIGCHLD in addition to clearing the tracked
pids it will now call poll() on requested C\Popen objects. This will
allow the called to synchronously wait for a process to exit with
timeout.
Change-Id: Idcd69b6187bc1c412c11f6cba9af431557ea7077
Signed-off-by: Saggi Mizrahi <smizrahi(a)redhat.com>
---
M lib/vdsm/infra/zombiereaper/__init__.py
M lib/vdsm/infra/zombiereaper/tests.py
2 files changed, 79 insertions(+), 5 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/55/35055/1
diff --git a/lib/vdsm/infra/zombiereaper/__init__.py b/lib/vdsm/infra/zombiereaper/__init__.py
index f85ec30..2169ffa 100644
--- a/lib/vdsm/infra/zombiereaper/__init__.py
+++ b/lib/vdsm/infra/zombiereaper/__init__.py
@@ -28,8 +28,10 @@
import os
import signal
+from threading import Condition
_trackedPids = set()
+_waited_on_procs = {}
def autoReapPID(pid):
@@ -46,6 +48,7 @@
pid, rv = os.waitpid(pid, os.WNOHANG)
if pid != 0:
_trackedPids.discard(pid)
+
except OSError:
_trackedPids.discard(pid)
@@ -54,6 +57,52 @@
for pid in _trackedPids.copy():
_tryReap(pid)
+ for proc in _waited_on_procs.keys():
+ _tryWait(proc)
+
+
+def _tryWait(proc):
+ res = None
+ err = None
+ try:
+ res = proc.poll()
+ except Exception as e:
+ err = e
+ finally:
+ try:
+ c = _waited_on_procs[proc]
+ except KeyError:
+ pass
+ else:
+ with c:
+ if c == _waited_on_procs[proc]:
+ _waited_on_procs[proc] = (res, err)
+ c.notify_all()
+
+
+def waitproc(proc, timeout=None):
+ """Wait until a process exits
+
+ Will block until the process exists or timeout (in seconds) has passed.
+ Passing None as timeout (the default) will block forever but in that case
+ it's recommended to just use the built in wait() functionality.
+ """
+ c = Condition()
+ with c:
+ if c != _waited_on_procs.setdefault(proc, c):
+ raise ValueError("proc already being tracked")
+
+ c.wait(timeout)
+ res = _waited_on_procs.pop(proc)
+ if res == c:
+ return None
+ else:
+ rv, err = res
+ if err is not None:
+ raise err
+
+ return rv
+
def registerSignalHandler():
"""
diff --git a/lib/vdsm/infra/zombiereaper/tests.py b/lib/vdsm/infra/zombiereaper/tests.py
index ee4fb02..817aa13 100644
--- a/lib/vdsm/infra/zombiereaper/tests.py
+++ b/lib/vdsm/infra/zombiereaper/tests.py
@@ -19,16 +19,17 @@
#
from time import sleep
import os
+from threading import Thread
+
+import nose.tools as nt
from .. import zombiereaper
from cpopen import CPopen
-from unittest import TestCase
-
zombiereaper.registerSignalHandler()
-class zombieReaperTests(TestCase):
+class TestZombieReaper(object):
def testProcessDiesAfterBeingTracked(self):
p = CPopen(["sleep", "1"])
zombiereaper.autoReapPID(p.pid)
@@ -36,7 +37,7 @@
sleep(4)
# Throws error because pid is not found or is not child
- self.assertRaises(OSError, os.waitpid, p.pid, os.WNOHANG)
+ nt.assert_raises(OSError, os.waitpid, p.pid, os.WNOHANG)
def testProcessDiedBeforeBeingTracked(self):
p = CPopen(["sleep", "0"])
@@ -46,4 +47,28 @@
zombiereaper.autoReapPID(p.pid)
# Throws error because pid is not found or is not child
- self.assertRaises(OSError, os.waitpid, p.pid, os.WNOHANG)
+ nt.assert_raises(OSError, os.waitpid, p.pid, os.WNOHANG)
+
+ def testWaitProc(self):
+ p = CPopen(["sleep", "1"])
+ nt.assert_is_not_none(zombiereaper.waitproc(p, 2))
+ nt.assert_is_not_none(p.returncode)
+
+ def testWaitProcCallTwice(self):
+ p = CPopen(["sleep", "3"])
+ nt.assert_is_none(zombiereaper.waitproc(p, 1))
+ nt.assert_is_not_none(zombiereaper.waitproc(p, 3))
+ nt.assert_is_not_none(p.returncode)
+
+ def testWaitProcRegisterTwice(self):
+ p = CPopen(["sleep", "2"])
+ t = Thread(
+ name="waiter",
+ target=zombiereaper.waitproc,
+ args=(p, 4),
+ )
+ t.setDaemon(True)
+ t.start()
+ sleep(1)
+ nt.assert_raises(ValueError, zombiereaper.waitproc, p, 1)
+ t.join()
--
To view, visit http://gerrit.ovirt.org/35055
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Idcd69b6187bc1c412c11f6cba9af431557ea7077
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Saggi Mizrahi <smizrahi(a)redhat.com>
8 years, 8 months
Change in vdsm[master]: vmxml.py, vm.py: Remove QEMU passthrough hack in ppc64
by vdelima@redhat.com
Vitor de Lima has uploaded a new change for review.
Change subject: vmxml.py, vm.py: Remove QEMU passthrough hack in ppc64
......................................................................
vmxml.py, vm.py: Remove QEMU passthrough hack in ppc64
Recent libvirt versions already include a virtual USB keyboard and
mouse in guests with graphical consoles. This patch does not include
any information about input devices in the domain XML in order to
libvirt handle the creation of these devices automatically.
Change-Id: I64d660bf7534203d5d5cdbc318ffd1429a16f954
Bug-Url: https://bugzilla.redhat.com/show_bug.cgi?id=1122627
Signed-off-by: Vitor de Lima <vdelima(a)redhat.com>
---
M vdsm/virt/vm.py
M vdsm/virt/vmxml.py
2 files changed, 3 insertions(+), 27 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/71/33871/1
diff --git a/vdsm/virt/vm.py b/vdsm/virt/vm.py
index 1eebb69..5b39752 100644
--- a/vdsm/virt/vm.py
+++ b/vdsm/virt/vm.py
@@ -2679,7 +2679,9 @@
_VMCHANNEL_DEVICE_NAME)
domxml._appendAgentDevice(self._qemuguestSocketFile.decode('utf-8'),
_QEMU_GA_DEVICE_NAME)
- domxml.appendInput()
+
+ if self.arch == caps.Architecture.X86_64:
+ domxml.appendInput()
if self.arch == caps.Architecture.PPC64:
domxml.appendEmulator()
@@ -2694,9 +2696,6 @@
for drive in self._devices[DISK_DEVICES][:]:
for leaseElement in drive.getLeasesXML():
domxml._devices.appendChild(leaseElement)
-
- if self.arch == caps.Architecture.PPC64:
- domxml.appendKeyboardDevice()
return domxml.toxml()
diff --git a/vdsm/virt/vmxml.py b/vdsm/virt/vmxml.py
index eb07ba9..a83adb1 100644
--- a/vdsm/virt/vmxml.py
+++ b/vdsm/virt/vmxml.py
@@ -165,14 +165,6 @@
domainAttrs = {'type': domainType}
- # Hack around libvirt issue BZ#988070, this is going to be removed as
- # soon as the domain XML format supports the specification of USB
- # keyboards
-
- if self.arch == caps.Architecture.PPC64:
- domainAttrs['xmlns:qemu'] = \
- 'http://libvirt.org/schemas/domain/qemu/1.0'
-
self.dom = Element('domain', **domainAttrs)
self.doc.appendChild(self.dom)
@@ -444,21 +436,6 @@
inputAttrs = {'type': 'mouse', 'bus': mouseBus}
self._devices.appendChildWithArgs('input', **inputAttrs)
-
- def appendKeyboardDevice(self):
- """
- Add keyboard device for ppc64 using a QEMU argument directly.
- This is a workaround to the issue BZ#988070 in libvirt
-
- <qemu:commandline>
- <qemu:arg value='-usbdevice'/>
- <qemu:arg value='keyboard'/>
- </qemu:commandline>
- """
- commandLine = Element('qemu:commandline')
- commandLine.appendChildWithArgs('qemu:arg', value='-usbdevice')
- commandLine.appendChildWithArgs('qemu:arg', value='keyboard')
- self.dom.appendChild(commandLine)
def appendEmulator(self):
emulatorPath = '/usr/bin/qemu-system-' + self.arch
--
To view, visit http://gerrit.ovirt.org/33871
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I64d660bf7534203d5d5cdbc318ffd1429a16f954
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Vitor de Lima <vdelima(a)redhat.com>
8 years, 8 months
Change in vdsm[master]: multipath: Move all calls to multipath exe to a single method
by smizrahi@redhat.com
Saggi Mizrahi has uploaded a new change for review.
Change subject: multipath: Move all calls to multipath exe to a single method
......................................................................
multipath: Move all calls to multipath exe to a single method
This makes the code a bit cleaner
Change-Id: I52afc07a07a925ed7572eb369deb7c203edb04cd
Signed-off-by: Saggi Mizrahi <smizrahi(a)redhat.com>
---
M vdsm/storage/multipath.py
1 file changed, 11 insertions(+), 4 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/55/19255/1
diff --git a/vdsm/storage/multipath.py b/vdsm/storage/multipath.py
index 924d747..c31b5c3 100644
--- a/vdsm/storage/multipath.py
+++ b/vdsm/storage/multipath.py
@@ -94,6 +94,10 @@
)
+def _runCmd(args):
+ return misc.execCmd([constants.EXT_MULTIPATH] + args, sudo=True)
+
+
def rescan():
"""
Forces multipath daemon to rescan the list of available devices and
@@ -108,8 +112,8 @@
supervdsm.getProxy().forceScsiScan()
# Now let multipath daemon pick up new devices
- cmd = [constants.EXT_MULTIPATH, "-r"]
- misc.execCmd(cmd, sudo=True)
+
+ _runCmd("-r")
def isEnabled():
@@ -154,6 +158,10 @@
return False
+def flushAll():
+ _runCmd("-F")
+
+
def setupMultipath():
"""
Set up the multipath daemon configuration to the known and
@@ -173,8 +181,7 @@
raise se.MultipathSetupError()
misc.persistFile(MPATH_CONF)
- # Flush all unused multipath device maps
- misc.execCmd([constants.EXT_MULTIPATH, "-F"], sudo=True)
+ flushAll()
cmd = [constants.EXT_VDSM_TOOL, "service-reload", "multipathd"]
rc = misc.execCmd(cmd, sudo=True)[0]
--
To view, visit http://gerrit.ovirt.org/19255
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I52afc07a07a925ed7572eb369deb7c203edb04cd
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Saggi Mizrahi <smizrahi(a)redhat.com>
8 years, 8 months
Change in vdsm[master]: network: ifcfg: synchronous ifup
by phoracek@redhat.com
Petr Horáček has uploaded a new change for review.
Change subject: network: ifcfg: synchronous ifup
......................................................................
network: ifcfg: synchronous ifup
Change-Id: I0c90a556f5fc52c1b8e675986ac39b6032ca0197
Signed-off-by: Petr Horáček <phoracek(a)redhat.com>
---
M vdsm/network/configurators/ifcfg.py
1 file changed, 63 insertions(+), 9 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/66/37366/1
diff --git a/vdsm/network/configurators/ifcfg.py b/vdsm/network/configurators/ifcfg.py
index 65e02b5..785c7f2 100644
--- a/vdsm/network/configurators/ifcfg.py
+++ b/vdsm/network/configurators/ifcfg.py
@@ -18,6 +18,7 @@
#
from __future__ import absolute_import
+from contextlib import contextmanager
import glob
import logging
import os
@@ -36,13 +37,14 @@
from vdsm import netinfo
from vdsm import utils
from vdsm.netconfpersistence import RunningConfig
+from vdsm.netlink import monitor
if utils.isOvirtNode():
from ovirt.node.utils import fs as node_fs
from . import Configurator, dhclient, getEthtoolOpts, libvirt
from ..errors import ConfigNetworkError, ERR_FAILED_IFUP
-from ..models import Nic, Bridge, IpConfig
+from ..models import Nic, Bridge, IpConfig, NetDevice
from ..sourceroute import StaticSourceRoute, DynamicSourceRoute
import dsaversion # TODO: Make parent package import when vdsm is a package
@@ -89,13 +91,13 @@
if bridge.port:
bridge.port.configure(**opts)
self._addSourceRoute(bridge)
- ifup(bridge.name, bridge.ipConfig.async)
+ ifup(bridge, bridge.ipConfig.async)
def configureVlan(self, vlan, **opts):
self.configApplier.addVlan(vlan, **opts)
vlan.device.configure(**opts)
self._addSourceRoute(vlan)
- ifup(vlan.name, vlan.ipConfig.async)
+ ifup(vlan, vlan.ipConfig.async)
def configureBond(self, bond, **opts):
self.configApplier.addBonding(bond, **opts)
@@ -105,7 +107,7 @@
for slave in bond.slaves:
slave.configure(**opts)
self._addSourceRoute(bond)
- ifup(bond.name, bond.ipConfig.async)
+ ifup(bond, bond.ipConfig.async)
if self.unifiedPersistence:
self.runningConfig.setBonding(
bond.name, {'options': bond.options,
@@ -142,11 +144,11 @@
if slave.name in nicsToAdd:
ifdown(slave.name) # nics must be down to join a bond
self.configApplier.addNic(slave)
- ifup(slave.name)
+ ifup(slave)
if bondIfcfgWritten:
ifdown(bond.name)
- ifup(bond.name)
+ ifup(bond)
if self.unifiedPersistence:
self.runningConfig.setBonding(
bond.name, {'options': bond.options,
@@ -158,7 +160,7 @@
if nic.bond is None:
if not netinfo.isVlanned(nic.name):
ifdown(nic.name)
- ifup(nic.name, nic.ipConfig.async)
+ ifup(nic, nic.ipConfig.async)
def removeBridge(self, bridge):
DynamicSourceRoute.addInterfaceTracking(bridge)
@@ -216,7 +218,7 @@
if to_be_removed:
self.configApplier.removeNic(nic.name)
if nic.name in netinfo.nics():
- ifup(nic.name)
+ ifup(nic)
else:
logging.warning('host interface %s missing', nic.name)
else:
@@ -804,7 +806,8 @@
def ifup(iface, async=False):
- "Bring up an interface"
+ """Bring up an interface. Parameter iface could be iface's name or
+ a NetDevice object."""
def _ifup(netIf):
rc, out, err = utils.execCmd([constants.EXT_IFUP, netIf], raw=False)
@@ -815,17 +818,68 @@
return rc, out, err
if async:
+ iface = iface.name if isinstance(iface, NetDevice) else iface
+
# wait for dhcp in another thread,
# so vdsm won't get stuck (BZ#498940)
t = threading.Thread(target=_ifup, name='ifup-waiting-on-dhcp',
args=(iface,))
t.daemon = True
t.start()
+ elif isinstance(iface, NetDevice) and iface.master is None:
+ # TEMP NOTE: ip could be None or IpConfig(IPv4(None,...),
+ # IPv6(None,...)...)
+ if iface.ip and (iface.ip.ipv4.address or iface.ip.ipv6.address):
+ # TEMP NOTE: we have to call setupNetworks with IPv6 addr, prefix
+ # included
+ if iface.ip.ipv6.address:
+ expected_event = {'event': 'new_addr', 'label': iface.name,
+ 'address': iface.ip.ipv6.address}
+ elif iface.ip.ipv4.address:
+ prefix = _netmask2prefix(iface.ip.ipv4.netmask)
+ expected_event = {'event': 'new_addr', 'label': iface.name,
+ 'address': iface.ip.ipv4.address + '/' + prefix}
+ else:
+ expected_event = {'event': 'new_addr', 'label': iface.name}
+
+ with _event_sniffer(expected_event):
+ rc, out, err = _ifup(iface.name)
+ return rc
else:
rc, out, err = _ifup(iface)
return rc
+def _netmask2prefix(netmask):
+ netmask = [int(octet) for octet in netmask]
+ return sum([sum([int(i) for i in list(bin(octet)[2:])])
+ for octet in netmask])
+
+
+@contextmanager
+def _event_sniffer(expected_event, timeout=10):
+ mon = monitor.Monitor(groups=('link', 'ipv4-ifaddr', 'ipv6-ifaddr'),
+ timeout=timeout)
+ mon.start()
+ try:
+ yield
+ finally:
+ try:
+ for event in mon:
+ if _is_subdict(expected_event, event):
+ mon.stop()
+ except monitor.MonitorError as e:
+ if e[0] == monitor.E_TIMEOUT:
+ logging.error('Expected event "%s" was not caught within the '
+ 'given timeout.', expected_event)
+ else:
+ raise
+
+
+def _is_subdict(subset, superset):
+ return all(item in superset.items() for item in subset.items())
+
+
def configuredPorts(nets, bridge):
"""Returns the list of ports a bridge has"""
ports = []
--
To view, visit http://gerrit.ovirt.org/37366
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I0c90a556f5fc52c1b8e675986ac39b6032ca0197
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Petr Horáček <phoracek(a)redhat.com>
8 years, 8 months