mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-26 17:11:19 +02:00
It is now possible to `start`/`stop`/`restart` any service via `osctl` commands. There are some changes in `ServiceRunner` to support re-use (re-entering running state). `Services` singleton now tracks service running state to avoid calling `Start()` on already running `ServiceRunner` instance. Method `Start()` was renamed to `LoadAndStart()` to break up service loading (adding to the list of service) and actual service start. Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
382 lines
7.7 KiB
Go
382 lines
7.7 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/machined/pkg/system"
|
|
"github.com/talos-systems/talos/internal/app/machined/pkg/system/conditions"
|
|
"github.com/talos-systems/talos/internal/app/machined/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, // once again healthy
|
|
events.StateFinished,
|
|
}, sr)
|
|
}
|
|
|
|
func (suite *ServiceRunnerSuite) TestWaitingDescriptionChange() {
|
|
oldWaitConditionCheckInterval := system.WaitConditionCheckInterval
|
|
system.WaitConditionCheckInterval = 10 * time.Millisecond
|
|
defer func() {
|
|
system.WaitConditionCheckInterval = oldWaitConditionCheckInterval
|
|
}()
|
|
|
|
cond1 := NewMockCondition("cond1")
|
|
cond2 := NewMockCondition("cond2")
|
|
sr := system.NewServiceRunner(&MockService{
|
|
condition: conditions.WaitForAll(cond1, cond2),
|
|
}, 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:
|
|
}
|
|
|
|
close(cond1.done)
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
select {
|
|
case <-finished:
|
|
suite.Require().Fail("service running should be still running")
|
|
default:
|
|
}
|
|
|
|
close(cond2.done)
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
sr.Shutdown()
|
|
|
|
<-finished
|
|
|
|
suite.assertStateSequence([]events.ServiceState{
|
|
events.StateWaiting,
|
|
events.StateWaiting,
|
|
events.StatePreparing,
|
|
events.StatePreparing,
|
|
events.StateRunning,
|
|
events.StateFinished,
|
|
}, sr)
|
|
|
|
events := sr.GetEventHistory(10000)
|
|
suite.Assert().Equal("Waiting for cond1, cond2", events[0].Message)
|
|
suite.Assert().Equal("Waiting for cond2", events[1].Message)
|
|
}
|
|
|
|
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 (suite *ServiceRunnerSuite) TestFullFlowRestart() {
|
|
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
|
|
|
|
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,
|
|
events.StateWaiting,
|
|
events.StatePreparing,
|
|
events.StatePreparing,
|
|
events.StateRunning,
|
|
events.StateFinished,
|
|
}, sr)
|
|
}
|
|
|
|
func TestServiceRunnerSuite(t *testing.T) {
|
|
suite.Run(t, new(ServiceRunnerSuite))
|
|
}
|