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 # shell path -> account class association
38 # account type -> account class association
41 # these semaphores are acquired before creating/destroying an account
42 create_sem = threading.Semaphore(1)
43 destroy_sem = threading.Semaphore(1)
45 def register_class(acct_class):
46 """Call once for each account class. This method adds the class
47 to the dictionaries used to look up account classes by shell and
49 shell_acct_class[acct_class.SHELL] = acct_class
50 type_acct_class[acct_class.TYPE] = acct_class
53 # private account name -> worker object association and associated lock
54 name_worker_lock = threading.Lock()
58 return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
61 """Return the names of all accounts on the system with recognized shells."""
62 return [pw_ent[0] for pw_ent in allpwents()]
65 """Return the worker object for a particular username. If no such object exists, create it first."""
66 name_worker_lock.acquire()
68 if name not in name_worker: name_worker[name] = Worker(name)
69 return name_worker[name]
70 finally: name_worker_lock.release()
74 def __init__(self, rec):
75 logger.verbose('accounts: Initing account %s'%rec['name'])
76 self.name = rec['name']
81 def create(name, vref = None): abstract
84 def destroy(name): abstract
86 def configure(self, rec):
87 """Write <rec['keys']> to my authorized_keys file."""
88 logger.verbose('accounts: configuring %s'%self.name)
89 new_keys = rec['keys']
90 if new_keys != self.keys:
91 # get the unix account info
92 gid = grp.getgrnam("slices")[2]
93 pw_info = pwd.getpwnam(self.name)
97 # write out authorized_keys file and conditionally create
98 # the .ssh subdir if need be.
99 dot_ssh = os.path.join(pw_dir,'.ssh')
100 if not os.path.isdir(dot_ssh):
101 if not os.path.isdir(pw_dir):
102 logger.verbose('accounts: WARNING: homedir %s does not exist for %s!'%(pw_dir,self.name))
104 os.chown(pw_dir, uid, gid)
107 auth_keys = os.path.join(dot_ssh,'authorized_keys')
108 tools.write_file(auth_keys, lambda f: f.write(new_keys))
110 # set access permissions and ownership properly
111 os.chmod(dot_ssh, 0700)
112 os.chown(dot_ssh, uid, gid)
113 os.chmod(auth_keys, 0600)
114 os.chown(auth_keys, uid, gid)
116 # set self.keys to new_keys only when all of the above ops succeed
119 logger.log('accounts: %s: installed ssh keys' % self.name)
121 def start(self, delay=0): pass
123 def is_running(self): pass
127 def __init__(self, name):
128 self.name = name # username
129 self._acct = None # the account object currently associated with this worker
131 def ensure_created(self, rec):
132 """Check account type is still valid. If not, recreate sliver.
133 If still valid, check if running and configure/start if not."""
134 logger.log_data_in_file(rec,"/var/lib/nodemanager/%s.rec.txt"%rec['name'],
135 'raw rec captured in ensure_created',logger.LOG_VERBOSE)
136 curr_class = self._get_class()
137 next_class = type_acct_class[rec['type']]
138 if next_class != curr_class:
139 self._destroy(curr_class)
141 try: next_class.create(self.name, rec['vref'])
142 finally: create_sem.release()
143 if not isinstance(self._acct, next_class): self._acct = next_class(rec)
144 logger.verbose("accounts.ensure_created: %s, running=%r"%(self.name,self.is_running()))
146 # reservation_alive is set on reervable nodes, and its value is a boolean
147 if 'reservation_alive' in rec:
149 if rec['reservation_alive']:
150 # this sliver has the lease, it is safe to start it
151 if not self.is_running(): self.start(rec)
152 else: self.configure(rec)
154 # not having the lease, do not start it
156 # usual nodes - preserve old code
157 # xxx it's not clear what to do when a sliver changes type/class
158 # in a reservable node
160 if not self.is_running() or next_class != curr_class:
162 else: self.configure(rec)
164 def ensure_destroyed(self): self._destroy(self._get_class())
166 def start(self, rec, d = 0):
167 self._acct.configure(rec)
168 self._acct.start(delay=d)
170 def configure(self, rec):
171 self._acct.configure(rec)
173 def stop(self): self._acct.stop()
175 def is_running(self):
176 if (self._acct != None) and self._acct.is_running():
180 logger.verbose("accounts: Worker(%s): is not running" % self.name)
183 def _destroy(self, curr_class):
186 destroy_sem.acquire()
187 try: curr_class.destroy(self.name)
188 finally: destroy_sem.release()
190 def _get_class(self):
191 try: shell = pwd.getpwnam(self.name)[6]
192 except KeyError: return None
193 return shell_acct_class[shell]