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
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()
73 # xxx strictly speaking this class should not use self.name that in fact
74 # is accidentally inited by the subclasses constructor...
76 def __init__(self, name):
79 logger.verbose('account: Initing account %s'%name)
82 def create(name, vref = None): abstract
85 def destroy(name): abstract
87 def configure(self, rec):
88 """Write <rec['keys']> to my authorized_keys file."""
89 logger.verbose('account: configuring %s'%self.name)
90 new_keys = rec['keys']
91 if new_keys != self.keys:
92 # get the unix account info
93 gid = grp.getgrnam("slices")[2]
94 pw_info = pwd.getpwnam(self.name)
98 # write out authorized_keys file and conditionally create
99 # the .ssh subdir if need be.
100 dot_ssh = os.path.join(pw_dir,'.ssh')
101 if not os.path.isdir(dot_ssh):
102 if not os.path.isdir(pw_dir):
103 logger.verbose('account: WARNING: homedir %s does not exist for %s!'%(pw_dir,self.name))
105 os.chown(pw_dir, uid, gid)
108 auth_keys = os.path.join(dot_ssh,'authorized_keys')
109 tools.write_file(auth_keys, lambda f: f.write(new_keys))
111 # set access permissions and ownership properly
112 os.chmod(dot_ssh, 0700)
113 os.chown(dot_ssh, uid, gid)
114 os.chmod(auth_keys, 0600)
115 os.chown(auth_keys, uid, gid)
117 # set self.keys to new_keys only when all of the above ops succeed
120 logger.log('account: %s: installed ssh keys' % self.name)
122 def start(self, delay=0): pass
124 def is_running(self): pass
126 # bind mount root side dir to sliver side
127 # needs to be done before sliver starts, in the vserver case at least
128 def expose_ssh_dir (self):
130 root_ssh="/home/%s/.ssh"%self.name
131 sliver_ssh="/vservers/%s/home/%s/.ssh"%(self.name,self.name)
132 # any of both might not exist yet
133 for path in [root_ssh,sliver_ssh]:
134 if not os.path.exists (path):
136 if not os.path.isdir (path):
138 mounts=file('/proc/mounts').read()
139 if mounts.find(sliver_ssh)<0:
141 subprocess.call("mount --bind -o ro %s %s"%(root_ssh,sliver_ssh),shell=True)
142 logger.log("expose_ssh_dir: %s mounted into slice %s"%(root_ssh,self.name))
144 logger.log_exc("expose_ssh_dir with slice %s failed"%self.name)
148 def __init__(self, name):
149 self.name = name # username
150 self._acct = None # the account object currently associated with this worker
152 def ensure_created(self, rec):
153 """Check account type is still valid. If not, recreate sliver.
154 If still valid, check if running and configure/start if not."""
155 logger.log_data_in_file(rec,"/var/lib/nodemanager/%s.rec.txt"%rec['name'],
156 'raw rec captured in ensure_created',logger.LOG_VERBOSE)
157 curr_class = self._get_class()
158 next_class = type_acct_class[rec['type']]
159 if next_class != curr_class:
160 self._destroy(curr_class)
162 try: next_class.create(self.name, rec)
163 finally: create_sem.release()
164 if not isinstance(self._acct, next_class): self._acct = next_class(rec)
165 logger.verbose("account.Worker.ensure_created: %s, running=%r"%(self.name,self.is_running()))
167 # reservation_alive is set on reervable nodes, and its value is a boolean
168 if 'reservation_alive' in rec:
170 if rec['reservation_alive']:
171 # this sliver has the lease, it is safe to start it
172 if not self.is_running(): self.start(rec)
173 else: self.configure(rec)
175 # not having the lease, do not start it
177 # usual nodes - preserve old code
178 # xxx it's not clear what to do when a sliver changes type/class
179 # in a reservable node
181 if not self.is_running() or next_class != curr_class:
183 else: self.configure(rec)
185 def ensure_destroyed(self): self._destroy(self._get_class())
187 def start(self, rec, d = 0):
188 self._acct.configure(rec)
189 self._acct.start(delay=d)
191 def configure(self, rec):
192 self._acct.configure(rec)
194 def stop(self): self._acct.stop()
196 def is_running(self):
197 if (self._acct != None) and self._acct.is_running():
201 logger.verbose("account: Worker(%s): is not running" % self.name)
204 def _destroy(self, curr_class):
207 destroy_sem.acquire()
208 try: curr_class.destroy(self.name)
209 finally: destroy_sem.release()
211 def _get_class(self):
212 try: shell = pwd.getpwnam(self.name)[6]
213 except KeyError: return None
214 return shell_acct_class[shell]