Expand the glob.
[nodemanager.git] / accounts.py
1 """Functionality common to all account classes.
2
3 Each subclass of Account must provide five methods: create() and
4 destroy(), which are static; configure(), start(), and stop(), which
5 are not.  configure(), which takes a record as its only argument, does
6 things like set up ssh keys.  In addition, an Account subclass must
7 provide static member variables SHELL, which contains the unique shell
8 that it uses; and TYPE, a string that is used by the account creation
9 code.  For no particular reason, TYPE is divided hierarchically by
10 periods; at the moment the only convention is that all sliver accounts
11 have type that begins with sliver.
12
13 There are any number of race conditions that may result from the fact
14 that account names are not unique over time.  Moreover, it's a bad
15 idea to perform lengthy operations while holding the database lock.
16 In order to deal with both of these problems, we use a worker thread
17 for each account name that ever exists.  On 32-bit systems with large
18 numbers of accounts, this may cause the NM process to run out of
19 *virtual* memory!  This problem may be remedied by decreasing the
20 maximum stack size.
21 """
22
23 import Queue
24 import os
25 import pwd
26 from grp import getgrnam
27 import threading
28
29 import logger
30 import tools
31
32
33 # When this variable is true, start after any ensure_created
34 Startingup = False
35 # shell path -> account class association
36 shell_acct_class = {}
37 # account type -> account class association
38 type_acct_class = {}
39
40 def register_class(acct_class):
41     """Call once for each account class.  This method adds the class to the dictionaries used to look up account classes by shell and type."""
42     shell_acct_class[acct_class.SHELL] = acct_class
43     type_acct_class[acct_class.TYPE] = acct_class
44
45
46 # private account name -> worker object association and associated lock
47 name_worker_lock = threading.Lock()
48 name_worker = {}
49
50 def allpwents():
51     return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
52
53 def all():
54     """Return the names of all accounts on the system with recognized shells."""
55     return [pw_ent[0] for pw_ent in allpwents()]
56
57 def get(name):
58     """Return the worker object for a particular username.  If no such object exists, create it first."""
59     name_worker_lock.acquire()
60     try:
61         if name not in name_worker: name_worker[name] = Worker(name)
62         return name_worker[name]
63     finally: name_worker_lock.release()
64
65
66 class Account:
67     def __init__(self, rec):
68         logger.verbose('Initing account %s'%rec['name'])
69         self.name = rec['name']
70         self.keys = ''
71         self.initscriptchanged = False
72         self.configure(rec)
73
74     @staticmethod
75     def create(name, vref = None): abstract
76     @staticmethod
77     def destroy(name): abstract
78
79     def configure(self, rec):
80         """Write <rec['keys']> to my authorized_keys file."""
81         logger.verbose('%s: in accounts:configure'%self.name)
82         new_keys = rec['keys']
83         if new_keys != self.keys:
84             self.keys = new_keys
85             dot_ssh = '/home/%s/.ssh' % self.name
86             if not os.access(dot_ssh, os.F_OK): os.mkdir(dot_ssh)
87             os.chmod(dot_ssh, 0700)
88             tools.write_file(dot_ssh + '/authorized_keys', lambda f: f.write(new_keys))
89             logger.log('%s: installing ssh keys' % self.name)
90             user = pwd.getpwnam(self.name)[2]
91             group = getgrnam("slices")[2]
92             os.chown(dot_ssh, user, group)
93             os.chown(dot_ssh + '/authorized_keys', user, group)
94
95     def start(self, delay=0): pass
96     def stop(self): pass
97     def is_running(self): pass
98
99 class Worker:
100     # these semaphores are acquired before creating/destroying an account
101     _create_sem = threading.Semaphore(1)
102     _destroy_sem = threading.Semaphore(1)
103
104     def __init__(self, name):
105         self.name = name  # username
106         self._acct = None  # the account object currently associated with this worker
107
108     def ensure_created(self, rec, startingup = Startingup):
109         curr_class = self._get_class()
110         next_class = type_acct_class[rec['type']]
111         if next_class != curr_class:
112             self._destroy(curr_class)
113             self._create_sem.acquire()
114             try: next_class.create(self.name, rec['vref'])
115             finally: self._create_sem.release()
116         if not isinstance(self._acct, next_class): self._acct = next_class(rec)
117         else: self._acct.configure(rec)
118         if startingup or \
119           not self.is_running() or \
120           next_class != curr_class or \
121           self._acct.initscriptchanged:
122             self._acct.start()
123
124     def ensure_destroyed(self): self._destroy(self._get_class())
125
126     def start(self, d): self._acct.start(delay=d)
127
128     def stop(self): self._acct.stop()
129
130     def is_running(self): 
131         if self._acct.is_running():
132             status = True
133         else:
134             status = False
135             logger.verbose("Worker(%s): is not running" % self.name)
136         return status
137
138     def _destroy(self, curr_class):
139         self._acct = None
140         if curr_class:
141             self._destroy_sem.acquire()
142             try: curr_class.destroy(self.name)
143             finally: self._destroy_sem.release()
144
145     def _get_class(self):
146         try: shell = pwd.getpwnam(self.name)[6]
147         except KeyError: return None
148         return shell_acct_class[shell]