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()
72 # xxx strictly speaking this class should not use self.name that in fact
73 # is accidentally inited by the subclasses constructor...
75 def __init__(self, name):
78 logger.verbose('account: Initing account %s'%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('account: 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('account: 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('account: %s: installed ssh keys' % self.name)
121 def start(self, delay=0): pass
123 def is_running(self): pass
125 # bind mount root side dir to sliver side
126 # needs to be done before sliver starts, in the vserver case at least
127 def expose_ssh_dir (self):
129 root_ssh="/home/%s/.ssh"%self.name
130 sliver_ssh="/vservers/%s/home/%s/.ssh"%(self.name,self.name)
131 # any of both might not exist yet
132 for path in [root_ssh,sliver_ssh]:
133 if not os.path.exists (path):
135 if not os.path.isdir (path):
137 mounts=file('/proc/mounts').read()
138 if mounts.find(sliver_ssh)<0:
140 subprocess.call("mount --bind -o ro %s %s"%(root_ssh,sliver_ssh),shell=True)
141 logger.log("expose_ssh_dir: %s mounted into slice %s"%(root_ssh,self.name))
143 logger.log_exc("expose_ssh_dir with slice %s failed"%self.name)
147 def __init__(self, name):
148 self.name = name # username
149 self._acct = None # the account object currently associated with this worker
151 def ensure_created(self, rec):
152 """Check account type is still valid. If not, recreate sliver.
153 If still valid, check if running and configure/start if not."""
154 logger.log_data_in_file(rec,"/var/lib/nodemanager/%s.rec.txt"%rec['name'],
155 'raw rec captured in ensure_created',logger.LOG_VERBOSE)
156 curr_class = self._get_class()
157 next_class = type_acct_class[rec['type']]
158 if next_class != curr_class:
159 self._destroy(curr_class)
161 try: next_class.create(self.name, rec)
162 finally: create_sem.release()
163 if not isinstance(self._acct, next_class): self._acct = next_class(rec)
164 logger.verbose("account.Worker.ensure_created: %s, running=%r"%(self.name,self.is_running()))
166 # reservation_alive is set on reervable nodes, and its value is a boolean
167 if 'reservation_alive' in rec:
169 if rec['reservation_alive']:
170 # this sliver has the lease, it is safe to start it
171 if not self.is_running(): self.start(rec)
172 else: self.configure(rec)
174 # not having the lease, do not start it
176 # usual nodes - preserve old code
177 # xxx it's not clear what to do when a sliver changes type/class
178 # in a reservable node
180 if not self.is_running() or next_class != curr_class:
182 else: self.configure(rec)
184 def ensure_destroyed(self): self._destroy(self._get_class())
186 def start(self, rec, d = 0):
187 self._acct.configure(rec)
188 self._acct.start(delay=d)
190 def configure(self, rec):
191 self._acct.configure(rec)
193 def stop(self): self._acct.stop()
195 def is_running(self):
196 if (self._acct != None) and self._acct.is_running():
200 logger.verbose("account: Worker(%s): is not running" % self.name)
203 def _destroy(self, curr_class):
206 destroy_sem.acquire()
207 try: curr_class.destroy(self.name)
208 finally: destroy_sem.release()
210 def _get_class(self):
211 try: shell = pwd.getpwnam(self.name)[6]
212 except KeyError: return None
213 return shell_acct_class[shell]