blob: 9ee6d3d64981e65edecabb91d98ca5d0a5643e13 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Provides service implementations and management
package services
import (
"bytes"
"fmt"
"io"
"log"
"strings"
"google.golang.org/grpc"
"go.chromium.org/chromiumos/test/local-cft/internal/utils"
)
// Represents how to start up a service
type ServiceSetup interface {
Setup() error
}
// Represents the implementation of a command being called within a service
type ServiceCommand interface {
Execute() error
}
// Represents the implementation for how to stop a service
type ServiceStopper interface {
Stop() error
}
// Service Command type alias
type ServiceCommand_ = string
// Service represents the required functions for Service structures
type Service interface {
// Sets up and runs the Service
Start() error
// Runs an implemented command on the Service
Execute(ServiceCommand_, ...interface{}) error
// Shutdowns the service and cleans up
Stop() error
}
// Base structure of Service implementations.
// Contains common variables used across implementations
type ServiceBase struct {
manager *LocalCFTManager
executor ServiceExecutor
Name string
Port uint16
BaseDir string
Started bool
ReadyChan chan error
CloseChan chan struct{}
CloseFinishedChan chan struct{}
LocalLogger *log.Logger
ServiceLogger *log.Logger
loggerBuf *bytes.Buffer
conn *grpc.ClientConn
}
// EnsurePort sets the Port member, if it is not already set.
// The port is first selected from the manager port list if possible, then any free port is assigned otherwise.
func (sb *ServiceBase) EnsurePort() {
if sb.Port > 0 {
return
}
port, ok := sb.manager.ports[sb.Name]
if ok {
sb.Port = port
return
}
sb.Port = utils.GetFreePort()
if sb.manager.ports == nil {
sb.manager.ports = make(map[string]uint16)
}
sb.manager.ports[sb.Name] = sb.Port
}
type ServiceResult_ = string
type ServiceResult struct {
Undefined ServiceResult_
Success ServiceResult_
Failed ServiceResult_
}
// SERVICE_RESULT provides possibilities for service result
func SERVICE_RESULT() ServiceResult {
return ServiceResult{
Undefined: "UNDEFINED",
Success: "SUCCESS",
Failed: "FAILED",
}
}
// example-service -> EXAMPLE_SERVICE
func kebabToUpperSnake(in string) (out string) {
parts := strings.Split(strings.ToUpper(in), "-")
out = strings.Join(parts, "_")
return
}
// Provides uniform construction of base services
func NewServiceBase(
manager *LocalCFTManager,
executor ServiceExecutor,
name string,
) ServiceBase {
buffer := new(bytes.Buffer)
multiwriter := io.MultiWriter(log.Default().Writer(), buffer)
return ServiceBase{
manager: manager,
executor: executor,
Name: name,
BaseDir: manager.BaseDir,
ReadyChan: make(chan error),
CloseChan: make(chan struct{}),
CloseFinishedChan: make(chan struct{}),
Started: false,
loggerBuf: buffer,
LocalLogger: log.New(multiwriter, fmt.Sprintf("%s (local): ", kebabToUpperSnake(name)), log.Ldate|log.Ltime|log.Lshortfile),
ServiceLogger: log.New(multiwriter, fmt.Sprintf("%s (service): ", kebabToUpperSnake(name)), 0),
}
}
// Provides a ServiceBase constructed with the DefaultServiceExecutor
func NewDefaultServiceBase(
manager *LocalCFTManager,
name string,
) ServiceBase {
return NewServiceBase(
manager,
&DefaultServiceExecutor{},
name,
)
}
// ServiceExecutor represents how the service should handle its execution requests
type ServiceExecutor interface {
Start(ServiceSetup) error
Execute(ServiceCommand) error
Stop(ServiceStopper) error
}
type DefaultServiceExecutor struct {
ServiceExecutor
}
func (c *DefaultServiceExecutor) Start(starter ServiceSetup) error {
return starter.Setup()
}
func (c *DefaultServiceExecutor) Execute(cmd ServiceCommand) error {
return cmd.Execute()
}
func (c *DefaultServiceExecutor) Stop(stopper ServiceStopper) error {
return stopper.Stop()
}