ensure-dirs: Implement debug logging
[ganeti-local] / lib / tools / ensure_dirs.py
1 #
2 #
3
4 # Copyright (C) 2011 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21 """Script to ensure permissions on files/dirs are accurate.
22
23 """
24
25 import errno
26 import os
27 import os.path
28 import optparse
29 import sys
30 import stat
31 import logging
32
33 from ganeti import constants
34 from ganeti import errors
35 from ganeti import runtime
36 from ganeti import ssconf
37 from ganeti import utils
38 from ganeti import cli
39
40
41 (DIR,
42  FILE,
43  QUEUE_DIR) = range(1, 4)
44
45 ALL_TYPES = frozenset([
46   DIR,
47   FILE,
48   QUEUE_DIR,
49   ])
50
51
52 class EnsureError(errors.GenericError):
53   """Top-level error class related to this script.
54
55   """
56
57
58 def EnsurePermission(path, mode, uid=-1, gid=-1, must_exist=True,
59                      _chmod_fn=os.chmod, _chown_fn=os.chown):
60   """Ensures that given path has given mode.
61
62   @param path: The path to the file
63   @param mode: The mode of the file
64   @param uid: The uid of the owner of this file
65   @param gid: The gid of the owner of this file
66   @param must_exist: Specifies if non-existance of path will be an error
67   @param _chmod_fn: chmod function to use (unittest only)
68   @param _chown_fn: chown function to use (unittest only)
69
70   """
71   logging.debug("Checking %s", path)
72   try:
73     _chmod_fn(path, mode)
74
75     if max(uid, gid) > -1:
76       _chown_fn(path, uid, gid)
77   except EnvironmentError, err:
78     if err.errno == errno.ENOENT:
79       if must_exist:
80         raise EnsureError("Path %s does not exists, but should" % path)
81     else:
82       raise EnsureError("Error while changing permission on %s: %s" %
83                         (path, err))
84
85
86 def EnsureDir(path, mode, uid, gid, _stat_fn=os.lstat, _mkdir_fn=os.mkdir,
87               _ensure_fn=EnsurePermission):
88   """Ensures that given path is a dir and has given mode, uid and gid set.
89
90   @param path: The path to the file
91   @param mode: The mode of the file
92   @param uid: The uid of the owner of this file
93   @param gid: The gid of the owner of this file
94   @param _stat_fn: Stat function to use (unittest only)
95   @param _mkdir_fn: mkdir function to use (unittest only)
96   @param _ensure_fn: ensure function to use (unittest only)
97
98   """
99   logging.debug("Checking directory %s", path)
100   try:
101     # We don't want to follow symlinks
102     st_mode = _stat_fn(path)[stat.ST_MODE]
103
104     if not stat.S_ISDIR(st_mode):
105       raise EnsureError("Path %s is expected to be a directory, but it's not" %
106                         path)
107   except EnvironmentError, err:
108     if err.errno == errno.ENOENT:
109       _mkdir_fn(path)
110     else:
111       raise EnsureError("Error while do a stat() on %s: %s" % (path, err))
112
113   _ensure_fn(path, mode, uid=uid, gid=gid)
114
115
116 def RecursiveEnsure(path, uid, gid, dir_perm, file_perm):
117   """Ensures permissions recursively down a directory.
118
119   This functions walks the path and sets permissions accordingly.
120
121   @param path: The absolute path to walk
122   @param uid: The uid used as owner
123   @param gid: The gid used as group
124   @param dir_perm: The permission bits set for directories
125   @param file_perm: The permission bits set for files
126
127   """
128   assert os.path.isabs(path), "Path %s is not absolute" % path
129   assert os.path.isdir(path), "Path %s is not a dir" % path
130
131   logging.debug("Recursively processing %s", path)
132
133   for root, dirs, files in os.walk(path):
134     for subdir in dirs:
135       EnsurePermission(os.path.join(root, subdir), dir_perm, uid=uid, gid=gid)
136
137     for filename in files:
138       EnsurePermission(os.path.join(root, filename), file_perm, uid=uid,
139                        gid=gid)
140
141
142 def EnsureQueueDir(path, mode, uid, gid):
143   """Sets the correct permissions on all job files in the queue.
144
145   @param path: Directory path
146   @param mode: Wanted file mode
147   @param uid: Wanted user ID
148   @param gid: Wanted group ID
149
150   """
151   for filename in utils.ListVisibleFiles(path):
152     if constants.JOB_FILE_RE.match(filename):
153       EnsurePermission(utils.PathJoin(path, filename), mode, uid=uid, gid=gid)
154
155
156 def ProcessPath(path):
157   """Processes a path component.
158
159   @param path: A tuple of the path component to process
160
161   """
162   (pathname, pathtype, mode, uid, gid) = path[0:5]
163
164   assert pathtype in ALL_TYPES
165
166   if pathtype in (DIR, QUEUE_DIR):
167     # No additional parameters
168     assert len(path[5:]) == 0
169     if pathtype == DIR:
170       EnsureDir(pathname, mode, uid, gid)
171     elif pathtype == QUEUE_DIR:
172       EnsureQueueDir(pathname, mode, uid, gid)
173   elif pathtype == FILE:
174     (must_exist, ) = path[5:]
175     EnsurePermission(pathname, mode, uid=uid, gid=gid, must_exist=must_exist)
176
177
178 def GetPaths():
179   """Returns a tuple of path objects to process.
180
181   """
182   getent = runtime.GetEnts()
183   masterd_log = constants.DAEMONS_LOGFILES[constants.MASTERD]
184   noded_log = constants.DAEMONS_LOGFILES[constants.NODED]
185   confd_log = constants.DAEMONS_LOGFILES[constants.CONFD]
186   rapi_log = constants.DAEMONS_LOGFILES[constants.RAPI]
187
188   rapi_dir = os.path.join(constants.DATA_DIR, "rapi")
189
190   paths = [
191     (constants.DATA_DIR, DIR, 0755, getent.masterd_uid,
192      getent.masterd_gid),
193     (constants.CLUSTER_DOMAIN_SECRET_FILE, FILE, 0640,
194      getent.masterd_uid, getent.masterd_gid, False),
195     (constants.CLUSTER_CONF_FILE, FILE, 0640, getent.masterd_uid,
196      getent.confd_gid, False),
197     (constants.CONFD_HMAC_KEY, FILE, 0440, getent.confd_uid,
198      getent.masterd_gid, False),
199     (constants.SSH_KNOWN_HOSTS_FILE, FILE, 0644, getent.masterd_uid,
200      getent.masterd_gid, False),
201     (constants.RAPI_CERT_FILE, FILE, 0440, getent.rapi_uid,
202      getent.masterd_gid, False),
203     (constants.NODED_CERT_FILE, FILE, 0440, getent.masterd_uid,
204      getent.masterd_gid, False),
205     ]
206
207   ss = ssconf.SimpleStore()
208   for ss_path in ss.GetFileList():
209     paths.append((ss_path, FILE, constants.SS_FILE_PERMS,
210                   getent.noded_uid, 0, False))
211
212   paths.extend([
213     (constants.QUEUE_DIR, DIR, 0700, getent.masterd_uid,
214      getent.masterd_gid),
215     (constants.QUEUE_DIR, QUEUE_DIR, 0600, getent.masterd_uid,
216      getent.masterd_gid),
217     (constants.JOB_QUEUE_LOCK_FILE, FILE, 0600,
218      getent.masterd_uid, getent.masterd_gid, False),
219     (constants.JOB_QUEUE_SERIAL_FILE, FILE, 0600,
220      getent.masterd_uid, getent.masterd_gid, False),
221     (constants.JOB_QUEUE_ARCHIVE_DIR, DIR, 0700,
222      getent.masterd_uid, getent.masterd_gid),
223     (rapi_dir, DIR, 0750, getent.rapi_uid, getent.masterd_gid),
224     (constants.RAPI_USERS_FILE, FILE, 0640, getent.rapi_uid,
225      getent.masterd_gid, False),
226     (constants.RUN_GANETI_DIR, DIR, 0775, getent.masterd_uid,
227      getent.daemons_gid),
228     (constants.SOCKET_DIR, DIR, 0750, getent.masterd_uid,
229      getent.daemons_gid),
230     (constants.MASTER_SOCKET, FILE, 0770, getent.masterd_uid,
231      getent.daemons_gid, False),
232     (constants.BDEV_CACHE_DIR, DIR, 0755, getent.noded_uid,
233      getent.masterd_gid),
234     (constants.UIDPOOL_LOCKDIR, DIR, 0750, getent.noded_uid,
235      getent.masterd_gid),
236     (constants.DISK_LINKS_DIR, DIR, 0755, getent.noded_uid,
237      getent.masterd_gid),
238     (constants.CRYPTO_KEYS_DIR, DIR, 0700, getent.noded_uid,
239      getent.masterd_gid),
240     (constants.IMPORT_EXPORT_DIR, DIR, 0755, getent.noded_uid,
241      getent.masterd_gid),
242     (constants.LOG_DIR, DIR, 0770, getent.masterd_uid,
243      getent.daemons_gid),
244     (masterd_log, FILE, 0600, getent.masterd_uid, getent.masterd_gid,
245      False),
246     (confd_log, FILE, 0600, getent.confd_uid, getent.masterd_gid, False),
247     (noded_log, FILE, 0600, getent.noded_uid, getent.masterd_gid, False),
248     (rapi_log, FILE, 0600, getent.rapi_uid, getent.masterd_gid, False),
249     (constants.LOG_OS_DIR, DIR, 0750, getent.masterd_uid,
250      getent.daemons_gid),
251     ])
252
253   return tuple(paths)
254
255
256 def SetupLogging(opts):
257   """Configures the logging module.
258
259   """
260   formatter = logging.Formatter("%(asctime)s: %(message)s")
261
262   stderr_handler = logging.StreamHandler()
263   stderr_handler.setFormatter(formatter)
264   if opts.debug:
265     stderr_handler.setLevel(logging.NOTSET)
266   elif opts.verbose:
267     stderr_handler.setLevel(logging.INFO)
268   else:
269     stderr_handler.setLevel(logging.WARNING)
270
271   root_logger = logging.getLogger("")
272   root_logger.setLevel(logging.NOTSET)
273   root_logger.addHandler(stderr_handler)
274
275
276 def ParseOptions():
277   """Parses the options passed to the program.
278
279   @return: Options and arguments
280
281   """
282   program = os.path.basename(sys.argv[0])
283
284   parser = optparse.OptionParser(usage="%%prog [--full-run]",
285                                  prog=program)
286   parser.add_option(cli.DEBUG_OPT)
287   parser.add_option(cli.VERBOSE_OPT)
288   parser.add_option("--full-run", "-f", dest="full_run", action="store_true",
289                     default=False, help=("Make a full run and collect"
290                                          " additional files (time consuming)"))
291
292   return parser.parse_args()
293
294
295 def Main():
296   """Main routine.
297
298   """
299   (opts, _) = ParseOptions()
300
301   SetupLogging(opts)
302
303   if opts.full_run:
304     logging.info("Running in full mode")
305
306   getent = runtime.GetEnts()
307
308   try:
309     for path in GetPaths():
310       ProcessPath(path)
311
312     if opts.full_run:
313       RecursiveEnsure(constants.JOB_QUEUE_ARCHIVE_DIR, getent.masterd_uid,
314                       getent.masterd_gid, 0700, 0600)
315   except EnsureError, err:
316     logging.error("An error occurred while setting permissions: %s", err)
317     return constants.EXIT_FAILURE
318
319   return constants.EXIT_SUCCESS