Blame


1 191337d0 2022-01-18 o package icinga_test
2 191337d0 2022-01-18 o
3 191337d0 2022-01-18 o import (
4 191337d0 2022-01-18 o "crypto/tls"
5 b717ce9b 2022-02-04 o "encoding/json"
6 191337d0 2022-01-18 o "errors"
7 b717ce9b 2022-02-04 o "fmt"
8 191337d0 2022-01-18 o "math/rand"
9 191337d0 2022-01-18 o "net/http"
10 b717ce9b 2022-02-04 o "net/http/httptest"
11 b717ce9b 2022-02-04 o "path"
12 191337d0 2022-01-18 o "reflect"
13 191337d0 2022-01-18 o "sort"
14 b717ce9b 2022-02-04 o "strings"
15 191337d0 2022-01-18 o "testing"
16 d762d1d1 2022-02-01 o "time"
17 191337d0 2022-01-18 o
18 191337d0 2022-01-18 o "olowe.co/icinga"
19 191337d0 2022-01-18 o )
20 191337d0 2022-01-18 o
21 d762d1d1 2022-02-01 o func init() {
22 d762d1d1 2022-02-01 o rand.Seed(time.Now().Unix())
23 d762d1d1 2022-02-01 o }
24 d762d1d1 2022-02-01 o
25 d762d1d1 2022-02-01 o func randomHostname(suffix string) string {
26 191337d0 2022-01-18 o var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
27 191337d0 2022-01-18 o b := make([]rune, 8)
28 191337d0 2022-01-18 o for i := range b {
29 191337d0 2022-01-18 o b[i] = letters[rand.Intn(len(letters))]
30 191337d0 2022-01-18 o }
31 d762d1d1 2022-02-01 o return string(b) + suffix
32 191337d0 2022-01-18 o }
33 191337d0 2022-01-18 o
34 b717ce9b 2022-02-04 o func randomTestAddr() string { return fmt.Sprintf("192.0.2.%d", rand.Intn(254)) }
35 7cb145ba 2022-01-18 o
36 b717ce9b 2022-02-04 o func randomHosts(n int, suffix string) []icinga.Host {
37 7cb145ba 2022-01-18 o var hosts []icinga.Host
38 b717ce9b 2022-02-04 o for i := 0; i < n; i++ {
39 7cb145ba 2022-01-18 o h := icinga.Host{
40 b717ce9b 2022-02-04 o Name: randomHostname(suffix),
41 7cb145ba 2022-01-18 o CheckCommand: "random",
42 b717ce9b 2022-02-04 o Address: randomTestAddr(),
43 7cb145ba 2022-01-18 o }
44 7cb145ba 2022-01-18 o hosts = append(hosts, h)
45 7cb145ba 2022-01-18 o }
46 b717ce9b 2022-02-04 o return hosts
47 7cb145ba 2022-01-18 o }
48 7cb145ba 2022-01-18 o
49 b717ce9b 2022-02-04 o func newTestClient(t *testing.T) *icinga.Client {
50 191337d0 2022-01-18 o tp := http.DefaultTransport.(*http.Transport)
51 191337d0 2022-01-18 o tp.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
52 b717ce9b 2022-02-04 o c := &http.Client{Transport: tp}
53 b717ce9b 2022-02-04 o if client, err := icinga.Dial("::1:5665", "icinga", "icinga", c); err == nil {
54 b717ce9b 2022-02-04 o return client
55 b717ce9b 2022-02-04 o }
56 b717ce9b 2022-02-04 o client, err := icinga.Dial("127.0.0.1:5665", "icinga", "icinga", c)
57 b717ce9b 2022-02-04 o if err == nil {
58 b717ce9b 2022-02-04 o return client
59 b717ce9b 2022-02-04 o }
60 b717ce9b 2022-02-04 o t.Skipf("cannot dial local icinga: %v", err)
61 b717ce9b 2022-02-04 o return nil
62 191337d0 2022-01-18 o }
63 191337d0 2022-01-18 o
64 191337d0 2022-01-18 o func compareStringSlice(a, b []string) bool {
65 191337d0 2022-01-18 o if len(a) != len(b) {
66 191337d0 2022-01-18 o return false
67 191337d0 2022-01-18 o }
68 d762d1d1 2022-02-01 o for i := range a {
69 d762d1d1 2022-02-01 o if a[i] != b[i] {
70 191337d0 2022-01-18 o return false
71 191337d0 2022-01-18 o }
72 191337d0 2022-01-18 o }
73 191337d0 2022-01-18 o return true
74 191337d0 2022-01-18 o }
75 191337d0 2022-01-18 o
76 191337d0 2022-01-18 o func TestFilter(t *testing.T) {
77 b717ce9b 2022-02-04 o client := newTestClient(t)
78 b717ce9b 2022-02-04 o var want, got []string // host names
79 191337d0 2022-01-18 o
80 b717ce9b 2022-02-04 o hosts := randomHosts(10, "example.org")
81 b717ce9b 2022-02-04 o for i := range hosts {
82 b717ce9b 2022-02-04 o if err := client.CreateHost(hosts[i]); err != nil {
83 b717ce9b 2022-02-04 o t.Fatal(err)
84 b717ce9b 2022-02-04 o }
85 b717ce9b 2022-02-04 o want = append(want, hosts[i].Name)
86 7cb145ba 2022-01-18 o }
87 b717ce9b 2022-02-04 o t.Cleanup(func() {
88 b717ce9b 2022-02-04 o for i := range hosts {
89 b717ce9b 2022-02-04 o if err := client.DeleteHost(hosts[i].Name, true); err != nil {
90 b717ce9b 2022-02-04 o t.Error(err)
91 191337d0 2022-01-18 o }
92 191337d0 2022-01-18 o }
93 b717ce9b 2022-02-04 o })
94 b717ce9b 2022-02-04 o
95 b717ce9b 2022-02-04 o filter := `match("*example.org", host.name)`
96 b717ce9b 2022-02-04 o hosts, err := client.Hosts(filter)
97 191337d0 2022-01-18 o if err != nil {
98 191337d0 2022-01-18 o t.Fatal(err)
99 191337d0 2022-01-18 o }
100 b717ce9b 2022-02-04 o for i := range hosts {
101 b717ce9b 2022-02-04 o got = append(got, hosts[i].Name)
102 191337d0 2022-01-18 o }
103 b717ce9b 2022-02-04 o
104 191337d0 2022-01-18 o sort.Strings(want)
105 191337d0 2022-01-18 o sort.Strings(got)
106 191337d0 2022-01-18 o if !compareStringSlice(want, got) {
107 b717ce9b 2022-02-04 o t.Error("want", want, "got", got)
108 191337d0 2022-01-18 o }
109 191337d0 2022-01-18 o }
110 191337d0 2022-01-18 o
111 191337d0 2022-01-18 o func TestUserRoundTrip(t *testing.T) {
112 b717ce9b 2022-02-04 o client := newTestClient(t)
113 191337d0 2022-01-18 o want := icinga.User{Name: "olly", Email: "olly@example.com", Groups: []string{}}
114 191337d0 2022-01-18 o if err := client.CreateUser(want); err != nil && !errors.Is(err, icinga.ErrExist) {
115 191337d0 2022-01-18 o t.Fatal(err)
116 191337d0 2022-01-18 o }
117 191337d0 2022-01-18 o defer func() {
118 02a1a100 2022-01-18 o if err := client.DeleteUser(want.Name, false); err != nil {
119 191337d0 2022-01-18 o t.Error(err)
120 191337d0 2022-01-18 o }
121 191337d0 2022-01-18 o }()
122 191337d0 2022-01-18 o got, err := client.LookupUser(want.Name)
123 191337d0 2022-01-18 o if err != nil {
124 191337d0 2022-01-18 o t.Fatal(err)
125 191337d0 2022-01-18 o }
126 191337d0 2022-01-18 o if !reflect.DeepEqual(want, got) {
127 191337d0 2022-01-18 o t.Errorf("want %+v, got %+v", want, got)
128 191337d0 2022-01-18 o }
129 191337d0 2022-01-18 o }
130 191337d0 2022-01-18 o
131 191337d0 2022-01-18 o func TestChecker(t *testing.T) {
132 b717ce9b 2022-02-04 o client := newTestClient(t)
133 b717ce9b 2022-02-04 o h := randomHosts(1, ".checker.example")[0]
134 d762d1d1 2022-02-01 o if err := client.CreateHost(h); err != nil {
135 d762d1d1 2022-02-01 o t.Fatal(err)
136 d762d1d1 2022-02-01 o }
137 b717ce9b 2022-02-04 o defer client.DeleteHost(h.Name, true)
138 b717ce9b 2022-02-04 o svc := icinga.Service{
139 d762d1d1 2022-02-01 o Name: h.Name + "!http",
140 d762d1d1 2022-02-01 o CheckCommand: "http",
141 d762d1d1 2022-02-01 o }
142 b717ce9b 2022-02-04 o if err := svc.Check(client); err == nil {
143 b717ce9b 2022-02-04 o t.Error("nil error checking non-existent service")
144 d762d1d1 2022-02-01 o }
145 b717ce9b 2022-02-04 o if err := client.CreateService(svc); err != nil {
146 191337d0 2022-01-18 o t.Fatal(err)
147 191337d0 2022-01-18 o }
148 b717ce9b 2022-02-04 o if err := svc.Check(client); err != nil {
149 b717ce9b 2022-02-04 o t.Error(err)
150 191337d0 2022-01-18 o }
151 b717ce9b 2022-02-04 o if err := client.DeleteService(svc.Name, false); err != nil {
152 b717ce9b 2022-02-04 o t.Error(err)
153 b717ce9b 2022-02-04 o }
154 191337d0 2022-01-18 o }
155 02a1a100 2022-01-18 o
156 7cb145ba 2022-01-18 o func TestCheckHostGroup(t *testing.T) {
157 b717ce9b 2022-02-04 o client := newTestClient(t)
158 b717ce9b 2022-02-04 o hostgroup := icinga.HostGroup{Name: "test", DisplayName: "Test Group"}
159 b717ce9b 2022-02-04 o if err := client.CreateHostGroup(hostgroup); err != nil && !errors.Is(err, icinga.ErrExist) {
160 b717ce9b 2022-02-04 o t.Fatal(err)
161 7cb145ba 2022-01-18 o }
162 b717ce9b 2022-02-04 o defer client.DeleteHostGroup(hostgroup.Name, false)
163 b717ce9b 2022-02-04 o hostgroup, err := client.LookupHostGroup(hostgroup.Name)
164 7cb145ba 2022-01-18 o if err != nil {
165 7cb145ba 2022-01-18 o t.Fatal(err)
166 7cb145ba 2022-01-18 o }
167 b717ce9b 2022-02-04 o hosts := randomHosts(10, "example.org")
168 b717ce9b 2022-02-04 o for _, h := range hosts {
169 b717ce9b 2022-02-04 o h.Groups = []string{hostgroup.Name}
170 b717ce9b 2022-02-04 o if err := client.CreateHost(h); err != nil {
171 b717ce9b 2022-02-04 o t.Fatal(err)
172 7cb145ba 2022-01-18 o }
173 b717ce9b 2022-02-04 o defer client.DeleteHost(h.Name, false)
174 7cb145ba 2022-01-18 o }
175 7cb145ba 2022-01-18 o if err := hostgroup.Check(client); err != nil {
176 7cb145ba 2022-01-18 o t.Fatal(err)
177 7cb145ba 2022-01-18 o }
178 7cb145ba 2022-01-18 o }
179 7cb145ba 2022-01-18 o
180 64e18a8d 2022-01-21 o func TestNonExistentService(t *testing.T) {
181 b717ce9b 2022-02-04 o client := newTestClient(t)
182 64e18a8d 2022-01-21 o filter := `match("blablabla", service.name)`
183 64e18a8d 2022-01-21 o service, err := client.Services(filter)
184 64e18a8d 2022-01-21 o if err == nil {
185 b717ce9b 2022-02-04 o t.Error("non-nil error TODO")
186 b717ce9b 2022-02-04 o t.Log(service)
187 64e18a8d 2022-01-21 o }
188 64e18a8d 2022-01-21 o }
189 b717ce9b 2022-02-04 o
190 b717ce9b 2022-02-04 o type fakeServer struct {
191 b717ce9b 2022-02-04 o objects map[string]attributes
192 b717ce9b 2022-02-04 o }
193 b717ce9b 2022-02-04 o
194 b717ce9b 2022-02-04 o func newFakeServer() *httptest.Server {
195 b717ce9b 2022-02-04 o return httptest.NewTLSServer(&fakeServer{objects: make(map[string]attributes)})
196 b717ce9b 2022-02-04 o }
197 b717ce9b 2022-02-04 o
198 b717ce9b 2022-02-04 o // Returns an error message in the same format as returned by the Icinga2 API.
199 b717ce9b 2022-02-04 o func jsonError(err error) string {
200 b717ce9b 2022-02-04 o return fmt.Sprintf("{ %q: %q }", "status", err.Error())
201 b717ce9b 2022-02-04 o }
202 b717ce9b 2022-02-04 o
203 b717ce9b 2022-02-04 o var notFoundResponse string = `{
204 b717ce9b 2022-02-04 o "error": 404,
205 b717ce9b 2022-02-04 o "status": "No objects found."
206 b717ce9b 2022-02-04 o }`
207 b717ce9b 2022-02-04 o
208 b717ce9b 2022-02-04 o var alreadyExistsResponse string = `
209 b717ce9b 2022-02-04 o {
210 b717ce9b 2022-02-04 o "results": [
211 b717ce9b 2022-02-04 o {
212 b717ce9b 2022-02-04 o "code": 500,
213 b717ce9b 2022-02-04 o "errors": [
214 b717ce9b 2022-02-04 o "Object already exists."
215 b717ce9b 2022-02-04 o ],
216 b717ce9b 2022-02-04 o "status": "Object could not be created."
217 b717ce9b 2022-02-04 o }
218 b717ce9b 2022-02-04 o ]
219 b717ce9b 2022-02-04 o }`
220 b717ce9b 2022-02-04 o
221 b717ce9b 2022-02-04 o func (srv *fakeServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
222 b717ce9b 2022-02-04 o if req.URL.RawQuery != "" {
223 b717ce9b 2022-02-04 o http.Error(w, jsonError(errors.New("query parameters unimplemented")), http.StatusBadRequest)
224 b717ce9b 2022-02-04 o return
225 b717ce9b 2022-02-04 o }
226 b717ce9b 2022-02-04 o
227 b717ce9b 2022-02-04 o switch {
228 b717ce9b 2022-02-04 o case path.Base(req.URL.Path) == "v1":
229 b717ce9b 2022-02-04 o srv.Permissions(w)
230 b717ce9b 2022-02-04 o return
231 b717ce9b 2022-02-04 o case strings.HasPrefix(req.URL.Path, "/v1/objects"):
232 b717ce9b 2022-02-04 o srv.ObjectsHandler(w, req)
233 b717ce9b 2022-02-04 o return
234 b717ce9b 2022-02-04 o }
235 b717ce9b 2022-02-04 o http.Error(w, jsonError(errors.New(req.URL.Path+" unimplemented")), http.StatusNotFound)
236 b717ce9b 2022-02-04 o }
237 b717ce9b 2022-02-04 o
238 b717ce9b 2022-02-04 o func (f *fakeServer) Permissions(w http.ResponseWriter) {
239 62bf235c 2022-02-04 o fmt.Fprint(w, `{"results": [{
240 62bf235c 2022-02-04 o "info": "Fake Icinga2 server",
241 62bf235c 2022-02-04 o "permissions": ["*"],
242 62bf235c 2022-02-04 o "user": "icinga",
243 62bf235c 2022-02-04 o "version": "fake"
244 62bf235c 2022-02-04 o }]}`)
245 62bf235c 2022-02-04 o
246 b717ce9b 2022-02-04 o }
247 b717ce9b 2022-02-04 o
248 b717ce9b 2022-02-04 o type apiResponse struct {
249 b717ce9b 2022-02-04 o Results []apiResult `json:"results"`
250 b717ce9b 2022-02-04 o Status string `json:"status,omitempty"`
251 b717ce9b 2022-02-04 o }
252 b717ce9b 2022-02-04 o
253 b717ce9b 2022-02-04 o type apiResult struct {
254 62bf235c 2022-02-04 o Name string `json:"name"`
255 62bf235c 2022-02-04 o Type string `json:"type"`
256 b717ce9b 2022-02-04 o Attrs attributes `json:"attrs"`
257 b717ce9b 2022-02-04 o }
258 b717ce9b 2022-02-04 o
259 b717ce9b 2022-02-04 o // attributes represent configuration object attributes
260 b717ce9b 2022-02-04 o type attributes map[string]interface{}
261 b717ce9b 2022-02-04 o
262 b717ce9b 2022-02-04 o // objType returns the icinga2 object type name from an API request path.
263 b717ce9b 2022-02-04 o // For example from "objects/services/test" the type name is "Service".
264 b717ce9b 2022-02-04 o func objType(path string) string {
265 b717ce9b 2022-02-04 o var t string
266 b717ce9b 2022-02-04 o a := strings.Split(path, "/")
267 b717ce9b 2022-02-04 o for i := range a {
268 b717ce9b 2022-02-04 o if a[i] == "objects" {
269 b717ce9b 2022-02-04 o t = a[i+1] // services
270 b717ce9b 2022-02-04 o }
271 b717ce9b 2022-02-04 o }
272 b717ce9b 2022-02-04 o return strings.TrimSuffix(strings.Title(t), "s") // Services to Service
273 b717ce9b 2022-02-04 o }
274 b717ce9b 2022-02-04 o
275 b717ce9b 2022-02-04 o func (srv *fakeServer) ObjectsHandler(w http.ResponseWriter, req *http.Request) {
276 b717ce9b 2022-02-04 o name := strings.TrimPrefix(req.URL.Path, "/v1/")
277 b717ce9b 2022-02-04 o switch req.Method {
278 b717ce9b 2022-02-04 o case http.MethodPut:
279 b717ce9b 2022-02-04 o if _, ok := srv.objects[name]; ok {
280 b717ce9b 2022-02-04 o http.Error(w, alreadyExistsResponse, http.StatusInternalServerError)
281 b717ce9b 2022-02-04 o return
282 b717ce9b 2022-02-04 o }
283 b717ce9b 2022-02-04 o srv.CreateObject(w, req)
284 b717ce9b 2022-02-04 o case http.MethodGet:
285 b717ce9b 2022-02-04 o srv.GetObject(w, req)
286 b717ce9b 2022-02-04 o case http.MethodDelete:
287 b717ce9b 2022-02-04 o if _, ok := srv.objects[name]; !ok {
288 b717ce9b 2022-02-04 o http.Error(w, notFoundResponse, http.StatusNotFound)
289 b717ce9b 2022-02-04 o return
290 b717ce9b 2022-02-04 o }
291 b717ce9b 2022-02-04 o delete(srv.objects, name)
292 b717ce9b 2022-02-04 o default:
293 b717ce9b 2022-02-04 o err := fmt.Errorf("%s unimplemented", req.Method)
294 b717ce9b 2022-02-04 o http.Error(w, jsonError(err), http.StatusMethodNotAllowed)
295 b717ce9b 2022-02-04 o }
296 b717ce9b 2022-02-04 o }
297 b717ce9b 2022-02-04 o
298 b717ce9b 2022-02-04 o func (srv *fakeServer) GetObject(w http.ResponseWriter, req *http.Request) {
299 b717ce9b 2022-02-04 o name := strings.TrimPrefix(req.URL.Path, "/v1/")
300 b717ce9b 2022-02-04 o attrs, ok := srv.objects[name]
301 b717ce9b 2022-02-04 o if !ok {
302 b717ce9b 2022-02-04 o http.Error(w, notFoundResponse, http.StatusNotFound)
303 b717ce9b 2022-02-04 o return
304 b717ce9b 2022-02-04 o }
305 b717ce9b 2022-02-04 o resp := apiResponse{
306 b717ce9b 2022-02-04 o Results: []apiResult{
307 b717ce9b 2022-02-04 o apiResult{
308 62bf235c 2022-02-04 o Name: path.Base(req.URL.Path),
309 62bf235c 2022-02-04 o Type: objType(req.URL.Path),
310 b717ce9b 2022-02-04 o Attrs: attrs,
311 b717ce9b 2022-02-04 o },
312 b717ce9b 2022-02-04 o },
313 b717ce9b 2022-02-04 o }
314 b717ce9b 2022-02-04 o json.NewEncoder(w).Encode(&resp)
315 b717ce9b 2022-02-04 o }
316 b717ce9b 2022-02-04 o
317 b717ce9b 2022-02-04 o func (srv *fakeServer) CreateObject(w http.ResponseWriter, req *http.Request) {
318 b717ce9b 2022-02-04 o defer req.Body.Close()
319 b717ce9b 2022-02-04 o m := make(map[string]attributes)
320 b717ce9b 2022-02-04 o if err := json.NewDecoder(req.Body).Decode(&m); err != nil {
321 b717ce9b 2022-02-04 o panic(err)
322 b717ce9b 2022-02-04 o }
323 b717ce9b 2022-02-04 o name := strings.TrimPrefix(req.URL.Path, "/v1/")
324 b717ce9b 2022-02-04 o srv.objects[name] = m["attrs"]
325 b717ce9b 2022-02-04 o }
326 b717ce9b 2022-02-04 o
327 b717ce9b 2022-02-04 o func TestDuplicateCreateDelete(t *testing.T) {
328 b717ce9b 2022-02-04 o srv := newFakeServer()
329 b717ce9b 2022-02-04 o defer srv.Close()
330 b717ce9b 2022-02-04 o client, err := icinga.Dial(srv.Listener.Addr().String(), "root", "icinga", srv.Client())
331 b717ce9b 2022-02-04 o if err != nil {
332 b717ce9b 2022-02-04 o t.Fatal(err)
333 b717ce9b 2022-02-04 o }
334 b717ce9b 2022-02-04 o
335 b717ce9b 2022-02-04 o host := randomHosts(1, ".example.org")[0]
336 b717ce9b 2022-02-04 o if err := client.CreateHost(host); err != nil {
337 b717ce9b 2022-02-04 o t.Fatal(err)
338 b717ce9b 2022-02-04 o }
339 b717ce9b 2022-02-04 o if err := client.CreateHost(host); !errors.Is(err, icinga.ErrExist) {
340 b717ce9b 2022-02-04 o t.Errorf("want %s got %v", icinga.ErrExist, err)
341 b717ce9b 2022-02-04 o }
342 b717ce9b 2022-02-04 o host, err = client.LookupHost(host.Name)
343 b717ce9b 2022-02-04 o if err != nil {
344 b717ce9b 2022-02-04 o t.Error(err)
345 b717ce9b 2022-02-04 o }
346 b717ce9b 2022-02-04 o if err := client.DeleteHost(host.Name, false); err != nil {
347 b717ce9b 2022-02-04 o t.Error(err)
348 b717ce9b 2022-02-04 o }
349 b717ce9b 2022-02-04 o if err := client.DeleteHost(host.Name, false); !errors.Is(err, icinga.ErrNotExist) {
350 b717ce9b 2022-02-04 o t.Errorf("want icinga.ErrNotExist got %s", err)
351 b717ce9b 2022-02-04 o }
352 b717ce9b 2022-02-04 o _, err = client.LookupHost(host.Name)
353 b717ce9b 2022-02-04 o if !errors.Is(err, icinga.ErrNotExist) {
354 b717ce9b 2022-02-04 o t.Errorf("want icinga.ErrNotExist got %s", err)
355 b717ce9b 2022-02-04 o }
356 b717ce9b 2022-02-04 o if err := client.CreateHost(host); err != nil {
357 b717ce9b 2022-02-04 o t.Error(err)
358 b717ce9b 2022-02-04 o }
359 b717ce9b 2022-02-04 o host, err = client.LookupHost(host.Name)
360 b717ce9b 2022-02-04 o if err != nil {
361 b717ce9b 2022-02-04 o t.Error(err)
362 b717ce9b 2022-02-04 o }
363 b717ce9b 2022-02-04 o }