2020-04-23 21:12:44 -07:00
// 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 v1alpha1
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"os/signal"
"strconv"
"strings"
"sync/atomic"
"syscall"
"time"
"golang.org/x/sync/errgroup"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
2020-06-05 00:20:40 +03:00
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/logging"
2020-04-23 21:12:44 -07:00
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/acpi"
2021-01-12 15:52:24 +03:00
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha2"
2020-08-17 19:12:47 +03:00
"github.com/talos-systems/talos/pkg/machinery/api/common"
"github.com/talos-systems/talos/pkg/machinery/api/machine"
"github.com/talos-systems/talos/pkg/machinery/config"
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
2020-04-23 21:12:44 -07:00
)
// Controller represents the controller responsible for managing the execution
// of sequences.
type Controller struct {
2021-01-12 15:52:24 +03:00
r * Runtime
s * Sequencer
v2 * v1alpha2 . Controller
2020-04-23 21:12:44 -07:00
semaphore int32
}
// NewController intializes and returns a controller.
func NewController ( b [ ] byte ) ( * Controller , error ) {
// Wait for USB storage in the case that the install disk is supplied over
// USB. If we don't wait, there is the chance that we will fail to detect the
// install disk.
err := waitForUSBDelay ( )
if err != nil {
return nil , err
}
s , err := NewState ( )
if err != nil {
return nil , err
}
2020-07-28 21:48:26 +03:00
var cfg config . Provider
2020-04-23 21:12:44 -07:00
if b != nil {
2020-07-28 21:48:26 +03:00
cfg , err = configloader . NewFromBytes ( b )
2020-04-23 21:12:44 -07:00
if err != nil {
return nil , fmt . Errorf ( "failed to parse config: %w" , err )
}
}
2020-05-15 18:19:06 +03:00
// TODO: this should be streaming capacity and probably some constant
2020-07-06 23:50:45 +03:00
e := NewEvents ( 1000 , 10 )
2020-05-07 22:50:41 -07:00
2020-06-19 23:45:50 +03:00
l := logging . NewCircularBufferLoggingManager ( )
2020-06-05 00:20:40 +03:00
2020-04-23 21:12:44 -07:00
ctlr := & Controller {
2020-06-05 00:20:40 +03:00
r : NewRuntime ( cfg , s , e , l ) ,
2020-04-23 21:12:44 -07:00
s : NewSequencer ( ) ,
}
2021-01-12 15:52:24 +03:00
ctlr . v2 , err = v1alpha2 . NewController ( ctlr . r , l )
if err != nil {
return nil , err
}
2020-04-23 21:12:44 -07:00
return ctlr , nil
}
// Run executes all phases known to the controller in serial. `Controller`
// aborts immediately if any phase fails.
2020-06-09 18:48:40 +00:00
func ( c * Controller ) Run ( seq runtime . Sequence , data interface { } , setters ... runtime . ControllerOption ) error {
2020-04-23 21:12:44 -07:00
// We must ensure that the runtime is configured since all sequences depend
// on the runtime.
if c . r == nil {
return runtime . ErrUndefinedRuntime
}
2020-06-09 18:48:40 +00:00
opts := runtime . DefaultControllerOptions ( )
for _ , f := range setters {
if err := f ( & opts ) ; err != nil {
return err
}
}
2020-06-09 17:43:30 +00:00
// Allow only one sequence to run at a time with the exception of bootstrap
// and reset sequences.
2020-07-16 16:25:04 +03:00
switch seq { //nolint: exhaustive
2021-01-19 15:45:50 +03:00
case runtime . SequenceBootstrap , runtime . SequenceReset :
2020-06-09 17:43:30 +00:00
// Do not attempt to lock.
default :
2020-06-09 18:48:40 +00:00
if opts . Force {
break
}
2020-05-01 15:28:55 -07:00
if c . TryLock ( ) {
2020-05-07 22:50:41 -07:00
c . Runtime ( ) . Events ( ) . Publish ( & machine . SequenceEvent {
Sequence : seq . String ( ) ,
Action : machine . SequenceEvent_NOOP ,
Error : & common . Error {
2020-07-21 00:17:50 +03:00
Code : common . Code_LOCKED ,
2020-05-07 22:50:41 -07:00
Message : fmt . Sprintf ( "sequence not started: %s" , runtime . ErrLocked . Error ( ) ) ,
} ,
} )
2020-05-01 15:28:55 -07:00
return runtime . ErrLocked
}
2020-04-23 21:12:44 -07:00
2020-05-01 15:28:55 -07:00
defer c . Unlock ( )
}
2020-04-23 21:12:44 -07:00
phases , err := c . phases ( seq , data )
if err != nil {
return err
}
2020-05-07 22:50:41 -07:00
err = c . run ( seq , phases , data )
if err != nil {
c . Runtime ( ) . Events ( ) . Publish ( & machine . SequenceEvent {
Sequence : seq . String ( ) ,
Action : machine . SequenceEvent_NOOP ,
Error : & common . Error {
Code : common . Code_FATAL ,
Message : fmt . Sprintf ( "sequence failed: %s" , err . Error ( ) ) ,
} ,
} )
return err
}
return nil
2020-04-23 21:12:44 -07:00
}
2021-01-12 15:52:24 +03:00
// V1Alpha2 implements the controller interface.
func ( c * Controller ) V1Alpha2 ( ) runtime . V1Alpha2Controller {
return c . v2
}
2020-04-23 21:12:44 -07:00
// Runtime implements the controller interface.
func ( c * Controller ) Runtime ( ) runtime . Runtime {
return c . r
}
// Sequencer implements the controller interface.
func ( c * Controller ) Sequencer ( ) runtime . Sequencer {
return c . s
}
// ListenForEvents starts the event listener. The listener will trigger a
// shutdown in response to a SIGTERM signal and ACPI button/power event.
func ( c * Controller ) ListenForEvents ( ) error {
sigs := make ( chan os . Signal , 1 )
signal . Notify ( sigs , syscall . SIGTERM )
errCh := make ( chan error , 2 )
go func ( ) {
<- sigs
signal . Stop ( sigs )
log . Printf ( "shutdown via SIGTERM received" )
if err := c . Run ( runtime . SequenceShutdown , nil ) ; err != nil {
log . Printf ( "shutdown failed: %v" , err )
}
errCh <- nil
} ( )
if c . r . State ( ) . Platform ( ) . Mode ( ) == runtime . ModeContainer {
return nil
}
go func ( ) {
if err := acpi . StartACPIListener ( ) ; err != nil {
errCh <- err
return
}
log . Printf ( "shutdown via ACPI received" )
// TODO: The sequencer lock will prevent this. We need a way to force the
// shutdown.
if err := c . Run ( runtime . SequenceShutdown , nil ) ; err != nil {
log . Printf ( "shutdown failed: %v" , err )
}
errCh <- nil
} ( )
err := <- errCh
return err
}
// TryLock attempts to set a lock that prevents multiple sequences from running
// at once. If currently locked, a value of true will be returned. If not
// currently locked, a value of false will be returned.
func ( c * Controller ) TryLock ( ) bool {
return ! atomic . CompareAndSwapInt32 ( & c . semaphore , 0 , 1 )
}
// Unlock removes the lock set by `TryLock`.
func ( c * Controller ) Unlock ( ) bool {
return atomic . CompareAndSwapInt32 ( & c . semaphore , 1 , 0 )
}
func ( c * Controller ) run ( seq runtime . Sequence , phases [ ] runtime . Phase , data interface { } ) error {
2020-05-07 22:50:41 -07:00
c . Runtime ( ) . Events ( ) . Publish ( & machine . SequenceEvent {
Sequence : seq . String ( ) ,
Action : machine . SequenceEvent_START ,
} )
defer c . Runtime ( ) . Events ( ) . Publish ( & machine . SequenceEvent {
Sequence : seq . String ( ) ,
Action : machine . SequenceEvent_STOP ,
} )
2020-04-23 21:12:44 -07:00
start := time . Now ( )
var (
number int
phase runtime . Phase
err error
)
2020-07-21 00:36:28 +03:00
log . Printf ( "%s sequence: %d phase(s)" , seq . String ( ) , len ( phases ) )
defer func ( ) {
if err != nil {
log . Printf ( "%s sequence: failed" , seq . String ( ) )
}
log . Printf ( "%s sequence: done: %s" , seq . String ( ) , time . Since ( start ) )
} ( )
2020-04-23 21:12:44 -07:00
for number , phase = range phases {
// Make the phase number human friendly.
number ++
start := time . Now ( )
progress := fmt . Sprintf ( "%d/%d" , number , len ( phases ) )
2020-07-09 16:17:47 +03:00
log . Printf ( "phase %s (%s): %d tasks(s)" , phase . Name , progress , len ( phase . Tasks ) )
2020-04-23 21:12:44 -07:00
if err = c . runPhase ( phase , seq , data ) ; err != nil {
2020-07-21 00:36:28 +03:00
log . Printf ( "phase %s (%s): failed" , phase . Name , progress )
2020-04-23 21:12:44 -07:00
return fmt . Errorf ( "error running phase %d in %s sequence: %w" , number , seq . String ( ) , err )
}
2020-07-09 16:17:47 +03:00
log . Printf ( "phase %s (%s): done, %s" , phase . Name , progress , time . Since ( start ) )
2020-04-23 21:12:44 -07:00
}
return nil
}
func ( c * Controller ) runPhase ( phase runtime . Phase , seq runtime . Sequence , data interface { } ) error {
2020-05-07 22:50:41 -07:00
c . Runtime ( ) . Events ( ) . Publish ( & machine . PhaseEvent {
2020-07-09 16:17:47 +03:00
Phase : phase . Name ,
2020-05-07 22:50:41 -07:00
Action : machine . PhaseEvent_START ,
} )
defer c . Runtime ( ) . Events ( ) . Publish ( & machine . PhaseEvent {
2020-07-09 16:17:47 +03:00
Phase : phase . Name ,
2020-05-07 22:50:41 -07:00
Action : machine . PhaseEvent_START ,
} )
2020-04-23 21:12:44 -07:00
var eg errgroup . Group
2020-07-09 16:17:47 +03:00
for number , task := range phase . Tasks {
2020-04-23 21:12:44 -07:00
// Make the task number human friendly.
number := number
number ++
task := task
eg . Go ( func ( ) error {
2020-07-09 16:17:47 +03:00
progress := fmt . Sprintf ( "%d/%d" , number , len ( phase . Tasks ) )
2020-04-23 21:12:44 -07:00
2020-07-09 16:17:47 +03:00
if err := c . runTask ( progress , task , seq , data ) ; err != nil {
2020-04-23 21:12:44 -07:00
return fmt . Errorf ( "task %s: failed, %w" , progress , err )
}
return nil
} )
}
return eg . Wait ( )
}
2020-07-09 16:17:47 +03:00
func ( c * Controller ) runTask ( progress string , f runtime . TaskSetupFunc , seq runtime . Sequence , data interface { } ) error {
task , taskName := f ( seq , data )
if task == nil {
return nil
}
start := time . Now ( )
2020-05-07 22:50:41 -07:00
c . Runtime ( ) . Events ( ) . Publish ( & machine . TaskEvent {
2020-07-09 16:17:47 +03:00
Task : taskName ,
2020-05-07 22:50:41 -07:00
Action : machine . TaskEvent_START ,
} )
2020-07-21 00:36:28 +03:00
var err error
2020-07-09 16:17:47 +03:00
log . Printf ( "task %s (%s): starting" , taskName , progress )
defer func ( ) {
2020-07-21 00:36:28 +03:00
if err != nil {
log . Printf ( "task %s (%s): failed" , taskName , progress )
}
2020-07-09 16:17:47 +03:00
log . Printf ( "task %s (%s): done, %s" , taskName , progress , time . Since ( start ) )
} ( )
2020-05-07 22:50:41 -07:00
defer c . Runtime ( ) . Events ( ) . Publish ( & machine . TaskEvent {
2020-07-09 16:17:47 +03:00
Task : taskName ,
2020-05-07 22:50:41 -07:00
Action : machine . TaskEvent_STOP ,
} )
2020-11-16 20:59:45 +03:00
logger := log . New ( log . Writer ( ) , fmt . Sprintf ( "[talos] task %s (%s): " , taskName , progress ) , log . Flags ( ) )
2020-04-23 21:12:44 -07:00
2020-07-21 00:36:28 +03:00
err = task ( context . TODO ( ) , logger , c . r )
return err
2020-04-23 21:12:44 -07:00
}
2020-04-28 10:27:19 -07:00
// nolint: gocyclo
2020-04-23 21:12:44 -07:00
func ( c * Controller ) phases ( seq runtime . Sequence , data interface { } ) ( [ ] runtime . Phase , error ) {
var phases [ ] runtime . Phase
switch seq {
2020-09-22 20:19:12 -04:00
case runtime . SequenceApplyConfiguration :
var (
in * machine . ApplyConfigurationRequest
ok bool
)
if in , ok = data . ( * machine . ApplyConfigurationRequest ) ; ! ok {
return nil , runtime . ErrInvalidSequenceData
}
phases = c . s . ApplyConfiguration ( c . r , in )
2020-04-23 21:12:44 -07:00
case runtime . SequenceBoot :
phases = c . s . Boot ( c . r )
2020-05-01 15:28:55 -07:00
case runtime . SequenceBootstrap :
phases = c . s . Bootstrap ( c . r )
2020-04-23 21:12:44 -07:00
case runtime . SequenceInitialize :
phases = c . s . Initialize ( c . r )
case runtime . SequenceInstall :
phases = c . s . Install ( c . r )
case runtime . SequenceShutdown :
phases = c . s . Shutdown ( c . r )
case runtime . SequenceReboot :
phases = c . s . Reboot ( c . r )
case runtime . SequenceUpgrade :
var (
in * machine . UpgradeRequest
ok bool
)
if in , ok = data . ( * machine . UpgradeRequest ) ; ! ok {
return nil , runtime . ErrInvalidSequenceData
}
phases = c . s . Upgrade ( c . r , in )
2020-12-21 23:36:23 +03:00
case runtime . SequenceStageUpgrade :
var (
in * machine . UpgradeRequest
ok bool
)
if in , ok = data . ( * machine . UpgradeRequest ) ; ! ok {
return nil , runtime . ErrInvalidSequenceData
}
phases = c . s . StageUpgrade ( c . r , in )
2020-04-23 21:12:44 -07:00
case runtime . SequenceReset :
var (
2020-12-09 15:49:46 +03:00
in runtime . ResetOptions
2020-04-23 21:12:44 -07:00
ok bool
)
2020-12-09 15:49:46 +03:00
if in , ok = data . ( runtime . ResetOptions ) ; ! ok {
2020-04-23 21:12:44 -07:00
return nil , runtime . ErrInvalidSequenceData
}
phases = c . s . Reset ( c . r , in )
2020-07-16 16:25:04 +03:00
case runtime . SequenceNoop :
2020-05-01 15:28:55 -07:00
default :
return nil , fmt . Errorf ( "sequence not implemented: %q" , seq )
2020-04-23 21:12:44 -07:00
}
return phases , nil
}
func waitForUSBDelay ( ) ( err error ) {
wait := true
file := "/sys/module/usb_storage/parameters/delay_use"
_ , err = os . Stat ( file )
if err != nil {
if os . IsNotExist ( err ) {
wait = false
} else {
return err
}
}
if wait {
var b [ ] byte
b , err = ioutil . ReadFile ( file )
if err != nil {
return err
}
val := strings . TrimSuffix ( string ( b ) , "\n" )
var i int
i , err = strconv . Atoi ( val )
if err != nil {
return err
}
log . Printf ( "waiting %d second(s) for USB storage" , i )
time . Sleep ( time . Duration ( i ) * time . Second )
}
return nil
}