add diagnostic api http server
This commit is contained in:
		
							parent
							
								
									51fdfb90f7
								
							
						
					
					
						commit
						82d5583809
					
				|  | @ -0,0 +1,217 @@ | ||||||
|  | package apiserver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/http/pprof" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/Sirupsen/logrus" | ||||||
|  | 
 | ||||||
|  | 	"github.com/zalando-incubator/postgres-operator/pkg/spec" | ||||||
|  | 	"github.com/zalando-incubator/postgres-operator/pkg/util/config" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	httpAPITimeout  = time.Minute * 1 | ||||||
|  | 	shutdownTimeout = time.Second * 10 | ||||||
|  | 	httpReadTimeout = time.Millisecond * 100 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // ControllerInformer describes stats methods of a controller
 | ||||||
|  | type ControllerInformer interface { | ||||||
|  | 	GetConfig() *spec.ControllerConfig | ||||||
|  | 	GetOperatorConfig() *config.Config | ||||||
|  | 	GetStatus() *spec.ControllerStatus | ||||||
|  | 	TeamClusterList() map[string][]spec.NamespacedName | ||||||
|  | 	ClusterStatus(team, cluster string) (*spec.ClusterStatus, error) | ||||||
|  | 	ClusterLogs(team, cluster string) ([]*spec.LogEntry, error) | ||||||
|  | 	WorkerLogs(workerID uint32) ([]*spec.LogEntry, error) | ||||||
|  | 	ListQueue(workerID uint32) (*spec.QueueDump, error) | ||||||
|  | 	GetWorkersCnt() uint32 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Server describes HTTP API server
 | ||||||
|  | type Server struct { | ||||||
|  | 	logger     *logrus.Entry | ||||||
|  | 	http       http.Server | ||||||
|  | 	controller ControllerInformer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	clusterStatusURL     = regexp.MustCompile(`^/clusters/(?P<team>[a-zA-Z][a-zA-Z0-9]*)/(?P<cluster>[a-zA-Z][a-zA-Z0-9]*)/?$`) | ||||||
|  | 	clusterLogsURL       = regexp.MustCompile(`^/clusters/(?P<team>[a-zA-Z][a-zA-Z0-9]*)/(?P<cluster>[a-zA-Z][a-zA-Z0-9]*)/logs/?$`) | ||||||
|  | 	teamURL              = regexp.MustCompile(`^/clusters/(?P<team>[a-zA-Z][a-zA-Z0-9]*)/?$`) | ||||||
|  | 	workerLogsURL        = regexp.MustCompile(`^/workers/(?P<id>\d+)/logs/?$`) | ||||||
|  | 	workerEventsQueueURL = regexp.MustCompile(`^/workers/(?P<id>\d+)/queue/?$`) | ||||||
|  | 	workerAllQueue       = regexp.MustCompile(`^/workers/all/queue/?$`) | ||||||
|  | 	clustersURL          = "/clusters/" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // New creates new HTTP API server
 | ||||||
|  | func New(controller ControllerInformer, port int, logger *logrus.Logger) *Server { | ||||||
|  | 	s := &Server{ | ||||||
|  | 		logger:     logger.WithField("pkg", "apiserver"), | ||||||
|  | 		controller: controller, | ||||||
|  | 	} | ||||||
|  | 	mux := http.NewServeMux() | ||||||
|  | 
 | ||||||
|  | 	mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) | ||||||
|  | 	mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) | ||||||
|  | 	mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) | ||||||
|  | 	mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) | ||||||
|  | 	mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) | ||||||
|  | 
 | ||||||
|  | 	mux.Handle("/status/", http.HandlerFunc(s.controllerStatus)) | ||||||
|  | 	mux.Handle("/config/", http.HandlerFunc(s.operatorConfig)) | ||||||
|  | 
 | ||||||
|  | 	mux.HandleFunc("/clusters/", s.clusters) | ||||||
|  | 	mux.HandleFunc("/workers/", s.workers) | ||||||
|  | 
 | ||||||
