Skip to content

Commit c8f52e1

Browse files
gateway: add automated tests
We have around 20 location NGINX directives for 5 underlying microservices. This level of complexity slowed down our dev and release process because every change had to be verified manually in a separate test environment. So, we are introducing automated tests for the API gateway.
1 parent 162490a commit c8f52e1

File tree

6 files changed

+192
-12
lines changed

6 files changed

+192
-12
lines changed

gateway/Dockerfile

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM openresty/openresty:1.15.8.2-6-alpine
1+
FROM openresty/openresty:1.15.8.2-6-alpine as base
22

33
RUN ["rm", "/etc/nginx/conf.d/default.conf"]
44

@@ -11,6 +11,28 @@ COPY gateway/shellhub.conf /etc/nginx/conf.d/
1111
COPY gateway/kickstart.sh /usr/local/openresty/nginx/html/
1212
COPY gateway/entrypoint.sh /
1313

14-
ENTRYPOINT ["/entrypoint.sh"]
14+
FROM base as test
15+
16+
RUN apk add --no-cache alpine-sdk
17+
18+
COPY --from=golang:1.16.4-alpine3.13 /usr/local/go /usr/local/go
19+
20+
ENV GOPATH /go
21+
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
22+
23+
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
24+
25+
WORKDIR $GOPATH/src/github.com/shellhub-io/shellhub/gateway/tests
1526

27+
COPY ./gateway/tests/go.mod ./gateway/tests/go.sum ./
28+
29+
RUN go mod download
30+
31+
COPY ./gateway/tests $GOPATH/src/github.com/shellhub-io/shellhub/gateway/tests
32+
33+
CMD ["go", "test"]
34+
35+
FROM base as production
36+
37+
ENTRYPOINT ["/entrypoint.sh"]
1638
CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]

gateway/shellhub.conf

