github.com/cilium/cilium@v1.16.2/Documentation/contributing/development/hive.rst (about)

     1  .. only:: not (epub or latex or html)
     2  
     3      WARNING: You are looking at unreleased Cilium documentation.
     4      Please use the official rendered version released here:
     5      https://docs.cilium.io
     6  
     7  .. _guide-to-the-hive:
     8  
     9  Guide to the Hive
    10  =================
    11  
    12  Introduction
    13  ~~~~~~~~~~~~
    14  
    15  Cilium is using dependency injection (via ``pkg/hive``) to wire up the
    16  initialization, starting and stopping of its components. 
    17  
    18  `Dependency injection <https://en.wikipedia.org/wiki/Dependency_injection>`_ (DI) is a
    19  technique for separating the use of objects from their creation and
    20  initialization. Essentially dependency injection is about automating the
    21  manual management of dependencies. Object constructors only need to declare
    22  their dependencies as function parameters and the rest is handled by the library. This
    23  helps with building a loosely-coupled modular architecture as it removes the
    24  need for centralization of initialization and configuration. It also reduces
    25  the inclination to use global variables over explicit passing of objects,
    26  which is often a source of bugs (due to unexpected initialization order)
    27  and difficult to deal with in tests (as the state needs to be restored for
    28  the next test). With dependency injection components are described as plain
    29  values (``Cell`` in our flavor of DI) enabling visualization of inter-component
    30  dependencies and opening the internal architecture up for inspection.
    31  
    32  Dependency injection and the machinery described here are only a tool to
    33  help us towards the real goal: a modular software architecture that can be
    34  easily understood, extended, repurposed, tested and refactored by a large
    35  group of developers with minimal overlap between modules. To achieve this we
    36  also need to have modularity in mind when designing the architecture and APIs.
    37  
    38  Hive and Cells
    39  ~~~~~~~~~~~~~~
    40  
    41  Cilium applications are composed using runtime dependency injection from a set
    42  of modular components called cells that compose together to form a hive (as in
    43  bee hive). A hive can then be supplied with configuration and executed. To provide
    44  a feel for what this is about, here is how a simple modular HTTP server application 
    45  would leverage hive:
    46  
    47  .. code-block:: go
    48  
    49      package server
    50  
    51      // The server cell implements a generic HTTP server. Provides the 'Server' API
    52      // for registering request handlers.
    53      //
    54      // Module() creates a named collection of cells.
    55      var Cell = cell.Module(
    56         "http-server", // Module identifier (for e.g. logging and tracing)
    57         "HTTP Server", // Module title (for documentation)
    58  
    59         // Provide the application the constructor for the server.
    60         cell.Provide(New),
    61  
    62         // Config registers a configuration when provided with the defaults 
    63         // and an implementation of Flags() for registering the configuration flags.
    64         cell.Config(defaultServerConfig),
    65      )
    66  
    67      // Server allows registering request handlers with the HTTP server
    68      type Server interface {
    69          ListenAddress() string
    70          RegisterHandler(path string, fn http.HandlerFunc)
    71      }
    72  
    73      func New(lc cell.Lifecycle, cfg ServerConfig) Server { 
    74        // Initialize http.Server, register Start and Stop hooks to Lifecycle 
    75        // for starting and stopping the server and return an implementation of
    76        // 'Server' for other cells for registering handlers.
    77        // ...
    78      }
    79  
    80      type ServerConfig struct {
    81          ServerPort uint16
    82      }
    83  
    84      var defaultServerConfig = ServerConfig{
    85          ServerPort: 8080,
    86      }
    87  
    88      func (def ServerConfig) Flags(flags *pflag.FlagSet) {
    89          // Register the "server-port" flag. Hive by convention maps the flag to the ServerPort 
    90          // field.
    91          flags.Uint16("server-port",  def.ServerPort, "Sets the HTTP server listen port")
    92      }
    93  
    94  With the above generic HTTP server in the ``server`` package, we can now implement a simple handler
    95  for /hello in the ``hello`` package:
    96  
    97  .. code-block:: go
    98  
    99      package hello
   100  
   101      // The hello cell implements and registers a hello handler to the HTTP server.
   102      //
   103      // This cell isn't a Module, but rather just a plain Invoke. An Invoke
   104      // is a cell that, unlike Provide, is always executed. Invoke functions
   105      // can depend on values that constructors registered with Provide() can
   106      // return. These constructors are then called and their results remembered.
   107      var Cell = cell.Invoke(registerHelloHandler)
   108  
   109      func helloHandler(w http.ResponseWriter, req *http.Request) {
   110          w.Write([]byte("hello"))
   111      }
   112  
   113      func registerHelloHandler(srv server.Server) {
   114          srv.RegisterHandler("/hello", helloHandler)
   115      }
   116    
   117  And then put the two together into a simple application:
   118  
   119  .. code-block:: go
   120  
   121      package main
   122  
   123      var (
   124          // exampleHive is an application with an HTTP server and a handler
   125          // at /hello.
   126          exampleHive = hive.New(
   127              server.Cell,
   128              hello.Cell,
   129          )
   130  
   131          // cmd is the root command for this application. Runs
   132          // exampleHive when executed.
   133          cmd *cobra.Command = &cobra.Command{
   134              Use: "example",
   135              Run: func(cmd *cobra.Command, args []string) {
   136                  // Run() will execute all invoke functions, followed by start hooks
   137                  // and will then wait for interrupt signal before executing stop hooks
   138                  // and returning.
   139                  exampleHive.Run()
   140              },
   141          }
   142      )
   143         
   144      func main() {
   145           // Register all command-line flags from each config cell to the
   146           // flag-set of our command.
   147       	 exampleHive.RegisterFlags(cmd.Flags())
   148  
   149           // Add the "hive" sub-command for inspecting the application. 
   150           cmd.AddCommand(exampleHive.Command()))
   151  
   152           // Execute the root command.
   153           cmd.Execute()
   154      }
   155  
   156  
   157  If you prefer to learn by example you can find a more complete and runnable example
   158  application from ``pkg/hive/example``. Try running it with ``go run .`` and also try
   159  ``go run . hive``. And if you're interested in how all this is implemented internally,
   160  see ``pkg/hive/example/mini``, a minimal example of how to do dependency injection with reflection.
   161  
   162  The Hive API
   163  ~~~~~~~~~~~~
   164  
   165  With the example hopefully having now whetted the appetite, we'll take a proper look at
   166  the hive API. 
   167  
   168  `pkg/hive <https://pkg.go.dev/github.com/cilium/cilium/pkg/hive>`_ provides the Hive type and 
   169  `hive.New <https://pkg.go.dev/github.com/cilium/cilium/pkg/hive#New>`_ constructor. 
   170  The ``hive.Hive`` type can be thought of as an application container, composed from cells:
   171  
   172  .. code-block:: go
   173  
   174      var myHive = hive.New(foo.Cell, bar.Cell)
   175  
   176      // Call Run() to run the hive.     
   177      myHive.Run() // Start(), wait for signal (ctrl-c) and then Stop() 
   178  
   179      // Hive can also be started and stopped directly. Useful in tests.
   180      if err := myHive.Start(ctx); err != nil { /* ... */ }
   181      if err := myHive.Stop(ctx); err != nil { /* ... */ }
   182  
   183      // Hive's configuration can be registered with a Cobra command:
   184      hive.RegisterFlags(cmd.Flags())
   185  
   186      // Hive also provides a sub-command for inspecting it:
   187      cmd.AddCommand(hive.Command())
   188  
   189  `pkg/hive/cell <https://pkg.go.dev/github.com/cilium/cilium/pkg/hive/cell>`_ defines the Cell interface that 
   190  ``hive.New()`` consumes and the following functions for creating cells:
   191  
   192  - :ref:`api_module`: A named set of cells.
   193  - :ref:`api_provide`: Provides constructor(s) to the hive.  Lazy and only invoked if referenced by an Invoke function (directly or indirectly via other constructor).
   194  - :ref:`ProvidePrivate <api_module>`: Provides private constructor(s) to a module and its sub-modules.
   195  - :ref:`api_decorate`: Wraps a set of cells with a decorator function to provide these cells with augmented objects.
   196  - :ref:`api_config`: Provides a configuration struct to the hive.
   197  - :ref:`api_invoke`: Registers an invoke function to instantiate and initialize objects.
   198  - :ref:`api_metric`: Provides metrics to the hive.
   199  
   200  Hive also by default provides the following globally available objects:
   201  
   202  - :ref:`api_lifecycle`: Methods for registering Start and Stop functions that are executed when Hive is started and stopped. 
   203    The hooks are appended to it in dependency order (since the constructors are invoked in dependency order).
   204  - :ref:`api_shutdowner`: Allows gracefully shutting down the hive from anywhere in case of a fatal error post-start.
   205  - ``logrus.FieldLogger``: Interface to the logger. Module() decorates it with ``subsys=<module id>``.
   206  
   207  .. _api_provide:
   208  
   209  Provide
   210  ^^^^^^^
   211  
   212  We'll now take a look at each of the different kinds of cells, starting with Provide(),
   213  which registers one or more constructors with the hive:
   214  
   215  .. code-block:: go
   216  
   217      // func Provide(ctors any...) Cell
   218  
   219      type A interface {}
   220      func NewA() A { return A{} }
   221      
   222      type B interface {}
   223      func NewB(A) B { return B{} }
   224  
   225      // simpleCell provides A and B
   226      var simpleCell cell.Cell = cell.Provide(NewA, NewB) 
   227  
   228  If the constructors take many parameters, we'll want to group them into a struct with ``cell.In``,
   229  and conversely if there are many return values, into a struct with ``cell.Out``. This tells
   230  hive to unpack them:
   231  
   232  .. code-block:: go
   233  
   234      type params struct {
   235      	cell.In
   236      
   237          A A
   238          B B
   239          Lifecycle cell.Lifecycle
   240      }
   241      
   242      type out struct {
   243          cell.Out
   244      
   245          C C
   246  	D D
   247          E E
   248      }
   249      func NewCDE(params params) out { ... }
   250      
   251      var Cell = cell.Provide(NewCDE)
   252  
   253  Sometimes we want to depend on a group of values sharing the same type, e.g. to collect API handlers or metrics. This can be done with 
   254  `value groups <https://pkg.go.dev/go.uber.org/dig#hdr-Value_Groups>`_ by combining ``cell.In``
   255  and ``cell.Out`` with the ``group`` struct tag:
   256  
   257  .. code-block:: go
   258  
   259      type HandlerOut struct {
   260          cell.Out
   261  
   262          Handler Handler `group:"handlers"`
   263      }
   264      func NewHelloHandler() HandlerOut { ... }
   265      func NewEventHandler(src events.Source) HandlerOut { ... }
   266  
   267      type ServerParams struct {
   268          cell.In
   269      
   270          Handlers []Handler `group:"handlers"`
   271      }
   272  
   273      func NewServer(params ServerParams) Server {
   274        // params.Handlers will have the "Handlers" from NewHelloHandler and 
   275        // NewEventHandler.
   276      }
   277  
   278      var Hive = hive.New(
   279        cell.Provide(NewHelloHandler, NewEventHandler, NewServer)
   280      )
   281  
   282  For a working example of group values this, see ``pkg/hive/example``.
   283  
   284  Use ``Provide()`` when you want to expose an object or an interface to the application. If there is nothing meaningful
   285  to expose, consider instead using ``Invoke()`` to register lifecycle hooks for an unexported object.
   286  
   287  .. _api_invoke:
   288  
   289  Invoke
   290  ^^^^^^
   291  
   292  Invoke is used to invoke a function to initialize some part of the application. The provided constructors
   293  won't be called unless an invoke function references them, either directly or indirectly via another
   294  constructor:
   295  
   296  .. code-block:: go
   297  
   298      // func Invoke(funcs ...any) Cell
   299  
   300      cell.Invoke(
   301          // Construct both B and C and then introduce them to each other.
   302          func(b B, c C) {
   303             b.SetHandler(c)
   304             c.SetOwner(b)
   305          },
   306  
   307          // Construct D for its side-effects only (e.g. start and stop hooks).
   308          // Avoid this if you can and use Invoke() to register hooks instead of Provide() if 
   309          // there's no API to provide.
   310          func(D){},
   311      )
   312  
   313  .. _api_module:
   314  
   315  Module
   316  ^^^^^^
   317  
   318  Cells can be grouped into modules (a named set of cells):
   319  
   320  .. code-block:: go
   321  
   322      // func Module(id, title string, cells ...Cell) Cell
   323  
   324      var Cell = cell.Module(
   325      	"example",           // short identifier (for use in e.g. logging and tracing)
   326  	"An example module", // one-line description (for documentation)
   327      
   328          cell.Provide(New),
   329  
   330          innerModule,         // modules can contain other modules
   331      )
   332  
   333      var innerModule cell.Cell = cell.Module(
   334          "example-inner",
   335          "An inner module",
   336  
   337          cell.Provide(newInner),
   338      )
   339  
   340  
   341  Module() also provides the wrapped cells with a personalized ``logrus.FieldLogger``
   342  with the ``subsys`` field set to module identifier ("example" above).
   343  
   344  The scope created by Module() is useful when combined with ProvidePrivate():
   345  
   346  .. code-block:: go
   347  
   348      var Cell = cell.Module(
   349          "example",
   350          "An example module",
   351      
   352          cell.ProvidePrivate(NewA), // A only accessible from this module (or sub-modules)
   353          cell.Provide(NewB),        // B is accessible from anywhere
   354      )
   355  
   356  .. _api_decorate:
   357  
   358  Decorate
   359  ^^^^^^^^
   360  
   361  Sometimes one may want to use a modified object inside a module, for example how above Module()
   362  provided the cells with a personalized logger. This can be done with a decorator:
   363  
   364  .. code-block:: go
   365  
   366      // func Decorate(dtor any, cells ...Cell) Cell
   367  
   368      var Cell = cell.Decorate(
   369          myLogger, // The decoration function
   370  
   371  	// These cells will see the objects returned by the 'myLogger' decorator
   372          // rather than the objects on the outside.
   373          foo.Cell, 
   374          bar.Cell,
   375      )
   376  
   377      // myLogger is a decorator that can depend on one or more objects in the application
   378      // and return one or more objects. The input parameters don't necessarily need to match
   379      // the output types.
   380      func myLogger(log logrus.FieldLogger) logrus.FieldLogger {
   381          return log.WithField("lasers", "stun")
   382      }
   383  
   384  
   385  .. _api_config:
   386  
   387  Config
   388  ^^^^^^
   389  
   390  Cilium applications use the `cobra <https://github.com/spf13/cobra>`_ and
   391  `pflag <https://github.com/spf13/pflag>`_ libraries for implementing the command-line
   392  interface. With Cobra, one defines a ``Command``, with optional sub-commands. Each command
   393  has an associated FlagSet which must be populated before a command is executed in order to
   394  parse or to produce usage documentation. Hive bridges to Cobra with ``cell.Config``, which
   395  takes a value that implements ``cell.Flagger`` for adding flags to a command's FlagSet and
   396  returns a cell that "provides" the parsed configuration to the application:
   397  
   398  .. code-block:: go
   399  
   400      // type Flagger interface {
   401      //    Flags(flags *pflag.FlagSet)
   402      // }
   403      // func Config[Cfg Flagger](defaultConfig Cfg) cell.Cell
   404  
   405      type MyConfig struct {
   406          MyOption string
   407  
   408          SliceOption []string
   409          MapOption map[string]string
   410      }
   411  
   412      func (def MyConfig) Flags(flags *pflag.FlagSet) {
   413          // Register the "my-option" flag. This matched against the MyOption field
   414          // by removing any dashes and doing case insensitive comparison.
   415          flags.String("my-option", def.MyOption, "My config option")
   416  
   417          // Flags are supported for representing complex types such as slices and maps.
   418          // * Slices are obtained splitting the input string on commas.
   419          // * Maps support different formats based on how they are provided:
   420          //   - CLI: key=value format, separated by commas; the flag can be
   421          //     repeated multiple times.
   422          //   - Environment variable or configuration file: either JSON encoded
   423          //     or comma-separated key=value format.
   424          flags.StringSlice("slice-option", def.SliceOption, "My slice config option")
   425          flags.StringToString("map-option", def.MapOption, "My map config option")
   426      }
   427  
   428      var defaultMyConfig = MyConfig{
   429          MyOption: "the default value",
   430      }
   431  
   432      func New(cfg MyConfig) MyThing
   433  
   434      var Cell = cell.Module(
   435          "module-with-config",
   436          "A module with a config",
   437  
   438          cell.Config(defaultMyConfig),
   439          cell.Provide(New),
   440      )
   441  
   442  In tests the configuration can be populated in various ways:
   443  
   444  .. code-block:: go
   445  
   446      func TestCell(t *testing.T) {
   447          h := hive.New(Cell)
   448  
   449  	// Options can be set via Viper
   450          h.Viper().Set("my-option", "test-value")
   451  
   452          // Or via pflags
   453          flags := pflag.NewFlagSet("", pflag.ContinueOnError)
   454          h.RegisterFlags(flags)
   455          flags.Set("my-option", "test-value")
   456  	flags.Parse("--my-option=test-value")
   457  
   458  	// Or the preferred way with a config override:
   459  	h = hive.New(
   460              Cell,
   461          )
   462          AddConfigOverride(
   463              h,
   464              func(cfg *MyConfig) {
   465                  cfg.MyOption = "test-override"
   466              })
   467  
   468  	// To validate that the Cell can be instantiated and the configuration
   469          // struct is well-formed without starting you can call Populate():
   470          if err := h.Populate(); err != nil {
   471              t.Fatalf("Failed to populate: %s", err)
   472          }
   473      }
   474  
   475  .. _api_metric:
   476  
   477  Metric
   478  ^^^^^^
   479  
   480  The metric cell allows you to define a collection of metrics near a feature you
   481  would like to instrument. Like the :ref:`api_provide` cell, you define a new 
   482  type and a constructor. In the case of a metric cell the type should be a 
   483  struct with only public fields. The types of these fields should implement
   484  both `metric.WithMetadata <https://pkg.go.dev/github.com/cilium/cilium/pkg/metrics/metric#WithMetadata>`_
   485  and `prometheus.Collector <https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Collector>`_.
   486  The easiest way to get such metrics is to use the types defined in `pkg/metrics/metric <https://pkg.go.dev/github.com/cilium/cilium/pkg/metrics/metric>`_.
   487  
   488  The metric collection struct type returned by the given constructor is made 
   489  available in the hive just like a normal provide. In addition all of the metrics
   490  are made available via the ``hive-metrics`` `value group <https://pkg.go.dev/go.uber.org/dig#hdr-Value_Groups>`_.
   491  This value group is consumed by the metrics package so any metrics defined 
   492  via a metric cell are automatically registered.
   493  
   494  .. code-block:: go
   495  
   496      var Cell = cell.Module("my-feature", "My Feature",
   497          cell.Metric(NewFeatureMetrics),
   498          cell.Provide(NewMyFeature),
   499      )
   500  
   501      type FeatureMetrics struct {
   502          Calls   metric.Vec[metric.Counter]
   503          Latency metric.Histogram
   504      }
   505  
   506      func NewFeatureMetrics() FeatureMetrics {
   507          return FeatureMetrics{
   508              Calls: metric.NewCounterVec(metric.CounterOpts{
   509                  ConfigName: metrics.Namespace + "_my_feature_calls_total",
   510                  Subsystem:  "my_feature",
   511                  Namespace:  metrics.Namespace,
   512                  Name:       "calls_total",
   513              }, []string{"caller"}),
   514              Latency: metric.NewHistogram(metric.HistogramOpts{
   515                  ConfigName: metrics.Namespace + "_my_feature_latency_seconds",
   516                  Namespace:  metrics.Namespace,
   517                  Subsystem:  "my_feature",
   518                  Name:       "latency_seconds",
   519              }),
   520          }
   521      }
   522  
   523      type MyFeature struct {
   524          metrics FeatureMetrics
   525      }
   526  
   527      func NewMyFeature(metrics FeatureMetrics) *MyFeature {
   528          return &MyFeature{
   529              metrics: metrics,
   530          }
   531      }
   532  
   533      func (mf *MyFeature) SomeFunction(caller string) {
   534          mf.metrics.Calls.With(prometheus.Labels{"caller": caller}).Inc()
   535  
   536          span := spanstat.Start()
   537          // Normally we would do some actual work here
   538          time.Sleep(time.Second)
   539          span.End(true)
   540  
   541          mf.metrics.Latency.Observe(span.Seconds())
   542      }
   543  
   544  .. _api_lifecycle:
   545  
   546  Lifecycle
   547  ^^^^^^^^^
   548  
   549  In addition to cells an important building block in hive is the lifecycle. A
   550  lifecycle is a list of start and stop hook pairs that are executed in order
   551  (reverse when stopping) when running the hive.
   552  
   553  .. code-block:: go
   554  
   555      package hive
   556  
   557      type Lifecycle {
   558          Append(HookInterface)
   559      }
   560      type HookContext context.Context
   561  
   562      type HookInterface interface {
   563          Start(HookContext) error
   564          Stop(HookContext) error
   565      }
   566  
   567      type Hook struct {
   568          OnStart func(HookContext) error
   569          OnStop func(HookContext) error
   570      }
   571  
   572      func (h Hook) Start(ctx HookContext) error { ... }
   573      func (h Hook) Stop(ctx HookContext) error { ... }
   574  
   575  The lifecycle hooks can be implemented either by implementing the HookInterface methods,
   576  or using the Hook struct. Lifecycle is accessible from any cell:
   577  
   578  .. code-block:: go
   579  
   580      var ExampleCell = cell.Module(
   581          "example",
   582          "Example module",
   583      
   584          cell.Provide(New),
   585      )
   586      
   587      type Example struct { /* ... */ }
   588      func (e *Example) Start(ctx HookContext) error { /* ... */ }
   589      func (e *Example) Stop(ctx HookContext) error { /* ... */ }
   590      
   591      func New(lc cell.Lifecycle) *Example {
   592          e := &Example{}
   593          lc.Append(e)
   594          return e
   595      }
   596  
   597  These hooks are executed when hive.Run() is called. The HookContext given to
   598  these hooks is there to allow graceful aborting of the starting or stopping,
   599  either due to user pressing ``Control-C`` or due to a timeout. By default Hive has
   600  5 minute start timeout and 1 minute stop timeout, but these are configurable
   601  with SetTimeouts(). A grace time of 5 seconds is given on top of the timeout
   602  after which the application is forcefully terminated, regardless of whether
   603  the hook has finished or not.
   604  
   605  .. _api_shutdowner:
   606  
   607  Shutdowner
   608  ^^^^^^^^^^
   609  
   610  Sometimes there's nothing else to do but crash. If a fatal error is encountered
   611  in a ``Start()`` hook it's easy: just return the error and abort the start. After
   612  starting one can initiate a shutdown using the ``hive.Shutdowner``:
   613  
   614  .. code-block:: go
   615  
   616      package hive
   617  
   618      type Shutdowner interface {
   619          Shutdown(...ShutdownOption)
   620      }
   621  
   622      func ShutdownWithError(err error) ShutdownOption { /* ... */ }
   623  
   624      package example
   625  
   626      type Example struct {
   627          /* ... */
   628          Shutdowner hive.Shutdowner
   629      }
   630  
   631      func (e *Example) eventLoop() {
   632          for { 
   633              /* ... */
   634              if err != nil {
   635                  // Uh oh, this is really bad, we've got to crash.
   636                  e.Shutdowner.Shutdown(hive.ShutdownWithError(err))
   637              }
   638          }
   639      }     
   640  
   641  Creating and running a hive
   642  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
   643  
   644  A hive is created using ``hive.New()``:
   645  
   646  .. code-block:: go
   647  
   648      // func New(cells ...cell.Cell) *Hive
   649      var myHive = hive.New(FooCell, BarCell)
   650  
   651  ``New()`` creates a new hive and registers all providers to it. Invoke
   652  functions are not yet executed as our application may have multiple hives
   653  and we need to delay object instantiation to until we know which hive to use.
   654  
   655  However ``New`` does execute an invoke function to gather all command-line flags from
   656  all configuration cells. These can be then registered with a Cobra command:
   657  
   658  .. code-block:: go
   659  
   660      var cmd *cobra.Command = /* ... */
   661      myHive.RegisterFlags(cmd.Flags())
   662  
   663  After that the hive can be started with ``myHive.Run()``.
   664  
   665  Run() will first construct the parsed configurations and will then execute
   666  all invoke functions to instantiate all needed objects. As part of this the
   667  lifecycle hooks will have been appended (in dependency order). After that
   668  the start hooks can be executed one after the other to start the hive. Once
   669  started, Run() waits for SIGTERM and SIGINT signals and upon receiving one
   670  will execute the stop hooks in reverse order to bring the hive down.
   671  
   672  Now would be a good time to try this out in practice. You'll find a small example
   673  application in `pkg/hive/example <https://github.com/cilium/cilium/tree/main/pkg/hive/example>`_.
   674  Try running it with ``go run .`` and exploring the implementation (try what happens if a provider is commented out!).
   675  
   676  Inspecting a hive
   677  ~~~~~~~~~~~~~~~~~
   678  
   679  The ``hive.Hive`` can be inspected with the 'hive' command after it's
   680  been registered with cobra:
   681  
   682  .. code-block:: go
   683  
   684      var rootCmd *cobra.Command = /* ... */
   685      rootCmd.AddCommand(myHive.Command())
   686  
   687  .. code-block:: shell-session
   688  
   689      cilium$ go run ./daemon hive
   690      Cells:
   691  
   692      Ⓜ️ agent (Cilium Agent):
   693        Ⓜ️ infra (Infrastructure):
   694          Ⓜ️ k8s-client (Kubernetes Client):
   695               ⚙️ (client.Config) {
   696                   K8sAPIServer: (string) "",
   697                   K8sKubeConfigPath: (string) "",
   698                   K8sClientQPS: (float32) 0,
   699                   K8sClientBurst: (int) 0,
   700                   K8sHeartbeatTimeout: (time.Duration) 30s,
   701                   EnableK8sAPIDiscovery: (bool) false
   702               }
   703   
   704               🚧 client.newClientset (cell.go:109):
   705                   ⇨ client.Config, cell.Lifecycle, logrus.FieldLogger 
   706                   ⇦ client.Clientset 
   707      ...
   708  
   709      Start hooks:
   710  
   711          • gops.registerGopsHooks.func1 (cell.go:44)
   712          • cmd.newDatapath.func1 (daemon_main.go:1625)
   713          ...
   714  
   715      Stop hooks:
   716          ...
   717     
   718  
   719  The hive command prints out the cells, showing what modules, providers,
   720  configurations etc. exist and what they're requiring and providing.
   721  Finally the command prints out all registered start and stop hooks.
   722  Note that these hooks often depend on the configuration (e.g. k8s-client
   723  will not insert a hook unless e.g. --k8s-kubeconfig-path is given). The
   724  hive command takes the same command-line flags as the root command.
   725  
   726  The provider dependencies in a hive can also be visualized as a graphviz dot-graph:
   727  
   728  .. code-block:: bash
   729  
   730      cilium$ go run ./daemon hive dot-graph | dot -Tx11
   731  
   732  Guidelines
   733  ~~~~~~~~~~
   734  
   735  Few guidelines one should strive to follow when implementing larger cells:
   736  
   737  * A constructor function should only do validation and allocation. Spawning
   738    of goroutines or I/O operations must not be performed from constructors,
   739    but rather via the Start hook. This is required as we want to inspect the
   740    object graph (e.g. ``hive.PrintObjects``) and side-effectful constructors would
   741    cause undesired effects.
   742  
   743  * Stop functions should make sure to block until all resources
   744    (goroutines, file handles, …) created by the module have been cleaned
   745    up (with e.g. ``sync.WaitGroup``). This makes sure that independent
   746    tests in the same test suite are not affecting each other. Use
   747    `goleak <https://github.com/uber-go/goleak>`_ to check that goroutines
   748    are not leaked.
   749  
   750  * Preferably each non-trivial cell would come with a test that validates that
   751    it implements its public API correctly. The test also serves
   752    as an example of how the cell's API is used and it also validates the
   753    correctness of the cells  it depends on which helps with refactoring.
   754  
   755  * Utility cells should not Invoke(). Since cells may be used in many
   756    applications it makes sense to make them lazy to allow bundling useful
   757    utilities into one collection. If a utility cell has an invoke, it may be
   758    instantiated even if it is never used.
   759  
   760  * For large cells, provide interfaces and not struct pointers. A cell
   761    can be thought of providing a service to the rest of the application. To
   762    make it accessible, one should think about what APIs the module provides and
   763    express these as well documented interface types. If the interface is large,
   764    try breaking it up into multiple small ones. Interface types also allows
   765    integration testing with mock implementations. The rational here is the same as
   766    with "return structs, accept interfaces": since hive works with the names of types,
   767    we want to "inject" interfaces into the object graph and not struct
   768    pointers. Extra benefit is that separating the API implemented by a module
   769    into one or more interfaces it is easier to document and easier to inspect
   770    as all public method declarations are in one place.
   771  
   772  * Use parameter (cell.In) and result (cell.Out) objects liberally. If a
   773    constructor takes more than two parameters, consider using a parameter
   774    struct instead.
   775  
   776  Internals: Dependency injection with reflection
   777  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   778  
   779  Hive is built on top of `uber/dig <https://github.com/uber-go/dig>`_, a reflection based library for building
   780  dependency injection frameworks. In dig, you create a container, add in your
   781  constructors and then "invoke" to create objects:
   782  
   783  .. code-block:: go
   784  
   785      func NewA() (A, error) { /* ... */ }
   786      func NewB() B { /* ... */ }
   787      func NewC(A, B) (C, error) { /* ... */ }
   788      func setupC(C) error
   789  
   790      // Create a new container for our constructors.
   791      c := dig.New(dig.DeferAcyclicVerification())
   792  
   793      // Add in the constructors. Order does not matter.
   794      c.Provide(NewC)
   795      c.Provide(NewB)
   796      c.Provide(NewA)
   797  
   798      // Invoke a function that can depend on any of the values supplied by the
   799      // registered constructors.
   800      // Since this depends on "C", dig will construct first A and B
   801      // (as C depends on them), and then C.
   802      c.Invoke(func(c *C) {
   803          // Do something with C
   804      })
   805  
   806  
   807  This is the basis on top of which Hive is built. Hive calls dig’s Provide()
   808  for each of the constructors registered with cell.Provide and then calls
   809  invoke functions to construct the needed objects. The results from the
   810  constructors are cached, so each constructor is called only once.
   811  
   812  ``uber/dig`` uses Go’s "reflect" package that provides access to the
   813  type information of the provide and invoke functions. For example, the
   814  `Provide <https://pkg.go.dev/go.uber.org/dig#Container.Provide>`_ method does
   815  something akin to this under the hood:
   816  
   817  .. code-block:: go
   818  
   819      // 'constructor' has type "func(...) ..."
   820      typ := reflect.TypeOf(constructor)
   821      if typ.Kind() != reflect.Func { /* error */ }
   822  
   823      in := make([]reflect.Type, 0, typ.NumIn())
   824      for i := 0; i < typ.NumIn(); i++ { 
   825          in[i] = typ.In(i) 
   826      }
   827  
   828      out := make([]reflect.Type, 0, typ.NumOut())
   829      for i := 0; i < typ.NumOut(); i++ {
   830          out[i] = typ.Out(i) 
   831      }
   832  
   833      container.providers = append(container.providers, &provider{constructor, in, out})
   834  
   835  
   836  `Invoke <https://pkg.go.dev/go.uber.org/dig#Container.Invoke>`_ will similarly
   837  reflect on the function value to find out what are the required inputs and
   838  then find the required constructors for the input objects and recursively
   839  their inputs.
   840  
   841  While building this on reflection is flexible, the downside is that missing
   842  dependencies lead to runtime errors. Luckily dig produces excellent errors and
   843  suggests closely matching object types in case of typos. Due to the desire
   844  to avoid these runtime errors the constructed hive should be as static
   845  as possible, e.g. the set of constructors and invoke functions should be
   846  determined at compile time and not be dependent on runtime configuration. This
   847  way the hive can be validated once with a simple unit test (``daemon/cmd/cells_test.go``).
   848  
   849  Cell showcase
   850  ~~~~~~~~~~~~~
   851  
   852  Logging
   853  ^^^^^^^
   854  
   855  Logging is provided to all cells by default with the ``logrus.FieldLogger`` interface type. The log lines will include the field ``subsys=<module id>``.
   856  
   857  .. code-block:: go
   858  
   859      cell.Module(
   860          "example",
   861          "log example module",
   862      
   863          cell.Provide(
   864        	    func(log logrus.FieldLogger) Example {
   865      	  	log.Info("Hello") // subsys=example message=Hello
   866                  return Example{log: log}
   867      	    },
   868          ),
   869      )
   870  
   871  Kubernetes client
   872  ^^^^^^^^^^^^^^^^^
   873  
   874  The `client package <https://pkg.go.dev/github.com/cilium/cilium/pkg/k8s/client>`_ provides the ``Clientset`` API 
   875  that combines the different clientsets used by Cilium into one composite value. Also provides ``FakeClientCell``
   876  for writing integration tests for cells that interact with the K8s api-server.
   877  
   878  .. code-block:: go
   879  
   880      var Cell = cell.Provide(New)
   881  
   882      func New(cs client.Clientset) Example {
   883           return Example{cs: cs}
   884      }
   885  
   886      func (e Example) CreateIdentity(id *ciliumv2.CiliumIdentity) error {
   887          return e.cs.CiliumV2().CiliumIdentities().Create(e.ctx, id, metav1.CreateOptions{})
   888      }
   889  
   890  Resource and the store (see below) is the preferred way of accessing Kubernetes object
   891  state to minimize traffic to the api-server. The Clientset should usually
   892  only be used for creating and updating objects.
   893  
   894  Kubernetes Resource and Store
   895  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   896  
   897  While not a cell by itself, `pkg/k8s/resource <https://pkg.go.dev/github.com/cilium/cilium/pkg/k8s/resource>`_ 
   898  provides an useful abstraction for providing shared event-driven access
   899  to Kubernetes objects. Implemented on top of the client-go informer,
   900  ``workqueue`` and store to codify the suggested pattern for controllers in a
   901  type-safe way. This shared abstraction provides a simpler API to write and
   902  test against and allows central control over what data (and at what rate)
   903  is pulled from the api-server and how it’s stored (in-memory or persisted).
   904  
   905  The resources are usually made available centrally for the application,
   906  e.g. in cilium-agent they’re provided from `pkg/k8s/resource.go <https://github.com/cilium/cilium/blob/main/daemon/k8s/resources.go>`_.
   907  See also the runnable example in `pkg/k8s/resource/example <https://github.com/cilium/cilium/tree/main/pkg/k8s/resource/example>`_.
   908  
   909  .. code-block:: go
   910  
   911      import "github.com/cilium/cilium/pkg/k8s/resource"
   912  
   913      var nodesCell = cell.Provide(
   914          func(lc cell.Lifecycle, cs client.Clientset) resource.Resource[v1.Node] {
   915              lw := utils.ListerWatcherFromTyped[*v1.NodeList](cs.CoreV1().Nodes())
   916              return resource.New[*v1.Node](lc, lw) 
   917          },
   918      )
   919  
   920      var Cell = cell.Module(
   921          "resource-example",
   922          "Example of how to use Resource",
   923  
   924          nodesCell,
   925          cell.Invoke(printNodeUpdates),
   926      )
   927  
   928      func printNodeUpdates(nodes resource.Resource[*v1.Node]) {
   929          // Store() returns a typed locally synced store of the objects.
   930          // This call blocks until the store has been synchronized.
   931          store, err := nodes.Store()
   932          ...
   933          obj, exists, err := store.Get("my-node")
   934          ...
   935          objs, err := store.List()
   936          ...
   937  
   938          // Events() returns a channel of object change events. Closes
   939          // when 'ctx' is cancelled.
   940          // type Event[T] struct { Kind Kind; Key Key; Object T; Done func(err error) }
   941          for ev := range nodes.Events(ctx) {
   942              switch ev.Kind {
   943              case resource.Sync:
   944                // The store has now synced with api-server and
   945                // the set of observed upsert events forms a coherent
   946                // snapshot. Usually some sort of garbage collection or
   947                // reconciliation is performed.
   948              case resource.Upsert:
   949                  fmt.Printf("Node %s has updated: %v\n", ev.Key, ev.Object)
   950              case resource.Delete:
   951                  fmt.Printf("Node %s has been deleted\n", key)
   952              }
   953              // Each event must be marked as handled. If non-nil error
   954              // is given, the processing for this key is retried later
   955              // according to rate-limiting and retry policy. The built-in
   956              // retrying is often used if we perform I/O operations (like API client
   957              // calls) from the handler and retrying makes sense. It should not
   958              // be used on parse errors and similar.
   959              ev.Done(nil)
   960          }
   961      }
   962  
   963  Job groups
   964  ^^^^^^^^^^
   965  
   966  The `job package <https://pkg.go.dev/github.com/cilium/hive/job>`_ contains logic that
   967  makes it easy to manage units of work that the package refers to as "jobs". These jobs are 
   968  scheduled as part of a job group.
   969  
   970  Every job is a callback function provided by the user with additional logic which
   971  differs slightly for each job type. The jobs and groups manage a lot of the boilerplate
   972  surrounding lifecycle management. The callbacks are called from the job to perform the actual
   973  work.
   974  
   975  These jobs themselves come in several varieties. The ``OneShot`` job invokes its callback just once.
   976  This job type can be used for initialization after cell startup, routines that run for the full lifecycle
   977  of the cell, or for any other task you would normally use a plain goroutine for.
   978  
   979  The ``Timer`` job invokes its callback periodically. This job type can be used for periodic tasks
   980  such as synchronization or garbage collection. Timer jobs can also be externally triggered in
   981  addition to the periodic invocations.
   982  
   983  The ``Observer`` job invokes its callback for every message sent on a ``stream.Observable``. This job
   984  type can be used to react to a data stream or events created by other cells.
   985  
   986  Please take a look at ``pkg/hive/examples/jobs.go`` for an example of how to use the job package.
   987