Merge branch 'devel' of ssh://git.planet-lab.org/git/nodemanager into devel
[nodemanager.git] / accounts.py
index 180a738..2f3bb32 100644 (file)
@@ -1,9 +1,13 @@
+### 
+
 """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; configure(), start(), and stop(), which
-are not.  configure(), which takes a record as its only argument, does
-things like set up ssh keys.  In addition, an Account subclass must
+Each subclass of Account must provide five methods:
+  (*) create() and destroy(), which are static;
+  (*) configure(), start(), and stop(), which are not.
+
+configure(), which takes a record as its only argument, does
+things like set up ssh keys. In addition, an Account subclass must
 provide static member variables SHELL, which contains the unique shell
 that it uses; and TYPE, a string that is used by the account creation
 code.  For no particular reason, TYPE is divided hierarchically by
 provide static member variables SHELL, which contains the unique shell
 that it uses; and TYPE, a string that is used by the account creation
 code.  For no particular reason, TYPE is divided hierarchically by
@@ -20,18 +24,14 @@ numbers of accounts, this may cause the NM process to run out of
 maximum stack size.
 """
 
 maximum stack size.
 """
 
-import Queue
 import os
 import os
-import pwd
-import grp
+import pwd, grp
 import threading
 
 import logger
 import tools
 
 
 import threading
 
 import logger
 import tools
 
 
-# When this variable is true, start after any ensure_created
-Startingup = False
 # shell path -> account class association
 shell_acct_class = {}
 # account type -> account class association
 # shell path -> account class association
 shell_acct_class = {}
 # account type -> account class association
@@ -42,7 +42,9 @@ create_sem = threading.Semaphore(1)
 destroy_sem = threading.Semaphore(1)
 
 def register_class(acct_class):
 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
 
     shell_acct_class[acct_class.SHELL] = acct_class
     type_acct_class[acct_class.TYPE] = acct_class
 
@@ -69,83 +71,112 @@ def get(name):
 
 class Account:
     def __init__(self, rec):
 
 class Account:
     def __init__(self, rec):
-        logger.verbose('Initing account %s'%rec['name'])
+        logger.verbose('accounts: Initing account %s'%rec['name'])
         self.name = rec['name']
         self.keys = ''
         self.name = rec['name']
         self.keys = ''
-        self.initscriptchanged = False
         self.configure(rec)
 
     @staticmethod
     def create(name, vref = None): abstract
         self.configure(rec)
 
     @staticmethod
     def create(name, vref = None): abstract
+
     @staticmethod
     def destroy(name): abstract
 
     def configure(self, rec):
         """Write <rec['keys']> to my authorized_keys file."""
     @staticmethod
     def destroy(name): abstract
 
     def configure(self, rec):
         """Write <rec['keys']> to my authorized_keys file."""
-        logger.verbose('%s: in accounts:configure'%self.name)
+        logger.verbose('accounts: configuring %s'%self.name)
         new_keys = rec['keys']
         if new_keys != self.keys:
         new_keys = rec['keys']
         if new_keys != self.keys:
-            self.keys = new_keys
+            # get the unix account info
             gid = grp.getgrnam("slices")[2]
             pw_info = pwd.getpwnam(self.name)
             uid = pw_info[2]
             pw_dir = pw_info[5]
 
             gid = grp.getgrnam("slices")[2]
             pw_info = pwd.getpwnam(self.name)
             uid = pw_info[2]
             pw_dir = pw_info[5]
 
-            dot_ssh = pw_dir + '/.ssh'
-            if not os.access(dot_ssh, os.F_OK): os.mkdir(dot_ssh)
-
-            auth_keys = dot_ssh + '/authorized_keys'
+            # write out authorized_keys file and conditionally create
+            # the .ssh subdir if need be.
+            dot_ssh = os.path.join(pw_dir,'.ssh')
+            if not os.path.isdir(dot_ssh):
+                if not os.path.isdir(pw_dir):
+                    logger.verbose('accounts: WARNING: homedir %s does not exist for %s!'%(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')
             tools.write_file(auth_keys, lambda f: f.write(new_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, 0700)
-            os.chmod(auth_keys, 0600)
-
             os.chown(dot_ssh, uid, gid)
             os.chown(dot_ssh, uid, gid)
+            os.chmod(auth_keys, 0600)
             os.chown(auth_keys, uid, gid)
 
             os.chown(auth_keys, uid, gid)
 
-            logger.log('%s: installed ssh keys' % self.name)
+            # set self.keys to new_keys only when all of the above ops succeed
+            self.keys = new_keys
+
+            logger.log('accounts: %s: installed ssh keys' % self.name)
 
     def start(self, delay=0): pass
     def stop(self): pass
     def is_running(self): pass
 
 class Worker:
 
     def start(self, delay=0): pass
     def stop(self): pass
     def is_running(self): pass
 
 class Worker:
+
     def __init__(self, name):
         self.name = name  # username
         self._acct = None  # the account object currently associated with this worker
 
     def __init__(self, name):
         self.name = name  # username
         self._acct = None  # the account object currently associated with this worker
 
-    def ensure_created(self, rec, startingup = Startingup):
-        """Check account type is still valid.  If not, recreate sliver.  If still valid,
-        check if running and configure/start if not."""
+    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)
         curr_class = self._get_class()
         next_class = type_acct_class[rec['type']]
         if next_class != curr_class:
             self._destroy(curr_class)
             create_sem.acquire()
         curr_class = self._get_class()
         next_class = type_acct_class[rec['type']]
         if next_class != curr_class:
             self._destroy(curr_class)
             create_sem.acquire()
-            try: next_class.create(self.name, rec['vref'])
+            try: next_class.create(self.name, rec)
             finally: create_sem.release()
         if not isinstance(self._acct, next_class): self._acct = next_class(rec)
             finally: create_sem.release()
         if not isinstance(self._acct, next_class): self._acct = next_class(rec)
-        if startingup or \
-          not self.is_running() or \
-          next_class != curr_class or \
-          self._acct.initscriptchanged:
-            self.start(rec)
-        else: self._acct.configure(rec)
+        logger.verbose("accounts.ensure_created: %s, running=%r"%(self.name,self.is_running()))
+
+        # reservation_alive is set on reervable 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)
+            else:
+                # not having the lease, do not start it
+                self.configure(rec)
+        # usual nodes - preserve old code
+        # 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:
+                self.start(rec)
+            else: self.configure(rec)
 
     def ensure_destroyed(self): self._destroy(self._get_class())
 
 
     def ensure_destroyed(self): self._destroy(self._get_class())
 
-    def start(self, rec, d = 0): 
+    def start(self, rec, d = 0):
         self._acct.configure(rec)
         self._acct.start(delay=d)
 
         self._acct.configure(rec)
         self._acct.start(delay=d)
 
+    def configure(self, rec):
+        self._acct.configure(rec)
+
     def stop(self): self._acct.stop()
 
     def stop(self): self._acct.stop()
 
-    def is_running(self): 
+    def is_running(self):
         if (self._acct != None) and self._acct.is_running():
             status = True
         else:
             status = False
         if (self._acct != None) and self._acct.is_running():
             status = True
         else:
             status = False
-            logger.verbose("Worker(%s): is not running" % self.name)
+            logger.verbose("accounts: Worker(%s): is not running" % self.name)
         return status
 
     def _destroy(self, curr_class):
         return status
 
     def _destroy(self, curr_class):