Skip to content

Commit 7758022

Browse files
authored
Use new NGINX Plus API (nginx#240)
1 parent b8e4b87 commit 7758022

File tree

6 files changed

+146
-72
lines changed

6 files changed

+146
-72
lines changed

docs/installation.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ For NGINX Plus, you can access the live activity monitoring dashboard:
147147
```
148148
$ kubectl port-forward <nginx-plus-ingress-pod> 8080:8080 --namespace=nginx-ingress
149149
```
150-
1. Open your browser at http://127.0.0.1:8080/status.html to access the dashboard.
150+
1. Open your browser at http://127.0.0.1:8080/dashboard.html to access the dashboard.
151151
152152
## Uninstall the Ingress Controller
153153

examples/openshift/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
1. Avoid conflicts with the OpenShift Router.
1515

16-
NGINX Plus Ingress controller must be able to bind to ports 80 and 443 of the cluster node, where it is running, like the OpenShift Router. Thus, you need to make sure that the Ingress controller and the Router are running on separate nodes. Additionally, NGINX Plus binds to port 8080 to expose its built-in status API and the monitoring dashboard.
16+
NGINX Plus Ingress controller must be able to bind to ports 80 and 443 of the cluster node, where it is running, like the OpenShift Router. Thus, you need to make sure that the Ingress controller and the Router are running on separate nodes. Additionally, NGINX Plus binds to port 8080 to expose its API and the monitoring dashboard.
1717

1818
To quickly disable the Router you can run:
1919
```

nginx-controller/main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"flag"
5+
"net/http"
56
"os"
67
"os/signal"
78
"syscall"
@@ -128,7 +129,7 @@ func main() {
128129
var nginxAPI *plus.NginxAPIController
129130
if *nginxPlus {
130131
time.Sleep(500 * time.Millisecond)
131-
nginxAPI, err = plus.NewNginxAPIController("http://127.0.0.1:8080/upstream_conf", "http://127.0.0.1:8080/status", local)
132+
nginxAPI, err = plus.NewNginxAPIController(&http.Client{}, "http://127.0.0.1:8080/api", local)
132133
if err != nil {
133134
glog.Fatalf("Failed to create NginxAPIController: %v", err)
134135
}

nginx-controller/nginx/plus/nginx_api.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package plus
22

3-
import "github.com/golang/glog"
3+
import (
4+
"net/http"
5+
6+
"github.com/golang/glog"
7+
)
48

59
type NginxAPIController struct {
610
client *NginxClient
711
local bool
812
}
913

10-
func NewNginxAPIController(upstreamConfEndpoint string, statusEndpoint string, local bool) (*NginxAPIController, error) {
11-
client, err := NewNginxClient(upstreamConfEndpoint, statusEndpoint)
14+
func NewNginxAPIController(httpClient *http.Client, endpoint string, local bool) (*NginxAPIController, error) {
15+
client, err := NewNginxClient(httpClient, endpoint)
1216
if !local && err != nil {
1317
return nil, err
1418
}

nginx-controller/nginx/plus/nginx_client.go

+127-56
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package plus
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
7+
"io"
68
"io/ioutil"
79
"net/http"
810
)
911

10-
// NginxClient lets you add/remove servers to/from NGINX Plus via its upstream_conf API
12+
// APIVersion is a version of NGINX Plus API
13+
const APIVersion = 2
14+
15+
// NginxClient lets you add/remove servers to/from NGINX Plus via its API
1116
type NginxClient struct {
12-
upstreamConfEndpoint string
13-
statusEndpoint string
17+
apiEndpoint string
18+
httpClient *http.Client
1419
}
1520

1621
type peers struct {
@@ -22,88 +27,135 @@ type peer struct {
2227
Server string
2328
}
2429

30+
type versions []int
31+
32+
type upstreamServer struct {
33+
Server string `json:"server"`
34+
}
35+
36+
type apiErrorResponse struct {
37+
Path string
38+
Method string
39+
Error apiError
40+
RequestID string `json:"request_id"`
41+
Href string
42+
}
43+
44+
func (resp *apiErrorResponse) toString() string {
45+
return fmt.Sprintf("path=%v; method=%v; error.status=%v; error.text=%v; error.code=%v; request_id=%v; href=%v",
46+
resp.Path, resp.Method, resp.Error.Status, resp.Error.Text, resp.Error.Code, resp.RequestID, resp.Href)
47+
}
48+
49+
type apiError struct {
50+
Status int
51+
Text string
52+
Code string
53+
}
54+
2555
// NewNginxClient creates an NginxClient.
26-
func NewNginxClient(upstreamConfEndpoint string, statusEndpoint string) (*NginxClient, error) {
27-
err := checkIfUpstreamConfIsAccessible(upstreamConfEndpoint)
56+
func NewNginxClient(httpClient *http.Client, apiEndpoint string) (*NginxClient, error) {
57+
versions, err := getAPIVersions(httpClient, apiEndpoint)
58+
2859
if err != nil {
29-
return nil, err
60+
return nil, fmt.Errorf("error accessing the API: %v", err)
3061
}
3162

32-
err = checkIfStatusIsAccessible(statusEndpoint)
33-
if err != nil {
34-
return nil, err
63+
found := false
64+
for _, v := range *versions {
65+
if v == APIVersion {
66+
found = true
67+
break
68+
}
69+
}
70+
71+
if !found {
72+
return nil, fmt.Errorf("API version %v of the client is not supported by API versions of NGINX Plus: %v", APIVersion, *versions)
3573
}
3674

37-
client := &NginxClient{upstreamConfEndpoint: upstreamConfEndpoint, statusEndpoint: statusEndpoint}
38-
return client, nil
75+
return &NginxClient{
76+
apiEndpoint: apiEndpoint,
77+
httpClient: httpClient,
78+
}, nil
3979
}
4080

41-
func checkIfUpstreamConfIsAccessible(endpoint string) error {
42-
resp, err := http.Get(endpoint)
81+
func getAPIVersions(httpClient *http.Client, endpoint string) (*versions, error) {
82+
resp, err := httpClient.Get(endpoint)
4383
if err != nil {
44-
return fmt.Errorf("upstream_conf endpoint %v is not accessible: %v", endpoint, err)
84+
return nil, fmt.Errorf("%v is not accessible: %v", endpoint, err)
4585
}
4686
defer resp.Body.Close()
4787

88+
if resp.StatusCode != http.StatusOK {
89+
return nil, fmt.Errorf("%v is not accessible: expected %v response, got %v", endpoint, http.StatusOK, resp.StatusCode)
90+
}
91+
4892
body, err := ioutil.ReadAll(resp.Body)
4993
if err != nil {
50-
return fmt.Errorf("upstream_conf endpoint %v is not accessible: %v", endpoint, err)
94+
return nil, fmt.Errorf("error while reading body of the response: %v", err)
5195
}
5296

53-
if resp.StatusCode != http.StatusBadRequest {
54-
return fmt.Errorf("upstream_conf endpoint %v is not accessible: expected 400 response, got %v", endpoint, resp.StatusCode)
97+
var vers versions
98+
err = json.Unmarshal(body, &vers)
99+
if err != nil {
100+
return nil, fmt.Errorf("error unmarshalling versions, got %q response: %v", string(body), err)
55101
}
56102

57-
bodyStr := string(body)
58-
expected := "missing \"upstream\" argument\n"
59-
if bodyStr != expected {
60-
return fmt.Errorf("upstream_conf endpoint %v is not accessible: expected %q body, got %q", endpoint, expected, bodyStr)
103+
return &vers, nil
104+
}
105+
106+
func createResponseMismatchError(respBody io.ReadCloser, mainErr error) error {
107+
apiErr, err := readAPIErrorResponse(respBody)
108+
if err != nil {
109+
return fmt.Errorf("%v; failed to read the response body: %v", mainErr, err)
61110
}
62111

63-
return nil
112+
return fmt.Errorf("%v; error: %v", mainErr, apiErr.toString())
64113
}
65114

66-
func checkIfStatusIsAccessible(endpoint string) error {
67-
resp, err := http.Get(endpoint)
115+
func readAPIErrorResponse(respBody io.ReadCloser) (*apiErrorResponse, error) {
116+
body, err := ioutil.ReadAll(respBody)
68117
if err != nil {
69-
return fmt.Errorf("status endpoint is %v not accessible: %v", endpoint, err)
118+
return nil, fmt.Errorf("failed to read the response body: %v", err)
70119
}
71-
defer resp.Body.Close()
72120

73-
if resp.StatusCode != http.StatusOK {
74-
return fmt.Errorf("status endpoint is %v not accessible: expected 200 response, got %v", endpoint, resp.StatusCode)
121+
var apiErr apiErrorResponse
122+
err = json.Unmarshal(body, &apiErr)
123+
if err != nil {
124+
return nil, fmt.Errorf("error unmarshalling apiErrorResponse: got %q response: %v", string(body), err)
75125
}
76126

77-
return nil
127+
return &apiErr, nil
78128
}
79129

80-
// CheckIfUpstreamExists checks if the upstream exists in NGINX. If the upstream doesn't exist, it returns an error.
130+
// CheckIfUpstreamExists checks if the upstream exists in NGINX. If the upstream doesn't exist, it returns the error.
81131
func (client *NginxClient) CheckIfUpstreamExists(upstream string) error {
82132
_, err := client.getUpstreamPeers(upstream)
83133
return err
84134
}
85135

86136
func (client *NginxClient) getUpstreamPeers(upstream string) (*peers, error) {
87-
request := fmt.Sprintf("%v/upstreams/%v", client.statusEndpoint, upstream)
137+
url := fmt.Sprintf("%v/%v/http/upstreams/%v", client.apiEndpoint, APIVersion, upstream)
88138

89-
resp, err := http.Get(request)
139+
resp, err := client.httpClient.Get(url)
90140
if err != nil {
91-
return nil, fmt.Errorf("Failed to connect to the status api to get upstream %v info: %v", upstream, err)
141+
return nil, fmt.Errorf("failed to connect to the API to get upstream %v info: %v", upstream, err)
92142
}
93143
defer resp.Body.Close()
94144

95-
if resp.StatusCode == http.StatusNotFound {
96-
return nil, fmt.Errorf("Upstream %v is not found", upstream)
145+
if resp.StatusCode != http.StatusOK {
146+
mainErr := fmt.Errorf("upstream %v is invalid: expected %v response, got %v", upstream, http.StatusOK, resp.StatusCode)
147+
return nil, createResponseMismatchError(resp.Body, mainErr)
97148
}
98149

99150
body, err := ioutil.ReadAll(resp.Body)
100151
if err != nil {
101-
return nil, fmt.Errorf("Failed to read the response body with upstream %v info: %v", upstream, err)
152+
return nil, fmt.Errorf("failed to read the response body with upstream %v info: %v", upstream, err)
102153
}
154+
103155
var prs peers
104156
err = json.Unmarshal(body, &prs)
105157
if err != nil {
106-
return nil, fmt.Errorf("Error unmarshaling upstream %v: got %q response: %v", upstream, string(body), err)
158+
return nil, fmt.Errorf("error unmarshalling upstream %v: got %q response: %v", upstream, string(body), err)
107159
}
108160

109161
return &prs, nil
@@ -114,22 +166,34 @@ func (client *NginxClient) AddHTTPServer(upstream string, server string) error {
114166
id, err := client.getIDOfHTTPServer(upstream, server)
115167

116168
if err != nil {
117-
return fmt.Errorf("Failed to add %v server to %v upstream: %v", server, upstream, err)
169+
return fmt.Errorf("failed to add %v server to %v upstream: %v", server, upstream, err)
118170
}
119171
if id != -1 {
120-
return fmt.Errorf("Failed to add %v server to %v upstream: server already exists", server, upstream)
172+
return fmt.Errorf("failed to add %v server to %v upstream: server already exists", server, upstream)
173+
}
174+
175+
upsServer := upstreamServer{
176+
Server: server,
121177
}
122178

123-
request := fmt.Sprintf("%v?upstream=%v&add=&server=%v", client.upstreamConfEndpoint, upstream, server)
179+
jsonServer, err := json.Marshal(upsServer)
180+
if err != nil {
181+
return fmt.Errorf("error marshalling upstream server %v: %v", upsServer, err)
182+
}
183+
184+
url := fmt.Sprintf("%v/%v/http/upstreams/%v/servers/", client.apiEndpoint, APIVersion, upstream)
185+
186+
resp, err := client.httpClient.Post(url, "application/json", bytes.NewBuffer(jsonServer))
124187

125-
resp, err := http.Get(request)
126188
if err != nil {
127-
return fmt.Errorf("Failed to add %v server to %v upstream: %v", server, upstream, err)
189+
return fmt.Errorf("failed to add %v server to %v upstream: %v", server, upstream, err)
128190
}
129191
defer resp.Body.Close()
130192

131-
if resp.StatusCode != http.StatusOK {
132-
return fmt.Errorf("Failed to add %v server to %v upstream: expected 200 response, got %v", server, upstream, resp.StatusCode)
193+
if resp.StatusCode != http.StatusCreated {
194+
mainErr := fmt.Errorf("failed to add %v server to %v upstream: expected %v response, got %v",
195+
server, upstream, http.StatusCreated, resp.StatusCode)
196+
return createResponseMismatchError(resp.Body, mainErr)
133197
}
134198

135199
return nil
@@ -139,22 +203,29 @@ func (client *NginxClient) AddHTTPServer(upstream string, server string) error {
139203
func (client *NginxClient) DeleteHTTPServer(upstream string, server string) error {
140204
id, err := client.getIDOfHTTPServer(upstream, server)
141205
if err != nil {
142-
return fmt.Errorf("Failed to remove %v server from %v upstream: %v", server, upstream, err)
206+
return fmt.Errorf("failed to remove %v server from %v upstream: %v", server, upstream, err)
143207
}
144208
if id == -1 {
145-
return fmt.Errorf("Failed to remove %v server from %v upstream: server doesn't exists", server, upstream)
209+
return fmt.Errorf("failed to remove %v server from %v upstream: server doesn't exists", server, upstream)
146210
}
147211

148-
request := fmt.Sprintf("%v?upstream=%v&remove=&id=%v", client.upstreamConfEndpoint, upstream, id)
212+
url := fmt.Sprintf("%v/%v/http/upstreams/%v/servers/%v", client.apiEndpoint, APIVersion, upstream, id)
149213

150-
resp, err := http.Get(request)
214+
req, err := http.NewRequest(http.MethodDelete, url, nil)
151215
if err != nil {
152-
return fmt.Errorf("Failed to remove %v server from %v upstream: %v", server, upstream, err)
216+
return fmt.Errorf("failed to create a request: %v", err)
217+
}
218+
219+
resp, err := client.httpClient.Do(req)
220+
if err != nil {
221+
return fmt.Errorf("failed to remove %v server from %v upstream: %v", server, upstream, err)
153222
}
154223
defer resp.Body.Close()
155224

156-
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
157-
return fmt.Errorf("Failed to add %v server to %v upstream: expected 200 or 204 response, got %v", server, upstream, resp.StatusCode)
225+
if resp.StatusCode != http.StatusOK {
226+
mainErr := fmt.Errorf("failed to remove %v server from %v upstream: expected %v response, got %v",
227+
server, upstream, http.StatusOK, resp.StatusCode)
228+
return createResponseMismatchError(resp.Body, mainErr)
158229
}
159230

160231
return nil
@@ -166,22 +237,22 @@ func (client *NginxClient) DeleteHTTPServer(upstream string, server string) erro
166237
func (client *NginxClient) UpdateHTTPServers(upstream string, servers []string) ([]string, []string, error) {
167238
serversInNginx, err := client.GetHTTPServers(upstream)
168239
if err != nil {
169-
return nil, nil, fmt.Errorf("Failed to update servers of %v upstream: %v", upstream, err)
240+
return nil, nil, fmt.Errorf("failed to update servers of %v upstream: %v", upstream, err)
170241
}
171242

172243
toAdd, toDelete := determineUpdates(servers, serversInNginx)
173244

174245
for _, server := range toAdd {
175246
err := client.AddHTTPServer(upstream, server)
176247
if err != nil {
177-
return nil, nil, fmt.Errorf("Failed to update servers of %v upstream: %v", upstream, err)
248+
return nil, nil, fmt.Errorf("failed to update servers of %v upstream: %v", upstream, err)
178249
}
179250
}
180251

181252
for _, server := range toDelete {
182253
err := client.DeleteHTTPServer(upstream, server)
183254
if err != nil {
184-
return nil, nil, fmt.Errorf("Failed to update servers of %v upstream: %v", upstream, err)
255+
return nil, nil, fmt.Errorf("failed to update servers of %v upstream: %v", upstream, err)
185256
}
186257
}
187258

@@ -222,7 +293,7 @@ func determineUpdates(updatedServers []string, nginxServers []string) (toAdd []s
222293
func (client *NginxClient) GetHTTPServers(upstream string) ([]string, error) {
223294
peers, err := client.getUpstreamPeers(upstream)
224295
if err != nil {
225-
return nil, fmt.Errorf("Error getting servers of %v upstream: %v", upstream, err)
296+
return nil, fmt.Errorf("error getting servers of %v upstream: %v", upstream, err)
226297
}
227298

228299
var servers []string
@@ -236,7 +307,7 @@ func (client *NginxClient) GetHTTPServers(upstream string) ([]string, error) {
236307
func (client *NginxClient) getIDOfHTTPServer(upstream string, name string) (int, error) {
237308
peers, err := client.getUpstreamPeers(upstream)
238309
if err != nil {
239-
return -1, fmt.Errorf("Error getting id of server %v of upstream %v: %v", name, upstream, err)
310+
return -1, fmt.Errorf("error getting id of server %v of upstream %v: %v", name, upstream, err)
240311
}
241312

242313
for _, p := range peers.Peers {

0 commit comments

Comments
 (0)