first working code for reservations
[nodemanager.git] / slivermanager.py
1 # $Id$
2 # $URL$
3
4 """Sliver manager.
5
6 The sliver manager has several functions.  It is responsible for
7 creating, resource limiting, starting, stopping, and destroying
8 slivers.  It provides an API for users to access these functions and
9 also to make inter-sliver resource loans.  The sliver manager is also
10 responsible for handling delegation accounts.
11 """
12
13 import string,re
14 import time
15
16 import logger
17 import api, api_calls
18 import database
19 import accounts
20 import controller
21 import sliver_vs
22
23 try: from bwlimit import bwmin, bwmax
24 except ImportError: bwmin, bwmax = 8, 1000*1000*1000
25
26 priority=10
27
28
29 DEFAULT_ALLOCATION = {
30     'enabled': 1,
31     # CPU parameters
32     'cpu_pct': 0, # percent CPU reserved
33     'cpu_share': 1, # proportional share
34     # bandwidth parameters
35     'net_min_rate': bwmin / 1000, # kbps
36     'net_max_rate': bwmax / 1000, # kbps
37     'net_share': 1, # proportional share
38     # bandwidth parameters over routes exempt from node bandwidth limits
39     'net_i2_min_rate': bwmin / 1000, # kbps
40     'net_i2_max_rate': bwmax / 1000, # kbps
41     'net_i2_share': 1, # proportional share
42     'net_max_kbyte' : 10546875, #Kbyte
43     'net_thresh_kbyte': 9492187, #Kbyte
44     'net_i2_max_kbyte': 31640625,
45     'net_i2_thresh_kbyte': 28476562,
46     # disk space limit
47     'disk_max': 10000000, # bytes
48     # capabilities
49     'capabilities': '',
50     # IP addresses
51     'ip_addresses': '0.0.0.0',
52
53     # NOTE: this table is further populated with resource names and
54     # default amounts via the start() function below.  This probably
55     # should be changeg and these values should be obtained via the
56     # API to myplc.
57     }
58
59 start_requested = False  # set to True in order to request that all slivers be started
60
61 # check leases and adjust the 'reservation_alive' field in slivers
62 # this is not expected to be saved as it will change for the next round
63 def adjustReservedSlivers (data):
64     """
65     On reservable nodes, tweak the 'reservation_alive' field to instruct cyclic loop
66     about what to do with slivers.
67     """
68     # only impacts reservable nodes
69     if 'reservation_policy' not in data: return
70     policy=data['reservation_policy'] 
71     if policy not in ['lease_or_idle', 'lease_or_shared']:
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, make dict
133     if 'initscripts' not in data:
134         logger.log_missing_data("slivermanager.GetSlivers",'initscripts')
135         return
136     initscripts = {}
137     for is_rec in data['initscripts']:
138         logger.verbose("slivermanager: initscript: %s" % is_rec['name'])
139         initscripts[str(is_rec['name'])] = is_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.  first check if exists, if not, leave empty.
168         is_name = attributes.get('initscript')
169         if is_name is not None and is_name in initscripts:
170             rec['initscript'] = initscripts[is_name]
171         else:
172             rec['initscript'] = ''
173
174         # set delegations, if none, set empty
175         rec.setdefault('delegations', attributes.get("delegations", []))
176
177         # extract the implied rspec
178         rspec = {}
179         rec['rspec'] = rspec
180         for resname, default_amount in DEFAULT_ALLOCATION.iteritems():
181             try:
182                 t = type(default_amount)
183                 amt = t.__new__(t, attributes[resname])
184             except (KeyError, ValueError): amt = default_amount
185             rspec[resname] = amt
186
187         # add in sysctl attributes into the rspec
188         for key in attributes.keys():
189             if key.find("sysctl.") == 0:
190                 rspec[key] = attributes[key]
191
192         database.db.deliver_record(rec)
193     if fullupdate: database.db.set_min_timestamp(data['timestamp'])
194     # slivers are created here.
195     database.db.sync()
196
197 def deliver_ticket(data):
198     return GetSlivers(data, fullupdate=False)
199
200 def start(options, config):
201     for resname, default_amount in sliver_vs.DEFAULT_ALLOCATION.iteritems():
202         DEFAULT_ALLOCATION[resname]=default_amount
203
204     accounts.register_class(sliver_vs.Sliver_VS)
205     accounts.register_class(controller.Controller)
206     database.start()
207     api_calls.deliver_ticket = deliver_ticket
208     api.start()