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