Skip to content
This repository is currently being migrated. It's locked while the migration is in progress.

Commit

Permalink
Adds set failover grace period and maintenance mode commands (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mojachieee authored Feb 21, 2023
1 parent e122d67 commit 4e189d7
Show file tree
Hide file tree
Showing 24 changed files with 571 additions and 78 deletions.
2 changes: 2 additions & 0 deletions apiclient/apiclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ type Transport interface {
// matching the node entity's version as seen by the server.
SetCordoned(ctx context.Context, nodeID id.Node, params *SetCordonedRequestParams) error

SetFailoverGracePeriod(ctx context.Context, nodeID id.Node, params *SetFailoverGracePeriodParams) error

EvictReplica(ctx context.Context, namespaceID string, id string, deploymentID string) error

AttemptPromotion(ctx context.Context, namespaceID string, id string, deploymentID string) error
Expand Down
4 changes: 4 additions & 0 deletions apiclient/mock_transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ func (m *mockTransport) SetCordoned(ctx context.Context, nodeID id.Node, params
return m.SetCordonedErr
}

func (m *mockTransport) SetFailoverGracePeriod(ctx context.Context, nodeID id.Node, params *SetFailoverGracePeriodParams) error {
panic("not implemented")
}

func (m *mockTransport) EvictReplica(ctx context.Context, namespaceID string, id string, deploymentID string) error {
return nil
}
Expand Down
4 changes: 4 additions & 0 deletions apiclient/no_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ func (t *noTransport) SetCordoned(ctx context.Context, nodeID id.Node, params *S
return ErrNoTransportConfigured
}

func (t *noTransport) SetFailoverGracePeriod(ctx context.Context, nodeID id.Node, params *SetFailoverGracePeriodParams) error {
return ErrNoTransportConfigured
}

func (t *noTransport) EvictReplica(ctx context.Context, namespaceID string, id string, deploymentID string) error {
return ErrNoTransportConfigured
}
Expand Down
7 changes: 7 additions & 0 deletions apiclient/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ type SetCordonedRequestParams struct {
CASVersion version.Version
}

// SetFailoverGracePeriodParams contains the required and optional parameteres
// for a set failover grace period operation
type SetFailoverGracePeriodParams struct {
GracePeriod uint64
CASVersion version.Version
}

// NodeNotFoundError indicates that the API could not find the StorageOS node
// specified.
type NodeNotFoundError struct {
Expand Down
6 changes: 4 additions & 2 deletions apiclient/openapi/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package openapi

import (
"errors"
"time"

openapi "github.com/storageos/go-api/autogenerated"

Expand Down Expand Up @@ -73,8 +74,9 @@ func (c codec) decodeNode(node openapi.Node) (*model.Node, error) {
GossipAddr: node.GossipEndpoint,
ClusteringAddr: node.ClusteringEndpoint,

Cordoned: node.Cordoned,
CordonedAt: node.CordonedAt,
Cordoned: node.Cordoned,
CordonedAt: node.CordonedAt,
FailoverGracePeriod: time.Millisecond * time.Duration(node.FailoverGracePeriod),

CreatedAt: node.CreatedAt,
UpdatedAt: node.UpdatedAt,
Expand Down
76 changes: 57 additions & 19 deletions apiclient/openapi/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,18 @@ func (o *OpenAPI) ListNodes(ctx context.Context) ([]*model.Node, error) {
//
// The behaviour of the operation is dictated by params:
//
// Version constraints:
// - If params is nil or params.CASVersion is empty then the delete request is
// unconditional
// - If params.CASVersion is set, the request is conditional upon it matching
// the node entity's version as seen by the server.
// Version constraints:
// - If params is nil or params.CASVersion is empty then the delete request is
// unconditional
// - If params.CASVersion is set, the request is conditional upon it matching
// the node entity's version as seen by the server.
//
// Asynchrony:
// - If params is nil or params.AsyncMax is empty/zero valued then the delete
// request is performed synchronously.
// - If params.AsyncMax is set, the request is performed asynchronously using
// the duration given as the maximum amount of time allowed for the request
// before it times out.
// Asynchrony:
// - If params is nil or params.AsyncMax is empty/zero valued then the delete
// request is performed synchronously.
// - If params.AsyncMax is set, the request is performed asynchronously using
// the duration given as the maximum amount of time allowed for the request
// before it times out.
func (o *OpenAPI) DeleteNode(ctx context.Context, nodeID id.Node, params *apiclient.DeleteNodeRequestParams) error {
o.mu.RLock()
defer o.mu.RUnlock()
Expand Down Expand Up @@ -115,15 +115,15 @@ func (o *OpenAPI) DeleteNode(ctx context.Context, nodeID id.Node, params *apicli
//
// The behaviour of the operation is dictated by params:
//
// Version constraints:
// - If params is nil or params.CASVersion is empty then the delete request is
// unconditional
// - If params.CASVersion is set, the request is conditional upon it matching
// the node entity's version as seen by the server.
// Version constraints:
// - If params is nil or params.CASVersion is empty then the delete request is
// unconditional
// - If params.CASVersion is set, the request is conditional upon it matching
// the node entity's version as seen by the server.
//
// Cordoned:
// - If true marks the node as cordoned
// - If false marks the node as not cordoned
// Cordoned:
// - If true marks the node as cordoned
// - If false marks the node as not cordoned
func (o *OpenAPI) SetCordoned(ctx context.Context, nodeID id.Node, params *apiclient.SetCordonedRequestParams) error {
o.mu.RLock()
defer o.mu.RUnlock()
Expand Down Expand Up @@ -160,3 +160,41 @@ func (o *OpenAPI) SetCordoned(ctx context.Context, nodeID id.Node, params *apicl

return nil
}

// SetFailoverGracePeriod calls the SetFailoverGracePeriod REST endpoint
func (o *OpenAPI) SetFailoverGracePeriod(ctx context.Context, nodeID id.Node, params *apiclient.SetFailoverGracePeriodParams) error {
o.mu.RLock()
defer o.mu.RUnlock()

var casVersion string
var ignoreVersion optional.Bool = optional.NewBool(true)

if params != nil {
if params.CASVersion.String() != "" {
ignoreVersion = optional.NewBool(false)
casVersion = params.CASVersion.String()
}
}
_, resp, err := o.client.DefaultApi.SetFailoverGracePeriod(
ctx,
nodeID.String(),
openapi.SetFailoverGracePeriodData{
GracePeriod: params.GracePeriod,
Version: casVersion,
},
&openapi.SetFailoverGracePeriodOpts{
IgnoreVersion: ignoreVersion,
},
)

if err != nil {
switch v := mapOpenAPIError(err, resp).(type) {
case notFoundError:
return apiclient.NewNodeNotFoundError(nodeID)
default:
return v
}
}

return nil
}
17 changes: 13 additions & 4 deletions apiclient/reauth_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,15 @@ func (tr *TransportWithReauth) SetCordoned(ctx context.Context, nodeID id.Node,
return err
}

func (tr *TransportWithReauth) SetFailoverGracePeriod(ctx context.Context, nodeID id.Node, params *SetFailoverGracePeriodParams) error {

err := tr.doWithReauth(ctx, func() error {
return tr.inner.SetFailoverGracePeriod(ctx, nodeID, params)
})

return err
}

func (tr *TransportWithReauth) EvictReplica(ctx context.Context, namespaceID string, id string, deploymentID string) error {
return tr.doWithReauth(ctx, func() error {
return tr.inner.EvictReplica(ctx, namespaceID, id, deploymentID)
Expand Down Expand Up @@ -569,10 +578,10 @@ func (tr *TransportWithReauth) SetPreferredEvictionCandidates(ctx context.Contex

// doWithReauth invokes fn, checking the resultant error.
//
// - If the error is an *AuthenticationError then tr's credentials are
// used to reauthenticate before returning the result from re-invoking fn.
// If any errors occur during reauthentication, they are returned.
// - Otherwise, the original error is returned to the caller.
// - If the error is an *AuthenticationError then tr's credentials are
// used to reauthenticate before returning the result from re-invoking fn.
// If any errors occur during reauthentication, they are returned.
// - Otherwise, the original error is returned to the caller.
func (tr *TransportWithReauth) doWithReauth(ctx context.Context, fn func() error) error {
originalErr := fn()

Expand Down
1 change: 1 addition & 0 deletions cmd/interfaces/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type Client interface {
DeletePolicyGroup(ctx context.Context, uid id.PolicyGroup, params *apiclient.DeletePolicyGroupRequestParams) error

SetCordoned(ctx context.Context, nodeID id.Node, params *apiclient.SetCordonedRequestParams) error
SetFailoverGracePeriod(ctx context.Context, nodeID id.Node, params *apiclient.SetFailoverGracePeriodParams) error
UpdateLicence(ctx context.Context, licenceKey []byte, params *apiclient.UpdateLicenceRequestParams) (*model.License, error)
AttachVolume(ctx context.Context, namespaceID id.Namespace, volumeID id.Volume, nodeID id.Node) error
DetachVolume(ctx context.Context, namespaceID id.Namespace, volumeID id.Volume, params *apiclient.DetachVolumeRequestParams) error
Expand Down
100 changes: 100 additions & 0 deletions cmd/update/maintenance_mode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package update

import (
"context"
"errors"
"fmt"
"io"
"strconv"
"time"

"code.storageos.net/storageos/c2-cli/apiclient"
"code.storageos.net/storageos/c2-cli/cmd/argwrappers"
"code.storageos.net/storageos/c2-cli/cmd/interfaces"
"code.storageos.net/storageos/c2-cli/cmd/runwrappers"
"github.com/spf13/cobra"
)

type maintenanceModeCommand struct {
config interfaces.ConfigProvider
client interfaces.Client
w io.Writer
}

// NewMaintenanceModeCmd defines config for the "maintenance-mode" command
func NewMaintenanceModeCmd(w io.Writer, client interfaces.Client, config interfaces.ConfigProvider) *cobra.Command {
c := &maintenanceModeCommand{
config: config,
client: client,
w: w,
}

cmd := &cobra.Command{
Use: "maintenance-mode <true|false>",
Short: "Sets maintenance mode for the entire cluster",
Long: `Sets maintenance mode for the entire cluster
When maintenance mode is enabled failovers will not occur`,
Example: " storageos maintenance-mode true",
Args: argwrappers.WrapInvalidArgsError(func(_ *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("must specify true or false")
}
return nil
}),
RunE: func(cmd *cobra.Command, args []string) error {
run := runwrappers.Chain(
runwrappers.RunWithTimeout(config),
runwrappers.AuthenticateClient(config, client),
)(c.run)

return run(context.Background(), cmd, args)
},
}

return cmd
}

func (c *maintenanceModeCommand) run(ctx context.Context, _ *cobra.Command, args []string) error {
useIDs, err := c.config.UseIDs()
if err != nil {
return err
}

enabled, err := strconv.ParseBool(args[0])
if err != nil {
return fmt.Errorf("failed to parse boolean from arguments: %w", err)
}

nodes, err := c.client.ListNodes(ctx)
if err != nil {
return fmt.Errorf("failed to list nodes: %w", err)
}

var gracePeriod uint64
if enabled {
// Set the failover period to a 100 years, which should be a long enough maintenance window
gracePeriod = uint64(time.Hour.Milliseconds() * 24 * 365 * 100)
}

for _, node := range nodes {

err = c.client.SetFailoverGracePeriod(ctx, node.ID, &apiclient.SetFailoverGracePeriodParams{
GracePeriod: gracePeriod,
})
if err != nil {
n := node.Name
if useIDs {
n = string(node.ID)
}

return fmt.Errorf("failed to set maintenance mode for node %v: %w", n, err)
}

nodeOutput := node.Name
if useIDs {
nodeOutput = string(node.ID)
}
fmt.Fprintf(c.w, "set maintenance mode to %v for node/%s\n", enabled, nodeOutput)
}
return nil
}
22 changes: 22 additions & 0 deletions cmd/update/node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package update

import (
"os"

"code.storageos.net/storageos/c2-cli/cmd/interfaces"
"github.com/spf13/cobra"
)

// newNodeUpdate configures the set of commands which are grouped by the
// "node" noun.
func newNodeUpdate(client interfaces.Client, config interfaces.ConfigProvider) *cobra.Command {
command := &cobra.Command{
Use: "node",
Short: "Make changes to a node",
}

command.AddCommand(NewFailoverGracePeriodCmd(os.Stdout, client, config))
command.AddCommand(NewMaintenanceModeCmd(os.Stdout, client, config))

return command
}
Loading

0 comments on commit 4e189d7

Please sign in to comment.