Skip to content

Commit 38de02b

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 38de02b

File tree

6 files changed

+229
-17
lines changed

6 files changed

+229
-17
lines changed

gateway/Dockerfile

Lines changed: 24 additions & 2 deletions
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

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,22 @@ 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';
17-
proxy_set_header Host $host;
1819
proxy_http_version 1.1;
1920
proxy_cache_bypass $http_upgrade;
2021
proxy_redirect off;
2122
}
2223

2324
location /api {
25+
set $upstream api:8080;
26+
2427
auth_request /auth;
2528
auth_request_set $tenant_id $upstream_http_x_tenant_id;
2629
auth_request_set $username $upstream_http_x_username;
@@ -30,7 +33,7 @@ server {
3033
proxy_set_header X-Tenant-ID $tenant_id;
3134
proxy_set_header X-Username $username;
3235
proxy_set_header X-ID $id;
33-
proxy_pass http://api:8080;
36+
proxy_pass http://$upstream;
3437
}
3538

3639
{{ if bool (env.Getenv "SHELLHUB_ENTERPRISE") -}}
@@ -46,7 +49,6 @@ server {
4649
proxy_pass http://$upstream;
4750
proxy_set_header Upgrade $http_upgrade;
4851
proxy_set_header Connection 'upgrade';
49-
proxy_set_header Host $host;
5052
proxy_http_version 1.1;
5153
proxy_cache_bypass $http_upgrade;
5254
proxy_redirect off;
@@ -65,12 +67,13 @@ server {
6567
{{ end -}}
6668

6769
location /ssh/connection {
70+
set $upstream ssh:8080;
71+
6872
auth_request /auth;
6973
auth_request_set $device_uid $upstream_http_x_device_uid;
70-
proxy_pass http://ssh:8080;
74+
proxy_pass http://$upstream;
7175
proxy_set_header Upgrade $http_upgrade;
7276
proxy_set_header Connection 'upgrade';
73-
proxy_set_header Host $host;
7477
{{ if bool (env.Getenv "SHELLHUB_PROXY") -}}
7578
proxy_set_header X-Real-IP $proxy_protocol_addr;
7679
{{ else -}}
@@ -83,10 +86,11 @@ server {
8386
}
8487

8588
location /ssh/revdial {
86-
proxy_pass http://ssh:8080;
89+
set $upstream ssh:8080;
90+
91+
proxy_pass http://$upstream;
8792
proxy_set_header Upgrade $http_upgrade;
8893
proxy_set_header Connection 'upgrade';
89-
proxy_set_header Host $host;
9094
{{ if bool (env.Getenv "SHELLHUB_PROXY") -}}
9195
proxy_set_header X-Real-IP $proxy_protocol_addr;
9296
{{ else -}}
@@ -98,10 +102,12 @@ server {
98102
}
99103

100104
location /ssh/auth {
105+
set $upstream api:8080;
106+
101107
auth_request /auth;
102108
auth_request_set $device_uid $upstream_http_x_device_uid;
103109
error_page 500 =401 /auth;
104-
proxy_pass http://api:8080;
110+
proxy_pass http://$upstream;
105111
proxy_set_header X-Device-UID $device_uid;
106112
}
107113

@@ -143,37 +149,46 @@ server {
143149
{{ end -}}
144150

145151
location ~* /api/sessions/(.*)/close {
152+
set $upstream ssh:8080;
153+
146154
auth_request /auth;
147155
auth_request_set $tenant_id $upstream_http_x_tenant_id;
148156
error_page 500 =401 /auth;
149157
rewrite ^/api/(.*)$ /$1 break;
150158
proxy_set_header X-Tenant-ID $tenant_id;
151-
proxy_pass http://ssh:8080;
159+
proxy_pass http://$upstream;
152160
}
153161

154162
location /api/devices/auth {
163+
set $upstream api:8080;
164+
155165
auth_request off;
156166
rewrite ^/api/(.*)$ /api/$1 break;
157-
proxy_pass http://api:8080;
167+
proxy_pass http://$upstream;
158168
}
159169

160170
location /api/login {
171+
set $upstream api:8080;
172+
161173
auth_request off;
162174
rewrite ^/api/(.*)$ /api/$1 break;
163-
proxy_pass http://api:8080;
175+
proxy_pass http://$upstream;
164176
}
165177

166178
location /auth {
179+
set $upstream api:8080;
180+
167181
internal;
168182
rewrite ^/(.*)$ /internal/$1 break;
169-
proxy_pass http://api:8080;
183+
proxy_pass http://$upstream;
170184
}
171185

172186
location /ws {
173-
proxy_pass http://ssh:8080;
187+
set $upstream ssh:8080;
188+
189+
proxy_pass http://$upstream;
174190
proxy_set_header Upgrade $http_upgrade;
175191
proxy_set_header Connection 'upgrade';
176-
proxy_set_header Host $host;
177192

178193
{{ if bool (env.Getenv "SHELLHUB_PROXY") -}}
179194
proxy_set_header X-Real-IP $proxy_protocol_addr;

gateway/tests/basic_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"testing"
7+
)
8+
9+
func TestDummy(t *testing.T) {
10+
fmt.Println("dummy")
11+
12+
res := make(chan *http.Request)
13+
14+
mux := http.NewServeMux()
15+
mux.Handle("/", http.HandlerFunc(
16+
func(w http.ResponseWriter, r *http.Request) {
17+
fmt.Println(r.Header)
18+
fmt.Fprintf(w, "okay")
19+
res <- r
20+
},
21+
))
22+
23+
srv := &http.Server{
24+
Addr: "127.0.0.1:8080",
25+
Handler: mux,
26+
}
27+
28+
go func() {
29+
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
30+
panic(err)
31+
}
32+
}()
33+
34+
if !waitForConnection("tcp", "127.0.0.1:8080") {
35+
panic("Failed to start http server")
36+
}
37+
38+
client := &http.Client{}
39+
40+
req, _ := http.NewRequest("GET", "http://localhost:80/", nil)
41+
go client.Do(req)
42+
43+
rr := <-res
44+
fmt.Println(rr.Host)
45+
46+
// / => UI
47+
}

gateway/tests/go.mod

Lines changed: 5 additions & 0 deletions
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

Lines changed: 11 additions & 0 deletions
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

Lines changed: 112 additions & 0 deletions
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)