Merge from branch.
[nodemanager.git] / accounts.py
1 """Functionality common to all account classes.
2
3 Each subclass of Account must provide 6 methods: create() and
4 destroy(), which are static; configure(), is_running(), start(), and stop(),
5 which are not.  configure() takes a record as its only argument, and 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 name_worker = {}
45
46 def allpwents():
47     return [pw_ent for pw_ent in pwd.getpwall() if pw_ent[6] in shell_acct_class]
48
49 def all():
50     """Return the names of all accounts on the system with recognized shells."""
51     return [pw_ent[0] for pw_ent in allpwents()]
52
53 def get(name):
54     """Return the worker object for a particular username.  If no such object exists, create it first."""
55     name_worker_lock.acquire()
56     try:
57         if name not in name_worker: name_worker[name] = Worker(name)
58         return name_worker[name]
59     finally: name_worker_lock.release()
60
61
62 class Account:
63     def __init__(self, rec):
64         logger.verbose('Initing account %s'%rec['name'])
65         self.name = rec['name']
66         self.keys = ''
67         self.initscriptchanged = False
68         self.configure(rec)
69
70     @staticmethod
71     def create(name, vref = None): abstract
72     @staticmethod
73     def destroy(name): abstract
74
75     def configure(self, rec):
76         """Write <rec['keys']> to my authorized_keys file."""
77         logger.verbose('%s: in accounts:configure'%self.name)
78         new_keys = rec['keys']
79         if new_keys != self.keys:
80             self.keys = new_keys
81             dot_ssh = '/home/%s/.ssh' % self.name
82             if not os.access(dot_ssh, os.F_OK): os.mkdir(dot_ssh)
83             os.chmod(dot_ssh, 0700)
84             tools.write_file(dot_ssh + '/authorized_keys', lambda f: f.write(new_keys))
85             logger.log('%s: installing ssh keys' % self.name)
86             user = pwd.getpwnam(self.name)[2]
87             group = getgrnam("slices")[2]
88             os.chown(dot_ssh, user, group)
89             os.chown(dot_ssh + '/authorized_keys', user, group)
90
91     def start(self, delay=0): pass
92     def stop(self): pass
93     def is_running(self): pass
94
95 class Worker:
96     def __init__(self, name):
97         self.name = name  # username
98         self._acct = None  # the account object currently associated with this worker
99
100     def ensure_created(self, rec, startingup = Startingup):
101         """Check account type is still valid.  If not, recreate sliver.  If still valid,
102         check if running and configure/start if not."""
103         curr_class = self._get_class()
104         next_class = type_acct_class[rec['type']]
105         if next_class != curr_class:
106             self._destroy(curr_class)
107             create_sem.acquire()
108             try: next_class.create(self.name, rec['vref'])
109             finally: create_sem.release()
110         if not isinstance(self._acct, next_class): self._acct = next_class(rec)
111         if startingup or \
112           not self.is_running() or \
113           next_class != curr_class or \
114           self._acct.initscriptchanged:
115             self.start(rec)
116         else: self._acct.configure(rec)
117
118     def ensure_destroyed(self): self._destroy(self._get_class())
119
120     def start(self, rec, d = 0): 
121         self._acct.configure(rec)
122         self._acct.start(delay=d)
123
124     def stop(self): self._acct.stop()
125
126     def is_running(self): 
127         if (self._acct != None) and self._acct.is_running():
128             status = True
129         else:
130             status = False
131             logger.verbose("Worker(%s): is not running" % self.name)
132         return status
133
134     def _destroy(self, curr_class):
135         self._acct = None
136         if curr_class:
137             destroy_sem.acquire()
138             try: curr_class.destroy(self.name)
139             finally: destroy_sem.release()
140
141     def _get_class(self):
142         try: shell = pwd.getpwnam(self.name)[6]
143         except KeyError: return None
144         return shell_acct_class[shell]