-
Notifications
You must be signed in to change notification settings - Fork 303
/
utils.go
398 lines (350 loc) · 12.1 KB
/
utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"k8s.io/klog"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
"google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
api_v1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/types"
listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/ingress-gce/pkg/annotations"
"k8s.io/ingress-gce/pkg/flags"
)
const (
// Add used to record additions in a sync pool.
Add = iota
// Remove used to record removals from a sync pool.
Remove
// Sync used to record syncs of a sync pool.
Sync
// Get used to record Get from a sync pool.
Get
// Create used to record creations in a sync pool.
Create
// Update used to record updates in a sync pool.
Update
// Delete used to record deltions from a sync pool.
Delete
// AddInstances used to record a call to AddInstances.
AddInstances
// RemoveInstances used to record a call to RemoveInstances.
RemoveInstances
// LabelNodeRoleMaster specifies that a node is a master
// This is a duplicate definition of the constant in:
// kubernetes/kubernetes/pkg/controller/service/service_controller.go
LabelNodeRoleMaster = "node-role.kubernetes.io/master"
// LabelNodeRoleExcludeBalancer specifies that a node should be excluded from load-balancing
// This is a duplicate definition of the constant in:
// kubernetes/kubernetes/pkg/controller/service/service_controller.go
// This label is feature-gated in kubernetes/kubernetes but we do not have feature gates
// This will need to be updated after the end of the alpha
LabelNodeRoleExcludeBalancer = "alpha.service-controller.kubernetes.io/exclude-balancer"
)
// FakeGoogleAPIForbiddenErr creates a Forbidden error with type googleapi.Error
func FakeGoogleAPIForbiddenErr() *googleapi.Error {
return &googleapi.Error{Code: http.StatusForbidden}
}
// FakeGoogleAPINotFoundErr creates a NotFound error with type googleapi.Error
func FakeGoogleAPINotFoundErr() *googleapi.Error {
return &googleapi.Error{Code: http.StatusNotFound}
}
// IsHTTPErrorCode checks if the given error matches the given HTTP Error code.
// For this to work the error must be a googleapi Error.
func IsHTTPErrorCode(err error, code int) bool {
apiErr, ok := err.(*googleapi.Error)
return ok && apiErr.Code == code
}
// ToNamespacedName returns a types.NamespacedName struct parsed from namespace/name.
func ToNamespacedName(s string) (r types.NamespacedName, err error) {
parts := strings.Split(s, "/")
if len(parts) != 2 {
return r, fmt.Errorf("service should take the form 'namespace/name': %q", s)
}
return types.NamespacedName{
Namespace: parts[0],
Name: parts[1],
}, nil
}
// IgnoreHTTPNotFound returns the passed err if it's not a GoogleAPI error
// with a NotFound status code.
func IgnoreHTTPNotFound(err error) error {
if err != nil && IsHTTPErrorCode(err, http.StatusNotFound) {
return nil
}
return err
}
// IsInUsedByError returns true if the resource is being used by another GCP resource
func IsInUsedByError(err error) bool {
apiErr, ok := err.(*googleapi.Error)
if !ok || apiErr.Code != http.StatusBadRequest {
return false
}
return strings.Contains(apiErr.Message, "being used by")
}
// IsNotFoundError returns true if the resource does not exist
func IsNotFoundError(err error) bool {
return IsHTTPErrorCode(err, http.StatusNotFound)
}
// IsForbiddenError returns true if the operation was forbidden
func IsForbiddenError(err error) bool {
return IsHTTPErrorCode(err, http.StatusForbidden)
}
// TrimFieldsEvenly trims the fields evenly and keeps the total length
// <= max. Truncation is spread in ratio with their original length,
// meaning smaller fields will be truncated less than longer ones.
func TrimFieldsEvenly(max int, fields ...string) []string {
if max <= 0 {
return fields
}
total := 0
for _, s := range fields {
total += len(s)
}
if total <= max {
return fields
}
// Distribute truncation evenly among the fields.
excess := total - max
remaining := max
var lengths []int
for _, s := range fields {
// Scale truncation to shorten longer fields more than ones that are already short.
l := len(s) - len(s)*excess/total - 1
lengths = append(lengths, l)
remaining -= l
}
// Add fractional space that was rounded down.
for i := 0; i < remaining; i++ {
lengths[i]++
}
var ret []string
for i, l := range lengths {
ret = append(ret, fields[i][:l])
}
return ret
}
// PrettyJson marshals an object in a human-friendly format.
func PrettyJson(data interface{}) (string, error) {
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", "\t")
err := encoder.Encode(data)
if err != nil {
return "", err
}
return buffer.String(), nil
}
// KeyName returns the name portion from a full or partial GCP resource URL.
// Example:
// Input: https://googleapis.com/v1/compute/projects/my-project/global/backendServices/my-backend
// Output: my-backend
func KeyName(url string) (string, error) {
id, err := cloud.ParseResourceURL(url)
if err != nil {
return "", err
}
if id.Key == nil {
// Resource is projects
return id.ProjectID, nil
}
return id.Key.Name, nil
}
// RelativeResourceName returns the project, location, resource, and name from a full/partial GCP
// resource URL. This removes the endpoint prefix and version.
// Example:
// Input: https://googleapis.com/v1/compute/projects/my-project/global/backendServices/my-backend
// Output: projects/my-project/global/backendServices/my-backend
func RelativeResourceName(url string) (string, error) {
resID, err := cloud.ParseResourceURL(url)
if err != nil {
return "", err
}
return resID.RelativeResourceName(), nil
}
// ResourcePath returns the location, resource and name portion from a
// full or partial GCP resource URL. This removes the endpoint prefix, version, and project.
// Example:
// Input: https://googleapis.com/v1/compute/projects/my-project/global/backendServices/my-backend
// Output: global/backendServices/my-backend
func ResourcePath(url string) (string, error) {
resID, err := cloud.ParseResourceURL(url)
if err != nil {
return "", err
}
return resID.ResourcePath(), nil
}
// EqualResourcePaths returns true if a and b have equal ResourcePaths. Resource paths
// entail the location, resource type, and resource name.
func EqualResourcePaths(a, b string) bool {
aPath, err := ResourcePath(a)
if err != nil {
return false
}
bPath, err := ResourcePath(b)
if err != nil {
return false
}
return aPath == bPath
}
// EqualResourceIDs returns true if a and b have equal ResourceIDs which entail the project,
// location, resource type, and resource name.
func EqualResourceIDs(a, b string) bool {
aId, err := cloud.ParseResourceURL(a)
if err != nil {
return false
}
bId, err := cloud.ParseResourceURL(b)
if err != nil {
return false
}
return aId.Equal(bId)
}
// IGLinks returns a list of links extracted from the passed in list of
// compute.InstanceGroup's.
func IGLinks(igs []*compute.InstanceGroup) (igLinks []string) {
for _, ig := range igs {
igLinks = append(igLinks, ig.SelfLink)
}
return
}
// IsGCEIngress returns true if the Ingress matches the class managed by this
// controller.
func IsGCEIngress(ing *extensions.Ingress) bool {
class := annotations.FromIngress(ing).IngressClass()
if flags.F.IngressClass == "" {
return class == "" || class == annotations.GceIngressClass
}
return class == flags.F.IngressClass
}
// IsGCEMultiClusterIngress returns true if the given Ingress has
// ingress.class annotation set to "gce-multi-cluster".
func IsGCEMultiClusterIngress(ing *extensions.Ingress) bool {
class := annotations.FromIngress(ing).IngressClass()
return class == annotations.GceMultiIngressClass
}
// IsGLBCIngress returns true if the given Ingress should be processed by GLBC
func IsGLBCIngress(ing *extensions.Ingress) bool {
return IsGCEIngress(ing) || IsGCEMultiClusterIngress(ing)
}
// GetReadyNodeNames returns names of schedulable, ready nodes from the node lister
// It also filters out masters and nodes excluded from load-balancing
// TODO(rramkumar): Add a test for this.
func GetReadyNodeNames(lister listers.NodeLister) ([]string, error) {
var nodeNames []string
nodes, err := lister.ListWithPredicate(GetNodeConditionPredicate())
if err != nil {
return nodeNames, err
}
for _, n := range nodes {
nodeNames = append(nodeNames, n.Name)
}
return nodeNames, nil
}
// NodeIsReady returns true if a node contains at least one condition of type "Ready"
func NodeIsReady(node *api_v1.Node) bool {
for i := range node.Status.Conditions {
condition := &node.Status.Conditions[i]
if condition.Type == api_v1.NodeReady {
return condition.Status == api_v1.ConditionTrue
}
}
return false
}
// This is a duplicate definition of the function in:
// kubernetes/kubernetes/pkg/controller/service/service_controller.go
func GetNodeConditionPredicate() listers.NodeConditionPredicate {
return func(node *api_v1.Node) bool {
// We add the master to the node list, but its unschedulable. So we use this to filter
// the master.
if node.Spec.Unschedulable {
return false
}
// As of 1.6, we will taint the master, but not necessarily mark it unschedulable.
// Recognize nodes labeled as master, and filter them also, as we were doing previously.
if _, hasMasterRoleLabel := node.Labels[LabelNodeRoleMaster]; hasMasterRoleLabel {
return false
}
if _, hasExcludeBalancerLabel := node.Labels[LabelNodeRoleExcludeBalancer]; hasExcludeBalancerLabel {
return false
}
// If we have no info, don't accept
if len(node.Status.Conditions) == 0 {
return false
}
for _, cond := range node.Status.Conditions {
// We consider the node for load balancing only when its NodeReady condition status
// is ConditionTrue
if cond.Type == api_v1.NodeReady && cond.Status != api_v1.ConditionTrue {
klog.V(4).Infof("Ignoring node %v with %v condition status %v", node.Name, cond.Type, cond.Status)
return false
}
}
return true
}
}
// NewNamespaceIndexer returns a new Indexer for use by SharedIndexInformers
func NewNamespaceIndexer() cache.Indexers {
return cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
}
// JoinErrs returns an aggregated error based on the passed in list of errors.
func JoinErrs(errs []error) error {
var errStrs []string
for _, e := range errs {
errStrs = append(errStrs, e.Error())
}
return errors.New(strings.Join(errStrs, "; "))
}
func IngressKeyFunc(ing *extensions.Ingress) string {
if ing == nil {
return ""
}
return types.NamespacedName{Namespace: ing.Namespace, Name: ing.Name}.String()
}
// TraverseIngressBackends traverse thru all backends specified in the input ingress and call process
// If process return true, then return and stop traversing the backends
func TraverseIngressBackends(ing *extensions.Ingress, process func(id ServicePortID) bool) {
if ing == nil {
return
}
// Check service of default backend
if ing.Spec.Backend != nil {
if process(ServicePortID{Service: types.NamespacedName{Namespace: ing.Namespace, Name: ing.Spec.Backend.ServiceName}, Port: ing.Spec.Backend.ServicePort}) {
return
}
}
// Check the target service for each path rule
for _, rule := range ing.Spec.Rules {
if rule.IngressRuleValue.HTTP == nil {
continue
}
for _, p := range rule.IngressRuleValue.HTTP.Paths {
if process(ServicePortID{Service: types.NamespacedName{Namespace: ing.Namespace, Name: p.Backend.ServiceName}, Port: p.Backend.ServicePort}) {
return
}
}
}
return
}
func ServiceKeyFunc(namespace, name string) string {
return fmt.Sprintf("%s/%s", namespace, name)
}