Adds support for clusterctl move
of capi and bmo resources
from ephermeral to workload cluster. Change-Id: Ib6d31282056468d5a153177dfb33ce4a55514ab3
This commit is contained in:
parent
1e6c449a8c
commit
3edf72eeb6
@ -44,6 +44,7 @@ func NewClusterCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Comm
|
||||
}
|
||||
|
||||
clusterRootCmd.AddCommand(NewInitCommand(rootSettings))
|
||||
clusterRootCmd.AddCommand(NewMoveCommand(rootSettings))
|
||||
|
||||
return clusterRootCmd
|
||||
}
|
||||
|
60
cmd/cluster/move.go
Normal file
60
cmd/cluster/move.go
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
clusterctlcmd "opendev.org/airship/airshipctl/pkg/clusterctl/cmd"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
const (
|
||||
moveLong = `
|
||||
Move Cluster API objects, provider specific objects and all dependencies to the target cluster.
|
||||
|
||||
Note: The destination cluster MUST have the required provider components installed.
|
||||
`
|
||||
|
||||
moveExample = `
|
||||
Move Cluster API objects, provider specific objects and all dependencies to the target cluster.
|
||||
|
||||
airshipctl cluster move --target-context <context name>
|
||||
`
|
||||
)
|
||||
|
||||
// NewMoveCommand creates a command to move capi and bmo resources to the target cluster
|
||||
func NewMoveCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
|
||||
var toKubeconfigContext string
|
||||
moveCmd := &cobra.Command{
|
||||
Use: "move",
|
||||
Short: "Move Cluster API objects, provider specific objects and all dependencies to the target cluster",
|
||||
Long: moveLong[1:],
|
||||
Example: moveExample,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
command, err := clusterctlcmd.NewCommand(rootSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return command.Move(toKubeconfigContext)
|
||||
},
|
||||
}
|
||||
|
||||
moveCmd.Flags().StringVar(&toKubeconfigContext, "target-context", "",
|
||||
"Context to be used within the kubeconfig file for the target cluster. If empty, current context will be used.")
|
||||
return moveCmd
|
||||
}
|
@ -7,6 +7,7 @@ Usage:
|
||||
Available Commands:
|
||||
help Help about any command
|
||||
init Deploy cluster-api provider components
|
||||
move Move Cluster API objects, provider specific objects and all dependencies to the target cluster
|
||||
|
||||
Flags:
|
||||
-h, --help help for cluster
|
||||
|
@ -26,4 +26,5 @@ such as getting status and deploying initial infrastructure.
|
||||
|
||||
* [airshipctl](airshipctl.md) - A unified entrypoint to various airship components
|
||||
* [airshipctl cluster init](airshipctl_cluster_init.md) - Deploy cluster-api provider components
|
||||
* [airshipctl cluster move](airshipctl_cluster_move.md) - Move Cluster API objects, provider specific objects and all dependencies to the target cluster
|
||||
|
||||
|
44
docs/source/cli/airshipctl_cluster_move.md
Normal file
44
docs/source/cli/airshipctl_cluster_move.md
Normal file
@ -0,0 +1,44 @@
|
||||
## airshipctl cluster move
|
||||
|
||||
Move Cluster API objects, provider specific objects and all dependencies to the target cluster
|
||||
|
||||
### Synopsis
|
||||
|
||||
Move Cluster API objects, provider specific objects and all dependencies to the target cluster.
|
||||
|
||||
Note: The destination cluster MUST have the required provider components installed.
|
||||
|
||||
|
||||
```
|
||||
airshipctl cluster move [flags]
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
|
||||
Move Cluster API objects, provider specific objects and all dependencies to the target cluster.
|
||||
|
||||
airshipctl cluster move --target-context <context name>
|
||||
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for move
|
||||
--target-context string Context to be used within the kubeconfig file for the target cluster. If empty, current context will be used.
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config")
|
||||
--debug enable verbose output
|
||||
--kubeconfig string Path to kubeconfig associated with airshipctl configuration. (default "$HOME/.airship/kubeconfig")
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [airshipctl cluster](airshipctl_cluster.md) - Manage Kubernetes clusters
|
||||
|
33
go.mod
33
go.mod
@ -3,42 +3,41 @@ module opendev.org/airship/airshipctl
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/Microsoft/go-winio v0.4.12 // indirect
|
||||
github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect
|
||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0
|
||||
github.com/docker/go-connections v0.3.0 // indirect
|
||||
github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce
|
||||
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.0.0
|
||||
github.com/go-git/go-git-fixtures/v4 v4.0.1
|
||||
github.com/go-git/go-git/v5 v5.0.0
|
||||
github.com/gorilla/mux v1.7.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
|
||||
github.com/huandu/xstrings v1.3.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/metal3-io/baremetal-operator v0.0.0-20200501205115-2c0dc9997bfa
|
||||
github.com/onsi/gomega v1.9.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/stretchr/testify v1.4.0
|
||||
k8s.io/api v0.17.3
|
||||
k8s.io/apiextensions-apiserver v0.17.3
|
||||
k8s.io/apimachinery v0.17.3
|
||||
k8s.io/cli-runtime v0.17.3
|
||||
k8s.io/client-go v11.0.0+incompatible
|
||||
k8s.io/kubectl v0.17.3
|
||||
k8s.io/api v0.17.4
|
||||
k8s.io/apiextensions-apiserver v0.17.4
|
||||
k8s.io/apimachinery v0.17.4
|
||||
k8s.io/cli-runtime v0.17.4
|
||||
k8s.io/client-go v12.0.0+incompatible
|
||||
k8s.io/kubectl v0.17.4
|
||||
opendev.org/airship/go-redfish v0.0.0-20200318103738-db034d1d753a
|
||||
opendev.org/airship/go-redfish/client v0.0.0-20200318103738-db034d1d753a
|
||||
sigs.k8s.io/cluster-api v0.3.5
|
||||
sigs.k8s.io/controller-runtime v0.5.2
|
||||
sigs.k8s.io/kustomize/api v0.3.1
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
replace github.com/russross/blackfriday => github.com/russross/blackfriday v1.5.2
|
||||
|
||||
// Required by baremetal-operator:
|
||||
replace (
|
||||
github.com/russross/blackfriday => github.com/russross/blackfriday v1.5.2
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20191114101535-6c5935290e33
|
||||
github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.3.2+incompatible // Required by OLM
|
||||
k8s.io/client-go => k8s.io/client-go v0.17.4
|
||||
)
|
||||
|
@ -34,6 +34,7 @@ type Clusterctl struct {
|
||||
|
||||
Providers []*Provider `json:"providers,omitempty"`
|
||||
InitOptions *InitOptions `json:"init-options,omitempty"`
|
||||
MoveOptions *MoveOptions `json:"move-options,omitempty"`
|
||||
}
|
||||
|
||||
// Provider is part of clusterctl config
|
||||
@ -79,3 +80,9 @@ func (c *Clusterctl) Provider(name string, providerType clusterctlv1.ProviderTyp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveOptions carries the options supported by move.
|
||||
type MoveOptions struct {
|
||||
// The namespace where the workload cluster is hosted. If unspecified, the target context's namespace is used.
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
@ -34,12 +34,14 @@ var _ Interface = &Client{}
|
||||
// Interface is abstraction to Clusterctl
|
||||
type Interface interface {
|
||||
Init(kubeconfigPath, kubeconfigContext string) error
|
||||
Move(fromKubeconfigPath, fromKubeconfigContext, toKubeconfigPath, toKubeconfigContext, namespace string) error
|
||||
}
|
||||
|
||||
// Client Implements interface to Clusterctl
|
||||
type Client struct {
|
||||
clusterctlClient clusterctlclient.Client
|
||||
initOptions clusterctlclient.InitOptions
|
||||
moveOptions clusterctlclient.MoveOptions
|
||||
}
|
||||
|
||||
// NewClient returns instance of clusterctl client
|
||||
|
168
pkg/clusterctl/client/move.go
Normal file
168
pkg/clusterctl/client/move.go
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
|
||||
bmoapis "github.com/metal3-io/baremetal-operator/pkg/apis"
|
||||
bmh "github.com/metal3-io/baremetal-operator/pkg/apis/metal3/v1alpha1"
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
clusterctlclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client"
|
||||
"sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func init() {
|
||||
//nolint:errcheck
|
||||
bmoapis.AddToScheme(cluster.Scheme)
|
||||
}
|
||||
|
||||
// Move implements interface to Clusterctl
|
||||
func (c *Client) Move(fromKubeconfigPath, fromKubeconfigContext,
|
||||
toKubeconfigPath, toKubeconfigContext, namespace string) error {
|
||||
ctx := context.TODO()
|
||||
var err error
|
||||
// ephemeral cluster client
|
||||
pFrom := cluster.New(cluster.Kubeconfig{
|
||||
Path: fromKubeconfigPath,
|
||||
Context: fromKubeconfigContext}, nil).Proxy()
|
||||
cFrom, err := pFrom.NewClient()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create ephemeral cluster client")
|
||||
}
|
||||
// target cluster client
|
||||
pTo := cluster.New(cluster.Kubeconfig{
|
||||
Path: toKubeconfigPath,
|
||||
Context: toKubeconfigContext}, nil).Proxy()
|
||||
cTo, err := pTo.NewClient()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create target cluster client")
|
||||
}
|
||||
// If namespace is empty, try to detect it.
|
||||
if namespace == "" {
|
||||
var currentNamespace string
|
||||
currentNamespace, err = pFrom.CurrentNamespace()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
namespace = currentNamespace
|
||||
}
|
||||
// Pause
|
||||
err = pauseUnpauseBMHs(ctx, cFrom, namespace, true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to pause BareMetalHost objects")
|
||||
}
|
||||
|
||||
// clusterctl move
|
||||
c.moveOptions = clusterctlclient.MoveOptions{
|
||||
FromKubeconfig: clusterctlclient.Kubeconfig{Path: fromKubeconfigPath, Context: fromKubeconfigContext},
|
||||
ToKubeconfig: clusterctlclient.Kubeconfig{Path: toKubeconfigPath, Context: toKubeconfigContext},
|
||||
Namespace: namespace,
|
||||
}
|
||||
err = c.clusterctlClient.Move(c.moveOptions)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error during clusterctl move")
|
||||
}
|
||||
// Update BMH Status
|
||||
err = copyBMHStatus(ctx, cFrom, cTo, namespace)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to copy BareMetalHost Status")
|
||||
}
|
||||
// Unpause
|
||||
err = pauseUnpauseBMHs(ctx, cFrom, namespace, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to unpause BareMetalHost objects")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// copyBMHStatus will copy the BareMetalHost Status field from a specific
|
||||
// cluser to a target cluster.
|
||||
func copyBMHStatus(ctx context.Context, cFrom client.Client, cTo client.Client, namespace string) error {
|
||||
fromHosts, err := getBMHs(ctx, cFrom, namespace)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to list BareMetalHost objects")
|
||||
}
|
||||
toHosts, err := getBMHs(ctx, cTo, namespace)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to list BMH objects")
|
||||
}
|
||||
// Copy the Status field from old BMH to new BMH
|
||||
log.Debugf("Copying BareMetalHost status to target cluster")
|
||||
for _, toHost := range toHosts.Items {
|
||||
var found bool
|
||||
t := metav1.Now()
|
||||
for _, fromHost := range fromHosts.Items {
|
||||
if fromHost.Name == toHost.Name {
|
||||
toHost.Status = fromHost.Status
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return errors.Errorf("BMH with the same name %s/%s not found in the source cluster", toHost.Name, namespace)
|
||||
}
|
||||
toHost.Status.LastUpdated = &t
|
||||
err = cTo.Status().Update(ctx, &toHost)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update BareMetalHost status")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pauseUnpauseBMHs will add/remove the pause annotation from the
|
||||
// BareMetalHost objects.
|
||||
func pauseUnpauseBMHs(ctx context.Context, crClient client.Client, namespace string, pause bool) error {
|
||||
hosts, err := getBMHs(ctx, crClient, namespace)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to list BMH objects")
|
||||
}
|
||||
for _, host := range hosts.Items {
|
||||
annotations := host.GetAnnotations()
|
||||
if annotations == nil {
|
||||
host.Annotations = map[string]string{}
|
||||
}
|
||||
if pause {
|
||||
log.Debugf("Pausing BareMetalHost object %s/%s", host.Name, namespace)
|
||||
host.Annotations[bmh.PausedAnnotation] = "true"
|
||||
} else {
|
||||
log.Debugf("Unpausing BareMetalHost object %s/%s", host.Name, namespace)
|
||||
delete(host.Annotations, bmh.PausedAnnotation)
|
||||
}
|
||||
if err := crClient.Update(ctx, &host); err != nil {
|
||||
return errors.Wrapf(err, "error updating BareMetalHost %q %s/%s",
|
||||
host.GroupVersionKind(), host.GetNamespace(), host.GetName())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getBMHs will return all BareMetalHost objects in the specified namepace.
|
||||
// It also checks to see if the BareMetalHost resource is installed, if not,
|
||||
// it will return false.
|
||||
func getBMHs(ctx context.Context, crClient client.Client, namespace string) (bmh.BareMetalHostList, error) {
|
||||
hosts := bmh.BareMetalHostList{}
|
||||
opts := &client.ListOptions{
|
||||
Namespace: namespace,
|
||||
}
|
||||
err := crClient.List(ctx, &hosts, opts)
|
||||
return hosts, err
|
||||
}
|
344
pkg/clusterctl/client/move_test.go
Normal file
344
pkg/clusterctl/client/move_test.go
Normal file
@ -0,0 +1,344 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
bmoapis "github.com/metal3-io/baremetal-operator/pkg/apis"
|
||||
bmh "github.com/metal3-io/baremetal-operator/pkg/apis/metal3/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
var bmh1 = &bmh.BareMetalHost{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "metal3.io/v1alpha1",
|
||||
Kind: "BareMetalHost",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bmh1",
|
||||
Namespace: "ns1",
|
||||
},
|
||||
Spec: bmh.BareMetalHostSpec{
|
||||
Online: false,
|
||||
BootMACAddress: "00:2e:30:d7:11:19",
|
||||
},
|
||||
Status: bmh.BareMetalHostStatus{
|
||||
HardwareProfile: "bmh1-hw-profile",
|
||||
},
|
||||
}
|
||||
|
||||
var bmh2 = &bmh.BareMetalHost{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "metal3.io/v1alpha1",
|
||||
Kind: "BareMetalHost",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bmh2",
|
||||
Namespace: "ns1",
|
||||
},
|
||||
Spec: bmh.BareMetalHostSpec{
|
||||
Online: false,
|
||||
BootMACAddress: "01:23:45:67:89:ab",
|
||||
},
|
||||
Status: bmh.BareMetalHostStatus{
|
||||
HardwareProfile: "bmh2-hw-profile",
|
||||
},
|
||||
}
|
||||
|
||||
func newClientWithBMHObject() client.Client {
|
||||
scheme := runtime.NewScheme()
|
||||
//nolint:errcheck
|
||||
bmoapis.AddToScheme(scheme)
|
||||
return fake.NewFakeClientWithScheme(scheme, bmh1)
|
||||
}
|
||||
|
||||
func newClientWithTwoBMHObjects() client.Client {
|
||||
scheme := runtime.NewScheme()
|
||||
//nolint:errcheck
|
||||
bmoapis.AddToScheme(scheme)
|
||||
return fake.NewFakeClientWithScheme(scheme, bmh1, bmh2)
|
||||
}
|
||||
|
||||
func newClientWithNoBMHObject() client.Client {
|
||||
scheme := runtime.NewScheme()
|
||||
//nolint:errcheck
|
||||
bmoapis.AddToScheme(scheme)
|
||||
return fake.NewFakeClientWithScheme(scheme)
|
||||
}
|
||||
|
||||
func Test_move_getBMHs(t *testing.T) {
|
||||
type args struct {
|
||||
c client.Client
|
||||
namespace string
|
||||
}
|
||||
type want struct {
|
||||
bmhList bmh.BareMetalHostList
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "returns a BareMetalHost object",
|
||||
args: args{
|
||||
c: newClientWithBMHObject(),
|
||||
namespace: "ns1",
|
||||
},
|
||||
wantErr: false,
|
||||
want: want{
|
||||
bmhList: bmh.BareMetalHostList{
|
||||
Items: []bmh.BareMetalHost{*bmh1},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "returns multiple BareMetalHost object",
|
||||
args: args{
|
||||
c: newClientWithTwoBMHObjects(),
|
||||
namespace: "ns1",
|
||||
},
|
||||
wantErr: false,
|
||||
want: want{
|
||||
bmhList: bmh.BareMetalHostList{
|
||||
Items: []bmh.BareMetalHost{*bmh1, *bmh2},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "returns an empty list of BareMetalHost objects",
|
||||
args: args{
|
||||
c: newClientWithNoBMHObject(),
|
||||
namespace: "ns2",
|
||||
},
|
||||
wantErr: false,
|
||||
want: want{
|
||||
bmhList: bmh.BareMetalHostList{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
bmhList, err := getBMHs(context.TODO(), tt.args.c, tt.args.namespace)
|
||||
if tt.wantErr {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
return
|
||||
}
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(len(bmhList.Items)).To(BeEquivalentTo(len(tt.want.bmhList.Items)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_move_pauseUnpauseBMHs(t *testing.T) {
|
||||
type args struct {
|
||||
c client.Client
|
||||
namespace string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "pause and unpause a single BareMetalHost object",
|
||||
args: args{
|
||||
c: newClientWithBMHObject(),
|
||||
namespace: "ns1",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "pause and unpause multiple BareMetalHost objects",
|
||||
args: args{
|
||||
c: newClientWithTwoBMHObjects(),
|
||||
namespace: "ns1",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "pause and unpause should do nothing when there is no BareMetalHost object present",
|
||||
args: args{
|
||||
c: newClientWithNoBMHObject(),
|
||||
namespace: "ns2",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
err := pauseUnpauseBMHs(context.TODO(), tt.args.c, tt.args.namespace, true)
|
||||
if tt.wantErr {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
return
|
||||
}
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
bmhList, err := getBMHs(context.TODO(), tt.args.c, tt.args.namespace)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
for _, host := range bmhList.Items {
|
||||
g.Expect(host.Annotations[bmh.PausedAnnotation]).To(Equal("true"))
|
||||
}
|
||||
err = pauseUnpauseBMHs(context.TODO(), tt.args.c, tt.args.namespace, false)
|
||||
if tt.wantErr {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
return
|
||||
}
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
bmhList, err = getBMHs(context.TODO(), tt.args.c, tt.args.namespace)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
for _, host := range bmhList.Items {
|
||||
_, present := host.Annotations[bmh.PausedAnnotation]
|
||||
g.Expect(present).To(Equal(false))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var bmh1NoStatus = &bmh.BareMetalHost{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "metal3.io/v1alpha1",
|
||||
Kind: "BareMetalHost",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bmh1",
|
||||
Namespace: "ns1",
|
||||
},
|
||||
Spec: bmh.BareMetalHostSpec{
|
||||
Online: false,
|
||||
BootMACAddress: "00:2e:30:d7:11:19",
|
||||
},
|
||||
}
|
||||
|
||||
var bmh2NoStatus = &bmh.BareMetalHost{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "metal3.io/v1alpha1",
|
||||
Kind: "BareMetalHost",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bmh2",
|
||||
Namespace: "ns1",
|
||||
},
|
||||
Spec: bmh.BareMetalHostSpec{
|
||||
Online: false,
|
||||
BootMACAddress: "01:23:45:67:89:ab",
|
||||
},
|
||||
}
|
||||
|
||||
func newClientFromCluster() client.Client {
|
||||
scheme := runtime.NewScheme()
|
||||
//nolint:errcheck
|
||||
bmoapis.AddToScheme(scheme)
|
||||
return fake.NewFakeClientWithScheme(scheme, bmh1, bmh2)
|
||||
}
|
||||
|
||||
func newClientToCluster() client.Client {
|
||||
scheme := runtime.NewScheme()
|
||||
//nolint:errcheck
|
||||
bmoapis.AddToScheme(scheme)
|
||||
return fake.NewFakeClientWithScheme(scheme, bmh1NoStatus, bmh2NoStatus)
|
||||
}
|
||||
|
||||
func Test_move_copyBMHStatus(t *testing.T) {
|
||||
type args struct {
|
||||
cFrom client.Client
|
||||
cTo client.Client
|
||||
namespace string
|
||||
}
|
||||
type want struct {
|
||||
bmhList bmh.BareMetalHostList
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "copies the status field of multiple BareMetalHost objects",
|
||||
args: args{
|
||||
cFrom: newClientFromCluster(),
|
||||
cTo: newClientToCluster(),
|
||||
namespace: "ns1",
|
||||
},
|
||||
wantErr: false,
|
||||
want: want{
|
||||
bmhList: bmh.BareMetalHostList{
|
||||
Items: []bmh.BareMetalHost{*bmh1, *bmh2},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no copy occurs b/c no BareMetalHost objects are present",
|
||||
args: args{
|
||||
cFrom: newClientWithNoBMHObject(),
|
||||
cTo: newClientWithNoBMHObject(),
|
||||
namespace: "ns1",
|
||||
},
|
||||
wantErr: false,
|
||||
want: want{
|
||||
bmhList: bmh.BareMetalHostList{
|
||||
Items: []bmh.BareMetalHost{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error should occur b/c BareMetalHost does not exist in the source cluster",
|
||||
args: args{
|
||||
cFrom: newClientWithNoBMHObject(),
|
||||
cTo: newClientToCluster(),
|
||||
namespace: "ns1",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
err := copyBMHStatus(context.TODO(), tt.args.cFrom, tt.args.cTo, tt.args.namespace)
|
||||
if tt.wantErr {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
return
|
||||
}
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
bmhList, err := getBMHs(context.TODO(), tt.args.cTo, tt.args.namespace)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
NEXTHOST:
|
||||
for _, host := range bmhList.Items {
|
||||
for _, wantHost := range tt.want.bmhList.Items {
|
||||
if host.Name == wantHost.Name {
|
||||
g.Expect(host.Status.HardwareProfile).To(Equal(wantHost.Status.HardwareProfile))
|
||||
continue NEXTHOST
|
||||
}
|
||||
}
|
||||
t.Errorf("unexpected host %s", host.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@ func NewCommand(rs *environment.AirshipCTLSettings) (*Command, error) {
|
||||
return nil, err
|
||||
}
|
||||
kubeConfigPath := rs.Config.KubeConfigPath()
|
||||
|
||||
return &Command{
|
||||
kubeconfigPath: kubeConfigPath,
|
||||
documentRoot: root,
|
||||
@ -94,3 +95,12 @@ func getBundle(conf *config.Config) (document.Bundle, error) {
|
||||
}
|
||||
return document.NewBundleByPath(path)
|
||||
}
|
||||
|
||||
// Move runs clusterctl move
|
||||
func (c *Command) Move(toKubeconfigContext string) error {
|
||||
if c.options.MoveOptions != nil {
|
||||
return c.client.Move(c.kubeconfigPath, c.kubeconfigContext,
|
||||
c.kubeconfigPath, toKubeconfigContext, c.options.MoveOptions.Namespace)
|
||||
}
|
||||
return c.client.Move(c.kubeconfigPath, c.kubeconfigContext, c.kubeconfigPath, toKubeconfigContext, "")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user