]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2011 New Dream Network | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | #include "common/ConfUtils.h" | |
15 | #include "common/config.h" | |
16 | #include "common/errno.h" | |
17 | #include "gtest/gtest.h" | |
18 | #include "include/buffer.h" | |
19 | ||
20 | #include <errno.h> | |
21 | #include <iostream> | |
22 | #include <stdlib.h> | |
23 | #include <sstream> | |
24 | #include <stdint.h> | |
25 | #include <sys/stat.h> | |
26 | #include <sys/types.h> | |
27 | #include "include/memory.h" | |
28 | ||
29 | using ceph::bufferlist; | |
30 | using std::cerr; | |
31 | using std::ostringstream; | |
32 | ||
33 | #define MAX_FILES_TO_DELETE 1000UL | |
34 | ||
35 | static size_t config_idx = 0; | |
36 | static size_t unlink_idx = 0; | |
37 | static char *to_unlink[MAX_FILES_TO_DELETE]; | |
38 | ||
39 | static std::string get_temp_dir() | |
40 | { | |
41 | static std::string temp_dir; | |
42 | ||
43 | if (temp_dir.empty()) { | |
44 | const char *tmpdir = getenv("TMPDIR"); | |
45 | if (!tmpdir) | |
46 | tmpdir = "/tmp"; | |
47 | srand(time(NULL)); | |
48 | ostringstream oss; | |
49 | oss << tmpdir << "/confutils_test_dir." << rand() << "." << getpid(); | |
50 | umask(022); | |
51 | int res = mkdir(oss.str().c_str(), 01777); | |
52 | if (res) { | |
53 | cerr << "failed to create temp directory '" << temp_dir << "'" << std::endl; | |
54 | return ""; | |
55 | } | |
56 | temp_dir = oss.str(); | |
57 | } | |
58 | return temp_dir; | |
59 | } | |
60 | ||
61 | static void unlink_all(void) | |
62 | { | |
63 | for (size_t i = 0; i < unlink_idx; ++i) { | |
64 | unlink(to_unlink[i]); | |
65 | } | |
66 | for (size_t i = 0; i < unlink_idx; ++i) { | |
67 | free(to_unlink[i]); | |
68 | } | |
69 | rmdir(get_temp_dir().c_str()); | |
70 | } | |
71 | ||
72 | static int create_tempfile(const std::string &fname, const char *text) | |
73 | { | |
74 | FILE *fp = fopen(fname.c_str(), "w"); | |
75 | if (!fp) { | |
76 | int err = errno; | |
77 | cerr << "Failed to write file '" << fname << "' to temp directory '" | |
78 | << get_temp_dir() << "'. " << cpp_strerror(err) << std::endl; | |
79 | return err; | |
80 | } | |
81 | ceph::shared_ptr<FILE> fpp(fp, fclose); | |
82 | if (unlink_idx >= MAX_FILES_TO_DELETE) | |
83 | return -ENOBUFS; | |
84 | if (unlink_idx == 0) { | |
85 | memset(to_unlink, 0, sizeof(to_unlink)); | |
86 | atexit(unlink_all); | |
87 | } | |
88 | to_unlink[unlink_idx++] = strdup(fname.c_str()); | |
89 | size_t strlen_text = strlen(text); | |
90 | size_t res = fwrite(text, 1, strlen_text, fp); | |
91 | if (res != strlen_text) { | |
92 | int err = errno; | |
93 | cerr << "fwrite error while writing to " << fname | |
94 | << ": " << cpp_strerror(err) << std::endl; | |
95 | return err; | |
96 | } | |
97 | return 0; | |
98 | } | |
99 | ||
100 | static std::string next_tempfile(const char *text) | |
101 | { | |
102 | ostringstream oss; | |
103 | std::string temp_dir(get_temp_dir()); | |
104 | if (temp_dir.empty()) | |
105 | return ""; | |
106 | oss << temp_dir << "/test_config." << config_idx++ << ".config"; | |
107 | int ret = create_tempfile(oss.str(), text); | |
108 | if (ret) | |
109 | return ""; | |
110 | return oss.str(); | |
111 | } | |
112 | ||
113 | const char * const trivial_conf_1 = ""; | |
114 | ||
115 | const char * const trivial_conf_2 = "log dir = foobar"; | |
116 | ||
117 | const char * const trivial_conf_3 = "log dir = barfoo\n"; | |
118 | ||
119 | const char * const trivial_conf_4 = "log dir = \"barbaz\"\n"; | |
120 | ||
121 | const char * const simple_conf_1 = "\ | |
122 | ; here's a comment\n\ | |
123 | [global]\n\ | |
124 | keyring = .my_ceph_keyring\n\ | |
125 | \n\ | |
126 | [mds]\n\ | |
127 | log dir = out\n\ | |
128 | log per instance = true\n\ | |
129 | log sym history = 100\n\ | |
130 | profiling logger = true\n\ | |
131 | profiling logger dir = wowsers\n\ | |
132 | chdir = ""\n\ | |
133 | pid file = out/$name.pid\n\ | |
134 | \n\ | |
135 | mds debug frag = true\n\ | |
136 | [osd]\n\ | |
137 | pid file = out/$name.pid\n\ | |
138 | osd scrub load threshold = 5.0\n\ | |
139 | \n\ | |
140 | lockdep = 1\n\ | |
141 | [osd0]\n\ | |
142 | osd data = dev/osd0\n\ | |
143 | osd journal size = 100\n\ | |
144 | [mds.a]\n\ | |
145 | [mds.b]\n\ | |
146 | [mds.c]\n\ | |
147 | "; | |
148 | ||
149 | // we can add whitespace at odd locations and it will get stripped out. | |
150 | const char * const simple_conf_2 = "\ | |
151 | [mds.a]\n\ | |
152 | log dir = special_mds_a\n\ | |
153 | [mds]\n\ | |
154 | log sym history = 100\n\ | |
155 | log dir = out # after a comment, anything # can ### happen ;;; right?\n\ | |
156 | log per instance = true\n\ | |
157 | profiling logger = true\n\ | |
158 | profiling logger dir = log\n\ | |
159 | chdir = ""\n\ | |
160 | pid file\t=\tfoo2\n\ | |
161 | [osd0]\n\ | |
162 | keyring = osd_keyring ; osd's keyring\n\ | |
163 | \n\ | |
164 | \n\ | |
165 | [global]\n\ | |
166 | # I like pound signs as comment markers.\n\ | |
167 | ; Do you like pound signs as comment markers?\n\ | |
168 | keyring = shenanigans ; The keyring of a leprechaun\n\ | |
169 | \n\ | |
170 | # Let's just have a line with a lot of whitespace and nothing else.\n\ | |
171 | \n\ | |
172 | lockdep = 1\n\ | |
173 | "; | |
174 | ||
175 | // test line-combining | |
176 | const char * const conf3 = "\ | |
177 | [global]\n\ | |
178 | log file = /quite/a/long/path\\\n\ | |
179 | /for/a/log/file\n\ | |
180 | pid file = \\\n\ | |
181 | spork\\\n\ | |
182 | \n\ | |
183 | [mon] #nothing here \n\ | |
184 | "; | |
185 | ||
186 | const char * const escaping_conf_1 = "\ | |
187 | [global]\n\ | |
188 | log file = the \"scare quotes\"\n\ | |
189 | pid file = a \\\n\ | |
190 | pid file\n\ | |
191 | [mon]\n\ | |
192 | keyring = \"nested \\\"quotes\\\"\"\n\ | |
193 | "; | |
194 | ||
195 | const char * const escaping_conf_2 = "\ | |
196 | [apple \\]\\[]\n\ | |
197 | log file = floppy disk\n\ | |
198 | [mon]\n\ | |
199 | keyring = \"backslash\\\\\"\n\ | |
200 | "; | |
201 | ||
202 | // illegal because it contains an invalid utf8 sequence. | |
203 | const char illegal_conf1[] = "\ | |
204 | [global]\n\ | |
205 | log file = foo\n\ | |
206 | pid file = invalid-utf-\xe2\x28\xa1\n\ | |
207 | [osd0]\n\ | |
208 | keyring = osd_keyring ; osd's keyring\n\ | |
209 | "; | |
210 | ||
211 | // illegal because it contains a malformed section header. | |
212 | const char illegal_conf2[] = "\ | |
213 | [global\n\ | |
214 | log file = foo\n\ | |
215 | [osd0]\n\ | |
216 | keyring = osd_keyring ; osd's keyring\n\ | |
217 | "; | |
218 | ||
219 | // illegal because it contains a line that doesn't parse | |
220 | const char illegal_conf3[] = "\ | |
221 | [global]\n\ | |
222 | who_what_where\n\ | |
223 | [osd0]\n\ | |
224 | keyring = osd_keyring ; osd's keyring\n\ | |
225 | "; | |
226 | ||
227 | // illegal because it has unterminated quotes | |
228 | const char illegal_conf4[] = "\ | |
229 | [global]\n\ | |
230 | keyring = \"unterminated quoted string\n\ | |
231 | [osd0]\n\ | |
232 | keyring = osd_keyring ; osd's keyring\n\ | |
233 | "; | |
234 | ||
235 | // illegal because it has a backslash at the very end | |
236 | const char illegal_conf5[] = "\ | |
237 | [global]\n\ | |
238 | keyring = something awful\\\\\n\ | |
239 | "; | |
240 | ||
241 | // unicode config file | |
242 | const char unicode_config_1[] = "\ | |
243 | [global]\n\ | |
244 | log file = \x66\xd1\x86\xd1\x9d\xd3\xad\xd3\xae \n\ | |
245 | pid file = foo-bar\n\ | |
246 | [osd0]\n\ | |
247 | "; | |
248 | ||
249 | const char override_config_1[] = "\ | |
250 | [global]\n\ | |
251 | log file = global_log\n\ | |
252 | [mds]\n\ | |
253 | log file = mds_log\n\ | |
254 | [osd]\n\ | |
255 | log file = osd_log\n\ | |
256 | [osd.0]\n\ | |
257 | log file = osd0_log\n\ | |
258 | "; | |
259 | ||
260 | const char dup_key_config_1[] = "\ | |
261 | [mds.a]\n\ | |
262 | log_file = 1\n\ | |
263 | log_file = 3\n\ | |
264 | "; | |
265 | ||
266 | TEST(ConfUtils, Whitespace) { | |
267 | std::string test0(""); | |
268 | ConfFile::trim_whitespace(test0, false); | |
269 | ASSERT_EQ(test0, ""); | |
270 | ||
271 | std::string test0a(""); | |
272 | ConfFile::trim_whitespace(test0a, true); | |
273 | ASSERT_EQ(test0a, ""); | |
274 | ||
275 | std::string test0b(" "); | |
276 | ConfFile::trim_whitespace(test0b, false); | |
277 | ASSERT_EQ(test0b, ""); | |
278 | ||
279 | std::string test0c(" "); | |
280 | ConfFile::trim_whitespace(test0c, true); | |
281 | ASSERT_EQ(test0c, ""); | |
282 | ||
283 | std::string test1(" abc "); | |
284 | ConfFile::trim_whitespace(test1, false); | |
285 | ASSERT_EQ(test1, "abc"); | |
286 | ||
287 | std::string test2(" abc d "); | |
288 | ConfFile::trim_whitespace(test2, true); | |
289 | ASSERT_EQ(test2, "abc d"); | |
290 | ||
291 | std::string test3(" abc d "); | |
292 | ConfFile::trim_whitespace(test3, false); | |
293 | ASSERT_EQ(test3, "abc d"); | |
294 | ||
295 | std::string test4("abcd"); | |
296 | ConfFile::trim_whitespace(test4, false); | |
297 | ASSERT_EQ(test4, "abcd"); | |
298 | ||
299 | std::string test5("abcd"); | |
300 | ConfFile::trim_whitespace(test5, true); | |
301 | ASSERT_EQ(test5, "abcd"); | |
302 | } | |
303 | ||
304 | TEST(ConfUtils, ParseFiles0) { | |
305 | std::deque<std::string> err; | |
306 | std::string val; | |
307 | std::ostringstream warn; | |
308 | ||
309 | std::string trivial_conf_1_f(next_tempfile(trivial_conf_1)); | |
310 | ConfFile cf1; | |
311 | ASSERT_EQ(cf1.parse_file(trivial_conf_1_f.c_str(), &err, &warn), 0); | |
312 | ASSERT_EQ(err.size(), 0U); | |
313 | ||
314 | std::string trivial_conf_2_f(next_tempfile(trivial_conf_2)); | |
315 | ConfFile cf2; | |
316 | ASSERT_EQ(cf2.parse_file(trivial_conf_2_f.c_str(), &err, &warn), 0); | |
317 | ASSERT_EQ(err.size(), 1U); | |
318 | ||
319 | bufferlist bl3; | |
320 | bl3.append(trivial_conf_3, strlen(trivial_conf_3)); | |
321 | ConfFile cf3; | |
322 | ASSERT_EQ(cf3.parse_bufferlist(&bl3, &err, &warn), 0); | |
323 | ASSERT_EQ(err.size(), 0U); | |
324 | ASSERT_EQ(cf3.read("global", "log dir", val), 0); | |
325 | ASSERT_EQ(val, "barfoo"); | |
326 | ||
327 | std::string trivial_conf_4_f(next_tempfile(trivial_conf_4)); | |
328 | ConfFile cf4; | |
329 | ASSERT_EQ(cf4.parse_file(trivial_conf_4_f.c_str(), &err, &warn), 0); | |
330 | ASSERT_EQ(err.size(), 0U); | |
331 | ASSERT_EQ(cf4.read("global", "log dir", val), 0); | |
332 | ASSERT_EQ(val, "barbaz"); | |
333 | } | |
334 | ||
335 | TEST(ConfUtils, ParseFiles1) { | |
336 | std::deque<std::string> err; | |
337 | std::ostringstream warn; | |
338 | std::string simple_conf_1_f(next_tempfile(simple_conf_1)); | |
339 | ConfFile cf1; | |
340 | ASSERT_EQ(cf1.parse_file(simple_conf_1_f.c_str(), &err, &warn), 0); | |
341 | ASSERT_EQ(err.size(), 0U); | |
342 | ||
343 | std::string simple_conf_2_f(next_tempfile(simple_conf_1)); | |
344 | ConfFile cf2; | |
345 | ASSERT_EQ(cf2.parse_file(simple_conf_2_f.c_str(), &err, &warn), 0); | |
346 | ASSERT_EQ(err.size(), 0U); | |
347 | ||
348 | bufferlist bl3; | |
349 | bl3.append(simple_conf_1, strlen(simple_conf_1)); | |
350 | ConfFile cf3; | |
351 | ASSERT_EQ(cf3.parse_bufferlist(&bl3, &err, &warn), 0); | |
352 | ASSERT_EQ(err.size(), 0U); | |
353 | ||
354 | bufferlist bl4; | |
355 | bl4.append(simple_conf_2, strlen(simple_conf_2)); | |
356 | ConfFile cf4; | |
357 | ASSERT_EQ(cf4.parse_bufferlist(&bl4, &err, &warn), 0); | |
358 | ASSERT_EQ(err.size(), 0U); | |
359 | } | |
360 | ||
361 | TEST(ConfUtils, ReadFiles1) { | |
362 | std::deque<std::string> err; | |
363 | std::ostringstream warn; | |
364 | std::string simple_conf_1_f(next_tempfile(simple_conf_1)); | |
365 | ConfFile cf1; | |
366 | ASSERT_EQ(cf1.parse_file(simple_conf_1_f.c_str(), &err, &warn), 0); | |
367 | ASSERT_EQ(err.size(), 0U); | |
368 | ||
369 | std::string val; | |
370 | ASSERT_EQ(cf1.read("global", "keyring", val), 0); | |
371 | ASSERT_EQ(val, ".my_ceph_keyring"); | |
372 | ||
373 | ASSERT_EQ(cf1.read("mds", "profiling logger dir", val), 0); | |
374 | ASSERT_EQ(val, "wowsers"); | |
375 | ||
376 | ASSERT_EQ(cf1.read("mds", "something that does not exist", val), -ENOENT); | |
377 | ||
378 | // exists in mds section, but not in global | |
379 | ASSERT_EQ(cf1.read("global", "profiling logger dir", val), -ENOENT); | |
380 | ||
381 | bufferlist bl2; | |
382 | bl2.append(simple_conf_2, strlen(simple_conf_2)); | |
383 | ConfFile cf2; | |
384 | ASSERT_EQ(cf2.parse_bufferlist(&bl2, &err, &warn), 0); | |
385 | ASSERT_EQ(err.size(), 0U); | |
386 | ASSERT_EQ(cf2.read("osd0", "keyring", val), 0); | |
387 | ASSERT_EQ(val, "osd_keyring"); | |
388 | ||
389 | ASSERT_EQ(cf2.read("mds", "pid file", val), 0); | |
390 | ASSERT_EQ(val, "foo2"); | |
391 | ASSERT_EQ(cf2.read("nonesuch", "keyring", val), -ENOENT); | |
392 | } | |
393 | ||
394 | TEST(ConfUtils, ReadFiles2) { | |
395 | std::deque<std::string> err; | |
396 | std::ostringstream warn; | |
397 | std::string conf3_f(next_tempfile(conf3)); | |
398 | ConfFile cf1; | |
399 | std::string val; | |
400 | ASSERT_EQ(cf1.parse_file(conf3_f.c_str(), &err, &warn), 0); | |
401 | ASSERT_EQ(err.size(), 0U); | |
402 | ASSERT_EQ(cf1.read("global", "log file", val), 0); | |
403 | ASSERT_EQ(val, "/quite/a/long/path/for/a/log/file"); | |
404 | ASSERT_EQ(cf1.read("global", "pid file", val), 0); | |
405 | ASSERT_EQ(val, "spork"); | |
406 | ||
407 | std::string unicode_config_1f(next_tempfile(unicode_config_1)); | |
408 | ConfFile cf2; | |
409 | ASSERT_EQ(cf2.parse_file(unicode_config_1f.c_str(), &err, &warn), 0); | |
410 | ASSERT_EQ(err.size(), 0U); | |
411 | ASSERT_EQ(cf2.read("global", "log file", val), 0); | |
412 | ASSERT_EQ(val, "\x66\xd1\x86\xd1\x9d\xd3\xad\xd3\xae"); | |
413 | } | |
414 | ||
415 | TEST(ConfUtils, IllegalFiles) { | |
416 | std::deque<std::string> err; | |
417 | std::ostringstream warn; | |
418 | std::string illegal_conf1_f(next_tempfile(illegal_conf1)); | |
419 | ConfFile cf1; | |
420 | ASSERT_EQ(cf1.parse_file(illegal_conf1_f.c_str(), &err, &warn), 0); | |
421 | ASSERT_EQ(err.size(), 1U); | |
422 | ||
423 | bufferlist bl2; | |
424 | bl2.append(illegal_conf2, strlen(illegal_conf2)); | |
425 | ConfFile cf2; | |
426 | ASSERT_EQ(cf2.parse_bufferlist(&bl2, &err, &warn), 0); | |
427 | ASSERT_EQ(err.size(), 1U); | |
428 | ||
429 | std::string illegal_conf3_f(next_tempfile(illegal_conf3)); | |
430 | ConfFile cf3; | |
431 | ASSERT_EQ(cf3.parse_file(illegal_conf3_f.c_str(), &err, &warn), 0); | |
432 | ASSERT_EQ(err.size(), 1U); | |
433 | ||
434 | std::string illegal_conf4_f(next_tempfile(illegal_conf4)); | |
435 | ConfFile cf4; | |
436 | ASSERT_EQ(cf4.parse_file(illegal_conf4_f.c_str(), &err, &warn), 0); | |
437 | ASSERT_EQ(err.size(), 1U); | |
438 | ||
439 | std::string illegal_conf5_f(next_tempfile(illegal_conf5)); | |
440 | ConfFile cf5; | |
441 | ASSERT_EQ(cf5.parse_file(illegal_conf5_f.c_str(), &err, &warn), 0); | |
442 | ASSERT_EQ(err.size(), 1U); | |
443 | } | |
444 | ||
445 | TEST(ConfUtils, EscapingFiles) { | |
446 | std::deque<std::string> err; | |
447 | std::ostringstream warn; | |
448 | std::string escaping_conf_1_f(next_tempfile(escaping_conf_1)); | |
449 | ConfFile cf1; | |
450 | std::string val; | |
451 | ASSERT_EQ(cf1.parse_file(escaping_conf_1_f.c_str(), &err, &warn), 0); | |
452 | ASSERT_EQ(err.size(), 0U); | |
453 | ||
454 | ASSERT_EQ(cf1.read("global", "log file", val), 0); | |
455 | ASSERT_EQ(val, "the \"scare quotes\""); | |
456 | ASSERT_EQ(cf1.read("global", "pid file", val), 0); | |
457 | ASSERT_EQ(val, "a pid file"); | |
458 | ASSERT_EQ(cf1.read("mon", "keyring", val), 0); | |
459 | ASSERT_EQ(val, "nested \"quotes\""); | |
460 | ||
461 | std::string escaping_conf_2_f(next_tempfile(escaping_conf_2)); | |
462 | ConfFile cf2; | |
463 | ASSERT_EQ(cf2.parse_file(escaping_conf_2_f.c_str(), &err, &warn), 0); | |
464 | ASSERT_EQ(err.size(), 0U); | |
465 | ||
466 | ASSERT_EQ(cf2.read("apple ][", "log file", val), 0); | |
467 | ASSERT_EQ(val, "floppy disk"); | |
468 | ASSERT_EQ(cf2.read("mon", "keyring", val), 0); | |
469 | ASSERT_EQ(val, "backslash\\"); | |
470 | } | |
471 | ||
472 | TEST(ConfUtils, Overrides) { | |
473 | md_config_t conf; | |
474 | std::ostringstream warn; | |
475 | std::string override_conf_1_f(next_tempfile(override_config_1)); | |
476 | ||
477 | conf.name.set(CEPH_ENTITY_TYPE_MON, "0"); | |
478 | conf.parse_config_files(override_conf_1_f.c_str(), &warn, 0); | |
479 | ASSERT_EQ(conf.parse_errors.size(), 0U); | |
480 | ASSERT_EQ(conf.log_file, "global_log"); | |
481 | ||
482 | conf.name.set(CEPH_ENTITY_TYPE_MDS, "a"); | |
483 | conf.parse_config_files(override_conf_1_f.c_str(), &warn, 0); | |
484 | ASSERT_EQ(conf.parse_errors.size(), 0U); | |
485 | ASSERT_EQ(conf.log_file, "mds_log"); | |
486 | ||
487 | conf.name.set(CEPH_ENTITY_TYPE_OSD, "0"); | |
488 | conf.parse_config_files(override_conf_1_f.c_str(), &warn, 0); | |
489 | ASSERT_EQ(conf.parse_errors.size(), 0U); | |
490 | ASSERT_EQ(conf.log_file, "osd0_log"); | |
491 | } | |
492 | ||
493 | TEST(ConfUtils, DupKey) { | |
494 | md_config_t conf; | |
495 | std::ostringstream warn; | |
496 | std::string dup_key_config_f(next_tempfile(dup_key_config_1)); | |
497 | ||
498 | conf.name.set(CEPH_ENTITY_TYPE_MDS, "a"); | |
499 | conf.parse_config_files(dup_key_config_f.c_str(), &warn, 0); | |
500 | ASSERT_EQ(conf.parse_errors.size(), 0U); | |
501 | ASSERT_EQ(conf.log_file, string("3")); | |
502 | } | |
503 | ||
504 |