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  }