[lxc-devel] [nova-lxd/master] Disk and NIC quotas
peter-slovak on Github
lxc-bot at linuxcontainers.org
Fri May 13 17:14:12 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 760 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160513/2560fa2e/attachment.bin>
-------------- next part --------------
From fdfcbd43fe45eb92224bbab47cdae2fc78d91f70 Mon Sep 17 00:00:00 2001
From: Peter Slovak <peter.slovak at websupport.sk>
Date: Fri, 13 May 2016 15:35:41 +0200
Subject: [PATCH 1/3] respect disk and network quotas defined in
OS::Compute::Quota MD namespace
---
nova/virt/lxd/config.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 72 insertions(+), 2 deletions(-)
diff --git a/nova/virt/lxd/config.py b/nova/virt/lxd/config.py
index 555f5cb..9c31807 100644
--- a/nova/virt/lxd/config.py
+++ b/nova/virt/lxd/config.py
@@ -22,7 +22,7 @@
from nova.virt import configdrive
from oslo_log import log as logging
-from oslo_utils import excutils
+from oslo_utils import excutils, units
from nova.virt.lxd import session
from nova.virt.lxd import utils as container_dir
@@ -128,7 +128,7 @@ def create_config(self, instance_name, instance):
try:
config = {}
- # Update continaer options
+ # Update container options
config.update(self.config_instance_options(config, instance))
# Set the instance memory limit
@@ -188,6 +188,10 @@ def configure_container_root(self, instance):
else:
config['root'] = {'path': '/',
'type': 'disk'}
+
+ # Set disk quotas
+ config['root'].update(self.create_disk_quota_config(instance))
+
return config
except Exception as ex:
with excutils.save_and_reraise_exception():
@@ -196,6 +200,41 @@ def configure_container_root(self, instance):
{'instance': instance.name, 'ex': ex},
instance=instance)
+ def create_disk_quota_config(self, instance):
+ extra_specs = instance.flavor.extra_specs
+ disk_config = {}
+
+ # Get disk quotas from flavor metadata
+ disk_read_iops_sec = extra_specs.get('quota:disk_read_iops_sec')
+ disk_read_bytes_sec = extra_specs.get('quota:disk_read_bytes_sec')
+ disk_write_iops_sec = extra_specs.get('quota:disk_write_iops_sec')
+ disk_write_bytes_sec = extra_specs.get('quota:disk_write_bytes_sec')
+ disk_total_iops_sec = extra_specs.get('quota:disk_total_iops_sec')
+ disk_total_bytes_sec = extra_specs.get('quota:disk_total_bytes_sec')
+
+ # Bytes and IOps are not separate config options in a container
+ # profile - we let Bytes take priority over IOps if both are set.
+ # Align all limits to MiB/s, which should be a sensible middle road.
+ if disk_read_iops_sec:
+ disk_config['limits.read'] = str(disk_read_iops_sec) + 'iops'
+
+ if disk_read_bytes_sec:
+ disk_config['limits.read'] = str(disk_read_bytes_sec / units.Mi) + 'MB'
+
+ if disk_write_iops_sec:
+ disk_config['limits.write'] = str(disk_write_iops_sec) + 'iops'
+
+ if disk_write_bytes_sec:
+ disk_config['limits.write'] = str(disk_write_bytes_sec / units.Mi) + 'MB'
+
+ if disk_total_iops_sec:
+ disk_config['limits.max'] = str(disk_total_iops_sec) + 'iops'
+
+ if disk_total_bytes_sec:
+ disk_config['limits.max'] = str(disk_total_bytes_sec / units.Mi) + 'MB'
+
+ return disk_config
+
def create_network(self, instance_name, instance, network_info):
"""Create the LXD container network on the host
@@ -218,6 +257,11 @@ def create_network(self, instance_name, instance, network_info):
'hwaddr': str(cfg['mac_address']),
'parent': str(cfg['bridge']),
'type': 'nic'}
+
+ # Set network device quotas
+ network_devices[str(cfg['bridge'])].update(
+ self.create_network_quota_config(instance)
+ )
return network_devices
except Exception as ex:
with excutils.save_and_reraise_exception():
@@ -225,6 +269,32 @@ def create_network(self, instance_name, instance, network_info):
_LE('Fail to configure network for %(instance)s: %(ex)s'),
{'instance': instance_name, 'ex': ex}, instance=instance)
+ def create_network_quota_config(self, instance):
+ extra_specs = instance.flavor.extra_specs
+ network_config = {}
+
+ # Get network quotas from flavor metadata
+ vif_inbound_average = extra_specs.get('quota:vif_inbound_average')
+ vif_inbound_burst = extra_specs.get('quota:vif_inbound_burst')
+ vif_inbound_peak = extra_specs.get('quota:vif_inbound_peak')
+ vif_outbound_average = extra_specs.get('quota:vif_outbound_average')
+ vif_outbound_burst = extra_specs.get('quota:vif_outbound_burst')
+ vif_outbound_peak = extra_specs.get('quota:vif_outbound_peak')
+
+ # Since LXD does not implement average NIC IO and number of burst
+ # bytes, we take the max(vif_*_average, vif_*_peak) to set the peak
+ # network IO and simply ignore the burst bytes.
+ # Align values to MB/s (powers of 1000 in this case)
+ vif_inbound_limit = max(vif_inbound_average, vif_inbound_peak)
+ if vif_inbound_limit:
+ network_config['limits.ingress'] = str(vif_inbound_limit / units.M) + 'Mbit'
+
+ vif_outbound_limit = max(vif_outbound_average, vif_outbound_peak)
+ if vif_outbound_limit:
+ network_config['limits.egress'] = str(vif_outbound_limit / units.M) + 'Mbit'
+
+ return network_config
+
def get_container_source(self, instance):
"""Set the LXD container image for the instance.
From b6359441b3113c2ffc85a62738e77718fc56758c Mon Sep 17 00:00:00 2001
From: Peter Slovak <peter.slovak at websupport.sk>
Date: Fri, 13 May 2016 15:43:36 +0200
Subject: [PATCH 2/3] skip defining 'limits.max' if a more concrete quota has
been defined
---
nova/virt/lxd/config.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/nova/virt/lxd/config.py b/nova/virt/lxd/config.py
index 9c31807..836ec33 100644
--- a/nova/virt/lxd/config.py
+++ b/nova/virt/lxd/config.py
@@ -227,10 +227,15 @@ def create_disk_quota_config(self, instance):
if disk_write_bytes_sec:
disk_config['limits.write'] = str(disk_write_bytes_sec / units.Mi) + 'MB'
- if disk_total_iops_sec:
+ # If at least one of the above limits has been defined, do not set
+ # the "max" quota (which would apply to both read and write)
+ minor_quota_defined = (disk_read_iops_sec or disk_write_iops_sec or
+ disk_read_bytes_sec or disk_write_bytes_sec)
+
+ if disk_total_iops_sec and not minor_quota_defined:
disk_config['limits.max'] = str(disk_total_iops_sec) + 'iops'
- if disk_total_bytes_sec:
+ if disk_total_bytes_sec and not minor_quota_defined:
disk_config['limits.max'] = str(disk_total_bytes_sec / units.Mi) + 'MB'
return disk_config
From b8062285ad279e1eb7f1a2ffa471006c1da7c815 Mon Sep 17 00:00:00 2001
From: Peter Slovak <peter.slovak at websupport.sk>
Date: Fri, 13 May 2016 19:06:15 +0200
Subject: [PATCH 3/3] tests for disk and nic quotas
---
nova/tests/unit/virt/lxd/test_config.py | 118 ++++++++++++++++++++++++++++++++
nova/virt/lxd/config.py | 26 ++++---
2 files changed, 133 insertions(+), 11 deletions(-)
diff --git a/nova/tests/unit/virt/lxd/test_config.py b/nova/tests/unit/virt/lxd/test_config.py
index 19b658d..27eda7c 100644
--- a/nova/tests/unit/virt/lxd/test_config.py
+++ b/nova/tests/unit/virt/lxd/test_config.py
@@ -22,6 +22,8 @@
from nova.virt.lxd import config
from nova.virt.lxd import session
from nova.virt.lxd import utils as container_dir
+from oslo_utils import units
+
import stubs
@@ -132,3 +134,119 @@ def test_container_privileged_container(self):
config = self.config.config_instance_options({}, instance)
self.assertEqual({'security.privileged': 'True',
'boot.autostart': 'True'}, config)
+
+ @mock.patch.object(session.LXDAPISession, 'get_host_config',
+ mock.Mock(return_value={'storage': 'zfs'}))
+ def test_disk_quota_rw_iops(self):
+ instance = stubs._fake_instance()
+ instance.flavor.extra_specs = {'quota:disk_read_iops_sec': 10000,
+ 'quota:disk_write_iops_sec': 10000}
+ config = self.config.configure_container_root(instance)
+ self.assertEqual({'root': {'path': '/',
+ 'type': 'disk',
+ 'size': '10GB',
+ 'limits.read': '10000iops',
+ 'limits.write': '10000iops'}}, config)
+
+ @mock.patch.object(session.LXDAPISession, 'get_host_config',
+ mock.Mock(return_value={'storage': 'zfs'}))
+ def test_disk_quota_rw_iops_and_bytes(self):
+ # Byte values should take precedence
+ instance = stubs._fake_instance()
+ instance.flavor.extra_specs = {
+ 'quota:disk_read_iops_sec': 10000,
+ 'quota:disk_write_iops_sec': 10000,
+ 'quota:disk_read_bytes_sec': 13 * units.Mi,
+ 'quota:disk_write_bytes_sec': 5 * units.Mi
+ }
+ config = self.config.configure_container_root(instance)
+ self.assertEqual({'root': {'path': '/',
+ 'type': 'disk',
+ 'size': '10GB',
+ 'limits.read': '13MB',
+ 'limits.write': '5MB'}}, config)
+
+ @mock.patch.object(session.LXDAPISession, 'get_host_config',
+ mock.Mock(return_value={'storage': 'zfs'}))
+ def test_disk_quota_total_iops(self):
+ instance = stubs._fake_instance()
+ instance.flavor.extra_specs = {
+ 'quota:disk_total_iops_sec': 10000
+ }
+ config = self.config.configure_container_root(instance)
+ self.assertEqual({'root': {'path': '/',
+ 'type': 'disk',
+ 'size': '10GB',
+ 'limits.max': '10000iops'}}, config)
+
+ @mock.patch.object(session.LXDAPISession, 'get_host_config',
+ mock.Mock(return_value={'storage': 'zfs'}))
+ def test_disk_quota_total_iops_and_bytes(self):
+ instance = stubs._fake_instance()
+ instance.flavor.extra_specs = {
+ 'quota:disk_total_iops_sec': 10000,
+ 'quota:disk_total_bytes_sec': 11 * units.Mi
+ }
+ config = self.config.configure_container_root(instance)
+ self.assertEqual({'root': {'path': '/',
+ 'type': 'disk',
+ 'size': '10GB',
+ 'limits.max': '11MB'}}, config)
+
+ @mock.patch.object(session.LXDAPISession, 'get_host_config',
+ mock.Mock(return_value={'storage': 'zfs'}))
+ def test_disk_quota_rw_and_total_iops_and_bytes(self):
+ # More granular quotas should be set only, moreover
+ # in MBytes, not iops
+ instance = stubs._fake_instance()
+ instance.flavor.extra_specs = {
+ 'quota:disk_read_iops_sec': 10000,
+ 'quota:disk_write_iops_sec': 10000,
+ 'quota:disk_read_bytes_sec': 13 * units.Mi,
+ 'quota:disk_write_bytes_sec': 5 * units.Mi,
+ 'quota:disk_total_iops_sec': 10000,
+ 'quota:disk_total_bytes_sec': 11 * units.Mi
+ }
+ config = self.config.configure_container_root(instance)
+ self.assertEqual({'root': {'path': '/',
+ 'type': 'disk',
+ 'size': '10GB',
+ 'limits.read': '13MB',
+ 'limits.write': '5MB'}}, config)
+
+ def test_network_in_out_average(self):
+ instance = stubs._fake_instance()
+ instance.flavor.extra_specs = {
+ 'quota:vif_inbound_average': 20 * units.M,
+ 'quota:vif_outbound_average': 8 * units.M
+ }
+ instance_name = 'fake_instance'
+ network_info = fake_network.fake_get_instance_nw_info(self)
+ config = self.config.create_network(instance_name, instance,
+ network_info)
+ self.assertEqual({'fake_br1': {'hwaddr': 'DE:AD:BE:EF:00:01',
+ 'nictype': 'bridged',
+ 'parent': 'fake_br1',
+ 'type': 'nic',
+ 'limits.ingress': '20Mbit',
+ 'limits.egress': '8Mbit'}}, config)
+
+ def test_network_in_out_average_and_peak(self):
+ # Max of the two values should take precedence
+ instance = stubs._fake_instance()
+ instance.flavor.extra_specs = {
+ 'quota:vif_inbound_average': 20 * units.M,
+ 'quota:vif_outbound_average': 9 * units.M,
+ 'quota:vif_inbound_peak': 21 * units.M,
+ 'quota:vif_outbound_peak': 8 * units.M,
+ }
+ instance_name = 'fake_instance'
+ network_info = fake_network.fake_get_instance_nw_info(self)
+ config = self.config.create_network(instance_name, instance,
+ network_info)
+ self.assertEqual({'fake_br1': {'hwaddr': 'DE:AD:BE:EF:00:01',
+ 'nictype': 'bridged',
+ 'parent': 'fake_br1',
+ 'type': 'nic',
+ 'limits.ingress': '21Mbit',
+ 'limits.egress': '9Mbit'}}, config)
diff --git a/nova/virt/lxd/config.py b/nova/virt/lxd/config.py
index 836ec33..a29f387 100644
--- a/nova/virt/lxd/config.py
+++ b/nova/virt/lxd/config.py
@@ -22,7 +22,8 @@
from nova.virt import configdrive
from oslo_log import log as logging
-from oslo_utils import excutils, units
+from oslo_utils import excutils
+from oslo_utils import units
from nova.virt.lxd import session
from nova.virt.lxd import utils as container_dir
@@ -216,16 +217,18 @@ def create_disk_quota_config(self, instance):
# profile - we let Bytes take priority over IOps if both are set.
# Align all limits to MiB/s, which should be a sensible middle road.
if disk_read_iops_sec:
- disk_config['limits.read'] = str(disk_read_iops_sec) + 'iops'
+ disk_config['limits.read'] = disk_read_iops_sec + 'iops'
if disk_read_bytes_sec:
- disk_config['limits.read'] = str(disk_read_bytes_sec / units.Mi) + 'MB'
+ disk_config['limits.read'] = \
+ str(int(disk_read_bytes_sec) / units.Mi) + 'MB'
if disk_write_iops_sec:
- disk_config['limits.write'] = str(disk_write_iops_sec) + 'iops'
+ disk_config['limits.write'] = disk_write_iops_sec + 'iops'
if disk_write_bytes_sec:
- disk_config['limits.write'] = str(disk_write_bytes_sec / units.Mi) + 'MB'
+ disk_config['limits.write'] = \
+ str(int(disk_write_bytes_sec) / units.Mi) + 'MB'
# If at least one of the above limits has been defined, do not set
# the "max" quota (which would apply to both read and write)
@@ -233,10 +236,11 @@ def create_disk_quota_config(self, instance):
disk_read_bytes_sec or disk_write_bytes_sec)
if disk_total_iops_sec and not minor_quota_defined:
- disk_config['limits.max'] = str(disk_total_iops_sec) + 'iops'
+ disk_config['limits.max'] = disk_total_iops_sec + 'iops'
if disk_total_bytes_sec and not minor_quota_defined:
- disk_config['limits.max'] = str(disk_total_bytes_sec / units.Mi) + 'MB'
+ disk_config['limits.max'] = \
+ str(int(disk_total_bytes_sec) / units.Mi) + 'MB'
return disk_config
@@ -280,10 +284,8 @@ def create_network_quota_config(self, instance):
# Get network quotas from flavor metadata
vif_inbound_average = extra_specs.get('quota:vif_inbound_average')
- vif_inbound_burst = extra_specs.get('quota:vif_inbound_burst')
vif_inbound_peak = extra_specs.get('quota:vif_inbound_peak')
vif_outbound_average = extra_specs.get('quota:vif_outbound_average')
- vif_outbound_burst = extra_specs.get('quota:vif_outbound_burst')
vif_outbound_peak = extra_specs.get('quota:vif_outbound_peak')
# Since LXD does not implement average NIC IO and number of burst
@@ -292,11 +294,13 @@ def create_network_quota_config(self, instance):
# Align values to MB/s (powers of 1000 in this case)
vif_inbound_limit = max(vif_inbound_average, vif_inbound_peak)
if vif_inbound_limit:
- network_config['limits.ingress'] = str(vif_inbound_limit / units.M) + 'Mbit'
+ network_config['limits.ingress'] = \
+ str(int(vif_inbound_limit) / units.M) + 'Mbit'
vif_outbound_limit = max(vif_outbound_average, vif_outbound_peak)
if vif_outbound_limit:
- network_config['limits.egress'] = str(vif_outbound_limit / units.M) + 'Mbit'
+ network_config['limits.egress'] = \
+ str(int(vif_outbound_limit) / units.M) + 'Mbit'
return network_config
More information about the lxc-devel
mailing list