6bffdbe8ae725061ee626f7cf08245b96910052f
[nodemanager.git] / accounts.py
1 """Functionality common to all account classes.
2
3 Each subclass of Account must provide five methods: create() and
4 destroy(), which are static; configure(), start(), and stop(), which
5 are not.  configure(), which takes a record as its only argument, does
6 things like set up ssh keys.  In addition, an Account subclass must
7 provide static member variables SHELL, which contains the unique shell
8 that it uses; and TYPE, a string that is used by the account creation
9 code.  For no particular reason, TYPE is divided hierarchically by
10 periods; at the moment the only convention is that all sliver accounts
11 have type that begins with sliver.
12
13 """
14
15 import Queue
16 import os
17 import pwd
18 from grp import getgrnam
19 import threading
20
21 import logger
22 import tools
23
24
25 # When this variable is true, start after any ensure_created
26 Startingup = False
27 # shell path -> account class association
28 shell_acct_class = {}
29 # account type -> account class association
30 type_acct_class = {}
31
32 # these semaphores are acquired before creating/destroying an account
33 create_sem = threading.Semaphore(1)
34 destroy_sem = threading.Semaphore(1)
35
36 def register_class(acct_class):
37     """Call once for each account class.  This method adds the class to the dictionaries used to look up account classes by shell and type."""
38     shell_acct_class[acct_class.SHELL] = acct_class
39     type_acct_class[acct_class.TYPE] = acct_class
40
41
42 # private account name -> worker object association and associated lock
43 name_worker_lock = threading.Lock()
44 # dict of account_name: <Worker Object>
45 name_worker = {}
46
47 def allpwents():
48     return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
49
50 def all():
51     """Return the names of all accounts on the system with recognized shells."""
52     return [pw_ent[0] for pw_ent in allpwents()]
53
54 def get(name):
55     """Return the worker object for a particular username.  If no such object exists, create it first."""
56     name_worker_lock.acquire()
57     try:
58         if name not in name_worker: 
59             logger.verbose("Accounts:get(%s) new Worker" % name)
60             name_worker[name] = Worker(name)
61         return name_worker[name]
62     finally: name_worker_lock.release()
63
64
65 class Account:
66     def __init__(self, rec):
67         logger.verbose('Initing account %s'%rec['name'])
68         self.name = rec['name']
69         self.keys = ''
70         self.initscriptchanged = False
71         self.configure(rec)
72
73     @staticmethod
74     def create(name, vref = None): abstract
75     @staticmethod
76     def destroy(name): abstract
77
78     def configure(self, rec):
79         """Write <rec['keys']> to my authorized_keys file."""
80         logger.verbose('%s: in accounts:configure'%self.name)
81         new_keys = rec['keys']
82         if new_keys != self.keys:
83             self.keys = new_keys
84             dot_ssh = '/home/%s/.ssh' % self.name
85             if not os.access(dot_ssh, os.F_OK): os.mkdir(dot_ssh)
86             os.chmod(dot_ssh, 0700)
87             tools.write_file(dot_ssh + '/authorized_keys', lambda f: f.write(new_keys))
88             logger.log('%s: installing ssh keys' % self.name)
89             user = pwd.getpwnam(self.name)[2]
90             group = getgrnam("slices")[2]
91             os.chown(dot_ssh, user, group)
92             os.chown(dot_ssh + '/authorized_keys', user, group)
93
94     def start(self, delay=0): pass
95     def stop(self): pass
96     def is_running(self): pass
97
98 class Worker:
99     def __init__(self, name):
100         self.name = name  # username
101         self._acct = None  # the account object currently associated with this worker
102
103     def ensure_created(self, rec, startingup = Startingup):
104         """Check account type is still valid.  If not, recreate sliver.  If still valid,
105         check if running and configure/start if not."""
106         curr_class = self._get_class()
107         next_class = type_acct_class[rec['type']]
108         if next_class != curr_class:
109             self._destroy(curr_class)
110             create_sem.acquire()
111             try: next_class.create(self.name, rec['vref'])
112             finally: create_sem.release()
113         if not isinstance(self._acct, next_class): self._acct = next_class(rec)
114         if startingup or \
115           not self.is_running() or \
116           next_class != curr_class or \
117           self._acct.initscriptchanged:
118             self.start(rec)
119         else: self._acct.configure(rec)
120
121     def ensure_destroyed(self): self._destroy(self._get_class())
122
123     def start(self, rec, d = 0): 
124         self._acct.configure(rec)
125         self._acct.start(delay=d)
126
127     def stop(self): self._acct.stop()
128
129     def is_running(self): 
130         if self._acct.is_running():
131             status = True
132         else:
133             status = False
134             logger.verbose("Worker(%s): is not running" % self.name)
135         return status
136
137     def _destroy(self, curr_class):
138         self._acct = None
139         if curr_class:
140             destroy_sem.acquire()
141             try: curr_class.destroy(self.name)
142             finally: destroy_sem.release()
143
144     def _get_class(self):
145         try: shell = pwd.getpwnam(self.name)[6]
146         except KeyError: return None
147         return shell_acct_class[shell]