github.com/sacloud/iaas-api-go@v1.12.0/state.go (about) 1 // Copyright 2022-2023 The sacloud/iaas-api-go 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 iaas 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "time" 22 23 "github.com/sacloud/iaas-api-go/accessor" 24 "github.com/sacloud/iaas-api-go/defaults" 25 "github.com/sacloud/iaas-api-go/types" 26 "github.com/sacloud/packages-go/wait" 27 ) 28 29 // UnexpectedAvailabilityError 予期しないAvailabilityとなった場合のerror 30 type UnexpectedAvailabilityError struct { 31 // Err エラー詳細 32 Err error 33 } 34 35 // Error errorインターフェース実装 36 func (e *UnexpectedAvailabilityError) Error() string { 37 return fmt.Sprintf("resource returns unexpected availability value: %s", e.Err.Error()) 38 } 39 40 // UnexpectedInstanceStatusError 予期しないInstanceStatusとなった場合のerror 41 type UnexpectedInstanceStatusError struct { 42 // Err エラー詳細 43 Err error 44 } 45 46 // Error errorインターフェース実装 47 func (e *UnexpectedInstanceStatusError) Error() string { 48 return fmt.Sprintf("resource returns unexpected instance status value: %s", e.Err.Error()) 49 } 50 51 var _ wait.StateWaiter = (*StatePollingWaiter)(nil) // StatePollingWaiterでIaaS固有の事情を考慮したwait.StateWaiterを実装する 52 53 // StatePollingWaiter ポーリングによりリソースの状態が変わるまで待機する 54 type StatePollingWaiter struct { 55 // ReadFunc 対象リソースの状態を取得するためのfunc 56 ReadFunc wait.StateReadFunc 57 58 // StateCheckFunc ReadFuncで得たリソースの情報を元に待ちを継続するかの判定を行うためのfunc 59 StateCheckFunc wait.StateCheckFunc 60 61 // Timeout タイムアウト 62 Timeout time.Duration // タイムアウト 63 64 // Interval ポーリング間隔 65 Interval time.Duration 66 67 // NotFoundRetry Readで404が返ってきた場合のリトライ回数 68 // 69 // アプライアンスなどの一部のリソースでは作成~起動完了までの間に404を返すことがある。 70 // これに対応するためこのフィールドにて404発生の許容回数を指定可能にする。 71 NotFoundRetry int 72 73 // TargetAvailability 対象リソースのAvailabilityがこの状態になった場合になるまで待つ 74 // 75 // この値を指定する場合、ReadFuncにてAvailabilityHolderを返す必要がある。 76 // AvailabilityがTargetAvailabilityとPendingAvailabilityで指定されていない状態になった場合はUnexpectedAvailabilityErrorを返す 77 // 78 // TargetAvailability(Pending)とTargetInstanceState(Pending)の両方が指定された場合は両方を満たすまで待つ 79 // StateCheckFuncとの併用は不可。併用した場合はpanicする。 80 TargetAvailability []types.EAvailability 81 82 // PendingAvailability 対象リソースのAvailabilityがこの状態になった場合は待ちを継続する。 83 // 84 // 詳細はTargetAvailabilityのコメントを参照 85 PendingAvailability []types.EAvailability 86 87 // TargetInstanceStatus 対象リソースのInstanceStatusがこの状態になった場合になるまで待つ 88 // 89 // この値を指定する場合、ReadFuncにてInstanceStatusHolderを返す必要がある。 90 // InstanceStatusがTargetInstanceStatusとPendingInstanceStatusで指定されていない状態になった場合はUnexpectedInstanceStatusErrorを返す 91 // 92 // TargetAvailabilityとTargetInstanceStateの両方が指定された場合は両方を満たすまで待つ 93 // 94 // StateCheckFuncとの併用は不可。併用した場合はpanicする。 95 TargetInstanceStatus []types.EServerInstanceStatus 96 97 // PendingInstanceStatus 対象リソースのInstanceStatusがこの状態になった場合は待ちを継続する。 98 // 99 // 詳細はTargetInstanceStatusのコメントを参照 100 PendingInstanceStatus []types.EServerInstanceStatus 101 102 // RaiseErrorWithUnknownState State(AvailabilityとInstanceStatus)が予期しない値だった場合にエラーとするか 103 RaiseErrorWithUnknownState bool 104 } 105 106 // WaitForState リソースが指定の状態になるまで待つ 107 func (w *StatePollingWaiter) WaitForState(ctx context.Context) (interface{}, error) { 108 c, p, e := w.WaitForStateAsync(ctx) 109 for { 110 select { 111 case <-ctx.Done(): 112 return nil, ctx.Err() 113 case lastState := <-c: 114 return lastState, nil 115 case <-p: 116 // noop 117 case err := <-e: 118 return nil, err 119 } 120 } 121 } 122 123 // WaitForStateAsync リソースが指定の状態になるまで待つ 124 func (w *StatePollingWaiter) WaitForStateAsync(ctx context.Context) (<-chan interface{}, <-chan interface{}, <-chan error) { 125 w.validateFields() 126 if w.Timeout == time.Duration(0) { 127 w.Timeout = defaults.DefaultStatePollingTimeout 128 } 129 if w.Interval == time.Duration(0) { 130 w.Interval = defaults.DefaultStatePollingInterval 131 } 132 133 waiter := wait.PollingWaiter{ 134 ReadFunc: w.readFunc(), 135 StateCheckFunc: w.stateCheckFunc, 136 Timeout: w.Timeout, 137 Interval: w.Interval, 138 } 139 140 return waiter.WaitForStateAsync(ctx) 141 } 142 143 func (w *StatePollingWaiter) readFunc() func() (interface{}, error) { 144 notFoundCounter := w.NotFoundRetry 145 return func() (interface{}, error) { 146 read, err := w.ReadFunc() 147 if err != nil { 148 if IsNotFoundError(err) { 149 notFoundCounter-- 150 if notFoundCounter >= 0 { 151 return nil, nil 152 } 153 } 154 return nil, err 155 } 156 return read, err 157 } 158 } 159 160 func (w *StatePollingWaiter) validateFields() { 161 if w.ReadFunc == nil { 162 panic(errors.New("StatePollingWaiter has invalid setting: ReadFunc is required")) 163 } 164 165 if w.StateCheckFunc != nil && (len(w.TargetAvailability) > 0 || len(w.TargetInstanceStatus) > 0) { 166 panic(errors.New("StatePollingWaiter has invalid setting: StateCheckFunc and TargetAvailability/TargetInstanceStatus can not use together")) 167 } 168 169 if w.StateCheckFunc == nil && len(w.TargetAvailability) == 0 && len(w.TargetInstanceStatus) == 0 { 170 panic(errors.New("StatePollingWaiter has invalid setting: TargetAvailability or TargetInstanceState must have least 1 items when StateCheckFunc is not set")) 171 } 172 } 173 174 func (w *StatePollingWaiter) stateCheckFunc(state interface{}) (bool, error) { 175 if w.StateCheckFunc != nil { 176 return w.StateCheckFunc(state) 177 } 178 179 availabilityHolder, hasAvailability := state.(accessor.Availability) 180 instanceStateHolder, hasInstanceState := state.(accessor.InstanceStatus) 181 182 switch { 183 case hasAvailability && hasInstanceState: 184 185 res1, err := w.handleAvailability(availabilityHolder) 186 if err != nil { 187 return false, err 188 } 189 res2, err := w.handleInstanceState(instanceStateHolder) 190 if err != nil { 191 return false, err 192 } 193 return res1 && res2, nil 194 195 case hasAvailability: 196 return w.handleAvailability(availabilityHolder) 197 case hasInstanceState: 198 return w.handleInstanceState(instanceStateHolder) 199 default: 200 // どちらのインターフェースも実装していない場合、stateが存在するだけでtrueとする 201 return true, nil 202 } 203 } 204 205 func (w *StatePollingWaiter) handleAvailability(state accessor.Availability) (bool, error) { 206 if len(w.TargetAvailability) == 0 { 207 return true, nil 208 } 209 v := state.GetAvailability() 210 switch { 211 case w.isInAvailability(v, w.TargetAvailability): 212 return true, nil 213 case w.isInAvailability(v, w.PendingAvailability): 214 return false, nil 215 default: 216 var err error 217 if w.RaiseErrorWithUnknownState { 218 err = fmt.Errorf("got unexpected value of Availability: got %q", v) 219 } 220 return false, err 221 } 222 } 223 224 func (w *StatePollingWaiter) handleInstanceState(state accessor.InstanceStatus) (bool, error) { 225 if len(w.TargetInstanceStatus) == 0 { 226 return true, nil 227 } 228 v := state.GetInstanceStatus() 229 switch { 230 case w.isInInstanceStatus(v, w.TargetInstanceStatus): 231 return true, nil 232 case w.isInInstanceStatus(v, w.PendingInstanceStatus): 233 return false, nil 234 default: 235 var err error 236 if w.RaiseErrorWithUnknownState { 237 err = fmt.Errorf("got unexpected value of InstanceState: got %q", v) 238 } 239 return false, err 240 } 241 } 242 243 func (w *StatePollingWaiter) isInAvailability(v types.EAvailability, conditions []types.EAvailability) bool { 244 for _, cond := range conditions { 245 if v == cond { 246 return true 247 } 248 } 249 return false 250 } 251 252 func (w *StatePollingWaiter) isInInstanceStatus(v types.EServerInstanceStatus, conditions []types.EServerInstanceStatus) bool { 253 for _, cond := range conditions { 254 if v == cond { 255 return true 256 } 257 } 258 return false 259 }