+30-10
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ server {
88
resolver 127.0.0.11 ipv6=off;
99

1010
location / {
11+
set $upstream ui:8080;
12+
1113
add_header Cache-Control "no-cache, no-store";
1214
add_header Pragma "no-cache";
1315

14-
proxy_pass http://ui:8080;
16+
proxy_pass http://$upstream;
1517
proxy_set_header Upgrade $http_upgrade;
1618
proxy_set_header Connection 'upgrade';
1719
proxy_set_header Host $host;
@@ -21,6 +23,8 @@ server {
2123
}
2224

2325
location /api {
26+
set $upstream api:8080;
27+
2428
auth_request /auth;
2529
auth_request_set $tenant_id $upstream_http_x_tenant_id;
2630
auth_request_set $username $upstream_http_x_username;
@@ -30,7 +34,7 @@ server {
3034
proxy_set_header X-Tenant-ID $tenant_id;
3135
proxy_set_header X-Username $username;
3236
proxy_set_header X-ID $id;
33-
proxy_pass http://api:8080;
37+
proxy_pass http://$upstream;
3438
}
3539

3640
{{ if bool (env.Getenv "SHELLHUB_ENTERPRISE") -}}
@@ -65,9 +69,11 @@ server {
6569
{{ end -}}
6670

6771
location /ssh/connection {
72+
set $upstream ssh:8080;
73+
6874
auth_request /auth;
6975
auth_request_set $device_uid $upstream_http_x_device_uid;
70-
proxy_pass http://ssh:8080;
76+
proxy_pass http://$upstream;
7177
proxy_set_header Upgrade $http_upgrade;
7278
proxy_set_header Connection 'upgrade';
7379
proxy_set_header Host $host;
@@ -83,7 +89,9 @@ server {
8389
}
8490

8591
location /ssh/revdial {
86-
proxy_pass http://ssh:8080;
92+
set $upstream ssh:8080;
93+
94+
proxy_pass http://$upstream;
8795
proxy_set_header Upgrade $http_upgrade;
8896
proxy_set_header Connection 'upgrade';
8997
proxy_set_header Host $host;
@@ -98,10 +106,12 @@ server {
98106
}
99107

100108
location /ssh/auth {
109+
set $upstream api:8080;
110+
101111
auth_request /auth;
102112
auth_request_set $device_uid $upstream_http_x_device_uid;
103113
error_page 500 =401 /auth;
104-
proxy_pass http://api:8080;
114+
proxy_pass http://$upstream;
105115
proxy_set_header X-Device-UID $device_uid;
106116
}
107117

@@ -143,34 +153,44 @@ server {
143153
{{ end -}}
144154

145155
location ~* /api/sessions/(.*)/close {
156+
set $upstream ssh:8080;
157+
146158
auth_request /auth;
147159
auth_request_set $tenant_id $upstream_http_x_tenant_id;
148160
error_page 500 =401 /auth;
149161
rewrite ^/api/(.*)$ /$1 break;
150162
proxy_set_header X-Tenant-ID $tenant_id;
151-
proxy_pass http://ssh:8080;
163+
proxy_pass http://$upstream;
152164
}
153165

154166
location /api/devices/auth {
167+
set $upstream api:8080;
168+
155169
auth_request off;
156170
rewrite ^/api/(.*)$ /api/$1 break;
157-
proxy_pass http://api:8080;
171+
proxy_pass http://$upstream;
158172
}
159173

160174
location /api/login {
175+
set $upstream api:8080;
176+
161177
auth_request off;
162178
rewrite ^/api/(.*)$ /api/$1 break;
163-
proxy_pass http://api:8080;
179+
proxy_pass http://$upstream;
164180
}
165181

166182
location /auth {
183+
set $upstream api:8080;
184+
167185
internal;
168186
rewrite ^/(.*)$ /internal/$1 break;
169-
proxy_pass http://api:8080;
187+
proxy_pass http://$upstream;
170188
}
171189

172190
location /ws {
173-
proxy_pass http://ssh:8080;
191+
set $upstream ssh:8080;
192+
193+
proxy_pass http://$upstream;
174194
proxy_set_header Upgrade $http_upgrade;
175195
proxy_set_header Connection 'upgrade';
176196
proxy_set_header Host $host;

gateway/tests/basic_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestDummy(t *testing.T) {
9+
fmt.Println("dummy")
10+
}

gateway/tests/go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/shellhub-io/shellhub/gateway/tests
2+
3+
go 1.16
4+
5+
require github.com/miekg/dns v1.1.42 // indirect

gateway/tests/go.sum

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY=
2+
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
3+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
4+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
5+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
6+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
7+
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g=
8+
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
9+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
10+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
11+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

gateway/tests/main_test.go

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"os"
7+
"os/exec"
8+
"testing"
9+
"time"
10+
11+
"github.com/miekg/dns"
12+
)
13+
14+
type dnsHandler struct {
15+
records map[string]string
16+
}
17+
18+
func (d *dnsHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
19+
m := new(dns.Msg)
20+
m.SetReply(r)
21+
m.Compress = false
22+
23+
switch r.Opcode {
24+
case dns.OpcodeQuery:
25+
d.parseQuery(m)
26+
}
27+
28+
w.WriteMsg(m)
29+
}
30+
31+
func (d *dnsHandler) parseQuery(m *dns.Msg) {
32+
for _, q := range m.Question {
33+
switch q.Qtype {
34+
case dns.TypeA:
35+
if ip, ok := d.records[q.Name]; ok {
36+
rr, err := dns.NewRR(fmt.Sprintf("%s A %s", q.Name, ip))
37+
if err == nil {
38+
m.Answer = append(m.Answer, rr)
39+
}
40+
}
41+
}
42+
}
43+
}
44+
45+
func waitForConnection(proto, addr string) bool {
46+
for i := 0; i < 10; i++ {
47+
conn, err := net.Dial(proto, addr)
48+
if err == nil {
49+
conn.Close()
50+
return true
51+
}
52+
53+
time.Sleep(time.Second)
54+
}
55+
56+
return false
57+
}
58+
59+
func TestMain(m *testing.M) {
60+
// Create a virtual network adapter
61+
if _, err := exec.Command("ifconfig", "eth0:0", "127.0.0.11").Output(); err != nil {
62+
panic(err)
63+
}
64+
65+
server := &dns.Server{
66+
Addr: "127.0.0.11:53",
67+
Net: "udp",
68+
Handler: &dnsHandler{
69+
records: map[string]string{
70+
"api.": "127.0.0.1",
71+
"ssh.": "127.0.0.1",
72+
"ui.": "127.0.0.1",
73+
},
74+
},
75+
}
76+
77+
go func() {
78+
if err := server.ListenAndServe(); err != nil {
79+
panic(err)
80+
}
81+
}()
82+
83+
// Wait for DNS test server to be started
84+
if !waitForConnection("udp", "127.0.0.11:53") {
85+
panic("Failed to connect to DNS test server")
86+
}
87+
88+
// Start OpenResty daemon
89+
cmd := exec.Command("/entrypoint.sh", "/usr/local/openresty/bin/openresty", "-g", "daemon off;")
90+
cmd.Stdout = os.Stdout
91+
cmd.Stderr = os.Stderr
92+
93+
if err := cmd.Start(); err != nil {
94+
panic(err)
95+
}
96+
97+
// Wait for OpenResty to be started
98+
if !waitForConnection("tcp", "127.0.0.1:80") {
99+
panic("Failed to connect to OpenResty")
100+
}
101+
102+
// Run unit test
103+
code := m.Run()
104+
105+
server.Shutdown()
106+
107+
if err := cmd.Process.Kill(); err != nil {
108+
panic(err)
109+
}
110+
111+
os.Exit(code)
112+
}

0 commit comments

Comments
 (0)