X-Git-Url: http://git.onelab.eu/?p=nodemanager.git;a=blobdiff_plain;f=account.py;h=d45f5335f1c6f4a796aab9de5c175b04b554a9df;hp=616761c6c37b1c433645a96075211028a4212c9c;hb=HEAD;hpb=8edf881ae2829d4bf4dcb1911dc86332de30c807 diff --git a/account.py b/account.py index 616761c..d45f533 100644 --- a/account.py +++ b/account.py @@ -1,6 +1,7 @@ ### -"""Functionality common to all account classes. +""" +Functionality common to all account classes. Each subclass of Account must provide five methods: (*) create() and destroy(), which are static; @@ -27,6 +28,7 @@ maximum stack size. import os import pwd, grp import threading +import subprocess import logger import tools @@ -42,9 +44,11 @@ create_sem = threading.Semaphore(1) destroy_sem = threading.Semaphore(1) def register_class(acct_class): - """Call once for each account class. This method adds the class -to the dictionaries used to look up account classes by shell and -type.""" + """ + Call once for each account class. This method adds the class + to the dictionaries used to look up account classes + by shell and type. + """ shell_acct_class[acct_class.SHELL] = acct_class type_acct_class[acct_class.TYPE] = acct_class @@ -61,32 +65,44 @@ def all(): return [pw_ent[0] for pw_ent in allpwents()] def get(name): - """Return the worker object for a particular username. If no such object exists, create it first.""" + """ + Return the worker object for a particular username. + If no such object exists, create it first. + """ name_worker_lock.acquire() try: - if name not in name_worker: name_worker[name] = Worker(name) + if name not in name_worker: + name_worker[name] = Worker(name) return name_worker[name] - finally: name_worker_lock.release() + finally: + name_worker_lock.release() -# xxx strictly speaking this class should not use self.name that in fact -# is accidentally inited by the subclasses constructor... class Account: + """ + Base class for all types of account + """ + def __init__(self, name): self.name = name self.keys = '' - logger.verbose('account: Initing account %s'%name) + logger.verbose('account: Initing account {}'.format(name)) -# @staticmethod -# def create(name, vref = None): abstract + @staticmethod + def create(name, vref = None): + abstract @staticmethod - def destroy(name): abstract + def destroy(name): + abstract def configure(self, rec): - """Write to my authorized_keys file.""" - logger.verbose('account: configuring %s'%self.name) + """ + Write to my authorized_keys file. + """ new_keys = rec['keys'] + nb_keys = len(new_keys) if isinstance(new_keys, list) else 1 + logger.verbose('account: configuring {} with {} keys'.format(self.name, nb_keys)) if new_keys != self.keys: # get the unix account info gid = grp.getgrnam("slices")[2] @@ -96,31 +112,93 @@ class Account: # write out authorized_keys file and conditionally create # the .ssh subdir if need be. - dot_ssh = os.path.join(pw_dir,'.ssh') + dot_ssh = os.path.join(pw_dir, '.ssh') if not os.path.isdir(dot_ssh): if not os.path.isdir(pw_dir): - logger.verbose('account: WARNING: homedir %s does not exist for %s!'%(pw_dir,self.name)) + logger.verbose('account: WARNING: homedir {} does not exist for {}!' + .format(pw_dir, self.name)) os.mkdir(pw_dir) os.chown(pw_dir, uid, gid) os.mkdir(dot_ssh) - auth_keys = os.path.join(dot_ssh,'authorized_keys') + auth_keys = os.path.join(dot_ssh, 'authorized_keys') tools.write_file(auth_keys, lambda f: f.write(new_keys)) # set access permissions and ownership properly - os.chmod(dot_ssh, 0700) + os.chmod(dot_ssh, 0o700) os.chown(dot_ssh, uid, gid) - os.chmod(auth_keys, 0600) + os.chmod(auth_keys, 0o600) os.chown(auth_keys, uid, gid) # set self.keys to new_keys only when all of the above ops succeed self.keys = new_keys - logger.log('account: %s: installed ssh keys' % self.name) + logger.log('account: {}: installed ssh keys'.format(self.name)) - def start(self, delay=0): pass - def stop(self): pass - def is_running(self): pass + def start(self, delay=0): + pass + def stop(self): + pass + def is_running(self): + pass + def needs_reimage(self, target_slicefamily): + stampname = "/vservers/{}/etc/slicefamily".format(self.name) + try: + with open(stampname) as f: + current_slicefamily = f.read().strip() + return current_slicefamily != target_slicefamily + except IOError as e: + logger.verbose("Account.needs_reimage: missing slicefamily {} - left as-is" + .format(self.name)) + return False + + ### this used to be a plain method but because it needs to be invoked by destroy + # which is a static method, they need to become static as well + # needs to be done before sliver starts (checked with vs and lxc) + @staticmethod + def mount_ssh_dir (slicename): + return Account._manage_ssh_dir (slicename, do_mount=True) + @staticmethod + def umount_ssh_dir (slicename): + return Account._manage_ssh_dir (slicename, do_mount=False) + + # bind mount / umount root side dir to sliver side + @staticmethod + def _manage_ssh_dir (slicename, do_mount): + logger.log("_manage_ssh_dir, requested to " + + ( "mount" if do_mount else "umount" ) + + " ssh dir for "+ slicename) + try: + root_ssh = "/home/{}/.ssh".format(slicename) + sliver_ssh = "/vservers/{}/home/{}/.ssh".format(slicename, slicename) + def is_mounted (root_ssh): + with open('/proc/mounts') as mountsfile: + for mount_line in mountsfile.readlines(): + if mount_line.find (root_ssh) >= 0: + return True + return False + if do_mount: + # any of both might not exist yet + for path in [root_ssh, sliver_ssh]: + if not os.path.exists (path): + os.mkdir(path) + if not os.path.isdir (path): + raise Exception + if not is_mounted(root_ssh): + command = ['mount', '--bind', '-o', 'ro', root_ssh, sliver_ssh] + mounted = logger.log_call (command) + msg = "OK" if mounted else "WARNING: FAILED" + logger.log("_manage_ssh_dir: mounted {} into slice {} - {}" + .format(root_ssh, slicename, msg)) + else: + if is_mounted (sliver_ssh): + command = ['umount', sliver_ssh] + umounted = logger.log_call(command) + msg = "OK" if umounted else "WARNING: FAILED" + logger.log("_manage_ssh_dir: umounted {} - {}" + .format(sliver_ssh, msg)) + except Exception as e: + logger.log_exc("_manage_ssh_dir failed : {}".format(e), name=slicename) class Worker: @@ -129,10 +207,12 @@ class Worker: self._acct = None # the account object currently associated with this worker def ensure_created(self, rec): - """Check account type is still valid. If not, recreate sliver. -If still valid, check if running and configure/start if not.""" - logger.log_data_in_file(rec,"/var/lib/nodemanager/%s.rec.txt"%rec['name'], - 'raw rec captured in ensure_created',logger.LOG_VERBOSE) + """ + Check account type is still valid. If not, recreate sliver. + If still valid, check if running and configure/start if not. + """ + logger.log_data_in_file(rec, "/var/lib/nodemanager/{}.rec.txt".format(rec['name']), + 'raw rec captured in ensure_created', logger.LOG_VERBOSE) curr_class = self._get_class() next_class = type_acct_class[rec['type']] if next_class != curr_class: @@ -140,16 +220,20 @@ If still valid, check if running and configure/start if not.""" create_sem.acquire() try: next_class.create(self.name, rec) finally: create_sem.release() - if not isinstance(self._acct, next_class): self._acct = next_class(rec) - logger.verbose("account.Worker.ensure_created: %s, running=%r"%(self.name,self.is_running())) + if not isinstance(self._acct, next_class): + self._acct = next_class(rec) + logger.verbose("account.Worker.ensure_created: {}, running={}" + .format(self.name, self.is_running())) - # reservation_alive is set on reervable nodes, and its value is a boolean + # reservation_alive is set on reservable nodes, and its value is a boolean if 'reservation_alive' in rec: # reservable nodes if rec['reservation_alive']: # this sliver has the lease, it is safe to start it - if not self.is_running(): self.start(rec) - else: self.configure(rec) + if not self.is_running(): + self.start(rec) + else: + self.configure(rec) else: # not having the lease, do not start it self.configure(rec) @@ -157,12 +241,15 @@ If still valid, check if running and configure/start if not.""" # xxx it's not clear what to do when a sliver changes type/class # in a reservable node else: - if not self.is_running() or next_class != curr_class: + if not self.is_running() or self.needs_reimage(rec['vref']) or next_class != curr_class: self.start(rec) - else: self.configure(rec) + else: + self.configure(rec) - def ensure_destroyed(self): self._destroy(self._get_class()) + def ensure_destroyed(self): + self._destroy(self._get_class()) + # take rec as an arg here for api_calls def start(self, rec, d = 0): self._acct.configure(rec) self._acct.start(delay=d) @@ -170,45 +257,45 @@ If still valid, check if running and configure/start if not.""" def configure(self, rec): self._acct.configure(rec) - def stop(self): self._acct.stop() + def stop(self): + self._acct.stop() def is_running(self): - if (self._acct != None) and self._acct.is_running(): - status = True + if self._acct and self._acct.is_running(): + return True else: - status = False - logger.verbose("account: Worker(%s): is not running" % self.name) - return status + logger.verbose("Worker.is_running ({}) - no account or not running".format(self.name)) + return False + def needs_reimage(self, target_slicefamily): + if not self._acct: + logger.verbose("Worker.needs_reimage ({}) - no account -> True".format(self.name)) + return True + else: + account_needs_reimage = self._acct.needs_reimage(target_slicefamily) + if account_needs_reimage: + logger.log("Worker.needs_reimage ({}) - account needs reimage (tmp: DRY RUN)" + .format(self.name)) + else: + logger.verbose("Worker.needs_reimage ({}) - everything fine" + .format(self.name)) + return account_needs_reimage + def _destroy(self, curr_class): self._acct = None if curr_class: destroy_sem.acquire() - try: curr_class.destroy(self.name) - finally: destroy_sem.release() + try: + logger.verbose("account._destroy is callling destroy from {}" + .format(curr_class.__name__)) + curr_class.destroy(self.name) + finally: + destroy_sem.release() def _get_class(self): - try: shell = pwd.getpwnam(self.name)[6] - except KeyError: return None - return shell_acct_class[shell] - - # bind mount root side dir to sliver side - # needs to be done before sliver starts, in the vserver case at least - def expose_ssh_dir (self): try: - root_ssh="/home/%s/.ssh"%self.name - sliver_ssh="/vservers/%s/home/%s/.ssh"%(self.name,self.name) - # any of both might not exist yet - for path in [root_ssh,sliver_ssh]: - if not os.path.exists (path): - os.mkdir(path) - if not os.path.isdir (path): - raise Exception - mounts=file('/proc/mounts').read() - if mounts.find(sliver_ssh)<0: - # xxx perform mount - subprocess.call("mount --bind -o ro %s %s"%(root_ssh,sliver_ssh),shell=True) - logger.log("expose_ssh_dir: %s mounted into slice %s"%(root_ssh,self.name)) - except: - logger.log_exc("expose_ssh_dir with slice %s failed"%self.name) + shell = pwd.getpwnam(self.name)[6] + except KeyError: + return None + return shell_acct_class[shell]