fix passing optional -i to vuseradd
[nodemanager.git] / coresched.py
1 # $Id$
2 # $URL$
3
4 """Whole core scheduling
5
6 """
7
8 import logger
9 import os
10
11 class CoreSched:
12     """ Whole-core scheduler
13
14         The main entrypoint is adjustCores(self, slivers) which takes a
15         dictionary of sliver records. The cpu_cores field is pulled from the
16         effective rspec (rec["_rspec"]) for each sliver.
17
18         If cpu_cores > 0 for a sliver, then that sliver will reserve one or
19         more of the cpu_cores on the machine.
20
21         One core is always left unreserved for system slices.
22     """
23
24     def __init__(self):
25         self.cpus = []
26
27     def get_cpus(self):
28         """ return a list of available cpu identifiers: [0,1,2,3...]
29         """
30
31         # the cpus never change, so if it's already been computed then don't
32         # worry about it.
33         if self.cpus!=[]:
34             return self.cpus
35
36         cpuset_cpus = open("/dev/cgroup/cpuset.cpus").readline().strip()
37
38         # cpuset.cpus could be something as arbitrary as:
39         #    0,1,2-3,4,5-6
40         # deal with commas and ranges
41         for part in cpuset_cpus.split(","):
42             cpuRange = part.split("-")
43             if len(cpuRange) == 1:
44                 cpuRange = (cpuRange[0], cpuRange[0])
45             for i in range(int(cpuRange[0]), int(cpuRange[1])+1):
46                 if not i in self.cpus:
47                     self.cpus.append(i)
48
49             return self.cpus
50
51     def get_cgroups (self):
52         """ return a list of cgroups
53             this might change as vservers are instantiated, so always compute
54             it dynamically.
55         """
56         cgroups = []
57         filenames = os.listdir("/dev/cgroup")
58         for filename in filenames:
59             if os.path.isdir(os.path.join("/dev/cgroup", filename)):
60                 cgroups.append(filename)
61         return cgroups
62
63     def decodeCoreSpec (self, cores):
64         """ Decode the value of the core attribute. It's a number, followed by
65             an optional letter "b" to indicate besteffort cores should also
66             be supplied.
67         """
68         bestEffort = False
69
70         if cores.endswith("b"):
71            cores = cores[:-1]
72            bestEffort = True
73
74         try:
75             cores = int(cores)
76         except ValueError:
77             cores = 0
78
79         return (cores, bestEffort)
80
81     def adjustCores (self, slivers):
82         """ slivers is a dict of {sliver_name: rec}
83                 rec is a dict of attributes
84                     rec['_rspec'] is the effective rspec
85         """
86
87         logger.log("CoreSched: adjusting cores")
88
89         cpus = self.get_cpus()[:]
90
91         reservations = {}
92
93         # allocate the cores to the slivers that have them reserved
94         for name, rec in slivers.iteritems():
95             rspec = rec["_rspec"]
96             cores = rspec.get("cpu_cores", 0)
97             (cores, bestEffort) = self.decodeCoreSpec(cores)
98
99             while (cores>0):
100                 # one cpu core reserved for best effort and system slices
101                 if len(cpus)<=1:
102                     logger.log("CoreSched: ran out of cpu cores while scheduling: " + name)
103                 else:
104                     cpu = cpus.pop()
105                     logger.log("CoreSched: allocating cpu " + str(cpu) + " to slice " + name)
106                     reservations[name] = reservations.get(name,[]) + [cpu]
107
108                 cores = cores-1
109
110         # the leftovers go to everyone else
111         logger.log("CoreSched: allocating cpus " + str(cpus) + " to _default")
112         reservations["_default"] = cpus[:]
113
114         # now check and see if any of our reservations had the besteffort flag
115         # set
116         for name, rec in slivers.iteritems():
117             rspec = rec["_rspec"]
118             cores = rspec.get("cpu_cores", 0)
119             (cores, bestEffort) = self.decodeCoreSpec(cores)
120
121             if not (reservations.get(name,[])):
122                 # if there is no reservation for this slice, then it's already
123                 # besteffort by default.
124                 continue
125
126             if bestEffort:
127                 reservations[name] = reservations[name] + reservations["_default"]
128                 logger.log("CoreSched: adding besteffort cores to " + name + ". new cores = " + str(reservations[name]))
129
130         self.reserveCores(reservations)
131
132     def reserveCores (self, reservations):
133         """ give a set of reservations (dictionary of slicename:cpuid_list),
134             write those reservations to the appropriate cgroup files.
135
136             reservations["_default"] is assumed to be the default reservation
137             for slices that do not reserve cores. It's essentially the leftover
138             cpu cores.
139         """
140
141         default = reservations["_default"]
142
143         # set the default vserver cpuset. this will deal with any vservers
144         # that might be created before the nodemanager has had a chance to
145         # update the cpusets.
146         self.reserveDefault(default)
147
148         for cgroup in self.get_cgroups():
149             cpus = reservations.get(cgroup, default)
150
151             logger.log("CoreSched: reserving " + cgroup + " " + str(cpus))
152
153             file("/dev/cgroup/" + cgroup + "/cpuset.cpus", "w").write( self.listToRange(cpus) + "\n" )
154
155     def reserveDefault (self, cpus):
156         if not os.path.exists("/etc/vservers/.defaults/cgroup"):
157             os.makedirs("/etc/vservers/.defaults/cgroup")
158
159         file("/etc/vservers/.defaults/cgroup/cpuset.cpus", "w").write( self.listToRange(cpus) + "\n" )
160
161     def listToRange (self, list):
162         """ take a list of items [1,2,3,5,...] and return it as a range: "1-3,5"
163             for now, just comma-separate
164         """
165         return ",".join( [str(i) for i in list] )
166
167 # a little self-test
168 if __name__=="__main__":
169     x = CoreSched()
170
171     print "cpus:", x.listToRange(x.get_cpus())
172     print "cgroups:", ",".join(x.get_cgroups())
173
174     # a quick self-test for ScottLab slices sl_test1 and sl_test2
175     #    sl_test1 = 1 core
176     #    sl_test2 = 1 core
177
178     rspec_sl_test1 = {"cpu_cores": 1}
179     rec_sl_test1 = {"_rspec": rspec_sl_test1}
180
181     rspec_sl_test2 = {"cpu_cores": 1}
182     rec_sl_test2 = {"_rspec": rspec_sl_test2}
183
184     slivers = {"sl_test1": rec_sl_test1, "sl_test2": rec_sl_test2}
185
186     x.adjustCores(slivers)
187