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

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

remconffs: cat conf-files together

  • Property svn:executable set to *
File size: 5.9 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):
108                """Build the master conf file, with all machines
109                """
110                return '\n'.join("control %s /usr/sbin/sipb-xen-remote-proxy-control"
111                                 " /etc/remctl/remconffs/acl/%s"
112                                 % (machine_name, machine_name)
113                                 for machine_name in self.getMachines())+'\n'
114       
115        def userToPrinc(self, user):
116                """Convert Kerberos v4-style names to v5-style and append a default
117                realm if none is specified
118                """
119                if '@' in user:
120                        (princ, realm) = user.split('@')
121                else:
122                        princ = user
123                        realm = "ATHENA.MIT.EDU"
124               
125                return princ.replace('.', '/') + '@' + realm
126       
127        def getattr(self, path):
128                """
129                - st_mode (protection bits)
130                - st_ino (inode number)
131                - st_dev (device)
132                - st_nlink (number of hard links)
133                - st_uid (user ID of owner)
134                - st_gid (group ID of owner)
135                - st_size (size of file, in bytes)
136                - st_atime (time of most recent access)
137                - st_mtime (time of most recent content modification)
138                - st_ctime (platform dependent; time of most recent metadata change on Unix,
139                                        or the time of creation on Windows).
140                """
141               
142                syslog(LOG_DEBUG, "*** getattr: " + path)
143               
144                depth = getDepth(path)
145                parts = getParts(path)
146               
147                st = MyStat()
148                if path == '/':
149                        st.st_mode = stat.S_IFDIR | 0755
150                        st.st_nlink = 2
151                elif depth == 1:
152                        if parts[0] == 'acl':
153                                st.st_mode = stat.S_IFDIR | 0755
154                                st.st_nlink = 2
155                        elif parts[0] == 'conf':
156                                st.st_mode = stat.S_IFREG | 0444
157                                st.st_nlink = 1
158                                st.st_size = len(self.getconf())
159                        else:
160                                return -errno.ENOENT
161                elif depth == 2:
162                        if parts[0] != 'acl':
163                                return -errno.ENOENT
164                        if parts[1] not in self.getMachines():
165                                return -errno.ENOENT
166                        st.st_mode = stat.S_IFREG | 0444
167                        st.st_nlink = 1
168                        st.st_size = len(self.getacl(parts[1]))
169
170                return st.toTuple()
171       
172        # This call isn't actually used in the version of Fuse on console, but we
173        # wanted to leave it implemented to ease the transition in the future
174        def readdir(self, path, offset):
175                """Return a generator with the listing for a directory
176                """
177                syslog(LOG_DEBUG, '*** readdir %s %s' % (path, offset))
178                for (value, zero) in self.getdir(path):
179                        yield fuse.Direntry(value)
180       
181        def getdir(self, path):
182                """Return a list of tuples of the form (item, 0) with the contents of
183                the directory path
184               
185                Fuse doesn't add '.' or '..' on its own, so we have to
186                """
187                syslog(LOG_DEBUG, '*** getdir %s' % path)
188               
189                parts, depth = parse(path)
190
191                if depth == 0:
192                        contents = ('acl', 'conf')
193                elif depth == 1:
194                        if parts[0] == 'acl':
195                                contents = self.getMachines()
196                        else:
197                                return -errno.ENOENT
198                else:
199                        return -errno.ENOTDIR
200
201                # Format the list the way that Fuse wants it - and don't forget to add
202                # '.' and '..'
203                return [(i, 0) for i in (list(contents) + ['.', '..'])]
204
205        def read(self, path, length, offset):
206                """Read length bytes starting at offset of path. In most cases, this
207                just gets passed on to the OS
208                """
209                syslog(LOG_DEBUG, '*** read %s %s %s' % (path, length, offset))
210               
211                parts, depth = parse(path)
212               
213                if depth == 0:
214                        return -errno.EISDIR
215                elif parts[0] == 'conf':
216                        return self.getconf()[offset:offset+length]
217                elif parts[0] == 'acl':
218                        if depth == 1:
219                                return -errno.EISDIR
220                        if parts[1] in self.getMachines():
221                                return self.getacl(parts[1])[offset:offset+length]
222                return -errno.ENOENT
223       
224        def readlink(self, path):
225                syslog(LOG_DEBUG, '*** readlink %s' % path)
226                return -errno.ENOENT
227
228
229if __name__ == '__main__':
230        sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
231        usage="""
232$0 [mount_path]
233"""
234        server = RemConfFS()
235        server.flags = 0
236        server.main()
Note: See TracBrowser for help on using the repository browser.