1 """Functionality common to all account classes.
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.
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
26 from grp import getgrnam
33 # When this variable is true, start after any ensure_created
35 # shell path -> account class association
37 # account type -> account class association
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
46 # private account name -> worker object association and associated lock
47 name_worker_lock = threading.Lock()
51 return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
54 """Return the names of all accounts on the system with recognized shells."""
55 return [pw_ent[0] for pw_ent in allpwents()]
58 """Return the worker object for a particular username. If no such object exists, create it first."""
59 name_worker_lock.acquire()
61 if name not in name_worker: name_worker[name] = Worker(name)
62 return name_worker[name]
63 finally: name_worker_lock.release()
67 def __init__(self, rec):
68 logger.verbose('Initing account %s'%rec['name'])
69 self.name = rec['name']
71 self.initscriptchanged = False
75 def create(name, vref = None): abstract
77 def destroy(name): abstract
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:
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)
95 def start(self, delay=0): pass
97 def is_running(self): pass
100 # these semaphores are acquired before creating/destroying an account
101 _create_sem = threading.Semaphore(1)
102 _destroy_sem = threading.Semaphore(1)
104 def __init__(self, name):
105 self.name = name # username
106 self._acct = None # the account object currently associated with this worker
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)
119 not self.is_running() or \
120 next_class != curr_class or \
121 self._acct.initscriptchanged:
124 def ensure_destroyed(self): self._destroy(self._get_class())
126 def start(self, d): self._acct.start(delay=d)
128 def stop(self): self._acct.stop()
130 def is_running(self):
131 if self._acct.is_running():
135 logger.verbose("Worker(%s): is not running" % self.name)
138 def _destroy(self, curr_class):
141 self._destroy_sem.acquire()
142 try: curr_class.destroy(self.name)
143 finally: self._destroy_sem.release()
145 def _get_class(self):
146 try: shell = pwd.getpwnam(self.name)[6]
147 except KeyError: return None
148 return shell_acct_class[shell]