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

Commit

Permalink
Merge pull request #206 from storageos/add-promote-command
Browse files Browse the repository at this point in the history
Add promote command
  • Loading branch information
Mojachieee authored Sep 29, 2022
2 parents 9801451 + 5d99dcf commit 94820e1
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 1 deletion.
2 changes: 2 additions & 0 deletions apiclient/apiclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ type Transport interface {
SetCordoned(ctx context.Context, nodeID id.Node, params *SetCordonedRequestParams) error

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

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

// Client provides a collection of methods for consumers to interact with the
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 @@ -434,3 +434,7 @@ func (m *mockTransport) SetCordoned(ctx context.Context, nodeID id.Node, params
func (m *mockTransport) EvictReplica(ctx context.Context, namespaceID string, id string, deploymentID string) error {
return nil
}

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

func (t *noTransport) AttemptPromotion(ctx context.Context, namespaceID string, id string, deploymentID string) error {
return ErrNoTransportConfigured
}
24 changes: 23 additions & 1 deletion apiclient/openapi/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,6 @@ func (o *OpenAPI) ResizeVolume(
return o.codec.decodeVolume(model)
}


func (o *OpenAPI) EvictReplica(ctx context.Context, namespaceID string, id string, deploymentID string) error {
o.mu.RLock()
defer o.mu.RUnlock()
Expand All @@ -650,3 +649,26 @@ func (o *OpenAPI) EvictReplica(ctx context.Context, namespaceID string, id strin

return nil
}

func (o *OpenAPI) AttemptPromotion(ctx context.Context, namespaceID string, id string, deploymentID string) error {
o.mu.RLock()
defer o.mu.RUnlock()

resp, err := o.client.DefaultApi.AttemptPromotion(
ctx,
namespaceID,
id,
deploymentID,
)

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

return nil
}
7 changes: 7 additions & 0 deletions apiclient/reauth_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,13 @@ func (tr *TransportWithReauth) EvictReplica(ctx context.Context, namespaceID str
return tr.inner.EvictReplica(ctx, namespaceID, id, deploymentID)
})
}

func (tr *TransportWithReauth) AttemptPromotion(ctx context.Context, namespaceID string, id string, deploymentID string) error {
return tr.doWithReauth(ctx, func() error {
return tr.inner.AttemptPromotion(ctx, namespaceID, id, deploymentID)
})
}

// DetachVolume wraps the inner transport's call with a reauthenticate and retry
// upon encountering an authentication error.
func (tr *TransportWithReauth) DetachVolume(ctx context.Context, namespaceID id.Namespace, volumeID id.Volume, params *DetachVolumeRequestParams) error {
Expand Down
1 change: 1 addition & 0 deletions cmd/interfaces/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ type Client interface {
SetFailureModeIntent(ctx context.Context, nsID id.Namespace, volID id.Volume, intent string, params *apiclient.SetFailureModeRequestParams) (*model.Volume, error)
SetFailureThreshold(ctx context.Context, nsID id.Namespace, volID id.Volume, threshold uint64, params *apiclient.SetFailureModeRequestParams) (*model.Volume, error)
EvictReplica(ctx context.Context, namespaceID string, id string, deploymentID string) error
AttemptPromotion(ctx context.Context, namespaceID string, id string, deploymentID string) error
}

type Displayer interface {
Expand Down
122 changes: 122 additions & 0 deletions cmd/promote/promote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package promote

import (
"context"
"errors"
"fmt"
"time"

"github.com/spf13/cobra"

"code.storageos.net/storageos/c2-cli/cmd/argwrappers"
"code.storageos.net/storageos/c2-cli/cmd/interfaces"
"code.storageos.net/storageos/c2-cli/cmd/runwrappers"
"code.storageos.net/storageos/c2-cli/pkg/health"
"code.storageos.net/storageos/c2-cli/pkg/id"
)

type promoteCommand struct {
config interfaces.ConfigProvider
client interfaces.Client

cordonTargetNode bool
}

// NewCommand configures the promote command
func NewCommand(client interfaces.Client, config interfaces.ConfigProvider) *cobra.Command {
c := &promoteCommand{
config: config,
client: client,
}

command := &cobra.Command{
Hidden: true,
Use: "promote",
Short: "promotes a volume's replica deployment to a primary",
Example: `
$ storageos promote my-namespace my-volume my-replica-id`,
Args: argwrappers.WrapInvalidArgsError(func(_ *cobra.Command, args []string) error {
if len(args) < 3 {
return errors.New("received too few args")
}
if len(args) > 3 {
return fmt.Errorf("received more args than expected: %v", args)
}
return nil
}),
RunE: func(cmd *cobra.Command, args []string) error {
run := runwrappers.Chain(
runwrappers.RunWithTimeout(config),
runwrappers.EnsureNamespaceSetWhenUseIDs(config),
runwrappers.AuthenticateClient(config, client),
)(c.runWithCtx)

return run(context.Background(), cmd, args)
},
SilenceUsage: true,
}

return command
}

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

nsID := id.Namespace(args[0])
vID := id.Volume(args[1])
dID := id.Deployment(args[2])

// A deployment has no name, so an ID will always be provided
if !useIDs {
nn, err := c.client.GetNamespaceByName(ctx, string(nsID))
if err != nil {
return err
}
nsID = nn.ID

v, err := c.client.GetVolumeByName(ctx, nsID, string(vID))
if err != nil {
return err
}
vID = v.ID
}

vol, err := c.client.GetVolume(ctx, nsID, vID)
if err != nil {
return err
}

// Do some basic sanity checking to ensure we're not about to destroy a volume
// Controlplane does the same sanity checking, but we might as well be safe
if vol.Master.Health != health.MasterOnline {
return fmt.Errorf("cannot promote deployment - master unhealthy: %v", vol.Master.Health.String())
}
for _, replica := range vol.Replicas {
if replica.Health != health.ReplicaReady || replica.Promotable == false {
return fmt.Errorf("cannot promote deployment - replica unhealthy, id: %v health: %v, promotable: %v", replica.ID, replica.Health, replica.Promotable)
}
}

err = c.client.AttemptPromotion(ctx, nsID.String(), vID.String(), dID.String())
if err != nil {
return fmt.Errorf("failed promotion attempt: %w", err)
}

for i := 0; i < 10; i++ {
time.Sleep(3 * time.Second)
vol, err := c.client.GetVolume(ctx, nsID, vID)
if err != nil {
fmt.Printf("failed getting vol: %v\n", err)
continue
}

if vol.Master.ID == dID {
fmt.Println("replica promotion successful")
break
}
}
return nil
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"code.storageos.net/storageos/c2-cli/cmd/evict"
"code.storageos.net/storageos/c2-cli/cmd/get"
"code.storageos.net/storageos/c2-cli/cmd/nfs"
"code.storageos.net/storageos/c2-cli/cmd/promote"
"code.storageos.net/storageos/c2-cli/cmd/update"
"code.storageos.net/storageos/c2-cli/config"
)
Expand Down Expand Up @@ -122,6 +123,7 @@ To be notified about stable releases and latest features, sign up at https://my.
cordon.NewUncordonCommand(os.Stdout, client, config),
versionCommand,
evict.NewCommand(client, config),
promote.NewCommand(client, config),
)

// Cobra subcommands which are not runnable and do not themselves have
Expand Down

0 comments on commit 94820e1

Please sign in to comment.