3 """Functionality common to all account classes.
5 Each subclass of Account must provide five methods:
6 (*) create() and destroy(), which are static;
7 (*) configure(), start(), and stop(), which are not.
9 configure(), which takes a record as its only argument, does
10 things like set up ssh keys. In addition, an Account subclass must
11 provide static member variables SHELL, which contains the unique shell
12 that it uses; and TYPE, a string that is used by the account creation
13 code. For no particular reason, TYPE is divided hierarchically by
14 periods; at the moment the only convention is that all sliver accounts
15 have type that begins with sliver.
17 There are any number of race conditions that may result from the fact
18 that account names are not unique over time. Moreover, it's a bad
19 idea to perform lengthy operations while holding the database lock.
20 In order to deal with both of these problems, we use a worker thread
21 for each account name that ever exists. On 32-bit systems with large
22 numbers of accounts, this may cause the NM process to run out of
23 *virtual* memory! This problem may be remedied by decreasing the
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
46 to the dictionaries used to look up account classes by shell and
48 shell_acct_class[acct_class.SHELL] = acct_class
49 type_acct_class[acct_class.TYPE] = acct_class
52 # private account name -> worker object association and associated lock
53 name_worker_lock = threading.Lock()
57 return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
60 """Return the names of all accounts on the system with recognized shells."""
61 return [pw_ent[0] for pw_ent in allpwents()]
64 """Return the worker object for a particular username. If no such object exists, create it first."""
65 name_worker_lock.acquire()
67 if name not in name_worker: name_worker[name] = Worker(name)
68 return name_worker[name]
69 finally: name_worker_lock.release()
73 def __init__(self, rec):
74 logger.verbose('accounts: Initing account %s'%rec['name'])
75 self.name = rec['name']
80 def create(name, vref = None): abstract
83 def destroy(name): abstract
85 def configure(self, rec):
86 """Write <rec['keys']> to my authorized_keys file."""
87 logger.verbose('accounts: configuring %s'%self.name)
88 new_keys = rec['keys']
89 if new_keys != self.keys:
90 # get the unix account info
91 gid = grp.getgrnam("slices")[2]
92 pw_info = pwd.getpwnam(self.name)
96 # write out authorized_keys file and conditionally create
97 # the .ssh subdir if need be.
98 dot_ssh = os.path.join(pw_dir,'.ssh')
99 if not os.path.isdir(dot_ssh):
100 if not os.path.isdir(pw_dir):
101 logger.verbose('accounts: WARNING: homedir %s does not exist for %s!'%(pw_dir,self.name))
103 os.chown(pw_dir, uid, gid)
106 auth_keys = os.path.join(dot_ssh,'authorized_keys')
107 tools.write_file(auth_keys, lambda f: f.write(new_keys))
109 # set access permissions and ownership properly
110 os.chmod(dot_ssh, 0700)
111 os.chown(dot_ssh, uid, gid)
112 os.chmod(auth_keys, 0600)
113 os.chown(auth_keys, uid, gid)
115 # set self.keys to new_keys only when all of the above ops succeed
118 logger.log('accounts: %s: installed ssh keys' % self.name)
120 def start(self, delay=0): pass
122 def is_running(self): pass
126 def __init__(self, name):
127 self.name = name # username
128 self._acct = None # the account object currently associated with this worker
130 def ensure_created(self, rec):
131 """Check account type is still valid. If not, recreate sliver.
132 If still valid, check if running and configure/start if not."""
133 logger.log_data_in_file(rec,"/var/lib/nodemanager/%s.rec.txt"%rec['name'],
134 'raw rec captured in ensure_created',logger.LOG_VERBOSE)
135 curr_class = self._get_class()
136 next_class = type_acct_class[rec['type']]
137 if next_class != curr_class:
138 self._destroy(curr_class)
140 try: next_class.create(self.name, rec)
141 finally: create_sem.release()
142 if not isinstance(self._acct, next_class): self._acct = next_class(rec)
143 logger.verbose("accounts.ensure_created: %s, running=%r"%(self.name,self.is_running()))
145 # reservation_alive is set on reervable nodes, and its value is a boolean
146 if 'reservation_alive' in rec:
148 if rec['reservation_alive']:
149 # this sliver has the lease, it is safe to start it
150 if not self.is_running(): self.start(rec)
151 else: self.configure(rec)
153 # not having the lease, do not start it
155 # usual nodes - preserve old code
156 # xxx it's not clear what to do when a sliver changes type/class
157 # in a reservable node
159 if not self.is_running() or next_class != curr_class:
161 else: self.configure(rec)
163 def ensure_destroyed(self): self._destroy(self._get_class())
165 def start(self, rec, d = 0):
166 self._acct.configure(rec)
167 self._acct.start(delay=d)
169 def configure(self, rec):
170 self._acct.configure(rec)
172 def stop(self): self._acct.stop()
174 def is_running(self):
175 if (self._acct != None) and self._acct.is_running():
179 logger.verbose("accounts: Worker(%s): is not running" % self.name)
182 def _destroy(self, curr_class):
185 destroy_sem.acquire()
186 try: curr_class.destroy(self.name)
187 finally: destroy_sem.release()
189 def _get_class(self):
190 try: shell = pwd.getpwnam(self.name)[6]
191 except KeyError: return None
192 return shell_acct_class[shell]