+# $Id$
+# $URL$
+
"""Functionality common to all account classes.
-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
+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
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.
"""
import Queue
import os
import pwd
-from grp import getgrnam
+import grp
import threading
import logger
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.initscriptchanged = False
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:
- self.keys = new_keys
- dot_ssh = '/home/%s/.ssh' % self.name
- if not os.access(dot_ssh, os.F_OK): os.mkdir(dot_ssh)
+ # 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]
+
+ # 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))
+
+ # set access permissions and ownership properly
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)
- user = pwd.getpwnam(self.name)[2]
- group = getgrnam("slices")[2]
- os.chown(dot_ssh, user, group)
- os.chown(dot_ssh + '/authorized_keys', user, group)
+ os.chown(dot_ssh, uid, gid)
+ os.chmod(auth_keys, 0600)
+ 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('accounts: %s: installed ssh keys' % self.name)
def start(self, delay=0): pass
def stop(self): pass
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):