feat: add reboot-mode flag to talosctl upgrade

Allow specifying the reboot mode during upgrades by introducing `--reboot-mode` flag, similar to the `--mode` flag of the reboot command.

Closes siderolabs/talos#7302.

Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
This commit is contained in:
Utku Ozdemir 2023-06-26 17:33:25 +02:00
parent 7ce87f20c3
commit 0d313b9733
No known key found for this signature in database
GPG Key ID: 65933E76F0549B0D
9 changed files with 1988 additions and 1795 deletions

View File

@ -339,10 +339,15 @@ message ShutdownResponse {
// rpc upgrade
message UpgradeRequest {
enum RebootMode {
DEFAULT = 0;
POWERCYCLE = 1;
}
string image = 1;
bool preserve = 2;
bool stage = 3;
bool force = 4;
RebootMode reboot_mode = 5;
}
message Upgrade {

View File

@ -8,9 +8,12 @@ import (
"context"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"time"
"github.com/siderolabs/gen/maps"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/peer"
@ -20,6 +23,7 @@ import (
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/helpers"
"github.com/siderolabs/talos/pkg/cli"
"github.com/siderolabs/talos/pkg/images"
"github.com/siderolabs/talos/pkg/machinery/api/machine"
"github.com/siderolabs/talos/pkg/machinery/client"
"github.com/siderolabs/talos/pkg/version"
)
@ -27,6 +31,7 @@ import (
var upgradeCmdFlags struct {
trackableActionCmdFlags
upgradeImage string
rebootMode string
preserve bool
stage bool
force bool
@ -48,8 +53,23 @@ var upgradeCmd = &cobra.Command{
return fmt.Errorf("cannot use --wait and --insecure together")
}
rebootModeStr := strings.ToUpper(upgradeCmdFlags.rebootMode)
rebootMode, rebootModeOk := machine.UpgradeRequest_RebootMode_value[rebootModeStr]
if !rebootModeOk {
return fmt.Errorf("invalid reboot mode: %s", upgradeCmdFlags.rebootMode)
}
opts := []client.UpgradeOption{
client.WithUpgradeImage(upgradeCmdFlags.upgradeImage),
client.WithUpgradeRebootMode(machine.UpgradeRequest_RebootMode(rebootMode)),
client.WithUpgradePreserve(upgradeCmdFlags.preserve),
client.WithUpgradeStage(upgradeCmdFlags.stage),
client.WithUpgradeForce(upgradeCmdFlags.force),
}
if !upgradeCmdFlags.wait {
return runUpgradeNoWait()
return runUpgradeNoWait(opts)
}
common.SuppressErrors = true
@ -57,7 +77,9 @@ var upgradeCmd = &cobra.Command{
return action.NewTracker(
&GlobalArgs,
action.MachineReadyEventFn,
upgradeGetActorID,
func(ctx context.Context, c *client.Client) (string, error) {
return upgradeGetActorID(ctx, c, opts)
},
action.WithPostCheck(action.BootIDChangedPostCheckFn),
action.WithDebug(upgradeCmdFlags.debug),
action.WithTimeout(upgradeCmdFlags.timeout),
@ -65,7 +87,7 @@ var upgradeCmd = &cobra.Command{
},
}
func runUpgradeNoWait() error {
func runUpgradeNoWait(opts []client.UpgradeOption) error {
upgradeFn := func(ctx context.Context, c *client.Client) error {
if err := helpers.ClientVersionCheck(ctx, c); err != nil {
return err
@ -73,15 +95,10 @@ func runUpgradeNoWait() error {
var remotePeer peer.Peer
opts = append(opts, client.WithUpgradeGRPCCallOptions(grpc.Peer(&remotePeer)))
// TODO: See if we can validate version and prevent starting upgrades to an unknown version
resp, err := c.Upgrade(
ctx,
upgradeCmdFlags.upgradeImage,
upgradeCmdFlags.preserve,
upgradeCmdFlags.stage,
upgradeCmdFlags.force,
grpc.Peer(&remotePeer),
)
resp, err := c.UpgradeWithOptions(ctx, opts...)
if err != nil {
if resp == nil {
return fmt.Errorf("error performing upgrade: %s", err)
@ -115,14 +132,8 @@ func runUpgradeNoWait() error {
return WithClient(upgradeFn)
}
func upgradeGetActorID(ctx context.Context, c *client.Client) (string, error) {
resp, err := c.Upgrade(
ctx,
upgradeCmdFlags.upgradeImage,
upgradeCmdFlags.preserve,
upgradeCmdFlags.stage,
upgradeCmdFlags.force,
)
func upgradeGetActorID(ctx context.Context, c *client.Client, opts []client.UpgradeOption) (string, error) {
resp, err := c.UpgradeWithOptions(ctx, opts...)
if err != nil {
return "", err
}
@ -135,9 +146,18 @@ func upgradeGetActorID(ctx context.Context, c *client.Client) (string, error) {
}
func init() {
rebootModes := maps.KeysFunc(machine.UpgradeRequest_RebootMode_value, strings.ToLower)
sort.Slice(rebootModes, func(i, j int) bool {
return machine.UpgradeRequest_RebootMode_value[rebootModes[i]] > machine.UpgradeRequest_RebootMode_value[rebootModes[j]]
})
upgradeCmd.Flags().StringVarP(&upgradeCmdFlags.upgradeImage, "image", "i",
fmt.Sprintf("%s/%s/installer:%s", images.Registry, images.Username, version.Trim(version.Tag)),
"the container image to use for performing the install")
upgradeCmd.Flags().StringVarP(&upgradeCmdFlags.rebootMode, "reboot-mode", "m", strings.ToLower(machine.UpgradeRequest_DEFAULT.String()),
fmt.Sprintf("select the reboot mode during upgrade. Mode %q bypasses kexec. Valid values are: %q.",
strings.ToLower(machine.UpgradeRequest_POWERCYCLE.String()),
rebootModes))
upgradeCmd.Flags().BoolVarP(&upgradeCmdFlags.preserve, "preserve", "p", false, "preserve data")
upgradeCmd.Flags().BoolVarP(&upgradeCmdFlags.stage, "stage", "s", false, "stage the upgrade to perform it after a reboot")
upgradeCmd.Flags().BoolVarP(&upgradeCmdFlags.force, "force", "f", false, "force the upgrade (skip checks on etcd health and members, might lead to data loss)")

View File

@ -451,7 +451,7 @@ func (s *Server) Upgrade(ctx context.Context, in *machine.UpgradeRequest) (*mach
return nil, err
}
log.Printf("upgrade request received: preserve %v, staged %v, force %v", in.GetPreserve(), in.GetStage(), in.GetForce())
log.Printf("upgrade request received: preserve %v, staged %v, force %v, reboot mode %v", in.GetPreserve(), in.GetStage(), in.GetForce(), in.GetRebootMode().String())
log.Printf("validating %q", in.GetImage())

View File

@ -419,7 +419,7 @@ func (*Sequencer) StageUpgrade(r runtime.Runtime, in *machineapi.UpgradeRequest)
"leave",
LeaveEtcd,
).AppendList(
stopAllPhaselist(r, true),
stopAllPhaselist(r, in.GetRebootMode() == machineapi.UpgradeRequest_DEFAULT),
).Append(
"reboot",
Reboot,
@ -430,7 +430,7 @@ func (*Sequencer) StageUpgrade(r runtime.Runtime, in *machineapi.UpgradeRequest)
}
// MaintenanceUpgrade is the upgrade sequence in maintenance mode.
func (*Sequencer) MaintenanceUpgrade(r runtime.Runtime, _ *machineapi.UpgradeRequest) []runtime.Phase {
func (*Sequencer) MaintenanceUpgrade(r runtime.Runtime, in *machineapi.UpgradeRequest) []runtime.Phase {
phases := PhaseList{}
switch r.State().Platform().Mode() { //nolint:exhaustive
@ -449,7 +449,8 @@ func (*Sequencer) MaintenanceUpgrade(r runtime.Runtime, _ *machineapi.UpgradeReq
).Append(
"meta",
ReloadMeta,
).Append(
).AppendWhen(
in.GetRebootMode() == machineapi.UpgradeRequest_DEFAULT,
"kexec",
KexecPrepare,
).Append(
@ -517,7 +518,8 @@ func (*Sequencer) Upgrade(r runtime.Runtime, in *machineapi.UpgradeRequest) []ru
).Append(
"meta",
ReloadMeta,
).Append(
).AppendWhen(
in.GetRebootMode() == machineapi.UpgradeRequest_DEFAULT,
"kexec",
KexecPrepare,
).Append(

File diff suppressed because it is too large Load Diff

View File

@ -1623,6 +1623,11 @@ func (m *UpgradeRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.RebootMode != 0 {
i = encodeVarint(dAtA, i, uint64(m.RebootMode))
i--
dAtA[i] = 0x28
}
if m.Force {
i--
if m.Force {
@ -10461,6 +10466,9 @@ func (m *UpgradeRequest) SizeVT() (n int) {
if m.Force {
n += 2
}
if m.RebootMode != 0 {
n += 1 + sov(uint64(m.RebootMode))
}
n += len(m.unknownFields)
return n
}
@ -17117,6 +17125,25 @@ func (m *UpgradeRequest) UnmarshalVT(dAtA []byte) error {
}
}
m.Force = bool(v != 0)
case 5:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field RebootMode", wireType)
}
m.RebootMode = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.RebootMode |= UpgradeRequest_RebootMode(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])

View File

@ -543,24 +543,85 @@ func (c *Client) Copy(ctx context.Context, rootPath string) (io.ReadCloser, <-ch
return ReadStream(stream)
}
// Upgrade initiates a Talos upgrade and implements the proto.MachineServiceClient interface.
func (c *Client) Upgrade(ctx context.Context, image string, preserve, stage, force bool, callOptions ...grpc.CallOption) (resp *machineapi.UpgradeResponse, err error) {
resp, err = c.MachineClient.Upgrade(
ctx,
&machineapi.UpgradeRequest{
Image: image,
Preserve: preserve,
Stage: stage,
Force: force,
},
callOptions...,
)
// UpgradeOptions provides upgrade API options.
type UpgradeOptions struct {
Request machineapi.UpgradeRequest
GRPCCallOptions []grpc.CallOption
}
var filtered interface{}
// UpgradeOption provides upgrade API options.
type UpgradeOption func(*UpgradeOptions)
// WithUpgradeImage sets the image for the upgrade.
func WithUpgradeImage(image string) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.Image = image
}
}
// WithUpgradeRebootMode sets the reboot mode for the upgrade.
func WithUpgradeRebootMode(mode machineapi.UpgradeRequest_RebootMode) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.RebootMode = mode
}
}
// WithUpgradePreserve sets the preserve flag for the upgrade.
func WithUpgradePreserve(preserve bool) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.Preserve = preserve
}
}
// WithUpgradeStage sets the stage flag for the upgrade.
func WithUpgradeStage(stage bool) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.Stage = stage
}
}
// WithUpgradeForce sets the force flag for the upgrade.
func WithUpgradeForce(force bool) UpgradeOption {
return func(req *UpgradeOptions) {
req.Request.Force = force
}
}
// WithUpgradeGRPCCallOptions sets the gRPC call options for the upgrade.
func WithUpgradeGRPCCallOptions(opts ...grpc.CallOption) UpgradeOption {
return func(req *UpgradeOptions) {
req.GRPCCallOptions = opts
}
}
// Upgrade initiates a Talos upgrade and implements the proto.MachineServiceClient interface.
func (c *Client) Upgrade(ctx context.Context, image string, preserve, stage, force bool, callOptions ...grpc.CallOption) (*machineapi.UpgradeResponse, error) {
return c.UpgradeWithOptions(
ctx,
WithUpgradeImage(image),
WithUpgradeRebootMode(machineapi.UpgradeRequest_DEFAULT),
WithUpgradePreserve(preserve),
WithUpgradeStage(stage),
WithUpgradeForce(force),
WithUpgradeGRPCCallOptions(callOptions...),
)
}
// UpgradeWithOptions initiates a Talos upgrade with the given options.
func (c *Client) UpgradeWithOptions(ctx context.Context, opts ...UpgradeOption) (*machineapi.UpgradeResponse, error) {
var options UpgradeOptions
for _, opt := range opts {
opt(&options)
}
resp, err := c.MachineClient.Upgrade(ctx, &options.Request, options.GRPCCallOptions...)
var filtered any
filtered, err = FilterMessages(resp, err)
resp, _ = filtered.(*machineapi.UpgradeResponse) //nolint:errcheck
return
return resp, err
}
// ServiceList returns list of services with their state.

View File

@ -404,6 +404,7 @@ description: Talos gRPC API reference.
- [SequenceEvent.Action](#machine.SequenceEvent.Action)
- [ServiceStateEvent.Action](#machine.ServiceStateEvent.Action)
- [TaskEvent.Action](#machine.TaskEvent.Action)
- [UpgradeRequest.RebootMode](#machine.UpgradeRequest.RebootMode)
- [MachineService](#machine.MachineService)
@ -6644,6 +6645,7 @@ rpc upgrade
| preserve | [bool](#bool) | | |
| stage | [bool](#bool) | | |
| force | [bool](#bool) | | |
| reboot_mode | [UpgradeRequest.RebootMode](#machine.UpgradeRequest.RebootMode) | | |
@ -6923,6 +6925,18 @@ File type.
| STOP | 1 | |
<a name="machine.UpgradeRequest.RebootMode"></a>
### UpgradeRequest.RebootMode
| Name | Number | Description |
| ---- | ------ | ----------- |
| DEFAULT | 0 | |
| POWERCYCLE | 1 | |
<!-- end enums -->
<!-- end HasExtensions -->

View File

@ -2629,15 +2629,16 @@ talosctl upgrade [flags]
### Options
```
--debug debug operation from kernel logs. --wait is set to true when this flag is set
-f, --force force the upgrade (skip checks on etcd health and members, might lead to data loss)
-h, --help help for upgrade
-i, --image string the container image to use for performing the install (default "ghcr.io/siderolabs/installer:v1.5.0-alpha.1")
--insecure upgrade using the insecure (encrypted with no auth) maintenance service
-p, --preserve preserve data
-s, --stage stage the upgrade to perform it after a reboot
--timeout duration time to wait for the operation is complete if --debug or --wait is set (default 30m0s)
--wait wait for the operation to complete, tracking its progress. always set to true when --debug is set (default true)
--debug debug operation from kernel logs. --wait is set to true when this flag is set
-f, --force force the upgrade (skip checks on etcd health and members, might lead to data loss)
-h, --help help for upgrade
-i, --image string the container image to use for performing the install (default "ghcr.io/siderolabs/installer:v1.5.0-alpha.1")
--insecure upgrade using the insecure (encrypted with no auth) maintenance service
-p, --preserve preserve data
-m, --reboot-mode string select the reboot mode during upgrade. Mode "powercycle" bypasses kexec. Valid values are: ["default" "powercycle"]. (default "default")
-s, --stage stage the upgrade to perform it after a reboot
--timeout duration time to wait for the operation is complete if --debug or --wait is set (default 30m0s)
--wait wait for the operation to complete, tracking its progress. always set to true when --debug is set (default true)
```
### Options inherited from parent commands