Blob


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