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
37 # shell path -> account class association
39 # account type -> account class association
42 # these semaphores are acquired before creating/destroying an account
43 create_sem = threading.Semaphore(1)
44 destroy_sem = threading.Semaphore(1)
46 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
52 shell_acct_class[acct_class.SHELL] = acct_class
53 type_acct_class[acct_class.TYPE] = acct_class
56 # private account name -> worker object association and associated lock
57 name_worker_lock = threading.Lock()
61 return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
64 """Return the names of all accounts on the system with recognized shells."""
65 return [pw_ent[0] for pw_ent in allpwents()]
69 Return the worker object for a particular username.
70 If no such object exists, create it first.
72 name_worker_lock.acquire()
74 if name not in name_worker:
75 name_worker[name] = Worker(name)
76 return name_worker[name]
78 name_worker_lock.release()
83 Base class for all types of account
86 def __init__(self, name):
89 logger.verbose('account: Initing account {}'.format(name))
92 def create(name, vref = None):
99 def configure(self, rec):
101 Write <rec['keys']> to my authorized_keys file.
103 new_keys = rec['keys']
104 nb_keys = len(new_keys) if isinstance(new_keys, list) else 1
105 logger.verbose('account: configuring {} with {} keys'.format(self.name, nb_keys))
106 if new_keys != self.keys:
107 # get the unix account info
108 gid = grp.getgrnam("slices")[2]
109 pw_info = pwd.getpwnam(self.name)
113 # write out authorized_keys file and conditionally create
114 # the .ssh subdir if need be.
115 dot_ssh = os.path.join(pw_dir, '.ssh')
116 if not os.path.isdir(dot_ssh):
117 if not os.path.isdir(pw_dir):
118 logger.verbose('account: WARNING: homedir {} does not exist for {}!'
119 .format(pw_dir, self.name))
121 os.chown(pw_dir, uid, gid)
124 auth_keys = os.path.join(dot_ssh, 'authorized_keys')
125 tools.write_file(auth_keys, lambda f: f.write(new_keys))
127 # set access permissions and ownership properly
128 os.chmod(dot_ssh, 0o700)
129 os.chown(dot_ssh, uid, gid)
130 os.chmod(auth_keys, 0o600)
131 os.chown(auth_keys, uid, gid)
133 # set self.keys to new_keys only when all of the above ops succeed
136 logger.log('account: {}: installed ssh keys'.format(self.name))
138 def start(self, delay=0):
142 def is_running(self):
144 def needs_reimage(self, target_slicefamily):
145 stampname = "/vservers/{}/etc/slicefamily".format(self.name)
147 with open(stampname) as f:
148 current_slicefamily = f.read().strip()
149 return current_slicefamily != target_slicefamily
151 logger.verbose("Account.needs_reimage: missing slicefamily {} - left as-is"
155 ### this used to be a plain method but because it needs to be invoked by destroy
156 # which is a static method, they need to become static as well
157 # needs to be done before sliver starts (checked with vs and lxc)
159 def mount_ssh_dir (slicename):
160 return Account._manage_ssh_dir (slicename, do_mount=True)
162 def umount_ssh_dir (slicename):
163 return Account._manage_ssh_dir (slicename, do_mount=False)
165 # bind mount / umount root side dir to sliver side
167 def _manage_ssh_dir (slicename, do_mount):
168 logger.log("_manage_ssh_dir, requested to " +
169 ( "mount" if do_mount else "umount" ) +
170 " ssh dir for "+ slicename)
172 root_ssh = "/home/{}/.ssh".format(slicename)
173 sliver_ssh = "/vservers/{}/home/{}/.ssh".format(slicename, slicename)
174 def is_mounted (root_ssh):
175 with open('/proc/mounts') as mountsfile:
176 for mount_line in mountsfile.readlines():
177 if mount_line.find (root_ssh) >= 0:
181 # any of both might not exist yet
182 for path in [root_ssh, sliver_ssh]:
183 if not os.path.exists (path):
185 if not os.path.isdir (path):
187 if not is_mounted(root_ssh):
188 command = ['mount', '--bind', '-o', 'ro', root_ssh, sliver_ssh]
189 mounted = logger.log_call (command)
190 msg = "OK" if mounted else "WARNING: FAILED"
191 logger.log("_manage_ssh_dir: mounted {} into slice {} - {}"
192 .format(root_ssh, slicename, msg))
194 if is_mounted (sliver_ssh):
195 command = ['umount', sliver_ssh]
196 umounted = logger.log_call(command)
197 msg = "OK" if umounted else "WARNING: FAILED"
198 logger.log("_manage_ssh_dir: umounted {} - {}"
199 .format(sliver_ssh, msg))
200 except Exception as e:
201 logger.log_exc("_manage_ssh_dir failed : {}".format(e), name=slicename)
205 def __init__(self, name):
206 self.name = name # username
207 self._acct = None # the account object currently associated with this worker
209 def ensure_created(self, rec):
211 Check account type is still valid. If not, recreate sliver.
212 If still valid, check if running and configure/start if not.
214 logger.log_data_in_file(rec, "/var/lib/nodemanager/{}.rec.txt".format(rec['name']),
215 'raw rec captured in ensure_created', logger.LOG_VERBOSE)
216 curr_class = self._get_class()
217 next_class = type_acct_class[rec['type']]
218 if next_class != curr_class:
219 self._destroy(curr_class)
221 try: next_class.create(self.name, rec)
222 finally: create_sem.release()
223 if not isinstance(self._acct, next_class):
224 self._acct = next_class(rec)
225 logger.verbose("account.Worker.ensure_created: {}, running={}"
226 .format(self.name, self.is_running()))
228 # reservation_alive is set on reservable nodes, and its value is a boolean
229 if 'reservation_alive' in rec:
231 if rec['reservation_alive']:
232 # this sliver has the lease, it is safe to start it
233 if not self.is_running():
238 # not having the lease, do not start it
240 # usual nodes - preserve old code
241 # xxx it's not clear what to do when a sliver changes type/class
242 # in a reservable node
244 if not self.is_running() or self.needs_reimage(rec['vref']) or next_class != curr_class:
249 def ensure_destroyed(self):
250 self._destroy(self._get_class())
252 # take rec as an arg here for api_calls
253 def start(self, rec, d = 0):
254 self._acct.configure(rec)
255 self._acct.start(delay=d)
257 def configure(self, rec):
258 self._acct.configure(rec)
263 def is_running(self):
264 if self._acct and self._acct.is_running():
267 logger.verbose("Worker.is_running ({}) - no account or not running".format(self.name))
270 def needs_reimage(self, target_slicefamily):
272 logger.verbose("Worker.needs_reimage ({}) - no account -> True".format(self.name))
275 account_needs_reimage = self._acct.needs_reimage(target_slicefamily)
276 if account_needs_reimage:
277 logger.log("Worker.needs_reimage ({}) - account needs reimage (tmp: DRY RUN)"
280 logger.verbose("Worker.needs_reimage ({}) - everything fine"
282 return account_needs_reimage
284 def _destroy(self, curr_class):
287 destroy_sem.acquire()
289 logger.verbose("account._destroy is callling destroy from {}"
290 .format(curr_class.__name__))
291 curr_class.destroy(self.name)
293 destroy_sem.release()
295 def _get_class(self):
297 shell = pwd.getpwnam(self.name)[6]
300 return shell_acct_class[shell]