1 """The database houses information on slivers. This information
2 reaches the sliver manager in two different ways: one, through the
3 GetSlivers() call made periodically; two, by users delivering tickets.
4 The sync() method of the Database class turns this data into reality.
6 The database format is a dictionary that maps account names to records
7 (also known as dictionaries). Inside each record, data supplied or
8 computed locally is stored under keys that begin with an underscore,
9 and data from PLC is stored under keys that don't.
11 In order to maintain service when the node reboots during a network
12 partition, the database is constantly being dumped to disk.
24 # We enforce minimum allocations to keep the clueless from hosing their slivers.
25 # Disallow disk loans because there's currently no way to punish slivers over quota.
26 MINIMUM_ALLOCATION = {'cpu_pct': 0, 'cpu_share': 1, 'net_min_rate': 0, 'net_max_rate': 8, 'net_i2_min_rate': 0, 'net_i2_max_rate': 8, 'net_share': 1}
27 LOANABLE_RESOURCES = MINIMUM_ALLOCATION.keys()
29 DB_FILE = '/root/sliver_mgr_db.pickle'
32 # database object and associated lock
33 db_lock = threading.RLock()
36 # these are used in tandem to request a database dump from the dumper daemon
37 db_cond = threading.Condition(db_lock)
38 dump_requested = False
40 # decorator that acquires and releases the database lock before and after the decorated operation
41 # XXX - replace with "with" statements once we switch to 2.5
43 def sync_fn(*args, **kw_args):
45 try: return fn(*args, **kw_args)
46 finally: db_lock.release()
47 sync_fn.__doc__ = fn.__doc__
48 sync_fn.__name__ = fn.__name__
54 self._min_timestamp = 0
56 def _compute_effective_rspecs(self):
57 """Calculate the effects of loans and store the result in field _rspec. At the moment, we allow slivers to loan only those resources that they have received directly from PLC. In order to do the accounting, we store three different rspecs: field 'rspec', which is the resources given by PLC; field '_rspec', which is the actual amount of resources the sliver has after all loans; and variable resid_rspec, which is the amount of resources the sliver has after giving out loans but not receiving any."""
59 for name, rec in self.iteritems():
61 rec['_rspec'] = rec['rspec'].copy()
63 for rec in slivers.itervalues():
64 eff_rspec = rec['_rspec']
65 resid_rspec = rec['rspec'].copy()
66 for target, resname, amt in rec.get('_loans', []):
67 if target in slivers and amt <= resid_rspec[resname] - MINIMUM_ALLOCATION[resname]:
68 eff_rspec[resname] -= amt
69 resid_rspec[resname] -= amt
70 slivers[target]['_rspec'][resname] += amt
72 def deliver_record(self, rec):
73 """A record is simply a dictionary with 'name' and 'timestamp' keys. We keep some persistent private data in the records under keys that start with '_'; thus record updates should not displace such keys."""
74 if rec['timestamp'] < self._min_timestamp: return
76 old_rec = self.get(name)
77 if old_rec == None: self[name] = rec
78 elif rec['timestamp'] > old_rec['timestamp']:
79 for key in old_rec.keys():
80 if not key.startswith('_'): del old_rec[key]
83 def set_min_timestamp(self, ts):
84 """The ._min_timestamp member is the timestamp on the last comprehensive update. We use it to determine if a record is stale. This method should be called whenever new GetSlivers() data comes in."""
85 self._min_timestamp = ts
86 for name, rec in self.items():
87 if rec['timestamp'] < ts: del self[name]
90 """Synchronize reality with the database contents. This method does a lot of things, and it's currently called after every single batch of database changes (a GetSlivers(), a loan, a record). It may be necessary in the future to do something smarter."""
92 # delete expired records
94 for name, rec in self.items():
95 if rec.get('expires', now) < now: del self[name]
97 self._compute_effective_rspecs()
99 # create and destroy accounts as needed
100 logger.verbose("database:sync : fetching accounts")
101 existing_acct_names = accounts.all()
102 for name in existing_acct_names:
103 logger.verbose("database:sync : loop on %s"%name)
104 if name not in self: accounts.get(name).ensure_destroyed()
105 for name, rec in self.iteritems():
106 slivr = accounts.get(name)
107 logger.verbose("database:sync : %s is %s" %(name,slivr._get_class()))
108 # Make sure we refresh accounts that are running
109 if rec['instantiation'] == 'plc-instantiated': slivr.ensure_created(rec)
110 elif rec['instantiation'] == 'nm-controller': slivr.ensure_created(rec)
111 # Back door to ensure PLC overrides Ticket in delegation.
112 elif rec['instantiation'] == 'delegated' and slivr._get_class() != None:
113 # if the ticket has been delivered and the nm-contoroller started the slice
114 # update rspecs and keep them up to date.
115 if slivr.is_running(): slivr.ensure_created(rec)
117 # Wake up bwmom to update limits.
119 global dump_requested
120 dump_requested = True
125 """The database dumper daemon. When it starts up, it populates the database with the last dumped database. It proceeds to handle dump requests forever."""
127 global dump_requested
130 while not dump_requested: db_cond.wait()
131 db_pickle = cPickle.dumps(db, cPickle.HIGHEST_PROTOCOL)
132 dump_requested = False
134 try: tools.write_file(DB_FILE, lambda f: f.write(db_pickle))
135 except: logger.log_exc()
139 try: db = cPickle.load(f)
144 tools.as_daemon_thread(run)