github.com/Jeffail/benthos/v3@v3.65.0/lib/stream/manager/type.go (about)

     1  package manager
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"sync"
     8  	"sync/atomic"
     9  	"time"
    10  
    11  	"github.com/Jeffail/benthos/v3/internal/interop"
    12  	"github.com/Jeffail/benthos/v3/lib/log"
    13  	"github.com/Jeffail/benthos/v3/lib/manager"
    14  	"github.com/Jeffail/benthos/v3/lib/metrics"
    15  	"github.com/Jeffail/benthos/v3/lib/stream"
    16  	"github.com/Jeffail/benthos/v3/lib/types"
    17  )
    18  
    19  //------------------------------------------------------------------------------
    20  
    21  // StreamStatus tracks a stream along with information regarding its internals.
    22  type StreamStatus struct {
    23  	stoppedAfter int64
    24  	config       stream.Config
    25  	strm         *stream.Type
    26  	logger       log.Modular
    27  	metrics      *metrics.Local
    28  	createdAt    time.Time
    29  }
    30  
    31  // NewStreamStatus creates a new StreamStatus.
    32  func NewStreamStatus(
    33  	conf stream.Config,
    34  	strm *stream.Type,
    35  	logger log.Modular,
    36  	stats *metrics.Local,
    37  ) *StreamStatus {
    38  	return &StreamStatus{
    39  		config:    conf,
    40  		strm:      strm,
    41  		logger:    logger,
    42  		metrics:   stats,
    43  		createdAt: time.Now(),
    44  	}
    45  }
    46  
    47  // IsRunning returns a boolean indicating whether the stream is currently
    48  // running.
    49  func (s *StreamStatus) IsRunning() bool {
    50  	return atomic.LoadInt64(&s.stoppedAfter) == 0
    51  }
    52  
    53  // IsReady returns a boolean indicating whether the stream is connected at both
    54  // the input and output level.
    55  func (s *StreamStatus) IsReady() bool {
    56  	return s.strm.IsReady()
    57  }
    58  
    59  // Uptime returns a time.Duration indicating the current uptime of the stream.
    60  func (s *StreamStatus) Uptime() time.Duration {
    61  	if stoppedAfter := atomic.LoadInt64(&s.stoppedAfter); stoppedAfter > 0 {
    62  		return time.Duration(stoppedAfter)
    63  	}
    64  	return time.Since(s.createdAt)
    65  }
    66  
    67  // Config returns the configuration of the stream.
    68  func (s *StreamStatus) Config() stream.Config {
    69  	return s.config
    70  }
    71  
    72  // Metrics returns a metrics aggregator of the stream.
    73  func (s *StreamStatus) Metrics() *metrics.Local {
    74  	return s.metrics
    75  }
    76  
    77  // Logger returns the logger of the stream.
    78  func (s *StreamStatus) Logger() log.Modular {
    79  	return s.logger
    80  }
    81  
    82  // setClosed sets the flag indicating that the stream is closed.
    83  func (s *StreamStatus) setClosed() {
    84  	atomic.SwapInt64(&s.stoppedAfter, int64(time.Since(s.createdAt)))
    85  }
    86  
    87  //------------------------------------------------------------------------------
    88  
    89  // StreamProcConstructorFunc is a closure type that constructs a processor type
    90  // for new streams, where the id of the stream is provided as an argument.
    91  type StreamProcConstructorFunc func(streamID string) (types.Processor, error)
    92  
    93  //------------------------------------------------------------------------------
    94  
    95  // Type manages a collection of streams, providing APIs for CRUD operations on
    96  // the streams.
    97  type Type struct {
    98  	closed  bool
    99  	streams map[string]*StreamStatus
   100  
   101  	manager    types.Manager
   102  	stats      metrics.Type
   103  	logger     log.Modular
   104  	apiTimeout time.Duration
   105  	apiEnabled bool
   106  
   107  	pipelineProcCtors []StreamProcConstructorFunc
   108  
   109  	lock sync.Mutex
   110  }
   111  
   112  // New creates a new stream manager.Type.
   113  func New(opts ...func(*Type)) *Type {
   114  	t := &Type{
   115  		streams:    map[string]*StreamStatus{},
   116  		manager:    types.DudMgr{},
   117  		stats:      metrics.Noop(),
   118  		apiTimeout: time.Second * 5,
   119  		logger:     log.Noop(),
   120  		apiEnabled: true,
   121  	}
   122  	for _, opt := range opts {
   123  		opt(t)
   124  	}
   125  	t.registerEndpoints(t.apiEnabled)
   126  	return t
   127  }
   128  
   129  //------------------------------------------------------------------------------
   130  
   131  // OptAPIEnabled sets whether the stream manager registers API endpoints for
   132  // CRUD operations on streams. This is enabled by default.
   133  func OptAPIEnabled(b bool) func(*Type) {
   134  	return func(t *Type) {
   135  		t.apiEnabled = b
   136  	}
   137  }
   138  
   139  // OptSetStats sets the metrics aggregator to be used by the manager and all
   140  // child streams.
   141  func OptSetStats(stats metrics.Type) func(*Type) {
   142  	return func(t *Type) {
   143  		t.stats = stats
   144  		t.manager = manager.SwapMetrics(t.manager, t.stats)
   145  	}
   146  }
   147  
   148  // OptSetLogger sets the logging output to be used by the manager and all child
   149  // streams.
   150  func OptSetLogger(log log.Modular) func(*Type) {
   151  	return func(t *Type) {
   152  		t.logger = log
   153  	}
   154  }
   155  
   156  // OptSetManager sets the service manager to be used by the stream manager and
   157  // all child streams.
   158  func OptSetManager(mgr types.Manager) func(*Type) {
   159  	return func(t *Type) {
   160  		t.manager = manager.SwapMetrics(mgr, t.stats)
   161  	}
   162  }
   163  
   164  // OptSetAPITimeout sets the default timeout for HTTP API requests.
   165  func OptSetAPITimeout(tout time.Duration) func(*Type) {
   166  	return func(t *Type) {
   167  		t.apiTimeout = tout
   168  	}
   169  }
   170  
   171  // OptAddProcessors adds processor constructors that will be called for every
   172  // new stream and attached to the processor pipelines. The constructor is given
   173  // the name of the stream as an argument.
   174  func OptAddProcessors(procs ...StreamProcConstructorFunc) func(*Type) {
   175  	return func(t *Type) {
   176  		t.pipelineProcCtors = append(t.pipelineProcCtors, procs...)
   177  	}
   178  }
   179  
   180  //------------------------------------------------------------------------------
   181  
   182  // Errors specifically returned by a stream manager.
   183  var (
   184  	ErrStreamExists       = errors.New("stream already exists")
   185  	ErrStreamDoesNotExist = errors.New("stream does not exist")
   186  )
   187  
   188  //------------------------------------------------------------------------------
   189  
   190  // Create attempts to construct and run a new stream under a unique ID. If the
   191  // ID already exists an error is returned.
   192  func (m *Type) Create(id string, conf stream.Config) error {
   193  	m.lock.Lock()
   194  	defer m.lock.Unlock()
   195  
   196  	if m.closed {
   197  		return types.ErrTypeClosed
   198  	}
   199  
   200  	if _, exists := m.streams[id]; exists {
   201  		return ErrStreamExists
   202  	}
   203  
   204  	var procCtors []types.ProcessorConstructorFunc
   205  	for _, ctor := range m.pipelineProcCtors {
   206  		func(c StreamProcConstructorFunc) {
   207  			procCtors = append(procCtors, func() (types.Processor, error) {
   208  				return c(id)
   209  			})
   210  		}(ctor)
   211  	}
   212  
   213  	sMgr, sLog, sStats := interop.LabelStream(id, m.manager, m.logger, m.stats)
   214  	if u, ok := sStats.(interface {
   215  		Unwrap() metrics.Type
   216  	}); ok {
   217  		sStats = u.Unwrap()
   218  	}
   219  
   220  	strmFlatMetrics := metrics.NewLocal()
   221  	sStats = metrics.Combine(sStats, strmFlatMetrics)
   222  	sMgr = manager.SwapMetrics(sMgr, sStats)
   223  
   224  	var wrapper *StreamStatus
   225  	strm, err := stream.New(
   226  		conf,
   227  		stream.OptAddProcessors(procCtors...),
   228  		stream.OptSetLogger(sLog),
   229  		stream.OptSetStats(sStats),
   230  		stream.OptSetManager(sMgr),
   231  		stream.OptOnClose(func() {
   232  			wrapper.setClosed()
   233  		}),
   234  	)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	wrapper = NewStreamStatus(conf, strm, sLog, strmFlatMetrics)
   240  	m.streams[id] = wrapper
   241  	return nil
   242  }
   243  
   244  // Read attempts to obtain the status of a managed stream. Returns an error if
   245  // the stream does not exist.
   246  func (m *Type) Read(id string) (*StreamStatus, error) {
   247  	m.lock.Lock()
   248  	defer m.lock.Unlock()
   249  
   250  	if m.closed {
   251  		return nil, types.ErrTypeClosed
   252  	}
   253  
   254  	wrapper, exists := m.streams[id]
   255  	if !exists {
   256  		return nil, ErrStreamDoesNotExist
   257  	}
   258  
   259  	return wrapper, nil
   260  }
   261  
   262  // Update attempts to stop an existing stream and replace it with a new version
   263  // of the same stream.
   264  func (m *Type) Update(id string, conf stream.Config, timeout time.Duration) error {
   265  	m.lock.Lock()
   266  	wrapper, exists := m.streams[id]
   267  	closed := m.closed
   268  	m.lock.Unlock()
   269  
   270  	if closed {
   271  		return types.ErrTypeClosed
   272  	}
   273  	if !exists {
   274  		return ErrStreamDoesNotExist
   275  	}
   276  
   277  	if reflect.DeepEqual(wrapper.config, conf) {
   278  		return nil
   279  	}
   280  
   281  	if err := m.Delete(id, timeout); err != nil {
   282  		return err
   283  	}
   284  	return m.Create(id, conf)
   285  }
   286  
   287  // Delete attempts to stop and remove a stream by its ID. Returns an error if
   288  // the stream was not found, or if clean shutdown fails in the specified period
   289  // of time.
   290  func (m *Type) Delete(id string, timeout time.Duration) error {
   291  	m.lock.Lock()
   292  	if m.closed {
   293  		m.lock.Unlock()
   294  		return types.ErrTypeClosed
   295  	}
   296  
   297  	wrapper, exists := m.streams[id]
   298  	m.lock.Unlock()
   299  	if !exists {
   300  		return ErrStreamDoesNotExist
   301  	}
   302  
   303  	if err := wrapper.strm.Stop(timeout); err != nil {
   304  		return err
   305  	}
   306  
   307  	m.lock.Lock()
   308  	delete(m.streams, id)
   309  	m.lock.Unlock()
   310  
   311  	return nil
   312  }
   313  
   314  //------------------------------------------------------------------------------
   315  
   316  // Stop attempts to gracefully shut down all active streams and close the
   317  // stream manager.
   318  func (m *Type) Stop(timeout time.Duration) error {
   319  	m.lock.Lock()
   320  	defer m.lock.Unlock()
   321  
   322  	resultChan := make(chan string)
   323  
   324  	for k, v := range m.streams {
   325  		go func(id string, strm *StreamStatus) {
   326  			if err := strm.strm.Stop(timeout); err != nil {
   327  				resultChan <- id
   328  			} else {
   329  				resultChan <- ""
   330  			}
   331  		}(k, v)
   332  	}
   333  
   334  	failedStreams := []string{}
   335  	for i := 0; i < len(m.streams); i++ {
   336  		if failedStrm := <-resultChan; len(failedStrm) > 0 {
   337  			failedStreams = append(failedStreams, failedStrm)
   338  		}
   339  	}
   340  
   341  	m.streams = map[string]*StreamStatus{}
   342  	m.closed = true
   343  
   344  	if len(failedStreams) > 0 {
   345  		return fmt.Errorf("failed to gracefully stop the following streams: %v", failedStreams)
   346  	}
   347  	return nil
   348  }
   349  
   350  //------------------------------------------------------------------------------