#!/usr/bin/python
-import fuse
-from fuse import Fuse
-
-from time import time
-
-import stat # for file properties
-import os # for filesystem modes (O_RDONLY, etc)
-import errno # for error number codes (ENOENT, etc)
- # - note: these must be returned as negatives
+import routefs
+from routes import Mapper
from syslog import *
+from time import time
-import sipb_xen_database
-
-fuse.fuse_python_api = (0, 2)
-
-def getDepth(path):
- """
- Return the depth of a given path, zero-based from root ('/')
- """
- if path == '/':
- return 0
- else:
- return path.count('/')
-
-def getParts(path):
- """
- Return the slash-separated parts of a given path as a list
- """
- # [1:] to exclude leading empty element
- split = path.split('/')
- if split[-1]:
- return split[1:]
- else:
- return split[1:-1]
-
-def parse(path):
- parts = getParts(path)
- return parts, len(parts)
-
-class MyStat:
- def __init__(self):
- self.st_mode = 0
- self.st_ino = 0
- self.st_dev = 0
- self.st_nlink = 0
- self.st_uid = 0
- self.st_gid = 0
- self.st_size = 0
- self.st_atime = 0
- self.st_mtime = 0
- self.st_ctime = 0
-
- def toTuple(self):
- return (self.st_mode, self.st_ino, self.st_dev, self.st_nlink, self.st_uid, self.st_gid, self.st_size, self.st_atime, self.st_mtime, self.st_ctime)
+from invirt import database
+from invirt.config import structs as config
-class RemConfFS(Fuse):
+class RemConfFS(routefs.RouteFS):
"""
RemConfFS creates a filesytem for configuring remctl, like this:
/
| |-- machine1
| ...
| `-- machinen
- `-- conf.d
- |-- machine1
- ...
- `-- machinen
-
+ `-- conf
+
The machine list and the acls are drawn from a database.
-
- This filesystem only implements the getattr, getdir, read, and readlink
- calls, because this is a read-only filesystem.
"""
def __init__(self, *args, **kw):
"""Initialize the filesystem and set it to allow_other access besides
the user who mounts the filesystem (i.e. root)
"""
- Fuse.__init__(self, *args, **kw)
+ super(RemConfFS, self).__init__(*args, **kw)
self.lasttime = time()
- self.allow_other = 1
+ self.fuse_args.add("allow_other", True)
openlog('sipb-xen-remconffs ', LOG_PID, LOG_DAEMON)
syslog(LOG_DEBUG, 'Init complete.')
-
- def getMachines(self):
- """Get the list of VMs in the database, clearing the cache if it's
- older than 15 seconds"""
- if time() - self.lasttime > 15:
- self.lasttime = time()
- sipb_xen_database.clear_cache()
- return [machine.name for machine in sipb_xen_database.Machine.select()]
-
- def getacl(self, machine_name):
+
+ def make_map(self):
+ m = Mapper()
+ m.connect('', controller='getroot')
+ m.connect('acl', controller='getmachines')
+ m.connect('acl/:machine', controller='getacl')
+ m.connect('conf', controller='getconf')
+ return m
+
+ def getroot(self, **kw):
+ return ['acl', 'conf']
+
+ def getacl(self, machine, **kw):
"""Build the ACL file for a machine
"""
- machine = sipb_xen_database.Machine.get_by(name=machine_name)
+ machine = database.Machine.query().filter_by(name=machine).one()
users = [acl.user for acl in machine.acl]
return "\n".join(map(self.userToPrinc, users)
+ ['include /etc/remctl/acl/web',
''])
-
- def getconf(self, machine_name):
- """Build the command file for a machine
+
+ def getconf(self, **kw):
+ """Build the master conf file, with all machines
"""
- return ("control %s /usr/sbin/sipb-xen-remote-proxy-control"
- " /etc/remctl/sipb-xen-auto/acl/%s\n"
- % (machine_name, machine_name))
-
- def getfile(self, dir, machine_name):
- """Build the ACL or command file for a machine
- """
- if dir == 'acl': return self.getacl(machine_name)
- if dir == 'conf.d': return self.getconf(machine_name)
- raise "this shouldn't happen"
+ return '\n'.join("control %s /usr/sbin/sipb-xen-remote-proxy-control"
+ " /etc/remctl/remconffs/acl/%s"
+ % (machine_name, machine_name)
+ for machine_name in self.getmachines())+'\n'
+ def getmachines(self, **kw):
+ """Get the list of VMs in the database, clearing the cache if it's
+ older than 15 seconds"""
+ if time() - self.lasttime > 15:
+ self.lasttime = time()
+ database.clear_cache()
+ return [machine.name for machine in database.session.query(database.Machine).all()]
+
def userToPrinc(self, user):
"""Convert Kerberos v4-style names to v5-style and append a default
realm if none is specified
(princ, realm) = user.split('@')
else:
princ = user
- realm = "ATHENA.MIT.EDU"
+ realm = config.authn[0].realm
return princ.replace('.', '/') + '@' + realm
-
- def getattr(self, path):
- """
- - st_mode (protection bits)
- - st_ino (inode number)
- - st_dev (device)
- - st_nlink (number of hard links)
- - st_uid (user ID of owner)
- - st_gid (group ID of owner)
- - st_size (size of file, in bytes)
- - st_atime (time of most recent access)
- - st_mtime (time of most recent content modification)
- - st_ctime (platform dependent; time of most recent metadata change on Unix,
- or the time of creation on Windows).
- """
-
- syslog(LOG_DEBUG, "*** getattr: " + path)
-
- depth = getDepth(path)
- parts = getParts(path)
-
- st = MyStat()
- if path == '/':
- st.st_mode = stat.S_IFDIR | 0755
- st.st_nlink = 2
- elif depth == 1:
- if parts[0] not in ('acl', 'conf.d'):
- return -errno.ENOENT
- st.st_mode = stat.S_IFDIR | 0755
- st.st_nlink = 2
- elif depth == 2:
- if parts[0] not in ('acl', 'conf.d'):
- return -errno.ENOENT
- if parts[1] not in self.getMachines():
- return -errno.ENOENT
- st.st_mode = stat.S_IFREG | 0444
- st.st_nlink = 1
- st.st_size = len(self.getfile(parts[0], parts[1]))
-
- return st.toTuple()
-
- # This call isn't actually used in the version of Fuse on console, but we
- # wanted to leave it implemented to ease the transition in the future
- def readdir(self, path, offset):
- """Return a generator with the listing for a directory
- """
- syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset))
- for (value, zero) in self.getdir(path):
- yield fuse.Direntry(value)
-
- def getdir(self, path):
- """Return a list of tuples of the form (item, 0) with the contents of
- the directory path
-
- Fuse doesn't add '.' or '..' on its own, so we have to
- """
- syslog(LOG_DEBUG, '*** getdir %s' % path)
-
- parts, depth = parse(path)
-
- if depth == 0:
- contents = ('acl', 'conf.d')
- elif depth == 1:
- if parts[0] in ('acl', 'conf.d'):
- contents = self.getMachines()
- else:
- return -errno.ENOENT
- else:
- return -errno.ENOTDIR
-
- # Format the list the way that Fuse wants it - and don't forget to add
- # '.' and '..'
- return [(i, 0) for i in (list(contents) + ['.', '..'])]
-
- def read(self, path, length, offset):
- """Read length bytes starting at offset of path. In most cases, this
- just gets passed on to the OS
- """
- syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset))
-
- parts, depth = parse(path)
-
- # If the depth is not 2, then either it's a directory or the file
- # doesn't exist
- # (realistically this doesn't appear to ever happen)
- if getDepth(path) != 2:
- return -errno.ENOENT
- elif parts[1] in self.getMachines():
- if parts[0] == 'acl':
- return self.getacl(parts[1])[offset:offset+length]
- if parts[0] == 'conf.d':
- return self.getconf(parts[1])[offset:offset+length]
- return -errno.ENOENT
-
- def readlink(self, path):
- syslog(LOG_DEBUG, '*** readlink %s' % path)
- return -errno.ENOENT
-
if __name__ == '__main__':
- sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
- usage="""
-$0 [mount_path]
-"""
- server = RemConfFS()
- server.flags = 0
- server.main()
+ database.connect()
+ routefs.main(RemConfFS)