PageRenderTime 50ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/bin/linz_bde_uploader.pl

https://github.com/linz/linz_bde_uploader
Perl | 510 lines | 445 code | 44 blank | 21 comment | 33 complexity | 3274b919a17e61e95524303e8f1516d9 MD5 | raw file
  1. #!/usr/bin/env perl
  2. ################################################################################
  3. #
  4. # linz_bde_uploader - LINZ BDE uploader for PostgreSQL
  5. #
  6. # Copyright 2016 Crown copyright (c)
  7. # Land Information New Zealand and the New Zealand Government.
  8. # All rights reserved
  9. #
  10. # This program is released under the terms of the new BSD license. See the
  11. # LICENSE file for more information.
  12. #
  13. ################################################################################
  14. # Script to upload BDE files to the postgres database. Reads options from
  15. # linz_bde_uploader.conf located in the configuration directory
  16. ################################################################################
  17. use strict;
  18. use warnings;
  19. our $VERSION = '@@VERSION@@';
  20. use File::Basename qw( dirname );
  21. use FindBin;
  22. use lib $FindBin::Bin;
  23. use lib "$FindBin::Bin/../lib";
  24. use Getopt::Long;
  25. use Log::Log4perl qw(:easy :levels get_logger);
  26. use Log::Log4perl::Layout;
  27. use Try::Tiny;
  28. use LINZ::BdeUpload;
  29. use LINZ::Config;
  30. $SIG{INT} = \&signal_handler;
  31. $SIG{TERM} = \&signal_handler;
  32. #log levels
  33. my %LOG_LEVELS =
  34. (
  35. OFF => $OFF,
  36. FATAL => $FATAL,
  37. ERROR => $ERROR,
  38. WARN => $WARN,
  39. INFO => $INFO,
  40. DEBUG => $DEBUG,
  41. TRACE => $TRACE,
  42. ALL => $ALL
  43. );
  44. # Main program controls
  45. my $do_purge_old = 0; # Clean up old jobs if set
  46. my $do_remove_zombie = 0; # Clean up zombied jobs if set
  47. my $apply_level0 = 0; # Do level 0 updates if set
  48. my $apply_level0_inc = 0; # Do level 0 incremental load if set
  49. my $apply_level5 = 0; # Do level 5 updates is set
  50. my $rebuild = 0; # Do level 5 updates is set
  51. my $l0_timeout = 0; # Maximum time for level0 updates
  52. my $l5_timeout = 0; # Maximum time for level5 updates
  53. my $skip_postupload = 0; # Skip post upload tasks
  54. my $dry_run = 0; # Dry run only - print out files to be updated
  55. my $verbose = 0; # Dry run only - print out files to be updated
  56. my $keep_files = 0; # Keep working files - for testing
  57. my $cfgext = ''; # Alternative configuration
  58. my $cfgpath = '~/config'; # Configuration path
  59. my $showhelp = 0; # Show help
  60. my $override_locks = 0; # Clear existing locks
  61. my $listing_file = '';
  62. my $enddate = ''; # Only use files before this date
  63. my $maintain_db = 0; # run database maintain after run.
  64. my $enable_hooks = 0; # if enabled will run any event hooks defined in the config
  65. my $print_version = 0;
  66. my $log_level = undef;
  67. my $status = 0;
  68. my $logger;
  69. my $upload;
  70. GetOptions (
  71. "help|h" => \$showhelp,
  72. "config-extension|x=s" => \$cfgext,
  73. "config-path|c=s" => \$cfgpath,
  74. "purge|p!" => \$do_purge_old,
  75. "remove-zombie|z!" => \$do_remove_zombie,
  76. "skip-postupload-tasks!" => \$skip_postupload,
  77. "full|f!" => \$apply_level0,
  78. "full-incremental|j!" => \$apply_level0_inc,
  79. "incremental|i!" => \$apply_level5,
  80. "rebuild|r!" => \$rebuild,
  81. "dry-run|d!" => \$dry_run,
  82. "full-timeout|t=f" => \$l0_timeout,
  83. "inc-timeout|u=f" => \$l5_timeout,
  84. "override-locks|o" => \$override_locks,
  85. "keep-files|k" => \$keep_files,
  86. "before|b=s" => \$enddate,
  87. "maintain-database|m" => \$maintain_db,
  88. "listing_file|l=s" => \$listing_file,
  89. "enable-hooks|e!" => \$enable_hooks,
  90. "verbose|v" => \$verbose,
  91. "log-level=s" => \$log_level,
  92. "version" => \$print_version,
  93. )
  94. || help(0, *STDERR, 1);
  95. help(1, *STDOUT, 0) if $showhelp;
  96. if ($print_version)
  97. {
  98. print "$VERSION\n";
  99. exit(0);
  100. }
  101. if (defined $log_level && !exists $LOG_LEVELS{$log_level})
  102. {
  103. print "Log level must be one of " . join(', ', keys %LOG_LEVELS) . "\n";
  104. help(0);
  105. }
  106. if($apply_level0_inc && !$apply_level0)
  107. {
  108. $apply_level0 = 1;
  109. }
  110. if($apply_level0_inc && $rebuild)
  111. {
  112. # See https://github.com/linz/linz-bde-uploader/issues/116
  113. print STDERR "-full-incremental and -rebuild are contraddictory, use one or the other\n";
  114. help(0, *STDERR, 1);
  115. }
  116. if( ! $apply_level0 && ! $apply_level5 && ! $do_purge_old && ! $do_remove_zombie && ! $rebuild)
  117. {
  118. # NOTE: $apply_level0_inc implies $apply_level0
  119. print STDERR "Need at least one option of -full, -incremental, -full-incremental, -purge, or -remove-zombie\n";
  120. help(0, *STDERR, 1);
  121. }
  122. $enddate .= '000000' if $enddate =~ /^\d{8}$/;
  123. if( $enddate && $enddate !~ /^\d{14}$/ )
  124. {
  125. print "Invalid value $enddate for --before - must be yyyymmdd or yyyymmddhhmmss\n";
  126. help(0);
  127. }
  128. if( $rebuild && ! $apply_level0 )
  129. {
  130. $apply_level0 = 1;
  131. $apply_level5 = 1;
  132. }
  133. try
  134. {
  135. my $options =
  136. {
  137. _configpath=>$cfgpath,
  138. _configextra=>$cfgext,
  139. verbose=>$verbose,
  140. max_level0_runtime_hours=>$l0_timeout,
  141. max_level5_runtime_hours=>$l5_timeout,
  142. override_locks => $override_locks,
  143. rebuild => $rebuild,
  144. apply_level0 => $apply_level0,
  145. apply_level0_inc => $apply_level0_inc,
  146. apply_level5 => $apply_level5,
  147. skip_postupload_tasks => $skip_postupload,
  148. keep_files => $keep_files,
  149. end_date => $enddate,
  150. maintain_db => $maintain_db,
  151. select_tables => join(' ',@ARGV),
  152. enable_hooks => $enable_hooks,
  153. log_level => $log_level,
  154. };
  155. my $cfg = new LINZ::Config($options);
  156. my $log_config;
  157. # turn off config logging if doing a dry run.
  158. my $log_layout_string = "%d %p> %F{1}:%L - %m%n";
  159. my $layout = Log::Log4perl::Layout::PatternLayout->new($log_layout_string);
  160. if ($dry_run)
  161. {
  162. Log::Log4perl->easy_init( { level => $INFO,
  163. file => "STDOUT",
  164. layout => $log_layout_string} );
  165. $logger = get_logger("");
  166. }
  167. else
  168. {
  169. my $log_config = $cfg->has('log_settings') ? $cfg->log_settings : "";
  170. if ($log_config)
  171. {
  172. if ( $log_config !~ /^[^#]?log4perl\.(root)?[Ll]ogger\s+\=\s+
  173. (FATAL|ERROR|WARN|INFO|DEBUG|TRACE|ALL)/mx )
  174. {
  175. die "log_setting within the configuration doesn't define a root logger\n";
  176. }
  177. Log::Log4perl->init(\$log_config);
  178. $logger = get_logger("");
  179. }
  180. else
  181. {
  182. Log::Log4perl->easy_init( { level => $INFO,
  183. file => "STDOUT",
  184. layout => $log_layout_string} );
  185. $logger = get_logger("");
  186. }
  187. if($listing_file)
  188. {
  189. my $file_appender = Log::Log4perl::Appender->new(
  190. "Log::Dispatch::FileRotate",
  191. name => "listing_file_log",
  192. filename => $listing_file,
  193. mode => "append",
  194. min_level => 'debug',
  195. max => 99,
  196. );
  197. $file_appender->layout($layout);
  198. $logger->add_appender( $file_appender );
  199. }
  200. }
  201. if($verbose and $log_config and not $dry_run)
  202. {
  203. my $stdout_appender = Log::Log4perl::Appender->new(
  204. "Log::Log4perl::Appender::Screen",
  205. name => "verbose_screen_log",
  206. );
  207. $stdout_appender->layout($layout);
  208. $logger->add_appender($stdout_appender);
  209. }
  210. if (defined $log_level)
  211. {
  212. $logger->level($LOG_LEVELS{$log_level});
  213. }
  214. # Set default value for bde_tables_config
  215. if ( ! $cfg->has('bde_tables_config') ) {
  216. my $bde_tables_config = dirname($cfgpath) . '/tables.conf';
  217. $cfg->bde_tables_config( $bde_tables_config );
  218. }
  219. $upload = new LINZ::BdeUpload($cfg);
  220. if(!$dry_run)
  221. {
  222. $upload->RemoveZombiedJobs if $do_remove_zombie;
  223. $upload->PurgeOldJobs if $do_purge_old;
  224. }
  225. $upload->ApplyUpdates($dry_run);
  226. }
  227. catch
  228. {
  229. if ($upload)
  230. {
  231. $upload->FireEvent('error');
  232. undef $upload;
  233. }
  234. Log::Log4perl->initialized() ? ERROR($_) : print STDERR $_;
  235. $status = 1;
  236. };
  237. INFO("Duration of job: ". runtime_duration());
  238. exit $status;
  239. sub runtime_duration
  240. {
  241. my $duration = time() - $^T;
  242. my $str;
  243. my $day;
  244. my $hour;
  245. my $min;
  246. my $sec;
  247. {
  248. use integer;
  249. $min = $duration / 60;
  250. $sec = $duration % 60;
  251. $hour = $min / 60;
  252. $min = $min % 60;
  253. $day = $hour / 24;
  254. $hour = $hour % 24;
  255. }
  256. if ($day) {
  257. $str = sprintf("%dd:%02d:%02d:%02d",$day, $hour, $min, $sec);
  258. }
  259. else
  260. {
  261. $str = sprintf("%02d:%02d:%02d", $hour, $min, $sec);
  262. }
  263. return $str;
  264. }
  265. sub signal_handler
  266. {
  267. if ($upload)
  268. {
  269. # TODO: might need to kill upload job first
  270. # $upload->KillUpload();
  271. $upload->FireEvent('error');
  272. undef $upload;
  273. }
  274. die ("Caught $_[0] signal: $!");
  275. }
  276. sub help
  277. {
  278. my($full, $stream, $exitcode) = @_;
  279. my $level = $full ? 2 : 99;
  280. my $sections = 'Syntax';
  281. require Pod::Usage;
  282. Pod::Usage::pod2usage({
  283. -verbose=>$level,
  284. -sections=>$sections,
  285. -output=>$stream,
  286. -exitval=>'NOEXIT'
  287. });
  288. exit $exitcode;
  289. }
  290. __END__
  291. =head1 linz_bde_uploader.pl
  292. Script for updating a database with BDE files generated by Landonline.
  293. =head1 Version
  294. Version: @@VERSION@@
  295. =head1 Syntax
  296. perl linz_bde_uploader.pl [options..] [tables..]
  297. If no options are given a brief help message is displayed. At least one of the
  298. -full, -incremental, -rebuild, -purge, -remove-zombie options must be supplied.
  299. If tables are included, then only those tables will be updated.
  300. The list of tables is optional and defines the subset of the tables that will
  301. be updated. Only tables defined in the configuration will be updated -
  302. any additional tables listed are silently ignored(!)
  303. Options:
  304. =over
  305. =item -config-path or -c I<cfgpath>
  306. =item -config-extension or -x I<cfgext>
  307. =item -purge or -p
  308. =item -remove-zombie or -z
  309. =item -full or -f
  310. =item -full-incremental or -j
  311. =item -incremental or -i
  312. =item -rebuild or -r
  313. =item -before or -b yyyymmdd
  314. =item -maintain-database or -m
  315. =item -dry-run or -d
  316. =item -full-timeout or -t I<timeout>
  317. =item -inc-timeout or -u I<timeout>
  318. =item -override-locks or -o
  319. =item -skip-postupload-tasks
  320. =item -listing_file or -l I<listing_file>
  321. =item -keep-files or -k
  322. =item -version
  323. =item -verbose or -v
  324. =item -log-level
  325. =item -enable-hooks or -e
  326. =item -help or -h
  327. =back
  328. =head1 Options
  329. =over
  330. =item -config-path or -c I<cfgpath>
  331. Select the configuration file that will be used. Default is
  332. ~/config/linz_bde_uploader.cfg, where ~ is the directory in which the
  333. linz_bde_uploader.pl script is located.
  334. If a file I<cfgpath>.test exists, it is parsed after all the other
  335. configuration files (including the one specified via -config-extension)
  336. allowing for override of all configuration items.
  337. =item -config-extension or -x I<cfgext>
  338. Extra configuration extension. Overrides selected configuration items with
  339. values from I<cfgpath>.I<cfgext>
  340. =item -purge or -p
  341. Purge old jobs from the database and file system. The expiry times for
  342. old jobs is defined in the configuration file. This clears locks and
  343. purges expired jobs from the system
  344. =item -remove-zombie or -z
  345. For jobs that are recorded as active but are no longer running this will release
  346. the locks, delete working directories/temp schemas, and set the job status to
  347. error
  348. =item -full or -f
  349. Apply any pending level 0 updates
  350. =item -full-incremental or -j
  351. Apply any pending level 0 updates not replacing table data, rather apply
  352. differences between the current table data and the pending level 0 data. This
  353. is useful when versioning is enabled on tables.
  354. Also works in absence of a previously populated table, but it is
  355. faster to use -full in that case, to avoid incurring in the cost of
  356. computing table difference against empty tables.
  357. =item -incremental or -i
  358. Apply any pending level 5 updates.
  359. Differences will be calculated between the current table and the new file data.
  360. Those difference will then be applied as an incremental update.
  361. Only works in presence of a previously populated table, lacking of
  362. which results in skipping the upload for that table.
  363. =item -rebuild or -r
  364. Apply the last level 0 and any subsequent level 5 updates to rebuild the
  365. tables. If -rebuild and -full are specified, then only the last level 0
  366. is loaded.
  367. =item -dry-run or -d
  368. Just list the updates that will be applied - don't actually make any changes.
  369. =item -before or -b I<date>
  370. Only use BDE files from before the specified date (entered as a string
  371. yyyymmdd or yyyymmddhhmmss). Used for testing or restoring to a previous date.
  372. =item -maintain-database or -m
  373. After a job has been successfully run and the database has been updated, the
  374. database will be garbage collected and analysed
  375. =item -full-timeout or -t I<timeout>
  376. Specify the timeout in hours for the full (level 0) updates that are applied.
  377. No level 0 jobs will be started after this time has expired.
  378. =item -inc-timeout or -u I<timeout>
  379. Specify the timeout in hours for incremental (level 5) updates.
  380. =item -override-locks or -o
  381. Override any existing locks on files when doing the update. This will
  382. also override constraints on allowing concurrent uploads.
  383. =item -skip-postupload-tasks
  384. Choose not to run any postupload tasks defined for the schema.
  385. =item -listing_file or -l I<listing_file>
  386. Specifies a file for logging. This is in addition to other log appenders set in
  387. the config file.
  388. =item -keep-files or -k
  389. Keeps the files generated during the upload rather than deleting them
  390. for debugging use.
  391. =item -enable-hooks or -e
  392. Fire any defined command line hooks in the configuration.
  393. =item -version
  394. Print the version number for the software.
  395. =item -log-level I<level>
  396. Set the logging level for the software. Will override the defined value in the
  397. config. Only useful if logging is set in config or if the verbose or
  398. -listing options are used. I<level> can be one of the following values:
  399. OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL
  400. =item -verbose or -v
  401. Specifies that messages will be sent to standard output (as well as any other
  402. log appenders set in the config)
  403. =back