github.com/nsqio/nsq@v1.3.0/nsqd/nsqd.go (about) 1 package nsqd 2 3 import ( 4 "context" 5 "crypto/tls" 6 "crypto/x509" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "log" 11 "math/rand" 12 "net" 13 "os" 14 "path" 15 "strings" 16 "sync" 17 "sync/atomic" 18 "time" 19 20 "github.com/nsqio/nsq/internal/clusterinfo" 21 "github.com/nsqio/nsq/internal/dirlock" 22 "github.com/nsqio/nsq/internal/http_api" 23 "github.com/nsqio/nsq/internal/protocol" 24 "github.com/nsqio/nsq/internal/statsd" 25 "github.com/nsqio/nsq/internal/util" 26 "github.com/nsqio/nsq/internal/version" 27 ) 28 29 const ( 30 TLSNotRequired = iota 31 TLSRequiredExceptHTTP 32 TLSRequired 33 ) 34 35 type errStore struct { 36 err error 37 } 38 39 type NSQD struct { 40 // 64bit atomic vars need to be first for proper alignment on 32bit platforms 41 clientIDSequence int64 42 43 sync.RWMutex 44 ctx context.Context 45 // ctxCancel cancels a context that main() is waiting on 46 ctxCancel context.CancelFunc 47 48 opts atomic.Value 49 50 dl *dirlock.DirLock 51 isLoading int32 52 isExiting int32 53 errValue atomic.Value 54 startTime time.Time 55 56 topicMap map[string]*Topic 57 58 lookupPeers atomic.Value 59 60 tcpServer *tcpServer 61 tcpListener net.Listener 62 httpListener net.Listener 63 httpsListener net.Listener 64 tlsConfig *tls.Config 65 clientTLSConfig *tls.Config 66 67 poolSize int 68 69 notifyChan chan interface{} 70 optsNotificationChan chan struct{} 71 exitChan chan int 72 waitGroup util.WaitGroupWrapper 73 74 ci *clusterinfo.ClusterInfo 75 } 76 77 func New(opts *Options) (*NSQD, error) { 78 var err error 79 80 dataPath := opts.DataPath 81 if opts.DataPath == "" { 82 cwd, _ := os.Getwd() 83 dataPath = cwd 84 } 85 if opts.Logger == nil { 86 opts.Logger = log.New(os.Stderr, opts.LogPrefix, log.Ldate|log.Ltime|log.Lmicroseconds) 87 } 88 89 n := &NSQD{ 90 startTime: time.Now(), 91 topicMap: make(map[string]*Topic), 92 exitChan: make(chan int), 93 notifyChan: make(chan interface{}), 94 optsNotificationChan: make(chan struct{}, 1), 95 dl: dirlock.New(dataPath), 96 } 97 n.ctx, n.ctxCancel = context.WithCancel(context.Background()) 98 httpcli := http_api.NewClient(nil, opts.HTTPClientConnectTimeout, opts.HTTPClientRequestTimeout) 99 n.ci = clusterinfo.New(n.logf, httpcli) 100 101 n.lookupPeers.Store([]*lookupPeer{}) 102 103 n.swapOpts(opts) 104 n.errValue.Store(errStore{}) 105 106 err = n.dl.Lock() 107 if err != nil { 108 return nil, fmt.Errorf("failed to lock data-path: %v", err) 109 } 110 111 if opts.MaxDeflateLevel < 1 || opts.MaxDeflateLevel > 9 { 112 return nil, errors.New("--max-deflate-level must be [1,9]") 113 } 114 115 if opts.ID < 0 || opts.ID >= 1024 { 116 return nil, errors.New("--node-id must be [0,1024)") 117 } 118 119 if opts.TLSClientAuthPolicy != "" && opts.TLSRequired == TLSNotRequired { 120 opts.TLSRequired = TLSRequired 121 } 122 123 tlsConfig, err := buildTLSConfig(opts) 124 if err != nil { 125 return nil, fmt.Errorf("failed to build TLS config - %s", err) 126 } 127 if tlsConfig == nil && opts.TLSRequired != TLSNotRequired { 128 return nil, errors.New("cannot require TLS client connections without TLS key and cert") 129 } 130 n.tlsConfig = tlsConfig 131 132 clientTLSConfig, err := buildClientTLSConfig(opts) 133 if err != nil { 134 return nil, fmt.Errorf("failed to build client TLS config - %s", err) 135 } 136 n.clientTLSConfig = clientTLSConfig 137 138 for _, v := range opts.E2EProcessingLatencyPercentiles { 139 if v <= 0 || v > 1 { 140 return nil, fmt.Errorf("invalid E2E processing latency percentile: %v", v) 141 } 142 } 143 144 n.logf(LOG_INFO, version.String("nsqd")) 145 n.logf(LOG_INFO, "ID: %d", opts.ID) 146 147 n.tcpServer = &tcpServer{nsqd: n} 148 n.tcpListener, err = net.Listen(util.TypeOfAddr(opts.TCPAddress), opts.TCPAddress) 149 if err != nil { 150 return nil, fmt.Errorf("listen (%s) failed - %s", opts.TCPAddress, err) 151 } 152 if opts.HTTPAddress != "" { 153 n.httpListener, err = net.Listen(util.TypeOfAddr(opts.HTTPAddress), opts.HTTPAddress) 154 if err != nil { 155 return nil, fmt.Errorf("listen (%s) failed - %s", opts.HTTPAddress, err) 156 } 157 } 158 if n.tlsConfig != nil && opts.HTTPSAddress != "" { 159 n.httpsListener, err = tls.Listen("tcp", opts.HTTPSAddress, n.tlsConfig) 160 if err != nil { 161 return nil, fmt.Errorf("listen (%s) failed - %s", opts.HTTPSAddress, err) 162 } 163 } 164 if opts.BroadcastHTTPPort == 0 { 165 tcpAddr, ok := n.RealHTTPAddr().(*net.TCPAddr) 166 if ok { 167 opts.BroadcastHTTPPort = tcpAddr.Port 168 } 169 } 170 171 if opts.BroadcastTCPPort == 0 { 172 tcpAddr, ok := n.RealTCPAddr().(*net.TCPAddr) 173 if ok { 174 opts.BroadcastTCPPort = tcpAddr.Port 175 } 176 } 177 178 if opts.StatsdPrefix != "" { 179 var port string = fmt.Sprint(opts.BroadcastHTTPPort) 180 statsdHostKey := statsd.HostKey(net.JoinHostPort(opts.BroadcastAddress, port)) 181 prefixWithHost := strings.Replace(opts.StatsdPrefix, "%s", statsdHostKey, -1) 182 if prefixWithHost[len(prefixWithHost)-1] != '.' { 183 prefixWithHost += "." 184 } 185 opts.StatsdPrefix = prefixWithHost 186 } 187 188 return n, nil 189 } 190 191 func (n *NSQD) getOpts() *Options { 192 return n.opts.Load().(*Options) 193 } 194 195 func (n *NSQD) swapOpts(opts *Options) { 196 n.opts.Store(opts) 197 } 198 199 func (n *NSQD) triggerOptsNotification() { 200 select { 201 case n.optsNotificationChan <- struct{}{}: 202 default: 203 } 204 } 205 206 func (n *NSQD) RealTCPAddr() net.Addr { 207 if n.tcpListener == nil { 208 return &net.TCPAddr{} 209 } 210 return n.tcpListener.Addr() 211 212 } 213 214 func (n *NSQD) RealHTTPAddr() net.Addr { 215 if n.httpListener == nil { 216 return &net.TCPAddr{} 217 } 218 return n.httpListener.Addr() 219 } 220 221 func (n *NSQD) RealHTTPSAddr() *net.TCPAddr { 222 if n.httpsListener == nil { 223 return &net.TCPAddr{} 224 } 225 return n.httpsListener.Addr().(*net.TCPAddr) 226 } 227 228 func (n *NSQD) SetHealth(err error) { 229 n.errValue.Store(errStore{err: err}) 230 } 231 232 func (n *NSQD) IsHealthy() bool { 233 return n.GetError() == nil 234 } 235 236 func (n *NSQD) GetError() error { 237 errValue := n.errValue.Load() 238 return errValue.(errStore).err 239 } 240 241 func (n *NSQD) GetHealth() string { 242 err := n.GetError() 243 if err != nil { 244 return fmt.Sprintf("NOK - %s", err) 245 } 246 return "OK" 247 } 248 249 func (n *NSQD) GetStartTime() time.Time { 250 return n.startTime 251 } 252 253 func (n *NSQD) Main() error { 254 exitCh := make(chan error) 255 var once sync.Once 256 exitFunc := func(err error) { 257 once.Do(func() { 258 if err != nil { 259 n.logf(LOG_FATAL, "%s", err) 260 } 261 exitCh <- err 262 }) 263 } 264 265 n.waitGroup.Wrap(func() { 266 exitFunc(protocol.TCPServer(n.tcpListener, n.tcpServer, n.logf)) 267 }) 268 if n.httpListener != nil { 269 httpServer := newHTTPServer(n, false, n.getOpts().TLSRequired == TLSRequired) 270 n.waitGroup.Wrap(func() { 271 exitFunc(http_api.Serve(n.httpListener, httpServer, "HTTP", n.logf)) 272 }) 273 } 274 if n.httpsListener != nil { 275 httpsServer := newHTTPServer(n, true, true) 276 n.waitGroup.Wrap(func() { 277 exitFunc(http_api.Serve(n.httpsListener, httpsServer, "HTTPS", n.logf)) 278 }) 279 } 280 281 n.waitGroup.Wrap(n.queueScanLoop) 282 n.waitGroup.Wrap(n.lookupLoop) 283 if n.getOpts().StatsdAddress != "" { 284 n.waitGroup.Wrap(n.statsdLoop) 285 } 286 287 err := <-exitCh 288 return err 289 } 290 291 // Metadata is the collection of persistent information about the current NSQD. 292 type Metadata struct { 293 Topics []TopicMetadata `json:"topics"` 294 Version string `json:"version"` 295 } 296 297 // TopicMetadata is the collection of persistent information about a topic. 298 type TopicMetadata struct { 299 Name string `json:"name"` 300 Paused bool `json:"paused"` 301 Channels []ChannelMetadata `json:"channels"` 302 } 303 304 // ChannelMetadata is the collection of persistent information about a channel. 305 type ChannelMetadata struct { 306 Name string `json:"name"` 307 Paused bool `json:"paused"` 308 } 309 310 func newMetadataFile(opts *Options) string { 311 return path.Join(opts.DataPath, "nsqd.dat") 312 } 313 314 func readOrEmpty(fn string) ([]byte, error) { 315 data, err := os.ReadFile(fn) 316 if err != nil { 317 if !os.IsNotExist(err) { 318 return nil, fmt.Errorf("failed to read metadata from %s - %s", fn, err) 319 } 320 } 321 return data, nil 322 } 323 324 func writeSyncFile(fn string, data []byte) error { 325 f, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) 326 if err != nil { 327 return err 328 } 329 330 _, err = f.Write(data) 331 if err == nil { 332 err = f.Sync() 333 } 334 f.Close() 335 return err 336 } 337 338 func (n *NSQD) LoadMetadata() error { 339 atomic.StoreInt32(&n.isLoading, 1) 340 defer atomic.StoreInt32(&n.isLoading, 0) 341 342 fn := newMetadataFile(n.getOpts()) 343 344 data, err := readOrEmpty(fn) 345 if err != nil { 346 return err 347 } 348 if data == nil { 349 return nil // fresh start 350 } 351 352 var m Metadata 353 err = json.Unmarshal(data, &m) 354 if err != nil { 355 return fmt.Errorf("failed to parse metadata in %s - %s", fn, err) 356 } 357 358 for _, t := range m.Topics { 359 if !protocol.IsValidTopicName(t.Name) { 360 n.logf(LOG_WARN, "skipping creation of invalid topic %s", t.Name) 361 continue 362 } 363 topic := n.GetTopic(t.Name) 364 if t.Paused { 365 topic.Pause() 366 } 367 for _, c := range t.Channels { 368 if !protocol.IsValidChannelName(c.Name) { 369 n.logf(LOG_WARN, "skipping creation of invalid channel %s", c.Name) 370 continue 371 } 372 channel := topic.GetChannel(c.Name) 373 if c.Paused { 374 channel.Pause() 375 } 376 } 377 topic.Start() 378 } 379 return nil 380 } 381 382 // GetMetadata retrieves the current topic and channel set of the NSQ daemon. If 383 // the ephemeral flag is set, ephemeral topics are also returned even though these 384 // are not saved to disk. 385 func (n *NSQD) GetMetadata(ephemeral bool) *Metadata { 386 meta := &Metadata{ 387 Version: version.Binary, 388 } 389 for _, topic := range n.topicMap { 390 if topic.ephemeral && !ephemeral { 391 continue 392 } 393 topicData := TopicMetadata{ 394 Name: topic.name, 395 Paused: topic.IsPaused(), 396 } 397 topic.Lock() 398 for _, channel := range topic.channelMap { 399 if channel.ephemeral { 400 continue 401 } 402 topicData.Channels = append(topicData.Channels, ChannelMetadata{ 403 Name: channel.name, 404 Paused: channel.IsPaused(), 405 }) 406 } 407 topic.Unlock() 408 meta.Topics = append(meta.Topics, topicData) 409 } 410 return meta 411 } 412 413 func (n *NSQD) PersistMetadata() error { 414 // persist metadata about what topics/channels we have, across restarts 415 fileName := newMetadataFile(n.getOpts()) 416 417 n.logf(LOG_INFO, "NSQ: persisting topic/channel metadata to %s", fileName) 418 419 data, err := json.Marshal(n.GetMetadata(false)) 420 if err != nil { 421 return err 422 } 423 tmpFileName := fmt.Sprintf("%s.%d.tmp", fileName, rand.Int()) 424 425 err = writeSyncFile(tmpFileName, data) 426 if err != nil { 427 return err 428 } 429 err = os.Rename(tmpFileName, fileName) 430 if err != nil { 431 return err 432 } 433 // technically should fsync DataPath here 434 435 return nil 436 } 437 438 func (n *NSQD) Exit() { 439 if !atomic.CompareAndSwapInt32(&n.isExiting, 0, 1) { 440 // avoid double call 441 return 442 } 443 if n.tcpListener != nil { 444 n.tcpListener.Close() 445 } 446 447 if n.tcpServer != nil { 448 n.tcpServer.Close() 449 } 450 451 if n.httpListener != nil { 452 n.httpListener.Close() 453 } 454 455 if n.httpsListener != nil { 456 n.httpsListener.Close() 457 } 458 459 n.Lock() 460 err := n.PersistMetadata() 461 if err != nil { 462 n.logf(LOG_ERROR, "failed to persist metadata - %s", err) 463 } 464 n.logf(LOG_INFO, "NSQ: closing topics") 465 for _, topic := range n.topicMap { 466 topic.Close() 467 } 468 n.Unlock() 469 470 n.logf(LOG_INFO, "NSQ: stopping subsystems") 471 close(n.exitChan) 472 n.waitGroup.Wait() 473 n.dl.Unlock() 474 n.logf(LOG_INFO, "NSQ: bye") 475 n.ctxCancel() 476 } 477 478 // GetTopic performs a thread safe operation 479 // to return a pointer to a Topic object (potentially new) 480 func (n *NSQD) GetTopic(topicName string) *Topic { 481 // most likely we already have this topic, so try read lock first 482 n.RLock() 483 t, ok := n.topicMap[topicName] 484 n.RUnlock() 485 if ok { 486 return t 487 } 488 489 n.Lock() 490 491 t, ok = n.topicMap[topicName] 492 if ok { 493 n.Unlock() 494 return t 495 } 496 deleteCallback := func(t *Topic) { 497 n.DeleteExistingTopic(t.name) 498 } 499 t = NewTopic(topicName, n, deleteCallback) 500 n.topicMap[topicName] = t 501 502 n.Unlock() 503 504 n.logf(LOG_INFO, "TOPIC(%s): created", t.name) 505 // topic is created but messagePump not yet started 506 507 // if this topic was created while loading metadata at startup don't do any further initialization 508 // (topic will be "started" after loading completes) 509 if atomic.LoadInt32(&n.isLoading) == 1 { 510 return t 511 } 512 513 // if using lookupd, make a blocking call to get channels and immediately create them 514 // to ensure that all channels receive published messages 515 lookupdHTTPAddrs := n.lookupdHTTPAddrs() 516 if len(lookupdHTTPAddrs) > 0 { 517 channelNames, err := n.ci.GetLookupdTopicChannels(t.name, lookupdHTTPAddrs) 518 if err != nil { 519 n.logf(LOG_WARN, "failed to query nsqlookupd for channels to pre-create for topic %s - %s", t.name, err) 520 } 521 for _, channelName := range channelNames { 522 if strings.HasSuffix(channelName, "#ephemeral") { 523 continue // do not create ephemeral channel with no consumer client 524 } 525 t.GetChannel(channelName) 526 } 527 } else if len(n.getOpts().NSQLookupdTCPAddresses) > 0 { 528 n.logf(LOG_ERROR, "no available nsqlookupd to query for channels to pre-create for topic %s", t.name) 529 } 530 531 // now that all channels are added, start topic messagePump 532 t.Start() 533 return t 534 } 535 536 // GetExistingTopic gets a topic only if it exists 537 func (n *NSQD) GetExistingTopic(topicName string) (*Topic, error) { 538 n.RLock() 539 defer n.RUnlock() 540 topic, ok := n.topicMap[topicName] 541 if !ok { 542 return nil, errors.New("topic does not exist") 543 } 544 return topic, nil 545 } 546 547 // DeleteExistingTopic removes a topic only if it exists 548 func (n *NSQD) DeleteExistingTopic(topicName string) error { 549 n.RLock() 550 topic, ok := n.topicMap[topicName] 551 if !ok { 552 n.RUnlock() 553 return errors.New("topic does not exist") 554 } 555 n.RUnlock() 556 557 // delete empties all channels and the topic itself before closing 558 // (so that we dont leave any messages around) 559 // 560 // we do this before removing the topic from map below (with no lock) 561 // so that any incoming writes will error and not create a new topic 562 // to enforce ordering 563 topic.Delete() 564 565 n.Lock() 566 delete(n.topicMap, topicName) 567 n.Unlock() 568 569 return nil 570 } 571 572 func (n *NSQD) Notify(v interface{}, persist bool) { 573 // since the in-memory metadata is incomplete, 574 // should not persist metadata while loading it. 575 // nsqd will call `PersistMetadata` it after loading 576 loading := atomic.LoadInt32(&n.isLoading) == 1 577 n.waitGroup.Wrap(func() { 578 // by selecting on exitChan we guarantee that 579 // we do not block exit, see issue #123 580 select { 581 case <-n.exitChan: 582 case n.notifyChan <- v: 583 if loading || !persist { 584 return 585 } 586 n.Lock() 587 err := n.PersistMetadata() 588 if err != nil { 589 n.logf(LOG_ERROR, "failed to persist metadata - %s", err) 590 } 591 n.Unlock() 592 } 593 }) 594 } 595 596 // channels returns a flat slice of all channels in all topics 597 func (n *NSQD) channels() []*Channel { 598 var channels []*Channel 599 n.RLock() 600 for _, t := range n.topicMap { 601 t.RLock() 602 for _, c := range t.channelMap { 603 channels = append(channels, c) 604 } 605 t.RUnlock() 606 } 607 n.RUnlock() 608 return channels 609 } 610 611 // resizePool adjusts the size of the pool of queueScanWorker goroutines 612 // 613 // 1 <= pool <= min(num * 0.25, QueueScanWorkerPoolMax) 614 func (n *NSQD) resizePool(num int, workCh chan *Channel, responseCh chan bool, closeCh chan int) { 615 idealPoolSize := int(float64(num) * 0.25) 616 if idealPoolSize < 1 { 617 idealPoolSize = 1 618 } else if idealPoolSize > n.getOpts().QueueScanWorkerPoolMax { 619 idealPoolSize = n.getOpts().QueueScanWorkerPoolMax 620 } 621 for { 622 if idealPoolSize == n.poolSize { 623 break 624 } else if idealPoolSize < n.poolSize { 625 // contract 626 closeCh <- 1 627 n.poolSize-- 628 } else { 629 // expand 630 n.waitGroup.Wrap(func() { 631 n.queueScanWorker(workCh, responseCh, closeCh) 632 }) 633 n.poolSize++ 634 } 635 } 636 } 637 638 // queueScanWorker receives work (in the form of a channel) from queueScanLoop 639 // and processes the deferred and in-flight queues 640 func (n *NSQD) queueScanWorker(workCh chan *Channel, responseCh chan bool, closeCh chan int) { 641 for { 642 select { 643 case c := <-workCh: 644 now := time.Now().UnixNano() 645 dirty := false 646 if c.processInFlightQueue(now) { 647 dirty = true 648 } 649 if c.processDeferredQueue(now) { 650 dirty = true 651 } 652 responseCh <- dirty 653 case <-closeCh: 654 return 655 } 656 } 657 } 658 659 // queueScanLoop runs in a single goroutine to process in-flight and deferred 660 // priority queues. It manages a pool of queueScanWorker (configurable max of 661 // QueueScanWorkerPoolMax (default: 4)) that process channels concurrently. 662 // 663 // It copies Redis's probabilistic expiration algorithm: it wakes up every 664 // QueueScanInterval (default: 100ms) to select a random QueueScanSelectionCount 665 // (default: 20) channels from a locally cached list (refreshed every 666 // QueueScanRefreshInterval (default: 5s)). 667 // 668 // If either of the queues had work to do the channel is considered "dirty". 669 // 670 // If QueueScanDirtyPercent (default: 25%) of the selected channels were dirty, 671 // the loop continues without sleep. 672 func (n *NSQD) queueScanLoop() { 673 workCh := make(chan *Channel, n.getOpts().QueueScanSelectionCount) 674 responseCh := make(chan bool, n.getOpts().QueueScanSelectionCount) 675 closeCh := make(chan int) 676 677 workTicker := time.NewTicker(n.getOpts().QueueScanInterval) 678 refreshTicker := time.NewTicker(n.getOpts().QueueScanRefreshInterval) 679 680 channels := n.channels() 681 n.resizePool(len(channels), workCh, responseCh, closeCh) 682 683 for { 684 select { 685 case <-workTicker.C: 686 if len(channels) == 0 { 687 continue 688 } 689 case <-refreshTicker.C: 690 channels = n.channels() 691 n.resizePool(len(channels), workCh, responseCh, closeCh) 692 continue 693 case <-n.exitChan: 694 goto exit 695 } 696 697 num := n.getOpts().QueueScanSelectionCount 698 if num > len(channels) { 699 num = len(channels) 700 } 701 702 loop: 703 for _, i := range util.UniqRands(num, len(channels)) { 704 workCh <- channels[i] 705 } 706 707 numDirty := 0 708 for i := 0; i < num; i++ { 709 if <-responseCh { 710 numDirty++ 711 } 712 } 713 714 if float64(numDirty)/float64(num) > n.getOpts().QueueScanDirtyPercent { 715 goto loop 716 } 717 } 718 719 exit: 720 n.logf(LOG_INFO, "QUEUESCAN: closing") 721 close(closeCh) 722 workTicker.Stop() 723 refreshTicker.Stop() 724 } 725 726 func buildTLSConfig(opts *Options) (*tls.Config, error) { 727 var tlsConfig *tls.Config 728 729 if opts.TLSCert == "" && opts.TLSKey == "" { 730 return nil, nil 731 } 732 733 tlsClientAuthPolicy := tls.VerifyClientCertIfGiven 734 735 cert, err := tls.LoadX509KeyPair(opts.TLSCert, opts.TLSKey) 736 if err != nil { 737 return nil, err 738 } 739 switch opts.TLSClientAuthPolicy { 740 case "require": 741 tlsClientAuthPolicy = tls.RequireAnyClientCert 742 case "require-verify": 743 tlsClientAuthPolicy = tls.RequireAndVerifyClientCert 744 default: 745 tlsClientAuthPolicy = tls.NoClientCert 746 } 747 748 tlsConfig = &tls.Config{ 749 Certificates: []tls.Certificate{cert}, 750 ClientAuth: tlsClientAuthPolicy, 751 MinVersion: opts.TLSMinVersion, 752 } 753 754 if opts.TLSRootCAFile != "" { 755 tlsCertPool := x509.NewCertPool() 756 caCertFile, err := os.ReadFile(opts.TLSRootCAFile) 757 if err != nil { 758 return nil, err 759 } 760 if !tlsCertPool.AppendCertsFromPEM(caCertFile) { 761 return nil, errors.New("failed to append certificate to pool") 762 } 763 tlsConfig.ClientCAs = tlsCertPool 764 } 765 766 return tlsConfig, nil 767 } 768 769 func buildClientTLSConfig(opts *Options) (*tls.Config, error) { 770 tlsConfig := &tls.Config{ 771 MinVersion: opts.TLSMinVersion, 772 } 773 774 if opts.TLSRootCAFile != "" { 775 tlsCertPool := x509.NewCertPool() 776 caCertFile, err := os.ReadFile(opts.TLSRootCAFile) 777 if err != nil { 778 return nil, err 779 } 780 if !tlsCertPool.AppendCertsFromPEM(caCertFile) { 781 return nil, errors.New("failed to append certificate to pool") 782 } 783 tlsConfig.RootCAs = tlsCertPool 784 } 785 786 return tlsConfig, nil 787 } 788 789 func (n *NSQD) IsAuthEnabled() bool { 790 return len(n.getOpts().AuthHTTPAddresses) != 0 791 } 792 793 // Context returns a context that will be canceled when nsqd initiates the shutdown 794 func (n *NSQD) Context() context.Context { 795 return n.ctx 796 }