Initial commit
[ta/infra-ansible.git] / roles / allocate_cpu_cores / filter_plugins / helpers.py
1 #!/usr/bin/python
2
3 # Copyright 2019 Nokia
4
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 import itertools
18 import re
19
20 from ansible.errors import AnsibleError
21
22
23 def cpulist_to_set(cl):
24     r = set()
25     for e in cl.split(','):
26         if '-' in e:
27             p = e.split('-')
28         else:
29             p = [e, e]
30         r.update(map(str, range(int(p[0]), int(p[1]) + 1)))
31     return list(r)
32
33
34 def set_to_cpulist(cs):
35     r = []
36     for k, g in itertools.groupby(enumerate(sorted(cs, key=int)), lambda (i, v): int(v) - i):
37         t = list(g)
38         if len(t) == 1:
39             r.append(str(t[0][1]))
40         else:
41             r.append('-'.join([str(t[0][1]), str(t[-1][1])]))
42     return ','.join(r)
43
44
45 def _validate_node(topo, node, count):
46     if node not in topo:
47         raise AnsibleError("Unknown NUMA node '%s' (known nodes: %s)"
48                            % (node, ', '.join(topo.keys())))
49     if len(topo[node]) < count:
50         raise AnsibleError("Cannot allocate %d CPUs in NUMA node '%s' (%d CPUs available)"
51                            % (count, node, len(topo[node])))
52
53
54 def cpu_topology_alloc(topo, req, where='head'):
55     r = set()
56     for node, count in req.iteritems():
57         _validate_node(topo, node, count)
58         if count == 0:
59             continue
60         if where == 'tail':
61             for i in topo[node][-count:]:
62                 r.update(i)
63         else:
64             for i in topo[node][:count]:
65                 r.update(i)
66     return sorted(r)
67
68
69 def cpu_topology_trim(topo, req, where='head'):
70     for node, count in req.iteritems():
71         _validate_node(topo, node, count)
72         if count == 0:
73             continue
74         if where == 'tail':
75             topo[node] = topo[node][:-count]
76         else:
77             topo[node] = topo[node][count:]
78     return topo
79
80
81 def _natural_sort(k):
82     return [int(x) if x.isdigit() else x for x in re.split(r'([0-9]+)', k)]
83
84
85 def cpu_topology_defaults(topo, srv, req, is_virtual=False, is_single=False):
86     own = 0
87     shared = 0
88
89     if is_virtual:
90         req = req['virtual']
91     elif is_single:
92         req = req['single']
93     else:
94         req = req['default']
95
96     for s in srv:
97         if s not in req:
98             continue
99         if 'own' in req[s]:
100             own += req[s]['own']
101         if 'shared' in req[s]:
102             shared = max(shared, req[s]['shared'])
103
104     platform = own + shared
105     if platform == 0:
106         platform = 1
107
108     nodes = [{'node': n, 'count': len(topo[n])} for n in sorted(topo, key=_natural_sort)]
109
110     r = {}
111
112     while platform > 0:
113         prev = platform
114         for i in nodes:
115             if i['count'] <= 0:
116                 continue
117             if i['node'] not in r:
118                 r[i['node']] = 0
119             r[i['node']] += 1
120             i['count'] -= 1
121             platform -= 1
122             if platform <= 0:
123                 break
124         if platform == prev:
125             break
126
127     return r
128
129
130 def get_cpu_count_by_percent(topo, percent):
131     return {
132         n: int(_amount_with_minimum(len(topo[n]), float(percent)))
133         for n in sorted(topo, key=_natural_sort)}
134
135
136 def _amount_with_minimum(whole, percent):
137     amount = (whole * percent) / 100.0
138     return amount if 0 < int(amount) else 1
139
140
141 class FilterModule(object):
142     def filters(self):
143         return {
144             'cpulist_to_set': cpulist_to_set,
145             'set_to_cpulist': set_to_cpulist,
146             'cpu_topology_alloc': cpu_topology_alloc,
147             'cpu_topology_trim': cpu_topology_trim,
148             'cpu_topology_defaults': cpu_topology_defaults,
149             'get_cpu_count_by_percent': get_cpu_count_by_percent,
150         }