github.com/tommi2day/tnscli@v0.0.0-20240401211958-338fc0647b73/cmd/service.go (about) 1 // Package cmd commands 2 package cmd 3 4 import ( 5 "fmt" 6 "net" 7 "path" 8 "regexp" 9 "sort" 10 "strings" 11 "time" 12 13 "github.com/spf13/viper" 14 15 goora "github.com/sijms/go-ora/v2" 16 17 "github.com/tommi2day/gomodules/common" 18 "github.com/tommi2day/gomodules/dblib" 19 20 log "github.com/sirupsen/logrus" 21 22 "github.com/spf13/cobra" 23 ) 24 25 var ( 26 serviceCmd = &cobra.Command{ 27 Use: "service", 28 Short: "Service sub command", 29 Long: ``, 30 } 31 32 checkCmd = &cobra.Command{ 33 Use: "check", 34 Short: "Check TNS Entries", 35 Long: `Check all TNS Entries or one with real connect to database`, 36 RunE: checkTns, 37 SilenceUsage: true, 38 } 39 portcheckCmd = &cobra.Command{ 40 Use: "portcheck", 41 Short: "try to connect each service and report if it is open or not", 42 Long: `list defined host:port and checks if requested. If racinfo.ini or SRV info given, addresses will be checked as well`, 43 RunE: doPortcheck, 44 SilenceUsage: true, 45 } 46 infoCmd = &cobra.Command{ 47 Use: "info", 48 Short: "give details for the given service", 49 Long: `printout more details for the service`, 50 } 51 portInfoCmd = &cobra.Command{ 52 Use: "ports", 53 Short: "list service addresses and ports", 54 Long: `list defined host:port and checks if requested. If racinfo.ini given, it will be listed as well`, 55 RunE: portInfo, 56 SilenceUsage: true, 57 } 58 jdbcInfoCmd = &cobra.Command{ 59 Use: "jdbc", 60 Short: "print tns entry as jdbc string", 61 Long: `printout jdbc string for the service`, 62 RunE: getJdbcInfo, 63 SilenceUsage: true, 64 } 65 tnsInfoCmd = &cobra.Command{ 66 Use: "tns", 67 Short: "print tns entry for the given service", 68 Long: `printout tns entry for the service`, 69 RunE: getTnsInfo, 70 SilenceUsage: true, 71 } 72 ) 73 74 const defaultUser = "C##TCHECK" 75 const defaultPassword = "<MyCheckPassword>" 76 const racinfoFile = "racinfo.ini" 77 78 var dbUser = "" 79 var dbPass = "" 80 var tnsKey = "" 81 var timeout = 15 82 var dbhostFlag = false 83 var all = false 84 var racinfo = "" 85 var nodns = false 86 var ipv4 = false 87 var nameserver = "" 88 var pingTimeout = 5 89 var tcpcheck = false 90 var dnstcp = false 91 var noModifyTransportConnectTimeout = false 92 93 func init() { 94 serviceCmd.PersistentFlags().StringVarP(&tnsKey, "service", "s", "", "service name to check") 95 RootCmd.AddCommand(serviceCmd) 96 97 checkCmd.PersistentFlags().StringVarP(&dbUser, "user", "u", dbUser, "User for real connect or set TNSCLI_USER") 98 checkCmd.PersistentFlags().StringVarP(&dbPass, "password", "p", dbPass, "Password for real connect or set TNSCLI_PASSWORD") 99 checkCmd.PersistentFlags().BoolVarP(&all, "all", "a", false, "check all entries") 100 checkCmd.PersistentFlags().IntVarP(&timeout, "timeout", "t", timeout, "timeout in sec") 101 checkCmd.Flags().BoolVarP(&dbhostFlag, "dbhost", "H", false, "print actual connected host:cdb:pdb") 102 103 portInfoCmd.Flags().StringVarP(&racinfo, "racinfo", "r", "", "path to racinfo.ini to resolve all RAC TCP Adresses, default $TNS_ADMIN/racinfo.ini") 104 portInfoCmd.Flags().StringVarP(&nameserver, "nameserver", "n", "", "alternative nameserver to use for DNS lookup (IP:PORT)") 105 portInfoCmd.Flags().BoolVar(&nodns, "nodns", false, "do not use DNS to resolve hostnames") 106 portInfoCmd.Flags().BoolVar(&ipv4, "ipv4", false, "resolve only IPv4 addresses") 107 portInfoCmd.Flags().BoolVar(&dnstcp, "dnstcp", false, "Use TCP to resolve DNS names") 108 109 portcheckCmd.Flags().StringVarP(&racinfo, "racinfo", "r", "", "path to racinfo.ini to resolve all RAC TCP Adresses, default $TNS_ADMIN/racinfo.ini") 110 portcheckCmd.Flags().StringVarP(&nameserver, "nameserver", "n", "", "alternative nameserver to use for DNS lookup (IP:PORT)") 111 portcheckCmd.Flags().BoolVar(&nodns, "nodns", false, "do not use DNS to resolve hostnames") 112 portcheckCmd.Flags().BoolVar(&ipv4, "ipv4", false, "resolve only IPv4 addresses") 113 portcheckCmd.Flags().IntVarP(&pingTimeout, "timeout", "t", pingTimeout, "timeout for tcp ping") 114 portcheckCmd.Flags().BoolVar(&dnstcp, "dnstcp", false, "Use TCP to resolve DNS names") 115 116 jdbcInfoCmd.Flags().BoolVar(&noModifyTransportConnectTimeout, "noModifyTransportConnectTimeout", false, "Do not modify TRANSPORT_CONNECT_TIMEOUT in ms") 117 infoCmd.AddCommand(portInfoCmd) 118 infoCmd.AddCommand(jdbcInfoCmd) 119 infoCmd.AddCommand(tnsInfoCmd) 120 121 serviceCmd.AddCommand(checkCmd) 122 serviceCmd.AddCommand(infoCmd) 123 serviceCmd.AddCommand(portcheckCmd) 124 } 125 func getEntry(tnsKey string) (entry dblib.TNSEntry, err error) { 126 // load available tns entries 127 tnsEntries, domain, err := dblib.GetTnsnames(filename, true) 128 l := len(tnsEntries) 129 if err != nil { 130 return 131 } 132 if l == 0 { 133 err = fmt.Errorf("cannot proceed without tns entries") 134 log.Error(err) 135 return 136 } 137 if tnsKey == "" { 138 err = fmt.Errorf("dont have a service to check, use --service to provide") 139 return 140 } 141 log.Debugf("get info for service %s ", tnsKey) 142 143 entry, found := dblib.GetEntry(tnsKey, tnsEntries, domain) 144 if !found { 145 err = fmt.Errorf("xealias %s not found", tnsKey) 146 return 147 } 148 return 149 } 150 151 func portInfo(_ *cobra.Command, args []string) (err error) { 152 if len(args) > 0 { 153 tnsKey = args[0] 154 } 155 if tnsKey == "" { 156 err = fmt.Errorf("dont have a service to check, use --service to provide") 157 return 158 } 159 if racinfo == "" { 160 racinfo = path.Join(viper.GetString("tns_admin"), racinfoFile) 161 } 162 entry, err := getEntry(tnsKey) 163 if err != nil { 164 return 165 } 166 servers := entry.Servers 167 l := len(servers) 168 if l == 0 { 169 err = fmt.Errorf("xealias %s: No hosts found", tnsKey) 170 return 171 } 172 log.Infof("Alias %s uses %d hosts", tnsKey, l) 173 dblib.IgnoreDNSLookup = nodns 174 dblib.IPv4Only = ipv4 175 ns, p, e := common.GetHostPort(nameserver) 176 if e != nil { 177 ns = nameserver 178 p = 0 179 } 180 dblib.Resolver = dblib.SetResolver(ns, p, dnstcp) 181 allservices := getServices(servers) 182 log.Infof("Alias %s uses %d addresses", tnsKey, len(allservices)) 183 for _, s := range allservices { 184 if tcpcheck { 185 doTCPPing(s.Host, s.Address) 186 } else { 187 fmt.Printf("%s (%s)\n", s.Host, s.Address) 188 } 189 } 190 return 191 } 192 193 func getServices(servers []dblib.TNSAddress) (allservices dblib.ServiceEntries) { 194 for _, s := range servers { 195 host := s.Host 196 port := s.Port 197 services := dblib.GetRacAdresses(host, racinfo) 198 if len(services) == 0 { 199 log.Debugf("no racinfo found, will use original entry %s:%s", host, port) 200 services = append(services, dblib.ServiceEntryType{Host: host, Port: port, Address: host + ":" + port}) 201 } 202 allservices = append(allservices, services...) 203 } 204 return 205 } 206 207 func doPortcheck(c *cobra.Command, args []string) (err error) { 208 tcpcheck = true 209 err = portInfo(c, args) 210 return 211 } 212 213 func doTCPPing(host string, address string) { 214 d := net.Dialer{Timeout: time.Duration(pingTimeout) * time.Second} 215 _, err := d.Dial("tcp", address) 216 if err != nil { 217 match, _ := regexp.MatchString("refused", err.Error()) 218 if match { 219 // Closed 220 log.Infof("%s, %s is CLOSED/REFUSED (no service)", host, address) 221 fmt.Printf("%s (%s) is CLOSED/REFUSED (no service)\n", host, address) 222 return 223 } 224 match, _ = regexp.MatchString("timeout", err.Error()) 225 if match { 226 // Timeout 227 log.Infof("%s (%s) TIMEOUT (blocked)", host, address) 228 fmt.Printf("%s (%s) TIMEOUT (blocked)\n", host, address) 229 return 230 } 231 } else { 232 // Open 233 log.Infof("%s(%s) is OPEN", host, address) 234 fmt.Printf("%s (%s) is OPEN\n", host, address) 235 return 236 } 237 // Default 238 e := err.Error() 239 e = strings.ReplaceAll(e, "dial tcp:", "") 240 log.Infof("%s(%s) port status PROBLEM: %s ", host, address, e) 241 fmt.Printf("%s (%s) port status PROBLEM: %s\n", host, address, e) 242 } 243 244 func getTnsInfo(_ *cobra.Command, args []string) (err error) { 245 if tnsKey == "" { 246 tnsKey = args[0] 247 } 248 entry, err := getEntry(tnsKey) 249 if err == nil { 250 desc := entry.Desc 251 tnsAlias := entry.Name 252 loc := entry.Location 253 desc = strings.ReplaceAll(desc, "\r", " ") 254 desc = strings.ReplaceAll(desc, "\n", "\n ") 255 desc = strings.ReplaceAll(desc, "(ADDRESS_LIST", " (ADDRESS_LIST") 256 desc = strings.ReplaceAll(desc, "(CONNECT_DATA", " (CONNECT_DATA") 257 out := fmt.Sprintf("# Location: %s \n%s= %s", loc, tnsAlias, desc) 258 log.Infof(out) 259 fmt.Println(out) 260 } 261 return 262 } 263 264 // getJdbcInfo prints the jdbc url for the given service 265 func getJdbcInfo(_ *cobra.Command, args []string) (err error) { 266 out := "" 267 if tnsKey == "" { 268 tnsKey = args[0] 269 } 270 entry, err := getEntry(tnsKey) 271 if err != nil { 272 return err 273 } 274 desc := entry.Desc 275 // disable TRANSPORT_CONNECT_TIMEOUT modification 276 if noModifyTransportConnectTimeout { 277 dblib.ModifyJDBCTransportConnectTimeout = false 278 } 279 out, err = dblib.GetJDBCUrl(desc) 280 log.Infof(out) 281 fmt.Println(out) 282 283 return 284 } 285 286 func checkTns(_ *cobra.Command, args []string) (err error) { 287 // load available tns entries 288 tnsEntries, domain, err := dblib.GetTnsnames(filename, true) 289 l := len(tnsEntries) 290 if err != nil { 291 return 292 } 293 if l == 0 { 294 err = fmt.Errorf("cannot proceed without tns entries") 295 log.Error(err) 296 return 297 } 298 log.Debugf("have %d tns entries", l) 299 if dbUser == "" { 300 dbUser = common.GetEnv("TNSCLI_USER", "") 301 log.Debugf("use default db user %s", dbUser) 302 } 303 if dbPass == "" { 304 dbPass = common.GetEnv("TNSCLI_PASSWORD", "") 305 log.Debug("use default db pass") 306 } 307 308 // do checks depending on mode 309 if all { 310 // all flag given, check every entry 311 return allCheck(tnsEntries) 312 } 313 // check specific entries from arg 314 return singleCheck(args, tnsEntries, domain) 315 } 316 317 func allCheck(tnsEntries dblib.TNSEntries) (err error) { 318 var failed []string 319 l := len(tnsEntries) 320 log.Debugf("check all %d entries", l) 321 keys := make([]string, 0, l) 322 for k := range tnsEntries { 323 keys = append(keys, k) 324 } 325 sort.Strings(keys) 326 o := 0 327 e := 0 328 i := 0 329 for _, k := range keys { 330 entry := tnsEntries[k] 331 desc := entry.Desc 332 tnsAlias := entry.Name 333 fmt.Printf("%s: ", tnsAlias) 334 ok, elapsed, hostval, errmsg := CheckWithOracle(dbUser, dbPass, desc, timeout) 335 if ok { 336 o++ 337 if dbhostFlag { 338 fmt.Printf(" OK-> %s, %s\n", hostval, elapsed.Round(time.Millisecond)) 339 } else { 340 fmt.Printf(" OK-> %s\n", elapsed.Round(time.Millisecond)) 341 } 342 } else { 343 e++ 344 fmt.Printf(" ERROR: %s\n", errmsg) 345 failed = append(failed, fmt.Sprintf("%s: %v", tnsAlias, errmsg)) 346 } 347 i++ 348 } 349 log.Info("Checks finished ...") 350 log.Infof(" %d entries checked, %d ok, %d failed\n", i, o, e) 351 if len(failed) > 0 { 352 for _, s := range failed { 353 fmt.Println(s) 354 } 355 err = fmt.Errorf("some checks failed") 356 } 357 return 358 } 359 360 func singleCheck(args []string, tnsEntries dblib.TNSEntries, domain string) (err error) { 361 // not all modus, we have to check one single entry 362 // use first argument as service if is nothing given 363 la := len(args) 364 log.Debugf("service %s, domain '%s', args: %d -> %v", tnsKey, domain, la, args) 365 if tnsKey == "" && la > 0 { 366 tnsKey = args[0] 367 } 368 if tnsKey == "" { 369 err = fmt.Errorf("dont have a service to check, use --service to provide") 370 return 371 } 372 log.Debugf("get Entry for service %s ", tnsKey) 373 if entry, found := dblib.GetEntry(tnsKey, tnsEntries, domain); found { 374 err = testService(entry) 375 return 376 } 377 err = fmt.Errorf("alias %s not found", tnsKey) 378 return 379 } 380 func testService(entry dblib.TNSEntry) (err error) { 381 desc := entry.Desc 382 location := entry.Location 383 tnsAlias := entry.Name 384 log.Debugf("connect service %s from %s, timeout: %d s", tnsAlias, location, timeout) 385 log.Infof("use entry \n%s=%s\n", tnsAlias, strings.ReplaceAll(desc, "\r", " ")) 386 con := "" 387 if len(dbUser) > 0 { 388 con = fmt.Sprintf("using user '%s'", dbUser) 389 } 390 ok, elapsed, hostval, errmsg := CheckWithOracle(dbUser, dbPass, desc, timeout) 391 if ok { 392 hv := "" 393 if hostval != "" { 394 hv = "(" + hostval + ") " 395 } 396 log.Infof("service %s connected %s%s in %s\n", tnsKey, hv, con, elapsed.Round(time.Millisecond)) 397 if dbhostFlag { 398 fmt.Printf("%s -> %s\n", tnsKey, hostval) 399 } else { 400 fmt.Printf("OK, service %s reachable\n", tnsAlias) 401 } 402 } else { 403 err = fmt.Errorf("service %s %s NOT reached:%s", tnsAlias, con, errmsg) 404 } 405 return 406 } 407 408 // CheckWithOracle try connecting to oracle with dummy creds to get an ORA error. 409 // If this happens, the connection is working 410 func CheckWithOracle(dbuser string, dbpass string, tnsDesc string, timeout int) (ok bool, elapsed time.Duration, hostval string, err error) { 411 urlOptions := map[string]string{ 412 // "CONNECTION TIMEOUT": "3", 413 } 414 ok = false 415 if dbuser == "" { 416 dbuser = defaultUser 417 } 418 if dbpass == "" { 419 dbpass = defaultPassword 420 } 421 // jdbc url needs spaces stripped 422 tnsDesc = strings.Join(strings.Fields(tnsDesc), "") 423 url := goora.BuildJDBC(dbuser, dbpass, tnsDesc, urlOptions) 424 log.Debugf("Try to connect %s@%s", dbuser, tnsDesc) 425 start := time.Now() 426 db, err := dblib.DBConnect("oracle", url, timeout) 427 elapsed = time.Since(start) 428 429 // check results 430 if err != nil { 431 // check error code, we expect 1017 432 isOerr, code, _ := dblib.HaveOerr(err) 433 if isOerr && code == 1017 { 434 ok = true 435 log.Warnf("Connect OK, but Login error, maybe expected") 436 } 437 } else { 438 log.Debugf("Connection OK, test if db is open using select") 439 sql := "select 'DB is open, sysdate:'||to_char(sysdate,'YYYY-MM-DD HH24:MI:SS') from dual" 440 if dbhostFlag { 441 // extract host,cdb and pdb from database 442 sql = "select sys_context('USERENV','SERVER_HOST')||':'||sys_context('USERENV','INSTANCE_NAME')||':'||nvl(sys_context('USERENV','CON_NAME'),'') as dbhostFlag from dual" 443 } 444 hostval, err = dblib.SelectOneStringValue(db, sql) 445 log.Infof("Query returned: %s", hostval) 446 if err == nil { 447 ok = true 448 } 449 } 450 return 451 }