...

Source file src/edge-infra.dev/pkg/sds/interlock/interlock.go

Documentation: edge-infra.dev/pkg/sds/interlock

     1  // Package classification Interlock
     2  //
     3  // Documentation for the Interlock API
     4  //
     5  //	Schemes: http
     6  //	BasePath: /
     7  //	Version: 1.0.0
     8  //	Host: interlock.interlock.svc.cluster.local
     9  //
    10  //	Consumes:
    11  //	- application/json
    12  //
    13  //	Produces:
    14  //	- application/json
    15  //
    16  // swagger:meta
    17  package interlock
    18  
    19  import (
    20  	"context"
    21  	"flag"
    22  	"fmt"
    23  	"net"
    24  	"net/http"
    25  	"os"
    26  	"slices"
    27  	"strconv"
    28  
    29  	"github.com/gin-contrib/requestid"
    30  	"github.com/gin-gonic/gin"
    31  	"github.com/peterbourgon/ff/v3"
    32  	"github.com/spf13/afero"
    33  
    34  	"edge-infra.dev/pkg/lib/fog"
    35  	"edge-infra.dev/pkg/sds/interlock/internal/config"
    36  	errs "edge-infra.dev/pkg/sds/interlock/internal/errors"
    37  	"edge-infra.dev/pkg/sds/interlock/internal/middleware"
    38  	"edge-infra.dev/pkg/sds/interlock/internal/observability"
    39  	"edge-infra.dev/pkg/sds/interlock/topic/cluster"
    40  	"edge-infra.dev/pkg/sds/interlock/topic/host"
    41  	"edge-infra.dev/pkg/sds/interlock/topic/instances"
    42  	"edge-infra.dev/pkg/sds/interlock/websocket"
    43  )
    44  
    45  // +kubebuilder:rbac:groups="",resources=nodes;pods,verbs=get;watch;list
    46  
    47  const (
    48  	InterlockAPIPort        = 80
    49  	InterlockMetricsAPIPort = 8080
    50  )
    51  
    52  // EndpointRegistrant is an interface for package to register endpoints to a
    53  // router
    54  type EndpointRegistrant interface {
    55  	RegisterEndpoints(*gin.Engine)
    56  }
    57  
    58  // api represents an API provider
    59  type api struct {
    60  	router *gin.Engine
    61  	port   int
    62  }
    63  
    64  // newAPI creates a new API instance with the provided router, middleware and
    65  // port
    66  func newAPI(port int, router *gin.Engine, middleware ...gin.HandlerFunc) *api {
    67  	router.Use(middleware...)
    68  	return &api{
    69  		router,
    70  		port,
    71  	}
    72  }
    73  
    74  // registerEndpoints registers the 404, 405 and root endpoints alongside all
    75  // provided EndpointRegistrant endpoints
    76  func (a *api) registerEndpoints(ers ...EndpointRegistrant) {
    77  	a.router.NoRoute(func(c *gin.Context) {
    78  		c.JSON(http.StatusNotFound, errs.NewErrorResponse(errs.NewError(http.StatusText(http.StatusNotFound))))
    79  	})
    80  	a.router.HandleMethodNotAllowed = true
    81  	a.router.NoMethod(func(c *gin.Context) {
    82  		c.JSON(http.StatusMethodNotAllowed, errs.NewErrorResponse(errs.NewError(http.StatusText(http.StatusMethodNotAllowed))))
    83  	})
    84  	for _, er := range ers {
    85  		er.RegisterEndpoints(a.router)
    86  	}
    87  	a.registerRoot()
    88  }
    89  
    90  // registerRoot registers an API index at the root endpoint
    91  func (a *api) registerRoot() {
    92  	endpoints := []string{}
    93  	for _, route := range a.router.Routes() {
    94  		if !slices.Contains(endpoints, route.Path) {
    95  			endpoints = append(endpoints, route.Path)
    96  		}
    97  	}
    98  	a.router.GET("/", func(c *gin.Context) {
    99  		c.IndentedJSON(200, endpoints)
   100  	})
   101  }
   102  
   103  // run the API router on the API port
   104  func (a *api) run() error {
   105  	addr := net.JoinHostPort("", strconv.Itoa(a.port))
   106  	return a.router.Run(addr)
   107  }
   108  
   109  // Run the Interlock webserver and start the Interlock API and Interlock metrics
   110  // API
   111  func Run() error {
   112  	log := observability.NewLogger()
   113  	log.Info("starting Interlock API")
   114  	ctx := fog.IntoContext(context.Background(), log)
   115  
   116  	api := newAPI(
   117  		InterlockAPIPort, // on port 80
   118  		gin.New(),        // custom gin engine
   119  		requestid.New(),
   120  		middleware.SetLoggerInContext(log),
   121  		middleware.RequestLogger(),
   122  		middleware.RequestRecorder(),
   123  		middleware.SetAccessControlHeaders(),
   124  		gin.Recovery(),
   125  	)
   126  
   127  	fs := afero.NewOsFs()
   128  	cfg, err := config.New(fs)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	flags := flag.NewFlagSet("interlock", flag.ExitOnError)
   133  	cfg.BindFlags(flags)
   134  	if err := ff.Parse(flags, os.Args[1:], ff.WithEnvVarNoPrefix(), ff.WithIgnoreUndefined(true)); err != nil {
   135  		return err
   136  	}
   137  
   138  	websocketManager := websocket.NewManager()
   139  	cluster, err := cluster.New(ctx, cfg, websocketManager)
   140  	if err != nil {
   141  		return fmt.Errorf("failed to set up cluster topic: %w", err)
   142  	}
   143  	host, err := host.New(ctx, cfg, websocketManager)
   144  	if err != nil {
   145  		return fmt.Errorf("failed to set up host topic: %w", err)
   146  	}
   147  	instances, err := instances.New(ctx, cfg, websocketManager)
   148  	if err != nil {
   149  		return fmt.Errorf("failed to set up discover topic: %w", err)
   150  	}
   151  
   152  	api.registerEndpoints(
   153  		websocketManager,
   154  		host,
   155  		cluster,
   156  		instances,
   157  	)
   158  
   159  	metrics := newAPI(
   160  		InterlockMetricsAPIPort, // on port 8080
   161  		gin.Default(),           // default gin engine
   162  	)
   163  	metrics.registerEndpoints(
   164  		observability.NewMetrics(),
   165  	)
   166  	go func() {
   167  		// run the Interlock metrics API and exit the program if there is an
   168  		// error
   169  		if err := metrics.run(); err != nil {
   170  			log.Error(err, "error while running metrics server")
   171  			os.Exit(1)
   172  		}
   173  	}()
   174  	go func() {
   175  		// run the Interlock informers to keep state up to date with API Server
   176  		if err := cfg.Cache.Start(ctx); err != nil {
   177  			log.Error(err, "failed to run informers")
   178  		}
   179  	}()
   180  	// run the Interlock API and return the error if any
   181  	return api.run()
   182  }
   183  
   184  // The request has failed
   185  //
   186  // swagger:response ErrorResponse
   187  type ErrorResponseWrapper struct {
   188  	// Descriptions of the errors that occurred
   189  	//
   190  	// in: body
   191  	Body Errors `json:"body"`
   192  }
   193  
   194  // Errors returned on API failures
   195  type Errors struct {
   196  	Errors []errs.Error `json:"errors"`
   197  }
   198  

View as plain text