source: trunk/packages/sipb-xen-remote-server/files/usr/sbin/sipb-xen-remconffs @ 518

Last change on this file since 518 was 518, checked in by price, 16 years ago

first version of remctl-conf fuse fs

Heavily borrows from consolefs, obviously. Refactor later.

  • Property svn:executable set to *
File size: 6.2 KB
Line 
1#!/usr/bin/python
2
3import fuse
4from fuse import Fuse
5
6from time import time
7
8import stat     # for file properties
9import os         # for filesystem modes (O_RDONLY, etc)
10import errno   # for error number codes (ENOENT, etc)
11                           # - note: these must be returned as negatives
12
13from syslog import *
14
15import sipb_xen_database
16
17fuse.fuse_python_api = (0, 2)
18
19def getDepth(path):
20        """
21        Return the depth of a given path, zero-based from root ('/')
22        """
23        if path == '/':
24                return 0
25        else:
26                return path.count('/')
27
28def getParts(path):
29        """
30        Return the slash-separated parts of a given path as a list
31        """
32        # [1:] to exclude leading empty element
33        split = path.split('/')
34        if split[-1]:
35                return split[1:]
36        else:
37                return split[1:-1]
38
39def parse(path):
40        parts = getParts(path)
41        return parts, len(parts)
42
43class MyStat:
44        def __init__(self):
45                self.st_mode = 0
46                self.st_ino = 0
47                self.st_dev = 0
48                self.st_nlink = 0
49                self.st_uid = 0
50                self.st_gid = 0
51                self.st_size = 0
52                self.st_atime = 0
53                self.st_mtime = 0
54                self.st_ctime = 0
55       
56        def toTuple(self):
57                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)
58
59class RemConfFS(Fuse):
60        """
61        RemConfFS creates a filesytem for configuring remctl, like this:
62        /
63        |-- acl
64        |   |-- machine1
65        |   ...
66        |   `-- machinen
67        `-- conf.d
68            |-- machine1
69            ...
70            `-- machinen
71
72        The machine list and the acls are drawn from a database.
73       
74        This filesystem only implements the getattr, getdir, read, and readlink
75        calls, because this is a read-only filesystem.
76        """
77       
78        def __init__(self, *args, **kw):
79                """Initialize the filesystem and set it to allow_other access besides
80                the user who mounts the filesystem (i.e. root)
81                """
82                Fuse.__init__(self, *args, **kw)
83                self.lasttime = time()
84                self.allow_other = 1
85               
86                openlog('sipb-xen-remconffs ', LOG_PID, LOG_DAEMON)
87               
88                syslog(LOG_DEBUG, 'Init complete.')
89
90        def getMachines(self):
91                """Get the list of VMs in the database, clearing the cache if it's
92                older than 15 seconds"""
93                if time() - self.lasttime > 15:
94                        self.lasttime = time()
95                        sipb_xen_database.clear_cache()
96                return [machine.name for machine in sipb_xen_database.Machine.select()]
97               
98        def getacl(self, machine_name):
99                """Build the ACL file for a machine
100                """
101                machine = sipb_xen_database.Machine.get_by(name=machine_name)
102                users = [acl.user for acl in machine.acl]
103                return "\n".join(map(self.userToPrinc, users)
104                                 + ['include /etc/remctl/acl/web',
105                                    ''])
106               
107        def getconf(self, machine_name):
108                """Build the command file for a machine
109                """
110                return ("control %s /usr/sbin/sipb-xen-remote-proxy-control"
111                        " /etc/remctl/sipb-xen-auto/acl/%s\n"
112                        % (machine_name, machine_name))
113               
114        def getfile(self, dir, machine_name):
115                """Build the ACL or command file for a machine
116                """
117                if dir == 'acl':    return self.getacl(machine_name)
118                if dir == 'conf.d': return self.getconf(machine_name)
119                raise "this shouldn't happen"
120       
121        def userToPrinc(self, user):
122                """Convert Kerberos v4-style names to v5-style and append a default
123                realm if none is specified
124                """
125                if '@' in user:
126                        (princ, realm) = user.split('@')
127                else:
128                        princ = user
129                        realm = "ATHENA.MIT.EDU"
130               
131                return princ.replace('.', '/') + '@' + realm
132       
133        def getattr(self, path):
134                """
135                - st_mode (protection bits)
136                - st_ino (inode number)
137                - st_dev (device)
138                - st_nlink (number of hard links)
139                - st_uid (user ID of owner)
140                - st_gid (group ID of owner)
141                - st_size (size of file, in bytes)
142                - st_atime (time of most recent access)
143                - st_mtime (time of most recent content modification)
144                - st_ctime (platform dependent; time of most recent metadata change on Unix,
145                                        or the time of creation on Windows).
146                """
147               
148                syslog(LOG_DEBUG, "*** getattr: " + path)
149               
150                depth = getDepth(path)
151                parts = getParts(path)
152               
153                st = MyStat()
154                if path == '/':
155                        st.st_mode = stat.S_IFDIR | 0755
156                        st.st_nlink = 2
157                elif depth == 1:
158                        if parts[0] not in ('acl', 'conf.d'):
159                                return -errno.ENOENT
160                        st.st_mode = stat.S_IFDIR | 0755
161                        st.st_nlink = 2
162                elif depth == 2:
163                        if parts[0] not in ('acl', 'conf.d'):
164                                return -errno.ENOENT
165                        if parts[1] not in self.getMachines():
166                                return -errno.ENOENT
167                        st.st_mode = stat.S_IFREG | 0444
168                        st.st_nlink = 1
169                        st.st_size = len(self.getfile(parts[0], parts[1]))
170
171                return st.toTuple()
172       
173        # This call isn't actually used in the version of Fuse on console, but we
174        # wanted to leave it implemented to ease the transition in the future
175        def readdir(self, path, offset):
176                """Return a generator with the listing for a directory
177                """
178                syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset))
179                for (value, zero) in self.getdir(path):
180                        yield fuse.Direntry(value)
181       
182        def getdir(self, path):
183                """Return a list of tuples of the form (item, 0) with the contents of
184                the directory path
185               
186                Fuse doesn't add '.' or '..' on its own, so we have to
187                """
188                syslog(LOG_DEBUG, '*** getdir %s' % path)
189               
190                parts, depth = parse(path)
191
192                if depth == 0:
193                        contents = ('acl', 'conf.d')
194                elif depth == 1:
195                        if parts[0] in ('acl', 'conf.d'):
196                                contents = self.getMachines()
197                        else:
198                                return -errno.ENOENT
199                else:
200                        return -errno.ENOTDIR
201
202                # Format the list the way that Fuse wants it - and don't forget to add
203                # '.' and '..'
204                return [(i, 0) for i in (list(contents) + ['.', '..'])]
205
206        def read(self, path, length, offset):
207                """Read length bytes starting at offset of path. In most cases, this
208                just gets passed on to the OS
209                """
210                syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset))
211               
212                parts, depth = parse(path)
213               
214                # If the depth is not 2, then either it's a directory or the file
215                # doesn't exist
216                # (realistically this doesn't appear to ever happen)
217                if getDepth(path) != 2:
218                        return -errno.ENOENT
219                elif parts[1] in self.getMachines():
220                        if parts[0] == 'acl':
221                                return self.getacl(parts[1])[offset:offset+length]
222                        if parts[0] == 'conf.d':
223                                return self.getconf(parts[1])[offset:offset+length]
224                return -errno.ENOENT
225       
226        def readlink(self, path):
227                syslog(LOG_DEBUG, '*** readlink %s' % path)
228                return -errno.ENOENT
229
230
231if __name__ == '__main__':
232        sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
233        usage="""
234$0 [mount_path]
235"""
236        server = RemConfFS()
237        server.flags = 0
238        server.main()
Note: See TracBrowser for help on using the repository browser.