Skip to content
This repository has been archived by the owner on Aug 3, 2020. It is now read-only.

Commit

Permalink
Merge pull request #241 from cjellick/blkio
Browse files Browse the repository at this point in the history
Add supprt for iops
  • Loading branch information
Craig Jellick authored Jun 10, 2016
2 parents 174f137 + 33f3604 commit 54146ea
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
image: rancher/dind:v1.9.0-rancher1
image: rancher/dind:v1.10.3-rancher1
script:
- ./scripts/ci
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM rancher/dind:v1.9.0-rancher1
FROM rancher/dind:v1.10.3-rancher1
COPY ./scripts/bootstrap /scripts/bootstrap
RUN /scripts/bootstrap
WORKDIR /source
35 changes: 33 additions & 2 deletions cattle/plugins/docker/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,8 @@ def _setup_dns_search(config, instance):
if line.startswith('search'):
s = line.split()[1:]
for search in s[::-1]:
if search not in dns_search:
dns_search.insert(0, search)
if search not in dns_search:
dns_search.insert(0, search)

@staticmethod
def _setup_links(start_config, instance):
Expand Down Expand Up @@ -597,6 +597,8 @@ def _do_instance_activate(self, instance, host, progress):
create_config['host_config'] = \
client.create_host_config(**start_config)

self._setup_device_options(create_config['host_config'], instance)

container = self.get_container(client, instance)
created = False
if container is None:
Expand Down Expand Up @@ -763,6 +765,35 @@ def _setup_hostname(self, create_config, instance):
except (KeyError, AttributeError):
pass

def _setup_device_options(self, config, instance):
option_configs = \
[('readIops', [], 'BlkioDeviceReadIOps', 'Rate'),
('writeIops', [], 'BlkioDeviceWriteIOps', 'Rate'),
('readBps', [], 'BlkioDeviceReadBps', 'Rate'),
('writeBps', [], 'BlkioDeviceWriteBps', 'Rate'),
('weight', [], 'BlkioWeightDevice', 'Weight')]

try:
device_options = instance.data.fields['blkioDeviceOptions']
except (KeyError, AttributeError):
return

for dev, options in device_options.iteritems():
if dev == 'DEFAULT_DISK':
dev = self.host_info.get_default_disk()
if not dev:
log.warn("Couldn't find default device. Not setting"
"device options: %s", options)
continue
for k, dev_list, _, field in option_configs:
if k in options and options[k] is not None:
value = options[k]
dev_list.append({'Path': dev, field: value})

for _, dev_list, docker_field, _ in option_configs:
if len(dev_list):
config[docker_field] = dev_list

def _setup_networking(self, instance, host, create_config, start_config):
client = docker_client()

Expand Down
50 changes: 50 additions & 0 deletions cattle/plugins/host_info/iops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import logging
import platform
import json

log = logging.getLogger('iops')


class IopsCollector(object):
def __init__(self):
self.data = {}

def _get_iops_data(self, read_or_write):
with open('/var/lib/rancher/state/' + read_or_write + '.json') as f:
return json.load(f)

def _parse_iops_file(self):
data = {}

try:
read_json_data = self._get_iops_data('read')
write_json_data = self._get_iops_data('write')
except IOError:
# File doesn't exist. Silently skip.
return {}

read_iops = read_json_data['jobs'][0]['read']['iops']
write_iops = write_json_data['jobs'][0]['write']['iops']
device = read_json_data['disk_util'][0]['name']
key = '/dev/' + device.encode('ascii', 'ignore')
data[key] = {'read': read_iops, 'write': write_iops}
return data

def key_name(self):
return "iopsInfo"

def get_data(self):
if platform.system() == 'Linux':
if not self.data:
self.data = self._parse_iops_file()
return self.data
else:
return {}

def get_default_disk(self):
data = self.get_data()
if not data:
return None

# Return the first and only item in the dict
return data[data.keys()[0]]
9 changes: 7 additions & 2 deletions cattle/plugins/host_info/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@
from cattle.plugins.host_info.os_c import OSCollector
from cattle.plugins.host_info.cpu import CpuCollector
from cattle.plugins.host_info.disk import DiskCollector
from cattle.plugins.host_info.iops import IopsCollector

log = logging.getLogger('host_info')


class HostInfo(object):
def __init__(self, docker_client=None):
self.docker_client = docker_client

self.iops_collector = IopsCollector()
self.collectors = [MemoryCollector(),
OSCollector(self.docker_client),
DiskCollector(self.docker_client),
CpuCollector()]
CpuCollector(),
self.iops_collector]

def collect_data(self):
data = {}
Expand All @@ -41,3 +43,6 @@ def host_labels(self, label_pfx="io.rancher.host"):
"Error getting {0} labels".format(collector.key_name()))

return labels if len(labels) > 0 else None

def get_default_disk(self):
return self.iops_collector.get_default_disk()
90 changes: 90 additions & 0 deletions tests/test_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,10 @@ def post(req, resp):

@if_docker
def test_instance_activate_lxc_conf(agent, responses):
if newer_than('1.22'):
# lxc conf fields don't work in docker 1.10 and above
return

delete_container('/c861f990-4472-4fa1-960f-65171b544c28')
expectedLxcConf = {"lxc.network.type": "veth"}