|  | 	s.http = http.Server{ | ||||||
|  | 		Addr:        fmt.Sprintf(":%d", port), | ||||||
|  | 		Handler:     http.TimeoutHandler(mux, httpAPITimeout, ""), | ||||||
|  | 		ReadTimeout: httpReadTimeout, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Run starts the HTTP server
 | ||||||
|  | func (s *Server) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) { | ||||||
|  | 	defer wg.Done() | ||||||
|  | 
 | ||||||
|  | 	go func() { | ||||||
|  | 		err := s.http.ListenAndServe() | ||||||
|  | 		if err != http.ErrServerClosed { | ||||||
|  | 			s.logger.Fatalf("Could not start http server: %v", err) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 	s.logger.Infof("Listening on %s", s.http.Addr) | ||||||
|  | 
 | ||||||
|  | 	<-stopCh | ||||||
|  | 
 | ||||||
|  | 	ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) | ||||||
|  | 	defer cancel() | ||||||
|  | 	err := s.http.Shutdown(ctx) | ||||||
|  | 	if err == context.DeadlineExceeded { | ||||||
|  | 		s.logger.Warnf("Shutdown timeout exceeded. closing http server") | ||||||
|  | 		s.http.Close() | ||||||
|  | 	} else if err != nil { | ||||||
|  | 		s.logger.Errorf("Could not shutdown http server: %v", err) | ||||||
|  | 	} | ||||||
|  | 	s.logger.Infoln("Http server shut down") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) respond(obj interface{}, err error, w http.ResponseWriter) { | ||||||
|  | 	w.Header().Set("Content-Type", "application/json") | ||||||
|  | 	if err != nil { | ||||||
|  | 		w.WriteHeader(http.StatusInternalServerError) | ||||||
|  | 		json.NewEncoder(w).Encode(map[string]interface{}{"error": err.Error()}) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = json.NewEncoder(w).Encode(obj) | ||||||
|  | 	if err != nil { | ||||||
|  | 		w.WriteHeader(http.StatusInternalServerError) | ||||||
|  | 		s.logger.Errorf("Could not encode: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) controllerStatus(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 	s.respond(s.controller.GetStatus(), nil, w) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) operatorConfig(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 	s.respond(map[string]interface{}{ | ||||||
|  | 		"controller": s.controller.GetConfig(), | ||||||
|  | 		"operator":   s.controller.GetOperatorConfig(), | ||||||
|  | 	}, nil, w) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) clusters(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 	var ( | ||||||
|  | 		resp interface{} | ||||||
|  | 		err  error | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	if matches := clusterStatusURL.FindAllStringSubmatch(req.URL.Path, -1); matches != nil { | ||||||
|  | 		resp, err = s.controller.ClusterStatus(matches[0][1], matches[0][2]) | ||||||
|  | 	} else if matches := teamURL.FindAllStringSubmatch(req.URL.Path, -1); matches != nil { | ||||||
|  | 		teamClusters := s.controller.TeamClusterList() | ||||||
|  | 		clusters, found := teamClusters[matches[0][1]] | ||||||
|  | 		if !found { | ||||||
|  | 			s.respond(nil, fmt.Errorf("could not find clusters for the team"), w) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		clusterNames := make([]string, 0) | ||||||
|  | 		for _, cluster := range clusters { | ||||||
|  | 			clusterNames = append(clusterNames, cluster.Name[len(matches[0][1])+1:]) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		s.respond(clusterNames, nil, w) | ||||||
|  | 		return | ||||||
|  | 	} else if matches := clusterLogsURL.FindAllStringSubmatch(req.URL.Path, -1); matches != nil { | ||||||
|  | 		resp, err = s.controller.ClusterLogs(matches[0][1], matches[0][2]) | ||||||
|  | 	} else if req.URL.Path == clustersURL { | ||||||
|  | 		res := make(map[string][]string) | ||||||
|  | 		for team, clusters := range s.controller.TeamClusterList() { | ||||||
|  | 			for _, cluster := range clusters { | ||||||
|  | 				res[team] = append(res[team], cluster.Name[len(team)+1:]) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		s.respond(res, nil, w) | ||||||
|  | 		return | ||||||
|  | 	} else { | ||||||
|  | 		s.respond(nil, fmt.Errorf("page not found"), w) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	s.respond(resp, err, w) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) workers(w http.ResponseWriter, req *http.Request) { | ||||||
|  | 	var ( | ||||||
|  | 		resp interface{} | ||||||
|  | 		err  error | ||||||
|  | 	) | ||||||
|  | 	if workerAllQueue.MatchString(req.URL.Path) { | ||||||
|  | 		s.allQueues(w, req) | ||||||
|  | 		return | ||||||
|  | 	} else if matches := workerLogsURL.FindAllStringSubmatch(req.URL.Path, -1); matches != nil { | ||||||
|  | 		workerID, _ := strconv.Atoi(matches[0][1]) | ||||||
|  | 
 | ||||||
|  | 		resp, err = s.controller.WorkerLogs(uint32(workerID)) | ||||||
|  | 	} else if matches := workerEventsQueueURL.FindAllStringSubmatch(req.URL.Path, -1); matches != nil { | ||||||
|  | 		workerID, _ := strconv.Atoi(matches[0][1]) | ||||||
|  | 
 | ||||||
|  | 		resp, err = s.controller.ListQueue(uint32(workerID)) | ||||||
|  | 	} else { | ||||||
|  | 		s.respond(nil, fmt.Errorf("page not found"), w) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	s.respond(resp, err, w) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Server) allQueues(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	workersCnt := s.controller.GetWorkersCnt() | ||||||
|  | 	resp := make(map[uint32]*spec.QueueDump, workersCnt) | ||||||
|  | 	for i := uint32(0); i < workersCnt; i++ { | ||||||
|  | 		queueDump, err := s.controller.ListQueue(i) | ||||||
|  | 		if err != nil { | ||||||
|  | 			s.respond(nil, err, w) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		resp[i] = queueDump | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	s.respond(resp, nil, w) | ||||||
|  | } | ||||||
|  | @ -10,6 +10,7 @@ import ( | ||||||
| 	"k8s.io/client-go/rest" | 	"k8s.io/client-go/rest" | ||||||
| 	"k8s.io/client-go/tools/cache" | 	"k8s.io/client-go/tools/cache" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/zalando-incubator/postgres-operator/pkg/apiserver" | ||||||
| 	"github.com/zalando-incubator/postgres-operator/pkg/cluster" | 	"github.com/zalando-incubator/postgres-operator/pkg/cluster" | ||||||
| 	"github.com/zalando-incubator/postgres-operator/pkg/spec" | 	"github.com/zalando-incubator/postgres-operator/pkg/spec" | ||||||
| 	"github.com/zalando-incubator/postgres-operator/pkg/util/config" | 	"github.com/zalando-incubator/postgres-operator/pkg/util/config" | ||||||
|  | @ -26,6 +27,7 @@ type Controller struct { | ||||||
| 	logger     *logrus.Entry | 	logger     *logrus.Entry | ||||||
| 	KubeClient k8sutil.KubernetesClient | 	KubeClient k8sutil.KubernetesClient | ||||||
| 	RestClient rest.Interface // kubernetes API group REST client
 | 	RestClient rest.Interface // kubernetes API group REST client
 | ||||||
|  | 	apiserver  *apiserver.Server | ||||||
| 
 | 
 | ||||||
| 	stopCh chan struct{} | 	stopCh chan struct{} | ||||||
| 
 | 
 | ||||||
|  | @ -170,16 +172,19 @@ func (c *Controller) initController() { | ||||||
| 			return fmt.Sprintf("%s-%s", e.EventType, e.UID), nil | 			return fmt.Sprintf("%s-%s", e.EventType, e.UID), nil | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	c.apiserver = apiserver.New(c, c.opConfig.APIPort, c.logger.Logger) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Run starts background controller processes
 | // Run starts background controller processes
 | ||||||
| func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) { | func (c *Controller) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) { | ||||||
| 	c.initController() | 	c.initController() | ||||||
| 
 | 
 | ||||||
| 	wg.Add(3) | 	wg.Add(4) | ||||||
| 	go c.runPodInformer(stopCh, wg) | 	go c.runPodInformer(stopCh, wg) | ||||||
| 	go c.runPostgresqlInformer(stopCh, wg) | 	go c.runPostgresqlInformer(stopCh, wg) | ||||||
| 	go c.clusterResync(stopCh, wg) | 	go c.clusterResync(stopCh, wg) | ||||||
|  | 	go c.apiserver.Run(stopCh, wg) | ||||||
| 
 | 
 | ||||||
| 	for i := range c.clusterEventQueues { | 	for i := range c.clusterEventQueues { | ||||||
| 		wg.Add(1) | 		wg.Add(1) | ||||||
|  |  | ||||||
|  | @ -61,6 +61,7 @@ type Config struct { | ||||||
| 	MasterDNSNameFormat  stringTemplate `name:"master_dns_name_format" default:"{cluster}.{team}.{hostedzone}"` | 	MasterDNSNameFormat  stringTemplate `name:"master_dns_name_format" default:"{cluster}.{team}.{hostedzone}"` | ||||||
| 	ReplicaDNSNameFormat stringTemplate `name:"replica_dns_name_format" default:"{cluster}-repl.{team}.{hostedzone}"` | 	ReplicaDNSNameFormat stringTemplate `name:"replica_dns_name_format" default:"{cluster}-repl.{team}.{hostedzone}"` | ||||||
| 	Workers              uint32         `name:"workers" default:"4"` | 	Workers              uint32         `name:"workers" default:"4"` | ||||||
|  | 	APIPort              int            `name:"api_port" default:"8080"` | ||||||
| 	RingLogLines         int            `name:"ring_log_lines" default:"100"` | 	RingLogLines         int            `name:"ring_log_lines" default:"100"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue