Yeela Kaplan has uploaded a new change for review.
Change subject: [WIP]configurators: move configurator functions into conf_utils
......................................................................
[WIP]configurators: move configurator functions into conf_utils
Change-Id: Id67a0f51adb2d543c0542a48c020d34a13d1f2ed
Signed-off-by: Yeela Kaplan <ykaplan(a)redhat.com>
---
M lib/vdsm/tool/Makefile.am
A lib/vdsm/tool/conf_utils.py
M vdsm.spec.in
3 files changed, 110 insertions(+), 1 deletion(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/86/44286/1
diff --git a/lib/vdsm/tool/Makefile.am b/lib/vdsm/tool/Makefile.am
index c8979ca..c3ed0cc 100644
--- a/lib/vdsm/tool/Makefile.am
+++ b/lib/vdsm/tool/Makefile.am
@@ -35,7 +35,8 @@
dump_bonding_defaults.py \
dump_volume_chains.py \
nwfilter.py \
- configfile.py \
+ conf_utils.py \
+ configfile.py \
configurator.py \
register.py \
restore_nets.py \
diff --git a/lib/vdsm/tool/conf_utils.py b/lib/vdsm/tool/conf_utils.py
new file mode 100644
index 0000000..1308097
--- /dev/null
+++ b/lib/vdsm/tool/conf_utils.py
@@ -0,0 +1,107 @@
+# Copyright 2015 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+import os
+
+from .. configfile import (
+ ConfigFile,
+)
+
+from ... import utils
+
+if utils.isOvirtNode():
+ from ovirt.node.utils.fs import Config as NodeCfg
+
+
+def get_file(fname, files):
+ return files[fname]['path']
+
+
+def remove_conf(files, version):
+ for cfile, content in files.items():
+ content['removeConf'](content['path'], version)
+
+
+def add_section(content, version, vdsmConfiguration={}):
+ """
+ Add a 'configuration section by vdsm' part to a config file.
+ This section contains only keys not originally defined
+ The section headers will include the current configuration version.
+ """
+ configuration = {}
+ for fragment in content['fragments']:
+ if vdsmConfiguration:
+ if is_applicable(fragment, vdsmConfiguration):
+ configuration.update(fragment['content'])
+ else:
+ configuration.update(fragment['content'])
+ if configuration:
+ with open_config(content['path'], version) as conff:
+ for key, val in configuration.items():
+ conff.addEntry(key, val)
+
+
+def remove_section(path, version):
+ """
+ remove entire 'configuration section by vdsm' section.
+ section is removed regardless of it's version.
+ """
+ if os.path.exists(path):
+ with open_config(path, version) as conff:
+ conff.removeConf()
+
+
+def remove_file(content, vdsmConfiguration):
+ """
+ delete a file if it exists.
+ """
+ if utils.isOvirtNode():
+ NodeCfg().delete(content['path'])
+ else:
+ try:
+ os.unlink(content['path'])
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+
+def get_persisted_files(files):
+ """
+ get files where vdsm is expected to add a section.
+ """
+ return [
+ cfile['path'] for cfile in files.values()
+ if cfile['persisted']
+ ]
+
+
+def open_config(path, conf_version):
+ return ConfigFile(path, conf_version)
+
+
+def is_applicable(fragment, vdsmConfiguration):
+ """
+ Return true if 'fragment' should be included for current
+ configuration. An applicable fragment is a fragment who's list
+ of conditions are met according to vdsmConfiguration.
+ """
+ applyFragment = True
+ for key, booleanValue in fragment['conditions'].items():
+ if vdsmConfiguration[key] != booleanValue:
+ applyFragment = False
+ return applyFragment
diff --git a/vdsm.spec.in b/vdsm.spec.in
index 5020d8b..21e1b50 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -1094,6 +1094,7 @@
%{python_sitelib}/%{vdsm_name}/tool/dummybr.py*
%{python_sitelib}/%{vdsm_name}/tool/dump_bonding_defaults.py*
%{python_sitelib}/%{vdsm_name}/tool/nwfilter.py*
+%{python_sitelib}/%{vdsm_name}/tool/conf_utils.py*
%{python_sitelib}/%{vdsm_name}/tool/configurator.py*
%{python_sitelib}/%{vdsm_name}/tool/configurators/__init__*
%{python_sitelib}/%{vdsm_name}/tool/configurators/certificates.py*
--
To view, visit https://gerrit.ovirt.org/44286
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Id67a0f51adb2d543c0542a48c020d34a13d1f2ed
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Yeela Kaplan <ykaplan(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: contrib: Simple jsonrpc client
......................................................................
contrib: Simple jsonrpc client
This is a simple jsonrpc client for communicating with the jsonrpc
server from the command line.
Arguments
method one of the mehtods described in json schema
params optionl json object with message parameters
Examples
Calling method without arguements:
# jsonrpc Host.getVMList
{
"jsonrpc": "2.0",
"id": "0e043d83-294a-4d31-b1b6-6dc2f2747494",
"result": [
"b3f6fa00-b315-4ad4-8108-f73da817b5c5"
]
}
Calling method with arguements:
# jsonrpc VM.getStats '{"vmID": "b3f6fa00-b315-4ad4-8108-f73da817b5c5"}'
{
"jsonrpc": "2.0",
"id": "cefd25a3-6250-4123-8a56-d7047899e19e",
"result": [
{
"status": "Down",
"exitMessage": "Admin shut down from the engine",
"vmId": "b3f6fa00-b315-4ad4-8108-f73da817b5c5",
"exitReason": 6,
"timeOffset": "0",
"exitCode": 0
}
]
}
Requires stomp.py library:
https://pypi.python.org/pypi/stomp.py
Change-Id: Ia6273eabf6f3601602659d1e4e748d8025ae8084
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
A contrib/jsonrpc
1 file changed, 104 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/81/35181/1
diff --git a/contrib/jsonrpc b/contrib/jsonrpc
new file mode 100755
index 0000000..3080193
--- /dev/null
+++ b/contrib/jsonrpc
@@ -0,0 +1,104 @@
+#!/usr/bin/python
+#
+# Copyright 2014 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+"""
+jsonrpc-cli - Vdsm jsonrpc client
+"""
+
+import json
+import os
+import signal
+import sys
+import time
+import uuid
+import optparse
+
+import stomp
+
+# Copied from lib/vdsm/vdscli.py
+PKIDIR = '/etc/pki/vdsm'
+KEYFILE = os.path.join(PKIDIR, 'keys/vdsmkey.pem')
+CERTFILE = os.path.join(PKIDIR, 'certs/vdsmcert.pem')
+CACERT = os.path.join(PKIDIR, 'certs/cacert.pem')
+
+PORT = 54321
+DESTINATION = "/queue/_local/vdsm/requests"
+
+
+class Listener(stomp.ConnectionListener):
+
+ def on_error(self, headers, message):
+ print 'Error: %s' % message
+ terminate()
+
+ def on_message(self, headers, message):
+ msg = json.loads(message)
+ print json.dumps(msg, indent=4)
+ terminate()
+
+
+def main(args):
+ parser = option_parser()
+ options, args = parser.parse_args(args)
+ if not args:
+ parser.error("method required")
+
+ msg = {
+ "id": str(uuid.uuid4()),
+ "jsonrpc": "2.0",
+ "method": args[0]
+ }
+
+ if len(args) > 1:
+ msg["params"] = json.loads(args[1])
+
+ conn = stomp.Connection10(
+ host_and_ports=((options.host, PORT),),
+ use_ssl=True,
+ ssl_key_file=KEYFILE,
+ ssl_cert_file=CERTFILE,
+ ssl_ca_certs=CACERT)
+
+ conn.set_listener("", Listener())
+ conn.start()
+ conn.send(body=json.dumps(msg), destination=DESTINATION)
+
+ try:
+ signal.pause()
+ except KeyboardInterrupt:
+ pass
+
+ conn.disconnect()
+
+
+def option_parser():
+ parser = optparse.OptionParser(usage='%prog [options] method [params]')
+ parser.add_option("-a", "--host", dest="host",
+ help="host address (default localhost)")
+ parser.set_defaults(host="localhost")
+ return parser
+
+
+def terminate():
+ os.kill(os.getpid(), signal.SIGINT)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
--
To view, visit http://gerrit.ovirt.org/35181
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia6273eabf6f3601602659d1e4e748d8025ae8084
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Dan Kenigsberg has uploaded a new change for review.
Change subject: spbackends: simplify over-general evaluation
......................................................................
spbackends: simplify over-general evaluation
The removed code is too clever for the use case of only two values.
Change-Id: I775c4a6003a03c6b65bee3abc8559a88e3eaec01
Signed-off-by: Dan Kenigsberg <danken(a)redhat.com>
---
M vdsm/storage/spbackends.py
1 file changed, 5 insertions(+), 2 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/53/41253/1
diff --git a/vdsm/storage/spbackends.py b/vdsm/storage/spbackends.py
index 953e01e..f6ce02b 100644
--- a/vdsm/storage/spbackends.py
+++ b/vdsm/storage/spbackends.py
@@ -229,8 +229,11 @@
def setSpmStatus(self, lVer=None, spmId=None):
self.invalidateMetadata()
- metaParams = dict(filter(lambda kv: kv[1] is not None,
- ((PMDK_LVER, lVer), (PMDK_SPM_ID, spmId))))
+ metaParams = {}
+ if lVer is not None:
+ metaParams[PMDK_LVER] = lVer
+ if spmId is not None:
+ metaParams[PMDK_SPM_ID] = spmId
self._metadata.update(metaParams)
@unsecured
--
To view, visit https://gerrit.ovirt.org/41253
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I775c4a6003a03c6b65bee3abc8559a88e3eaec01
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Dan Kenigsberg <danken(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: lvm: Fail loudly if called with unexptected input.
......................................................................
lvm: Fail loudly if called with unexptected input.
When creating pvs with the force option, we are very carefull to accept
only True. When using the jsonrpc transport, engine was sending "true"
and "false", causing the call to fail misteiously.
Now we are also carefull about rejecting invlid input, making debugging
easier.
Change-Id: If9e6754d4aa2efaf894a9309cfaa4595d710063b
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M vdsm/storage/lvm.py
1 file changed, 5 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/29/37329/1
diff --git a/vdsm/storage/lvm.py b/vdsm/storage/lvm.py
index aa3c04b..549839a 100644
--- a/vdsm/storage/lvm.py
+++ b/vdsm/storage/lvm.py
@@ -724,6 +724,11 @@
else:
raise
+ # We must be very carefull here; any value execpt True or False is a user
+ # error.
+ if type(force) != bool:
+ raise ValueError("Invalid value for 'force': %r" % force)
+
if force is True:
options = ("-y", "-ff")
_initpvs_removeHolders()
--
To view, visit http://gerrit.ovirt.org/37329
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If9e6754d4aa2efaf894a9309cfaa4595d710063b
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: hsm: Report vg name in getDeviceList
......................................................................
hsm: Report vg name in getDeviceList
Hosted engine needs the iscsi session info used by the hosted engine
storage domain. getDeviceList() seems to include the needed info, but it
does not report the vg name for each device, making it hard to match the
iscsi session info and the hosted engine storage domain.
We return now the vg name of each device, which seems to be useful info
regardless of hosted engine needs, and can be used on the engine side
for reconstructing host state or validating engine view vs host view.
Change-Id: I116714cb5143ea92f5cb54c3f80f895c07ada536
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M vdsm/rpc/vdsmapi-schema.json
M vdsm/storage/hsm.py
2 files changed, 11 insertions(+), 2 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/23/45823/1
diff --git a/vdsm/rpc/vdsmapi-schema.json b/vdsm/rpc/vdsmapi-schema.json
index e0e95b9..1b2a171 100644
--- a/vdsm/rpc/vdsmapi-schema.json
+++ b/vdsm/rpc/vdsmapi-schema.json
@@ -1473,6 +1473,10 @@
#
# @status: The device status (free/used/unknown)
#
+# @vgname: The LVM volume group name, if this device is used as
+# a physical volume. This is typically a storage domain
+# UUID.
+#
# Since: 4.10.0
#
# Notes: The value of @serial may be dependent on the current host so this
@@ -1490,7 +1494,8 @@
'pathstatus': ['BlockDevicePathInfo'],
'pathlist': ['IscsiSessionInfo'], 'logicalblocksize': 'uint',
'physicalblocksize': 'uint', 'partitioned': 'bool',
- 'pvsize': 'uint', 'status': 'BlockDeviceStatus'}}
+ 'pvsize': 'uint', 'status': 'BlockDeviceStatus',
+ 'vgname': 'str'}}
##
# @Host.getDeviceList:
diff --git a/vdsm/storage/hsm.py b/vdsm/storage/hsm.py
index 1b8c064..32e16c3 100644
--- a/vdsm/storage/hsm.py
+++ b/vdsm/storage/hsm.py
@@ -2019,14 +2019,18 @@
pvuuid = pv.uuid
pvsize = pv.size
vguuid = pv.vg_uuid
+ vgname = pv.vg_name
else:
pvuuid = ""
pvsize = ""
vguuid = ""
+ vgname = ""
devInfo = {'GUID': dev.get("guid", ""), 'pvUUID': pvuuid,
'pvsize': str(pvsize),
- 'vgUUID': vguuid, 'vendorID': dev.get("vendor", ""),
+ 'vgUUID': vguuid,
+ 'vgname': vgname,
+ 'vendorID': dev.get("vendor", ""),
'productID': dev.get("product", ""),
'fwrev': dev.get("fwrev", ""),
"serial": dev.get("serial", ""),
--
To view, visit https://gerrit.ovirt.org/45823
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I116714cb5143ea92f5cb54c3f80f895c07ada536
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: utils: Consider sleep time in deadline calculation
......................................................................
utils: Consider sleep time in deadline calculation
This patches fixes 2 utils.retry() broken tests:
- The special case when deadline has reached when an operation was done.
Previously we use to sleep and perform another retry, now we bail out.
- The special case when deadline was not reached when an operation was
done, but we don't have time for sleep (sleeping will reach or exceed
the deadline). Previously we used to sleep and perform another retry,
now we bail out.
Change-Id: I7f41c6b21e3432159c13d46cfe75d1f6236cbb8c
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M lib/vdsm/utils.py
M tests/utilsTests.py
2 files changed, 3 insertions(+), 8 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/00/46400/1
diff --git a/lib/vdsm/utils.py b/lib/vdsm/utils.py
index caf4cc2..4a4b153 100644
--- a/lib/vdsm/utils.py
+++ b/lib/vdsm/utils.py
@@ -938,10 +938,7 @@
if tries in [0, None]:
tries = -1
- if timeout in [0, None]:
- timeout = -1
-
- startTime = monotonic_time()
+ deadline = monotonic_time() + timeout if timeout else None
while True:
tries -= 1
@@ -951,7 +948,7 @@
if tries == 0:
raise
- if (timeout > 0) and ((monotonic_time() - startTime) > timeout):
+ if deadline and monotonic_time() + sleep >= deadline:
raise
if stopCallback is not None and stopCallback():
diff --git a/tests/utilsTests.py b/tests/utilsTests.py
index d213de5..fd4f42e 100644
--- a/tests/utilsTests.py
+++ b/tests/utilsTests.py
@@ -44,7 +44,7 @@
from testlib import permutations, expandPermutations
from testlib import VdsmTestCase as TestCaseBase
from testValidation import checkSudo
-from testValidation import brokentest, stresstest
+from testValidation import stresstest
from multiprocessing import Process
EXT_SLEEP = "sleep"
@@ -86,7 +86,6 @@
# Make sure we had the proper amount of iterations before failing
self.assertEquals(counter[0], limit)
- @brokentest("deadline is not respected")
@MonkeyPatch(utils, 'monotonic_time', FakeTime(0))
@MonkeyPatch(time, 'sleep', fake_sleep)
def testTimeoutDeadlineReached(self):
@@ -104,7 +103,6 @@
timeout=3, sleep=1)
self.assertEqual(utils.monotonic_time.now, 3)
- @brokentest("sleep is not considered in deadline calculation")
@MonkeyPatch(utils, 'monotonic_time', FakeTime(0))
@MonkeyPatch(time, 'sleep', fake_sleep)
def testTimeoutNoTimeForSleep(self):
--
To view, visit https://gerrit.ovirt.org/46400
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I7f41c6b21e3432159c13d46cfe75d1f6236cbb8c
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Adam Litke has uploaded a new change for review.
Change subject: Live Merge: Restore watermark tracking
......................................................................
Live Merge: Restore watermark tracking
Change-Id: I632f31e7795ec5d8c6f52a480116b14470c3163f
Signed-off-by: Adam Litke <alitke(a)redhat.com>
---
M vdsm/virt/vm.py
1 file changed, 108 insertions(+), 10 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/24/36924/1
diff --git a/vdsm/virt/vm.py b/vdsm/virt/vm.py
index f22610d..09080b9 100644
--- a/vdsm/virt/vm.py
+++ b/vdsm/virt/vm.py
@@ -1512,12 +1512,94 @@
with self._confLock:
self.conf['timeOffset'] = newTimeOffset
+ def _getWriteWatermarks(self):
+ def pathToVolID(drive, path):
+ for vol in drive.volumeChain:
+ if os.path.realpath(vol['path']) == os.path.realpath(path):
+ return vol['volumeID']
+ raise LookupError("Unable to find VolumeID for path '%s'", path)
+
+ volAllocMap = {}
+ statsFlags = self._libvirtBackingChainStatsFlag()
+ conn = libvirtconnection.get()
+ blkStats = conn.domainListGetStats([self._dom._dom],
+ libvirt.VIR_DOMAIN_STATS_BLOCK,
+ statsFlags)[0][1]
+ for i in xrange(0, blkStats['block.count']):
+ name = blkStats['block.%i.name' % i]
+ try:
+ drive = self._findDriveByName(name)
+ except LookupError:
+ continue
+ if not drive.blockDev or drive.format != 'cow':
+ continue
+
+ try:
+ path = blkStats['block.%i.path' % i]
+ alloc = blkStats['block.%i.allocation' % i]
+ except KeyError as e:
+ self.log.debug("Block stats are missing expected key '%s', "
+ "skipping volume", e.args[0])
+ continue
+ volID = pathToVolID(drive, path)
+ volAllocMap[volID] = alloc
+ return volAllocMap
+
+ def _getLiveMergeExtendCandidates(self):
+ # The common case is that there are no active jobs.
+ if not self.conf['_blockJobs'].values():
+ return {}
+
+ candidates = {}
+ watermarks = self._getWriteWatermarks()
+ for job in self.conf['_blockJobs'].values():
+ try:
+ drive = self._findDriveByUUIDs(job['disk'])
+ except LookupError:
+ # After an active layer merge completes the vdsm metadata will
+ # be out of sync for a brief period. If we cannot find the old
+ # disk then it's safe to skip it.
+ continue
+
+ if not drive.blockDev:
+ continue
+
+ if job['strategy'] == 'commit':
+ volumeID = job['baseVolume']
+ else:
+ self.log.debug("Unrecognized merge strategy '%s'",
+ job['strategy'])
+ continue
+ res = self.cif.irs.getVolumeInfo(drive.domainID, drive.poolID,
+ drive.imageID, volumeID)
+ if res['status']['code'] != 0:
+ self.log.error("Unable to get the info of volume %s (domain: "
+ "%s image: %s)", volumeID, drive.domainID,
+ drive.imageID)
+ continue
+ volInfo = res['info']
+
+ if volInfo['format'].lower() != 'cow':
+ continue
+
+ if volumeID in watermarks:
+ self.log.debug("Adding live merge extension candidate: "
+ "volume=%s allocation=%i", volumeID,
+ watermarks[volumeID])
+ candidates[drive.imageID] = {
+ 'alloc': watermarks[volumeID],
+ 'physical': int(volInfo['truesize']),
+ 'capacity': int(volInfo['apparentsize']),
+ 'volumeID': volumeID}
+ else:
+ self.log.warning("No watermark info available for %s",
+ volumeID)
+ return candidates
+
def _getExtendCandidates(self):
ret = []
- # FIXME: mergeCandidates should be a dictionary of candidate volumes
- # once libvirt starts reporting watermark information for all volumes.
- mergeCandidates = {}
+ mergeCandidates = self._getLiveMergeExtendCandidates()
for drive in self._devices[hwclass.DISK]:
if not drive.blockDev or drive.format != 'cow':
continue
@@ -4771,6 +4853,14 @@
jobsRet[jobID] = entry
return jobsRet
+ def _libvirtBackingChainStatsFlag(self):
+ # Since libvirt 1.2.13, the virConnectGetAllDomainStats API will return
+ # block statistics for all volumes in the chain when using a new flag.
+ try:
+ return libvirt.VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING
+ except AttributeError:
+ return 0
+
def merge(self, driveSpec, baseVolUUID, topVolUUID, bandwidth, jobUUID):
if not caps.getLiveMergeSupport():
self.log.error("Live merge is not supported on this host")
@@ -4815,6 +4905,8 @@
if res['info']['voltype'] == 'SHARED':
self.log.error("merge: Refusing to merge into a shared volume")
return errCode['mergeErr']
+ baseSize = int(res['info']['apparentsize'])
+ baseCow = bool(res['info']['format'].lower() == 'cow')
# Indicate that we expect libvirt to maintain the relative paths of
# backing files. This is necessary to ensure that a volume chain is
@@ -4865,13 +4957,19 @@
# blockCommit will cause data to be written into the base volume.
# Perform an initial extension to ensure there is enough space to
- # copy all the required data. Normally we'd use monitoring to extend
- # the volume on-demand but internal watermark information is not being
- # reported by libvirt so we must do the full extension up front. In
- # the worst case, we'll need to extend 'base' to the same size as 'top'
- # plus a bit more to accomodate additional writes to 'top' during the
- # live merge operation.
- self.extendDriveVolume(drive, baseVolUUID, topSize)
+ # copy all the required data. If libvirt supports monitoring of
+ # backing chain volumes, just extend by one chunk now and monitor
+ # during the rest of the operation. Otherwise, extend now to
+ # accomodate the worst case scenario: no intersection between the
+ # allocated blocks in the base volume and the top volume.
+ if drive.blockDev and baseCow:
+ if self._libvirtBackingChainStatsFlag():
+ self.extendDrivesIfNeeded()
+ else:
+ extendSize = baseSize + topSize
+ self.log.debug("Preemptively extending volume %s with size %i"
+ "(job: %s)", baseVolUUID, extendSize, jobUUID)
+ self.extendDriveVolume(drive, baseVolUUID, extendCurSize)
# Trigger the collection of stats before returning so that callers
# of getVmStats after this returns will see the new job
--
To view, visit http://gerrit.ovirt.org/36924
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I632f31e7795ec5d8c6f52a480116b14470c3163f
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Adam Litke <alitke(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: libvirtconnection: Replace assert with AssertionError
......................................................................
libvirtconnection: Replace assert with AssertionError
The code wrongly assumed that assert always exists. When running in
optimized mode, the check would be skipped, and instead of getting an
AssertionError, which is the expected error for programmer error
(starting the eventloop twice), we could get a confusing
RuntimeException or RuntimeError from Thread.start (depending on Python
version).
RuntimeError misused in the standard library for all kinds of errors
that do not have builtin errors. It is particularry bad option when used
for usage error.
Change-Id: Icf1564f81f4c1fbf77ccaff6d93c047a02d946da
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M lib/vdsm/libvirtconnection.py
1 file changed, 2 insertions(+), 1 deletion(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/64/34364/1
diff --git a/lib/vdsm/libvirtconnection.py b/lib/vdsm/libvirtconnection.py
index 5430c82..009f8b7 100644
--- a/lib/vdsm/libvirtconnection.py
+++ b/lib/vdsm/libvirtconnection.py
@@ -37,7 +37,8 @@
self.__thread = None
def start(self):
- assert not self.run
+ if self.run:
+ raise AssertionError("EventLoop is running")
self.__thread = threading.Thread(target=self.__run,
name="libvirtEventLoop")
self.__thread.setDaemon(True)
--
To view, visit http://gerrit.ovirt.org/34364
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Icf1564f81f4c1fbf77ccaff6d93c047a02d946da
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: cache: Add caching decorator with invalidation
......................................................................
cache: Add caching decorator with invalidation
The new cache.memoized extends utils.memoized, adding invalidation
support.
Features added:
- An optional "validate" argument. This is a callable invoked each time
the memoized function is called. When the callable returns False, the
cache is invalidated.
- Memoized functions have an "invalidate" method, used to invalidate the
cache during testing.
- file_validator - invalidates the cache when a file changes.
Example usage:
from vdsm.cache import memoized, file_validator
@memoized(file_validator('/bigfile'))
def parse_bigfile():
# Expensive code processing '/bigfile' contents
Change-Id: I6dd8fb29d94286e3e3a3e29b8218501cbdc5c018
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M lib/vdsm/Makefile.am
A lib/vdsm/cache.py
M tests/Makefile.am
A tests/cacheTests.py
4 files changed, 366 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/09/34709/1
diff --git a/lib/vdsm/Makefile.am b/lib/vdsm/Makefile.am
index b862e71..6f0040d 100644
--- a/lib/vdsm/Makefile.am
+++ b/lib/vdsm/Makefile.am
@@ -23,6 +23,7 @@
dist_vdsmpylib_PYTHON = \
__init__.py \
+ cache.py \
compat.py \
define.py \
exception.py \
diff --git a/lib/vdsm/cache.py b/lib/vdsm/cache.py
new file mode 100644
index 0000000..9806e40
--- /dev/null
+++ b/lib/vdsm/cache.py
@@ -0,0 +1,98 @@
+#
+# Copyright 2014 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+import errno
+import os
+import functools
+
+
+def memoized(validate=None):
+ """
+ Return a caching decorator supporting invalidation.
+
+ The decorator accepts an optional validate callable, called each time the
+ memoized function is called. If the validate callable return True, the
+ memoized function will use the cache. If the validate callable return
+ False, the memoized cache is cleared.
+
+ The memoized function may accept multiple positional arguments. The
+ cache store the result for each combination of arguments. Functions with
+ kwargs are not supported.
+
+ Memoized functions have an "invalidate" method, used to invalidate the
+ memoized cache during testing.
+
+ To invalidate the cache when a file changes, use the file_validator from
+ this module.
+
+ Example usage:
+
+ from vdsm.cache import memoized, file_validator
+
+ @memoized(file_validator('/bigfile'))
+ def parse_bigfile():
+ # Expensive code processing '/bigfile' contents
+
+ """
+ def decorator(f):
+ cache = {}
+
+ @functools.wraps(f)
+ def wrapper(*args):
+ if validate is not None and not validate():
+ cache.clear()
+ try:
+ value = cache[args]
+ except KeyError:
+ value = cache[args] = f(*args)
+ return value
+
+ wrapper.invalidate = cache.clear
+ return wrapper
+
+ return decorator
+
+
+class file_validator(object):
+ """
+ I'm a validator returning False when a file has changed since the last
+ validation.
+ """
+
+ UNKNOWN = 0
+ MISSING = 1
+
+ def __init__(self, path):
+ self.path = path
+ self.stats = self.UNKNOWN
+
+ def __call__(self):
+ try:
+ stats = os.stat(self.path)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ stats = self.MISSING
+ else:
+ stats = stats.st_ino, stats.st_size, stats.st_mtime
+ if stats != self.stats:
+ self.stats = stats
+ return False
+ return True
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 36a1cdd..6fa7e64 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -26,6 +26,7 @@
alignmentScanTests.py \
blocksdTests.py \
bridgeTests.py \
+ cacheTests.py \
cPopenTests.py \
capsTests.py \
clientifTests.py \
diff --git a/tests/cacheTests.py b/tests/cacheTests.py
new file mode 100644
index 0000000..8927b39
--- /dev/null
+++ b/tests/cacheTests.py
@@ -0,0 +1,266 @@
+#
+# Copyright 2014 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+import os
+from vdsm.cache import memoized
+from vdsm.cache import file_validator
+from testlib import VdsmTestCase
+from testlib import namedTemporaryDir
+
+
+class Validator(object):
+ """ I'm a callable returning a boolean value (self.valid) """
+
+ def __init__(self):
+ self.valid = True
+ self.count = 0
+
+ def __call__(self):
+ self.count += 1
+ return self.valid
+
+
+class Accessor(object):
+ """ I'm recording how many times a dict was accessed. """
+
+ def __init__(self, d):
+ self.d = d
+ self.count = 0
+
+ def get(self, key):
+ self.count += 1
+ return self.d[key]
+
+
+class MemoizedTests(VdsmTestCase):
+
+ def setUp(self):
+ self.values = {'a': 0, 'b': 10, ('a',): 20, ('a', 'b'): 30}
+
+ def test_no_args(self):
+ accessor = Accessor(self.values)
+
+ @memoized()
+ def func(key):
+ return accessor.get(key)
+
+ # Fill the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 2)
+
+ # Values served now from the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 2)
+
+ def test_validation(self):
+ accessor = Accessor(self.values)
+ validator = Validator()
+
+ @memoized(validator)
+ def func(key):
+ return accessor.get(key)
+
+ # Fill the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 2)
+ self.assertEqual(validator.count, 2)
+
+ # Values served now from the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 2)
+ self.assertEqual(validator.count, 4)
+
+ # Values has changed
+ self.values['a'] += 1
+ self.values['b'] += 1
+
+ # Next call should clear the cache
+ validator.valid = False
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(accessor.count, 3)
+ self.assertEqual(validator.count, 5)
+
+ # Next call should add next value to cache
+ validator.valid = True
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 4)
+ self.assertEqual(validator.count, 6)
+
+ # Values served now from the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 4)
+ self.assertEqual(validator.count, 8)
+
+ def test_raise_errors_in_memoized_func(self):
+ accessor = Accessor(self.values)
+ validator = Validator()
+
+ @memoized(validator)
+ def func(key):
+ return accessor.get(key)
+
+ # First run should fail, second shold fill the cache
+ self.assertRaises(KeyError, func, 'no such key')
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(accessor.count, 2)
+ self.assertEqual(validator.count, 2)
+
+ def test_multiple_args(self):
+ accessor = Accessor(self.values)
+
+ @memoized()
+ def func(*args):
+ return accessor.get(args)
+
+ # Fill the cache
+ self.assertEqual(func('a'), self.values[('a',)])
+ self.assertEqual(func('a', 'b'), self.values[('a', 'b')])
+ self.assertEqual(accessor.count, 2)
+
+ # Values served now from the cache
+ self.assertEqual(func('a'), self.values[('a',)])
+ self.assertEqual(func('a', 'b'), self.values[('a', 'b')])
+ self.assertEqual(accessor.count, 2)
+
+ def test_kwargs_not_supported(self):
+ @memoized()
+ def func(a=None, b=None):
+ pass
+ self.assertRaises(TypeError, func, a=1, b=2)
+
+ def test_invalidate(self):
+ accessor = Accessor(self.values)
+
+ @memoized()
+ def func(key):
+ return accessor.get(key)
+
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(accessor.count, 1)
+
+ func.invalidate()
+
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(accessor.count, 2)
+
+
+class FileValidatorTests(VdsmTestCase):
+
+ def test_no_file(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+
+ # Must be False so memoise call the decorated function
+ self.assertEqual(validator(), False)
+
+ # Since file state did not change, must remain True
+ self.assertEqual(validator(), True)
+
+ def test_file_created(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ with open(path, 'w') as f:
+ f.write('data')
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ def test_file_removed(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+
+ with open(path, 'w') as f:
+ f.write('data')
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ os.unlink(path)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ def test_size_changed(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+ data = 'old data'
+ with open(path, 'w') as f:
+ f.write(data)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ with open(path, 'w') as f:
+ f.write(data + ' new data')
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ def test_mtime_changed(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+ data = 'old data'
+ with open(path, 'w') as f:
+ f.write(data)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ # Fake timestamp change, as timestamp resolution may not be good
+ # enough when comparing changes during the test.
+ atime = mtime = os.path.getmtime(path) + 1
+ os.utime(path, (atime, mtime))
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ def test_ino_changed(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+ data = 'old data'
+ with open(path, 'w') as f:
+ f.write(data)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ tmp = path + '.tmp'
+ with open(tmp, 'w') as f:
+ f.write(data)
+ os.rename(tmp, path)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
--
To view, visit http://gerrit.ovirt.org/34709
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I6dd8fb29d94286e3e3a3e29b8218501cbdc5c018
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: sdc: Rename method to make it less confusing
......................................................................
sdc: Rename method to make it less confusing
StorageCache.refresh() does not do any refresh, but clearing the domain
cahce and invlidating lvm cache (actually clearing it). Rename to
clear() to reflect what it does.
Change-Id: I2c67ae0ddc98857e406fec62be0cbcf817213236
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M vdsm/storage/sdc.py
M vdsm/storage/sp.py
2 files changed, 3 insertions(+), 3 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/16/47916/1
diff --git a/vdsm/storage/sdc.py b/vdsm/storage/sdc.py
index ecb9708..a26c05a 100644
--- a/vdsm/storage/sdc.py
+++ b/vdsm/storage/sdc.py
@@ -181,7 +181,7 @@
return uuids
- def refresh(self):
+ def clear(self):
with self._syncroot:
lvm.invalidateCache()
self.__domainCache.clear()
diff --git a/vdsm/storage/sp.py b/vdsm/storage/sp.py
index b8fd8f3..6ecaf8d 100644
--- a/vdsm/storage/sp.py
+++ b/vdsm/storage/sp.py
@@ -627,7 +627,7 @@
self.id = hostID
# Make sure SDCache doesn't have stale data (it can be in case of FC)
sdCache.invalidateStorage()
- sdCache.refresh()
+ sdCache.clear()
# Rebuild whole Pool
self.__rebuild(msdUUID=msdUUID, masterVersion=masterVersion)
self.__createMailboxMonitor()
@@ -1245,7 +1245,7 @@
'msdUUID' - master storage domain UUID
"""
sdCache.invalidateStorage()
- sdCache.refresh()
+ sdCache.clear()
self.__rebuild(msdUUID=msdUUID, masterVersion=masterVersion)
def updateVM(self, vmList, sdUUID):
--
To view, visit https://gerrit.ovirt.org/47916
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I2c67ae0ddc98857e406fec62be0cbcf817213236
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: scsi: Scan only the required domain type
......................................................................
scsi: Scan only the required domain type
We used to perform both iSCSI and FCP rescan when creating or editing a
storage domain, connecting to storage server, getting vg and storage
domain list and more.
The unneeded rescan is typically fast, but if a storage server or device
is not accessible, a SCSI rescan may block for couple of minutes,
leading to unwanted blocking of unrelated storage threads. This is
particularly bad when you are interested only in one domain type, but
the host get stuck scanning the other type.
To improve storage domain isolation, we use the specified storage type
to perform a rescan only of the relevant type. If storage type was not
specified, we scan both ISCSI and FCP keeping the old behavior.
Change-Id: Ic32cd683020e94df016dd77b19ae3eb7317c5554
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M vdsm/storage/hsm.py
M vdsm/storage/multipath.py
M vdsm/storage/sdc.py
3 files changed, 25 insertions(+), 17 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/24/45824/1
diff --git a/vdsm/storage/hsm.py b/vdsm/storage/hsm.py
index 1b8c064..541b699 100644
--- a/vdsm/storage/hsm.py
+++ b/vdsm/storage/hsm.py
@@ -1984,15 +1984,16 @@
return dict(devList=devices)
def _getDeviceList(self, storageType=None, guids=(), checkStatus=True):
- sdCache.refreshStorage()
- typeFilter = lambda dev: True
- if storageType:
- if sd.storageType(storageType) == sd.type2name(sd.ISCSI_DOMAIN):
- typeFilter = \
- lambda dev: multipath.devIsiSCSI(dev.get("devtype"))
- elif sd.storageType(storageType) == sd.type2name(sd.FCP_DOMAIN):
- typeFilter = \
- lambda dev: multipath.devIsFCP(dev.get("devtype"))
+ domType = sd.storageType(storageType) if storageType else None
+
+ sdCache.refreshStorage(domType)
+
+ if domType == sd.ISCSI_DOMAIN:
+ typeFilter = lambda dev: multipath.devIsiSCSI(dev.get("devtype"))
+ elif domType == sd.FCP_DOMAIN:
+ typeFilter = lambda dev: multipath.devIsFCP(dev.get("devtype"))
+ else:
+ typeFilter = lambda dev: True
devices = []
pvs = {}
@@ -2470,7 +2471,7 @@
# while the VDSM was not connected, we need to
# call refreshStorage.
if domType in (sd.FCP_DOMAIN, sd.ISCSI_DOMAIN):
- sdCache.refreshStorage()
+ sdCache.refreshStorage(domType)
try:
doms = self.__prefetchDomains(domType, conObj)
except:
@@ -2864,7 +2865,8 @@
"""
vars.task.setDefaultException(
se.StorageDomainActionError("spUUID: %s" % spUUID))
- sdCache.refreshStorage()
+ domType = sd.storageType(storageType) if storageType else None
+ sdCache.refreshStorage(domType)
if spUUID and spUUID != volume.BLANK_UUID:
domList = self.getPool(spUUID).getDomains()
domains = domList.keys()
@@ -2925,7 +2927,8 @@
:rtype: dict
"""
vars.task.setDefaultException(se.VolumeGroupActionError())
- sdCache.refreshStorage()
+ domType = sd.storageType(storageType) if storageType else None
+ sdCache.refreshStorage(domType)
# getSharedLock(connectionsResource...)
vglist = []
vgs = self.__getVGsInfo()
diff --git a/vdsm/storage/multipath.py b/vdsm/storage/multipath.py
index ad81d2d..32deb98 100644
--- a/vdsm/storage/multipath.py
+++ b/vdsm/storage/multipath.py
@@ -39,6 +39,7 @@
import misc
import iscsi
import devicemapper
+import sd
DEV_ISCSI = "iSCSI"
DEV_FCP = "FCP"
@@ -61,7 +62,7 @@
""" multipath operation failed """
-def rescan():
+def rescan(domType=None):
"""
Forces multipath daemon to rescan the list of available devices and
refresh the mapping table. New devices can be found under /dev/mapper
@@ -70,8 +71,12 @@
"""
# First rescan iSCSI and FCP connections
- iscsi.rescan()
- hba.rescan()
+
+ if domType in (None, sd.ISCSI_DOMAIN):
+ iscsi.rescan()
+
+ if domType in (None, sd.FCP_DOMAIN):
+ hba.rescan()
# Now let multipath daemon pick up new devices
misc.execCmd([constants.EXT_MULTIPATH], sudo=True)
diff --git a/vdsm/storage/sdc.py b/vdsm/storage/sdc.py
index ecb9708..273c5c0 100644
--- a/vdsm/storage/sdc.py
+++ b/vdsm/storage/sdc.py
@@ -77,10 +77,10 @@
self.__staleStatus = self.STORAGE_STALE
@misc.samplingmethod
- def refreshStorage(self):
+ def refreshStorage(self, domType=None):
self.__staleStatus = self.STORAGE_REFRESHING
- multipath.rescan()
+ multipath.rescan(domType)
multipath.resize_devices()
lvm.invalidateCache()
--
To view, visit https://gerrit.ovirt.org/45824
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic32cd683020e94df016dd77b19ae3eb7317c5554
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Yeela Kaplan has uploaded a new change for review.
Change subject: [WIP]fencenode: split fenceNode into its own module
......................................................................
[WIP]fencenode: split fenceNode into its own module
Change-Id: I7bd5e7246cf6da21e355849014a8fc71d5edbde6
Signed-off-by: Yeela Kaplan <ykaplan(a)redhat.com>
---
M debian/vdsm-python.install
M lib/vdsm/Makefile.am
A lib/vdsm/fencenode.py
M vdsm.spec.in
M vdsm/API.py
5 files changed, 120 insertions(+), 80 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/97/43597/1
diff --git a/debian/vdsm-python.install b/debian/vdsm-python.install
index d9402c3..901edb6 100644
--- a/debian/vdsm-python.install
+++ b/debian/vdsm-python.install
@@ -10,6 +10,7 @@
./usr/lib/python2.7/dist-packages/vdsm/exception.py
./usr/lib/python2.7/dist-packages/vdsm/executor.py
./usr/lib/python2.7/dist-packages/vdsm/ipwrapper.py
+./usr/lib/python2.7/dist-packages/vdsm/fencenode.py
./usr/lib/python2.7/dist-packages/vdsm/jsonrpcvdscli.py
./usr/lib/python2.7/dist-packages/vdsm/libvirtconnection.py
./usr/lib/python2.7/dist-packages/vdsm/netconfpersistence.py
diff --git a/lib/vdsm/Makefile.am b/lib/vdsm/Makefile.am
index 95e236f..875507c 100644
--- a/lib/vdsm/Makefile.am
+++ b/lib/vdsm/Makefile.am
@@ -30,6 +30,7 @@
exception.py \
executor.py \
ipwrapper.py \
+ fencenode.py \
jsonrpcvdscli.py \
libvirtconnection.py \
netconfpersistence.py \
diff --git a/lib/vdsm/fencenode.py b/lib/vdsm/fencenode.py
new file mode 100644
index 0000000..3456b2f
--- /dev/null
+++ b/lib/vdsm/fencenode.py
@@ -0,0 +1,100 @@
+#
+# Copyright 2015 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+import logging
+import os
+import signal
+
+import utils
+import constants
+from define import doneCode, errCode
+
+
+(a)utils.traceback(on=logging.name)
+def fence(script, data):
+ # non-status actions are sent asyncronously. deathSignal is set to
+ # make sure that no stray fencing scripts are left behind if Vdsm
+ # crashes.
+ rc, out, err = utils.execCmd(
+ [script], deathSignal=signal.SIGTERM, data=data)
+ logging.debug('rc %s data %s out %s err %s', rc,
+ hidePasswd(data), out, err)
+ return rc, out, err
+
+
+def hidePasswd(text):
+ cleantext = ''
+ for line in text.splitlines(True):
+ if line.startswith('passwd='):
+ line = 'passwd=XXXX\n'
+ cleantext += line
+ return cleantext
+
+
+def fenceNode(addr, port, agent, username, password, action,
+ secure=False, options='', policy=None):
+
+ logging.debug('fenceNode(addr=%s,port=%s,agent=%s,user=%s,passwd=%s,'
+ 'action=%s,secure=%s,options=%s,policy=%s)',
+ addr, port, agent, username, password, action, secure,
+ options, policy)
+
+ if action not in ('status', 'on', 'off', 'reboot'):
+ raise ValueError('illegal action ' + action)
+
+ script = constants.EXT_FENCE_PREFIX + agent
+
+ data = ('agent=fence_%s\nipaddr=%s\nlogin=%s\naction=%s\n'
+ 'passwd=%s\n') % (agent, addr, username, action, password.value)
+ if port != '':
+ data += 'port=%s\n' % (port,)
+ if utils.tobool(secure):
+ data += 'secure=yes\n'
+ data += options
+
+ try:
+ rc, out, err = fence(script, data)
+ except OSError as e:
+ if e.errno == os.errno.ENOENT:
+ return errCode['fenceAgent']
+ raise
+
+ logging.debug('rc %s data %s out %s err %s',
+ rc, hidePasswd(data), out, err)
+
+ if not 0 <= rc <= 2:
+ return {'status': {'code': 1,
+ 'message': out + err}}
+
+ message = doneCode['message']
+ if action == 'status':
+ if rc == 0:
+ power = 'on'
+ elif rc == 2:
+ power = 'off'
+ else:
+ power = 'unknown'
+ message = out + err
+ return {'status': {'code': 0, 'message': message},
+ 'power': power}
+ if rc != 0:
+ message = out + err
+ return {'status': {'code': rc, 'message': message},
+ 'power': 'unknown', 'operationStatus': 'initiated'}
diff --git a/vdsm.spec.in b/vdsm.spec.in
index 966a974..9e69162 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -1071,6 +1071,7 @@
%{python_sitelib}/%{vdsm_name}/exception.py*
%{python_sitelib}/%{vdsm_name}/executor.py*
%{python_sitelib}/%{vdsm_name}/ipwrapper.py*
+%{python_sitelib}/%{vdsm_name}/fencenode.py*
%{python_sitelib}/%{vdsm_name}/jsonrpcvdscli.py*
%{python_sitelib}/%{vdsm_name}/libvirtconnection.py*
%{python_sitelib}/%{vdsm_name}/netinfo.py*
diff --git a/vdsm/API.py b/vdsm/API.py
index 0a93d2a..0719926 100644
--- a/vdsm/API.py
+++ b/vdsm/API.py
@@ -21,8 +21,6 @@
# pylint: disable=R0904
from contextlib import contextmanager
-import os
-import signal
import six
import sys
import time
@@ -38,6 +36,7 @@
from clientIF import clientIF
from vdsm import netinfo
from vdsm import constants
+from vdsm import fencenode
import storage.misc
import storage.clusterlock
import storage.volume
@@ -1187,6 +1186,11 @@
APIBase.__init__(self)
# General Host functions
+ def ping(self):
+ "Ping the server. Useful for tests"
+ updateTimestamp()
+ return {'status': doneCode}
+
def fenceNode(self, addr, port, agent, username, password, action,
secure=False, options='', policy=None):
"""Send a fencing command to a remote node.
@@ -1194,44 +1198,24 @@
agent is one of (rsa, ilo, drac5, ipmilan, etc)
action can be one of (status, on, off, reboot)."""
- @utils.traceback(on=self.log.name)
- def fence(script, inp):
- # non-status actions are sent asyncronously. deathSignal is set to
- # make sure that no stray fencing scripts are left behind if Vdsm
- # crashes.
- rc, out, err = utils.execCmd(
- [script], deathSignal=signal.SIGTERM,
- data=inp)
- self.log.debug('rc %s inp %s out %s err %s', rc,
- hidePasswd(inp), out, err)
- return rc, out, err
-
- def hidePasswd(text):
- cleantext = ''
- for line in text.splitlines(True):
- if line.startswith('passwd='):
- line = 'passwd=XXXX\n'
- cleantext += line
- return cleantext
-
def should_fence(policy):
# skip fence execution if map of storage domains with host id is
# entered and at least one storage domain connection from host is
# alive
if policy is None:
- self.log.debug('No policy specified')
+ logging.debug('No policy specified')
return True
hostIdMap = policy.get('storageDomainHostIdMap')
if not hostIdMap:
- self.log.warning('Invalid policy specified')
+ logging.warning('Invalid policy specified')
return True
result = self._irs.getHostLeaseStatus(hostIdMap)
- if result['status']['code'] != 0:
- self.log.error(
- "Error getting host lease status, error code '%s'",
- result['status']['code'])
+ rc = result['status']['code']
+ if rc != 0:
+ logging.error(
+ "Error getting host lease status, error code '%s'", rc)
return True
# HOST_STATUS_LIVE means that host renewed its lease in last 80
@@ -1239,65 +1223,18 @@
# fencing, even when it's unreachable from engine
for sd, status in result['domains'].iteritems():
if status == storage.clusterlock.HOST_STATUS_LIVE:
- self.log.debug("Host has live lease on '%s'", sd)
+ logging.debug("Host has live lease on '%s'", sd)
return False
- self.log.debug("Host doesn't have any live lease")
+ logging.debug("Host doesn't have any live lease")
return True
- self.log.debug('fenceNode(addr=%s,port=%s,agent=%s,user=%s,passwd=%s,'
- 'action=%s,secure=%s,options=%s,policy=%s)',
- addr, port, agent, username, password, action, secure,
- options, policy)
-
- if action not in ('status', 'on', 'off', 'reboot'):
- raise ValueError('illegal action ' + action)
-
if action != 'status' and not should_fence(policy):
- self.log.debug("Skipping execution of action '%s'", action)
+ logging.debug("Skipping execution of action '%s'", action)
return {'status': doneCode, 'operationStatus': 'skipped'}
- script = constants.EXT_FENCE_PREFIX + agent
-
- inp = ('agent=fence_%s\nipaddr=%s\nlogin=%s\naction=%s\n'
- 'passwd=%s\n') % (agent, addr, username, action, password.value)
- if port != '':
- inp += 'port=%s\n' % (port,)
- if utils.tobool(secure):
- inp += 'secure=yes\n'
- inp += options
-
- try:
- rc, out, err = fence(script, inp)
- except OSError as e:
- if e.errno == os.errno.ENOENT:
- return errCode['fenceAgent']
- raise
- self.log.debug('rc %s in %s out %s err %s', rc,
- hidePasswd(inp), out, err)
- if not 0 <= rc <= 2:
- return {'status': {'code': 1,
- 'message': out + err}}
- message = doneCode['message']
- if action == 'status':
- if rc == 0:
- power = 'on'
- elif rc == 2:
- power = 'off'
- else:
- power = 'unknown'
- message = out + err
- return {'status': {'code': 0, 'message': message},
- 'power': power}
- if rc != 0:
- message = out + err
- return {'status': {'code': rc, 'message': message},
- 'power': 'unknown', 'operationStatus': 'initiated'}
-
- def ping(self):
- "Ping the server. Useful for tests"
- updateTimestamp()
- return {'status': doneCode}
+ return fencenode.fenceNode(addr, port, agent, username, password,
+ action, secure, options, policy)
def getCapabilities(self):
"""
--
To view, visit https://gerrit.ovirt.org/43597
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I7bd5e7246cf6da21e355849014a8fc71d5edbde6
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Yeela Kaplan <ykaplan(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: qemuimg: Memoize _supports_qcow2_compat
......................................................................
qemuimg: Memoize _supports_qcow2_compat
We used to run qemu-img twice when creating or converting qcow2 images.
The first run check if qemu-img supports the qcow2 "comapt" option, and
the second run uses the result to format the qemu-img command.
Now we run qemu-img once for "create" and "convert" to learn about its
capabilities, and use the cached value on the next runs. If qemu-img
executable is modified, we drop the cache, in case a new version was
installed with different capabilities.
Change-Id: Ic63f5e8c06993df8e4066bf7ac2dabfb4b4bdbfb
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M lib/vdsm/qemuimg.py
M tests/qemuimgTests.py
2 files changed, 8 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/11/34711/1
diff --git a/lib/vdsm/qemuimg.py b/lib/vdsm/qemuimg.py
index cf428b2..f295acc 100644
--- a/lib/vdsm/qemuimg.py
+++ b/lib/vdsm/qemuimg.py
@@ -23,6 +23,7 @@
import signal
from . import utils
+from . import cache
_qemuimg = utils.CommandPath("qemu-img",
"/usr/bin/qemu-img",) # Fedora, EL6
@@ -221,6 +222,7 @@
raise QImgError(rc, out, err)
+(a)cache.memoized(cache.file_validator(_qemuimg.cmd))
def _supports_qcow2_compat(command):
"""
qemu-img "create" and "convert" commands support a "compat" option in
diff --git a/tests/qemuimgTests.py b/tests/qemuimgTests.py
index 813a497..a642374 100644
--- a/tests/qemuimgTests.py
+++ b/tests/qemuimgTests.py
@@ -118,6 +118,9 @@
class CreateTests(TestCaseBase):
+ def setUp(self):
+ qemuimg._supports_qcow2_compat.invalidate()
+
def test_no_format(self):
def create(cmd, **kw):
expected = [QEMU_IMG, 'create', 'image']
@@ -161,6 +164,9 @@
class ConvertTests(TestCaseBase):
+ def setUp(self):
+ qemuimg._supports_qcow2_compat.invalidate()
+
def test_no_format(self):
def convert(cmd, **kw):
expected = [QEMU_IMG, 'convert', '-t', 'none', 'src', 'dst']
--
To view, visit http://gerrit.ovirt.org/34711
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic63f5e8c06993df8e4066bf7ac2dabfb4b4bdbfb
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Federico Simoncelli has uploaded a new change for review.
Change subject: vdsm: add eventfd and EventFile synchronization
......................................................................
vdsm: add eventfd and EventFile synchronization
Change-Id: I0d237f13c42b1f4505c90d30c6d3c3ecbd1e9fa7
Signed-off-by: Federico Simoncelli <fsimonce(a)redhat.com>
---
M lib/vdsm/Makefile.am
A lib/vdsm/eventfd.py
M tests/Makefile.am
A tests/eventfdTests.py
4 files changed, 251 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/87/33687/1
diff --git a/lib/vdsm/Makefile.am b/lib/vdsm/Makefile.am
index 4bebf28..e712cad 100644
--- a/lib/vdsm/Makefile.am
+++ b/lib/vdsm/Makefile.am
@@ -25,6 +25,7 @@
__init__.py \
compat.py \
define.py \
+ eventfd.py \
exception.py \
ipwrapper.py \
libvirtconnection.py \
diff --git a/lib/vdsm/eventfd.py b/lib/vdsm/eventfd.py
new file mode 100644
index 0000000..b2a7084
--- /dev/null
+++ b/lib/vdsm/eventfd.py
@@ -0,0 +1,140 @@
+#
+# Copyright 2014 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+"""\
+This module provides the support for eventfd(2).
+
+More information about eventfd and usage examples can be found in the
+eventfd(2) man page.
+
+The EventFile class provides a single synchronization object exposing
+the python Event interface and associated eventfds.
+
+The eventfd() context manager returns a file descriptor that can be
+used to provide the event notice to select, poll and epoll, e.g.
+
+ import os
+ import sys
+ import select
+ import threading
+ import time
+ from vdsm.eventfd import EventFile, DATASIZE
+
+ e = EventFile()
+ p = select.epoll()
+
+ threading.Timer(5, e.set).start()
+
+ with e.eventfd() as efd:
+ p.register(efd, select.EPOLLIN)
+ p.register(sys.stdin.fileno(), select.EPOLLIN)
+
+ print "Echoing lines until event is received"
+ event_received = False
+
+ while not event_received:
+ for fileno, event in p.poll():
+ if not event & select.EPOLLIN:
+ continue
+
+ if fileno == efd:
+ os.read(efd, DATASIZE)
+ event_received = True
+ elif fileno == sys.stdin.fileno():
+ print os.read(sys.stdin.fileno(), 1024),
+
+ print "Event received!"
+
+
+The Event set() semantic is preserved in the eventfd context manager:
+if the event is set then the eventfd already contains the notification.
+This is both to maintain the semantic and to avoid possible races as:
+
+ if not e.is_set():
+ with e.eventfd() as efd:
+ ...
+"""
+
+import os
+import ctypes
+import threading
+
+from contextlib import contextmanager
+
+_libc = ctypes.CDLL('libc.so.6', use_errno=True)
+
+EFD_NONBLOCK = os.O_NONBLOCK
+EFD_CLOEXEC = 02000000 # os.O_CLOEXEC in python 3.3
+EFD_SEMAPHORE = 00000001
+
+DATASIZE = ctypes.sizeof(ctypes.c_ulonglong)
+
+
+def eventfd(initval, flags):
+ return _libc.eventfd(initval, flags)
+
+
+class EventFile(object):
+ def __init__(self, event=None):
+ self.__lock = threading.Lock()
+ self.__fds = set()
+ self.__event = event or threading.Event()
+
+ @staticmethod
+ def __fire_event(fd):
+ os.write(fd, ctypes.c_ulonglong(1))
+
+ def open_eventfd(self):
+ with self.__lock:
+ fd = eventfd(0, 0)
+
+ self.__fds.add(fd)
+
+ if self.__event.is_set():
+ self.__fire_event(fd)
+
+ return fd
+
+ @contextmanager
+ def eventfd(self):
+ fd = self.open_eventfd()
+
+ yield fd
+
+ with self.__lock:
+ self.__fds.remove(fd)
+ os.close(fd)
+
+ def isSet(self):
+ return self.__event.isSet()
+
+ is_set = isSet
+
+ def set(self):
+ with self.__lock:
+ self.__event.set()
+ for fd in self.__fds:
+ self.__fire_event(fd)
+
+ def clear(self):
+ self.__event.clear()
+
+ def wait(self, timeout=None, balancing=True):
+ self.__event.wait(timeout)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 449d7b1..120712e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -31,6 +31,7 @@
clientifTests.py \
configNetworkTests.py \
domainMonitorTests.py \
+ eventfdTests.py \
fileVolumeTests.py \
fileUtilTests.py \
fuserTests.py \
diff --git a/tests/eventfdTests.py b/tests/eventfdTests.py
new file mode 100644
index 0000000..be15248
--- /dev/null
+++ b/tests/eventfdTests.py
@@ -0,0 +1,109 @@
+#
+# Copyright 2014 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+import os
+import select
+from vdsm.eventfd import EventFile, DATASIZE
+from nose.tools import timed, raises, TimeExpired
+
+TEST_TIMEOUT = 1
+WAIT_TIMEOUT = 2
+
+
+def test_set():
+ e = EventFile()
+ e.set()
+ assert e.is_set()
+ assert e.isSet()
+
+
+def text_clear():
+ e = EventFile()
+ e.set()
+ assert e.is_set()
+ e.clear()
+ assert not e.is_set()
+
+
+@timed(TEST_TIMEOUT)
+def test_wait_set():
+ e = EventFile()
+ e.set()
+ e.wait(WAIT_TIMEOUT)
+
+
+@raises(TimeExpired)
+@timed(TEST_TIMEOUT)
+def test_wait_noset():
+ e = EventFile()
+ e.wait(WAIT_TIMEOUT)
+
+
+@timed(TEST_TIMEOUT)
+def test_eventfd_earlyset():
+ e = EventFile()
+ e.set()
+ with e.eventfd() as fd:
+ assert len(__select_and_read(fd)) == DATASIZE
+
+
+@timed(TEST_TIMEOUT)
+def test_eventfd_lateset():
+ e = EventFile()
+ with e.eventfd() as fd:
+ e.set()
+ assert len(__select_and_read(fd)) == DATASIZE
+
+
+@raises(TimeExpired)
+@timed(TEST_TIMEOUT)
+def test_eventfd_noset():
+ e = EventFile()
+ with e.eventfd() as fd:
+ assert len(__select_and_read(fd)) != DATASIZE
+
+
+@timed(TEST_TIMEOUT)
+def test_eventfd_multiple():
+ e = EventFile()
+ e.set()
+ with e.eventfd() as fd1:
+ assert len(__select_and_read(fd1)) == DATASIZE
+ with e.eventfd() as fd2:
+ assert len(__select_and_read(fd2)) == DATASIZE
+ with e.eventfd() as fd3:
+ assert len(__select_and_read(fd3)) == DATASIZE
+
+
+@raises(TimeExpired)
+@timed(TEST_TIMEOUT)
+def test_eventfd_clear():
+ e = EventFile()
+ e.set()
+ e.clear()
+ with e.eventfd() as fd:
+ assert len(__select_and_read(fd)) != DATASIZE
+
+
+def __select_and_read(fd):
+ rd, wr, ex = select.select((fd,), (), (), WAIT_TIMEOUT)
+ if fd in rd:
+ return os.read(fd, DATASIZE)
+ return ''
--
To view, visit http://gerrit.ovirt.org/33687
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I0d237f13c42b1f4505c90d30c6d3c3ecbd1e9fa7
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Federico Simoncelli <fsimonce(a)redhat.com>