Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make timeout during waiting for uploadImage configurable #52

Merged
merged 1 commit into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/apis/migration.harvesterhci.io/v1beta1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@ type SourceInterface interface {
ClusterStatus() ClusterStatus
SecretReference() corev1.SecretReference
GetKind() string

// GetConnectionInfo returns the connection information of the Source.
GetConnectionInfo() (string, string)

// GetOptions returns the additional configuration options of the Source.
GetOptions() interface{}
}
34 changes: 31 additions & 3 deletions pkg/apis/migration.harvesterhci.io/v1beta1/openstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import (
"github.com/harvester/vm-import-controller/pkg/apis/common"
)

const (
OpenstackDefaultRetryCount = 30
OpenstackDefaultRetryDelay = 10
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

Expand All @@ -18,9 +23,10 @@ type OpenstackSource struct {
}

type OpenstackSourceSpec struct {
EndpointAddress string `json:"endpoint"`
Region string `json:"region"`
Credentials corev1.SecretReference `json:"credentials"`
EndpointAddress string `json:"endpoint"`
Region string `json:"region"`
Credentials corev1.SecretReference `json:"credentials"`
OpenstackSourceOptions `json:",inline"`
}

type OpenstackSourceStatus struct {
Expand All @@ -29,6 +35,15 @@ type OpenstackSourceStatus struct {
Conditions []common.Condition `json:"conditions,omitempty"`
}

type OpenstackSourceOptions struct {
// +optional
// The number of max. retries for uploading an image.
UploadImageRetryCount int `json:"uploadImageRetryCount,omitempty"`
// +optional
// The upload retry delay in seconds.
UploadImageRetryDelay int `json:"uploadImageRetryDelay,omitempty"`
}

func (o *OpenstackSource) ClusterStatus() ClusterStatus {
return o.Status.Status
}
Expand All @@ -44,3 +59,16 @@ func (o *OpenstackSource) GetKind() string {
func (o *OpenstackSource) GetConnectionInfo() (string, string) {
return o.Spec.EndpointAddress, o.Spec.Region
}

// GetOptions returns the sanitized OpenstackSourceOptions. This means,
// optional values are set to their default values.
func (o *OpenstackSource) GetOptions() interface{} {
options := o.Spec.OpenstackSourceOptions
if options.UploadImageRetryCount <= 0 {
options.UploadImageRetryCount = OpenstackDefaultRetryCount
}
if options.UploadImageRetryDelay <= 0 {
options.UploadImageRetryDelay = OpenstackDefaultRetryDelay
}
return options
}
4 changes: 4 additions & 0 deletions pkg/apis/migration.harvesterhci.io/v1beta1/vmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ func (v *VmwareSource) GetKind() string {
func (v *VmwareSource) GetConnectionInfo() (string, string) {
return v.Spec.EndpointAddress, v.Spec.Datacenter
}

func (v *VmwareSource) GetOptions() interface{} {
return nil
}
2 changes: 1 addition & 1 deletion pkg/controllers/migration/openstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (h *openstackHandler) OnSourceChange(_ string, o *migration.OpenstackSource
return o, fmt.Errorf("error looking up secret for openstacksource: %v", err)
}

client, err := openstack.NewClient(h.ctx, o.Spec.EndpointAddress, o.Spec.Region, secretObj)
client, err := openstack.NewClient(h.ctx, o.Spec.EndpointAddress, o.Spec.Region, secretObj, o.GetOptions().(migration.OpenstackSourceOptions))
if err != nil {
return o, fmt.Errorf("error generating openstack client for openstack migration: %s: %v", o.Name, err)
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/controllers/migration/virtualmachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,8 @@ func (h *virtualMachineHandler) generateVMO(vm *migration.VirtualMachineImport)

if source.GetKind() == strings.ToLower("openstacksource") {
endpoint, region := source.GetConnectionInfo()
return openstack.NewClient(h.ctx, endpoint, region, secret)
options := source.GetOptions().(migration.OpenstackSourceOptions)
return openstack.NewClient(h.ctx, endpoint, region, secret, options)
}

return nil, fmt.Errorf("unsupport source kind")
Expand Down
20 changes: 11 additions & 9 deletions pkg/source/openstack/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ import (
const (
NotUniqueName = "notUniqueName"
NotServerFound = "noServerFound"
defaultInterval = 10 * time.Second
defaultCount = 30
pollingTimeout = 2 * 60 * 60 // in seconds
annotationDescription = "field.cattle.io/description"
)
Expand All @@ -49,6 +47,7 @@ type Client struct {
storageClient *gophercloud.ServiceClient
computeClient *gophercloud.ServiceClient
imageClient *gophercloud.ServiceClient
options migration.OpenstackSourceOptions
}

type ExtendedVolume struct {
Expand All @@ -65,7 +64,7 @@ type ExtendedServer struct {
}

// NewClient will generate a GopherCloud client
func NewClient(ctx context.Context, endpoint string, region string, secret *corev1.Secret) (*Client, error) {
func NewClient(ctx context.Context, endpoint string, region string, secret *corev1.Secret, options migration.OpenstackSourceOptions) (*Client, error) {
username, ok := secret.Data["username"]
if !ok {
return nil, fmt.Errorf("no username provided in secret %s", secret.Name)
Expand All @@ -86,18 +85,18 @@ func NewClient(ctx context.Context, endpoint string, region string, secret *core
return nil, fmt.Errorf("no domain_name provided in secret %s", secret.Name)
}

config := &tls.Config{}
tlsClientConfig := &tls.Config{}

customCA, ok := secret.Data["ca_cert"]
if ok {
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(customCA)
config.RootCAs = caCertPool
tlsClientConfig.RootCAs = caCertPool
} else {
config.InsecureSkipVerify = true
tlsClientConfig.InsecureSkipVerify = true
}

tr := &http.Transport{TLSClientConfig: config}
tr := &http.Transport{TLSClientConfig: tlsClientConfig}

authOpts := gophercloud.AuthOptions{
IdentityEndpoint: endpoint,
Expand Down Expand Up @@ -143,6 +142,7 @@ func NewClient(ctx context.Context, endpoint string, region string, secret *core
storageClient: storageClient,
computeClient: computeClient,
imageClient: imageClient,
options: options,
}, nil
}

Expand Down Expand Up @@ -237,6 +237,8 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
"volume.snapshotID": volObj.SnapshotID,
"volume.size": volObj.Size,
"volume.status": volObj.Status,
"retryCount": c.options.UploadImageRetryCount,
"retryDelay": c.options.UploadImageRetryDelay,
}).Info("Waiting for volume to be available")

if err := volumes.WaitForStatus(c.storageClient, volObj.ID, "available", pollingTimeout); err != nil {
Expand All @@ -252,7 +254,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
}

// wait for image to be ready
for i := 0; i < defaultCount; i++ {
for i := 0; i < c.options.UploadImageRetryCount; i++ {
imgObj, err := images.Get(c.imageClient, volImage.ImageID).Extract()
if err != nil {
return fmt.Errorf("error checking status of volume image %s: %v", volImage.ImageID, err)
Expand All @@ -267,7 +269,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
"image.id": imgObj.ID,
"image.status": imgObj.Status,
}).Info("Waiting for raw image to be available")
time.Sleep(defaultInterval)
time.Sleep(time.Duration(c.options.UploadImageRetryDelay) * time.Second)
}

contents, err := imagedata.Download(c.imageClient, volImage.ImageID).Extract()
Expand Down
64 changes: 63 additions & 1 deletion pkg/source/openstack/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ func TestMain(t *testing.M) {
logrus.Fatal(err)
}

c, err = NewClient(context.TODO(), endpoint, region, s)
source := migration.OpenstackSource{}
options := source.GetOptions().(migration.OpenstackSourceOptions)

c, err = NewClient(context.TODO(), endpoint, region, s, options)
if err != nil {
logrus.Fatal(err)
}
Expand Down Expand Up @@ -138,3 +141,62 @@ func Test_generateNetworkInfo(t *testing.T) {
assert.Len(vmInterfaceDetails, 2, "expected to find 2 interfaces only")

}

func Test_ClientOptions(t *testing.T) {
assert := require.New(t)
assert.Equal(c.options.UploadImageRetryCount, migration.OpenstackDefaultRetryCount)
assert.Equal(c.options.UploadImageRetryDelay, migration.OpenstackDefaultRetryDelay)
}

func Test_SourceGetOptions(t *testing.T) {
assert := require.New(t)
testCases := []struct {
desc string
options migration.OpenstackSourceOptions
expected migration.OpenstackSourceOptions
}{
{
desc: "custom count and delay",
options: migration.OpenstackSourceOptions{
UploadImageRetryCount: 25,
UploadImageRetryDelay: 15,
},
expected: migration.OpenstackSourceOptions{
UploadImageRetryCount: 25,
UploadImageRetryDelay: 15,
},
},
{
desc: "custom count and default delay",
options: migration.OpenstackSourceOptions{
UploadImageRetryCount: 100,
},
expected: migration.OpenstackSourceOptions{
UploadImageRetryCount: 100,
UploadImageRetryDelay: migration.OpenstackDefaultRetryDelay,
},
},
{
desc: "default count and custom delay",
options: migration.OpenstackSourceOptions{
UploadImageRetryDelay: 50,
},
expected: migration.OpenstackSourceOptions{
UploadImageRetryCount: migration.OpenstackDefaultRetryCount,
UploadImageRetryDelay: 50,
},
},
}

for _, tc := range testCases {
source := migration.OpenstackSource{
Spec: migration.OpenstackSourceSpec{
OpenstackSourceOptions: tc.options,
},
}
options := source.GetOptions().(migration.OpenstackSourceOptions)

assert.Equal(options.UploadImageRetryCount, tc.expected.UploadImageRetryCount, tc.desc)
assert.Equal(options.UploadImageRetryDelay, tc.expected.UploadImageRetryDelay, tc.desc)
}
}
Loading