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

Add supprt for iops #241

Merged
merged 2 commits into from
Jun 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to check array length value here? or is it safe to assume that it's always > 0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alena, that is the FIO output json file, it should always have the same format, the value could be 0, but items always there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimengliu cool

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
}
Copy link

@jimengliu jimengliu Jun 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a test case that options only has writeIops ? That ensure most user cases work, bc most of times, people won't put all options here. If you do have it, ignore my comment, it is jsut not clear to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't have it @jimengliu. Added.

}

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