-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathhanachecker.py
More file actions
1389 lines (1319 loc) · 92.5 KB
/
Copy pathhanachecker.py
File metadata and controls
1389 lines (1319 loc) · 92.5 KB
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
# -*- coding: utf-8 -*-
from datetime import datetime
import sys, time, os, subprocess, re
import zipfile
def printHelp():
print(" ")
print("DESCRIPTION: ")
print(' The HANA Checker executes SQL: "HANA_Configuration_MiniChecks" (See SAP Note 1969700). For every "potential critical" mini-check, ')
print(' i.e. where the column C has an X, it sends out an email to the email address specified for that particular mini-check. This can run "forever" ')
print(" with a specified interval. See also SAP Note 1999993. ")
print(" ")
print("INPUT ARGUMENTS: ")
print(" *** MINI-CHECKS, PARAMETER-CHECKS and EMAILS mapping *** ")
print(" -<CHID> mini-check to email address, if that particular mini-check specifed by the flag is potential critical an email is sent to the address ")
print(" specified by the value of the flag, e.g. -M0230 peter@ourcompany.com, then Peter will get an email if CHID 230 shows an X in C. ")
print(" -cg mini-check groups, groupings of mini-checks with responsible email addresses associated, example: ")
print(" -cg M0001-M1500,peter@ourcompany.com,M1501-M3000,sara@ourcompany.com,S0100-S0200,chris@ourcompany.com ")
print(" -pe parameter emails, a comma seperated list of emails that catches all parameter checks, default '' (not used) ")
print(" Note: This only makes sense if HANA_Configuration_Parameters is included in the input, either with -mf, or -ct P ")
print(" -se sql emails, a comma seperated list of emails that catches all sql statements with recommendation in SAP Note 2000002, default '' (not used)")
print(" Note: This only makes sense if HANA_SQL_SQLCache_TopLists is included in the input, either with -mf, or -ct R ")
print(" -ee error emails, a comma seperated list of emails that recieves the most important HANAChecker errors, default '' (not used) ")
print(" -is ignore check_why_set parameter, if this is true parameter with recommended value 'check why set' are ignored, default false ")
print(" -ips ignore parameters, comma seperated list of parameters to be ignored, default '' (not used) ")
print(" -at active threads, set MIN_ACTIVE_THREADS in modification section in the Call Stacks Minichecks, default '' (not used, i.e. 0.2) ")
print(" -abs ABAP schema, to define the ABAP schema in case -ct A is used, default '' (not used) ")
print(" -ip ignore dublicated parameter, parameters that are set to same value in different layers will only be mentioned once, default true ")
print(" -oe one email [true/false], true: only one email is sent per email address, false: one email is sent per critical mini check, default: false ")
print(" -as always send [true/false], true: all email addresses will be send at least a notification email, even if none of the mini-checks assigned ")
print(" to the emails were potential critical, default: false ")
print(" -ca catch all emails, the email addresses specified by the -ca flag recieve an email about each potential critical mini-check (also from ")
print(" parameter checks and SQL checks, if any), default: '' (not used) ")
print(" -ic ignore checks, a list of mini-check CHIDs (seperated by commas) that should be ignored by the catch all emails (they will however ")
print(" still be included in the log files), default '' (not used) ")
print(" -il ignore list, a list of mini-check CHIDs (seperated by commas) that are ignored (i.e. not even in log files), default '' (not used) ")
print(" ---- HOST ---- ")
print(" -vlh virtual local host, if hanachecker runs on a virtual host this can be specified, default: '' (physical host is assumed) ")
print(" *** INPUT *** ")
print(" -mf full path(s) of a mini-check file(s) ")
print(" Example: -mf /tmp/SQLStatements/HANA_Configuration_MiniChecks_1.00.102.01+.txt ")
print(" Example: -mf /tmp/SQLStatements/HANA_Configuration_MiniChecks_1.00.120+.txt,/tmp/SQLStatements/HANA_Security_MiniChecks.txt ")
print(" -zf full path to SQLStatement.zip (cannot be used together with -mf and -zff, and must be used together with -ct), default '' (not used) ")
print(" Example: -zf SQLStatements.zip (if the zip file is located in same directory as hanachecker.py) ")
print(" -zff full path to a folder with .zip files (cannot be used together with -mf and -zf, and must be used together with -ct), default '' (not used)")
print(" Example: -zff zip_files (if the folder with zip files is located in same directory as hanachecker.py) ")
print(" -ct check types, specifies what types of mini-checks to executes (must be used together with -zf), default '' (not used) ")
print(" Example: -ct M,I,S,T,P,C,R,A (in this example all possible mini-check types; mini, internal, security, trace, parameter, call stacks, ")
print(" SQL recommendations, and ABAP would be executed) ")
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 in the file that ")
print(" do not start with a flag (a minus) are considered comments, default: '' (not used) ")
print(" *** CLOUD *** ")
print(" -cdb cloud data base, if a HANACloud database name is specified, HANAChecker assumes the key (-k) is pointing to a HANACloud instance, ")
print(" default: '' (HANAChecker assumes the key (-k) points to the on-prem HANA instance of the current server) ")
print(" *** OUTPUT *** ")
print(" -od output directory, full path of the folder where the log files will end up (if not exist it will be created), ")
print(" default: '/tmp/hanachecker_output' ")
print(" -or output retention days, logs in the path specified with -od are only saved for this number of days, default: -1 (not used) ")
print(" -so standard out switch, 1: write to std out, 0: do not write to std out, default: 1 ")
print(" -oc output configuration [true/false], logs, in the emails send out if -as is set, all parameters set by the flags and where the flags were ")
print(" set, i.e. what flag file(one of the files listed in -ff) or if it was set via a flag specified on the command line, default = false ")
print(" *** EMAIL *** ")
print(" Possibly there is no need for any -en* flag. This depends on the configuration of your email client. If you recieve an email from this ")
print(' echo "Text in email" | mailx -s "subject" <your email address> ')
print(" then there is no need for any -en* flag. Contact your Linux expert for more information. ")
print(" -enc email client, for example mail, mailx, mutt, ..., default: mailx ")
print(" NOTE: If you want to use HANAChecker just to write log files, without sending emails, then specify this flag empty, -enc '', and provide a ")
print(" dummy email address for the -ca flag to write to file all potential critical mini-checks ")
print(' NOTE: For e.g. mailx to work you have to install the linux program "sendmail" and add something like DSsmtp.intra.ourcompany.com in the ')
print(" file sendmail.cf in /etc/mail/, see https://www.systutorials.com/5167/sending-email-using-mailx-in-linux-through-internal-smtp/ ")
print(" -ens sender's email, to explicitly specify sender's email address, this might not be needed, default: (configured sender's email used) ")
print(" This is the same as adding -S from=<senders email address> to the echo statement above ")
print(" -enm mail server, to explicitly specify mail server, this might not be needed, default: (configured mail server used) ")
print(" This is the same as adding -S smtp=smtp://<email server> to the echo statement above ")
print(" -en depricated! email notification, <sender's email>,<mail server> example: me@ourcompany.com,smtp.intra.ourcompany.com Don't use! ")
print(" ---- INSTANCE ONLINE CHECK ---- ")
print(" -oi online test interval [days], < 0: HANAChecker does not check if online or secondary, default: -1 (not used) ")
print(" = 0: if not online or not primary HANAChecker 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(" *** ADMIN *** ")
print(" -hci hana checker interval [days], number days that HANA Checker waits before it checks again, default: -1 (exits after 1 cycle) ")
print(" NOTE: Do NOT use if you run hanachecker in a cron job! ")
print(" -ssl turns on ssl certificate [true/false], makes it possible to use SAP HANA Checker despite SSL, default: false ")
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(" -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(" Example: -k PQLSYSDB -dbs SYSTEMDB,PQL ")
print(" ")
print(" ")
print(" EXAMPLE: The default mail client mailx is used ")
print(" python hanachecker.py -k T1KEY -zf SQLStatements.zip -ct M,S -M1142 chris@du.my,lena@du.my -M1165 per@du.my,lena@du.my -S0120 chris@du.my -as true -oe true ")
print(" ")
print(" TODO: test ")
print(" CALL SYS.STATISTICSSERVER_SENDMAIL_DEV('SMTP',25,'emailfrom','emailto1,emailto2','Test mail from HANA system subject','Test body from HANA, body',?); ")
print(" ")
print("AUTHOR: Christian Hansen ")
print(" ")
print(" ")
os._exit(1)
def printDisclaimer():
print(" ")
print("ANY USAGE OF HANACHECKER ASSUMES THAT YOU HAVE UNDERSTOOD AND AGREED THAT: ")
print(" 1. HANAChecker is NOT SAP official software, so normal SAP support of HANAChecker cannot be assumed ")
print(" 2. HANAChecker is open source ")
print(' 3. HANAChecker is provided "as is" ')
print(' 4. HANAChecker is to be used on "your own risk" ')
print(" 5. HANAChecker is a one-man's hobby (developed, maintained and supported only during non-working hours) ")
print(" 6 All HANAChecker documentations have to be read and understood before any usage: ")
print(" a) The .pdf file that can be downloaded at the bottom of SAP Note 1999993 ")
print(" c) All output from executing ")
print(" python hanachecker.py --help ")
print(" 7. HANAChecker is not providing any recommendations, all flags shown in the documentation (see point 6.) are only examples ")
os._exit(1)
############ GLOBAL VARIABLES ##############
chidMax = 9999
######################## DEFINE CLASSES ##################################
class EmailSender:
def __init__(self, emailClient, senderEmail, mailServer):
self.emailClient = emailClient
self.senderEmail = senderEmail
self.mailServer = mailServer
def printEmailSender(self):
print("Email Client: ", self.emailClient, " Sender Email: ", self.senderEmail, " Mail Server: ", self.mailServer)
class MiniCheck:
def __init__(self, CHID, Area, Description, Host, Port, Count, ActiveThreads, LastOccurrence, Value, Expectation, C, SAPNote, TraceText):
self.CHID = CHID
self.Type = get_check_type(CHID)
self.Number = get_check_number(CHID)
self.Area = Area
self.Description = Description
self.Host = Host
self.Port = Port
self.Count = Count
self.ActiveThreads = ActiveThreads
self.LastOccurrence = LastOccurrence
self.Value = Value
self.Expectation = Expectation
self.C = C
self.SAPNote = SAPNote
self.TraceText = TraceText
def summary(self):
sum = "\nMini Check ID "+str(self.CHID)
if self.Area:
sum += " Area: "+self.Area
if self.Description:
sum += " Description: "+self.Description
if self.Host:
sum += " Host: "+self.Host
if self.Port:
sum += " Port: "+self.Port
if self.Count:
sum += " Count: "+self.Count
if self.ActiveThreads:
sum += " Active Threads: "+self.ActiveThreads
if self.LastOccurrence:
sum += " LastOccurrence: "+self.LastOccurrence
if self.Value:
sum += " Value: "+self.Value
if self.Expectation:
sum += " Expectation: "+self.Expectation
if self.C:
sum += " C: "+self.C
if self.SAPNote:
sum += " SAPNote: "+self.SAPNote
if self.TraceText:
sum += " TraceText: "+self.TraceText
return sum
def printMiniCheck(self):
print(self.summary())
class ParameterCheck:
def __init__(self, IniFile, Section, Parameter, Priority, ConfiguredValue, RecommendedValue, SAPNote, ConfiguredLayer, Revision, Environment, CpuThreads, CpuFrequency, NumaNodes, GlobalAllocationLimit, WorkerNodes, LogVolumeSize):
self.IniFile = IniFile
self.Section = Section
self.Parameter = Parameter
self.Priority = Priority
self.ConfiguredValue = ConfiguredValue
self.RecommendedValue = RecommendedValue
self.SAPNote = SAPNote
self.ConfiguredLayer = ConfiguredLayer
self.Revision = Revision
self.Environment = Environment
self.CpuThreads = CpuThreads
self.CpuFrequency = CpuFrequency
self.NumaNodes = NumaNodes
self.GlobalAllocationLimit = GlobalAllocationLimit
self.WorkerNodes = WorkerNodes
self.LogVolumeSize = LogVolumeSize
def summary(self):
sum = "\nParameter '"+self.Parameter+"' in configuration file '"+self.IniFile+"' and in section '"+self.Section+"'"
if "-- not set --" in self.ConfiguredValue:
sum += ", is not configured"
else:
sum += ", is configured to '"+self.ConfiguredValue+"' in "+self.ConfiguredLayer+" layer"
if "-- check why set --" in self.RecommendedValue:
sum += ", but has no recommended value, so please check why it is set."
else:
sum += ", but the recommendation is '"+self.RecommendedValue+"'."
if self.SAPNote:
sum += " For more information see SAP Note "+self.SAPNote+"."
if self.Priority:
sum += " This has priority "+self.Priority+"."
sum += "\nFollowing scenario has been taken into account:\n"
if self.Revision:
sum += "Revision: "+self.Revision
if self.Environment:
sum += " Environment: "+self.Environment
if self.CpuThreads:
sum += " CpuThreads: "+self.CpuThreads
if self.CpuFrequency:
sum += " CpuFrequency: "+self.CpuFrequency
if self.NumaNodes:
sum += " NumaNodes: "+self.NumaNodes
if self.GlobalAllocationLimit:
sum += " GlobalAllocationLimit: "+self.GlobalAllocationLimit
if self.WorkerNodes:
sum += " WorkerNodes: "+self.WorkerNodes
if self.LogVolumeSize:
sum += " LogVolumeSize: "+self.LogVolumeSize
return sum
class SQLWithRecommendation:
def __init__(self, Hash, Type, Origin, Engine):
self.Hash = Hash
sql_types = {"AI":"ALTER INDEX", "AS":"ALTER SYSTEM", "AT":"ALTER TABLE", "AL":"ALTER", "CA":"CALL", "CI":"CREATE INDEX", "CO":"COMMIT", "CR":"CREATE", "DE":"DELETE", "DI":"DROP INDEX", "DM":"Data Modification", "DT":"DROP TABLE", "DR":"DROP", "EX":"EXECUTE", "IN":"INSERT", "RE":"REPLACE", "RO":"ROLLBACK", "SU":"SELECT FOR UPDATE", "SE":"SELECT", "ST":"START TASK", "TR":"TRUNCATE", "UP":"UPDATE", "US":"UPSERT", "WI":"WITH"}
if Type in sql_types:
self.Type = sql_types[Type]
else:
self.Type = " Unknown Type "
self.Type = Type
sql_origins = {"AB": "ABAP", "BA": "Backup Scheduler", "CO": "SAP HANA Cockpit", "CR": "Crystal reports", "DS": "Data Services", "EW": "Extended warehouse management", "FC": "Business Objects", "HA": "SAP Host Agenet", "HQ": "hdbsql", "HS": "SAP HANA Studio", "IA": "Information Access", "IF": "Informatica", "IN": "Internal Request", "IS": "indexserver", "LU": "Lumira", "MS": "MicroStrategy", "PA": "Patrol", "PY": "Python", "RT": "R3trans", "SC": "sapdbctrl", "SD": "SDA", "ST": "Statistics server", "TA": "Tableau", "UN": "unknown", "VO": "SAP HANA Vora", "WI": "Web Intelligence", "XC": "XS Classic", "XS": "XSA", "XI": "XimoStudio", "XM": "XML/A"}
if Origin in sql_origins:
self.Origin = sql_origins[Origin]
else:
self.Origin = " No SQL Origin Specified "
engines = {"C":"Column", "E":"ESX", "H":"HEX", "O":"OLAP", "R":"Row"}
if Engine in engines:
self.Engine = engines[Engine]
else:
self.Engine = " Unknown Engine "
def summary(self):
sum = "\nSQL statement "+self.Hash+" is one of the most expensive statements in the SQL cache and there is a recommendation available in SAP Note 2000002.\n"
sum += "This SQL statement is of type "+self.Type+", originates from "+self.Origin
if self.Engine:
sum += ", and executed by the "+self.Engine+" engine."
else:
sum += "."
return sum
class LogManager:
def __init__(self, std_out, out_dir, SID, emailSender = "", db = ""):
self.std_out = std_out
self.out_dir = out_dir
self.SID = SID
self.emailSender = emailSender
self.db = db
class SQLManager:
def __init__(self, hdbsql_string, dbuserkey, dbase):
self.key = dbuserkey
self.db = dbase
if len(dbase) > 1:
self.hdbsql_jAU = hdbsql_string + " -j -A -U " + self.key + " -d " + self.db
self.hdbsql_jAaxU = hdbsql_string + " -j -A -a -x -U " + self.key + " -d " + self.db
else:
self.hdbsql_jAU = hdbsql_string + " -j -A -U " + self.key
self.hdbsql_jAaxU = hdbsql_string + " -j -A -a -x -U " + self.key
######################## DEFINE FUNCTIONS ################################
def run_command(cmd, ignore_error = False): #ignore_error only makes sense in Python 2
if sys.version_info[0] == 2:
if ignore_error:
out = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT).strip("\n")
else:
out = subprocess.check_output(cmd, shell=True).strip("\n")
elif sys.version_info[0] == 3: #run().stderr is ignored
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 is_integer(s):
try:
int(s)
return True
except ValueError:
return False
def is_number(s):
try:
float(s)
return True
except ValueError:
return False
def is_email(s):
s = s.split('@')
if not len(s) == 2:
return False
return '.' in s[1]
def get_sid():
SID = run_command('echo $SAPSYSTEMNAME').upper()
return SID
def cdalias(alias, local_dbinstance): # alias e.g. cdtrace, cdhdb, ...
su_cmd = ''
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+'\'|tail -1' #tail -1 to remove other messages from e.g. profile.local
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+'\'|tail -1' #tail -1 to remove other messages from e.g. profile.local
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 checkUserKey(dbuserkey, virtual_local_host, cloud_data_base, logman, error_emails):
try: #check if key exists
key_environment = run_command('''hdbuserstore LIST '''+dbuserkey)
if "NOT FOUND" in key_environment:
message = "ERROR, the key "+dbuserkey+" is not maintained in hdbuserstore."
log_with_emails(message, logman, error_emails)
os._exit(1)
except:
message = "ERROR, the key "+dbuserkey+" is not maintained in hdbuserstore."
log_with_emails(message, logman, error_emails)
os._exit(1)
if cloud_data_base: #if cloud, only need to check that DATABASE: is not in key
if "DATABASE:" in key_environment:
message = "KEY ERROR: if -cdb is used, they key may not contain the DATABASE. Please recreate the key without @<DATABASE>."
log_with_emails(message, logman, error_emails)
os._exit(1)
else: #if on-prem, also check if key is according to local host, i.e. "hostname"
ENV = key_environment.split('\n')[1].replace(' ENV : ','').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
local_host = run_command("hostname").replace('\n','') if virtual_local_host == "" else virtual_local_host
if not is_integer(local_host.split('.')[0]): #first check that it is not an IP address
local_host = local_host.split('.')[0] #if full host name is specified in the local host (or virtual host), only the first part is used
if not local_host in key_hosts:
message = "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)"
log_with_emails(message, logman, error_emails)
os._exit(1)
def checkAndConvertBooleanFlag(boolean, flagstring):
boolean = boolean.lower()
if boolean not in ("false", "true"):
print("INPUT ERROR: ", flagstring, " must be either 'true' or 'false'. Please see --help for more information.")
os._exit(1)
boolean = True if boolean == "true" else False
return boolean
def convertToCheckId(checkType, checkNumber):
return checkType+'0'*(4-len(str(checkNumber)))+str(checkNumber)
def is_check_id(checkString):
if not len(checkString) == 5:
return False
if checkString[0] not in ['M', 'I', 'S', 'T', 'C', 'A']:
return False
if not is_integer(checkString[1:5].lstrip('0')):
return False
return True
def get_check_type(checkString):
if not is_check_id(checkString):
print("ERROR: checkString, "+checkString+", in get_check_type is not a check ID")
os._exit(1)
return checkString[0]
def get_check_number(checkString):
if not is_check_id(checkString):
print("ERROR: checkString in get_check_numer is not a check ID")
os._exit(1)
return int(checkString[1:5].lstrip('0'))
def checkIfAcceptedFlag(word):
if not is_check_id(word.strip('-')):
if not word in ["-h", "--help", "-d", "--disclaimer", "-cg", "-pe", "-se", "-ee", "-is", "-ips", "-at", "-abs", "-ip", "-oe", "-as", "-ca", "-ic", "-il", "-vlh", "-mf", "-zf", "-zff", "-ct", "-ff", "-cdb", "-od", "-or", "-so", "-oc", "-enc", "-ens", "-enm", "-en", "-hci", "-ssl", "-oi", "-k", "-dbs"]:
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, delimeter = ','):
if flag == flag_string:
parameter = [x for x in flag_value.split(delimeter)]
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, delimeter = ','):
if flag_string in sysargv:
parameter = [x for x in sysargv[ sysargv.index(flag_string) + 1 ].split(delimeter)]
flag_log[flag_string] = [','.join(parameter), "command line"]
return parameter
def clean_output(minRetainedOutputDays, sqlman, logman):
path = logman.out_dir
nFilesBefore = len([name for name in os.listdir(path) if os.path.isfile(os.path.join(path, name))])
dummyout = run_command("find "+path+"/hanacheckerlog* -mtime +"+str(minRetainedOutputDays)+" -delete")
nFilesAfter = len([name for name in os.listdir(path) if os.path.isfile(os.path.join(path, name))])
return nFilesBefore - nFilesAfter
def log(message, logman, file_name = "", recieversEmail = ""):
logMessage = '"'+message+'"'
if logman.emailSender:
logMessage = logMessage + " is sent to " + recieversEmail if recieversEmail else message
if logman.std_out:
print(logMessage)
if file_name == "":
file_name = "hanacheckerlog"
logfile = open(logman.out_dir+"/"+file_name+"_"+datetime.now().strftime("%Y-%m-%d_%H-%M-%S"+".txt").replace(" ", "_"), "a")
logfile.write(logMessage+"\n")
logfile.flush()
logfile.close()
if recieversEmail:
if logman.emailSender: #if -enc was specified as empty, then emailSender is None, so no emails will be send
mailstring = 'echo "'+message.replace('"','')+'" | '+logman.emailSender.emailClient+' -s "HANAChecker: Potential Critical Situation(s) '+logman.db+"@"+logman.SID+'!" '
if logman.emailSender.mailServer:
mailstring += ' -S smtp=smtp://'+logman.emailSender.mailServer+' '
if logman.emailSender.senderEmail:
mailstring += ' -S from="'+logman.emailSender.senderEmail+'" '
mailstring += recieversEmail
dummyout = run_command(mailstring)
def log_with_emails(message, logman, error_emails):
if error_emails:
for error_email in error_emails:
log(message, logman, recieversEmail = error_email)
else:
log(message, logman)
def hana_version_rev_mrev(sqlman):
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):
print("ERROR: something went wrong checking hana revision.")
os._exit(1)
return [int(hanaver), int(hanarev), int(hanamrev)]
def get_file_revision_number_str(file_name, base_file_name, tmp_sql_dir):
if '+' not in file_name:
file_revision = '0'
else:
file_revision = file_name.split(tmp_sql_dir+base_file_name+'_')[1].split('+')[0]
if not file_revision:
file_revision = '0'
file_revision = file_revision.split('.')
if len(file_revision) == 3:
file_revision = file_revision[0]+file_revision[1]+file_revision[2]+'00' #then maintanance revision is 00
elif len(file_revision) == 4:
file_revision = file_revision[0]+file_revision[1]+file_revision[2]+file_revision[3]
else:
print("ERROR: Something went wrong with file revision.")
os._exit(1)
return file_revision
def get_revision_number_str(version, revision, mrevision):
mrevision_str = str(mrevision)
if mrevision < 10:
mrevision_str = '0'+str(mrevision)
revision_str = '00'+str(revision)
if revision < 10:
revision_str = '0000'+str(revision)
elif revision < 100:
revision_str = '000'+str(revision)
return str(version)+revision_str+mrevision_str
def getFileVersion(base_file_name, tmp_sql_dir, version, revision, mrevision):
revision_number_str = get_revision_number_str(version, revision, mrevision)
if int(revision_number_str) >= 40000000: #then, cloud
try:
output = run_command('ls '+tmp_sql_dir+base_file_name+'*SHC*', True)
except Exception as e:
output = str(e.output)
output = output.splitlines(1)
files = [f.strip('\n') for f in output if not 'cannot access' in f]
if len(files) == 0:
print("ERROR: There were no SHC files found with name "+base_file_name+", so please modify -ct")
os._exit(1)
if len(files) > 1:
print("COMPATIBILITY ERROR: There were are now multiple SHC files found with name "+base_file_name+", files = ", files)
print("This needs to be fixed in hanachecker.py so please let christian.hansen01@sap.com know, thanks!")
os._exit(1)
return files[0]
else: #then, on-prem
try:
output = run_command('ls '+tmp_sql_dir+base_file_name+'_[12]* '+tmp_sql_dir+base_file_name+'.txt', True) #[12] removes SHC
except Exception as e:
output = str(e.output)
output = output.splitlines(1)
files = [f.strip('\n') for f in output if not 'cannot access' in f]
if len(files) == 0:
print("ERROR: There were no on-premise files found with name "+base_file_name+", files = ", files)
os._exit(1)
chosen_file_name = files[0]
for file_name in files:
file_revision_number_str = get_file_revision_number_str(file_name, base_file_name, tmp_sql_dir)
choosen_file_revision_number_str = get_file_revision_number_str(chosen_file_name, base_file_name, tmp_sql_dir)
if int(file_revision_number_str) <= int(revision_number_str) and int(file_revision_number_str) > int(choosen_file_revision_number_str):
chosen_file_name = file_name
return chosen_file_name
def getZipFiles(zip_file, zip_folder):
zip_files = []
if zip_file:
zip_files.append(zip_file)
else:
try:
output = run_command('ls '+zip_folder+'/*.zip', True)
except Exception as e:
output = str(e.output)
output = output.splitlines(1)
zip_files = [f.strip('\n') for f in output if not 'cannot access' in f]
if len(zip_files) == 0:
print("ERROR: There were no .zip files found in "+zip_folder)
os._exit(1)
return zip_files
def getCheckFiles(tmp_sql_dir, check_types, version, revision, mrevision, active_threads, abap_schema):
check_files = []
for ct in check_types:
if ct == 'M':
check_files.append(getFileVersion('HANA_Configuration_MiniChecks', tmp_sql_dir, version, revision, mrevision))
elif ct == 'I':
check_files.append(getFileVersion('HANA_Configuration_MiniChecks_Internal', tmp_sql_dir, version, revision, mrevision))
elif ct == 'S':
check_files.append(getFileVersion('HANA_Security_MiniChecks', tmp_sql_dir, version, revision, mrevision))
elif ct == 'T':
check_files.append(getFileVersion('HANA_TraceFiles_MiniChecks', tmp_sql_dir, version, revision, mrevision))
elif ct == 'P':
check_files.append(getFileVersion('HANA_Configuration_Parameters', tmp_sql_dir, version, revision, mrevision))
elif ct == 'C':
revision_number_str = get_revision_number_str(version, revision, mrevision)
if int(revision_number_str) < 200040:
print("COMPATIBILITY ERRROR: There are no Call Stack Mini-Checks for your SAP HANA revision, so you cannot use -ct C")
os._exit(1)
if active_threads: # then must change modification section
cs_mc_file = getFileVersion('HANA_Threads_Callstacks_MiniChecks', tmp_sql_dir, version, revision, mrevision).strip('\n')
cs_mc_file_temp = tmp_sql_dir+"HANA_Threads_Callstacks_MiniChecks_Temp.txt"
fin = open(cs_mc_file, "rt")
fout = open(cs_mc_file_temp, "wt") # will be removed when tmp_sql_dir is removed
for line in fin:
fout.write(line.replace('0.2 MIN_ACTIVE_THREADS', active_threads+' MIN_ACTIVE_THREADS'))
fin.close()
fout.close()
check_files.append(cs_mc_file_temp)
else:
check_files.append(getFileVersion('HANA_Threads_Callstacks_MiniChecks', tmp_sql_dir, version, revision, mrevision))
elif ct == 'R':
check_files.append(getFileVersion('HANA_SQL_SQLCache_TopLists', tmp_sql_dir, version, revision, mrevision))
elif ct == 'A':
abap_mc_file = getFileVersion('HANA_ABAP_MiniChecks', tmp_sql_dir, version, revision, mrevision).strip('\n')
abap_mc_file_temp = tmp_sql_dir+"HANA_ABAP_MiniChecks_Temp.txt"
fin = open(abap_mc_file, "rt")
fout = open(abap_mc_file_temp, "wt") # will be removed when tmp_sql_dir is removed
fout.write("SET SCHEMA "+abap_schema+"; ")
for line in fin:
fout.write(line)
fin.close()
fout.close()
check_files.append(abap_mc_file_temp)
return check_files
def getCriticalChecks(check_files, ignore_check_why_set, ignore_parameters, ignore_dublicated_parameter, ignore_checks, sqlman, logman):
revision = ''
environment = ''
cputhreads = ''
cpufrequency = ''
numa_nodes = ''
global_allocation_limit = ''
worker_nodes = ''
log_volume_size = ''
critical_mini_checks = []
critical_parameter_checks = []
sqls_with_recommendation = []
for check_file in check_files:
checkType = 'M'
if 'Internal' in check_file:
checkType = 'I'
elif 'Security' in check_file:
checkType = 'S'
elif 'Trace' in check_file:
checkType = 'T'
elif 'Parameters' in check_file:
checkType = 'P'
elif 'Callstacks' in check_file:
checkType = 'C'
elif 'SQLCache_TopLists' in check_file:
checkType = 'R'
elif 'ABAP' in check_file:
checkType = 'A'
try:
result = run_command(sqlman.hdbsql_jAaxU + ' -I '+check_file).splitlines()
except:
log("ERROR: The check file "+check_file+" could not be executed. Either there is a problem with the check file (did you get the latest SQLStatements.zip from SAP Note 1969700?) or there is a problem with the user (is user properly saved in hdbuserstore?) or there is another problem (OOM?).", logman)
os._exit(1)
result = [ [word.strip(' ') for word in line.split('|')] for line in result]
old_checkId = "-1"
old_description = ""
for line in result:
if len(line) > 1:
if checkType == 'P':
if "Revision:" in line[1]:
revision = line[2]
elif "Environment:" in line[1]:
environment = line[2]
elif "CPU threads:" in line[1]:
cputhreads = line[2]
elif "CPU frequency (MHz):" in line[1]:
cpufrequency = line[2]
elif "NUMA nodes:" in line[1]:
numa_nodes = line[2]
elif "Alloc. lim. (GB):" in line[1]:
global_allocation_limit = line[2]
elif "Worker nodes:" in line[1]:
worker_nodes = line[2]
elif "Log volume size (GB):" in line[1]:
log_volume_size = line[2]
elif line[10] and line[11]: # if implementation and undo command exist then (after column COMMENT was added)
if cputhreads == '0' or cpufrequency == '0' or numa_nodes == '?' or global_allocation_limit == '?' or log_volume_size == '?':
log("PRIVILEGE ERROR: The user represented by the key in the hdbuserstore has insufficient privileges.", logman)
log("Make sure he has sufficient privileges to read these:", logman)
log("CPU threads: "+cputhreads+" CPU frequency: "+cpufrequency+" NUMA nodes: "+numa_nodes+" GAL (GB): "+global_allocation_limit+" Log volume size (GB): "+log_volume_size, logman)
log("Suggestion: The role MONITORING could help.", logman)
os._exit(1)
inifile = line[1]
section = line[2]
parameter = line[3]
priority = line[4]
configuredvalue= line[5]
recommendedvalue = line[6]
sapnote = line[7]
configuredlayer = line[8]
if not ignore_dublicated_parameter or not parameter_is_dublicate(inifile, section, parameter, configuredvalue, critical_parameter_checks):
if not ignore_check_why_set or not "-- check why set --" in recommendedvalue:
if not parameter in ignore_parameters:
if parameter in ['allocationlimit', 'statement_memory_limit', 'max_partitions_limited_by_locations']:
critical_parameter_checks.append(ParameterCheck(inifile, section, parameter, priority, configuredvalue, recommendedvalue, sapnote, configuredlayer, revision, environment, '', '', '', global_allocation_limit, '', ''))
elif parameter in ['default_statement_concurrency_limit', 'max_concurrency', 'max_concurrency_hint', 'num_cores', 'max_gc_parallelity', 'tables_preloaded_in_parallel']:
critical_parameter_checks.append(ParameterCheck(inifile, section, parameter, priority, configuredvalue, recommendedvalue, sapnote, configuredlayer, revision, environment, cputhreads, '', '', '', '', ''))
elif parameter in ['savepoint_pre_critical_flush_retry_threshold']:
critical_parameter_checks.append(ParameterCheck(inifile, section, parameter, priority, configuredvalue, recommendedvalue, sapnote, configuredlayer, revision, environment, '', cpufrequency, '', '', '', ''))
elif parameter in ['logshipping_max_retention_size']:
critical_parameter_checks.append(ParameterCheck(inifile, section, parameter, priority, configuredvalue, recommendedvalue, sapnote, configuredlayer, revision, environment, '', '', '', '', '', log_volume_size))
elif parameter in ['max_partitions']:
critical_parameter_checks.append(ParameterCheck(inifile, section, parameter, priority, configuredvalue, recommendedvalue, sapnote, configuredlayer, revision, environment, '', '', '', global_allocation_limit, worker_nodes, ''))
else:
critical_parameter_checks.append(ParameterCheck(inifile, section, parameter, priority, configuredvalue, recommendedvalue, sapnote, configuredlayer, revision, environment, '', '', '', '', '', ''))
elif checkType == 'R':
if ("1.00.122" in check_file and line[4] == 'X') or ("1.00.122" not in check_file and line[5] == 'X'):
sql_hash = line[1]
if not hash_is_dublicate(sql_hash, sqls_with_recommendation):
sql_type = line[2]
origin = line[3]
engine = ''
if revision >= 53:
engine = line[4]
sqls_with_recommendation.append(SQLWithRecommendation(sql_hash, sql_type, origin, engine))
elif is_check_id(line[1]): # then this line is an M, I, S, T, C, or A mini-check
if not line[1] in ignore_checks:
if checkType == 'T':
potential_critical = 'X'
checkId = line[1]
area = line[2]
description = line[3]
host = line[4]
port = line[5]
count = line[6]
last_occurrence = line[7]
sap_note = line[8]
trace_text = line[9]
critical_mini_checks.append(MiniCheck(checkId, area, description, host, port, count, '', last_occurrence, '', '', potential_critical, sap_note, trace_text))
if checkType == 'C':
potential_critical = 'X'
checkId = line[1]
area = line[2]
description = line[3]
host = line[4]
port = line[5]
count = line[6]
act_thr = line[7]
last_occurrence = line[8]
sap_note = line[9]
details = line[10]
critical_mini_checks.append(MiniCheck(checkId, area, description, host, port, count, act_thr, last_occurrence, '', '', potential_critical, sap_note, details))
else: # M, I, S, A
potential_critical = line[6] if checkType in ['M','I'] else line[5] #'M', 'I' column 6, but for 'S' and 'A' column 5
if potential_critical == 'X':
checkId = line[1] if line[1] else old_checkId
description = line[2] if line[2] else old_description
host = line[3] if checkType in ['M','I'] else '' #no host for 'S' or 'A'
value = line[4] if checkType in ['M','I'] else line[3]
expected_value = line[5] if checkType in ['M','I'] else line[4]
sap_note = line[7] if checkType in ['M','I'] else line[6]
critical_mini_checks.append(MiniCheck(checkId, '', description, host, '', '', '', '', value, expected_value, potential_critical, sap_note, ''))
old_checkId = line[1] if line[1] else old_checkId
old_description = line[2] if line[2] else old_description
return [critical_mini_checks, critical_parameter_checks, sqls_with_recommendation]
def is_master(local_dbinstance, local_host, logman): #will not be executed in case of HANACloud
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): #will not be executed in case of HANACloud
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 online_and_master_tests(online_test_interval, local_dbinstance, local_host, logman):
if online_test_interval < 0: #then dont test #in case of HANACloud this parameter is forced to be -1
return True
else:
if is_online(local_dbinstance, logman) and not is_secondary(logman):
return is_master(local_dbinstance, local_host, logman) #HANAChecker should only run on the Master Node
else:
return False
def get_key_info(dbuserkey, local_host, logman):
try:
key_environment = run_command('''hdbuserstore LIST '''+dbuserkey)
except:
log("ERROR, the key "+dbuserkey+" is not maintained in hdbuserstore.", logman, True)
os._exit(1)
if "NOT FOUND" in key_environment:
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 ping_db(sqlman, logman, error_emails):
with open(os.devnull, 'w') as devnull: # just to get no stdout in case HANA is offline
try:
command_run = run_command(sqlman.hdbsql_jAaxU + ' "select * from dummy"').splitlines(1) #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.
output = command_run[0].strip("\n").strip("|").strip(" ")
if not output == 'X':
message = "CONNECTION ERROR: Something went wrong getting results from an SQL from database "+sqlman.db+" with user "+sqlman.key+".\n"
log_with_emails(message, logman, error_emails)
os._exit(1)
except:
message = "CONNECTION ERROR: Something went wrong connecting to the database "+sqlman.db+" with user "+sqlman.key+". Check the key and/or possibility you want to use -oi.\n"
log_with_emails(message, logman, error_emails)
os._exit(1)
def parameter_is_dublicate(inifile, section, parameter, configuredvalue, critical_parameter_checks): #find parameters set to same value in different layers
for check in critical_parameter_checks:
if inifile == check.IniFile and section == check.Section and parameter == check.Parameter and configuredvalue == check.ConfiguredValue:
return True
return False
def hash_is_dublicate(hash, sqls_with_recommendation):
for sql in sqls_with_recommendation:
if hash == sql.Hash:
return True
return False
def addCheckGroupsToDict(checkEmailDict, check_groups):
global chidMax
for checkNumber in range(1, chidMax):
for cg in check_groups:
if get_check_number(cg[0][0]) <= checkNumber <= get_check_number(cg[0][1]):
checkType = get_check_type(cg[0][0])
if checkNumber in list(checkEmailDict[checkType].keys()):
if cg[1] not in checkEmailDict[checkType][checkNumber]:
checkEmailDict[checkType][checkNumber].append(cg[1])
else:
checkEmailDict[checkType][checkNumber] = [cg[1]]
return checkEmailDict
def addCatchAllEmailsToDict(checkEmailDict, catch_all_emails, ignore_checks_for_ca):
global chidMax
for checkType in ['M', 'I', 'S', 'T', 'C', 'A']: #mini, internal, security, trace, call stacks, ABAP
for checkNumber in range(1, chidMax):
if convertToCheckId(checkType, checkNumber) not in ignore_checks_for_ca:
if checkNumber in list(checkEmailDict[checkType].keys()):
for ca_email in catch_all_emails:
if ca_email not in checkEmailDict[checkType][checkNumber]:
checkEmailDict[checkType][checkNumber].append(ca_email)
else:
checkEmailDict[checkType][checkNumber] = catch_all_emails
return checkEmailDict
def sendEmails(critical_checks, checkEmailDict, parameter_emails, sql_emails, one_email, always_send, check_files, execution_string, out_config, flag_log, logman):
critical_mini_checks = critical_checks[0]
critical_parameter_checks = critical_checks[1]
sqls_with_recommendation = critical_checks[2]
messages = {}
dbstring = ""
parameter_string = " "
if out_config:
parameter_string = " with"
for key, value in flag_log.items():
parameter_string += "\n"+key
for i in range(0, len(value), 2):
parameter_string += "\t"+value[i]+" from "+value[i+1]
if len(logman.db) > 1:
dbstring = logman.db+'@'
if always_send:
unique_emails = []
for val in checkEmailDict.values():
for emails in val.values():
for email in emails:
if email not in unique_emails:
unique_emails.append(email)
always_send_message = "HANAChecker was executed "+datetime.now().strftime("%Y-%m-%d %H:%M:%S")+parameter_string+" on "+dbstring+logman.SID+" with \n"+execution_string+".\nIf any of the mini and/or parameter checks from following check files:"
always_send_message += ",".join(check_files)
always_send_message += "\nthat you are responsible for seem critical, you will be notified now.\n"
for email in unique_emails:
messages.update({email:[always_send_message]})
for email in parameter_emails:
if email not in unique_emails:
messages.update({email:[always_send_message]})
for email in sql_emails:
if email not in unique_emails:
messages.update({email:[always_send_message]})
for check in critical_mini_checks:
if check.Number in list(checkEmailDict[check.Type].keys()):
for email in checkEmailDict[check.Type][check.Number]:
if email in messages:
messages[email].append(check.summary())
else:
messages.update({email:[check.summary()]})
for check in critical_parameter_checks:
for email in parameter_emails:
if email in messages:
messages[email].append(check.summary())
else:
messages.update({email:[check.summary()]})
for sql in sqls_with_recommendation:
for email in sql_emails:
if email in messages:
messages[email].append(sql.summary())
else:
messages.update({email:[sql.summary()]})
for email, messages_for_email in messages.items():
if one_email:
message = "\n".join(messages_for_email)
log(message, logman, recieversEmail = email)
else:
for message in messages_for_email:
log(message, logman, recieversEmail = email)
def main():
### globals ###
global chidMax
##################### CHECK PYTHON VERSION ###########
pythonversion = sys.version_info
print("HANAChecker started on python ", str(pythonversion[0])+"."+str(pythonversion[1])+"."+str(pythonversion[2]))
if pythonversion[0] != 2 and pythonversion[0] != 3:
if sys.version_info[1] != 7:
print("VERSION ERROR: hanachecker is only supported for Python 2.7.x (for HANA 2 SPS05 and lower) and for Python 3.7.x (for HANA 2 SPS06 and higher). Did you maybe forget to log in as <sid>adm before executing this?")
os._exit(1)
##################### DEFAULTS ####################
email_client = 'mailx' #default email client
email_sender_address = ''
mail_server = ''
email_sender = [] #depricated!
hanachecker_interval = -1
minRetainedOutputDays = -1
ignore_check_why_set = "false"
ignore_parameters = ['']
active_threads = ''
abap_schema = ''
ignore_dublicated_parameter = "true"
one_email = "false"
always_send = "false"
ssl = "false"
virtual_local_host = "" #default: assume physical local host
dbuserkeys = ["SYSTEMKEY"] # This/these KEY(S) has to be maintained in hdbuserstore
# so that hdbuserstore LIST gives e.g.
# KEY SYSTEMKEY
# ENV : mo-fc8d991e0:30015
# USER: SYSTEM
online_test_interval = "-1" #days
dbases = ['']
cloud_data_base = ''
std_out = "1" #print to std out
out_config = "false"
out_dir = "/tmp/hanachecker_output"
flag_files = [] #default: no configuration input file
check_files = None
zip_file = None
zip_folder = None
check_types = []
checkEmailDict = {'M':{}, 'I':{}, 'S':{}, 'T':{}, 'C':{}, 'A':{}} #mini, internal, security, trace, call stack, ABAP
check_groups = []
parameter_emails = []
sql_emails = []
error_emails = []
catch_all_emails = []
ignore_checks_for_ca = []
ignore_checks = []
##################### CHECK INPUT ARGUMENTS #################
if len(sys.argv) == 1:
print("INPUT ERROR: hanachecker needs input arguments. Please see --help for more information.")
os._exit(1)
if len(sys.argv) != 2 and len(sys.argv) % 2 == 0:
print("INPUT ERROR: Wrong number of input arguments. Please see --help for more information.")
os._exit(1)
for i in range(len(sys.argv)):
if i % 2 != 0:
if sys.argv[i][0] != '-':
print("INPUT ERROR: Every second argument has to be a flag, i.e. start with -. Please see --help for more information.")
os._exit(1)
##################### PRIMARY INPUT ARGUMENTS ####################
flag_log = {}
for word in sys.argv:
if word[0:1] == '-':
checkIfAcceptedFlag(word)
if '-h' in sys.argv or '--help' in sys.argv:
printHelp()
if '-d' in sys.argv or '--disclaimer' in sys.argv:
printDisclaimer()
flag_files = getParameterListFromCommandLine(sys.argv, '-ff', flag_log, flag_files)
if flag_files:
print("Double check that the configuration file only contains ascii characters!")
############ CONFIGURATION FILE ###################
for flag_file in flag_files: