Merge from branch.
[nodemanager.git] / accounts.py
index 8363e7f..d540368 100644 (file)
@@ -1,37 +1,38 @@
 """Functionality common to all account classes.
 
 """Functionality common to all account classes.
 
-Each subclass of Account must provide five methods: create(),
-destroy(), configure(), start(), and stop().  In addition, it must
+Each subclass of Account must provide 6 methods: create() and
+destroy(), which are static; configure(), is_running(), start(), and stop(),
+which are not.  configure() takes a record as its only argument, and does
+things like set up ssh keys.  In addition, an Account subclass must
 provide static member variables SHELL, which contains the unique shell
 provide static member variables SHELL, which contains the unique shell
-that it uses; and TYPE, which contains a description of the type that
-it uses.  TYPE is divided hierarchically by periods; at the moment the
-only convention is that all sliver accounts have type that begins with
-sliver.
-
-There are any number of race conditions that may result from the fact
-that account names are not unique over time.  Moreover, it's a bad
-idea to perform lengthy operations while holding the database lock.
-In order to deal with both of these problems, we use a worker thread
-for each account name that ever exists.  On 32-bit systems with large
-numbers of accounts, this may cause the NM process to run out of
-*virtual* memory!  This problem may be remedied by decreasing the
-maximum stack size.
+that it uses; and TYPE, a string that is used by the account creation
+code.  For no particular reason, TYPE is divided hierarchically by
+periods; at the moment the only convention is that all sliver accounts
+have type that begins with sliver.
+
 """
 
 import Queue
 import os
 import pwd
 """
 
 import Queue
 import os
 import pwd
+from grp import getgrnam
 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
 type_acct_class = {}
 
 # shell path -> account class association
 shell_acct_class = {}
 # account type -> account class association
 type_acct_class = {}
 
+# these semaphores are acquired before creating/destroying an account
+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."""
     shell_acct_class[acct_class.SHELL] = acct_class
 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."""
     shell_acct_class[acct_class.SHELL] = acct_class
@@ -42,9 +43,12 @@ def register_class(acct_class):
 name_worker_lock = threading.Lock()
 name_worker = {}
 
 name_worker_lock = threading.Lock()
 name_worker = {}
 
+def allpwents():
+    return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
+
 def all():
     """Return the names of all accounts on the system with recognized shells."""
 def all():
     """Return the names of all accounts on the system with recognized shells."""
-    return [pw_ent[0] for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
+    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."""
 
 def get(name):
     """Return the worker object for a particular username.  If no such object exists, create it first."""
@@ -57,85 +61,84 @@ def get(name):
 
 class Account:
     def __init__(self, rec):
 
 class Account:
     def __init__(self, rec):
+        logger.verbose('Initing account %s'%rec['name'])
         self.name = rec['name']
         self.keys = ''
         self.name = rec['name']
         self.keys = ''
+        self.initscriptchanged = False
         self.configure(rec)
 
     @staticmethod
         self.configure(rec)
 
     @staticmethod
-    def create(name): abstract
+    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)
         new_keys = rec['keys']
         if new_keys != self.keys:
             self.keys = new_keys
             dot_ssh = '/home/%s/.ssh' % self.name
         new_keys = rec['keys']
         if new_keys != self.keys:
             self.keys = new_keys
             dot_ssh = '/home/%s/.ssh' % self.name
-            def do_installation():
-                if not os.access(dot_ssh, os.F_OK): os.mkdir(dot_ssh)
-                tools.write_file(dot_ssh + '/authorized_keys', lambda f: f.write(new_keys))
+            if not os.access(dot_ssh, os.F_OK): os.mkdir(dot_ssh)
+            os.chmod(dot_ssh, 0700)
+            tools.write_file(dot_ssh + '/authorized_keys', lambda f: f.write(new_keys))
             logger.log('%s: installing ssh keys' % self.name)
             logger.log('%s: installing ssh keys' % self.name)
-            tools.fork_as(self.name, do_installation)
+            user = pwd.getpwnam(self.name)[2]
+            group = getgrnam("slices")[2]
+            os.chown(dot_ssh, user, group)
+            os.chown(dot_ssh + '/authorized_keys', user, group)
 
 
-    def start(self): pass
+    def start(self, delay=0): pass
     def stop(self): pass
     def stop(self): pass
-
+    def is_running(self): pass
 
 class Worker:
 
 class Worker:
-    # these semaphores are acquired before creating/destroying an account
-    _create_sem = threading.Semaphore(1)
-    _destroy_sem = threading.Semaphore(1)
-
     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
-        # task list
-        # outsiders request operations by putting (fn, args...) tuples on _q
-        # the worker thread (created below) will perform these operations in order
-        self._q = Queue.Queue()
-        tools.as_daemon_thread(self._run)
-
-    def ensure_created(self, rec):
-        """Cause the account specified by <rec> to exist if it doesn't already."""
-        self._q.put((self._ensure_created, rec.copy()))
 
 
-    def _ensure_created(self, rec):
+    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."""
         curr_class = self._get_class()
         next_class = type_acct_class[rec['type']]
         if next_class != curr_class:
             self._destroy(curr_class)
         curr_class = self._get_class()
         next_class = type_acct_class[rec['type']]
         if next_class != curr_class:
             self._destroy(curr_class)
-            self._create_sem.acquire()
-            try: next_class.create(self.name)
-            finally: self._create_sem.release()
+            create_sem.acquire()
+            try: next_class.create(self.name, rec['vref'])
+            finally: create_sem.release()
         if not isinstance(self._acct, next_class): self._acct = next_class(rec)
         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)
         else: self._acct.configure(rec)
-        if next_class != curr_class: self._acct.start()
 
 
-    def ensure_destroyed(self): self._q.put((self._ensure_destroyed,))
-    def _ensure_destroyed(self): self._destroy(self._get_class())
+    def ensure_destroyed(self): self._destroy(self._get_class())
 
 
-    def start(self): self._q.put((self._start,))
-    def _start(self): self._acct.start()
+    def start(self, rec, d = 0): 
+        self._acct.configure(rec)
+        self._acct.start(delay=d)
 
 
-    def stop(self): self._q.put((self._stop,))
-    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
+        else:
+            status = False
+            logger.verbose("Worker(%s): is not running" % self.name)
+        return status
 
     def _destroy(self, curr_class):
         self._acct = None
         if curr_class:
 
     def _destroy(self, curr_class):
         self._acct = None
         if curr_class:
-            self._destroy_sem.acquire()
+            destroy_sem.acquire()
             try: curr_class.destroy(self.name)
             try: curr_class.destroy(self.name)
-            finally: self._destroy_sem.release()
+            finally: destroy_sem.release()
 
     def _get_class(self):
         try: shell = pwd.getpwnam(self.name)[6]
         except KeyError: return None
         return shell_acct_class[shell]
 
     def _get_class(self):
         try: shell = pwd.getpwnam(self.name)[6]
         except KeyError: return None
         return shell_acct_class[shell]
-
-    def _run(self):
-        while True:
-            try:
-                cmd = self._q.get()
-                cmd[0](*cmd[1:])
-            except: logger.log_exc()