Merge branch 'devel' of ssh://git.planet-lab.org/git/nodemanager into devel
[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 accounts
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         logger.log ("unexpected reservation_policy %(policy)s"%locals())
72         return
73
74     logger.log("slivermanager.adjustReservedSlivers")
75     now=int(time.time())
76     # scan leases that are expected to show in ascending order
77     active_lease=None
78     for lease in data['leases']:
79         if lease['t_from'] <= now and now <= lease['t_until']:
80             active_lease=lease
81             break
82
83     def is_system_sliver (sliver):
84         for d in sliver['attributes']:
85             if d['tagname']=='system' and d['value']:
86                 return True
87         return False
88
89     # mark slivers as appropriate
90     for sliver in data['slivers']:
91         # system slivers must be kept alive
92         if is_system_sliver(sliver):
93             sliver['reservation_alive']=True
94             continue
95         
96         # regular slivers
97         if not active_lease:
98             # with 'idle_or_shared', just let the field out, behave like a shared node
99             # otherwise, mark all slivers as being turned down
100             if policy == 'lease_or_idle':
101                 sliver['reservation_alive']=False
102         else:
103             # there is an active lease, mark it alive and the other not
104             sliver['reservation_alive'] = sliver['name']==active_lease['name']
105
106 @database.synchronized
107 def GetSlivers(data, config = None, plc=None, fullupdate=True):
108     """This function has two purposes.  One, convert GetSlivers() data
109     into a more convenient format.  Two, even if no updates are coming
110     in, use the GetSlivers() heartbeat as a cue to scan for expired
111     slivers."""
112
113     logger.verbose("slivermanager: Entering GetSlivers with fullupdate=%r"%fullupdate)
114     for key in data.keys():
115         logger.verbose('slivermanager: GetSlivers key : ' + key)
116
117     node_id = None
118     try:
119         f = open('/etc/planetlab/node_id')
120         try: node_id = int(f.read())
121         finally: f.close()
122     except: logger.log_exc("slivermanager: GetSlivers failed to read /etc/planetlab/node_id")
123
124     if data.has_key('node_id') and data['node_id'] != node_id: return
125
126     if data.has_key('networks'):
127         for network in data['networks']:
128             if network['is_primary'] and network['bwlimit'] is not None:
129                 DEFAULT_ALLOCATION['net_max_rate'] = network['bwlimit'] / 1000
130
131     # Take initscripts (global) returned by API, build a hash scriptname->code
132     iscripts_hash = {}
133     if 'initscripts' not in data:
134         logger.log_missing_data("slivermanager.GetSlivers",'initscripts')
135         return
136     for initscript_rec in data['initscripts']:
137         logger.verbose("slivermanager: initscript: %s" % initscript_rec['name'])
138         iscripts_hash[str(initscript_rec['name'])] = initscript_rec['script']
139
140     adjustReservedSlivers (data)
141     for sliver in data['slivers']:
142         logger.verbose("slivermanager: %s: slivermanager.GetSlivers in slivers loop"%sliver['name'])
143         rec = sliver.copy()
144         rec.setdefault('timestamp', data['timestamp'])
145
146         # convert attributes field to a proper dict
147         attributes = {}
148         for attr in rec.pop('attributes'): attributes[attr['tagname']] = attr['value']
149         rec.setdefault("attributes", attributes)
150
151         # squash keys
152         keys = rec.pop('keys')
153         rec.setdefault('keys', '\n'.join([key_struct['key'] for key_struct in keys]))
154
155         ## 'Type' isn't returned by GetSlivers() for whatever reason.  We're overloading
156         ## instantiation here, but i suppose its the same thing when you think about it. -FA
157         # Handle nm-controller here
158         if rec['instantiation'].lower() == 'nm-controller':
159             rec.setdefault('type', attributes.get('type', 'controller.Controller'))
160         else:
161             rec.setdefault('type', attributes.get('type', 'sliver.VServer'))
162
163         # set the vserver reference.  If none, set to default.
164         rec.setdefault('vref', attributes.get('vref', 'default'))
165
166         ### set initscripts; set empty rec['initscript'] if not
167         # if tag 'initscript_code' is set, that's what we use
168         iscode = attributes.get('initscript_code','')
169         if iscode:
170             rec['initscript']=iscode
171         else:
172             isname = attributes.get('initscript')
173             if isname is not None and isname in iscripts_hash:
174                 rec['initscript'] = iscripts_hash[isname]
175             else:
176                 rec['initscript'] = ''
177
178         # set delegations, if none, set empty
179         rec.setdefault('delegations', attributes.get("delegations", []))
180
181         # extract the implied rspec
182         rspec = {}
183         rec['rspec'] = rspec
184         for resname, default_amount in DEFAULT_ALLOCATION.iteritems():
185             try:
186                 t = type(default_amount)
187                 amount = t.__new__(t, attributes[resname])
188             except (KeyError, ValueError): amount = default_amount
189             rspec[resname] = amount
190
191         # add in sysctl attributes into the rspec
192         for key in attributes.keys():
193             if key.find("sysctl.") == 0:
194                 rspec[key] = attributes[key]
195
196         # also export tags in rspec so they make it to the sliver_vs.start call
197         rspec['tags']=attributes
198
199         database.db.deliver_record(rec)
200     if fullupdate: database.db.set_min_timestamp(data['timestamp'])
201     # slivers are created here.
202     database.db.sync()
203
204 def deliver_ticket(data):
205     return GetSlivers(data, fullupdate=False)
206
207 def start():
208     for resname, default_amount in sliver_vs.DEFAULT_ALLOCATION.iteritems():
209         DEFAULT_ALLOCATION[resname]=default_amount
210
211     accounts.register_class(sliver_vs.Sliver_VS)
212     accounts.register_class(controller.Controller)
213     database.start()
214     api_calls.deliver_ticket = deliver_ticket
215     api.start()