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
33 # When this variable is true, start after any ensure_created
35 # shell path -> account class association
37 # account type -> account class association
40 # these semaphores are acquired before creating/destroying an account
41 create_sem = threading.Semaphore(1)
42 destroy_sem = threading.Semaphore(1)
44 def register_class(acct_class):
45 """Call once for each account class. This method adds the class to the dictionaries used to look up account classes by shell and type."""
46 shell_acct_class[acct_class.SHELL] = acct_class
47 type_acct_class[acct_class.TYPE] = acct_class
50 # private account name -> worker object association and associated lock
51 name_worker_lock = threading.Lock()
55 return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
58 """Return the names of all accounts on the system with recognized shells."""
59 return [pw_ent[0] for pw_ent in allpwents()]
62 """Return the worker object for a particular username. If no such object exists, create it first."""
63 name_worker_lock.acquire()
65 if name not in name_worker: name_worker[name] = Worker(name)
66 return name_worker[name]
67 finally: name_worker_lock.release()
71 def __init__(self, rec):
72 logger.verbose('Initing account %s'%rec['name'])
73 self.name = rec['name']
75 self.initscriptchanged = False
79 def create(name, vref = None): abstract
81 def destroy(name): abstract
83 def configure(self, rec):
84 """Write <rec['keys']> to my authorized_keys file."""
85 logger.verbose('%s: in accounts:configure'%self.name)
86 new_keys = rec['keys']
87 if new_keys != self.keys:
88 # get the unix account info
89 gid = grp.getgrnam("slices")[2]
90 pw_info = pwd.getpwnam(self.name)
94 # write out authorized_keys file and conditionally create
95 # the .ssh subdir if need be.
96 dot_ssh = os.path.join(pw_dir,'.ssh')
97 if not os.path.isdir(dot_ssh):
98 if not os.path.isdir(pw_dir):
99 logger.verbose('WARNING: homedir %s does not exist for %s!'%(pw_dir,self.name))
101 os.chown(pw_dir, uid, gid)
104 auth_keys = os.path.join(dot_ssh,'authorized_keys')
105 tools.write_file(auth_keys, lambda f: f.write(new_keys))
107 # set access permissions and ownership properly
108 os.chmod(dot_ssh, 0700)
109 os.chown(dot_ssh, uid, gid)
110 os.chmod(auth_keys, 0600)
111 os.chown(auth_keys, uid, gid)
113 # set self.keys to new_keys only when all of the above ops succeed
116 logger.log('%s: installed ssh keys' % self.name)
118 def start(self, delay=0): pass
120 def is_running(self): pass
123 def __init__(self, name):
124 self.name = name # username
125 self._acct = None # the account object currently associated with this worker
127 def ensure_created(self, rec, startingup = Startingup):
128 """Check account type is still valid. If not, recreate sliver. If still valid,
129 check if running and configure/start if not."""
130 curr_class = self._get_class()
131 next_class = type_acct_class[rec['type']]
132 if next_class != curr_class:
133 self._destroy(curr_class)
135 try: next_class.create(self.name, rec['vref'])
136 finally: create_sem.release()
137 if not isinstance(self._acct, next_class): self._acct = next_class(rec)
139 not self.is_running() or \
140 next_class != curr_class or \
141 self._acct.initscriptchanged:
143 else: self._acct.configure(rec)
145 def ensure_destroyed(self): self._destroy(self._get_class())
147 def start(self, rec, d = 0):
148 self._acct.configure(rec)
149 self._acct.start(delay=d)
151 def stop(self): self._acct.stop()
153 def is_running(self):
154 if (self._acct != None) and self._acct.is_running():
158 logger.verbose("Worker(%s): is not running" % self.name)
161 def _destroy(self, curr_class):
164 destroy_sem.acquire()
165 try: curr_class.destroy(self.name)
166 finally: destroy_sem.release()
168 def _get_class(self):
169 try: shell = pwd.getpwnam(self.name)[6]
170 except KeyError: return None
171 return shell_acct_class[shell]