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@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
automation@ovirt.org has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 1:
* Update tracker::IGNORE, no Bug-Url found * Check Bug-Url::WARN, no bug url found, make sure header matches 'Bug-Url: ' and is a valid url. * Check merged to previous::IGNORE, Not in stable branch (['ovirt-3.5', 'ovirt-3.4', 'ovirt-3.3'])
oVirt Jenkins CI Server has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 1:
Build Failed
http://jenkins.ovirt.org/job/vdsm_master_unit-tests_created/15044/ : FAILURE
http://jenkins.ovirt.org/job/vdsm_master_pep8_gerrit/14875/ : SUCCESS
http://jenkins.ovirt.org/job/vdsm_master_virt_functional_tests_gerrit/2179/ : There was an infra issue, please contact infra@ovirt.org
http://jenkins.ovirt.org/job/vdsm_master_unit_tests_gerrit_el/14087/ : FAILURE
Francesco Romani has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 1: Code-Review-1
(4 comments)
A few initial remarks. Most important: we need some form of dependency from new libvirt, either hard or soft, don't we?
http://gerrit.ovirt.org/#/c/36924/1/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 1520: raise LookupError("Unable to find VolumeID for path '%s'", path) Line 1521: Line 1522: volAllocMap = {} Line 1523: statsFlags = self._libvirtBackingChainStatsFlag() Line 1524: conn = libvirtconnection.get() unless you need a specific different connection (then please explain why), just use self._connection Line 1525: blkStats = conn.domainListGetStats([self._dom._dom], Line 1526: libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1527: statsFlags)[0][1] Line 1528: for i in xrange(0, blkStats['block.count']):
Line 1523: statsFlags = self._libvirtBackingChainStatsFlag() Line 1524: conn = libvirtconnection.get() Line 1525: blkStats = conn.domainListGetStats([self._dom._dom], Line 1526: libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1527: statsFlags)[0][1] I prefer to avoid the double indexing. Why we need [0] is sufficiently clear, but I prefer
_, blkStats = conn...
only for clarity Line 1528: for i in xrange(0, blkStats['block.count']): Line 1529: name = blkStats['block.%i.name' % i] Line 1530: try: Line 1531: drive = self._findDriveByName(name)
Line 1524: conn = libvirtconnection.get() Line 1525: blkStats = conn.domainListGetStats([self._dom._dom], Line 1526: libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1527: statsFlags)[0][1] Line 1528: for i in xrange(0, blkStats['block.count']): why the explicit start? Isn't xrange(blkStats['block.count']) enough? Line 1529: name = blkStats['block.%i.name' % i] Line 1530: try: Line 1531: drive = self._findDriveByName(name) Line 1532: except LookupError:
Line 4858: # block statistics for all volumes in the chain when using a new flag. Line 4859: try: Line 4860: return libvirt.VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING Line 4861: except AttributeError: Line 4862: return 0 This could be detected once per VDSM run, no need to check it for every operation.
Probably better as module level private constant. Line 4863: Line 4864: def merge(self, driveSpec, baseVolUUID, topVolUUID, bandwidth, jobUUID): Line 4865: if not caps.getLiveMergeSupport(): Line 4866: self.log.error("Live merge is not supported on this host")
Francesco Romani has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 1:
(1 comment)
http://gerrit.ovirt.org/#/c/36924/1/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 1523: statsFlags = self._libvirtBackingChainStatsFlag() Line 1524: conn = libvirtconnection.get() Line 1525: blkStats = conn.domainListGetStats([self._dom._dom], Line 1526: libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1527: statsFlags)[0][1]
I prefer to avoid the double indexing.
about the bulk stats, we need a common shared API/pattern to use them. We're (hopefully) going to use them for sampling as well and I want to avoid duplication Line 1528: for i in xrange(0, blkStats['block.count']): Line 1529: name = blkStats['block.%i.name' % i] Line 1530: try: Line 1531: drive = self._findDriveByName(name)
Adam Litke has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 1:
(5 comments)
http://gerrit.ovirt.org/#/c/36924/1/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 1520: raise LookupError("Unable to find VolumeID for path '%s'", path) Line 1521: Line 1522: volAllocMap = {} Line 1523: statsFlags = self._libvirtBackingChainStatsFlag() Line 1524: conn = libvirtconnection.get()
unless you need a specific different connection (then please explain why),
Done Line 1525: blkStats = conn.domainListGetStats([self._dom._dom], Line 1526: libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1527: statsFlags)[0][1] Line 1528: for i in xrange(0, blkStats['block.count']):
Line 1523: statsFlags = self._libvirtBackingChainStatsFlag() Line 1524: conn = libvirtconnection.get() Line 1525: blkStats = conn.domainListGetStats([self._dom._dom], Line 1526: libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1527: statsFlags)[0][1]
I prefer to avoid the double indexing.
Done Line 1528: for i in xrange(0, blkStats['block.count']): Line 1529: name = blkStats['block.%i.name' % i] Line 1530: try: Line 1531: drive = self._findDriveByName(name)
Line 1523: statsFlags = self._libvirtBackingChainStatsFlag() Line 1524: conn = libvirtconnection.get() Line 1525: blkStats = conn.domainListGetStats([self._dom._dom], Line 1526: libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1527: statsFlags)[0][1]
about the bulk stats, we need a common shared API/pattern to use them. We'r
I've headed down this direction in the next series. I hope you like the changes. Line 1528: for i in xrange(0, blkStats['block.count']): Line 1529: name = blkStats['block.%i.name' % i] Line 1530: try: Line 1531: drive = self._findDriveByName(name)
Line 1524: conn = libvirtconnection.get() Line 1525: blkStats = conn.domainListGetStats([self._dom._dom], Line 1526: libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1527: statsFlags)[0][1] Line 1528: for i in xrange(0, blkStats['block.count']):
why the explicit start? Isn't xrange(blkStats['block.count']) enough?
Just a personal preference but am happy to change it if it's important enough to mention here. Line 1529: name = blkStats['block.%i.name' % i] Line 1530: try: Line 1531: drive = self._findDriveByName(name) Line 1532: except LookupError:
Line 4858: # block statistics for all volumes in the chain when using a new flag. Line 4859: try: Line 4860: return libvirt.VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING Line 4861: except AttributeError: Line 4862: return 0
This could be detected once per VDSM run, no need to check it for every ope
Done Line 4863: Line 4864: def merge(self, driveSpec, baseVolUUID, topVolUUID, bandwidth, jobUUID): Line 4865: if not caps.getLiveMergeSupport(): Line 4866: self.log.error("Live merge is not supported on this host")
Adam Litke has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 1:
(1 comment)
Francesco, Regarding dependency on libvirt: We have a soft dependency built into the code. If libvirt does not support the backing chain stats then we detect that and apply the current, overzealous, preemptive extension of internal volumes. This deserves to be mentioned in the commit message that I will update in the next submission.
Thank you for your review!
http://gerrit.ovirt.org/#/c/36924/1/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 1523: statsFlags = self._libvirtBackingChainStatsFlag() Line 1524: conn = libvirtconnection.get() Line 1525: blkStats = conn.domainListGetStats([self._dom._dom], Line 1526: libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1527: statsFlags)[0][1]
I prefer to avoid the double indexing.
Done Line 1528: for i in xrange(0, blkStats['block.count']): Line 1529: name = blkStats['block.%i.name' % i] Line 1530: try: Line 1531: drive = self._findDriveByName(name)
Francesco Romani has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 1:
(1 comment)
Thanks for the answer!
Do you plan for a tighter integration of this code with followup patches? By looking at the methods you added and how they are going to play with _getExtendCandidates, it _seems_ to me that there is some room for further improvement.
If softdeps are properly handled (I just need to read the updated commit message)it seems to me (IIUC all the details of the flow) that we could have just one loop in _getExtendCandidates and issue domainListGetStats here and once, and do all checks per-drive. This could make the code easier to follow and spare us a lot of find*() and loops.
http://gerrit.ovirt.org/#/c/36924/1/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 1551: return {} Line 1552: Line 1553: candidates = {} Line 1554: watermarks = self._getWriteWatermarks() Line 1555: for job in self.conf['_blockJobs'].values(): I wonder if it makes any sense to iterate not on blockJobs but on watermarks. IIUC the flow, this should save us to do again checks at line 1564 and 1582 below, since the not interesting drives are been ruled out by check at line 1534 above.
This could make the code a bit easier to follow. Line 1556: try: Line 1557: drive = self._findDriveByUUIDs(job['disk']) Line 1558: except LookupError: Line 1559: # After an active layer merge completes the vdsm metadata will
automation@ovirt.org has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 2:
* Update tracker::IGNORE, no Bug-Url found * Check Bug-Url::WARN, no bug url found, make sure header matches 'Bug-Url: ' and is a valid url. * Check merged to previous::IGNORE, Not in stable branch (['ovirt-3.5', 'ovirt-3.4', 'ovirt-3.3'])
Adam Litke has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 2: Verified+1
oVirt Jenkins CI Server has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 2:
Build Failed
http://jenkins.ovirt.org/job/vdsm_master_unit-tests_created/15401/ : SUCCESS
http://jenkins.ovirt.org/job/vdsm_master_pep8_gerrit/15232/ : SUCCESS
http://jenkins.ovirt.org/job/vdsm_master_virt_functional_tests_gerrit/2260/ : There was an infra issue, please contact infra@ovirt.org
http://jenkins.ovirt.org/job/vdsm_master_unit_tests_gerrit_el/14444/ : FAILURE
Nir Soffer has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 2:
(5 comments)
Mostly ok, but will have to invest more time in this. Added few comments and questions.
http://gerrit.ovirt.org/#/c/36924/2/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 172: # block statistics for all volumes in the chain when using a new flag. Line 173: _libvirtBackingChainStatsFlag = \ Line 174: libvirt.VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING Line 175: except AttributeError: Line 176: _libvirtBackingChainStatsFlag = 0 This will be little nicer:
_STATS_BACKING_FLAG = getattr( libvirt, "VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING", 0) Line 177: Line 178: Line 179: class VmStatsThread(AdvancedStatsThread): Line 180: MBPS_TO_BPS = 10 ** 6 / 8
Line 1431: return vol['volumeID'] Line 1432: raise LookupError("Unable to find VolumeID for path '%s'", path) Line 1433: Line 1434: volAllocMap = {} Line 1435: blkStats = self._getBulkStats(libvirt.VIR_DOMAIN_STATS_BLOCK, Can we call this bulkStats? Line 1436: _libvirtBackingChainStatsFlag) Line 1437: for i in xrange(blkStats['block.count']): Line 1438: name = blkStats['block.%i.name' % i] Line 1439: try:
Line 4763: startCleanup(storedJob, drive, doPivot) Line 4764: jobsRet[jobID] = entry Line 4765: return jobsRet Line 4766: Line 4767: def _libvirtBackingChainStatsFlag(self): Can be deleted Line 4768: # Since libvirt 1.2.13, the virConnectGetAllDomainStats API will return Line 4769: # block statistics for all volumes in the chain when using a new flag. Line 4770: try: Line 4771: return libvirt.VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING
Line 4873: # during the rest of the operation. Otherwise, extend now to Line 4874: # accomodate the worst case scenario: no intersection between the Line 4875: # allocated blocks in the base volume and the top volume. Line 4876: if drive.blockDev and baseCow: Line 4877: if self._libvirtBackingChainStatsFlag(): Replace with the constant Line 4878: self.extendDrivesIfNeeded() Line 4879: else: Line 4880: extendSize = baseSize + topSize Line 4881: self.log.debug("Preemptively extending volume %s with size %i"
Line 4874: # accomodate the worst case scenario: no intersection between the Line 4875: # allocated blocks in the base volume and the top volume. Line 4876: if drive.blockDev and baseCow: Line 4877: if self._libvirtBackingChainStatsFlag(): Line 4878: self.extendDrivesIfNeeded() This will not extend by one chunk as you describe, but try to extend all drives, and will probably do not extend this drive.
I think we should calculate the requested size in the if, and extend out of the if. Line 4879: else: Line 4880: extendSize = baseSize + topSize Line 4881: self.log.debug("Preemptively extending volume %s with size %i" Line 4882: "(job: %s)", baseVolUUID, extendSize, jobUUID)
Adam Litke has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 2:
(5 comments)
http://gerrit.ovirt.org/#/c/36924/2/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 172: # block statistics for all volumes in the chain when using a new flag. Line 173: _libvirtBackingChainStatsFlag = \ Line 174: libvirt.VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING Line 175: except AttributeError: Line 176: _libvirtBackingChainStatsFlag = 0
This will be little nicer:
Done Line 177: Line 178: Line 179: class VmStatsThread(AdvancedStatsThread): Line 180: MBPS_TO_BPS = 10 ** 6 / 8
Line 1431: return vol['volumeID'] Line 1432: raise LookupError("Unable to find VolumeID for path '%s'", path) Line 1433: Line 1434: volAllocMap = {} Line 1435: blkStats = self._getBulkStats(libvirt.VIR_DOMAIN_STATS_BLOCK,
Can we call this bulkStats?
Done Line 1436: _libvirtBackingChainStatsFlag) Line 1437: for i in xrange(blkStats['block.count']): Line 1438: name = blkStats['block.%i.name' % i] Line 1439: try:
Line 4763: startCleanup(storedJob, drive, doPivot) Line 4764: jobsRet[jobID] = entry Line 4765: return jobsRet Line 4766: Line 4767: def _libvirtBackingChainStatsFlag(self):
Can be deleted
Done Line 4768: # Since libvirt 1.2.13, the virConnectGetAllDomainStats API will return Line 4769: # block statistics for all volumes in the chain when using a new flag. Line 4770: try: Line 4771: return libvirt.VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING
Line 4873: # during the rest of the operation. Otherwise, extend now to Line 4874: # accomodate the worst case scenario: no intersection between the Line 4875: # allocated blocks in the base volume and the top volume. Line 4876: if drive.blockDev and baseCow: Line 4877: if self._libvirtBackingChainStatsFlag():
Replace with the constant
Done Line 4878: self.extendDrivesIfNeeded() Line 4879: else: Line 4880: extendSize = baseSize + topSize Line 4881: self.log.debug("Preemptively extending volume %s with size %i"
Line 4874: # accomodate the worst case scenario: no intersection between the Line 4875: # allocated blocks in the base volume and the top volume. Line 4876: if drive.blockDev and baseCow: Line 4877: if self._libvirtBackingChainStatsFlag(): Line 4878: self.extendDrivesIfNeeded()
This will not extend by one chunk as you describe, but try to extend all dr
I think an adjustment to the language of the comment is all that's required. I personally prefer to keep the extension logic for the common case in extendDrivesIfNeeded(). Since the block of code above registered the block job, this call to extendDrivesIfNeeded() will be able to add the live merge candidate volume. We're simply choosing to try an extension right now rather than wait for the next stats collection interval. Line 4879: else: Line 4880: extendSize = baseSize + topSize Line 4881: self.log.debug("Preemptively extending volume %s with size %i" Line 4882: "(job: %s)", baseVolUUID, extendSize, jobUUID)
automation@ovirt.org has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 3:
* Update tracker::IGNORE, no Bug-Url found * Check Bug-Url::WARN, no bug url found, make sure header matches 'Bug-Url: ' and is a valid url. * Check merged to previous::IGNORE, Not in stable branch (['ovirt-3.5', 'ovirt-3.4', 'ovirt-3.3'])
oVirt Jenkins CI Server has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 3:
Build Failed
http://jenkins.ovirt.org/job/vdsm_master_unit-tests_created/15459/ : SUCCESS
http://jenkins.ovirt.org/job/vdsm_master_pep8_gerrit/15290/ : SUCCESS
http://jenkins.ovirt.org/job/vdsm_master_virt_functional_tests_gerrit/2275/ : There was an infra issue, please contact infra@ovirt.org
http://jenkins.ovirt.org/job/vdsm_master_unit_tests_gerrit_el/14502/ : FAILURE
Nir Soffer has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 3:
(5 comments)
Added few more comments, I still need more time to finish the review.
http://gerrit.ovirt.org/#/c/36924/3/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 169: Line 170: Line 171: # Since libvirt 1.2.13, the virConnectGetAllDomainStats API will return Line 172: # block statistics for all volumes in the chain when using a new flag. Line 173: _libvirtBackingChainStatsFlag = getattr( Why do you a constant which looks like a variable? Can this value change after it was set? Line 174: libvirt, "VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING", 0) Line 175: Line 176: Line 177: class VmStatsThread(AdvancedStatsThread):
Line 712: 'writeLatency': str(compute_latency('wr')), Line 713: 'flushLatency': str(compute_latency('flush'))} Line 714: Line 715: Line 716: def _vmsGetBulkStats(conn, vmList, statsTypes=0, statsFlags=0): I think _getAllVMsBulkStats would be more clear Line 717: domList = [x._dom._dom for x in vmList] Line 718: if statsTypes == 0: Line 719: statsTypes = (libvirt.VIR_DOMAIN_STATS_STATE | Line 720: libvirt.VIR_DOMAIN_STATS_CPU_TOTAL |
Line 1419: self.conf['timeOffset'] = newTimeOffset Line 1420: Line 1421: def _getBulkStats(self, statsTypes, statsFlags): Line 1422: return _vmsGetBulkStats(self._connection, [self], statsTypes, Line 1423: statsFlags)[self.id] So we must get stats for all vms for getting single vm stats?
I think we should get bulkstats for all vms in the sampling thread, and use cached data here.
Today perform virDomainBlockInfo for each disk on each vm every 2 seconds (and some other calls every 15 and 60 seconds). We can replace this with one call to get bulk stats every 2 seconds, and use cached values everywhere else. Line 1424: Line 1425: def _getWriteWatermarks(self): Line 1426: def pathToVolID(drive, path): Line 1427: for vol in drive.volumeChain:
Line 1424: Line 1425: def _getWriteWatermarks(self): Line 1426: def pathToVolID(drive, path): Line 1427: for vol in drive.volumeChain: Line 1428: if os.path.realpath(vol['path']) == os.path.realpath(path): Finding the real path should be done once.
Can you explain which values we get from libvirt and why we need to normalize them accessing the file system?
Why not use os.stat instead of the complex and expensive dance that is os.path.realpath()?
it = os.stat(path)
for vol in drive.volumeChain: st = os.stat(vol["path"]) if st.st_ino, st.st_dev == it.st_ino, it.st_dev: found it...
Last, having inline function make it harder to read the code and increase the chance for duplicate code doing the same thing. I think we should avoid these unless we must (e.g. decorators). Line 1429: return vol['volumeID'] Line 1430: raise LookupError("Unable to find VolumeID for path '%s'", path) Line 1431: Line 1432: volAllocMap = {}
Line 1436: name = bulkStats['block.%i.name' % i] Line 1437: try: Line 1438: drive = self._findDriveByName(name) Line 1439: except LookupError: Line 1440: continue Is this expected? I think we like at least a debug log here to understand why this happens. Line 1441: if not drive.blockDev or drive.format != 'cow': Line 1442: continue Line 1443: Line 1444: try:
Adam Litke has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 3:
(5 comments)
http://gerrit.ovirt.org/#/c/36924/3/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 169: Line 170: Line 171: # Since libvirt 1.2.13, the virConnectGetAllDomainStats API will return Line 172: # block statistics for all volumes in the chain when using a new flag. Line 173: _libvirtBackingChainStatsFlag = getattr(
Why do you a constant which looks like a variable? Can this value change af
No it cannot. I'll capitalize it. Line 174: libvirt, "VIR_CONNECT_GET_ALL_DOMAINS_STATS_BACKING", 0) Line 175: Line 176: Line 177: class VmStatsThread(AdvancedStatsThread):
Line 712: 'writeLatency': str(compute_latency('wr')), Line 713: 'flushLatency': str(compute_latency('flush'))} Line 714: Line 715: Line 716: def _vmsGetBulkStats(conn, vmList, statsTypes=0, statsFlags=0):
I think _getAllVMsBulkStats would be more clear
ok. Line 717: domList = [x._dom._dom for x in vmList] Line 718: if statsTypes == 0: Line 719: statsTypes = (libvirt.VIR_DOMAIN_STATS_STATE | Line 720: libvirt.VIR_DOMAIN_STATS_CPU_TOTAL |
Line 1419: self.conf['timeOffset'] = newTimeOffset Line 1420: Line 1421: def _getBulkStats(self, statsTypes, statsFlags): Line 1422: return _vmsGetBulkStats(self._connection, [self], statsTypes, Line 1423: statsFlags)[self.id]
So we must get stats for all vms for getting single vm stats?
Yes this is a good long-term idea but I am not sure I want to commit to it here. Getting all of the stats for all VMs could be expensive inside libvirt since it involves multiple qemu monitor calls to each VM. A 2 second sampling interval may not be appropriate for all of these things.
Further, we cannot replace the current call to dom.getBlockInfo (for the active layer monitoring because the bulk stats API only provides values for capacity and allocation. We need also the physical value which would then require calls down to the storage getVolumeInfo which won't end up making this any more efficient. Line 1424: Line 1425: def _getWriteWatermarks(self): Line 1426: def pathToVolID(drive, path): Line 1427: for vol in drive.volumeChain:
Line 1424: Line 1425: def _getWriteWatermarks(self): Line 1426: def pathToVolID(drive, path): Line 1427: for vol in drive.volumeChain: Line 1428: if os.path.realpath(vol['path']) == os.path.realpath(path):
Finding the real path should be done once.
Sure I can add a comment. Thanks for your suggestion on how to look it up using os.stat. I'll adopt that and move the logic info a global helper function. Line 1429: return vol['volumeID'] Line 1430: raise LookupError("Unable to find VolumeID for path '%s'", path) Line 1431: Line 1432: volAllocMap = {}
Line 1436: name = bulkStats['block.%i.name' % i] Line 1437: try: Line 1438: drive = self._findDriveByName(name) Line 1439: except LookupError: Line 1440: continue
Is this expected? I think we like at least a debug log here to understand w
No, not expected. I'll add a log.error message. Line 1441: if not drive.blockDev or drive.format != 'cow': Line 1442: continue Line 1443: Line 1444: try:
Nir Soffer has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 3:
(2 comments)
http://gerrit.ovirt.org/#/c/36924/3/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 712: 'writeLatency': str(compute_latency('wr')), Line 713: 'flushLatency': str(compute_latency('flush'))} Line 714: Line 715: Line 716: def _vmsGetBulkStats(conn, vmList, statsTypes=0, statsFlags=0):
ok.
Previously I missed the fact that this returns stats for *some* vms and not all of them, so this should not be _getAllVMsBulkStats but _getVMsBulkStats. Line 717: domList = [x._dom._dom for x in vmList] Line 718: if statsTypes == 0: Line 719: statsTypes = (libvirt.VIR_DOMAIN_STATS_STATE | Line 720: libvirt.VIR_DOMAIN_STATS_CPU_TOTAL |
Line 1419: self.conf['timeOffset'] = newTimeOffset Line 1420: Line 1421: def _getBulkStats(self, statsTypes, statsFlags): Line 1422: return _vmsGetBulkStats(self._connection, [self], statsTypes, Line 1423: statsFlags)[self.id]
Yes this is a good long-term idea but I am not sure I want to commit to it
I see now that this get the stats for only one vm ([self]), so this fine. Line 1424: Line 1425: def _getWriteWatermarks(self): Line 1426: def pathToVolID(drive, path): Line 1427: for vol in drive.volumeChain:
automation@ovirt.org has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 4:
* Update tracker::IGNORE, no Bug-Url found * Check Bug-Url::WARN, no bug url found, make sure header matches 'Bug-Url: ' and is a valid url. * Check merged to previous::IGNORE, Not in stable branch (['ovirt-3.5', 'ovirt-3.4', 'ovirt-3.3'])
Nir Soffer has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 4:
(13 comments)
http://gerrit.ovirt.org/#/c/36924/4/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 722: 'writeLatency': str(compute_latency('wr')), Line 723: 'flushLatency': str(compute_latency('flush'))} Line 724: Line 725: Line 726: def _getAllVMsBulkStats(conn, vmList, statsTypes=0, statsFlags=0): This returns stats for *some* vms, not all. Sorry for asking for the bad name :-) Line 727: domList = [x._dom._dom for x in vmList] Line 728: if statsTypes == 0: Line 729: statsTypes = (libvirt.VIR_DOMAIN_STATS_STATE | Line 730: libvirt.VIR_DOMAIN_STATS_CPU_TOTAL |
Line 723: 'flushLatency': str(compute_latency('flush'))} Line 724: Line 725: Line 726: def _getAllVMsBulkStats(conn, vmList, statsTypes=0, statsFlags=0): Line 727: domList = [x._dom._dom for x in vmList] "x" would be more clear as "vm". Line 728: if statsTypes == 0: Line 729: statsTypes = (libvirt.VIR_DOMAIN_STATS_STATE | Line 730: libvirt.VIR_DOMAIN_STATS_CPU_TOTAL | Line 731: libvirt.VIR_DOMAIN_STATS_BALLOON |
Line 732: libvirt.VIR_DOMAIN_STATS_VCPU | Line 733: libvirt.VIR_DOMAIN_STATS_INTERFACE | Line 734: libvirt.VIR_DOMAIN_STATS_BLOCK) Line 735: statsList = conn.domainListGetStats(domList, statsTypes, statsFlags) Line 736: return dict([(x[0].UUIDString(), x[1]) for x in statsList]) The x variable is not very helpful, specially when you don't know what are x[0] and x[1]. Better unpack the tuple/list.
And we can avoid the temporary list using generator expression, which is also little more clean:
return dict((dom.UUIDString(), domStats) for dom, domStats in statsList) Line 737: Line 738: Line 739: class TimeoutError(libvirt.libvirtError): Line 740: pass
Line 1429: self.conf['timeOffset'] = newTimeOffset Line 1430: Line 1431: def _getBulkStats(self, statsTypes, statsFlags): Line 1432: return _getAllVMsBulkStats(self._connection, [self], statsTypes, Line 1433: statsFlags)[self.id] This is little too hard to read as one line. It would be nicer as:
vmsStats = _getVMsBulkStats(self._connection, [self], statsTypes, statsFlags) return vmsStats[self.id] Line 1434: Line 1435: def _getWriteWatermarks(self): Line 1436: volAllocMap = {} Line 1437: bulkStats = self._getBulkStats(libvirt.VIR_DOMAIN_STATS_BLOCK,
Line 1431: def _getBulkStats(self, statsTypes, statsFlags): Line 1432: return _getAllVMsBulkStats(self._connection, [self], statsTypes, Line 1433: statsFlags)[self.id] Line 1434: Line 1435: def _getWriteWatermarks(self): Should be documented - what stats do we get, for which volumes. Line 1436: volAllocMap = {} Line 1437: bulkStats = self._getBulkStats(libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1438: _LIBVIRT_BACKING_CHAIN_STATS_FLAG) Line 1439: for i in xrange(bulkStats['block.count']):
Line 1432: return _getAllVMsBulkStats(self._connection, [self], statsTypes, Line 1433: statsFlags)[self.id] Line 1434: Line 1435: def _getWriteWatermarks(self): Line 1436: volAllocMap = {} Since this returns watermarks, it would be little bit nicer if you call this map "watermarks" instead of volAllocMap.
This is also more consistent with other code such as _getLiveMergeExtendCandidates. Line 1437: bulkStats = self._getBulkStats(libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1438: _LIBVIRT_BACKING_CHAIN_STATS_FLAG) Line 1439: for i in xrange(bulkStats['block.count']): Line 1440: name = bulkStats['block.%i.name' % i]
Line 1436: volAllocMap = {} Line 1437: bulkStats = self._getBulkStats(libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1438: _LIBVIRT_BACKING_CHAIN_STATS_FLAG) Line 1439: for i in xrange(bulkStats['block.count']): Line 1440: name = bulkStats['block.%i.name' % i] What is this expected key is missing? Line 1441: try: Line 1442: drive = self._findDriveByName(name) Line 1443: except LookupError: Line 1444: self.log.error("Unable to find drive '%s'", name)
Line 1450: path = bulkStats['block.%i.path' % i] Line 1451: alloc = bulkStats['block.%i.allocation' % i] Line 1452: except KeyError as e: Line 1453: self.log.debug("Block stats are missing expected key '%s', " Line 1454: "skipping volume", e.args[0]) Adding volume name would be helpful in this error. Is this expected error? (e.g. always missing in some configuration/condition) If not, this should be an error log. Line 1455: continue Line 1456: volID = _pathToVolumeID(drive, path) Line 1457: volAllocMap[volID] = alloc Line 1458: return volAllocMap
Line 1470: except LookupError: Line 1471: # After an active layer merge completes the vdsm metadata will Line 1472: # be out of sync for a brief period. If we cannot find the old Line 1473: # disk then it's safe to skip it. Line 1474: continue Adding log.debug for missing disks can be useful if the brief period turns out longer then we expect. Line 1475: Line 1476: if not drive.blockDev: Line 1477: continue Line 1478:
Line 1493: Line 1494: if volInfo['format'].lower() != 'cow': Line 1495: continue Line 1496: Line 1497: if volumeID in watermarks: If the volumeID is not in watermarks, we don't have to get the volume info. Lets check this just after we get the volume id. Line 1498: self.log.debug("Adding live merge extension candidate: " Line 1499: "volume=%s allocation=%i", volumeID, Line 1500: watermarks[volumeID]) Line 1501: candidates[drive.imageID] = {
Line 1504: 'capacity': int(volInfo['apparentsize']), Line 1505: 'volumeID': volumeID} Line 1506: else: Line 1507: self.log.warning("No watermark info available for %s", Line 1508: volumeID) Keeping the early exit style used before would be nicer, as we are not dealing with two possible cases, disk with watermarks and disk without watermarks:
if volumeID not in watermarks: log warning and continue...
add candidate... Line 1509: return candidates Line 1510: Line 1511: def _getExtendCandidates(self): Line 1512: ret = []
Line 4876: # check happens. If libvirt is too old to support this, extend the Line 4877: # internal volume now to accomodate the worst case scenario: no Line 4878: # intersection between the allocated blocks in the base volume and the Line 4879: # top volume. Line 4880: if drive.blockDev and baseCow: Would be nicer to separate this check from the rest. The big comment above is not related to this check. Or move the body and the log comment to a helper method - this method is way too long anyway. Line 4881: if _LIBVIRT_BACKING_CHAIN_STATS_FLAG: Line 4882: self.extendDrivesIfNeeded() Line 4883: else: Line 4884: extendSize = baseSize + topSize
Line 4918: self.log.warning("<backingStore/> missing from backing " Line 4919: "chain for drive %s", drive.name) Line 4920: break Line 4921: diskXML = bsXML Line 4922: entry = VolumeChainEntry(pathToVolID(drive, path), path) Can we separate the removal of the "alloc" property to another patch? Line 4923: volChain.insert(0, entry) Line 4924: return volChain or None Line 4925: Line 4926: def _driveGetActualVolumeChain(self, drives):
oVirt Jenkins CI Server has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 4:
Build Failed
http://jenkins.ovirt.org/job/vdsm_master_unit-tests_created/15582/ : FAILURE
http://jenkins.ovirt.org/job/vdsm_master_pep8_gerrit/15414/ : SUCCESS
http://jenkins.ovirt.org/job/vdsm_master_virt_functional_tests_gerrit/2314/ : There was an infra issue, please contact infra@ovirt.org
http://jenkins.ovirt.org/job/vdsm_master_unit_tests_gerrit_el/14610/ : FAILURE
Francesco Romani has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 4:
(4 comments)
sorry for going out of sync. Seen lot of action between versions 3 and 4, waiting to see how version 5 becomes before to add more comments.
http://gerrit.ovirt.org/#/c/36924/4/vdsm/virt/vm.py File vdsm/virt/vm.py:
Line 723: 'flushLatency': str(compute_latency('flush'))} Line 724: Line 725: Line 726: def _getAllVMsBulkStats(conn, vmList, statsTypes=0, statsFlags=0): Line 727: domList = [x._dom._dom for x in vmList]
"x" would be more clear as "vm".
+1 Line 728: if statsTypes == 0: Line 729: statsTypes = (libvirt.VIR_DOMAIN_STATS_STATE | Line 730: libvirt.VIR_DOMAIN_STATS_CPU_TOTAL | Line 731: libvirt.VIR_DOMAIN_STATS_BALLOON |
Line 730: libvirt.VIR_DOMAIN_STATS_CPU_TOTAL | Line 731: libvirt.VIR_DOMAIN_STATS_BALLOON | Line 732: libvirt.VIR_DOMAIN_STATS_VCPU | Line 733: libvirt.VIR_DOMAIN_STATS_INTERFACE | Line 734: libvirt.VIR_DOMAIN_STATS_BLOCK) Are you interested in this specific subset here and in the time being or you just want them all? Because if you want all it is safe to just use statsTypes == 0:
Using 0 for @stats returns all stats groups supported by the given hypervisor.
both in http://libvirt.org/html/libvirt-libvirt-domain.html#virDomainListGetStats and http://libvirt.org/html/libvirt-libvirt-domain.html#virConnectGetAllDomainSt... Line 735: statsList = conn.domainListGetStats(domList, statsTypes, statsFlags) Line 736: return dict([(x[0].UUIDString(), x[1]) for x in statsList]) Line 737: Line 738:
Line 732: libvirt.VIR_DOMAIN_STATS_VCPU | Line 733: libvirt.VIR_DOMAIN_STATS_INTERFACE | Line 734: libvirt.VIR_DOMAIN_STATS_BLOCK) Line 735: statsList = conn.domainListGetStats(domList, statsTypes, statsFlags) Line 736: return dict([(x[0].UUIDString(), x[1]) for x in statsList])
The x variable is not very helpful, specially when you don't know what are
+1 for the genexp Line 737: Line 738: Line 739: class TimeoutError(libvirt.libvirtError): Line 740: pass
Line 1436: volAllocMap = {} Line 1437: bulkStats = self._getBulkStats(libvirt.VIR_DOMAIN_STATS_BLOCK, Line 1438: _LIBVIRT_BACKING_CHAIN_STATS_FLAG) Line 1439: for i in xrange(bulkStats['block.count']): Line 1440: name = bulkStats['block.%i.name' % i]
What is this expected key is missing?
Yes, this is possible, especially when dealing with backing chains (and not with simple block devices). Line 1441: try: Line 1442: drive = self._findDriveByName(name) Line 1443: except LookupError: Line 1444: self.log.error("Unable to find drive '%s'", name)
Tal Nisan has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 4:
Ping?
Jenkins CI RO has abandoned this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Abandoned
Abandoned due to no activity - please restore if still relevant
gerrit-hooks has posted comments on this change.
Change subject: Live Merge: Restore watermark tracking ......................................................................
Patch Set 4:
* Update tracker: IGNORE, no Bug-Url found
vdsm-patches@lists.fedorahosted.org