Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
slapos
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
104
Merge Requests
104
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
nexedi
slapos
Commits
7c9fdce8
Commit
7c9fdce8
authored
Jun 13, 2017
by
Tomáš Peterka
Committed by
Rafael Monnerat
Jun 13, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[manager.cpuset] Manager interface and cpuset implementation (with tests)
parent
e01881fd
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
74 additions
and
37 deletions
+74
-37
setup.py
setup.py
+1
-0
slapos/README.manager.txt
slapos/README.manager.txt
+13
-0
slapos/format.py
slapos/format.py
+4
-1
slapos/grid/slapgrid.py
slapos/grid/slapgrid.py
+1
-1
slapos/manager/__init__.py
slapos/manager/__init__.py
+15
-7
slapos/manager/cpuset.py
slapos/manager/cpuset.py
+13
-10
slapos/tests/slapformat.py
slapos/tests/slapformat.py
+27
-18
No files found.
setup.py
View file @
7c9fdce8
...
...
@@ -55,6 +55,7 @@ setup(name=name,
'zc.buildout'
,
'cliff'
,
'requests>=2.4.3'
,
'six'
,
'uritemplate'
,
# used by hateoas navigator
]
+
additional_install_requires
,
extras_require
=
{
...
...
slapos/README.manager.txt
View file @
7c9fdce8
...
...
@@ -10,3 +10,16 @@ Manager is a plugin-like class that is being run in multiple phases of slapos no
Constructor will receive configuration of current stage. Then each method receives
object most related to the current operation. For details see <slapos/manager/interface.py>.
In code, a list of manager instances can be easily retreived by
from slapos import manager
manager_list = manager.from_config(config)
Where `from_config` extracts "manager_list" item from dict-like `config` argument
and then dynamically loads modules named according to the configuration inside
`slapos.manager` package. The manager must be a class named Manager and implementing
interface `slapos.manager.interface.IManager`.
Managers might require a list of user for whom they are allowed to perform tasks.
This list of users is given by "power_user_list" in [slapos] section in the
config file.
slapos/format.py
View file @
7c9fdce8
...
...
@@ -280,7 +280,10 @@ class Computer(object):
# attributes starting with '_' are saved from serialization
# monkey-patch use of class instead of dictionary
self
.
_config
=
config
if
isinstance
(
config
,
dict
)
else
config
.
__dict__
if
config
is
None
:
logger
.
warning
(
"Computer needs config in constructor to allow managers."
)
self
.
_config
=
config
if
config
is
None
or
isinstance
(
config
,
dict
)
else
config
.
__dict__
self
.
_manager_list
=
slapmanager
.
from_config
(
self
.
_config
)
def
__getinitargs__
(
self
):
...
...
slapos/grid/slapgrid.py
View file @
7c9fdce8
...
...
@@ -1086,7 +1086,7 @@ stderr_logfile_backups=1
# call manager for every software release
for
manager
in
self
.
_manager_list
:
manager
.
instance
(
partition
)
manager
.
instance
(
local_
partition
)
if
computer_partition_state
==
COMPUTER_PARTITION_STARTED_STATE
:
local_partition
.
install
()
...
...
slapos/manager/__init__.py
View file @
7c9fdce8
# coding: utf-8
import
re
import
importlib
import
re
import
six
from
zope.interface
import
declarations
...
...
@@ -12,20 +13,27 @@ def load_manager(name):
if
re
.
match
(
r'[a-zA-Z_]'
,
name
)
is
None
:
raise
ValueError
(
"Manager name
\
"
{!s}
\
"
is not allowed! Must contain only letters and
\
"
_
\
"
"
.
format
(
name
))
manager_module_name
=
"slapos.manager.{}"
.
format
(
name
)
from
slapos.manager
import
interface
manager_module
=
importlib
.
import_module
(
"slapos.manager."
+
name
)
manager_module
=
importlib
.
import_module
(
manager_module_
name
)
if
not
hasattr
(
manager_module
,
"Manager"
):
raise
AttributeError
(
"Manager class in {} has to be called
\
"
Manager
\
"
"
.
format
(
name
))
manager_module_
name
))
if
not
interface
.
IManager
.
implementedBy
(
manager_module
.
Manager
):
raise
RuntimeError
(
"Manager class in {} must zope.interface.implements
\
"
IManager
\
"
"
.
format
(
name
))
manager_module_
name
))
return
manager_module
.
Manager
def
from_config
(
config
):
"""Return list of instances of managers allowed from the config."""
name_list
=
config
.
get
(
config_option
,
""
).
split
()
if
config
is
None
:
return
[]
name_list
=
config
.
get
(
config_option
,
""
)
if
isinstance
(
name_list
,
six
.
string_types
):
name_list
=
name_list
.
replace
(
","
,
" "
).
split
()
return
[
load_manager
(
name
)(
config
)
for
name
in
name_list
]
\ No newline at end of file
slapos/manager/cpuset.py
View file @
7c9fdce8
...
...
@@ -2,6 +2,8 @@
import
logging
import
os
import
os.path
import
pwd
import
time
from
zope
import
interface
as
zope_interface
from
slapos.manager
import
interface
...
...
@@ -82,13 +84,14 @@ class Manager(object):
for
cpu_folder
in
self
.
_cpu_folder_list
()]
# Gather exclusive CPU usage map {username: set[cpu_id]}
cpu_usage
=
defaultdict
(
set
)
for
cpu_id
in
self
.
_cpu_id_list
()[
1
:]:
# skip the first public CPU
pids
=
[
int
(
pid
)
for
pid
in
read_file
(
cpu_tasks_file_list
[
cpu_id
]).
splitlines
()]
for
pid
in
pids
:
process
=
psutil
.
Process
(
pid
)
cpu_usage
[
process
.
username
()].
add
(
cpu_id
)
# We do not need to gather that since we have no limits yet
#cpu_usage = defaultdict(set)
#for cpu_id in self._cpu_id_list()[1:]: # skip the first public CPU
# pids = [int(pid)
# for pid in read_file(cpu_tasks_file_list[cpu_id]).splitlines()]
# for pid in pids:
# process = psutil.Process(pid)
# cpu_usage[process.username()].add(cpu_id)
# Move all PIDs from the pool of all CPUs onto the first exclusive CPU.
running_list
=
sorted
(
list
(
map
(
int
,
read_file
(
tasks_file
).
split
())),
reverse
=
True
)
...
...
@@ -105,7 +108,7 @@ class Manager(object):
"Suceeded in moving {:d} PIDs {!s}
\
n
"
.
format
(
len
(
refused_set
),
refused_set
,
len
(
success_set
),
success_set
))
cpu_list
=
self
.
_cpu_folder_list
()
cpu_
folder_
list
=
self
.
_cpu_folder_list
()
generic_cpu_path
=
cpu_folder_list
[
0
]
exclusive_cpu_path_list
=
cpu_folder_list
[
1
:]
...
...
@@ -116,7 +119,7 @@ class Manager(object):
# gather already exclusively running PIDs
exclusive_pid_set
=
set
()
for
cpu_tasks_file
in
cpu_tasks_file_list
[
1
:]:
exclusive_pid_set
.
update
(
map
(
int
,
read_
content
(
cpu_tasks_file
).
split
()))
exclusive_pid_set
.
update
(
map
(
int
,
read_
file
(
cpu_tasks_file
).
split
()))
# Move processes to their demanded exclusive CPUs
with
open
(
request_file
,
"rt"
)
as
fi
:
...
...
@@ -135,7 +138,7 @@ class Manager(object):
def
_cpu_folder_list
(
self
):
"""Return list of folders for exclusive cpu cores."""
return
[
os
.
path
.
join
(
self
.
cpuset_path
,
"cpu"
+
str
(
cpu_id
))
for
cpu_id
in
self
.
_cpu_id_list
]
for
cpu_id
in
self
.
_cpu_id_list
()
]
def
_cpu_id_list
(
self
):
"""Extract IDs of available CPUs and return them as a list.
...
...
slapos/tests/slapformat.py
View file @
7c9fdce8
...
...
@@ -155,14 +155,18 @@ class PwdMock:
global
USER_LIST
if
name
in
USER_LIST
:
class
PwdResult
:
pw_uid
=
0
pw_gid
=
0
def
__init__
(
self
,
name
):
self
.
pw_name
=
name
self
.
pw_uid
=
self
.
pw_gid
=
USER_LIST
.
index
(
name
)
def
__getitem__
(
self
,
index
):
if
index
==
0
:
return
self
.
pw_name
if
index
==
2
:
return
self
.
pw_uid
if
index
==
3
:
return
self
.
pw_gid
return
PwdResult
()
return
PwdResult
(
name
)
raise
KeyError
(
"User
\
"
{}
\
"
not in global USER_LIST {!s}"
.
format
(
name
,
USER_LIST
))
...
...
@@ -656,10 +660,14 @@ class TestComputer(SlapformatMixin):
class
SlapGridPartitionMock
:
def
__init__
(
self
,
partition
):
self
.
partition
=
partition
self
.
instance_path
=
partition
.
path
def
getUserGroupId
(
self
):
return
(
0
,
0
)
class
TestComputerWithCPUSet
(
SlapformatMixin
):
...
...
@@ -667,6 +675,9 @@ class TestComputerWithCPUSet(SlapformatMixin):
task_write_mode
=
"at"
# append insted of write tasks PIDs for the tests
def
setUp
(
self
):
logging
.
getLogger
(
"slapos.manager.cpuset"
).
addHandler
(
logging
.
StreamHandler
())
super
(
TestComputerWithCPUSet
,
self
).
setUp
()
self
.
restoreOs
()
...
...
@@ -710,14 +721,13 @@ class TestComputerWithCPUSet(SlapformatMixin):
],
config
=
{
"manager_list"
:
"cpuset"
,
"power_user_list"
:
"testuser"
"power_user_list"
:
"testuser
root
"
}
)
# self.patchOs(self.logger)
def
tearDown
(
self
):
"""Cleanup temporary test folders."""
from
slapos.manager.cpuset
import
Manager
Manager
.
cpuset_path
=
self
.
orig_cpuset_path
Manager
.
task_write_mode
=
self
.
orig_task_write_mode
...
...
@@ -726,6 +736,7 @@ class TestComputerWithCPUSet(SlapformatMixin):
shutil
.
rmtree
(
"/tmp/slapgrid/"
)
if
self
.
cpuset_path
.
startswith
(
"/tmp"
):
shutil
.
rmtree
(
self
.
cpuset_path
)
logging
.
getLogger
(
"slapos.manager.cpuset"
)
def
test_positive_cgroups
(
self
):
"""Positive test of cgroups."""
...
...
@@ -741,25 +752,23 @@ class TestComputerWithCPUSet(SlapformatMixin):
if
cpu_id
>
0
:
self
.
assertEqual
(
""
,
file_content
(
os
.
path
.
join
(
cpu_n_path
,
"tasks"
)))
# Simulate slapos instance call
self
.
computer
.
_manager_list
[
0
].
instance
(
SlapGridPartitionMock
(
self
.
computer
.
partition_list
[
0
]))
# Test that format moved all PIDs from CPU pool into CPU0
tasks_at_cpu0
=
file_content
(
os
.
path
.
join
(
self
.
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
(
self
.
cpuset_path
,
"tasks"
))
# test moving tasks from generic core to private core
# 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
,
s
elf
.
cpu_exclusive_file
)
s
lapos
.
manager
.
cpuset
.
Manager
.
cpu_exclusive_file
)
file_write
(
"1001
\
n
"
,
request_file_path
)
# let format do the moving
self
.
computer
.
update
()
# Simulate slapos instance call to perform the actual movement
self
.
computer
.
_manager_list
[
0
].
instance
(
SlapGridPartitionMock
(
self
.
computer
.
partition_list
[
0
]))
# Simulate cgroup behaviour - empty tasks in the pool
file_write
(
""
,
os
.
path
.
join
(
self
.
cpuset_path
,
"tasks"
))
# Test that format moved all PIDs from CPU pool into CPU0
tasks_at_cpu0
=
file_content
(
os
.
path
.
join
(
self
.
cpuset_path
,
"cpu0"
,
"tasks"
)).
split
()
self
.
assertIn
(
"1000"
,
tasks_at_cpu0
)
# 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
(
self
.
cpuset_path
,
"cpu[1-9]"
,
"tasks"
))))
self
.
assertIn
(
"1002"
,
tasks_at_cpu0
)
# slapformat should remove successfully moved PIDs from the .slapos-cpu-exclusive file
self
.
assertEqual
(
""
,
file_content
(
request_file_path
).
strip
())
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment