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

Last change on this file since 866 was 835, checked in by y_z, 16 years ago
  • sipb_xen_database -> invirt.database
  • removed redundant parameter to connect()
  • Property svn:executable set to *
File size: 5.9 KB
RevLine 
[518]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
[835]15from invirt import database
[830]16from invirt.config import structs as config
[518]17
18fuse.fuse_python_api = (0, 2)
19
20def getDepth(path):
21        """
22        Return the depth of a given path, zero-based from root ('/')
23        """
24        if path == '/':
25                return 0
26        else:
27                return path.count('/')
28
29def getParts(path):
30        """
31        Return the slash-separated parts of a given path as a list
32        """
33        # [1:] to exclude leading empty element
34        split = path.split('/')
35        if split[-1]:
36                return split[1:]
37        else:
38                return split[1:-1]
39
40def parse(path):
41        parts = getParts(path)
42        return parts, len(parts)
43
44class MyStat:
45        def __init__(self):
46                self.st_mode = 0
47                self.st_ino = 0
48                self.st_dev = 0
49                self.st_nlink = 0
50                self.st_uid = 0
51                self.st_gid = 0
52                self.st_size = 0
53                self.st_atime = 0
54                self.st_mtime = 0
55                self.st_ctime = 0
56       
57        def toTuple(self):
58                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)
59
60class RemConfFS(Fuse):
61        """
62        RemConfFS creates a filesytem for configuring remctl, like this:
63        /
64        |-- acl
65        |   |-- machine1
66        |   ...
67        |   `-- machinen
68        `-- conf.d
69            |-- machine1
70            ...
71            `-- machinen
72
73        The machine list and the acls are drawn from a database.
74       
75        This filesystem only implements the getattr, getdir, read, and readlink
76        calls, because this is a read-only filesystem.
77        """
78       
79        def __init__(self, *args, **kw):
80                """Initialize the filesystem and set it to allow_other access besides
81                the user who mounts the filesystem (i.e. root)
82                """
83                Fuse.__init__(self, *args, **kw)
84                self.lasttime = time()
85                self.allow_other = 1
86               
87                openlog('sipb-xen-remconffs ', LOG_PID, LOG_DAEMON)
88               
89                syslog(LOG_DEBUG, 'Init complete.')
90
91        def getMachines(self):
92                """Get the list of VMs in the database, clearing the cache if it's
93                older than 15 seconds"""
94                if time() - self.lasttime > 15:
95                        self.lasttime = time()
[835]96                        database.clear_cache()
97                return [machine.name for machine in database.Machine.select()]
[518]98               
99        def getacl(self, machine_name):
100                """Build the ACL file for a machine
101                """
[835]102                machine = database.Machine.get_by(name=machine_name)
[518]103                users = [acl.user for acl in machine.acl]
104                return "\n".join(map(self.userToPrinc, users)
105                                 + ['include /etc/remctl/acl/web',
106                                    ''])
107               
[519]108        def getconf(self):
109                """Build the master conf file, with all machines
[518]110                """
[519]111                return '\n'.join("control %s /usr/sbin/sipb-xen-remote-proxy-control"
112                                 " /etc/remctl/remconffs/acl/%s"
113                                 % (machine_name, machine_name)
114                                 for machine_name in self.getMachines())+'\n'
[518]115       
116        def userToPrinc(self, user):
117                """Convert Kerberos v4-style names to v5-style and append a default
118                realm if none is specified
119                """
120                if '@' in user:
121                        (princ, realm) = user.split('@')
122                else:
123                        princ = user
[830]124                        realm = config.authn[0].realm
[518]125               
126                return princ.replace('.', '/') + '@' + realm
127       
128        def getattr(self, path):
129                """
130                - st_mode (protection bits)
131                - st_ino (inode number)
132                - st_dev (device)
133                - st_nlink (number of hard links)
134                - st_uid (user ID of owner)
135                - st_gid (group ID of owner)
136                - st_size (size of file, in bytes)
137                - st_atime (time of most recent access)
138                - st_mtime (time of most recent content modification)
139                - st_ctime (platform dependent; time of most recent metadata change on Unix,
140                                        or the time of creation on Windows).
141                """
142               
143                syslog(LOG_DEBUG, "*** getattr: " + path)
144               
145                depth = getDepth(path)
146                parts = getParts(path)
147               
148                st = MyStat()
149                if path == '/':
150                        st.st_mode = stat.S_IFDIR | 0755
151                        st.st_nlink = 2
152                elif depth == 1:
[519]153                        if parts[0] == 'acl':
154                                st.st_mode = stat.S_IFDIR | 0755
155                                st.st_nlink = 2
156                        elif parts[0] == 'conf':
157                                st.st_mode = stat.S_IFREG | 0444
158                                st.st_nlink = 1
159                                st.st_size = len(self.getconf())
160                        else:
[518]161                                return -errno.ENOENT
162                elif depth == 2:
[519]163                        if parts[0] != 'acl':
[518]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
[519]169                        st.st_size = len(self.getacl(parts[1]))
[518]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:
[519]193                        contents = ('acl', 'conf')
[518]194                elif depth == 1:
[519]195                        if parts[0] == 'acl':
[518]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               
[519]214                if depth == 0:
215                        return -errno.EISDIR
216                elif parts[0] == 'conf':
217                        return self.getconf()[offset:offset+length]
218                elif parts[0] == 'acl':
219                        if depth == 1:
220                                return -errno.EISDIR
221                        if parts[1] in self.getMachines():
[518]222                                return self.getacl(parts[1])[offset:offset+length]
223                return -errno.ENOENT
224       
225        def readlink(self, path):
226                syslog(LOG_DEBUG, '*** readlink %s' % path)
227                return -errno.ENOENT
228
229
230if __name__ == '__main__':
[835]231        database.connect()
[518]232        usage="""
233$0 [mount_path]
234"""
235        server = RemConfFS()
236        server.flags = 0
237        server.main()
Note: See TracBrowser for help on using the repository browser.