github.com/nsqio/nsq@v1.3.0/nsqadmin/nsqadmin.go (about) 1 package nsqadmin 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "crypto/x509" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "log" 11 "net" 12 "net/http" 13 "net/url" 14 "os" 15 "path" 16 "sync" 17 "sync/atomic" 18 19 "github.com/nsqio/nsq/internal/http_api" 20 "github.com/nsqio/nsq/internal/util" 21 "github.com/nsqio/nsq/internal/version" 22 ) 23 24 type NSQAdmin struct { 25 sync.RWMutex 26 opts atomic.Value 27 httpListener net.Listener 28 waitGroup util.WaitGroupWrapper 29 notifications chan *AdminAction 30 graphiteURL *url.URL 31 httpClientTLSConfig *tls.Config 32 } 33 34 func New(opts *Options) (*NSQAdmin, error) { 35 if opts.Logger == nil { 36 opts.Logger = log.New(os.Stderr, opts.LogPrefix, log.Ldate|log.Ltime|log.Lmicroseconds) 37 } 38 39 n := &NSQAdmin{ 40 notifications: make(chan *AdminAction), 41 } 42 n.swapOpts(opts) 43 44 if len(opts.NSQDHTTPAddresses) == 0 && len(opts.NSQLookupdHTTPAddresses) == 0 { 45 return nil, errors.New("--nsqd-http-address or --lookupd-http-address required") 46 } 47 48 if len(opts.NSQDHTTPAddresses) != 0 && len(opts.NSQLookupdHTTPAddresses) != 0 { 49 return nil, errors.New("use --nsqd-http-address or --lookupd-http-address not both") 50 } 51 52 if opts.HTTPClientTLSCert != "" && opts.HTTPClientTLSKey == "" { 53 return nil, errors.New("--http-client-tls-key must be specified with --http-client-tls-cert") 54 } 55 56 if opts.HTTPClientTLSKey != "" && opts.HTTPClientTLSCert == "" { 57 return nil, errors.New("--http-client-tls-cert must be specified with --http-client-tls-key") 58 } 59 60 n.httpClientTLSConfig = &tls.Config{ 61 InsecureSkipVerify: opts.HTTPClientTLSInsecureSkipVerify, 62 } 63 if opts.HTTPClientTLSCert != "" && opts.HTTPClientTLSKey != "" { 64 cert, err := tls.LoadX509KeyPair(opts.HTTPClientTLSCert, opts.HTTPClientTLSKey) 65 if err != nil { 66 return nil, fmt.Errorf("failed to LoadX509KeyPair %s, %s - %s", 67 opts.HTTPClientTLSCert, opts.HTTPClientTLSKey, err) 68 } 69 n.httpClientTLSConfig.Certificates = []tls.Certificate{cert} 70 } 71 if opts.HTTPClientTLSRootCAFile != "" { 72 tlsCertPool := x509.NewCertPool() 73 caCertFile, err := os.ReadFile(opts.HTTPClientTLSRootCAFile) 74 if err != nil { 75 return nil, fmt.Errorf("failed to read TLS root CA file %s - %s", 76 opts.HTTPClientTLSRootCAFile, err) 77 } 78 if !tlsCertPool.AppendCertsFromPEM(caCertFile) { 79 return nil, fmt.Errorf("failed to AppendCertsFromPEM %s", opts.HTTPClientTLSRootCAFile) 80 } 81 n.httpClientTLSConfig.RootCAs = tlsCertPool 82 } 83 84 for _, address := range opts.NSQLookupdHTTPAddresses { 85 _, err := net.ResolveTCPAddr("tcp", address) 86 if err != nil { 87 return nil, fmt.Errorf("failed to resolve --lookupd-http-address (%s) - %s", address, err) 88 } 89 } 90 91 for _, address := range opts.NSQDHTTPAddresses { 92 _, err := net.ResolveTCPAddr("tcp", address) 93 if err != nil { 94 return nil, fmt.Errorf("failed to resolve --nsqd-http-address (%s) - %s", address, err) 95 } 96 } 97 98 if opts.ProxyGraphite { 99 url, err := url.Parse(opts.GraphiteURL) 100 if err != nil { 101 return nil, fmt.Errorf("failed to parse --graphite-url (%s) - %s", opts.GraphiteURL, err) 102 } 103 n.graphiteURL = url 104 } 105 106 if opts.AllowConfigFromCIDR != "" { 107 _, _, err := net.ParseCIDR(opts.AllowConfigFromCIDR) 108 if err != nil { 109 return nil, fmt.Errorf("failed to parse --allow-config-from-cidr (%s) - %s", opts.AllowConfigFromCIDR, err) 110 } 111 } 112 113 opts.BasePath = normalizeBasePath(opts.BasePath) 114 115 n.logf(LOG_INFO, version.String("nsqadmin")) 116 117 var err error 118 n.httpListener, err = net.Listen("tcp", n.getOpts().HTTPAddress) 119 if err != nil { 120 return nil, fmt.Errorf("listen (%s) failed - %s", n.getOpts().HTTPAddress, err) 121 } 122 123 return n, nil 124 } 125 126 func normalizeBasePath(p string) string { 127 if len(p) == 0 { 128 return "/" 129 } 130 // add leading slash 131 if p[0] != '/' { 132 p = "/" + p 133 } 134 return path.Clean(p) 135 } 136 137 func (n *NSQAdmin) getOpts() *Options { 138 return n.opts.Load().(*Options) 139 } 140 141 func (n *NSQAdmin) swapOpts(opts *Options) { 142 n.opts.Store(opts) 143 } 144 145 func (n *NSQAdmin) RealHTTPAddr() *net.TCPAddr { 146 return n.httpListener.Addr().(*net.TCPAddr) 147 } 148 149 func (n *NSQAdmin) handleAdminActions() { 150 for action := range n.notifications { 151 content, err := json.Marshal(action) 152 if err != nil { 153 n.logf(LOG_ERROR, "failed to serialize admin action - %s", err) 154 } 155 httpclient := &http.Client{ 156 Transport: http_api.NewDeadlineTransport(n.getOpts().HTTPClientConnectTimeout, n.getOpts().HTTPClientRequestTimeout), 157 } 158 n.logf(LOG_INFO, "POSTing notification to %s", n.getOpts().NotificationHTTPEndpoint) 159 resp, err := httpclient.Post(n.getOpts().NotificationHTTPEndpoint, 160 "application/json", bytes.NewBuffer(content)) 161 if err != nil { 162 n.logf(LOG_ERROR, "failed to POST notification - %s", err) 163 } 164 resp.Body.Close() 165 } 166 } 167 168 func (n *NSQAdmin) Main() error { 169 exitCh := make(chan error) 170 var once sync.Once 171 exitFunc := func(err error) { 172 once.Do(func() { 173 if err != nil { 174 n.logf(LOG_FATAL, "%s", err) 175 } 176 exitCh <- err 177 }) 178 } 179 180 httpServer := NewHTTPServer(n) 181 n.waitGroup.Wrap(func() { 182 exitFunc(http_api.Serve(n.httpListener, http_api.CompressHandler(httpServer), "HTTP", n.logf)) 183 }) 184 n.waitGroup.Wrap(n.handleAdminActions) 185 186 err := <-exitCh 187 return err 188 } 189 190 func (n *NSQAdmin) Exit() { 191 if n.httpListener != nil { 192 n.httpListener.Close() 193 } 194 close(n.notifications) 195 n.waitGroup.Wait() 196 }