reguire gnupg1 on f>=31; sense the system to use gpg1 when installed
[nodemanager.git] / slivermanager.py
1 """Sliver manager.
2
3 The sliver manager has several functions.  It is responsible for
4 creating, resource limiting, starting, stopping, and destroying
5 slivers.  It provides an API for users to access these functions and
6 also to make inter-sliver resource loans.  The sliver manager is also
7 responsible for handling delegation accounts.
8 """
9
10 import string
11 import re
12 import time
13
14 import logger
15 import api, api_calls
16 import database
17 import account
18 import controller
19
20 try:
21     import sliver_lxc
22     implementation='lxc'
23     sliver_default_type='sliver.LXC'
24     sliver_class_to_register = sliver_lxc.Sliver_LXC
25     sliver_password_shell = sliver_lxc.Sliver_LXC.SHELL
26 except:
27     try:
28         import sliver_vs
29         implementation='vs'
30         sliver_default_type='sliver.VServer'
31         sliver_class_to_register = sliver_vs.Sliver_VS
32         sliver_password_shell = sliver_vs.Sliver_VS.SHELL
33     except:
34         logger.log("Could not import either sliver_lxc or sliver_vs - bailing out")
35         exit(1)
36
37 # just being safe
38 try:
39     from plnode.bwlimit import bwmin, bwmax
40 except:
41     bwmin, bwmax = 8, 1000*1000*1000
42
43 priority = 10
44
45
46 DEFAULT_ALLOCATION = {
47     'enabled': 1,
48     # CPU parameters
49     'cpu_pct': 0, # percent CPU reserved
50     'cpu_share': 1, # proportional share
51     'cpu_cores': "0b", # reserved cpu cores <num_cores>[b]
52     'cpu_freezable': 0, # freeze processes if cpu_cores is 0
53     # bandwidth parameters
54     'net_min_rate': bwmin / 1000, # kbps
55     'net_max_rate': bwmax / 1000, # kbps
56     'net_share': 1, # proportional share
57     # bandwidth parameters over routes exempt from node bandwidth limits
58     'net_i2_min_rate': bwmin / 1000, # kbps
59     'net_i2_max_rate': bwmax / 1000, # kbps
60     'net_i2_share': 1, # proportional share
61     'net_max_kbyte' : 10546875, #Kbyte
62     'net_thresh_kbyte': 9492187, #Kbyte
63     'net_i2_max_kbyte': 31640625,
64     'net_i2_thresh_kbyte': 28476562,
65     # disk space limit
66     'disk_max': 10000000, # bytes
67     # capabilities
68     'capabilities': '',
69     # IP addresses
70     'ip_addresses': '0.0.0.0',
71
72     # NOTE: this table is further populated with resource names and
73     # default amounts via the start() function below.  This probably
74     # should be changeg and these values should be obtained via the
75     # API to myplc.
76     }
77
78 start_requested = False  # set to True in order to request that all slivers be started
79
80 # check leases and adjust the 'reservation_alive' field in slivers
81 # this is not expected to be saved as it will change for the next round
82 def adjustReservedSlivers (data):
83     """
84     On reservable nodes, tweak the 'reservation_alive' field to instruct cyclic loop
85     about what to do with slivers.
86     """
87     # only impacts reservable nodes
88     if 'reservation_policy' not in data: return
89     policy=data['reservation_policy'] 
90     if policy not in ['lease_or_idle', 'lease_or_shared']:
91         if policy is not None:
92             logger.log ("unexpected reservation_policy %(policy)s"%locals())
93         return
94
95     logger.log("slivermanager.adjustReservedSlivers")
96     now=int(time.time())
97     # scan leases that are expected to show in ascending order
98     active_lease=None
99     for lease in data['leases']:
100         if lease['t_from'] <= now and now <= lease['t_until']:
101             active_lease=lease
102             break
103
104     def is_system_sliver (sliver):
105         for d in sliver['attributes']:
106             if d['tagname']=='system' and d['value']:
107                 return True
108         return False
109
110     # mark slivers as appropriate
111     for sliver in data['slivers']:
112         # system slivers must be kept alive
113         if is_system_sliver(sliver):
114             sliver['reservation_alive']=True
115             continue
116
117         # regular slivers
118         if not active_lease:
119             # with 'idle_or_shared', just let the field out, behave like a shared node
120             # otherwise, mark all slivers as being turned down
121             if policy == 'lease_or_idle':
122                 sliver['reservation_alive']=False
123         else:
124             # there is an active lease, mark it alive and the other not
125             sliver['reservation_alive'] = sliver['name']==active_lease['name']
126
127 @database.synchronized
128 def GetSlivers(data, config = None, plc=None, fullupdate=True):
129     """This function has two purposes.  One, convert GetSlivers() data
130     into a more convenient format.  Two, even if no updates are coming
131     in, use the GetSlivers() heartbeat as a cue to scan for expired
132     slivers."""
133
134     logger.verbose("slivermanager: Entering GetSlivers with fullupdate=%r"%fullupdate)
135     for key in list(data.keys()):
136         logger.verbose('slivermanager: GetSlivers key : ' + key)
137
138     node_id = None
139     try:
140         f = open('/etc/planetlab/node_id')
141         try:
142             node_id = int(f.read())
143         finally:
144             f.close()
145     except:
146         logger.log_exc("slivermanager: GetSlivers failed to read /etc/planetlab/node_id")
147
148     if 'node_id' in data and data['node_id'] != node_id: return
149
150     if 'networks' in data:
151         for network in data['networks']:
152             if network['is_primary'] and network['bwlimit'] is not None:
153                 DEFAULT_ALLOCATION['net_max_rate'] = network['bwlimit'] / 1000
154
155     # Take initscripts (global) returned by API, build a hash scriptname->code
156     iscripts_hash = {}
157     if 'initscripts' not in data:
158         logger.log_missing_data("slivermanager.GetSlivers", 'initscripts')
159         return
160     for initscript_rec in data['initscripts']:
161         logger.verbose("slivermanager: initscript: %s" % initscript_rec['name'])
162         iscripts_hash[str(initscript_rec['name'])] = initscript_rec['script']
163
164     adjustReservedSlivers (data)
165     for sliver in data['slivers']:
166         logger.verbose("slivermanager: %s: slivermanager.GetSlivers in slivers loop"%sliver['name'])
167         rec = sliver.copy()
168         rec.setdefault('timestamp', data['timestamp'])
169
170         # convert attributes field to a proper dict
171         attributes = {}
172         for attr in rec.pop('attributes'): attributes[attr['tagname']] = attr['value']
173         rec.setdefault("attributes", attributes)
174
175         # squash keys
176         keys = rec.pop('keys')
177         rec.setdefault('keys', '\n'.join([key_struct['key'] for key_struct in keys]))
178
179         ## 'Type' isn't returned by GetSlivers() for whatever reason.  We're overloading
180         ## instantiation here, but i suppose its the same thing when you think about it. -FA
181         # Handle nm-controller here
182         if rec['instantiation'].lower() == 'nm-controller':
183             rec.setdefault('type', attributes.get('type', 'controller.Controller'))
184         else:
185             rec.setdefault('type', attributes.get('type', sliver_default_type))
186
187         # set the vserver reference.  If none, set to default.
188         rec.setdefault('vref', attributes.get('vref', 'default'))
189
190         ### set initscripts; set empty rec['initscript'] if not
191         # if tag 'initscript_code' is set, that's what we use
192         iscode = attributes.get('initscript_code', '')
193         if iscode:
194             rec['initscript'] = iscode
195         else:
196             isname = attributes.get('initscript')
197             if isname is not None and isname in iscripts_hash:
198                 rec['initscript'] = iscripts_hash[isname]
199             else:
200                 rec['initscript'] = ''
201
202         # set delegations, if none, set empty
203         rec.setdefault('delegations', attributes.get("delegations", []))
204
205         # extract the implied rspec
206         rspec = {}
207         rec['rspec'] = rspec
208         for resname, default_amount in DEFAULT_ALLOCATION.items():
209             try:
210                 t = type(default_amount)
211                 amount = t.__new__(t, attributes[resname])
212             except (KeyError, ValueError): amount = default_amount
213             rspec[resname] = amount
214
215         # add in sysctl attributes into the rspec
216         for key in list(attributes.keys()):
217             if key.find("sysctl.") == 0:
218                 rspec[key] = attributes[key]
219
220         # also export tags in rspec so they make it to the sliver_vs.start call
221         rspec['tags'] = attributes
222
223         database.db.deliver_record(rec)
224     if fullupdate:
225         database.db.set_min_timestamp(data['timestamp'])
226     # slivers are created here.
227     database.db.sync()
228
229 def deliver_ticket(data):
230     return GetSlivers(data, fullupdate=False)
231
232 def start():
233     # No default allocation values for LXC yet, think if its necessary given
234     # that they are also default allocation values in this module
235     if implementation == 'vs':
236         for resname, default_amount in sliver_vs.DEFAULT_ALLOCATION.items():
237             DEFAULT_ALLOCATION[resname]=default_amount
238
239     account.register_class(sliver_class_to_register)
240     account.register_class(controller.Controller)
241     database.start()
242     api_calls.deliver_ticket = deliver_ticket
243     api.start()
244
245 ### check if a sliver is running
246 ### a first step to a unified code for codemux
247 def is_running (name):
248     if implementation=='vs':
249         import vserver
250         return vserver.VServer(name).is_running()
251     else:
252         import libvirt
253         running = False
254         try:
255             conn = libvirt.open('lxc://')
256             dom  = conn.lookupByName(name)
257             running = dom.info()[0] == libvirt.VIR_DOMAIN_RUNNING
258         finally:
259             conn.close()
260         return running