github.com/sacloud/libsacloud/v2@v2.32.3/helper/setup/retryable_setup.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 "errors" 20 "fmt" 21 "time" 22 23 "github.com/sacloud/libsacloud/v2/helper/defaults" 24 25 "github.com/sacloud/libsacloud/v2/sacloud" 26 "github.com/sacloud/libsacloud/v2/sacloud/accessor" 27 "github.com/sacloud/libsacloud/v2/sacloud/types" 28 ) 29 30 // MaxRetryCountExceededError リトライ最大数超過エラー 31 type MaxRetryCountExceededError error 32 33 // CreateFunc リソース作成関数 34 type CreateFunc func(ctx context.Context, zone string) (accessor.ID, error) 35 36 // ProvisionBeforeUpFunc リソース作成後、起動前のプロビジョニング関数 37 // 38 // リソース作成後に起動が行われないリソース(VPCルータなど)向け。 39 // 必要であればこの中でリソース起動処理を行う。 40 type ProvisionBeforeUpFunc func(ctx context.Context, zone string, id types.ID, target interface{}) error 41 42 // DeleteFunc リソース削除関数。 43 // 44 // リソース作成時のコピー待ちの間にリソースのAvailabilityがFailedになった場合に利用される。 45 type DeleteFunc func(ctx context.Context, zone string, id types.ID) error 46 47 // ReadFunc リソース起動待ちなどで利用するリソースのRead用Func 48 type ReadFunc func(ctx context.Context, zone string, id types.ID) (interface{}, error) 49 50 // RetryableSetup リソース作成時にコピー待ちや起動待ちが必要なリソースのビルダー。 51 // 52 // リソースのビルドの際、必要に応じてリトライ(リソースの削除&再作成)を行う。 53 type RetryableSetup struct { 54 // Create リソース作成用関数 55 Create CreateFunc 56 // IsWaitForCopy コピー待ちを行うか 57 IsWaitForCopy bool 58 // IsWaitForUp 起動待ちを行うか 59 IsWaitForUp bool 60 // ProvisionBeforeUp リソース起動前のプロビジョニング関数 61 ProvisionBeforeUp ProvisionBeforeUpFunc 62 // Delete リソース削除用関数 63 Delete DeleteFunc 64 // WaitForUp リソース起動待ち関数 65 Read ReadFunc 66 // RetryCount リトライ回数 67 RetryCount int 68 // ProvisioningRetryCount プロビジョニングリトライ回数 69 ProvisioningRetryCount int 70 // ProvisioningRetryInterval プロビジョニングリトライ間隔 71 ProvisioningRetryInterval time.Duration 72 // DeleteRetryCount 削除リトライ回数 73 DeleteRetryCount int 74 // DeleteRetryInterval 削除リトライ間隔 75 DeleteRetryInterval time.Duration 76 // sacloud.StateWaiterによるステート待ちの間隔 77 PollingInterval time.Duration 78 } 79 80 // Setup リソースのビルドを行う。必要に応じてリトライ(リソースの削除&再作成)を行う。 81 func (r *RetryableSetup) Setup(ctx context.Context, zone string) (interface{}, error) { 82 if (r.IsWaitForCopy || r.IsWaitForUp) && r.Read == nil { 83 return nil, errors.New("failed: Read is required when IsWaitForCopy or IsWaitForUp is true") 84 } 85 86 r.init() 87 88 var created interface{} 89 for r.RetryCount+1 > 0 { 90 r.RetryCount-- 91 92 // リソース作成 93 target, err := r.createResource(ctx, zone) 94 if err != nil { 95 return nil, err 96 } 97 id := target.GetID() 98 99 // コピー待ち 100 if r.IsWaitForCopy { 101 // コピー待ち、Failedになった場合はリソース削除 102 state, err := r.waitForCopyWithCleanup(ctx, zone, id) 103 if err != nil { 104 return state, err 105 } 106 if state != nil { 107 created = state 108 } 109 } else { 110 created = target 111 } 112 113 // 起動前の設定など 114 if err := r.provisionBeforeUp(ctx, zone, id, created); err != nil { 115 return created, err 116 } 117 118 // 起動待ち 119 if err := r.waitForUp(ctx, zone, id, created); err != nil { 120 return created, err 121 } 122 123 if created != nil { 124 break 125 } 126 } 127 128 if created == nil { 129 return nil, MaxRetryCountExceededError(fmt.Errorf("max retry count exceeded")) 130 } 131 return created, nil 132 } 133 134 func (r *RetryableSetup) init() { 135 if r.RetryCount <= 0 { 136 r.RetryCount = defaults.DefaultMaxRetryCount 137 } 138 if r.DeleteRetryCount <= 0 { 139 r.DeleteRetryCount = defaults.DefaultDeleteRetryCount 140 } 141 if r.DeleteRetryInterval <= 0 { 142 r.DeleteRetryInterval = defaults.DefaultDeleteWaitInterval 143 } 144 if r.ProvisioningRetryCount <= 0 { 145 r.ProvisioningRetryCount = defaults.DefaultProvisioningRetryCount 146 } 147 if r.ProvisioningRetryInterval <= 0 { 148 r.ProvisioningRetryInterval = defaults.DefaultProvisioningWaitInterval 149 } 150 if r.PollingInterval <= 0 { 151 r.PollingInterval = defaults.DefaultPollingInterval 152 } 153 } 154 155 func (r *RetryableSetup) createResource(ctx context.Context, zone string) (accessor.ID, error) { 156 if r.Create == nil { 157 return nil, fmt.Errorf("create func is required") 158 } 159 return r.Create(ctx, zone) 160 } 161 162 func (r *RetryableSetup) waitForCopyWithCleanup(ctx context.Context, zone string, id types.ID) (interface{}, error) { 163 waiter := &sacloud.StatePollingWaiter{ 164 ReadFunc: func() (interface{}, error) { 165 return r.Read(ctx, zone, id) 166 }, 167 TargetAvailability: []types.EAvailability{ 168 types.Availabilities.Available, 169 types.Availabilities.Failed, 170 }, 171 PendingAvailability: []types.EAvailability{ 172 types.Availabilities.Unknown, 173 types.Availabilities.Migrating, 174 types.Availabilities.Uploading, 175 types.Availabilities.Transferring, 176 types.Availabilities.Discontinued, 177 }, 178 PollingInterval: r.PollingInterval, 179 } 180 181 //wait 182 compChan, progChan, errChan := waiter.AsyncWaitForState(ctx) 183 var state interface{} 184 var err error 185 186 loop: 187 for { 188 select { 189 case v := <-compChan: 190 state = v 191 break loop 192 case v := <-progChan: 193 state = v 194 case e := <-errChan: 195 err = e 196 break loop 197 } 198 } 199 200 if state != nil { 201 // Availabilityを持ち、Failedになっていた場合はリソースを削除してリトライ 202 if f, ok := state.(accessor.Availability); ok && f != nil { 203 if f.GetAvailability().IsFailed() { 204 // FailedになったばかりだとDelete APIが失敗する(コピー進行中など)場合があるため、 205 // 任意の回数リトライ&待機を行う 206 for i := 0; i < r.DeleteRetryCount; i++ { 207 time.Sleep(r.DeleteRetryInterval) 208 if err = r.Delete(ctx, zone, id); err == nil { 209 break 210 } 211 } 212 213 return nil, nil 214 } 215 } 216 217 return state, nil 218 } 219 if err != nil { 220 return nil, err 221 } 222 223 return nil, nil 224 } 225 226 func (r *RetryableSetup) provisionBeforeUp(ctx context.Context, zone string, id types.ID, created interface{}) error { 227 if r.ProvisionBeforeUp != nil && created != nil { 228 var err error 229 for i := 0; i < r.ProvisioningRetryCount; i++ { 230 if err = r.ProvisionBeforeUp(ctx, zone, id, created); err == nil { 231 break 232 } 233 time.Sleep(r.ProvisioningRetryInterval) 234 } 235 return err 236 } 237 return nil 238 } 239 240 func (r *RetryableSetup) waitForUp(ctx context.Context, zone string, id types.ID, created interface{}) error { 241 if r.IsWaitForUp && created != nil { 242 waiter := &sacloud.StatePollingWaiter{ 243 ReadFunc: func() (interface{}, error) { 244 return r.Read(ctx, zone, id) 245 }, 246 TargetAvailability: []types.EAvailability{ 247 types.Availabilities.Available, 248 }, 249 PendingAvailability: []types.EAvailability{ 250 types.Availabilities.Unknown, 251 types.Availabilities.Migrating, 252 types.Availabilities.Uploading, 253 types.Availabilities.Transferring, 254 types.Availabilities.Discontinued, 255 }, 256 TargetInstanceStatus: []types.EServerInstanceStatus{ 257 types.ServerInstanceStatuses.Up, 258 }, 259 PendingInstanceStatus: []types.EServerInstanceStatus{ 260 types.ServerInstanceStatuses.Unknown, 261 types.ServerInstanceStatuses.Cleaning, 262 types.ServerInstanceStatuses.Down, 263 }, 264 PollingInterval: r.PollingInterval, 265 } 266 _, err := waiter.WaitForState(ctx) 267 return err 268 } 269 return nil 270 }