forked from chriselswede/hanacleaner
-
Notifications
You must be signed in to change notification settings - Fork 0
/
hanacleaner.py
2260 lines (2176 loc) · 194 KB
/
hanacleaner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
import sys, os, time, subprocess, re
from difflib import Differ
import signal
def printHelp():
print(" ")
print("DESCRIPTION: ")
print(" The HANA cleaner is a house keeping service for SAP HANA. It can be used to clean the backup catalog, diagnostic files, ")
print(" and alerts and to compress the backup logs. It should be executed by <sid>adm or, in case you use a CRON job, with the same ")
print(" environment as the <sid>adm. See SAP Note 2399996 and SAP Note 2400024. ")
print(" ")
print("INPUT ARGUMENTS: ")
print(" ---- BACKUP ENTRIES in BACKUP CATALOG (and possibly BACKUPS) ---- ")
print(" Note: In case you use Azure Backup you might want to check out this first ")
print(" https://answers.sap.com/questions/13241352/manage-or-clean-up-the-hana-catalog-for-a-database.html ")
print(" -be minimum retained number of data backup (i.e. complete data backups and data snapshots) entries in the catalog, this ")
print(" number of entries of data backups will remain in the backup catalog, all older log backup entries will also be removed ")
print(" with BACKUP CATALOG DELETE BACKUP_ID <id> (see SQL reference for more info) default: -1 (not used) ")
print(" -bd min retained days of data backup (i.e. complete data backups and data snapshots) entries in the catalog [days], the ")
print(" youngest successful data backup entry in the backup catalog that is older than this number of days is the oldest ")
print(" successful data backup entry not removed from the backup catalog, default -1 (not used) ")
print(" Note: if both -be and -bd is used, the most conservative, i.e. the flag that removes the least number entries, decides ")
print(" Note: As mentioned in SAP Note 1812057 backup entries made via backint cannot be recovered, i.e. use -be and -bd with care")
print(" if you want to be able to recover from older data backups (it is possible to recover from a specific data backup without ")
print(" the backup catalog) ")
print(" -bb delete backups also [true/false], backups are deleted when the related backup catalog entries are deleted with ")
print(" BACKUP CATALOG DELETE BACKUP_ID <id> COMPLETE (see SQL reference for more info), default: false ")
print(" -bo output catalog [true/false], displays backup catalog before and after the cleanup, default: false ")
print(" -br output removed catalog entries [true/false], displays backup catalog entries that were removed, default: false ")
print(" Note: Please do not use -bo and -br if your catalog is huge (>10000) entries. ")
print(" -bn output number deleted log backup entries [true/false], prints out how many log backup entries were deleted from the ")
print(" backup catalog, it is only needed to change this to false in case of extremely huge backup catalogs, default: true ")
print(" ---- TRACE FILES ---- ")
print(" -tc retention days for trace files [days], trace files with their latest modification time older than these number of days are") #internal incident 1870190781
print(" removed from all hosts, default: -1 (not used) ")
print(" Note: Conceptual -tc is the same as -tf, but -tc is using ALTER SYSTEM CLEAR TRACES ... See SQL Ref. for more info. ")
print(" Note: there is a bug (fixed with rev.122.11) that could cause younger trace files to be removed. ")
print(" Note: expensive statements are not included in -tc, see -te below ")
print(" -te retention days for expensive statement files [days], same as for -tc, but only for expensive statement files, only use ") #BUG --> https://jira.tools.sap/browse/HDBKERNEL-7797
print(" if you read and understood SAP Note 2819941 (should probably only be used with use_in_memory_tracking = false), ")
print(" default: -1 (not used) ")
print(" -tcb with backup [true/false], the trace files that are removed with the -tc and -te flags are backed up (as a .gz), ")
print(" i.e. ALTER SYSTEM CLEAR TRACES ... WITH BACKUP, see SQL Ref., default: false ")
print(" NOTE: Some small, unnecessary files, like .stat (SAP Note 2370780), might be deleted by WITH BACKUP --> the numbers might ")
print(" not seem to fit ") #internal incident 2180309626
print(" -tbd directory for the trace backups, full path of the directory to where the back up (see -tcb) of the trace files ")
print(" are moved (for housekeeping of moved files, see ANY FILES below), default: '' (they stay in the original directory) ")
print(" -tmo time out for move [seconds], the move, requested by the -tbd flag, must wait until the compression, requested by the -tcb ")
print(" flag, is finished. This can take some seconds. If it is not finished before the time out, specified by the -tmo flag, ")
print(" the move will not happen, default: 30 seconds ")
print(" -tf retention days for trace files [days], trace files, in all hosts, that are older than this number of days are removed ")
print(" (except for the currently opened trace files), only files with certain extensions like .trc, .log etc are taken into ")
print(" account, backup.log and backint.log, are excepted, please see -zb and -zp instead, default: -1 (not used) ")
print(" Note: Conceptual -tf is the same as -tc, but -tf is using ALTER SYSTEM REMOVE TRACES ... See SQL Ref. for more info. ")
print(" Note: ALTER SYSTEM REMOVE TRACES ... WITH BACKUP does not exist, see SQL Ref., so there is no -tfb flag. ")
print(" -to output traces [true/false], displays trace files before and after the cleanup, default: false ")
print(" -td output deleted traces [true/false], displays trace files that were deleted, default: false ")
print(" ---- DUMP FILES ---- ")
print(" -dr retention days for dump files [days], manually created dump files (a.k.a. fullysytem dumps and runtime dumps) that are ")
print(" older than this number of days are removed, default: -1 (not used) ")
print(" ---- ANY FILES ---- ")
print(" -gr retention days for any general file [days], files in the directory specified with -gd and with the file names including ")
print(" the word specified with -gw are only saved for this number of days, default: -1 (not used) ")
print(" Note: -gd and -gw can also be same length lists with a commas as delimiter ")
print(" -gd directories for general files to be deleted, a comma separated list with full paths of directories with files to be ")
print(' deleted according to -gr (entries pairs with entries in -gw), default "" (not used) ')
print(" Note: if you include %SID, it will automatically be replaced with the actually SID of your system ")
print(" -gw filename parts for general files to be deleted, a comma separated list with words that files should have in their names ")
print(' to be deleted according to -gr (entries pairs with entries in -gd), default "" (not used) ')
print(" -gm max depth, maximum recursive folders from folder specified by -gd it will delete files from, default: 1 ")
print(" ---- BACKUP LOGS <H2SPS04 ---- ")
print(" -zb backup logs compression size limit [mb], if there are any backup.log or backint.log file (see -zp below) that is bigger ")
print(" than this size limit, then it is compressed and renamed, default: -1 (not used) ")
print(" Note: if -tf flag is used the resulting zip file could be removed by it. ")
print(" Note: Don't use this with version HANA 2 SPS04 or later, instead configure size with parameters, see SAP Note 2797078 ")
print(" -zp zip path, specifies the path (and all subdirectories) where to look for the backup.log and backint.log files, ")
print(" default is the directory specified by the alias cdtrace ")
print(" -zl zip links [true/false], specifies if symbolic links should be followed searching for backup logs in subdirectories ")
print(" of the directory defined by zp (or by alias cdtrace), default: false ")
print(" -zo print zipped backup logs, display the backup.log and backint.log that were zipped, default: false ")
print(" -zk keep zip, if this is set to false the zip file is deleted (use with care!), default: true ")
print(" ---- ALERTS ---- ")
print(" -ar min retained alerts days [days], min age (today not included) of retained statistics server alerts, default: -1 (not used)")
print(" -ao output alerts [true/false], displays statistics server alerts before and after the cleanup, default: false ")
print(" -ad output deleted alerts [true/false], displays statistics server alerts that were deleted, default: false ")
print(" ---- OBJECT LOCKS ENTRIES with UNKOWN OBJECT NAME ---- ")
print(" -kr min retained unknown object lock days [days], min age (today not included) of retained object lock entries with unknown ")
print(" object name, in accordance with SAP Note 2147247, default: -1 (not used) ")
print(" ---- OBJECT HISTORY ---- ")
print(" -om object history table max size [mb], if the table _SYS_REPO.OBJECT_HISTORY is bigger than this threshold this table ")
print(" will be cleaned up according to SAP Note 2479702, default: -1 (not used) ")
print(" -oo output cleaned memory from object table [true/false], displays how much memory was cleaned up from object history ")
print(" table, default: false ")
print(" ---- LOG SEGMENTS ---- ")
print(" -lr max free logsegments per service [number logsegments], if more free logsegments exist for a service the statement ")
print(" ALTER SYSTEM RECLAIM LOG is executed, default: -1 (not used) ")
print(" ---- EVENTS ---- ")
print(" -eh min retained days for handled events [day], minimum retained days for the handled events, handled events that are older ")
print(" are removed by first being acknowledged and then deleted, this is done for all hosts, default: -1 (not used) ")
print(" Note: Due to a current issue in HANA all events of type INFO are ignored. If automatic cleaning of INFO events are ")
print(" needed, please open an incident on component HAN-DB about the SQL statement ALTER SYSTEM SET EVENT HANDLED. ")
print(" -eu min retained days for unhandled events [day], minimum retained days for events, events that are older are removed by ")
print(" first being handled and acknowledged and then deleted, this is done for all hosts, default: -1 (not used) ")
print(" ---- AUDIT LOG ---- ")
print(" -ur retention days for audit log table [days], audit log content older than these number of days is removed, ")
print(" default: -1 (not used) ")
print(" ---- PENDING EMAILS ---- ")
print(" -pe retention days for pending e-mails [days], pending statistics server e-mail notifications older than these number of ")
print(" days are removed, default: -1 (not used) (requires SELECT and DELETE on the _SYS_STATISTICS schema) ")
print(" ---- DATA VOLUMES FRAGMENTATION ---- ")
print(" -fl fragmentation limit [%], maximum fragmentation of data volume files, of any service, before defragmentation of that ")
print(" service is started: ALTER SYSTEM RECLAIM DATAVOLUME '<host>:<port>' 120 DEFRAGMENT, default: -1 (not used) ")
print(" Note: If you use System Replication see Q19 in SAP Note 1999880. ")
print(" -fo output fragmentation [true/false], displays data volume statistics before and after defragmentation, default: false ")
print(" ---- MULTIPLE ROW STORE TABLE CONTAINERS ---- ")
print(" -rc row store containers cleanup [true/false], switch to clean up multiple row store table containers, default: false ")
print(" Note: Unfortunately there is NO nice way to give privileges to the DB User to be allowed to do this. Either you can ")
print(" run hanacleaner as SYSTEM user (NOT recommended) or grant DATA ADMIN to the user (NOT recommended) ")
print(" -ro output row containers [true/false], displays row store tables with more than one container before cleanup, default: false ")
print(" ---- COMPRESSION OPTIMIZATION ---- ")
print(" 1. Both following two flags, -cc, and -ce, must be > 0 to control the force compression optimization on tables that never ")
print(" was compression re-optimized (i.e. last_compressed_record_count = 0): ")
print(" -cc max allowed raw main records, if table has more raw main rows --> compress if -ce, default: -1 (not used) e.g. 10000000 ")
print(" -ce max allowed estimated size [GB], if estimated size is larger --> compress if -cc, default: -1 (not used) e.g. 1 ")
print(" 2. All following three flags, -cr, -cs, and -cd, must be > 0 to control the force compression optimization on tables with ")
print(" columns with compression type 'DEFAULT' (i.e. no additional compression algorithm in main) ")
print(" -cr max allowed rows, if a column has more rows --> compress if -cs&-cd, default: -1 (not used) e.g. 10000000 ")
print(" -cs max allowed size [MB], if a column is larger --> compress if -cr&-cd, default: -1 (not used) e.g. 500 ")
print(" -cd min allowed distinct count [%], if a column has less distinct quota --> compress if -cr&-cs, default -1 (not used) e.g. 5 ")
print(" 3. Both following two flags, -cu and -cq, must be > 0 to control the force compression optimization on tables whose UDIV ")
print(" quota is too large, i.e. #UDIVs/(#raw main + #raw delta) ")
print(" -cq max allowed UDIV quota [%], if the table has larger UDIV quota --> compress if -cu, default: -1 (not used) e.g. 150 ")
print(" -cu max allowed UDIVs, if a column has more then this number UDIVs --> compress if -cq, default: -1 (not used) e.g. 10000000 ")
print(" 4. Flag -cb must be > 0 to control the force compression optimization on tables with columns with SPARSE (<122.02) or ")
print(" PREFIXED and a BLOCK index ")
print(" -cb max allowed rows, if a column has more rows and a BLOCK index and SPARSE (<122.02) or PREFIXED then this table should ")
print(" be compression re-optimized, default -1 (not used) e.g. 100000 ")
print(" Following three flags are general; they control all three, 1., 2., 3., 4., compression optimization possibilities above ")
print(" -cp per partition [true/false], switch to consider flags above per partition instead of per column, default: false ")
print(" -cm merge before compress [true/false], switch to perform a delta merge on the tables before compression, default: false ")
print(" -co output compressed tables [true/false], switch to print all tables that were compression re-optimized, default: false ")
print(" ---- VIRTUAL TABLE STATISTICS CREATION ---- ")
print(" -vs create statistics for virtual tables [true/false], switch to create optimization statistics for those virtual tables ")
print(" that are missing statistics according to SAP Note 1872652 (Note: could cause expenive operations), default: false ")
print(" -vt default statistics type [HISTOGRAM/SIMPLE/TOPK/SKETCH/SAMPLE/RECORD_COUNT], type of data statistics object ")
print(" default: HISTOGRAM ")
print(" -vn max number of rows for defult type [number rows], if the VT has less or equal number of rows specified by -vn the default ")
print(" statistics type, defined by -vt, is used, else the type defined by -vtt is used, default: -1 (not considered) ")
print(" -vtt large statistics type [HISTOGRAM/SIMPLE/TOPK/SKETCH/SAMPLE/RECORD_COUNT], type of data statistics object used if the VT ")
print(" has more rows than specified by -vn and the database is HANA default: SIMPLE ")
print(" -vto statistics type for other DBs [HISTOGRAM/SIMPLE/TOPK/SKETCH/SAMPLE/RECORD_COUNT], type of data statistics object if the ")
print(" remote database is not HANA default: "" (not considered) ")
print(" -vl schema list of virtual tables, if you only want tables in some schemas to be considered for the creation of statistics ")
print(" provide here a comma separated list of those schemas, default '' (all schemas will be considered) ")
print(" -vr ignore secondary monitoring tables [true/false], normaly statistics for the the virtual tables in the ")
print(" _SYS_SR_SITE* schemas are not needed to be created nor updated, so they are by default ignored, default: true ")
print(" ---- VIRTUAL TABLE STATISTICS REFRESH ---- ")
print(" -vnr refresh age of VT statistics [number days > 0], if the VT statistics of a table is older than this number of days it will ")
print(" be refreshed (Note: -vl and -vr holds also for refresh) default: -1 (no refresh) ")
print(" ---- INIFILE CONTENT HISTORY ---- ")
print(" -ir inifile content history retention [days], deletes older inifile content history, default: -1 (not used) (should > 1 year) ")
print(" Note: Only supported with 03<SPS<SPS05. ")
print(" ---- INTERVALL ---- ")
print(" -hci hana cleaner interval [days], number days that hanacleaner waits before it restarts, default: -1 (exits after 1 cycle) ")
print(" NOTE: Do NOT use if you run hanacleaner in a cron job! ")
print(" ---- INPUT ---- ")
print(" -ff flag file(s), a comma seperated list of full paths to files that contain input flags, each flag in a new line, all lines ")
print(" in the files that do not start with a flag (a minus) are considered comments, default: '' (not used) ")
print(" Note: if you include %SID in the path, it will automatically be replaced with the actually SID of your system ")
print(" ---- EXECUTE ---- ")
print(" -es execute sql [true/false], execute all crucial housekeeping tasks (useful to turn off for investigation with -os=true, ")
print(" a.k.a. chicken mode :) default: true ")
print(" ---- OUTPUT ---- ")
print(" -os output sql [true/false], prints all crucial housekeeping tasks (useful for debugging with -es=false), default: false ")
print(" -op output path, full literal path of the folder for the output logs (will be created if not there), default = '' (not used) ")
print(" Note: if you include %SID in the output path, it will automatically be replaced with the actually SID of your system ")
print(" -of output prefix, adds a string to the output file, default: '' (not used) ")
print(" -or output retention days, logs in the paths specified with -op are only saved for this number of days, default: -1 (not used)")
print(" -oc output configuration [true/false], logs all parameters set by the flags and where the flags were set, i.e. what flag file ")
print(" (one of the files listed in -ff) or if it was set via a flag specified on the command line, default = false ")
print(" -so standard out switch [true/false], switch to write to standard out, default: true ")
print(" ---- INSTANCE ONLINE CHECK ---- ")
print(" -oi online test interval [seconds], < 0: HANACleaner does not check if online or secondary, default: -1 (not used) ")
print(" = 0: if not online or not primary HANACleaner will abort ")
print(" > 0: time it waits before it checks if DB is online and primary again ")
print(" Note: For the > 0 option it might be necessary to use cron with the lock option ")
print(" See the HANASitter & CRON slide in the HANASitter pdf ")
print(" Note: for now, -oi is not supported for a server running HANA Cockpit ")
print(" ---- SERVER FULL CHECK ---- ")
print(" -fs file system, path to server to check for disk full situation before hanacleaner runs, default: blank, i.e. df -h is used ")
print(' Could also be used to specify a couple of servers with e.g. -fs "|grep sapmnt" ')
print(" -if ignore filesystems and mounts, before hanacleaner starts it checks that there is no disk full situation in any of the ")
print(" filesystems and/or mounts, this flag makes it possible to ignore some filesystems, with comma separated list, from the ")
print(" df -h command (filesystems are in the first column and mounts normally in the 5th or 6th column), default: '' ")
print(" -df filesystem check switch [true/false], it is possible to completely ignore the filesystem check (necessary if non-ascii ")
print(" comes out from df -h). However, hanacleaner is NOT supported in case of full filesystem so if you turn this to false ")
print(" it is necessary that you check for disk full situation manually! default: true ")
print(" ---- SSL ---- ")
print(" -ssl turns on ssl certificate [true/false], makes it possible to use SAP HANA Cleaner despite SSL, default: false ")
print(" ---- HOST ---- ")
print(" -vlh virtual local host, if hanacleaner runs on a virtual host this has to be specified, default: '' (physical host is assumed)")
print(" ---- USER KEY ---- ")
print(" -k DB user key, this one has to be maintained in hdbuserstore, i.e. as <sid>adm do ")
print(" > hdbuserstore SET <DB USER KEY> <ENV> <USERNAME> <PASSWORD> , default: SYSTEMKEY ")
print(" It could also be a list of comma seperated userkeys (useful in MDC environments), e.g.: SYSTEMKEY,TENANT1KEY,TENANT2KEY ")
print(" Note: It is not possible to use underscore in the user key, e.g. HANA_HOUSEKEEPING is NOT possible ")
print(" -dbs DB key, this can be a list of databases accessed from the system defined by -k (-k can only be one key if -dbs is used) ")
print(" Note: Users with same name and password have to be maintained in all databases , default: '' (not used) ")
print(" It is possible to specify -dbs all to execute hanacleaner on all active databases, then -k must point to SYSTEMDB ")
print(" Example: -k PQLSYSDBKEY -dbs SYSTEMDB,PQL,PQ2 ")
print(" Example: -k PQLSYSDBKEY -dbs all ")
print(" ---- EMAIL ---- ")
print(" -en email notification for most fatal errors, <receiver 1's email>,<receiver 2's email>,... default: (not used) ")
print(" -et email timeout warning [seconds], sends email to the email addresses specified with -en, if HANACleaner took longer time ")
print(" than specified with -et, default: -1 (not used) ")
print(" -ena always send email, if set to true, a summary of the hanacleaner run is always send (-en must be true), default: false ")
print(" -enc email client, to explicitly specify the client (e.g mail, mailx, mutt,..), only useful if -en if used, default: mailx ")
print(" -ens sender's email, to explicitly specify sender's email address, only useful if -en if used, default: (configured used) ")
print(" -enm mail server, to explicitly specify the mail server, only useful if -en is used, default: (configured used) ")
print(' NOTE: For this to work you have to install the linux program "sendmail" and add a line similar to ')
print(' DSsmtp.intra.ourcompany.com in the file sendmail.cf in /etc/mail/, ')
print(" see https://www.systutorials.com/5167/sending-email-using-mailx-in-linux-through-internal-smtp/ ")
print(" ")
print(" ")
print("EXAMPLE (trace files, statistics server alerts and backup catalog entries, i.e. not the backups themselves, older than 42 days ")
print(" are deleted and backup logs bigger than 50 mb are compressed and renamed and logsegments a removed if more than 20 ")
print(" free once exist for a service): ")
print(" ")
print(" > python hanacleaner.py -tc 42 -tf 42 -ar 42 -bd 42 -zb 50 -lr 20 -eh 2 -eu 42 ")
print(" ")
print(" ")
print("EXAMPLE (reads a configuration file, one flag will overwrite, i.e. retention time for the alerts will be 200 days instead of 42): ")
print(" > python hanacleaner.py -ff /tmp/HANACleaner/hanacleaner_configfile.txt -ar 200 ")
print(" Where the config file could look like this: ")
print(" MY HANACLEANER CONFIGURATION FILE ")
print(" Oldest content of the trace files should only be 42 days old ")
print(" -tc 42 ")
print(" Oldest trace file should only be 42 days old ")
print(" -tf 42 ")
print(" Oldest alerts should only be 42 days old ")
print(" -ar 42 ")
print(" This is the key in hdbuserstore that is used: ")
print(" -k SYSTEMKEY ")
print(" ")
print("CURRENT KNOWN LIMITATIONS (i.e. TODO LIST): ")
print(" 1. HANACleaner should notice if HANA is not listening to SQL or only readable and then ")
print(" sleep for a while and test if this HANA becomes primary now and then --> useful in case of HSR ")
print(" 2. Allow granular control on minutes instead of days ")
print(" 3. Allow compression on trace files as well not only on backup related files ")
print(" 4. Allow a two steps cleanup for general files, e.g. compress file older than a few hours and delete files older than some days ")
print(" 5. Check for multiple definitions of one flag, give ERROR, and STOP ")
print(" 6. Move trace files instead of deleting ... --> not a good idea ... should not touch trace files from OS, only from HANA ")
print(" 7. Only send emails in case of some failure, either an found error or a catched error ")
print(" 8. HANA Cleaner should be able to clean up its own tracefiles (-or) even though it is Secondary on a System Replicaiton setup ")
print(" The same is true about some other cleanups... e.g. ANY FILES ")
print(" 9. It should be possible to influence the ignore list of the -tf flag with a flag getting a list of files not to delete ")
print(" ")
print("AUTHOR: Christian Hansen ")
print(" ")
print(" ")
os._exit(1)
def printDisclaimer():
print(" ")
print("ANY USAGE OF HANACLEANER ASSUMES THAT YOU HAVE UNDERSTOOD AND AGREED THAT: ")
print(" 1. HANACleaner is NOT SAP official software, so normal SAP support of HANACleaner cannot be assumed ")
print(" 2. HANACleaner is open source ")
print(' 3. HANACleaner is provided "as is" ')
print(' 4. HANACleaner is to be used on "your own risk" ')
print(" 5. HANACleaner is a one-man's hobby (developed, maintained and supported only during non-working hours) ")
print(" 6 All HANACleaner documentations have to be read and understood before any usage: ")
print(" a) SAP Note 2399996 ")
print(" b) The .pdf file that can be downloaded from https://github.com/chriselswede/hanacleaner ")
print(" c) All output from executing ")
print(" python hanacleaner.py --help ")
print(" 7. HANACleaner can help you execute certain SAP HANA tasks automatically but is NOT an attempt to teach you SAP HANA ")
print(" Therefore it is assumed that you understand all SQL statements that HANACleaner does to make changes in your system ")
print(" To find out what crucial SQL statements HANACleaner will do without actually executing them run with the additional flags ")
print(" -es false -os true ")
print(' To then learn what those statements do before you executing HANACleaner without "-es false", see SAP HANA Admin Guide or ')
print(" SAP HANA System Administration Workshops ")
print(" 8. HANACleaner is not providing any recommendations, all flags shown in the documentation (see point 6.) are only examples ")
print(" For recommendations see SAP HANA Administration Workshops or other documentation, like e.g. SAP Note 2400024 ")
os._exit(1)
######################## CLASS DEFINITIONS ################################
class SQLManager:
def __init__(self, execute_sql, hdbsql_string, dbuserkey, DATABASE, log_sql):
self.execute = execute_sql
self.key = dbuserkey
self.db = DATABASE
self.log = log_sql
if len(DATABASE) > 1:
self.hdbsql_jAU = hdbsql_string + " -j -A -U " + self.key + " -d " + self.db
self.hdbsql_jAxU = hdbsql_string + " -j -A -x -U " + self.key + " -d " + self.db
self.hdbsql_jAaxU = hdbsql_string + " -j -A -a -x -U " + self.key + " -d " + self.db
self.hdbsql_jAQaxU = hdbsql_string + " -j -A -Q -a -x -U " + self.key + " -d " + self.db
else:
self.hdbsql_jAU = hdbsql_string + " -j -A -U " + self.key
self.hdbsql_jAxU = hdbsql_string + " -j -A -x -U " + self.key
self.hdbsql_jAaxU = hdbsql_string + " -j -A -a -x -U " + self.key
self.hdbsql_jAQaxU = hdbsql_string + " -j -A -Q -a -x -U " + self.key
class LogManager:
def __init__(self, log_path, out_prefix, print_to_std, emailSender):
self.path = log_path
self.out_prefix = out_prefix
if self.out_prefix:
self.out_prefix = self.out_prefix + "_"
self.print_to_std = print_to_std
self.emailSender = emailSender
class EmailSender:
def __init__(self, receiverEmails, emailClient, senderEmail, mailServer, SID):
self.senderEmail = senderEmail
self.emailClient = emailClient
self.receiverEmails = receiverEmails
self.mailServer = mailServer
self.SID = SID
def printEmailSender(self):
print("Email Client: ", self.emailClient)
if self.senderEmail:
print("Sender Email: ", self.senderEmail)
else:
print("Configured sender email will be used.")
if self.mailServer:
print("Mail Server: ", self.mailServer)
else:
print("Configured mail server will be used.")
print("Reciever Emails: ", self.recieverEmails)
######################## FUNCTION DEFINITIONS ################################
def run_command(cmd):
if sys.version_info[0] == 2:
out = subprocess.check_output(cmd, shell=True).strip("\n")
elif sys.version_info[0] == 3:
out = subprocess.run(cmd, shell=True, capture_output=True, text=True).stdout.strip("\n")
else:
print("ERROR: Wrong Python version")
os._exit(1)
return out
def get_sid():
SID = run_command('echo $SAPSYSTEMNAME').upper()
return SID
def is_integer(s):
try:
int(s)
return True
except ValueError:
return False
def log(message, logmanager, send_email = False):
if logmanager.print_to_std:
print(message)
if logmanager.path:
file_name = "hanacleanerlog"
logfile = open(logmanager.path+"/"+file_name+"_"+logmanager.out_prefix+datetime.now().strftime("%Y-%m-%d"+".txt").replace(" ", "_"), "a")
logfile.write(message+"\n")
logfile.flush()
logfile.close()
if send_email and logmanager.emailSender: #sends email IF this call of log() wants it AND IF -en flag has been specified with email(s)
sendEmail(message, logmanager)
def sendEmail(message, logmanager):
message = 'Hi Team, \n\nHANACleaner reports:\n\n'+message
mailstring = 'echo "'+message+'" | '+logmanager.emailSender.emailClient+' -s "Message from HANACleaner on '+logmanager.emailSender.SID+'" '
if logmanager.emailSender.mailServer:
mailstring += ' -S smtp=smtp://'+logmanager.emailSender.mailServer+' '
if logmanager.emailSender.senderEmail:
mailstring += ' -S from="'+logmanager.emailSender.senderEmail+'" '
mailstring += ",".join(logmanager.emailSender.receiverEmails)
#output = subprocess.check_output(mailstring, shell=True)
dummyout = run_command(mailstring)
def try_execute_sql(sql, errorlog, sqlman, logman, exit_on_fail = True):
succeeded = True
out = ""
try:
if sqlman.log:
log(sql, logman)
if sqlman.execute:
#out = subprocess.check_output(sqlman.hdbsql_jAaxU + " \""+sql+"\"", shell=True)
out = run_command(sqlman.hdbsql_jAaxU + " \""+sql+"\"")
except:
errorMessage = "ERROR: Could not execute\n"+sql+"\n"+errorlog
succeeded = False
if exit_on_fail:
log(errorMessage, logman, True)
os._exit(1)
else:
log(errorMessage, logman)
return [out, succeeded]
def is_email(s):
s = s.split('@')
if not len(s) == 2:
return False
return '.' in s[1]
def hana_version_revision_maintenancerevision(sqlman, logman):
#command_run = subprocess.check_output(sqlman.hdbsql_jAU + " \"select value from sys.m_system_overview where name = 'Version'\"", shell=True)
command_run = run_command(sqlman.hdbsql_jAU + " \"select value from sys.m_system_overview where name = 'Version'\"")
hanaver = command_run.splitlines(1)[2].split('.')[0].replace('| ','')
hanarev = command_run.splitlines(1)[2].split('.')[2]
hanamrev = command_run.splitlines(1)[2].split('.')[3]
if not is_integer(hanarev):
log("ERROR: something went wrong checking hana revision.", logman, True)
os._exit(1)
return [int(hanaver), int(hanarev), int(hanamrev)]
def hosts(sqlman):
#hosts = subprocess.check_output(sqlman.hdbsql_jAaxU + " \"select distinct(host) from sys.m_host_information\"", shell=True).splitlines(1)
hosts = run_command(sqlman.hdbsql_jAaxU + " \"select distinct(host) from sys.m_host_information\"").splitlines(1)
hosts = [host.strip('\n').strip('|').strip(' ') for host in hosts]
return hosts
def get_key_info(dbuserkey, local_host, logman):
try:
#key_environment = subprocess.check_output('''hdbuserstore LIST '''+dbuserkey, shell=True)
key_environment = run_command('''hdbuserstore LIST '''+dbuserkey)
except:
log("ERROR, the key "+dbuserkey+" is not maintained in hdbuserstore.", logman, True)
os._exit(1)
key_environment = key_environment.split('\n')
key_environment = [ke for ke in key_environment if ke and not ke == 'Operation succeed.']
ENV = key_environment[1].replace(' ENV : ','').replace(';',',').split(',')
key_hosts = [env.split(':')[0].split('.')[0] for env in ENV] #if full host name is specified in the Key, only the first part is used
DATABASE = ''
if len(key_environment) == 4: # if DATABASE is specified in the key, this will by used in the SQLManager (but if -dbs is specified, -dbs wins)
DATABASE = key_environment[3].replace(' DATABASE: ','').replace(' ', '')
if not local_host in key_hosts:
print("ERROR, local host, ", local_host, ", should be one of the hosts specified for the key, ", dbuserkey, " (in case of virtual, please use -vlh, see --help for more info)")
os._exit(1)
return [key_hosts, ENV, DATABASE]
def sql_for_backup_id_for_min_retained_days(minRetainedDays):
oldestDayForKeepingBackup = datetime.now() + timedelta(days = -int(minRetainedDays))
return "SELECT TOP 1 ENTRY_ID, SYS_START_TIME from sys.m_backup_catalog where (ENTRY_TYPE_NAME = 'complete data backup' or ENTRY_TYPE_NAME = 'data snapshot') and STATE_NAME = 'successful' and SYS_START_TIME < '" + oldestDayForKeepingBackup.strftime('%Y-%m-%d')+" 00:00:00' order by SYS_START_TIME desc"
def sql_for_backup_id_for_min_retained_backups(minRetainedBackups):
return "SELECT ENTRY_ID, SYS_START_TIME from (SELECT ENTRY_ID, SYS_START_TIME, ROW_NUMBER() OVER(ORDER BY SYS_START_TIME desc) as NUM from sys.m_backup_catalog where (ENTRY_TYPE_NAME = 'complete data backup' or ENTRY_TYPE_NAME = 'data snapshot') and STATE_NAME = 'successful' order by SYS_START_TIME desc) as B where B.NUM = "+str(minRetainedBackups)
def online_and_master_tests(online_test_interval, local_dbinstance, local_host, logman):
if online_test_interval < 0: #then dont test
return True
else:
if is_online(local_dbinstance, logman) and not is_secondary(logman):
return is_master(local_dbinstance, local_host, logman) #HANACleaner should only run on the Master Node
else:
return False
def is_master(local_dbinstance, local_host, logman):
process = subprocess.Popen(['python', cdalias('cdpy', local_dbinstance)+"/landscapeHostConfiguration.py"], stdout=subprocess.PIPE)
out, err = process.communicate()
out = out.decode()
out_lines = out.splitlines(1)
host_line = [line for line in out_lines if local_host in line or local_host.upper() in line or local_host.lower() in line] #have not tested this with virtual and -vlh yet
if len(host_line) != 1:
print_out = "ERROR: Something went wrong. It found more than one (or none) host line" + " \n ".join(host_line)
log(print_out, logman, True)
os._exit(1)
nameserver_actual_role = host_line[0].strip('\n').split('|')[11].strip(' ')
test_ok = (str(err) == "None")
result = nameserver_actual_role == 'master'
printout = "Master Check , "+datetime.now().strftime("%Y-%m-%d %H:%M:%S")+" , - , "+str(test_ok)+" , "+str(result)+" , Nameserver actual role = "+nameserver_actual_role
log(printout, logman)
return result
def is_online(dbinstance, logman): #Checks if all services are GREEN and if there exists an indexserver (if not this is a Stand-By)
process = subprocess.Popen(['sapcontrol', '-nr', dbinstance, '-function', 'GetProcessList'], stdout=subprocess.PIPE)
out, err = process.communicate()
out = out.decode()
number_services = out.count(" HDB ") + out.count(" Local Secure Store")
number_running_services = out.count("GREEN")
number_indexservers = int(out.count("hdbindexserver")) # if not indexserver this is Stand-By
test_ok = (str(err) == "None")
result = (number_running_services == number_services) and (number_indexservers != 0)
printout = "Online Check , "+datetime.now().strftime("%Y-%m-%d %H:%M:%S")+" , - , "+str(test_ok)+" , "+str(result)+" , # index services: "+str(number_indexservers)+", # running services: "+str(number_running_services)+" out of "+str(number_services)
log(printout, logman)
return result
def is_secondary(logman):
process = subprocess.Popen(['hdbnsutil', '-sr_state'], stdout=subprocess.PIPE)
out, err = process.communicate()
out = out.decode()
test_ok = (str(err) == "None")
result = "active primary site" in out # then it is secondary!
printout = "Primary Check , "+datetime.now().strftime("%Y-%m-%d %H:%M:%S")+" , - , "+str(test_ok)+" , "+str(not result)+" , "
log(printout, logman)
return result
def get_all_databases(execute_sql, hdbsql_string, dbuserkey, local_host, out_sql, logman):
all_db_out = ''
[key_hosts, ENV, DATABASE] = get_key_info(dbuserkey, local_host, logman)
key_sqlports = [env.split(':')[1] for env in ENV]
if not key_sqlports[0][-2:] == '13':
log("ERROR: If -bds all is used, then -k must point to SYSTEMDB. Please see --help for more information.", logman)
os._exit(1)
sqlman = SQLManager(execute_sql, hdbsql_string, dbuserkey, DATABASE, out_sql)
all_db_out = run_command(sqlman.hdbsql_jAaxU + " \"select DATABASE_NAME from M_DATABASES WHERE (ACTIVE_STATUS = 'YES')\"")
all_databases = [line.strip('|').strip(' ') for line in all_db_out.splitlines()]
if (len(all_databases) == 0) or \
(len(all_databases) == 1) and all_databases[0] == '':
log("\nERROR: No active database found. ", logman)
os._exit(1)
return all_databases
def checkIfAcceptedFlag(word):
if not word in ["-h", "--help", "-d", "--disclaimer", "-ff", "-be", "-bd", "-bb", "-bo", "-br", "-bn", "-tc", "-te", "-tcb", "-tbd", "-tmo", "-tf", "-to", "-td", "-dr", "-gr", "-gd", "-gw", "-gm", "-zb", "-zp", "-zl", "-zo", "-zk", "-ar", "-kr", "-ao", "-ad", "-om", "-oo", "-lr", "-eh", "-eu", "-ur", "-pe", "-fl", "-fo", "-rc", "-ro", "-cc", "-ce", "-cr", "-cs", "-cd", "-cq", "-cu", "-cb", "-cp", "-cm", "-co", "-vs", "-vt", "-vn", "-vtt", "-vto", "-vr", "-vnr", "-vl", "-ir", "-es", "-os", "-op", "-of", "-or", "-oc", "-oi", "-fs", "-if", "-df", "-hci", "-so", "-ssl", "-vlh", "-k", "-dbs", "-en", "-et", "-ena", "-enc", "-ens", "-enm"]:
print("INPUT ERROR: ", word, " is not one of the accepted input flags. Please see --help for more information.")
os._exit(1)
def getParameterFromFile(flag, flag_string, flag_value, flag_file, flag_log, parameter):
if flag == flag_string:
parameter = flag_value
flag_log[flag_string] = [flag_value, flag_file]
return parameter
def getParameterListFromFile(flag, flag_string, flag_value, flag_file, flag_log, parameter):
if flag == flag_string:
parameter = [x for x in flag_value.split(',')]
flag_log[flag_string] = [flag_value, flag_file]
return parameter
def getParameterFromCommandLine(sysargv, flag_string, flag_log, parameter):
if flag_string in sysargv:
flag_value = sysargv[sysargv.index(flag_string) + 1]
parameter = flag_value
flag_log[flag_string] = [flag_value, "command line"]
return parameter
def getParameterListFromCommandLine(sysargv, flag_string, flag_log, parameter):
if flag_string in sysargv:
parameter = [x for x in sysargv[ sysargv.index(flag_string) + 1 ].split(',')]
flag_log[flag_string] = [','.join(parameter), "command line"]
return parameter
def backup_id(minRetainedBackups, minRetainedDays, sqlman):
if minRetainedDays >= 0:
#results = subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"" + sql_for_backup_id_for_min_retained_days(minRetainedDays) + "\"", shell=True).splitlines(1)
results = run_command(sqlman.hdbsql_jAQaxU + " \"" + sql_for_backup_id_for_min_retained_days(minRetainedDays) + "\"").splitlines(1)
[backupIdForMinRetainedDays, startTimeForMinRetainedDays] = results if results else ['', '']
if not backupIdForMinRetainedDays:
backupIdForMinRetainedDays = '-1'
startTimeForMinRetainedDays = '1000-01-01 08:00:00'
else:
backupIdForMinRetainedDays = backupIdForMinRetainedDays.strip('\n').strip(' ')
startTimeForMinRetainedDays = startTimeForMinRetainedDays.strip('\n').strip(' ').split('.')[0] #removing milliseconds
if minRetainedBackups >= 0:
#results = subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"" + sql_for_backup_id_for_min_retained_backups(minRetainedBackups) + "\"", shell=True).splitlines(1)
results = run_command(sqlman.hdbsql_jAQaxU + " \"" + sql_for_backup_id_for_min_retained_backups(minRetainedBackups) + "\"").splitlines(1)
[backupIdForMinRetainedBackups, startTimeForMinRetainedBackups] = results if results else ['', '']
if not backupIdForMinRetainedBackups:
backupIdForMinRetainedBackups = '-1'
startTimeForMinRetainedBackups = '1000-01-01 08:00:00'
else:
backupIdForMinRetainedBackups = backupIdForMinRetainedBackups.strip('\n').strip(' ')
startTimeForMinRetainedBackups = startTimeForMinRetainedBackups.strip('\n').strip(' ').split('.')[0] #removing milliseconds
if minRetainedDays >= 0 and minRetainedBackups >= 0:
backupId = backupIdForMinRetainedDays if datetime.strptime(startTimeForMinRetainedDays, '%Y-%m-%d %H:%M:%S') < datetime.strptime(startTimeForMinRetainedBackups, '%Y-%m-%d %H:%M:%S') else backupIdForMinRetainedBackups
elif minRetainedDays >= 0:
backupId = backupIdForMinRetainedDays
elif minRetainedBackups >= 0:
backupId = backupIdForMinRetainedBackups
else:
backupId = ""
return backupId
def sqls_for_backup_catalog_cleanup(minRetainedBackups, minRetainedDays, deleteBackups, sqlman):
sqls = []
backupId = backup_id(minRetainedBackups, minRetainedDays, sqlman)
if backupId:
#backupType = subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"select ENTRY_TYPE_NAME from sys.m_backup_catalog where backup_id = '"+backupId+"'\"", shell=True).strip('\n').strip(' ')
backupType = run_command(sqlman.hdbsql_jAQaxU + " \"select ENTRY_TYPE_NAME from sys.m_backup_catalog where backup_id = '"+backupId+"'\"").strip(' ')
if backupType == "complete data backup" or backupType == "data snapshot":
sqls = ["BACKUP CATALOG DELETE ALL BEFORE BACKUP_ID " + backupId]
if deleteBackups:
sqls = ["BACKUP CATALOG DELETE ALL BEFORE BACKUP_ID " + backupId + " COMPLETE"]
#If it will ever be possible to do BACKUP CATALOG DELETE BACKUP_ID <log backup id> then this will be useful:
else:
#backupIdStartTime = subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"select SYS_START_TIME from sys.m_backup_catalog where backup_id = '"+backupId+"'\"", shell=True).strip(' ')
backupIdStartTime = run_command(sqlman.hdbsql_jAQaxU + " \"select SYS_START_TIME from sys.m_backup_catalog where backup_id = '"+backupId+"'\"").strip(' ')
#olderBackupIds = subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"select BACKUP_ID from sys.m_backup_catalog where SYS_START_TIME < '"+backupIdStartTime+"'\"", shell=True).splitlines()
olderBackupIds = run_command(sqlman.hdbsql_jAQaxU + " \"select BACKUP_ID from sys.m_backup_catalog where SYS_START_TIME < '"+backupIdStartTime+"'\"").splitlines()
olderBackupIds = [x.strip('\n').strip(' ') for x in olderBackupIds if x]
for oldID in olderBackupIds:
sql = "BACKUP CATALOG DELETE BACKUP_ID " + oldID
if deleteBackups:
sql += " COMPLETE"
sqls.append(sql)
return sqls
def print_removed_entries(before, after, logman):
beforeLines = before.splitlines(1)
beforeLines = [x.replace('\n', '') for x in beforeLines]
afterLines = after.splitlines(1)
afterLines = [x.replace('\n', '') for x in afterLines]
dif = list(Differ().compare(beforeLines, afterLines))
removedLines = [line.strip("- ").strip("\n") for line in dif if line[0] == '-']
if removedLines:
log("\nREMOVED:\n"+beforeLines[0].strip("\n"), logman)
for line in removedLines:
if not "rows" in line:
log(line, logman)
log("\n", logman)
def clean_backup_catalog(minRetainedBackups, minRetainedDays, deleteBackups, outputCatalog, outputDeletedCatalog, outputNDeletedLBEntries, sqlman, logman):
if outputCatalog or outputDeletedCatalog:
#nCatalogEntries = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"select count(*) from sys.m_backup_catalog\"", shell=True).strip(' '))
nCatalogEntries = int(run_command(sqlman.hdbsql_jAQaxU + " \"select count(*) from sys.m_backup_catalog\"").strip(' '))
if nCatalogEntries > 100000:
log("INPUT ERROR: Please do not use -br true or -bo true if your backup catalog is larger than 100000 entries!", logman, True)
os._exit(1)
#nDataBackupCatalogEntriesBefore = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_backup_catalog where entry_type_name != 'log backup'\"", shell=True).strip(' '))
nDataBackupCatalogEntriesBefore = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_backup_catalog where entry_type_name != 'log backup'\"").strip(' '))
nLogBackupCatalogEntriesBefore = 0
if outputNDeletedLBEntries:
#nLogBackupCatalogEntriesBefore = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_backup_catalog where entry_type_name = 'log backup'\"", shell=True).strip(' '))
nLogBackupCatalogEntriesBefore = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_backup_catalog where entry_type_name = 'log backup'\"").strip(' '))
if nDataBackupCatalogEntriesBefore == 0:
return [0,0]
sqls_for_cleanup = sqls_for_backup_catalog_cleanup(minRetainedBackups, minRetainedDays, deleteBackups, sqlman)
if sqls_for_cleanup:
sql_for_catalog = "select ENTRY_ID, ENTRY_TYPE_NAME, BACKUP_ID, SYS_START_TIME, STATE_NAME from sys.m_backup_catalog"
if outputCatalog or outputDeletedCatalog:
#beforeCatalog = subprocess.check_output(sqlman.hdbsql_jAxU + " \"" + sql_for_catalog + "\"", shell=True)
beforeCatalog = run_command(sqlman.hdbsql_jAxU + " \"" + sql_for_catalog + "\"")
if outputCatalog:
log("\nBEFORE:\n"+beforeCatalog, logman)
for sql_for_cleanup in sqls_for_cleanup:
errorlog = "\nERROR: The user represented by the key "+sqlman.key+" could not clean backup catalog. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the system privilege BACKUP ADMIN.\n"
errorlog += "If there is another error (i.e. not insufficient privilege) then please try to execute \n"+sql_for_cleanup+"\nin e.g. the SQL editor in SAP HANA Studio. If you get the same error then this has nothing to do with hanacleaner"
try_execute_sql(sql_for_cleanup, errorlog, sqlman, logman)
#nDataBackupCatalogEntriesAfter = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_backup_catalog where entry_type_name != 'log backup'\"", shell=True).strip(' '))
nDataBackupCatalogEntriesAfter = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_backup_catalog where entry_type_name != 'log backup'\"").strip(' '))
nLogBackupCatalogEntriesAfter = 0
if outputNDeletedLBEntries:
#nLogBackupCatalogEntriesAfter = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_backup_catalog where entry_type_name = 'log backup'\"", shell=True).strip(' '))
nLogBackupCatalogEntriesAfter = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_backup_catalog where entry_type_name = 'log backup'\"").strip(' '))
if outputCatalog or outputDeletedCatalog:
#afterCatalog = subprocess.check_output(sqlman.hdbsql_jAxU + " \"" + sql_for_catalog + "\"", shell=True)
afterCatalog = run_command(sqlman.hdbsql_jAxU + " \"" + sql_for_catalog + "\"")
if outputCatalog:
log("\nAFTER:\n"+afterCatalog, logman)
if outputDeletedCatalog:
print_removed_entries(beforeCatalog, afterCatalog, logman)
return [nDataBackupCatalogEntriesBefore - nDataBackupCatalogEntriesAfter, max(nLogBackupCatalogEntriesBefore - nLogBackupCatalogEntriesAfter,0)] #if a logbackup was done during run
else:
return [0,0]
def clear_traces(trace_list, oldestRetainedTraceContentDate, backupTraceContent, sqlman, logman):
if backupTraceContent:
sql = "ALTER SYSTEM CLEAR TRACES ("+trace_list+") UNTIL '"+oldestRetainedTraceContentDate.strftime('%Y-%m-%d')+" "+datetime.now().strftime("%H:%M:%S")+"' WITH BACKUP"
else:
sql = "ALTER SYSTEM CLEAR TRACES ("+trace_list+") UNTIL '"+oldestRetainedTraceContentDate.strftime('%Y-%m-%d')+" "+datetime.now().strftime("%H:%M:%S")+"'"
errorlog = "\nERROR: The user represented by the key "+sqlman.key+" could not clear traces. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the system privilege TRACE ADMIN.\n"
errorlog += "If there is another error (i.e. not insufficient privilege) then please try to execute \n"+sql+"\nin e.g. the SQL editor in SAP HANA Studio. If you get the same error then this has nothing to do with hanacleaner"
try_execute_sql(sql, errorlog, sqlman, logman)
def clean_trace_files(retainedTraceContentDays, retainedExpensiveTraceContentDays, backupTraceContent, backupTraceDirectory, timeOutForMove, retainedTraceFilesDays, outputTraces, outputRemovedTraces, SID, DATABASE, local_dbinstance, hosts, sqlman, logman):
#nbrTracesBefore = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_tracefiles\"", shell=True).strip(' '))
nbrTracesBefore = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_tracefiles\"").strip(' '))
if nbrTracesBefore == 0:
return 0
if outputTraces:
#beforeTraces = subprocess.check_output(sqlman.hdbsql_jAxU + " \"select * from sys.m_tracefiles order by file_mtime desc\"", shell=True)
beforeTraces = run_command(sqlman.hdbsql_jAxU + " \"select * from sys.m_tracefiles order by file_mtime desc\"")
log("\nBEFORE:\n"+beforeTraces, logman)
if outputRemovedTraces:
#beforeTraceFiles = subprocess.check_output(sqlman.hdbsql_jAaxU + " \"select HOST, FILE_NAME from sys.m_tracefiles order by file_mtime desc\"", shell=True)
beforeTraceFiles = run_command(sqlman.hdbsql_jAaxU + " \"select HOST, FILE_NAME from sys.m_tracefiles order by file_mtime desc\"")
if retainedTraceContentDays != "-1" or retainedExpensiveTraceContentDays != "-1":
oldestRetainedTraceContentDate = datetime.now() + timedelta(days = -int(retainedTraceContentDays))
timeStampsForClearTraces = [datetime.now().strftime("%Y%m%d%H%M%S"), (datetime.now() + timedelta(seconds=1)).strftime("%Y%m%d%H%M%S"), (datetime.now() + timedelta(seconds=2)).strftime("%Y%m%d%H%M%S"), (datetime.now() + timedelta(seconds=3)).strftime("%Y%m%d%H%M%S")]
if retainedTraceContentDays != "-1":
clear_traces("'ALERT','CLIENT','CRASHDUMP','EMERGENCYDUMP','RTEDUMP','UNLOAD','ROWSTOREREORG','SQLTRACE','*'", oldestRetainedTraceContentDate, backupTraceContent, sqlman, logman)
if retainedExpensiveTraceContentDays != "-1": # internal incident 1980358670, SAP Note 2819941 shows a BUG that should be fixed! "expected behaviour" = bull s###
clear_traces("'EXPENSIVESTATEMENT'", oldestRetainedTraceContentDate, backupTraceContent, sqlman, logman)
if backupTraceDirectory:
if not DATABASE:
log("INPUT ERROR: If -tbd is used, either DATABASE must be specified in the key (see the manual of hdbuserstore), or -dbs must be specifed.", logman)
log("NOTE: -tbd is not supported for none MDC systems", logman, True)
os._exit(1)
if not os.path.exists(backupTraceDirectory):
os.makedirs(backupTraceDirectory)
fileNameEndingsToBeMoved = ['_'+timeStamp+'.gz' for timeStamp in timeStampsForClearTraces]
fileNameEndingsToWaitFor = ['_'+timeStamp+'.2gz' for timeStamp in timeStampsForClearTraces]
#This compression takes a while and .2gz files are created as intermediate files, when the .2gz files are gone, we are done
filesToWaitFor = ['dummy.2gz']
waitedSeconds = 0
while filesToWaitFor and waitedSeconds < timeOutForMove:
time.sleep(1)
waitedSeconds += 1
sql = "select FILE_NAME from sys.m_tracefiles where FILE_NAME like '%" + "' or FILE_NAME like '%".join(fileName for fileName in fileNameEndingsToWaitFor) + "'"
#filesToWaitFor = subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"" + sql + "\"", shell=True).splitlines(1)
filesToWaitFor = run_command(sqlman.hdbsql_jAQaxU + " \"" + sql + "\"").splitlines(1)
filesToWaitFor = [file.strip('\n').strip(' ') for file in filesToWaitFor]
filesToWaitFor = [file for file in filesToWaitFor if file]
if waitedSeconds < timeOutForMove:
sql = "select FILE_NAME from sys.m_tracefiles where FILE_NAME like '%" + "' or FILE_NAME like '%".join(fileName for fileName in fileNameEndingsToBeMoved) + "'"
#filesToBeMoved = subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"" + sql + "\"", shell=True).splitlines(1)
filesToBeMoved = run_command(sqlman.hdbsql_jAQaxU + " \"" + sql + "\"").splitlines(1)
filesToBeMoved = [file.strip('\n').strip(' ') for file in filesToBeMoved]
filesToBeMoved = [file for file in filesToBeMoved if file]
for host in hosts:
path = cdalias('cdhdb', local_dbinstance)+"/"+host
for filename in filesToBeMoved:
#fullFileName = subprocess.check_output("find "+path+" -name "+filename, shell=True).strip('\n').strip(' ')
fullFileName = run_command("find "+path+" -name "+filename).strip('\n').strip(' ')
if fullFileName and ((DATABASE == 'SYSTEMDB' and 'DB_' in fullFileName) or (DATABASE != 'SYSTEMDB' and not 'DB_'+DATABASE in fullFileName)):
fullFileName = ''
if fullFileName:
#subprocess.check_output("mv "+fullFileName+" "+backupTraceDirectory+"/", shell=True)
dummyout = run_command("mv "+fullFileName+" "+backupTraceDirectory+"/")
if outputRemovedTraces and filesToBeMoved:
log("\nARCHIVED ("+str(len(filesToBeMoved))+"):", logman)
for filename in filesToBeMoved:
log(filename, logman)
else:
log("WARNING: The compression, requested by the -tbd flag, took longer ("+str(waitedSeconds)+" seconds) than the timeout ("+str(timeOutForMove)+" seconds), so the archived trace files will not be moved.")
if retainedTraceFilesDays != "-1":
oldestRetainedTraceFilesDate = datetime.now() + timedelta(days = -int(retainedTraceFilesDays))
sql = "select FILE_NAME from sys.m_tracefiles where file_size != '-1' and file_mtime < '"+oldestRetainedTraceFilesDate.strftime('%Y-%m-%d')+" "+datetime.now().strftime("%H:%M:%S")+"'" # file_size = -1 --> folder, cannot be removed
#filesToBeRemoved = subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"" + sql + "\"", shell=True).splitlines(1)
filesToBeRemoved = run_command(sqlman.hdbsql_jAQaxU + " \"" + sql + "\"").splitlines(1)
filesToBeRemoved = [file.strip('\n').strip(' ') for file in filesToBeRemoved if file != '\n']
# Ignore files with names that breaks the ALTER command, or kill.sap according to SAP Note 2349144, and backup.log and backint.log since they are taken care of by -zb, see SAP Note 2431472 about hdbdaemon, we do not want to delete any .sem or .status file, and we do not want to delete any links, e.g. .sap<SID>_HDB<inst>, and we dont want to delete the log for the webdispatcher
filesToBeRemoved = [file for file in filesToBeRemoved if not (" " in file or "," in file or "'" in file or "kill.sap" in file or "backup.log" in file or "backint.log" in file or "hdbdaemon.status" in file or "sapstart.sem" in file or "sapstart.log" in file or ".sap"+SID+"_HDB"+local_dbinstance in file or "http_fe.log" in file)]
# Make sure we only delete files with known extensions (we dont delete .sem or .status files). Added two files without extensions that we want to delete. To delete files like dev_icm_sec one have to run HANACleaner as dev_icm_sec from SYSTEMDB, otherwise they are not in m_tracefiles
filesToBeRemoved = [file for file in filesToBeRemoved if any(x in file for x in [".trc", ".log", ".stat", ".py", ".tpt", ".gz", ".zip", ".old", ".xml", ".txt", ".docs", ".cfg", ".dmp", ".cockpit", ".xs", "dev_icm_sec", "wdisp_icm_log"])]
if filesToBeRemoved: # otherwise no file to remove
filesToBeRemoved = [filesToBeRemoved[i:i + 100] for i in range(0, len(filesToBeRemoved), 100)] #make sure we do not send too long statement, it could cause an error
for files in filesToBeRemoved:
filesToBeRemovedString = "'"+"', '".join(files)+"'"
for host in hosts:
sql = "ALTER SYSTEM REMOVE TRACES (" +"'"+host+"', "+filesToBeRemovedString+ ")"
errorlog = "\nERROR: The user represented by the key "+sqlman.key+" could not remove traces. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the system privilege TRACE ADMIN.\n"
errorlog += "If there is another error (i.e. not insufficient privilege) then please try to execute \n"+sql+"\nin e.g. the SQL editor in SAP HANA Studio. If you get the same error then this has nothing to do with hanacleaner"
try_execute_sql(sql, errorlog, sqlman, logman)
#nbrTracesAfter = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_tracefiles\"", shell=True).strip(' '))
nbrTracesAfter = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM sys.m_tracefiles\"").strip(' '))
nbrRemovedTraceFiles = nbrTracesBefore - nbrTracesAfter
if outputTraces:
#afterTraces = subprocess.check_output(sqlman.hdbsql_jAxU + " \"select * from sys.m_tracefiles order by file_mtime desc\"", shell=True)
afterTraces = run_command(sqlman.hdbsql_jAxU + " \"select * from sys.m_tracefiles order by file_mtime desc\"")
log("\nAFTER:\n"+afterTraces, logman)
if outputRemovedTraces and nbrRemovedTraceFiles:
#afterTraceFiles = subprocess.check_output(sqlman.hdbsql_jAaxU + " \"select HOST, FILE_NAME from sys.m_tracefiles order by file_mtime desc\"", shell=True)
afterTraceFiles = run_command(sqlman.hdbsql_jAaxU + " \"select HOST, FILE_NAME from sys.m_tracefiles order by file_mtime desc\"")
output_removed_trace_files(beforeTraceFiles, afterTraceFiles, logman)
return nbrRemovedTraceFiles
def clean_dumps(retainedDumpDays, local_dbinstance, sqlman, logman):
path = cdalias('cdglo', local_dbinstance)+"/sapcontrol/snapshots/"
with open(os.devnull, 'w') as devnull:
#nbrDumpsBefore = int(subprocess.check_output("ls "+path+"fullsysteminfodump* | wc -l", shell=True, stderr=devnull).strip(' '))
nbrDumpsBefore = int(run_command("ls "+path+"fullsysteminfodump* | wc -l").strip(' ')) #this might be a problem ... from https://docs.python.org/3/library/subprocess.html#subprocess.getoutput :
#The stdout and stderr arguments may not be supplied at the same time as capture_output. If you wish to capture and combine both streams into one, use stdout=PIPE and stderr=STDOUT instead of capture_output.
if not nbrDumpsBefore:
return 0
if sqlman.log:
log("find "+path+"fullsysteminfodump* -mtime +"+retainedDumpDays+" -delete", logman)
if sqlman.execute:
#subprocess.check_output("find "+path+"fullsysteminfodump* -mtime +"+retainedDumpDays+" -delete", shell=True, stderr=devnull)
dummyout = run_command("find "+path+"fullsysteminfodump* -mtime +"+retainedDumpDays+" -delete")
#nbrDumpsAfter = int(subprocess.check_output("ls "+path+"fullsysteminfodump* | wc -l", shell=True, stderr=devnull).strip(' '))
nbrDumpsAfter = int(run_command("ls "+path+"fullsysteminfodump* | wc -l").strip(' '))
return nbrDumpsBefore - nbrDumpsAfter
def output_removed_trace_files(before, after, logman):
beforeLines = before.splitlines(1)
afterLines = after.splitlines(1)
beforeFiles = [line.strip('\n').strip('|').strip(' ') for line in beforeLines]
afterFiles = [line.strip('\n').strip('|').strip(' ') for line in afterLines]
nbrTracesBefore = len(beforeFiles)
nbrTracesAfter = len(afterFiles)
nbrRemoved = nbrTracesBefore - nbrTracesAfter
if nbrRemoved > 0:
log("\nREMOVED ("+str(nbrRemoved)+"):", logman)
for beforeFile in beforeFiles:
if beforeFile not in afterFiles:
log(beforeFile, logman)
log("\n", logman)
def clean_alerts(minRetainedAlertDays, outputAlerts, outputDeletedAlerts, sqlman, logman):
try:
#nbrAlertsBefore = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM _sys_statistics.statistics_alerts_base\"", shell=True).strip(' '))
nbrAlertsBefore = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM _sys_statistics.statistics_alerts_base\"").strip(' '))
except:
log("\nERROR: The user represented by the key "+sqlman.key+" could not find amount of alerts. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the object privilege SELECT on the table _sys_statistics.statistics_alerts_base.\n", logman, True)
os._exit(1)
if nbrAlertsBefore > 10000 and (outputAlerts or outputDeletedAlerts):
outputAlerts = False
outputDeletedAlerts = False
log("INFO: The flags -ao and -ad were changed to false since there are too many alerts for printout.", logman)
if outputAlerts or outputDeletedAlerts:
#beforeAlerts = subprocess.check_output(sqlman.hdbsql_jAxU + " \"select SNAPSHOT_ID, ALERT_ID, ALERT_TIMESTAMP, ALERT_RATING from _SYS_STATISTICS.STATISTICS_ALERTS_BASE\"", shell=True)
beforeAlerts = run_command(sqlman.hdbsql_jAxU + " \"select SNAPSHOT_ID, ALERT_ID, ALERT_TIMESTAMP, ALERT_RATING from _SYS_STATISTICS.STATISTICS_ALERTS_BASE\"")
if outputAlerts:
log("\nBEFORE:\n"+beforeAlerts, logman)
sql = "DELETE FROM _SYS_STATISTICS.STATISTICS_ALERTS_BASE WHERE ALERT_TIMESTAMP < ADD_DAYS(CURRENT_TIMESTAMP, -"+str(minRetainedAlertDays)+")"
errorlog = "\nERROR: The user represented by the key "+sqlman.key+" could not delete alerts. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the object privilege DELETE on the table _sys_statistics.statistics_alerts_base.\n"
errorlog += "If there is another error (i.e. not insufficient privilege) then please try to execute \n"+sql+"\nin e.g. the SQL editor in SAP HANA Studio. If you get the same error then this has nothing to do with hanacleaner"
try_execute_sql(sql, errorlog, sqlman, logman)
#nbrAlertsAfter = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM _sys_statistics.statistics_alerts_base\"", shell=True).strip(' '))
nbrAlertsAfter = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM _sys_statistics.statistics_alerts_base\"").strip(' '))
if outputAlerts or outputDeletedAlerts:
#afterAlerts = subprocess.check_output(sqlman.hdbsql_jAxU + " \"select SNAPSHOT_ID, ALERT_ID, ALERT_TIMESTAMP, ALERT_RATING from _SYS_STATISTICS.STATISTICS_ALERTS_BASE\"", shell=True)
afterAlerts = run_command(sqlman.hdbsql_jAxU + " \"select SNAPSHOT_ID, ALERT_ID, ALERT_TIMESTAMP, ALERT_RATING from _SYS_STATISTICS.STATISTICS_ALERTS_BASE\"")
if outputAlerts:
log("\nAFTER:\n"+afterAlerts, logman)
if outputDeletedAlerts:
print_removed_entries(beforeAlerts, afterAlerts, logman)
return nbrAlertsBefore - nbrAlertsAfter
def clean_ini(minRetainedIniDays, version, revision, mrevision, sqlman, logman):
if version < 2 or revision < 30:
log("\nERROR: the -ir flag is only supported starting with SAP HANA 2.0 SPS03. You run on SAP HANA "+str(version)+" revision "+str(revision)+" maintenance revision "+str(mrevision), logman, True)
os._exit(1)
if version > 4:
log("\nERROR: the -ir flag is not supported any more with SAP HANA 2.0 SPS05. You run on SAP HANA "+str(version)+" revision "+str(revision)+" maintenance revision "+str(mrevision), logman, True)
# compare https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/2.0.05/en-US/fb097f2620c645d18064ce6b93c24a1e.html
# with https://help.sap.com/viewer/4fe29514fd584807ac9f2a04f6754767/2.0.04/en-US/fb097f2620c645d18064ce6b93c24a1e.html
os._exit(1)
try:
#nbrIniHistBefore = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM SYS.M_INIFILE_CONTENT_HISTORY\"", shell=True).strip(' '))
nbrIniHistBefore = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM SYS.M_INIFILE_CONTENT_HISTORY\"").strip(' '))
except:
log("\nERROR: The user represented by the key "+sqlman.key+" could not find amount of inifile history. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the object privilege SELECT on the view SYS.M_INIFILE_CONTENT_HISTORY.\n", logman, True)
os._exit(1)
d = datetime.today() - timedelta(days=minRetainedIniDays)
sql = "ALTER SYSTEM CLEAR INIFILE CONTENT HISTORY UNTIL '"+str(d)+"'"
errorlog = "\nERROR: The user represented by the key "+sqlman.key+" could not delete inifile history. \nOne possible reason for this is insufficient privilege.\n"
errorlog += "If there is another error (i.e. not insufficient privilege) then please try to execute \n"+sql+"\nin e.g. the SQL editor in SAP HANA Studio. If you get the same error then this has nothing to do with hanacleaner"
try_execute_sql(sql, errorlog, sqlman, logman)
#nbrIniHistAfter = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM SYS.M_INIFILE_CONTENT_HISTORY\"", shell=True).strip(' '))
nbrIniHistAfter = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM SYS.M_INIFILE_CONTENT_HISTORY\"").strip(' '))
return nbrIniHistBefore - nbrIniHistAfter
def clean_objlock(minRetainedObjLockDays, sqlman, logman):
try:
sql = "select count(*) FROM _SYS_STATISTICS.HOST_OBJECT_LOCK_STATISTICS_BASE WHERE OBJECT_NAME = '(unknown)'"
#nbrObjLockBefore = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \""+sql+"\"", shell=True).strip(' '))
nbrObjLockBefore = int(run_command(sqlman.hdbsql_jAQaxU + " \""+sql+"\"").strip(' '))
except:
errorlog = "\nERROR: The user represented by the key "+sqlman.key+" could not select object locks. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the object privilege SELECT on the table _SYS_STATISTICS.HOST_OBJECT_LOCK_STATISTICS_BASE.\n"
errorlog += "If there is another error (i.e. not insufficient privilege) then please try to execute \n"+sql+"\nin e.g. the SQL editor in SAP HANA Studio. If you get the same error then this has nothing to do with hanacleaner"
log(errorlog, logman)
sql = "DELETE FROM _SYS_STATISTICS.HOST_OBJECT_LOCK_STATISTICS_BASE WHERE OBJECT_NAME = '(unknown)' and SERVER_TIMESTAMP < ADD_DAYS(CURRENT_TIMESTAMP, -"+str(minRetainedObjLockDays)+")"
errorlog = "\nERROR: The user represented by the key "+sqlman.key+" could not delete object locks. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the object privilege DELETE on the table _SYS_STATISTICS.HOST_OBJECT_LOCK_STATISTICS_BASE.\n"
errorlog += "If there is another error (i.e. not insufficient privilege) then please try to execute \n"+sql+"\nin e.g. the SQL editor in SAP HANA Studio. If you get the same error then this has nothing to do with hanacleaner"
try_execute_sql(sql, errorlog, sqlman, logman)
#nbrObjLockAfter = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"select count(*) FROM _SYS_STATISTICS.HOST_OBJECT_LOCK_STATISTICS_BASE WHERE OBJECT_NAME = '(unknown)'\"", shell=True).strip(' '))
nbrObjLockAfter = int(run_command(sqlman.hdbsql_jAQaxU + " \"select count(*) FROM _SYS_STATISTICS.HOST_OBJECT_LOCK_STATISTICS_BASE WHERE OBJECT_NAME = '(unknown)'\"").strip(' '))
return nbrObjLockBefore - nbrObjLockAfter
def clean_objhist(objHistMaxSize, outputObjHist, sqlman, logman):
try:
#objHistSizeBefore = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"select disk_size from SYS.M_TABLE_PERSISTENCE_LOCATION_STATISTICS where table_name = 'OBJECT_HISTORY'\"", shell=True).strip(' '))
objHistSizeBefore = int(run_command(sqlman.hdbsql_jAQaxU + " \"select disk_size from SYS.M_TABLE_PERSISTENCE_LOCATION_STATISTICS where table_name = 'OBJECT_HISTORY'\"").strip(' '))
except:
log("\nERROR: The user represented by the key "+sqlman.key+" could not find size of object history. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the object privilege SELECT on the table SYS.M_TABLE_PERSISTENCE_LOCATION_STATISTICS.\n", logman, True)
os._exit(1)
if objHistSizeBefore > objHistMaxSize*1000000: #mb --> b
sql = "DELETE FROM _SYS_REPO.OBJECT_HISTORY WHERE (package_id, object_name, object_suffix, version_id) NOT IN (SELECT package_id, object_name, object_suffix, MAX(version_id) AS maxvid from _SYS_REPO.OBJECT_HISTORY GROUP BY package_id, object_name, object_suffix ORDER BY package_id, object_name, object_suffix)"
errorlog = "\nERROR: The user represented by the key "+sqlman.key+" could not clean the object history. \nOne possible reason for this is insufficient privilege, \ne.g. lack of the object privilege DELETE on the table _SYS_REPO.OBJECT_HISTORY.\n"
errorlog += "If there is another error (i.e. not insufficient privilege) then please try to execute \n"+sql+"\nin e.g. the SQL editor in SAP HANA Studio. If you get the same error then this has nothing to do with hanacleaner"
try_execute_sql(sql, errorlog, sqlman, logman)
#objHistSizeAfter = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"select disk_size from SYS.M_TABLE_PERSISTENCE_LOCATION_STATISTICS where table_name = 'OBJECT_HISTORY'\"", shell=True).strip(' '))
objHistSizeAfter = int(run_command(sqlman.hdbsql_jAQaxU + " \"select disk_size from SYS.M_TABLE_PERSISTENCE_LOCATION_STATISTICS where table_name = 'OBJECT_HISTORY'\"").strip(' '))
if outputObjHist:
log("Object History was:"+str(objHistSizeBefore/1000000)+" mb and is now "+str(objHistSizeAfter/1000000)+" mb.", logman)
return (objHistSizeBefore - objHistSizeAfter)/1000000
def max_filesystem_usage_in_percent(file_system, ignore_filesystems, logman):
log("Will now check most used memory in the file systems. If it hangs there is an issue with df -h, then see if the -fs flag helps.", logman)
maxPercentage = 0
lines = None
try:
#lines = subprocess.check_output("df -h -P -x fuse.gvfs-fuse-daemon "+file_system, shell=True).splitlines(1)
lines = run_command("df -h -P -x fuse.gvfs-fuse-daemon "+file_system).splitlines(1) # -x: telling df to ignore /root/.gvfs since normally <sid>adm lacking permissions, -P: Force output in one line for RedHat
except:
log("WARNING: Something went wrong executing df -h, \n therefore the most used memory in your file systems will not be checked. \n As a workaround it is possible to use the -fs flag to only take into account the most relevant file system.", logman)
if lines:
log("The most used filesystem is using ", logman)
used_percentages = []
for line in lines:
if not "Filesystem" in line and not "S.ficheros" in line and not "Dateisystem" in line: # english, spanish, german and ...
words = line.split()
if len(words) == 6:
filesystem_and_mount = [words[0].strip('\n'), words[5].strip('\n')]
elif len(words) == 5:
filesystem_and_mount = [words[0].strip('\n'), words[4].strip('\n')]
else:
log("ERROR, Unexpted number output columns from df -h: \n"+line, logman)
if len(words) == 6:
percentage = int(words[4].strip('%'))
if len(words) == 5:
percentage = int(words[3].strip('%'))
if len(words) > 1 and filesystem_and_mount[0] not in ignore_filesystems and filesystem_and_mount[1] not in ignore_filesystems:
used_percentages.append(percentage)
maxPercentage = max(used_percentages)
log(str(maxPercentage)+"%", logman)
return maxPercentage
def find_all(name, path, zipLinks):
result = []
if zipLinks:
pathes = os.walk(path, followlinks=True)
else:
pathes = os.walk(path)
for root, dirs, files in pathes:
if name in files:
result.append(os.path.join(root, name))
return result
def getNbrRows(schema, table, sqlman):
#return int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM "+schema+"."+table+" \"", shell=True).strip(' '))
return int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM "+schema+"."+table+" \"").strip(' '))
def getAdapterName(schema, table, sqlman):
#return subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT R.ADAPTER_NAME FROM SYS.REMOTE_SOURCES R JOIN SYS.VIRTUAL_TABLES V ON R.REMOTE_SOURCE_NAME = V.REMOTE_SOURCE_NAME WHERE V.SCHEMA_NAME = '"+schema+"' and TABLE_NAME = '"+table+"'\"", shell=True).strip(' ')
return run_command(sqlman.hdbsql_jAQaxU + " \"SELECT R.ADAPTER_NAME FROM SYS.REMOTE_SOURCES R JOIN SYS.VIRTUAL_TABLES V ON R.REMOTE_SOURCE_NAME = V.REMOTE_SOURCE_NAME WHERE V.SCHEMA_NAME = '"+schema+"' and TABLE_NAME = '"+table+"'\"").strip(' ')
def zipBackupLogs(zipBackupLogsSizeLimit, zipBackupPath, zipLinks, zipOut, zipKeep, sqlman, logman):
backup_log_pathes = find_all("backup.log", zipBackupPath, zipLinks)
backint_log_pathes = find_all("backint.log", zipBackupPath, zipLinks)
log_pathes = backup_log_pathes + backint_log_pathes
nZipped = 0
for aLog in log_pathes:
if os.path.getsize(aLog)/1000000.0 > zipBackupLogsSizeLimit:
newname = (aLog.strip(".log")+"_compressed_"+datetime.now().strftime("%Y-%m-%d %H:%M:%S")+".tar.gz").replace(":","-").replace(" ","_").replace("//", "/")
tempname = newname.replace(".tar.gz", ".log")
if sqlman.log:
log("mv "+aLog+" "+tempname, logman)
log("tar -czPf "+newname+" "+tempname, logman) # P to avoid annoying error message
log("rm "+tempname, logman)
if not zipKeep:
log("rm "+newname, logman)
if sqlman.execute:
#subprocess.check_output("mv "+aLog+" "+tempname, shell=True)
#subprocess.check_output("tar -czPf "+newname+" "+tempname, shell=True) # P to avoid annoying error message
#subprocess.check_output("rm "+tempname, shell=True)
dummyout = run_command("mv "+aLog+" "+tempname)
dummyout = run_command("tar -czPf "+newname+" "+tempname) # P to avoid annoying error message
dummyout = run_command("rm "+tempname)
if zipOut:
log(aLog+" was compressed to "+newname+" and then removed", logman)
nZipped += 1
if not zipKeep:
#subprocess.check_output("rm "+newname, shell=True)
dummyout = run_command("rm "+newname)
return nZipped
def cdalias(alias, local_dbinstance): # alias e.g. cdtrace, cdhdb, ...
su_cmd = ''
#whoami = subprocess.check_output('whoami', shell=True).replace('\n','')
whoami = run_command('whoami').replace('\n','')
if whoami.lower() == 'root':
sidadm = get_sid().lower()+'adm'
su_cmd = 'su - '+sidadm+' '
alias_cmd = su_cmd+'/bin/bash -l -c \'alias '+alias+'\''
#command_run = subprocess.check_output(alias_cmd, shell=True)
command_run = run_command(alias_cmd)
pieces = re.sub(r'.*cd ','',command_run).strip("\n").strip("'").split("/") #to remove ANSI escape codes (only needed in few systems)
path = ''
for piece in pieces:
if piece and piece[0] == '$':
piece_cmd = su_cmd+'/bin/bash -l -c'+" \' echo "+piece+'\''
#piece = subprocess.check_output(piece_cmd, shell=True).strip("\n")
piece = run_command(piece_cmd)
path = path + '/' + piece + '/'
path = path.replace("[0-9][0-9]", local_dbinstance) # if /bin/bash shows strange HDB[0-9][0-9] we force correct instance on it
return path
def reclaim_logsegments(maxFreeLogsegments, sqlman, logman):
#nTotFreeLogsegmentsBefore = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM SYS.M_LOG_SEGMENTS WHERE STATE = 'Free'\"", shell=True, stderr=subprocess.STDOUT).strip(' '))
nTotFreeLogsegmentsBefore = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM SYS.M_LOG_SEGMENTS WHERE STATE = 'Free'\"").strip(' ')) #this might be a problem ... from https://docs.python.org/3/library/subprocess.html#subprocess.getoutput :
#The stdout and stderr arguments may not be supplied at the same time as capture_output. If you wish to capture and combine both streams into one, use stdout=PIPE and stderr=STDOUT instead of capture_output.
if nTotFreeLogsegmentsBefore == 0:
return 0
#listOfPorts = subprocess.check_output(sqlman.hdbsql_jAaxU + " \"SELECT DISTINCT PORT FROM SYS.M_LOG_SEGMENTS\"", shell=True).splitlines(1)
listOfPorts = run_command(sqlman.hdbsql_jAaxU + " \"SELECT DISTINCT PORT FROM SYS.M_LOG_SEGMENTS\"").splitlines(1)
listOfPorts = [port.strip('\n').strip('|').strip(' ') for port in listOfPorts]
nFreeLogsegmentsPerServices = []
for port in listOfPorts:
#nFreeLogs = int(subprocess.check_output(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM SYS.M_LOG_SEGMENTS WHERE STATE = 'Free' AND PORT = '"+port+"'\"", shell=True).strip(' '))
nFreeLogs = int(run_command(sqlman.hdbsql_jAQaxU + " \"SELECT COUNT(*) FROM SYS.M_LOG_SEGMENTS WHERE STATE = 'Free' AND PORT = '"+port+"'\"").strip(' '))
nFreeLogsegmentsPerServices.append(nFreeLogs)
if max(nFreeLogsegmentsPerServices) > maxFreeLogsegments:
sql = "ALTER SYSTEM RECLAIM LOG"