Commit 640ae837 authored by Tomáš Peterka's avatar Tomáš Peterka

[format] Add CGroup Manager in form of optional "plugin"

-  Move all Computer attribute initialization into Computer constructor
-  Rename 'construct' to 'format' because we are in slap.format
-  Introduce Manager base class
-  Implement CGroupManager to take care of cgroup's cpuset
parent cf7225e5
format
Format
======
slapformat is an application to prepare SlapOS ready node (machine).
slapformat is an application to prepare SlapOS-ready node to be used inside SlapGrid Cloud.
It "formats" the machine by:
......@@ -10,9 +10,21 @@ It "formats" the machine by:
- creating needed tap interfaces
- creating TUN interfaces
- creating needed directories with proper ownership and permissions
- (optional-manager) creating cgroup resource tree for slapos
In the end special report is generated and information are posted to
configured SlapOS server.
It reads configuration from /etc/opt/slapos/slapos.cfg and formats computer
accordingly. The variables are number of partitions, IP addresses, storages
and network interfaces.
Format uploads a into configured SlapOS Master server.
Format dumps allocated resources for the partition into a JSON file per
partition ~/.slapos-resource. This file contains network interfaces,
IP address ranges and port ranges. The resource constraints can be
recursively folded.
Pluggable parts for formatting are available too. They are called Managers
and can be turned on/off via configuration property manager_list.
This program shall be only run by root.
......
......@@ -64,6 +64,10 @@ from slapos import version
logger = logging.getLogger("slapos.format")
# dict[str: ManagerClass] used in configuration and XML dump of computer
# this dictionary is intended to be filled after each definition of a Manager
available_manager_list = {}
def prettify_xml(xml):
root = lxml.etree.fromstring(xml)
......@@ -235,39 +239,239 @@ def _getDict(obj):
key: _getDict(value) \
for key, value in dikt.iteritems() \
# do not attempt to serialize logger: it is both useless and recursive.
if not isinstance(value, logging.Logger)
# do not serialize attributes starting with "_", let the classes have some privacy
if not key.startswith("_")
}
class Manager(object):
short_name = None
def format(self):
raise NotImplementedError("Implement function to format underlaying OS")
def update(self):
raise NotImplementedError("Implement function to format underlaying OS")
class CGroupManager(Manager):
"""Manage cgroups in terms on initializing and runtime operations.
This class takes advantage of slapformat being run periodically thus
it can act as a "daemon" performing runtime tasks.
"""
short_name = 'cgroup'
cpu_exclusive_file = ".slapos-cpu-exclusive"
cpuset_path = "/sys/fs/cgroup/cpuset/"
task_write_mode = "at"
def __init__(self, computer):
"""Extract necessary information from the ``computer``.
:param computer: slapos.format.Computer, extract necessary attributes
"""
self.instance_root = computer.instance_root
self.software_gid = computer.software_gid
logger.info("Allowing " + self.__class__.__name__)
def __str__(self):
"""Manager representation when dumped to string."""
return self.short_name
def is_allowed(self):
return os.path.exists("/sys/fs/cgroup/cpuset/cpuset.cpus")
def format(self):
"""Build CGROUP tree to fit SlapOS needs.
- Create hierarchy of CPU sets so that every partition can have exclusive
hold of one of the CPUs.
"""
self.prepare_cpuset()
self.prepare_cpu_space()
def update(self):
"""Control runtime state of the computer."""
cpu0_path = os.path.join(self.cpuset_path, "cpu0")
if os.path.exists(cpu0_path):
# proceed only whe CPUSETs were formatted by this manager
self.prepare_cpu_space()
self.ensure_exlusive_cpu()
else:
logger.warning("Computer was not formatted by {} because {} doesn't exist!".format(
self.__class__.__name__, cpu0_path))
def prepare_cpuset(self):
"""Create cgroup folder per-CPU with exclusive access to the CPU.
Those folders are "/sys/fs/cgroup/cpuset/cpu<N>".
"""
for cpu in self._cpu_list():
cpu_path = self._prepare_folder(
os.path.join(self.cpuset_path, "cpu" + str(cpu)))
with open(cpu_path + "/cpuset.cpus", "wt") as fx:
fx.write(str(cpu)) # this cgroup manages only this cpu
with open(cpu_path + "/cpuset.cpu_exclusive", "wt") as fx:
fx.write("1") # manages it exclusively
with open(cpu_path + "/cpuset.mems", "wt") as fx:
fx.write("0") # it doesn't work without that
os.chown(cpu_path + "/tasks", -1, self.software_gid)
os.chmod(cpu_path + "/tasks", 0o664)
def ensure_exlusive_cpu(self):
"""Move processes among exclusive CPUSets based on software release demands.
We expect PIDs which require own CPU to be found in ~instance/.slapos-cpu-exclusive
"""
cpu_list = self._cpu_list()
generic_cpu = cpu_list[0]
exclusive_cpu_list = cpu_list[1:]
# gather all running PIDs for filtering out stale PIDs
running_pid_set = set()
with open(os.path.join(self.cpuset_path, "tasks"), "rt") as fi:
running_pid_set.update(map(int, fi.read().split()))
with open(os.path.join(self.cpuset_path, "cpu" + str(generic_cpu), "tasks"), "rt") as fi:
running_pid_set.update(map(int, fi.read().split()))
# gather already exclusively running PIDs
exclusive_pid_set = set()
for exclusive_cpu in exclusive_cpu_list:
with open(os.path.join(self.cpuset_path, "cpu" + str(exclusive_cpu), "tasks"), "rt") as fi:
exclusive_pid_set.update(map(int, fi.read().split()))
for request_file in glob.iglob(os.path.join(self.instance_root, '*', CGroupManager.cpu_exclusive_file)):
with open(request_file, "rt") as fi:
# take such PIDs which are either really running or are not already exclusive
request_pid_list = [int(pid) for pid in fi.read().split()
if int(pid) in running_pid_set or int(pid) not in exclusive_pid_set]
with open(request_file, "wt") as fo:
fo.write("") # empty file (we will write back only PIDs which weren't moved)
for request_pid in request_pid_list:
assigned_cpu = self._move_to_exclusive_cpu(request_pid)
if assigned_cpu < 0:
# if no exclusive CPU was assigned - write the PID back and try other time
with open(request_file, "at") as fo:
fo.write(str(request_pid) + "\n")
def prepare_cpu_space(self):
"""Move all PIDs from the pool of all CPUs into the first exclusive CPU."""
with open(os.path.join(self.cpuset_path, "tasks"), "rt") as fi:
running_list = sorted(list(map(int, fi.read().split())), reverse=True)
first_cpu = self._cpu_list()[0]
success_set = set()
refused_set = set()
for pid in running_list:
try:
self._move_task(pid, first_cpu)
success_set.add(pid)
time.sleep(0.01)
except IOError as e:
refused_set.add(pid)
logger.debug("Refused to move {:d} PIDs: {!s}\n"
"Suceeded in moving {:d} PIDs {!s}\n".format(
len(refused_set), refused_set, len(success_set), success_set)
)
def _cpu_list(self):
"""Extract IDs of available CPUs and return them as a list.
The first one will be always used for all non-exclusive processes.
:return: list[int]
"""
cpu_list = [] # types: list[int]
with open(self.cpuset_path + "cpuset.cpus", "rt") as cpu_def:
for cpu_def_split in cpu_def.read().strip().split(","):
# IDs can be in form "0-4" or "0,1,2,3,4"
if "-" in cpu_def_split:
a, b = map(int, cpu_def_split.split("-"))
cpu_list.extend(range(a, b + 1)) # because cgroup's range is inclusive
continue
cpu_list.append(int(cpu_def_split))
return cpu_list
def _move_to_exclusive_cpu(self, pid):
"""Try all exclusive CPUs and place the ``pid`` to the first available one.
:return: int, cpu_id of used CPU, -1 if placement was not possible
"""
exclusive_cpu_list = self._cpu_list()[1:]
for exclusive_cpu in exclusive_cpu_list:
# gather tasks assigned to current exclusive CPU
task_path = os.path.join(self.cpuset_path, "cpu" + str(exclusive_cpu), "tasks")
with open(task_path, "rt") as fi:
task_list = fi.read().split()
if len(task_list) > 0:
continue # skip occupied CPUs
return self._move_task(pid, exclusive_cpu)[1]
return -1
def _move_task(self, pid, cpu_id):
"""Move ``pid`` to ``cpu_id``."""
with open(os.path.join(self.cpuset_path, "cpu" + str(cpu_id), "tasks"), self.task_write_mode) as fo:
fo.write(str(pid) + "\n")
return pid, cpu_id
def _prepare_folder(self, folder):
"""If-Create folder and set group write permission."""
if not os.path.exists(folder):
os.mkdir(folder)
os.chown(folder, -1, self.software_gid)
# make your life and testing easier and create mandatory files if they don't exist
mandatory_file_list = ("tasks", "cpuset.cpus")
for mandatory_file in mandatory_file_list:
file_path = os.path.join(folder, mandatory_file)
if not os.path.exists(file_path):
with open(file_path, "wb"):
pass # touche
return folder
# mark manager available
available_manager_list[CGroupManager.short_name] = CGroupManager
class Computer(object):
"Object representing the computer"
instance_root = None
software_root = None
instance_storage_home = None
"""Object representing the computer"""
def __init__(self, reference, interface=None, addr=None, netmask=None,
ipv6_interface=None, software_user='slapsoft',
tap_gateway_interface=None):
tap_gateway_interface=None,
instance_root=None, software_root=None, instance_storage_home=None,
partition_list=None, manager_list=None):
"""
Attributes:
reference: String, the reference of the computer.
interface: String, the name of the computer's used interface.
reference: str, the reference of the computer.
interface: str, the name of the computer's used interface.
"""
self.reference = str(reference)
self.interface = interface
self.partition_list = []
self.partition_list = partition_list or []
self.address = addr
self.netmask = netmask
self.ipv6_interface = ipv6_interface
self.software_user = software_user
self.tap_gateway_interface = tap_gateway_interface
# The follow properties are updated on update() method
# Used to be static attributes of the class object - didn't make sense (Marco again)
assert instance_root is not None and software_root is not None, \
"Computer's instance_root and software_root must not be empty!"
self.software_root = software_root
self.instance_root = instance_root
self.instance_storage_home = instance_storage_home
# The following properties are updated on update() method
self.public_ipv4_address = None
self.os_type = None
self.python_version = None
self.slapos_version = None
# HASA relation to managers (could turn into plugins with `format` and `update` methods)
self.manager_list = manager_list # for serialization
# hide list[Manager] from serializer by prepending "_"
self._manager_list = tuple(filter(lambda manager: manager.is_allowed(),
(available_manager_list[manager_str](self) for manager_str in manager_list))) \
if manager_list else tuple()
def __getinitargs__(self):
return (self.reference, self.interface)
......@@ -304,9 +508,12 @@ class Computer(object):
raise NoAddressOnInterface('No valid IPv6 found on %s.' % self.interface.name)
def update(self):
"""
Collect environmental hardware/network information.
"""
"""Update computer runtime info and state."""
for manager in self._manager_list:
logger.info("Updating computer with " + manager.__class__.__name__)
manager.update()
# Collect environmental hardware/network information.
self.public_ipv4_address = getPublicIPv4Address()
self.slapos_version = version.version
self.python_version = platform.python_version()
......@@ -392,7 +599,8 @@ class Computer(object):
archive.writestr(saved_filename, xml_content, zipfile.ZIP_DEFLATED)
@classmethod
def load(cls, path_to_xml, reference, ipv6_interface, tap_gateway_interface):
def load(cls, path_to_xml, reference, ipv6_interface, tap_gateway_interface,
instance_root=None, software_root=None, manager_list=None):
"""
Create a computer object from a valid xml file.
......@@ -414,6 +622,9 @@ class Computer(object):
ipv6_interface=ipv6_interface,
software_user=dumped_dict.get('software_user', 'slapsoft'),
tap_gateway_interface=tap_gateway_interface,
software_root=dumped_dict.get('software_root', software_root),
instance_root=dumped_dict.get('instance_root', instance_root),
manager_list=dumped_dict.get('manager_list', manager_list),
)
for partition_index, partition_dict in enumerate(dumped_dict['partition_list']):
......@@ -492,9 +703,22 @@ class Computer(object):
command = 'ip address add dev %s fd00::1/64' % interface_name
callAndRead(command.split())
def construct(self, alter_user=True, alter_network=True, create_tap=True, use_unique_local_address_block=False):
@property
def software_gid(self):
"""Return GID for self.software_user.
Has to be dynamic because __init__ happens before ``format`` where we
effectively create the user and group."""
return pwd.getpwnam(self.software_user)[3]
def format(self, alter_user=True, alter_network=True, create_tap=True, use_unique_local_address_block=False):
"""
Construct the computer object as it is.
Setup underlaying OS so it reflects this instance (``self``).
- setup interfaces and addresses
- setup TAP and TUN interfaces
- add groups and users
- construct partitions inside slapgrid
"""
if alter_network and self.address is not None:
self.interface.addAddr(self.address, self.netmask)
......@@ -521,6 +745,11 @@ class Computer(object):
os.chown(slapsoft.path, slapsoft_pw.pw_uid, slapsoft_pw.pw_gid)
os.chmod(self.software_root, 0o755)
# Iterate over all managers and let them `format` the computer too
for manager in self._manager_list:
logger.info("Formatting computer with " + manager.__class__.__name__)
manager.format()
# get list of instance external storage if exist
instance_external_list = []
if self.instance_storage_home:
......@@ -954,7 +1183,7 @@ class Interface(object):
name: String, the name of the interface
"""
self.logger = logger
self._logger = logger
self.name = str(name)
self.ipv4_local_network = ipv4_local_network
self.ipv6_interface = ipv6_interface
......@@ -1138,7 +1367,7 @@ class Interface(object):
if self._addSystemAddress(addr, netmask, False):
return dict(addr=addr, netmask=netmask)
else:
self.logger.warning('Impossible to add old local IPv4 %s. Generating '
self._logger.warning('Impossible to add old local IPv4 %s. Generating '
'new IPv4 address.' % addr)
return self._generateRandomIPv4Address(netmask)
else:
......@@ -1196,7 +1425,7 @@ class Interface(object):
# succeed, return it
return dict(addr=addr, netmask=netmask)
else:
self.logger.warning('Impossible to add old public IPv6 %s. '
self._logger.warning('Impossible to add old public IPv6 %s. '
'Generating new IPv6 address.' % addr)
# Try 10 times to add address, raise in case if not possible
......@@ -1276,7 +1505,10 @@ def parse_computer_xml(conf, xml_path):
computer = Computer.load(xml_path,
reference=conf.computer_id,
ipv6_interface=conf.ipv6_interface,
tap_gateway_interface=conf.tap_gateway_interface)
tap_gateway_interface=conf.tap_gateway_interface,
software_root=conf.software_root,
instance_root=conf.instance_root,
manager_list=conf.manager_list)
# Connect to the interface defined by the configuration
computer.interface = interface
else:
......@@ -1284,12 +1516,15 @@ def parse_computer_xml(conf, xml_path):
conf.logger.warning('Creating new computer data with id %r', conf.computer_id)
computer = Computer(
reference=conf.computer_id,
software_root=conf.software_root,
instance_root=conf.software_root,
interface=interface,
addr=None,
netmask=None,
ipv6_interface=conf.ipv6_interface,
software_user=conf.software_user,
tap_gateway_interface=conf.tap_gateway_interface,
manager_list=conf.manager_list,
)
partition_amount = int(conf.partition_amount)
......@@ -1360,8 +1595,6 @@ def do_format(conf):
# no definition file, figure out computer
computer = parse_computer_xml(conf, conf.computer_xml)
computer.instance_root = conf.instance_root
computer.software_root = conf.software_root
computer.instance_storage_home = conf.instance_storage_home
conf.logger.info('Updating computer')
address = computer.getAddress(conf.create_tap)
......@@ -1371,7 +1604,7 @@ def do_format(conf):
if conf.output_definition_file:
write_computer_definition(conf, computer)
computer.construct(alter_user=conf.alter_user,
computer.format(alter_user=conf.alter_user,
alter_network=conf.alter_network,
create_tap=conf.create_tap,
use_unique_local_address_block=conf.use_unique_local_address_block)
......@@ -1406,6 +1639,7 @@ class FormatConfig(object):
tap_gateway_interface = None
use_unique_local_address_block = None
instance_storage_home = None
manager_list = None
def __init__(self, logger):
self.logger = logger
......@@ -1493,6 +1727,16 @@ class FormatConfig(object):
self.logger.error(message)
raise UsageError(message)
# Split str into list[str] and check availability of every manager
# Config value is expected to be strings separated by spaces or commas
manager_list = []
for manager in self.manager_list.replace(",", " ").split():
if manager not in available_manager_list:
raise ValueError("Unknown manager \"{}\"! Known are: {!s}".format(
manager, list(available_manager_list.keys())))
manager_list.append(manager)
self.manager_list = manager_list # replace original str with list[str] of known managers
if not self.dry_run:
if self.alter_user:
self.checkRequiredBinary(['groupadd', 'useradd', 'usermod', ['passwd', '-h']])
......
......@@ -26,15 +26,15 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import glob
import logging
import slapos.format
import slapos.util
import unittest
import netaddr
import shutil
import socket
# for mocking
import grp
import netifaces
......@@ -47,6 +47,20 @@ GROUP_LIST = []
INTERFACE_DICT = {}
def file_content(file_path):
"""Read file(s) content."""
if isinstance(file_path, (list, tuple)):
return [file_content(fx) for fx in file_path]
with open(file_path, "rt") as fi:
return fi.read().strip()
def file_write(stuff, file_path):
"""Write stuff into file_path."""
with open(file_path, "wt") as fo:
fo.write(stuff)
class FakeConfig:
pass
......@@ -138,11 +152,16 @@ class PwdMock:
def getpwnam(self, name):
global USER_LIST
if name in USER_LIST:
class result:
class PwdResult:
pw_uid = 0
pw_gid = 0
return result
raise KeyError
def __getitem__(self, index):
if index == 2:
return self.pw_uid
if index == 3:
return self.pw_gid
return PwdResult()
raise KeyError("User \"{}\" not in global USER_LIST {!s}".format(name, USER_LIST))
class NetifacesMock:
......@@ -151,7 +170,8 @@ class NetifacesMock:
global INTERFACE_DICT
if name in INTERFACE_DICT:
return INTERFACE_DICT[name]
raise ValueError
raise ValueError("Interface \"{}\" not in INTERFACE_DICT {!s}".format(
name, INTERFACE_DICT))
@classmethod
def interfaces(self):
......@@ -163,6 +183,21 @@ class SlaposUtilMock:
def chownDirectory(*args, **kw):
pass
class CGroupManagerMock(slapos.format.CGroupManager):
short_name = 'cgroup_mock'
cpuset_path = "/tmp/cpuset/"
task_write_mode = "at" # append insted of write tasks PIDs for the tests
def is_allowed(self):
"""Always allowed."""
return True
# update available managers with our partially-mocked version
slapos.format.available_manager_list[CGroupManagerMock.short_name] = CGroupManagerMock
class SlapformatMixin(unittest.TestCase):
# keep big diffs
maxDiff = None
......@@ -220,6 +255,8 @@ class SlapformatMixin(unittest.TestCase):
setattr(os, fake, f)
def restoreOs(self):
if not hasattr(self, 'saved_os'):
return # os was never patched or already restored
for name, original_value in self.saved_os.items():
setattr(os, name, original_value)
del self.saved_os
......@@ -278,23 +315,23 @@ class SlapformatMixin(unittest.TestCase):
class TestComputer(SlapformatMixin):
def test_getAddress_empty_computer(self):
computer = slapos.format.Computer('computer')
computer = slapos.format.Computer('computer', instance_root='/instance_root', software_root='software_root')
self.assertEqual(computer.getAddress(), {'netmask': None, 'addr': None})
@unittest.skip("Not implemented")
def test_construct_empty(self):
computer = slapos.format.Computer('computer')
computer.construct()
computer = slapos.format.Computer('computer', instance_root='/instance_root', software_root='software_root')
computer.format()
@unittest.skip("Not implemented")
def test_construct_empty_prepared(self):
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.logger,
name='bridge',
ipv4_local_network='127.0.0.1/16'))
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
computer.construct()
instance_root='/instance_root',
software_root='/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='bridge', ipv4_local_network='127.0.0.1/16'),
partition_list=[])
computer.format()
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
......@@ -310,29 +347,31 @@ class TestComputer(SlapformatMixin):
def test_construct_empty_prepared_no_alter_user(self):
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.logger,
name='bridge',
ipv4_local_network='127.0.0.1/16'))
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
computer.construct(alter_user=False)
instance_root='/instance_root',
software_root='/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='bridge', ipv4_local_network='127.0.0.1/16'),
partition_list=[])
computer.format(alter_user=False)
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
"chmod('/software_root', 493)"],
self.test_result.bucket)
self.assertEqual(['ip addr list bridge'],
self.fakeCallAndRead.external_command_list)
self.assertEqual([
'ip addr list bridge',
'brctl show',
],
self.fakeCallAndRead.external_command_list)
@unittest.skip("Not implemented")
def test_construct_empty_prepared_no_alter_network(self):
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.logger,
name='bridge',
ipv4_local_network='127.0.0.1/16'))
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
computer.construct(alter_network=False)
instance_root='/instance_root',
software_root='/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='bridge', ipv4_local_network='127.0.0.1/16'),
partition_list=[])
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
......@@ -348,34 +387,34 @@ class TestComputer(SlapformatMixin):
def test_construct_empty_prepared_no_alter_network_user(self):
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.logger,
name='bridge',
ipv4_local_network='127.0.0.1/16'))
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
computer.construct(alter_network=False, alter_user=False)
instance_root='/instance_root',
software_root='/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='bridge', ipv4_local_network='127.0.0.1/16'),
partition_list=[])
computer.format(alter_network=False, alter_user=False)
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
"chmod('/software_root', 493)"],
self.test_result.bucket)
self.assertEqual([
'ip addr list bridge'
'ip addr list bridge',
'brctl show',
],
self.fakeCallAndRead.external_command_list)
@unittest.skip("Not implemented")
def test_construct_prepared(self):
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.logger,
name='bridge',
ipv4_local_network='127.0.0.1/16'))
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
partition = slapos.format.Partition('partition', '/part_path',
slapos.format.User('testuser'), [], None)
partition.tap = slapos.format.Tap('tap')
computer.partition_list = [partition]
instance_root='/instance_root',
software_root='/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='bridge', ipv4_local_network='127.0.0.1/16'),
partition_list=[
slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [], tap=slapos.format.Tap('tap')),
])
global INTERFACE_DICT
INTERFACE_DICT['bridge'] = {
socket.AF_INET: [{'addr': '192.168.242.77', 'broadcast': '127.0.0.1',
......@@ -383,7 +422,7 @@ class TestComputer(SlapformatMixin):
socket.AF_INET6: [{'addr': '2a01:e35:2e27::e59c', 'netmask': 'ffff:ffff:ffff:ffff::'}]
}
computer.construct()
computer.format()
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
......@@ -400,7 +439,8 @@ class TestComputer(SlapformatMixin):
'useradd -d /software_root -g slapsoft slapsoft -r',
'groupadd testuser',
'useradd -d /instance_root/partition -g testuser -G slapsoft testuser -r',
'tunctl -t tap -u testuser',
'brctl show',
'ip tuntap add dev tap mode tap user testuser',
'ip link set tap up',
'brctl show',
'brctl addif bridge tap',
......@@ -413,17 +453,16 @@ class TestComputer(SlapformatMixin):
def test_construct_prepared_no_alter_user(self):
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.logger,
name='bridge',
ipv4_local_network='127.0.0.1/16'))
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
partition = slapos.format.Partition('partition', '/part_path',
slapos.format.User('testuser'), [], None)
instance_root='/instance_root',
software_root='/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='bridge', ipv4_local_network='127.0.0.1/16'),
partition_list=[
slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [], tap=slapos.format.Tap('tap')),
])
global USER_LIST
USER_LIST = ['testuser']
partition.tap = slapos.format.Tap('tap')
computer.partition_list = [partition]
global INTERFACE_DICT
INTERFACE_DICT['bridge'] = {
socket.AF_INET: [{'addr': '192.168.242.77', 'broadcast': '127.0.0.1',
......@@ -431,7 +470,7 @@ class TestComputer(SlapformatMixin):
socket.AF_INET6: [{'addr': '2a01:e35:2e27::e59c', 'netmask': 'ffff:ffff:ffff:ffff::'}]
}
computer.construct(alter_user=False)
computer.format(alter_user=False)
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
......@@ -441,33 +480,34 @@ class TestComputer(SlapformatMixin):
],
self.test_result.bucket)
self.assertEqual([
'ip addr list bridge',
'tunctl -t tap -u testuser',
'ip link set tap up',
'brctl show', # we check bridges at Interface initialization
'brctl show',
'brctl addif bridge tap',
'ip addr add ip/255.255.255.255 dev bridge',
# 'ip addr list bridge',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev bridge',
'ip -6 addr list bridge',
],
'ip addr list bridge',
'brctl show',
'ip tuntap add dev tap mode tap user testuser',
'ip link set tap up',
'brctl show',
'brctl show',
'brctl addif bridge tap',
'ip addr add ip/255.255.255.255 dev bridge',
# 'ip addr list bridge',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev bridge',
'ip -6 addr list bridge',
],
self.fakeCallAndRead.external_command_list)
def test_construct_prepared_tap_no_alter_user(self):
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.test_result,
name='iface',
ipv4_local_network='127.0.0.1/16'),
tap_gateway_interface='eth1')
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
partition = slapos.format.Partition('partition', '/part_path',
slapos.format.User('testuser'), [], None)
instance_root='/instance_root',
software_root='/software_root',
tap_gateway_interface='eth1',
interface=slapos.format.Interface(
logger=self.logger, name='iface', ipv4_local_network='127.0.0.1/16'),
partition_list=[
slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')),
])
global USER_LIST
USER_LIST = ['testuser']
partition.tap = slapos.format.Tap('tap')
computer.partition_list = [partition]
global INTERFACE_DICT
INTERFACE_DICT['iface'] = {
socket.AF_INET: [{'addr': '192.168.242.77', 'broadcast': '127.0.0.1',
......@@ -479,7 +519,7 @@ class TestComputer(SlapformatMixin):
'netmask': '255.255.255.0'}]
}
computer.construct(alter_user=False)
computer.format(alter_user=False)
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
......@@ -490,15 +530,17 @@ class TestComputer(SlapformatMixin):
self.test_result.bucket)
self.assertEqual([
'ip addr list iface',
'tunctl -t tap -u testuser',
'brctl show',
'ip tuntap add dev tap mode tap user testuser',
'ip link set tap up',
'ip route show 10.8.0.2',
'route add -host 10.8.0.2 dev tap',
'ip route add 10.8.0.2 dev tap',
'ip addr add ip/255.255.255.255 dev iface',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev iface',
'ip -6 addr list iface'
],
self.fakeCallAndRead.external_command_list)
partition = computer.partition_list[0]
self.assertEqual(partition.tap.ipv4_addr, '10.8.0.2')
self.assertEqual(partition.tap.ipv4_netmask, '255.255.255.0')
self.assertEqual(partition.tap.ipv4_gateway, '10.8.0.1')
......@@ -507,23 +549,22 @@ class TestComputer(SlapformatMixin):
@unittest.skip("Not implemented")
def test_construct_prepared_no_alter_network(self):
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.logger,
name='bridge',
ipv4_local_network='127.0.0.1/16'))
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
partition = slapos.format.Partition('partition', '/part_path',
slapos.format.User('testuser'), [], None)
partition.tap = slapos.format.Tap('tap')
computer.partition_list = [partition]
instance_root='/instance_root',
software_root='/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='bridge', ipv4_local_network='127.0.0.1/16'),
partition_list=[
slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')),
])
global INTERFACE_DICT
INTERFACE_DICT['bridge'] = {
socket.AF_INET: [{'addr': '192.168.242.77', 'broadcast': '127.0.0.1',
'netmask': '255.255.255.0'}],
socket.AF_INET6: [{'addr': '2a01:e35:2e27::e59c', 'netmask': 'ffff:ffff:ffff:ffff::'}]
}
computer.construct(alter_network=False)
computer.format(alter_network=False)
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
......@@ -549,15 +590,15 @@ class TestComputer(SlapformatMixin):
def test_construct_prepared_no_alter_network_user(self):
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.logger,
name='bridge',
ipv4_local_network='127.0.0.1/16'))
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
partition = slapos.format.Partition('partition', '/part_path',
slapos.format.User('testuser'), [], None)
partition.tap = slapos.format.Tap('tap')
computer.partition_list = [partition]
instance_root='/instance_root',
software_root='/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='bridge', ipv4_local_network='127.0.0.1/16'),
partition_list=[
slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')),
])
global INTERFACE_DICT
INTERFACE_DICT['bridge'] = {
socket.AF_INET: [{'addr': '192.168.242.77', 'broadcast': '127.0.0.1',
......@@ -565,22 +606,23 @@ class TestComputer(SlapformatMixin):
socket.AF_INET6: [{'addr': '2a01:e35:2e27::e59c', 'netmask': 'ffff:ffff:ffff:ffff::'}]
}
computer.construct(alter_network=False, alter_user=False)
computer.format(alter_network=False, alter_user=False)
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
"chmod('/software_root', 493)",
"mkdir('/instance_root/partition', 488)",
"chmod('/instance_root/partition', 488)"
],
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
"chmod('/software_root', 493)",
"mkdir('/instance_root/partition', 488)",
"chmod('/instance_root/partition', 488)"
],
self.test_result.bucket)
self.assertEqual([
'ip addr list bridge',
'ip addr add ip/255.255.255.255 dev bridge',
# 'ip addr list bridge',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev bridge',
'ip -6 addr list bridge',
],
'ip addr list bridge',
'brctl show',
'ip addr add ip/255.255.255.255 dev bridge',
# 'ip addr list bridge',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev bridge',
'ip -6 addr list bridge',
],
self.fakeCallAndRead.external_command_list)
def test_construct_use_unique_local_address_block(self):
......@@ -590,15 +632,15 @@ class TestComputer(SlapformatMixin):
global USER_LIST
USER_LIST = ['root']
computer = slapos.format.Computer('computer',
interface=slapos.format.Interface(logger=self.logger,
name='myinterface',
ipv4_local_network='127.0.0.1/16'))
computer.instance_root = '/instance_root'
computer.software_root = '/software_root'
partition = slapos.format.Partition('partition', '/part_path',
slapos.format.User('testuser'), [], None)
partition.tap = slapos.format.Tap('tap')
computer.partition_list = [partition]
instance_root='/instance_root',
software_root='/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='myinterface', ipv4_local_network='127.0.0.1/16'),
partition_list=[
slapos.format.Partition(
'partition', '/part_path', slapos.format.User('testuser'), [],
tap=slapos.format.Tap('tap')),
])
global INTERFACE_DICT
INTERFACE_DICT['myinterface'] = {
socket.AF_INET: [{'addr': '192.168.242.77', 'broadcast': '127.0.0.1',
......@@ -606,24 +648,108 @@ class TestComputer(SlapformatMixin):
socket.AF_INET6: [{'addr': '2a01:e35:2e27::e59c', 'netmask': 'ffff:ffff:ffff:ffff::'}]
}
computer.construct(use_unique_local_address_block=True, alter_user=False, create_tap=False)
computer.format(use_unique_local_address_block=True, alter_user=False, create_tap=False)
self.assertEqual([
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
"chmod('/software_root', 493)",
"mkdir('/instance_root/partition', 488)",
"chmod('/instance_root/partition', 488)"
],
"makedirs('/instance_root', 493)",
"makedirs('/software_root', 493)",
"chmod('/software_root', 493)",
"mkdir('/instance_root/partition', 488)",
"chmod('/instance_root/partition', 488)"
],
self.test_result.bucket)
self.assertEqual([
'ip addr list myinterface',
'ip address add dev myinterface fd00::1/64',
'ip addr add ip/255.255.255.255 dev myinterface',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev myinterface',
'ip -6 addr list myinterface'
],
'ip addr list myinterface',
'brctl show',
'ip address add dev myinterface fd00::1/64',
'ip addr add ip/255.255.255.255 dev myinterface',
'ip addr add ip/ffff:ffff:ffff:ffff:: dev myinterface',
'ip -6 addr list myinterface'
],
self.fakeCallAndRead.external_command_list)
class TestComputerWithCGroup(SlapformatMixin):
def setUp(self):
super(TestComputerWithCGroup, self).setUp()
self.restoreOs()
if os.path.isdir("/tmp/slapgrid/"):
shutil.rmtree("/tmp/slapgrid/")
os.mkdir("/tmp/slapgrid/")
if os.path.isdir(CGroupManagerMock.cpuset_path):
shutil.rmtree(CGroupManagerMock.cpuset_path)
os.mkdir(CGroupManagerMock.cpuset_path)
file_write("0,1-3",
os.path.join(CGroupManagerMock.cpuset_path, "cpuset.cpus"))
file_write("\n".join(("1000", "1001", "1002", "")),
os.path.join(CGroupManagerMock.cpuset_path, "tasks"))
self.cpu_list = [0, 1, 2, 3]
global USER_LIST, INTERFACE_DICT
USER_LIST = ['testuser']
INTERFACE_DICT['bridge'] = {
socket.AF_INET: [
{'addr': '127.0.0.1', 'broadcast': '127.0.255.255', 'netmask': '255.255.0.0'}],
socket.AF_INET6: [
{'addr': '2a01:e35:2e27::e59c', 'netmask': 'ffff:ffff:ffff:ffff::'}]
}
self.computer = slapos.format.Computer('computer',
software_user='testuser',
instance_root='/tmp/slapgrid/instance_root',
software_root='/tmp/slapgrid/software_root',
interface=slapos.format.Interface(
logger=self.logger, name='bridge', ipv4_local_network='127.0.0.1/16'),
partition_list=[
slapos.format.Partition(
'partition', '/tmp/slapgrid/instance_root/part1', slapos.format.User('testuser'), [], tap=None),
],
manager_list=(CGroupManagerMock.short_name, )
)
# self.patchOs(self.logger)
def tearDown(self):
"""Cleanup temporary test folders."""
super(TestComputerWithCGroup, self).tearDown()
shutil.rmtree("/tmp/slapgrid/")
if CGroupManagerMock.cpuset_path.startswith("/tmp"):
shutil.rmtree(CGroupManagerMock.cpuset_path)
def test_positive_cgroups(self):
"""Positive test of cgroups."""
# Test parsing "cpuset.cpus" file
self.assertEqual(self.computer._manager_list[0]._cpu_list(), self.cpu_list)
# This should created per-cpu groups and move all tasks in CPU pool into cpu0
self.computer.format(alter_network=False, alter_user=False)
# Test files creation for exclusive CPUs
for cpu_id in self.cpu_list:
cpu_n_path = os.path.join(CGroupManagerMock.cpuset_path, "cpu" + str(cpu_id))
self.assertEqual(str(cpu_id), file_content(os.path.join(cpu_n_path, "cpuset.cpus")))
self.assertEqual("1", file_content(os.path.join(cpu_n_path, "cpuset.cpu_exclusive")))
if cpu_id > 0:
self.assertEqual("", file_content(os.path.join(cpu_n_path, "tasks")))
# Test that format moved all PIDs from CPU pool into CPU0
tasks_at_cpu0 = file_content(os.path.join(CGroupManagerMock.cpuset_path, "cpu0", "tasks")).split()
self.assertIn("1000", tasks_at_cpu0)
self.assertIn("1001", tasks_at_cpu0)
self.assertIn("1002", tasks_at_cpu0)
# Simulate cgroup behaviour - empty tasks in the pool
file_write("", os.path.join(CGroupManagerMock.cpuset_path, "tasks"))
# test moving tasks from generic core to private core
# request PID 1001 to be moved to its private CPU
request_file_path = os.path.join(self.computer.partition_list[0].path,
CGroupManagerMock.cpu_exclusive_file)
file_write("1001\n", request_file_path)
# let format do the moving
self.computer.update()
# test if the moving suceeded into any provate CPUS (id>0)
self.assertTrue(any("1001" in file_content(exclusive_task)
for exclusive_task in glob.glob(os.path.join(CGroupManagerMock.cpuset_path, "cpu[1-9]", "tasks"))))
# slapformat should remove successfully moved PIDs from the .slapos-cpu-exclusive file
self.assertEqual("", file_content(request_file_path).strip())
class TestPartition(SlapformatMixin):
def test_createPath_no_alter_user(self):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment