github.com/sacloud/iaas-api-go@v1.12.0/state_test.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 "net/http" 22 "testing" 23 "time" 24 25 "github.com/sacloud/iaas-api-go/types" 26 "github.com/stretchr/testify/require" 27 ) 28 29 type dummyState struct { 30 state interface{} 31 err error 32 } 33 34 func testStateCheckFunc(target interface{}) (bool, error) { 35 state, ok := target.(*dummyState) 36 if !ok { 37 return false, fmt.Errorf("got invalid state type: %+v", target) 38 } 39 return state.state != nil, state.err 40 } 41 42 func TestStatePollingWaiter_withStateCheckFunc(t *testing.T) { 43 t.Run("timeout", func(t *testing.T) { 44 waiter := &StatePollingWaiter{ 45 ReadFunc: func() (interface{}, error) { 46 return &dummyState{}, nil 47 }, 48 StateCheckFunc: testStateCheckFunc, 49 Timeout: 5 * time.Millisecond, 50 Interval: 1 * time.Millisecond, 51 } 52 ctx := context.Background() 53 _, err := waiter.WaitForState(ctx) 54 require.Error(t, err) 55 require.EqualError(t, err, "context deadline exceeded") 56 }) 57 58 t.Run("parent context was canceled", func(t *testing.T) { 59 waiter := &StatePollingWaiter{ 60 ReadFunc: func() (interface{}, error) { 61 return &dummyState{}, nil 62 }, 63 StateCheckFunc: testStateCheckFunc, 64 Timeout: 100 * time.Millisecond, 65 Interval: 1 * time.Millisecond, 66 } 67 ctx, cancel := context.WithCancel(context.Background()) 68 defer cancel() 69 70 _, err := waiter.WaitForState(ctx) 71 go func() { 72 time.Sleep(5 * time.Millisecond) 73 cancel() 74 }() 75 76 require.Error(t, err) 77 require.EqualError(t, err, "context deadline exceeded") 78 }) 79 80 t.Run("ReadFunc got 404", func(t *testing.T) { 81 retry := 5 82 read := 0 83 waiter := &StatePollingWaiter{ 84 NotFoundRetry: 10, 85 ReadFunc: func() (interface{}, error) { 86 read++ 87 if read < retry { 88 return nil, &apiError{responseCode: http.StatusNotFound} 89 } 90 return &dummyState{state: "done"}, nil 91 }, 92 StateCheckFunc: testStateCheckFunc, 93 Timeout: 100 * time.Millisecond, 94 Interval: 1 * time.Millisecond, 95 } 96 ctx := context.Background() 97 _, err := waiter.WaitForState(ctx) 98 99 require.NoError(t, err) 100 require.Equal(t, retry, read) 101 }) 102 103 t.Run("404 errors exceeded maximum", func(t *testing.T) { 104 retry := 5 105 read := 0 106 waiter := &StatePollingWaiter{ 107 NotFoundRetry: 2, 108 ReadFunc: func() (interface{}, error) { 109 read++ 110 if read < retry { 111 return nil, &apiError{responseCode: http.StatusNotFound} 112 } 113 return &dummyState{state: "done"}, nil 114 }, 115 StateCheckFunc: testStateCheckFunc, 116 Timeout: 100 * time.Millisecond, 117 Interval: 1 * time.Millisecond, 118 } 119 ctx := context.Background() 120 _, err := waiter.WaitForState(ctx) 121 122 require.Error(t, err) 123 require.True(t, IsNotFoundError(err)) 124 require.Equal(t, waiter.NotFoundRetry+1, read) 125 }) 126 127 t.Run("ReadFunc got unexpected error", func(t *testing.T) { 128 waiter := &StatePollingWaiter{ 129 ReadFunc: func() (interface{}, error) { 130 return &dummyState{}, errors.New("dummy") 131 }, 132 StateCheckFunc: testStateCheckFunc, 133 Timeout: 100 * time.Millisecond, 134 Interval: 1 * time.Millisecond, 135 } 136 ctx := context.Background() 137 _, err := waiter.WaitForState(ctx) 138 139 require.Error(t, err) 140 require.EqualError(t, err, "dummy") 141 }) 142 } 143 144 type dummyInstanceStatus struct { 145 status string 146 availability string 147 } 148 149 func (d *dummyInstanceStatus) GetInstanceStatus() types.EServerInstanceStatus { 150 return types.EServerInstanceStatus(d.status) 151 } 152 153 func (d *dummyInstanceStatus) SetInstanceStatus(s types.EServerInstanceStatus) { 154 d.status = string(s) 155 } 156 func (d *dummyInstanceStatus) GetAvailability() types.EAvailability { 157 return types.EAvailability(d.availability) 158 } 159 func (d *dummyInstanceStatus) SetAvailability(a types.EAvailability) { 160 d.availability = string(a) 161 } 162 163 func TestStatePollingWaiter_withTargetStates(t *testing.T) { 164 t.Run("ReadFunc got unknown state with RaiseErrorWithUnknownState=false", func(t *testing.T) { 165 counter := 0 166 max := 3 167 waiter := &StatePollingWaiter{ 168 ReadFunc: func() (interface{}, error) { 169 counter++ 170 status := "" 171 if counter > max { 172 status = string(types.ServerInstanceStatuses.Up) 173 } 174 return &dummyInstanceStatus{status: status}, nil 175 }, 176 TargetInstanceStatus: []types.EServerInstanceStatus{types.ServerInstanceStatuses.Up}, 177 Timeout: 100 * time.Millisecond, 178 Interval: 1 * time.Millisecond, 179 } 180 ctx := context.Background() 181 _, err := waiter.WaitForState(ctx) 182 183 require.NoError(t, err) 184 }) 185 t.Run("ReadFunc got unknown state with RaiseErrorWithUnknownState=true", func(t *testing.T) { 186 waiter := &StatePollingWaiter{ 187 ReadFunc: func() (interface{}, error) { 188 return &dummyInstanceStatus{status: "unknown-instance-status"}, nil 189 }, 190 TargetInstanceStatus: []types.EServerInstanceStatus{types.ServerInstanceStatuses.Up}, 191 Timeout: 100 * time.Millisecond, 192 Interval: 1 * time.Millisecond, 193 RaiseErrorWithUnknownState: true, 194 } 195 ctx := context.Background() 196 _, err := waiter.WaitForState(ctx) 197 198 require.Error(t, err) 199 require.EqualError(t, err, `got unexpected value of InstanceState: got "unknown-instance-status"`) 200 }) 201 202 t.Run("ReadFunc got unknown availability with RaiseErrorWithUnknownState=false", func(t *testing.T) { 203 counter := 0 204 max := 3 205 waiter := &StatePollingWaiter{ 206 ReadFunc: func() (interface{}, error) { 207 counter++ 208 availability := "" 209 if counter > max { 210 availability = string(types.Availabilities.Available) 211 } 212 return &dummyInstanceStatus{availability: availability}, nil 213 }, 214 TargetAvailability: []types.EAvailability{types.Availabilities.Available}, 215 Timeout: 100 * time.Millisecond, 216 Interval: 1 * time.Millisecond, 217 } 218 ctx := context.Background() 219 _, err := waiter.WaitForState(ctx) 220 221 require.NoError(t, err) 222 }) 223 224 t.Run("ReadFunc got unknown availability with RaiseErrorWithUnknownState=true", func(t *testing.T) { 225 waiter := &StatePollingWaiter{ 226 ReadFunc: func() (interface{}, error) { 227 return &dummyInstanceStatus{availability: "unknown-availability"}, nil 228 }, 229 TargetAvailability: []types.EAvailability{types.Availabilities.Available}, 230 Timeout: 100 * time.Millisecond, 231 Interval: 1 * time.Millisecond, 232 RaiseErrorWithUnknownState: true, 233 } 234 ctx := context.Background() 235 _, err := waiter.WaitForState(ctx) 236 237 require.Error(t, err) 238 require.EqualError(t, err, `got unexpected value of Availability: got "unknown-availability"`) 239 }) 240 }