Expand Down Expand Up @@ -906,6 +910,92 @@ def post(req, resp):
event_test(agent, schema, pre_func=pre, post_func=post)


@if_docker
def test_instance_activate_device_options(agent, responses):
delete_container('/c861f990-4472-4fa1-960f-65171b544c28')
# Note, can't test weight as it isn't supported in kernel by default
device_options = {'/dev/sda': {
'readIops': 1000,
'writeIops': 2000,
'readBps': 1024,
'writeBps': 2048
}
}

def pre(req):
instance = req['data']['instanceHostMap']['instance']
instance['data']['fields']['blkioDeviceOptions'] = device_options

def post(req, resp):
instance_activate_assert_host_config(resp)
instance_data = resp['data']['instanceHostMap']['instance']['+data']
host_config = instance_data['dockerInspect']['HostConfig']
assert host_config['BlkioDeviceReadIOps'] == [
{'Path': '/dev/sda', 'Rate': 1000}]
assert host_config['BlkioDeviceWriteIOps'] == [
{'Path': '/dev/sda', 'Rate': 2000}]
assert host_config['BlkioDeviceReadBps'] == [
{'Path': '/dev/sda', 'Rate': 1024}]
assert host_config['BlkioDeviceWriteBps'] == [
{'Path': '/dev/sda', 'Rate': 2048}]
container_field_test_boiler_plate(resp)

schema = 'docker/instance_activate_fields'
event_test(agent, schema, pre_func=pre, post_func=post)

# Test DEFAULT_DISK functionality
dc = DockerCompute()

device = '/dev/mock'

class MockHostInfo(object):
def get_default_disk(self):
return device

dc.host_info = MockHostInfo()
instance = JsonObject({'data': {}})
instance.data['fields'] = {
'blkioDeviceOptions': {
'DEFAULT_DISK': {'readIops': 10}
}
}
config = {}
dc._setup_device_options(config, instance)
assert config['BlkioDeviceReadIOps'] == [{'Path': '/dev/mock', 'Rate': 10}]

config = {}
device = None
dc._setup_device_options(config, instance)
assert not config # config should be empty


@if_docker
def test_instance_activate_single_device_option(agent, responses):
delete_container('/c861f990-4472-4fa1-960f-65171b544c28')
device_options = {'/dev/sda': {
'writeIops': 2000,
}
}

def pre(req):
instance = req['data']['instanceHostMap']['instance']
instance['data']['fields']['blkioDeviceOptions'] = device_options

def post(req, resp):
instance_activate_assert_host_config(resp)
instance_data = resp['data']['instanceHostMap']['instance']['+data']
host_config = instance_data['dockerInspect']['HostConfig']
assert host_config['BlkioDeviceWriteIOps'] == [
{'Path': '/dev/sda', 'Rate': 2000}]
assert host_config['BlkioDeviceReadIOps'] is None
assert host_config['BlkioDeviceReadBps'] is None
assert host_config['BlkioDeviceWriteBps'] is None
container_field_test_boiler_plate(resp)

schema = 'docker/instance_activate_fields'
event_test(agent, schema, pre_func=pre, post_func=post)


@if_docker
def test_instance_activate_dns(agent, responses):
delete_container('/c861f990-4472-4fa1-960f-65171b544c28')
Expand Down
13 changes: 7 additions & 6 deletions tests/test_docker_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,13 @@ def pre(req):
}
req = json_data('docker/image_activate')
pre(req)
with pytest.raises(ImageValidationError) as e:
if newer_than('1.22'):
error_class = APIError
else:
error_class = ImageValidationError
with pytest.raises(error_class) as e:
agent.execute(req)
assert e.value.message == 'Image [tianon/true] failed to pull:' \
' Authentication is required.'
assert 'auth' in str(e.value.message).lower()


@if_docker
Expand All @@ -211,9 +214,7 @@ def pre(req):
pre(req)
with pytest.raises(ImageValidationError) as e:
agent.execute(req)
assert e.value.message == 'Image [{}] failed to pull: Error: image ' \
'library/{}:latest not found'\
.format(image_name, image_name)
assert 'not found' in e.value.message


@if_docker
Expand Down
6 changes: 4 additions & 2 deletions tests/test_host_info_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ def test_hostlabels(host_labels):


def test_collect_data(host_data):
expected_top_keys = ['memoryInfo', 'osInfo', 'cpuInfo', 'diskInfo']
expected_top_keys = ['memoryInfo', 'osInfo', 'cpuInfo', 'diskInfo',
'iopsInfo']

assert sorted(host_data.keys()) == sorted(expected_top_keys)

Expand Down Expand Up @@ -286,7 +287,8 @@ def test_collect_data_cpu_freq_fallback(no_cadvisor_non_intel_cpuinfo_mock):


def test_non_linux_host(host_data_non_linux):
expected_top_keys = ['memoryInfo', 'osInfo', 'cpuInfo', 'diskInfo']
expected_top_keys = ['memoryInfo', 'osInfo', 'cpuInfo', 'diskInfo',
'iopsInfo']
expected_empty = {}

assert sorted(host_data_non_linux.keys()) == sorted(expected_top_keys)
Expand Down

0 comments on commit 54146ea

Please sign in to comment.