github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/server/registration/registration.go (about)

     1  // Copyright (c) 2017-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package registration
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"math/rand"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/choria-io/go-choria/config"
    15  	"github.com/choria-io/go-choria/inter"
    16  	"github.com/choria-io/go-choria/internal/util"
    17  	"github.com/choria-io/go-choria/providers/registration"
    18  	"github.com/choria-io/go-choria/server/data"
    19  
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  type ChoriaFramework interface {
    24  	NewMessage(payload []byte, agent string, collective string, msgType string, request inter.Message) (msg inter.Message, err error)
    25  	Configuration() *config.Config
    26  }
    27  
    28  // Registrator is a full managed registration plugin
    29  type Registrator interface {
    30  	Init(cfg *config.Config, l *logrus.Entry)
    31  	StartRegistration(context.Context, *sync.WaitGroup, int, chan *data.RegistrationItem)
    32  }
    33  
    34  // RegistrationDataProvider is a provider for data that can be registered
    35  // into a running server instance using AddRegistrationProvider()
    36  type RegistrationDataProvider interface {
    37  	StartRegistration(context.Context, *sync.WaitGroup, int, chan *data.RegistrationItem)
    38  }
    39  
    40  // Connection provides the connection to the middleware
    41  type Connection interface {
    42  	Publish(msg inter.Message) error
    43  	IsConnected() bool
    44  }
    45  
    46  // Manager of registration plugins
    47  type Manager struct {
    48  	log       *logrus.Entry
    49  	choria    ChoriaFramework
    50  	cfg       *config.Config
    51  	connector Connection
    52  	datac     chan *data.RegistrationItem
    53  	si        registration.ServerInfoSource
    54  }
    55  
    56  // New creates a new instance of the registration subsystem manager
    57  func New(c ChoriaFramework, si registration.ServerInfoSource, conn Connection, logger *logrus.Entry) *Manager {
    58  	r := &Manager{
    59  		log:       logger.WithFields(logrus.Fields{"subsystem": "registration"}),
    60  		choria:    c,
    61  		si:        si,
    62  		cfg:       c.Configuration(),
    63  		connector: conn,
    64  		datac:     make(chan *data.RegistrationItem, 1),
    65  	}
    66  
    67  	return r
    68  }
    69  
    70  // Start initializes the fully managed registration plugins and start publishing
    71  // their data
    72  func (reg *Manager) Start(ctx context.Context, wg *sync.WaitGroup) error {
    73  	defer wg.Done()
    74  
    75  	if reg.cfg.RegistrationCollective == "" {
    76  		reg.cfg.RegistrationCollective = reg.cfg.MainCollective
    77  	}
    78  
    79  	var err error
    80  	var registrator Registrator
    81  
    82  	for _, rtype := range reg.cfg.Registration {
    83  		switch rtype {
    84  		case "":
    85  			return nil
    86  		case "file_content":
    87  			registrator, err = registration.NewFileContent(reg.cfg, reg.si, reg.log)
    88  			if err != nil {
    89  				return fmt.Errorf("cannot start File Content Registrator: %s", err)
    90  			}
    91  
    92  		case "inventory_content":
    93  			registrator, err = registration.NewInventoryContent(reg.cfg, reg.si, reg.log)
    94  			if err != nil {
    95  				return fmt.Errorf("cannot start File Content Registrator: %s", err)
    96  			}
    97  
    98  		default:
    99  			return fmt.Errorf("unknown registration plugin: %s", reg.cfg.Registration)
   100  		}
   101  
   102  		reg.log.Infof("Starting registration worker for %s", rtype)
   103  		err = reg.RegisterProvider(ctx, wg, registrator)
   104  		if err != nil {
   105  			reg.log.Errorf("Could not register registration worker for %s: %s", rtype, err)
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  // RegisterProvider creates a publisher for a new provider
   113  func (reg *Manager) RegisterProvider(ctx context.Context, wg *sync.WaitGroup, provider RegistrationDataProvider) error {
   114  	wg.Add(1)
   115  	go reg.registrationWorker(ctx, wg, provider)
   116  
   117  	return nil
   118  }
   119  
   120  func (reg *Manager) registrationWorker(ctx context.Context, wg *sync.WaitGroup, registrator RegistrationDataProvider) {
   121  	defer wg.Done()
   122  
   123  	if reg.cfg.RegistrationSplay {
   124  		sleepTime := time.Duration(rand.Intn(reg.cfg.RegisterInterval)) * time.Second
   125  		reg.log.Infof("Sleeping %s seconds before first poll due to RegistrationSplay", sleepTime)
   126  		err := util.InterruptibleSleep(ctx, sleepTime)
   127  		if err != nil {
   128  			reg.log.Infof("Registration system exiting on shut down")
   129  			return
   130  		}
   131  	}
   132  
   133  	wg.Add(1)
   134  	go registrator.StartRegistration(ctx, wg, reg.cfg.RegisterInterval, reg.datac)
   135  
   136  	for {
   137  		select {
   138  		case msg := <-reg.datac:
   139  			reg.publish(msg)
   140  		case <-ctx.Done():
   141  			reg.log.Infof("Registration system exiting on shut down")
   142  			return
   143  		}
   144  	}
   145  }
   146  
   147  func (reg *Manager) publish(rmsg *data.RegistrationItem) {
   148  	if rmsg == nil {
   149  		reg.log.Warnf("Received nil data from Registratoin Plugin, skipping")
   150  		return
   151  	}
   152  
   153  	if rmsg.Data == nil {
   154  		reg.log.Warnf("Received nil data from Registratoin Plugin, skipping")
   155  		return
   156  	}
   157  
   158  	if len(rmsg.Data) == 0 {
   159  		reg.log.Warnf("Received empty data from Registratoin Plugin, skipping")
   160  		return
   161  	}
   162  
   163  	if rmsg.TargetAgent == "" {
   164  		rmsg.TargetAgent = "registration"
   165  	}
   166  
   167  	msg, err := reg.choria.NewMessage(rmsg.Data, rmsg.TargetAgent, reg.cfg.RegistrationCollective, "request", nil)
   168  	if err != nil {
   169  		reg.log.Warnf("Could not create Message for registration data: %s", err)
   170  		return
   171  	}
   172  
   173  	msg.SetReplyTo("dev.null")
   174  	msg.SetCustomTarget(rmsg.Destination)
   175  
   176  	if reg.connector.IsConnected() {
   177  		err = reg.connector.Publish(msg)
   178  		if err != nil {
   179  			reg.log.Warnf("Could not publish registration Message: %s", err)
   180  			return
   181  		}
   182  	} else {
   183  		reg.log.Warnf("Skipping registration publish while not connected to the network")
   184  	}
   185  }