mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-27 17:41:17 +02:00
This PR introduces dependencies between the services. Now each service has two virtual events associated with it: 'up' (running and healthy) and 'down' (finished or failed). These events are used to establish correct order via conditions abstraction. Service image unpacking was moved into 'pre' stage simplifying `init/main.go`, service images are now closer to the code which runs the service itself. Step 'pre' now runs after 'wait' step, and service dependencies are now mixed into other conditions of 'wait' step on startup. Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
267 lines
5.5 KiB
Go
267 lines
5.5 KiB
Go
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package system_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/suite"
|
|
"github.com/talos-systems/talos/internal/app/init/pkg/system"
|
|
"github.com/talos-systems/talos/internal/app/init/pkg/system/conditions"
|
|
"github.com/talos-systems/talos/internal/app/init/pkg/system/events"
|
|
)
|
|
|
|
type ServiceRunnerSuite struct {
|
|
suite.Suite
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) assertStateSequence(expectedStates []events.ServiceState, sr *system.ServiceRunner) {
|
|
states := []events.ServiceState{}
|
|
|
|
for _, event := range sr.GetEventHistory(1000) {
|
|
states = append(states, event.State)
|
|
}
|
|
|
|
suite.Assert().Equal(expectedStates, states)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestFullFlow() {
|
|
sr := system.NewServiceRunner(&MockService{
|
|
condition: conditions.None(),
|
|
}, nil)
|
|
|
|
finished := make(chan struct{})
|
|
go func() {
|
|
defer close(finished)
|
|
sr.Start()
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
select {
|
|
case <-finished:
|
|
suite.Require().Fail("service running should be still running")
|
|
default:
|
|
}
|
|
|
|
sr.Shutdown()
|
|
|
|
<-finished
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StateWaiting,
|
|
events.StatePreparing,
|
|
events.StatePreparing,
|
|
events.StateRunning,
|
|
events.StateFinished,
|
|
}, sr)
|
|
|
|
protoService := sr.AsProto()
|
|
suite.Assert().Equal("MockRunner", protoService.Id)
|
|
suite.Assert().Equal("Finished", protoService.State)
|
|
suite.Assert().True(protoService.Health.Unknown)
|
|
suite.Assert().Len(protoService.Events.Events, 5)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestFullFlowHealthy() {
|
|
sr := system.NewServiceRunner(&MockHealthcheckedService{}, nil)
|
|
|
|
finished := make(chan struct{})
|
|
go func() {
|
|
defer close(finished)
|
|
sr.Start()
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
select {
|
|
case <-finished:
|
|
suite.Require().Fail("service running should be still running")
|
|
default:
|
|
}
|
|
|
|
sr.Shutdown()
|
|
|
|
<-finished
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StatePreparing,
|
|
events.StatePreparing,
|
|
events.StateRunning,
|
|
events.StateRunning, // one more notification when service is healthy
|
|
events.StateFinished,
|
|
}, sr)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestFullFlowHealthChanges() {
|
|
m := MockHealthcheckedService{
|
|
MockService: MockService{
|
|
condition: conditions.None(),
|
|
},
|
|
}
|
|
sr := system.NewServiceRunner(&m, nil)
|
|
|
|
finished := make(chan struct{})
|
|
go func() {
|
|
defer close(finished)
|
|
sr.Start()
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
m.SetHealthy(false)
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
m.SetHealthy(true)
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
sr.Shutdown()
|
|
|
|
<-finished
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StateWaiting,
|
|
events.StatePreparing,
|
|
events.StatePreparing,
|
|
events.StateRunning,
|
|
events.StateRunning, // initial: healthy
|
|
events.StateRunning, // not healthy
|
|
events.StateRunning, // one again healthy
|
|
events.StateFinished,
|
|
}, sr)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestPreStageFail() {
|
|
svc := &MockService{
|
|
preError: errors.New("pre failed"),
|
|
}
|
|
sr := system.NewServiceRunner(svc, nil)
|
|
sr.Start()
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StatePreparing,
|
|
events.StateFailed,
|
|
}, sr)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestRunnerStageFail() {
|
|
svc := &MockService{
|
|
runnerError: errors.New("runner failed"),
|
|
}
|
|
sr := system.NewServiceRunner(svc, nil)
|
|
sr.Start()
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StatePreparing,
|
|
events.StatePreparing,
|
|
events.StateFailed,
|
|
}, sr)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestRunnerStageSkipped() {
|
|
svc := &MockService{
|
|
nilRunner: true,
|
|
}
|
|
sr := system.NewServiceRunner(svc, nil)
|
|
sr.Start()
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StatePreparing,
|
|
events.StatePreparing,
|
|
events.StateSkipped,
|
|
}, sr)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestAbortOnCondition() {
|
|
svc := &MockService{
|
|
condition: conditions.WaitForFileToExist("/doesntexistever"),
|
|
}
|
|
sr := system.NewServiceRunner(svc, nil)
|
|
|
|
finished := make(chan struct{})
|
|
|
|
go func() {
|
|
defer close(finished)
|
|
sr.Start()
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
select {
|
|
case <-finished:
|
|
suite.Require().Fail("service running should be still running")
|
|
default:
|
|
}
|
|
|
|
sr.Shutdown()
|
|
|
|
<-finished
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StateWaiting,
|
|
events.StateFailed,
|
|
}, sr)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestPostStateFail() {
|
|
svc := &MockService{
|
|
condition: conditions.None(),
|
|
postError: errors.New("post failed"),
|
|
}
|
|
sr := system.NewServiceRunner(svc, nil)
|
|
|
|
finished := make(chan struct{})
|
|
|
|
go func() {
|
|
defer close(finished)
|
|
sr.Start()
|
|
}()
|
|
|
|
sr.Shutdown()
|
|
|
|
<-finished
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StateWaiting,
|
|
events.StatePreparing,
|
|
events.StatePreparing,
|
|
events.StateRunning,
|
|
events.StateFinished,
|
|
events.StateFailed,
|
|
}, sr)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestRunFail() {
|
|
runner := &MockRunner{exitCh: make(chan error)}
|
|
svc := &MockService{runner: runner}
|
|
sr := system.NewServiceRunner(svc, nil)
|
|
|
|
finished := make(chan struct{})
|
|
|
|
go func() {
|
|
defer close(finished)
|
|
sr.Start()
|
|
}()
|
|
|
|
runner.exitCh <- errors.New("run failed")
|
|
|
|
<-finished
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StatePreparing,
|
|
events.StatePreparing,
|
|
events.StateRunning,
|
|
events.StateFailed,
|
|
}, sr)
|
|
}
|
|
|
|
func TestServiceRunnerSuite(t *testing.T) {
|
|
suite.Run(t, new(ServiceRunnerSuite))
|
|
}
|