This repository has been archived by the owner on Jan 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 560
/
service.go
178 lines (164 loc) · 4.78 KB
/
service.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
package service
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os/exec"
"regexp"
"time"
"github.com/Azure/acs-engine/test/e2e/kubernetes/util"
)
// Service represents a kubernetes service
type Service struct {
Metadata Metadata `json:"metadata"`
Spec Spec `json:"spec"`
Status Status `json:"status"`
}
// Metadata holds information like name, namespace, and labels
type Metadata struct {
CreatedAt time.Time `json:"creationTimestamp"`
Labels map[string]string `json:"labels"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
// Spec holds information like clusterIP and port
type Spec struct {
ClusterIP string `json:"clusterIP"`
Ports []Port `json:"ports"`
Type string `json:"type"`
}
// Port represents a service port definition
type Port struct {
NodePort int `json:"nodePort"`
Port int `json:"port"`
Protocol string `json:"protocol"`
TargetPort int `json:"targetPort"`
}
// Status holds the load balancer definition
type Status struct {
LoadBalancer LoadBalancer `json:"loadBalancer"`
}
// LoadBalancer holds the ingress definitions
type LoadBalancer struct {
Ingress []map[string]string `json:"ingress"`
}
// Get returns the service definition specified in a given namespace
func Get(name, namespace string) (*Service, error) {
cmd := exec.Command("kubectl", "get", "svc", "-o", "json", "-n", namespace, name)
util.PrintCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Error trying to run 'kubectl get svc':%s\n", string(out))
return nil, err
}
s := Service{}
err = json.Unmarshal(out, &s)
if err != nil {
log.Printf("Error unmarshalling service json:%s\n", err)
return nil, err
}
return &s, nil
}
// Delete will delete a service in a given namespace
func (s *Service) Delete() error {
cmd := exec.Command("kubectl", "delete", "svc", "-n", s.Metadata.Namespace, s.Metadata.Name)
util.PrintCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Error while trying to delete service %s in namespace %s:%s\n", s.Metadata.Namespace, s.Metadata.Name, string(out))
return err
}
return nil
}
// GetNodePort will return the node port for a given pod
func (s *Service) GetNodePort(port int) int {
for _, p := range s.Spec.Ports {
if p.Port == port {
return p.NodePort
}
}
return 0
}
// WaitForExternalIP waits for an external ip to be provisioned
func (s *Service) WaitForExternalIP(wait, sleep time.Duration) (*Service, error) {
svcCh := make(chan *Service)
errCh := make(chan error)
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
go func() {
for {
select {
case <-ctx.Done():
errCh <- fmt.Errorf("Timeout exceeded while waiting for External IP to be provisioned")
default:
svc, _ := Get(s.Metadata.Name, s.Metadata.Namespace)
if svc != nil && svc.Status.LoadBalancer.Ingress != nil {
svcCh <- svc
}
time.Sleep(sleep)
}
}
}()
for {
select {
case err := <-errCh:
return nil, err
case svc := <-svcCh:
return svc, nil
}
}
}
// Validate will attempt to run an http.Get against the root service url
func (s *Service) Validate(check string, attempts int, sleep, wait time.Duration) bool {
var err error
var url string
var i int
var resp *http.Response
svc, waitErr := s.WaitForExternalIP(wait, 5*time.Second)
if waitErr != nil {
log.Printf("Unable to verify external IP, cannot validate service:%s\n", waitErr)
return false
}
if svc.Status.LoadBalancer.Ingress == nil || len(svc.Status.LoadBalancer.Ingress) == 0 {
log.Printf("Service LB ingress is empty or nil: %#v\n", svc.Status.LoadBalancer.Ingress)
return false
}
for i = 1; i <= attempts; i++ {
url = fmt.Sprintf("http://%s", svc.Status.LoadBalancer.Ingress[0]["ip"])
resp, err = http.Get(url)
if err == nil {
body, _ := ioutil.ReadAll(resp.Body)
matched, _ := regexp.MatchString(check, string(body))
if matched {
defer resp.Body.Close()
return true
}
log.Printf("Got unexpected URL body, expected to find %s, got:\n%s\n", check, string(body))
}
time.Sleep(sleep)
}
log.Printf("Unable to validate URL %s after %s, err: %#v\n", url, time.Duration(i)*wait, err)
if resp != nil {
defer resp.Body.Close()
}
return false
}
// CreateServiceFromFile will create a Service from file with a name
func CreateServiceFromFile(filename, name, namespace string) (*Service, error) {
cmd := exec.Command("kubectl", "create", "-f", filename)
util.PrintCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Error trying to create Service %s:%s\n", name, string(out))
return nil, err
}
svc, err := Get(name, namespace)
if err != nil {
log.Printf("Error while trying to fetch Service %s:%s\n", name, err)
return nil, err
}
return svc, nil
}