1 /*
2    ctdb_io tests
3 
4    Copyright (C) Christof Schmitt 2019
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 3 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,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU 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, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "replace.h"
21 #include "system/filesys.h"
22 
23 #include <assert.h>
24 
25 #include "common/ctdb_io.c"
26 
ctdb_set_error(struct ctdb_context * ctdb,const char * fmt,...)27 void ctdb_set_error(struct ctdb_context *ctdb, const char *fmt, ...)
28 {
29 	va_list ap;
30 	va_start(ap, fmt);
31 	vprintf(fmt, ap);
32 	va_end(ap);
33 	assert(false);
34 }
35 
test_setup(ctdb_queue_cb_fn_t cb,int * pfd,struct ctdb_context ** pctdb,struct ctdb_queue ** pqueue)36 static void test_setup(ctdb_queue_cb_fn_t cb,
37 		       int *pfd,
38 		       struct ctdb_context **pctdb,
39 		       struct ctdb_queue **pqueue)
40 {
41 	int pipefd[2], ret;
42 	struct ctdb_context *ctdb;
43 	struct ctdb_queue *queue;
44 
45 	ret = pipe(pipefd);
46 	assert(ret == 0);
47 
48 	ctdb = talloc_zero(NULL, struct ctdb_context);
49 	assert(ctdb != NULL);
50 
51 	ctdb->ev = tevent_context_init(NULL);
52 
53 	queue = ctdb_queue_setup(ctdb, ctdb, pipefd[0], 0, cb,
54 				 NULL, "test queue");
55 	assert(queue != NULL);
56 
57 	*pctdb = ctdb;
58 	*pfd = pipefd[1];
59 	if (pqueue != NULL) {
60 		*pqueue = queue;
61 	}
62 }
63 
64 static const size_t test1_req_len = 8;
65 static const char *test1_req = "abcdefgh";
66 
test1_callback(uint8_t * data,size_t length,void * private_data)67 static void test1_callback(uint8_t *data, size_t length, void *private_data)
68 {
69 	uint32_t len;
70 
71 	len = *(uint32_t *)data;
72 	assert(len == sizeof(uint32_t) + test1_req_len);
73 
74 	assert(length == sizeof(uint32_t) + test1_req_len);
75 	assert(memcmp(data  + sizeof(len), test1_req, test1_req_len) == 0);
76 }
77 
test1(void)78 static void test1(void)
79 {
80 	struct ctdb_context *ctdb;
81 	int fd;
82 	ssize_t ret;
83 	uint32_t pkt_size;
84 
85 	test_setup(test1_callback, &fd, &ctdb, NULL);
86 
87 	pkt_size = sizeof(uint32_t) + test1_req_len;
88 	ret = write(fd, &pkt_size, sizeof(pkt_size));
89 	assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
90 
91 	ret = write(fd, test1_req, test1_req_len);
92 	assert(ret != -1 && (size_t)ret == test1_req_len);
93 
94 	tevent_loop_once(ctdb->ev);
95 
96 	TALLOC_FREE(ctdb);
97 }
98 
99 static const size_t test2_req_len[] = { 900, 24, 600 };
100 
101 static int test2_cb_num = 0;
102 
test2_callback(uint8_t * data,size_t length,void * private_data)103 static void test2_callback(uint8_t *data, size_t length, void *private_data)
104 {
105 	uint32_t len;
106 
107 	len = *(uint32_t *)data;
108 	assert(len == sizeof(uint32_t) + test2_req_len[test2_cb_num]);
109 	assert(length == sizeof(uint32_t) + test2_req_len[test2_cb_num]);
110 
111 	test2_cb_num++;
112 }
113 
test2(void)114 static void test2(void)
115 {
116 	struct ctdb_context *ctdb;
117 	int fd;
118 	ssize_t ret;
119 	size_t i;
120 	uint32_t pkt_size;
121 	char req[1024] = { 0 };
122 
123 	for (i = 0; i < sizeof(req); i++) {
124 		req[i] = i % CHAR_MAX;
125 	}
126 
127 	test_setup(test2_callback, &fd, &ctdb, NULL);
128 
129 	/*
130 	 * request 0
131 	 */
132 
133 	pkt_size = sizeof(uint32_t) + test2_req_len[0];
134 	ret = write(fd, &pkt_size, sizeof(pkt_size));
135 	assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
136 
137 	ret = write(fd, req, test2_req_len[0]);
138 	assert(ret != -1 && (size_t)ret == test2_req_len[0]);
139 
140 	/*
141 	 * request 1
142 	 */
143 	pkt_size = sizeof(uint32_t) + test2_req_len[1];
144 	ret = write(fd, &pkt_size, sizeof(pkt_size));
145 	assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
146 
147 	/*
148 	 * Omit the last byte to avoid buffer processing.
149 	 */
150 	ret = write(fd, req, test2_req_len[1] - 1);
151 	assert(ret != -1 && (size_t)ret == test2_req_len[1] - 1);
152 
153 	tevent_loop_once(ctdb->ev);
154 
155 	/*
156 	 * Write the missing byte now.
157 	 */
158 	ret = write(fd, &req[test2_req_len[1] - 1], 1);
159 	assert(ret != -1 && (size_t)ret == 1);
160 
161 	/*
162 	 * request 2
163 	 */
164 	pkt_size = sizeof(uint32_t) + test2_req_len[2];
165 	ret = write(fd, &pkt_size, sizeof(pkt_size));
166 	assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
167 
168 	ret = write(fd, req, test2_req_len[2]);
169 	assert(ret != -1 && (size_t)ret == test2_req_len[2]);
170 
171 	tevent_loop_once(ctdb->ev);
172 	tevent_loop_once(ctdb->ev);
173 
174 	assert(test2_cb_num == 2);
175 
176 	TALLOC_FREE(ctdb);
177 }
178 
test_cb(uint8_t * data,size_t length,void * private_data)179 static void test_cb(uint8_t *data, size_t length, void *private_data)
180 {
181 	/* dummy handler, not verifying anything */
182 	TALLOC_FREE(data);
183 }
184 
test3(void)185 static void test3(void)
186 {
187 	struct ctdb_context *ctdb;
188 	struct ctdb_queue *queue;
189 	uint32_t pkt_size;
190 	char *request;
191 	size_t req_len;
192 	int fd;
193 	ssize_t ret;
194 
195 	test_setup(test_cb, &fd, &ctdb, &queue);
196 	request = talloc_zero_size(queue, queue->buffer_size);
197 
198 	/*
199 	 * calculate a request length which will fit into the buffer
200 	 * but not twice. Because we need to write the size integer
201 	 * as well (4-bytes) we're guaranteed that no 2 packets will fit.
202 	 */
203 	req_len = queue->buffer_size >> 1;
204 
205 	/* writing first packet */
206 	pkt_size = sizeof(uint32_t) + req_len;
207 
208 	ret = write(fd, &pkt_size, sizeof(pkt_size));
209 	assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
210 
211 	ret = write(fd, request, req_len);
212 	assert(ret != -1 && (size_t)ret == req_len);
213 
214 	/* writing second, incomplete packet */
215 	pkt_size = sizeof(uint32_t) + req_len;
216 
217 	ret = write(fd, &pkt_size, sizeof(pkt_size));
218 	assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
219 
220 	ret = write(fd, request, req_len >> 1);
221 	assert(ret != -1 && (size_t)ret == req_len >> 1);
222 
223 	/* process...only 1st packet can be processed */
224 	tevent_loop_once(ctdb->ev);
225 
226 	/* we should see a progressed offset of req_len + sizeof(pkt_size) */
227 	assert(queue->buffer.offset == req_len + sizeof(pkt_size));
228 
229 	/* writing another few bytes of the still incomplete packet */
230 	ret = write(fd, request, (req_len >> 1) - 1);
231 	assert(ret != -1 && (size_t)ret == (req_len >> 1) - 1);
232 
233 	/*
234 	 * the packet is still incomplete and connot be processed
235 	 * but the packet data had to be moved in the buffer in order
236 	 * to fetch the new 199 bytes -> offset must be 0 now.
237 	 */
238 	tevent_loop_once(ctdb->ev);
239 	/*
240 	 * needs to be called twice as an incomplete packet
241 	 * does not trigger a schedule_immediate
242 	 */
243 	tevent_loop_once(ctdb->ev);
244 
245 	assert(queue->buffer.offset == 0);
246 
247 	TALLOC_FREE(ctdb);
248 }
249 
test4(void)250 static void test4(void)
251 {
252 	struct ctdb_context *ctdb;
253 	struct ctdb_queue *queue;
254 	uint32_t pkt_size;
255 	char *request;
256 	size_t req_len, half_buf_size;
257 	int fd;
258 	ssize_t ret;
259 
260 	test_setup(test_cb, &fd, &ctdb, &queue);
261 
262 	req_len = queue->buffer_size << 1; /* double the buffer size */
263 	request = talloc_zero_size(queue, req_len);
264 
265 	/* writing first part of packet exceeding standard buffer size */
266 	pkt_size = sizeof(uint32_t) + req_len;
267 
268 	ret = write(fd, &pkt_size, sizeof(pkt_size));
269 	assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
270 
271 	half_buf_size = queue->buffer_size >> 1;
272 
273 	ret = write(fd, request, req_len - half_buf_size);
274 	assert(ret != -1 && (size_t)ret == req_len - half_buf_size);
275 
276 	/*
277 	 * process...
278 	 * this needs to be done to have things changed
279 	 */
280 	tevent_loop_once(ctdb->ev);
281 	/*
282 	 * needs to be called twice as an initial incomplete packet
283 	 * does not trigger a schedule_immediate
284 	 */
285 	tevent_loop_once(ctdb->ev);
286 
287 	/* the buffer should be resized to packet size now */
288 	assert(queue->buffer.size == pkt_size);
289 
290 	/* writing remaining data */
291 	ret = write(fd, request, half_buf_size);
292 	assert(ret != -1 && (size_t)ret == half_buf_size);
293 
294 	/* process... */
295 	tevent_loop_once(ctdb->ev);
296 
297 	/*
298 	 * the buffer was increased beyond its standard size.
299 	 * once packet got processed, the buffer has to be free'd
300 	 * and will be re-allocated with standard size on new request arrival.
301 	 */
302 
303 	assert(queue->buffer.size == 0);
304 
305 	/* writing new packet to verify standard buffer size */
306 	pkt_size = sizeof(uint32_t) + half_buf_size;
307 
308 	ret = write(fd, &pkt_size, sizeof(pkt_size));
309 	assert(ret != -1 && (size_t)ret == sizeof(pkt_size));
310 
311 	ret = write(fd, request, half_buf_size);
312 	assert(ret != -1 && (size_t)ret == half_buf_size);
313 
314 	/* process... */
315 	tevent_loop_once(ctdb->ev);
316 
317 	/* back to standard buffer size */
318 	assert(queue->buffer.size == queue->buffer_size);
319 
320 	TALLOC_FREE(ctdb);
321 }
322 
main(int argc,const char ** argv)323 int main(int argc, const char **argv)
324 {
325 	int num;
326 
327 	if (argc != 2) {
328 		fprintf(stderr, "%s <testnum>\n", argv[0]);
329 		exit(1);
330 	}
331 
332 
333 	num = atoi(argv[1]);
334 	switch (num) {
335 	case 1:
336 		test1();
337 		break;
338 
339 	case 2:
340 		test2();
341 		break;
342 
343 	case 3:
344 		test3();
345 		break;
346 
347 	case 4:
348 		test4();
349 		break;
350 
351 	default:
352 		fprintf(stderr, "Unknown test number %s\n", argv[1]);
353 	}
354 
355 	return 0;
356 }
357