CinderException can be used as a low-level exception so we don't need to introduce one more wrapper for Exception. TSM backup driver modified without testing. Removed code was broken because it tried to use not existing Exception class attributes. Change-Id: Ie67d919f8f59572268f1f07946fb2d2f14a851f1
130 lines
4.1 KiB
Python
130 lines
4.1 KiB
Python
# Copyright (c) 2015 Hitachi Data Systems, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""Volume copy throttling helpers."""
|
|
|
|
|
|
import contextlib
|
|
|
|
from oslo_concurrency import processutils
|
|
from oslo_log import log as logging
|
|
|
|
from cinder import exception
|
|
import cinder.privsep.cgroup
|
|
from cinder import utils
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class Throttle(object):
|
|
"""Base class for throttling disk I/O bandwidth"""
|
|
|
|
DEFAULT = None
|
|
|
|
@staticmethod
|
|
def set_default(throttle):
|
|
Throttle.DEFAULT = throttle
|
|
|
|
@staticmethod
|
|
def get_default():
|
|
return Throttle.DEFAULT or Throttle()
|
|
|
|
def __init__(self, prefix=None):
|
|
self.prefix = prefix or []
|
|
|
|
@contextlib.contextmanager
|
|
def subcommand(self, srcpath, dstpath):
|
|
"""Sub-command that reads from srcpath and writes to dstpath.
|
|
|
|
Throttle disk I/O bandwidth used by a sub-command, such as 'dd',
|
|
that reads from srcpath and writes to dstpath. The sub-command
|
|
must be executed with the generated prefix command.
|
|
"""
|
|
yield {'prefix': self.prefix}
|
|
|
|
|
|
class BlkioCgroup(Throttle):
|
|
"""Throttle disk I/O bandwidth using blkio cgroups."""
|
|
|
|
def __init__(self, bps_limit, cgroup_name):
|
|
self.bps_limit = bps_limit
|
|
self.cgroup = cgroup_name
|
|
self.srcdevs = {}
|
|
self.dstdevs = {}
|
|
|
|
try:
|
|
cinder.privsep.cgroup.cgroup_create(self.cgroup)
|
|
except processutils.ProcessExecutionError:
|
|
LOG.error('Failed to create blkio cgroup \'%(name)s\'.',
|
|
{'name': cgroup_name})
|
|
raise
|
|
|
|
def _get_device_number(self, path):
|
|
try:
|
|
return utils.get_blkdev_major_minor(path)
|
|
except exception.CinderException as e:
|
|
LOG.error('Failed to get device number for throttling: '
|
|
'%(error)s', {'error': e})
|
|
|
|
def _limit_bps(self, rw, dev, bps):
|
|
try:
|
|
cinder.privsep.cgroup.cgroup_limit(self.cgroup, rw, dev, bps)
|
|
except processutils.ProcessExecutionError:
|
|
LOG.warning('Failed to setup blkio cgroup to throttle the '
|
|
'device \'%(device)s\'.', {'device': dev})
|
|
|
|
def _set_limits(self, rw, devs):
|
|
total = sum(devs.values())
|
|
for dev in sorted(devs):
|
|
self._limit_bps(rw, dev, self.bps_limit * devs[dev] / total)
|
|
|
|
@utils.synchronized('BlkioCgroup')
|
|
def _inc_device(self, srcdev, dstdev):
|
|
if srcdev:
|
|
self.srcdevs[srcdev] = self.srcdevs.get(srcdev, 0) + 1
|
|
self._set_limits('read', self.srcdevs)
|
|
if dstdev:
|
|
self.dstdevs[dstdev] = self.dstdevs.get(dstdev, 0) + 1
|
|
self._set_limits('write', self.dstdevs)
|
|
|
|
@utils.synchronized('BlkioCgroup')
|
|
def _dec_device(self, srcdev, dstdev):
|
|
if srcdev:
|
|
self.srcdevs[srcdev] -= 1
|
|
if self.srcdevs[srcdev] == 0:
|
|
del self.srcdevs[srcdev]
|
|
self._set_limits('read', self.srcdevs)
|
|
if dstdev:
|
|
self.dstdevs[dstdev] -= 1
|
|
if self.dstdevs[dstdev] == 0:
|
|
del self.dstdevs[dstdev]
|
|
self._set_limits('write', self.dstdevs)
|
|
|
|
@contextlib.contextmanager
|
|
def subcommand(self, srcpath, dstpath):
|
|
srcdev = self._get_device_number(srcpath)
|
|
dstdev = self._get_device_number(dstpath)
|
|
|
|
if srcdev is None and dstdev is None:
|
|
yield {'prefix': []}
|
|
return
|
|
|
|
self._inc_device(srcdev, dstdev)
|
|
try:
|
|
yield {'prefix': ['cgexec', '-g', 'blkio:%s' % self.cgroup]}
|
|
finally:
|
|
self._dec_device(srcdev, dstdev)
|