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