4 """Functionality common to all account classes.
6 Each subclass of Account must provide five methods:
7 (*) create() and destroy(), which are static;
8 (*) configure(), start(), and stop(), which are not.
10 configure(), which takes a record as its only argument, does
11 things like set up ssh keys. In addition, an Account subclass must
12 provide static member variables SHELL, which contains the unique shell
13 that it uses; and TYPE, a string that is used by the account creation
14 code. For no particular reason, TYPE is divided hierarchically by
15 periods; at the moment the only convention is that all sliver accounts
16 have type that begins with sliver.
18 There are any number of race conditions that may result from the fact
19 that account names are not unique over time. Moreover, it's a bad
20 idea to perform lengthy operations while holding the database lock.
21 In order to deal with both of these problems, we use a worker thread
22 for each account name that ever exists. On 32-bit systems with large
23 numbers of accounts, this may cause the NM process to run out of
24 *virtual* memory! This problem may be remedied by decreasing the
36 # When this variable is true, start after any ensure_created
38 # shell path -> account class association
40 # account type -> account class association
43 # these semaphores are acquired before creating/destroying an account
44 create_sem = threading.Semaphore(1)
45 destroy_sem = threading.Semaphore(1)
47 def register_class(acct_class):
48 """Call once for each account class. This method adds the class
49 to the dictionaries used to look up account classes by shell and
51 shell_acct_class[acct_class.SHELL] = acct_class
52 type_acct_class[acct_class.TYPE] = acct_class
55 # private account name -> worker object association and associated lock
56 name_worker_lock = threading.Lock()
60 return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
63 """Return the names of all accounts on the system with recognized shells."""
64 return [pw_ent[0] for pw_ent in allpwents()]
67 """Return the worker object for a particular username. If no such object exists, create it first."""
68 name_worker_lock.acquire()
70 if name not in name_worker: name_worker[name] = Worker(name)
71 return name_worker[name]
72 finally: name_worker_lock.release()
76 def __init__(self, rec):
77 logger.verbose('accounts: Initing account %s'%rec['name'])
78 self.name = rec['name']
80 self.initscriptchanged = False
84 def create(name, vref = None): abstract
87 def destroy(name): abstract
89 def configure(self, rec):
90 """Write <rec['keys']> to my authorized_keys file."""
91 logger.verbose('accounts: configuring %s'%self.name)
92 new_keys = rec['keys']
93 if new_keys != self.keys:
94 # get the unix account info
95 gid = grp.getgrnam("slices")[2]
96 pw_info = pwd.getpwnam(self.name)
100 # write out authorized_keys file and conditionally create
101 # the .ssh subdir if need be.
102 dot_ssh = os.path.join(pw_dir,'.ssh')
103 if not os.path.isdir(dot_ssh):
104 if not os.path.isdir(pw_dir):
105 logger.verbose('accounts: WARNING: homedir %s does not exist for %s!'%(pw_dir,self.name))
107 os.chown(pw_dir, uid, gid)
110 auth_keys = os.path.join(dot_ssh,'authorized_keys')
111 tools.write_file(auth_keys, lambda f: f.write(new_keys))
113 # set access permissions and ownership properly
114 os.chmod(dot_ssh, 0700)
115 os.chown(dot_ssh, uid, gid)
116 os.chmod(auth_keys, 0600)
117 os.chown(auth_keys, uid, gid)
119 # set self.keys to new_keys only when all of the above ops succeed
122 logger.log('accounts: %s: installed ssh keys' % self.name)
124 def start(self, delay=0): pass
126 def is_running(self): pass
130 def __init__(self, name):
131 self.name = name # username
132 self._acct = None # the account object currently associated with this worker
134 def ensure_created(self, rec, startingup = Startingup):
135 """Check account type is still valid. If not, recreate sliver.
136 If still valid, check if running and configure/start if not."""
137 logger.log_data_in_file(rec,"/var/lib/nodemanager/%s.rec.txt"%rec['name'],
138 'raw rec captured in ensure_created',logger.LOG_VERBOSE)
139 curr_class = self._get_class()
140 next_class = type_acct_class[rec['type']]
141 if next_class != curr_class:
142 self._destroy(curr_class)
144 try: next_class.create(self.name, rec['vref'])
145 finally: create_sem.release()
146 if not isinstance(self._acct, next_class): self._acct = next_class(rec)
148 not self.is_running() or \
149 next_class != curr_class or \
150 self._acct.initscriptchanged:
152 else: self._acct.configure(rec)
154 def ensure_destroyed(self): self._destroy(self._get_class())
156 def start(self, rec, d = 0):
157 self._acct.configure(rec)
158 self._acct.start(delay=d)
160 def stop(self): self._acct.stop()
162 def is_running(self):
163 if (self._acct != None) and self._acct.is_running():
167 logger.verbose("accounts: Worker(%s): is not running" % self.name)
170 def _destroy(self, curr_class):
173 destroy_sem.acquire()
174 try: curr_class.destroy(self.name)
175 finally: destroy_sem.release()
177 def _get_class(self):
178 try: shell = pwd.getpwnam(self.name)[6]
179 except KeyError: return None
180 return shell_acct_class[shell]