1 /***************************************************************
2 Copyright (C) 2010-2011 Hewlett-Packard Development Company, L.P.
3 Copyright (C) 2015 Siemens AG
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 version 2 as published by the Free Software Foundation.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 ***************************************************************/
19
20 /*!
21 * \file
22 * \brief Scheduler API for agents.
23 */
24
25 /* local includes */
26 #include "libfossscheduler.h"
27 #include "libfossdb.h"
28 #include "fossconfig.h"
29
30 /* unix includes */
31 #include <stdio.h>
32 #include <getopt.h>
33 #include <libgen.h>
34 #include <glib.h>
35
36 /* ************************************************************************** */
37 /* **** Locals ************************************************************** */
38 /* ************************************************************************** */
39
40 volatile gint items_processed; ///< The number of items processed by the agent
41 volatile int alive; ///< If the agent has updated with a hearbeat
42 char buffer[2048]; ///< The last thing received from the scheduler
43 int valid; ///< If the information stored in buffer is valid
44 int sscheduler; ///< Whether the agent was started by the scheduler
45 int userID; ///< The id of the user that created the job
46 int groupID; ///< The id of the group of the user that created the job
47 int jobId; ///< The id of the job
48 char* module_name = NULL; ///< The name of the agent
49
50 /** Check for an agent in DB */
51 const static char* sql_check = "\
52 SELECT * FROM agent \
53 WHERE agent_name = '%s' AND agent_rev='%s.%s'";
54
55 /** Insert new agent in DB */
56 const static char* sql_insert = "\
57 INSERT INTO agent (agent_name, agent_rev, agent_desc) \
58 VALUES ('%s', '%s.%s', '%s')";
59
60 /** System configuration settings */
61 fo_conf* sysconfig = NULL;
62 /** System configuration directory */
63 char* sysconfigdir = NULL;
64
65 /* these will be freed in fo_scheduler_disconnect */
66 extern GRegex* fo_conf_parse;
67 extern GRegex* fo_conf_replace;
68
69 /**
70 * Global verbose flags that agents should use instead of specific verbose
71 * flags. This is used by the scheduler to turn verbose on a particular agent
72 * on during run time. When the verbose flag is turned on by the scheduler
73 * the on_verbose function will be called. If nothing needs to be done when
74 * verbose is turned on, simply pass NULL to scheduler_connect
75 */
76 int agent_verbose;
77
78 /**
79 * @brief Internal function to send a heartbeat to the
80 * scheduler along with the number of items processed.
81 *
82 * \note Agents should NOT call this function directly.
83 * \note This is the alarm SIGALRM function.
84 * @return void
85 * @todo These functions are not safe for a signal handler
86 */
fo_heartbeat()87 void fo_heartbeat()
88 {
89 int processed = g_atomic_int_get(&items_processed);
90 // TODO these functions are not safe for a signal handler
91 fprintf(stdout, "HEART: %d %d\n", processed, alive);
92 fflush(stdout);
93 fflush(stderr);
94 alarm(ALARM_SECS);
95 alive = FALSE;
96 }
97
98 /**
99 * @brief Checks that the agent is already in the agent table.
100 *
101 * This uses the VERSION and COMMIT_HASH in the system configuration information to
102 * determine if a new agent record needs to be created for this agent in the
103 * database.
104 */
fo_check_agentdb(PGconn * db_conn)105 void fo_check_agentdb(PGconn* db_conn)
106 {
107 PGresult* db_result = NULL;
108 char* db_sql = NULL;
109
110 db_sql = g_strdup_printf(sql_check, module_name,
111 fo_config_get(sysconfig, module_name, "VERSION", NULL),
112 fo_config_get(sysconfig, module_name, "COMMIT_HASH", NULL));
113 db_result = PQexec(db_conn, db_sql);
114 if (PQresultStatus(db_result) != PGRES_TUPLES_OK)
115 {
116 fprintf(stderr, "FATAL %s.%d: unable to check agent table: %s",
117 __FILE__, __LINE__, PQresultErrorMessage(db_result));
118 fflush(stderr);
119 PQfinish(db_conn);
120 PQclear(db_result);
121 g_free(db_sql);
122 exit(252);
123 }
124
125 if (PQntuples(db_result) != 1)
126 {
127 g_free(db_sql);
128 PQclear(db_result);
129
130 db_sql = g_strdup_printf(sql_insert, module_name,
131 fo_config_get(sysconfig, module_name, "VERSION", NULL),
132 fo_config_get(sysconfig, module_name, "COMMIT_HASH", NULL),
133 fo_config_get(sysconfig, module_name, "DESCRIPTION", NULL));
134 db_result = PQexec(db_conn, db_sql);
135 if (PQresultStatus(db_result) != PGRES_COMMAND_OK)
136 {
137 fprintf(stderr, "FATAL %s.%d: unable to insert into agent table: %s",
138 __FILE__, __LINE__, PQresultErrorMessage(db_result));
139 fflush(stderr);
140 PQfinish(db_conn);
141 PQclear(db_result);
142 g_free(db_sql);
143 exit(251);
144 }
145 }
146
147 g_free(db_sql);
148 PQclear(db_result);
149 }
150
151 /* ************************************************************************** */
152 /* **** Global Functions **************************************************** */
153 /* ************************************************************************** */
154
155 /**
156 * @brief This function must be called by agents to let the scheduler know they
157 * are alive and how many items they have processed.
158 *
159 * @param i This is the number of itmes processed since the last call to
160 * fo_scheduler_heart()
161 *
162 * @return void
163 */
fo_scheduler_heart(int i)164 void fo_scheduler_heart(int i)
165 {
166 g_atomic_int_add(&items_processed, i);
167 alive = TRUE;
168
169 fflush(stdout);
170 fflush(stderr);
171 }
172
173 /**
174 * @brief Establish a connection between an agent and the scheduler.
175 * @param[in] argc Command line agrument count
176 * @param[in] argv Command line agrument vector
177 * @param[out] db_conn DB Connection
178 * @param[out] db_conf DB conf file
179 * @sa fo_scheduler_connect()
180 */
fo_scheduler_connect_conf(int * argc,char ** argv,PGconn ** db_conn,char ** db_conf)181 void fo_scheduler_connect_conf(int* argc, char** argv, PGconn** db_conn, char** db_conf)
182 {
183 /* locals */
184 GError* error = NULL;
185 GOptionContext* parsed;
186 fo_conf* version;
187 char fname[FILENAME_MAX + 1];
188
189 char* db_config = NULL;
190 char* db_error = NULL;
191
192 /* command line options */
193 GOptionEntry options[] =
194 {
195 {"config", 'c', 0, G_OPTION_ARG_STRING, &sysconfigdir, ""},
196 {"userID", 0, 0, G_OPTION_ARG_INT, &userID, ""},
197 {"groupID", 0, 0, G_OPTION_ARG_INT, &groupID, ""},
198 {"scheduler_start", 0, 0, G_OPTION_ARG_NONE, &sscheduler, ""},
199 {"jobId", 0, 0, G_OPTION_ARG_INT, &jobId, ""},
200 {NULL}
201 };
202
203 /* initialize memory associated with agent connection */
204 module_name = g_strdup(basename(argv[0]));
205 sysconfigdir = DEFAULT_SETUP;
206 items_processed = 0;
207 valid = 0;
208 sscheduler = 0;
209 userID = -1;
210 groupID = -1;
211 agent_verbose = 0;
212 memset(buffer, 0, sizeof(buffer));
213
214 if (sysconfig != NULL)
215 {
216 LOG_WARNING("fo_scheduler_connect() has already been called.");
217 sscheduler = 1;
218 return;
219 }
220
221 if (getenv("FO_SYSCONFDIR"))
222 sysconfigdir = getenv("FO_SYSCONFDIR");
223
224 /* parse command line options */
225 parsed = g_option_context_new("");
226 g_option_context_add_main_entries(parsed, options, NULL);
227 g_option_context_set_ignore_unknown_options(parsed, TRUE);
228 g_option_context_set_help_enabled(parsed, FALSE);
229 g_option_context_parse(parsed, argc, &argv, NULL);
230 g_option_context_free(parsed);
231
232 /* load system configuration */
233 if (sysconfigdir)
234 {
235 snprintf(fname, FILENAME_MAX, "%s/%s", sysconfigdir, "fossology.conf");
236 sysconfig = fo_config_load(fname, &error);
237 if (error)
238 {
239 fprintf(stderr, "FATAL %s.%d: unable to open system configuration: %s\n",
240 __FILE__, __LINE__, error->message);
241 exit(255);
242 }
243
244 snprintf(fname, FILENAME_MAX, "%s/mods-enabled/%s/VERSION",
245 sysconfigdir, module_name);
246
247 version = fo_config_load(fname, &error);
248 if (error)
249 {
250 fprintf(stderr, "FATAL %s.%d: unable to open VERSION configuration: %s\n",
251 __FILE__, __LINE__, error->message);
252 exit(254);
253 }
254
255 fo_config_join(sysconfig, version, &error);
256 if (error)
257 {
258 fprintf(stderr, "FATAL %s.%d: unable to join configuration files: %s\n",
259 __FILE__, __LINE__, error->message);
260 exit(250);
261 }
262
263
264 fo_config_free(version);
265 }
266
267 /* create the database connection */
268 if (db_conn)
269 {
270 db_config = g_strdup_printf("%s/Db.conf", sysconfigdir);
271 (*db_conn) = fo_dbconnect(db_config, &db_error);
272 if (db_conf)
273 *db_conf = db_config;
274 else
275 g_free(db_config);
276 if (db_error)
277 {
278 fprintf(stderr, "FATAL %s.%d: unable to open database connection: %s\n",
279 __FILE__, __LINE__, db_error);
280 fflush(stderr);
281 exit(253);
282 }
283 }
284
285 /* send "OK" to the scheduler */
286 if (sscheduler)
287 {
288 /* check that the agent record exists */
289 if (db_conn)
290 fo_check_agentdb(*db_conn);
291
292 if (fo_config_has_key(sysconfig, module_name, "VERSION"))
293 fprintf(stdout, "VERSION: %s\n",
294 fo_config_get(sysconfig, module_name, "VERSION", &error));
295 else fprintf(stdout, "VERSION: unknown\n");
296 fprintf(stdout, "\nOK\n");
297 fflush(stdout);
298
299 /* set up the heartbeat() */
300 signal(SIGALRM, fo_heartbeat);
301 alarm(ALARM_SECS);
302 }
303
304 fflush(stdout);
305 fflush(stderr);
306
307 alive = TRUE;
308 }
309
310 /**
311 * @brief Establish a connection between an agent and the scheduler.
312 *
313 * Steps taken by this function:
314 * - initialize memory associated with agent connection
315 * - send "SPAWNED" to the scheduler
316 * - receive the number of items between notifications
317 * - check the nfs mounts for the agent
318 * - set up the heartbeat()
319 *
320 * Making a call to this function should be the first thing that an agent does
321 * after parsing its command line arguments.
322 *
323 * If the database connection passed is NULL, then this will not return a
324 * database connection, and will not check the agent's database record.
325 *
326 * @param argc pointer to the number of arguments passed to the agent
327 * @param argv the command line arguments for the agent
328 * @param db_conn pointer to the location for the database connection
329 * @return void
330 */
fo_scheduler_connect(int * argc,char ** argv,PGconn ** db_conn)331 void fo_scheduler_connect(int* argc, char** argv, PGconn** db_conn)
332 {
333 fo_scheduler_connect_conf(argc, argv, db_conn, NULL);
334 }
335
336 /**
337 * @brief Make a connection from an agent to the scheduler and create a DB
338 * manager as well.
339 * @param[out] dbManager New DB manager
340 */
fo_scheduler_connect_dbMan(int * argc,char ** argv,fo_dbManager ** dbManager)341 void fo_scheduler_connect_dbMan(int* argc, char** argv, fo_dbManager** dbManager)
342 {
343 char* dbConf;
344 PGconn* dbConnection;
345 fo_scheduler_connect_conf(argc, argv, &dbConnection, &dbConf);
346 *dbManager = fo_dbManager_new_withConf(dbConnection, dbConf);
347 free(dbConf);
348 }
349
350 /**
351 * @brief Disconnect the scheduler connection.
352 *
353 * Making a call to this function should be the last thing that an agent does
354 * before exiting. Any error reporting to stdout or stderr will not work after
355 * this function has finished execution.
356 * @param retcode Return code to the scheduler
357 */
fo_scheduler_disconnect(int retcode)358 void fo_scheduler_disconnect(int retcode)
359 {
360 if (module_name != NULL) {
361 /* send "CLOSED" to the scheduler */
362 if (sscheduler)
363 {
364 fo_heartbeat();
365 fprintf(stdout, "\nBYE %d\n", retcode);
366 fflush(stdout);
367
368 valid = 0;
369 sscheduler = 0;
370
371 g_free(module_name);
372 }
373
374 if (strcmp(sysconfigdir, DEFAULT_SETUP))
375 g_free(sysconfigdir);
376
377 fo_config_free(sysconfig);
378 }
379 g_regex_unref(fo_conf_parse);
380 g_regex_unref(fo_conf_replace);
381
382 sysconfigdir = NULL;
383 sysconfig = NULL;
384 fo_conf_parse = NULL;
385 fo_conf_replace = NULL;
386 module_name = NULL;
387
388 fflush(stdout);
389 fflush(stderr);
390 }
391
392 /**
393 * @brief Get the next data to process from the scheduler.
394 *
395 * It is the job of the agent to decide how this string is
396 * interpreted.
397 *
398 * Steps taken by this function:
399 * - get the next line from the scheduler
400 * - if the scheduler has paused this agent this will block till unpaused
401 * - check for "CLOSE" from scheduler, return NULL if received
402 * - check for "VERBOSE" from scheduler
403 * - if this is received turn the verbose flag to whatever is specified
404 * - a new line must be received, perform same task (i.e. recursive call)
405 * - check for "END" from scheduler, if received print OK and recurse
406 * - this is used to simplify communications within the scheduler
407 * - return whatever has been received
408 *
409 * @return char* for the next thing to analyze, NULL if there is nothing
410 * left in this job, in which case the agent should close
411 */
fo_scheduler_next()412 char* fo_scheduler_next()
413 {
414 fflush(stdout);
415
416 /* get the next line from the scheduler and possibly WAIT */
417 while (fgets(buffer, sizeof(buffer), stdin) != NULL)
418 {
419 if (agent_verbose)
420 printf("\nNOTE: received %s\n", buffer);
421 if (strncmp(buffer, "CLOSE", 5) == 0)
422 break;
423 if (strncmp(buffer, "END", 3) == 0)
424 {
425 fprintf(stdout, "\nOK\n");
426 fflush(stdout);
427 fflush(stderr);
428 valid = 0;
429 continue;
430 }
431 else if (strncmp(buffer, "VERBOSE", 7) == 0)
432 {
433 agent_verbose = atoi(&buffer[8]);
434 valid = 0;
435 continue;
436 }
437 else if (strncmp(buffer, "VERSION", 7) == 0)
438 {
439 if (fo_config_has_key(sysconfig, module_name, "VERSION"))
440 fprintf(stdout, "VERSION: %s\n",
441 fo_config_get(sysconfig, module_name, "VERSION", NULL));
442 else fprintf(stdout, "VERSION: unknown\n");
443 fflush(stdout);
444 fflush(stderr);
445 valid = 0;
446 continue;
447 }
448
449 buffer[strlen(buffer) - 1] = '\0';
450 valid = 1;
451 return buffer;
452 }
453
454 valid = 0;
455
456 fflush(stdout);
457 fflush(stderr);
458
459 return NULL;
460 }
461
462 /**
463 * @brief Get the last read string from the scheduler.
464 *
465 * @return Returns the string buffer if it is valid.
466 * If it is not valid, return NULL.
467 * The buffer is not valid if the last received data from the scheduler
468 * was a command, rather than data to operate on.
469 */
fo_scheduler_current()470 char* fo_scheduler_current()
471 {
472 return valid ? buffer : NULL;
473 }
474
475 /**
476 * @brief Sets something special about the agent within the scheduler.
477 *
478 * Possible Options:
479 * `SPECIAL_NOKILL`: instruct the scheduler to not kill the agent
480 *
481 * @param option the option to set
482 * @param value whether to set the option to true or false
483 */
fo_scheduler_set_special(int option,int value)484 void fo_scheduler_set_special(int option, int value)
485 {
486 fprintf(stdout, "SPECIAL: %d %d\n", option, value);
487 fflush(stdout);
488 }
489
490 /**
491 * @brief Gets if a particular special attribute is set in the scheduler.
492 *
493 * Possible Options:
494 * - `SPECIAL_NOKILL` : the agent will not be killed if it stops updating status
495 * - `SPECIAL_EXCLUSIVE`: the agent cannot run simultaneously with any other agent
496 * - `SPECIAL_NOEMAIL` : the scheduler will not send notification emails for this agent
497 * - `SPECIAL_LOCAL` : the agent is required to run on the same machine as the scheduler
498 *
499 * @param option the relevant option to the get the value of
500 * @return if the value of the special option was true
501 */
fo_scheduler_get_special(int option)502 int fo_scheduler_get_special(int option)
503 {
504 int value;
505
506 fprintf(stdout, "GETSPECIAL: %d\n", option);
507 fflush(stdout);
508
509 if (fscanf(stdin, "VALUE: %d\n", &value) != 1)
510 return 0;
511 return value;
512 }
513
514 /**
515 * @brief Gets the id of the job that the agent is running
516 *
517 * @return the job id
518 */
fo_scheduler_jobId()519 int fo_scheduler_jobId()
520 {
521 return jobId;
522 }
523
524 /**
525 * @brief Gets the id of the user that created the job that the agent is running
526 *
527 * @return the user id
528 */
fo_scheduler_userID()529 int fo_scheduler_userID()
530 {
531 return userID;
532 }
533
534 /**
535 * @brief Gets the id of the group that created the job that the agent is running
536 *
537 * @return the group id
538 */
fo_scheduler_groupID()539 int fo_scheduler_groupID()
540 {
541 return groupID;
542 }
543
544 /**
545 * @brief gets a system configuration variable from the configuration data.
546 *
547 * This function should be called after fo_scheduler_connect has been called.
548 * This is because the configuration data it not loaded until after that.
549 *
550 * @param sectionname the group of the variable
551 * @param variablename the name of the variable
552 * @return the value of the variable
553 */
fo_sysconfig(const char * sectionname,const char * variablename)554 char* fo_sysconfig(const char* sectionname, const char* variablename)
555 {
556 GError* error = NULL;
557 char* ret;
558
559 ret = fo_config_get(
560 sysconfig,
561 sectionname,
562 variablename,
563 &error);
564
565 fflush(stdout);
566 fflush(stderr);
567
568 if (error)
569 {
570 g_clear_error(&error);
571 ret = NULL;
572 }
573
574 return ret;
575 }
576