github.com/sacloud/libsacloud/v2@v2.32.3/helper/setup/retryable_setup_test.go (about) 1 // Copyright 2016-2022 The Libsacloud Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package setup 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 "time" 22 23 "github.com/sacloud/libsacloud/v2/sacloud/accessor" 24 "github.com/sacloud/libsacloud/v2/sacloud/types" 25 "github.com/stretchr/testify/require" 26 ) 27 28 type dummyIDAccessor struct { 29 id types.ID 30 } 31 32 func (d *dummyIDAccessor) GetID() types.ID { 33 return d.id 34 } 35 36 func (d *dummyIDAccessor) SetID(id types.ID) { 37 d.id = id 38 } 39 40 type dummyAvailabilityAccessor struct { 41 available types.EAvailability 42 } 43 44 func (d *dummyAvailabilityAccessor) GetAvailability() types.EAvailability { 45 return d.available 46 } 47 48 func (d *dummyAvailabilityAccessor) SetAvailability(a types.EAvailability) { 49 d.available = a 50 } 51 52 func TestRetryableSetup_Setup(t *testing.T) { 53 ctx := context.Background() 54 zone := "tk1v" 55 56 t.Run("create", func(t *testing.T) { 57 t.Run("no error", func(t *testing.T) { 58 retryable := &RetryableSetup{ 59 Create: func(context.Context, string) (id accessor.ID, e error) { 60 return &dummyIDAccessor{id: 1}, nil 61 }, 62 Read: func(ctx context.Context, zone string, id types.ID) (interface{}, error) { 63 return &dummyIDAccessor{id: 1}, nil 64 }, 65 ProvisioningRetryInterval: time.Millisecond, 66 DeleteRetryInterval: time.Millisecond, 67 PollingInterval: time.Millisecond, 68 } 69 res, err := retryable.Setup(ctx, zone) 70 71 require.NotNil(t, res) 72 require.NoError(t, err) 73 _, ok := res.(accessor.ID) 74 require.True(t, ok) 75 }) 76 77 t.Run("error", func(t *testing.T) { 78 retryable := &RetryableSetup{ 79 Create: func(context.Context, string) (accessor.ID, error) { 80 return nil, fmt.Errorf("error") 81 }, 82 Read: func(ctx context.Context, zone string, id types.ID) (interface{}, error) { 83 return &dummyIDAccessor{id: 1}, nil 84 }, 85 ProvisioningRetryInterval: time.Millisecond, 86 DeleteRetryInterval: time.Millisecond, 87 PollingInterval: time.Millisecond, 88 } 89 res, err := retryable.Setup(ctx, zone) 90 91 require.Nil(t, res) 92 require.Error(t, err) 93 }) 94 }) 95 // 96 t.Run("Retry", func(t *testing.T) { 97 t.Run("retry under max count", func(t *testing.T) { 98 // リトライ3回、ReadFuncは3回呼ばれるまではFailedが返る(4回目以降はAvailable) 99 retryable := &RetryableSetup{ 100 Create: func(context.Context, string) (id accessor.ID, e error) { 101 return &dummyIDAccessor{id: 1}, nil 102 }, 103 IsWaitForCopy: true, 104 Delete: func(context.Context, string, types.ID) error { 105 return nil 106 }, 107 RetryCount: 3, 108 Read: withErrorReadFunc(func(ctx context.Context, zone string, id types.ID) (interface{}, error) { 109 return &dummyIDAccessor{id: 1}, nil 110 }, 3), 111 ProvisioningRetryInterval: time.Millisecond, 112 DeleteRetryInterval: time.Millisecond, 113 PollingInterval: time.Millisecond, 114 } 115 116 res, err := retryable.Setup(ctx, zone) 117 118 require.NotNil(t, res) 119 require.NoError(t, err) 120 }) 121 122 t.Run("max retry count exceeded", func(t *testing.T) { 123 retryable := &RetryableSetup{ 124 Create: func(context.Context, string) (id accessor.ID, e error) { 125 return &dummyIDAccessor{id: 1}, nil 126 }, 127 IsWaitForCopy: true, 128 Delete: func(context.Context, string, types.ID) error { 129 return nil 130 }, 131 RetryCount: 3, 132 Read: withErrorReadFunc(func(ctx context.Context, zone string, id types.ID) (interface{}, error) { 133 return &dummyIDAccessor{id: 1}, nil 134 }, 5), 135 ProvisioningRetryInterval: time.Millisecond, 136 DeleteRetryInterval: time.Millisecond, 137 PollingInterval: time.Millisecond, 138 } 139 140 res, err := retryable.Setup(ctx, zone) 141 142 require.Nil(t, res) 143 require.Error(t, err) 144 _, ok := err.(MaxRetryCountExceededError) 145 require.True(t, ok) 146 }) 147 }) 148 } 149 150 func withErrorReadFunc(readFunc ReadFunc, errCount int) ReadFunc { 151 maxErr := errCount 152 return func(ctx context.Context, zone string, id types.ID) (interface{}, error) { 153 maxErr-- 154 if maxErr <= 0 { 155 return &dummyAvailabilityAccessor{available: types.Availabilities.Available}, nil 156 } 157 return &dummyAvailabilityAccessor{available: types.Availabilities.Failed}, nil 158 } 159 }