@@ -17,15 +17,19 @@ limitations under the License.
17
17
package openstack
18
18
19
19
import (
20
+ "context"
20
21
"fmt"
21
22
"time"
22
23
23
24
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
24
25
25
26
"github.com/gophercloud/gophercloud"
26
27
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
28
+ "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers"
29
+ v2pools "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools"
27
30
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
28
31
"github.com/mitchellh/mapstructure"
32
+ "golang.org/x/sync/errgroup"
29
33
"k8s.io/apimachinery/pkg/util/wait"
30
34
"k8s.io/klog/v2"
31
35
"k8s.io/kops/pkg/cloudinstances"
@@ -170,6 +174,99 @@ func deleteInstanceWithID(c OpenstackCloud, instanceID string) error {
170
174
}
171
175
}
172
176
177
+ // DeregisterInstance drains a cloud instance and loadbalancers.
178
+ func (c * openstackCloud ) DeregisterInstance (i * cloudinstances.CloudInstance ) error {
179
+ return deregisterInstance (c , i .ID )
180
+ }
181
+
182
+ // deregisterInstance will drain all the loadbalancers attached to instance
183
+ func deregisterInstance (c OpenstackCloud , instanceID string ) error {
184
+ instance , err := c .GetInstance (instanceID )
185
+ if err != nil {
186
+ return err
187
+ }
188
+
189
+ // Kubernetes creates loadbalancers that member name matches to instance name
190
+ // However, kOps uses different name format in API LB which is <cluster>-<ig>
191
+ instanceName := instance .Name
192
+ kopsName := ""
193
+ ig , igok := instance .Metadata [TagKopsInstanceGroup ]
194
+ clusterName , clusterok := instance .Metadata [TagClusterName ]
195
+ if igok && clusterok {
196
+ kopsName = fmt .Sprintf ("%s-%s" , clusterName , ig )
197
+ }
198
+
199
+ lbs , err := c .ListLBs (loadbalancers.ListOpts {})
200
+ if err != nil {
201
+ return err
202
+ }
203
+ ctx := context .Background ()
204
+ eg , _ := errgroup .WithContext (ctx )
205
+ for i := range lbs {
206
+ func (lb loadbalancers.LoadBalancer ) {
207
+ eg .Go (func () error {
208
+ return drainSingleLB (c , lb , instanceName , kopsName )
209
+ })
210
+ }(lbs [i ])
211
+ }
212
+
213
+ if err := eg .Wait (); err != nil {
214
+ return fmt .Errorf ("failed to deregister instance from load balancers: %v" , err )
215
+ }
216
+
217
+ return nil
218
+ }
219
+
220
+ // drainSingleLB will drain single loadbalancer that is attached to instance
221
+ func drainSingleLB (c OpenstackCloud , lb loadbalancers.LoadBalancer , instanceName string , kopsName string ) error {
222
+ oldStats , err := c .GetLBStats (lb .ID )
223
+ if err != nil {
224
+ return err
225
+ }
226
+
227
+ draining := false
228
+ pools , err := c .ListPools (v2pools.ListOpts {
229
+ LoadbalancerID : lb .ID ,
230
+ })
231
+ if err != nil {
232
+ return err
233
+ }
234
+ for _ , pool := range pools {
235
+ members , err := c .ListPoolMembers (pool .ID , v2pools.ListMembersOpts {})
236
+ if err != nil {
237
+ return err
238
+ }
239
+ for _ , member := range members {
240
+ if member .Name == instanceName || (member .Name == kopsName && len (kopsName ) > 0 ) {
241
+ // https://docs.openstack.org/api-ref/load-balancer/v2/?expanded=update-a-member-detail
242
+ // Setting the member weight to 0 means that the member will not receive new requests but will finish any existing connections.
243
+ // This “drains” the backend member of active connections.
244
+ _ , err := c .UpdateMemberInPool (pool .ID , member .ID , v2pools.UpdateMemberOpts {
245
+ Weight : fi .Int (0 ),
246
+ })
247
+ if err != nil {
248
+ return err
249
+ }
250
+ draining = true
251
+ break
252
+ }
253
+ }
254
+ }
255
+
256
+ if draining {
257
+ // TODO: should we do somekind of loop here and check that connections are really drained?
258
+ time .Sleep (20 * time .Second )
259
+
260
+ newStats , err := c .GetLBStats (lb .ID )
261
+ if err != nil {
262
+ return err
263
+ }
264
+
265
+ klog .Infof ("Loadbalancer %s connections before draining %d and after %d" , lb .Name , oldStats .ActiveConnections , newStats .ActiveConnections )
266
+ }
267
+ return nil
268
+ }
269
+
173
270
// DetachInstance is not implemented yet. It needs to cause a cloud instance to no longer be counted against the group's size limits.
174
271
func (c * openstackCloud ) DetachInstance (i * cloudinstances.CloudInstance ) error {
175
272
return detachInstance (c , i )
0 commit comments