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 Address: randomTestAddr(),
43 }
44 hosts = append(hosts, h)
45 }
46 return hosts
47 }
49 func newTestClient(t *testing.T) *icinga.Client {
50 tp := http.DefaultTransport.(*http.Transport)
51 tp.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
52 c := &http.Client{Transport: tp}
53 if client, err := icinga.Dial("::1:5665", "icinga", "icinga", c); err == nil {
54 return client
55 }
56 client, err := icinga.Dial("127.0.0.1:5665", "icinga", "icinga", c)
57 if err == nil {
58 return client
59 }
60 t.Skipf("cannot dial local icinga: %v", err)
61 return nil
62 }
64 func compareStringSlice(a, b []string) bool {
65 if len(a) != len(b) {
66 return false
67 }
68 for i := range a {
69 if a[i] != b[i] {
70 return false
71 }
72 }
73 return true
74 }
76 func TestFilter(t *testing.T) {
77 client := newTestClient(t)
78 var want, got []string // host names
80 hosts := randomHosts(10, "example.org")
81 for i := range hosts {
82 if err := client.CreateHost(hosts[i]); err != nil {
83 t.Fatal(err)
84 }
85 want = append(want, hosts[i].Name)
86 }
87 t.Cleanup(func() {
88 for i := range hosts {
89 if err := client.DeleteHost(hosts[i].Name, true); err != nil {
90 t.Error(err)
91 }
92 }
93 })
95 filter := `match("*example.org", host.name)`
96 hosts, err := client.Hosts(filter)
97 if err != nil {
98 t.Fatal(err)
99 }
100 for i := range hosts {
101 got = append(got, hosts[i].Name)
104 sort.Strings(want)
105 sort.Strings(got)
106 if !compareStringSlice(want, got) {
107 t.Error("want", want, "got", got)
111 func TestUserRoundTrip(t *testing.T) {
112 client := newTestClient(t)
113 want := icinga.User{Name: "olly", Email: "olly@example.com", Groups: []string{}}
114 if err := client.CreateUser(want); err != nil && !errors.Is(err, icinga.ErrExist) {
115 t.Fatal(err)
117 defer func() {
118 if err := client.DeleteUser(want.Name, false); err != nil {
119 t.Error(err)
121 }()
122 got, err := client.LookupUser(want.Name)
123 if err != nil {
124 t.Fatal(err)
126 if !reflect.DeepEqual(want, got) {
127 t.Errorf("want %+v, got %+v", want, got)
131 func TestChecker(t *testing.T) {
132 client := newTestClient(t)
133 h := randomHosts(1, ".checker.example")[0]
134 if err := client.CreateHost(h); err != nil {
135 t.Fatal(err)
137 defer client.DeleteHost(h.Name, true)
138 svc := icinga.Service{
139 Name: h.Name + "!http",
140 CheckCommand: "http",
142 if err := svc.Check(client); err == nil {
143 t.Error("nil error checking non-existent service")
145 if err := client.CreateService(svc); err != nil {
146 t.Fatal(err)
148 if err := svc.Check(client); err != nil {
149 t.Error(err)
151 if err := client.DeleteService(svc.Name, false); err != nil {
152 t.Error(err)
156 func TestCheckHostGroup(t *testing.T) {
157 client := newTestClient(t)
158 hostgroup := icinga.HostGroup{Name: "test", DisplayName: "Test Group"}
159 if err := client.CreateHostGroup(hostgroup); err != nil && !errors.Is(err, icinga.ErrExist) {
160 t.Fatal(err)
162 defer client.DeleteHostGroup(hostgroup.Name, false)
163 hostgroup, err := client.LookupHostGroup(hostgroup.Name)
164 if err != nil {
165 t.Fatal(err)
167 hosts := randomHosts(10, "example.org")
168 for _, h := range hosts {
169 h.Groups = []string{hostgroup.Name}
170 if err := client.CreateHost(h); err != nil {
171 t.Fatal(err)
173 defer client.DeleteHost(h.Name, false)
175 if err := hostgroup.Check(client); err != nil {
176 t.Fatal(err)
180 func TestNonExistentService(t *testing.T) {
181 client := newTestClient(t)
182 filter := `match("blablabla", service.name)`
183 service, err := client.Services(filter)
184 if err == nil {
185 t.Error("non-nil error TODO")
186 t.Log(service)
190 type fakeServer struct {
191 objects map[string]attributes
194 func newFakeServer() *httptest.Server {
195 return httptest.NewTLSServer(&fakeServer{objects: make(map[string]attributes)})
198 // Returns an error message in the same format as returned by the Icinga2 API.
199 func jsonError(err error) string {
200 return fmt.Sprintf("{ %q: %q }", "status", err.Error())
203 var notFoundResponse string = `{
204 "error": 404,
205 "status": "No objects found."
206 }`
208 var alreadyExistsResponse string = `
210 "results": [
212 "code": 500,
213 "errors": [
214 "Object already exists."
215 ],
216 "status": "Object could not be created."
219 }`
221 func (srv *fakeServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
222 if req.URL.RawQuery != "" {
223 http.Error(w, jsonError(errors.New("query parameters unimplemented")), http.StatusBadRequest)
224 return
227 switch {
228 case path.Base(req.URL.Path) == "v1":
229 srv.Permissions(w)
230 return
231 case strings.HasPrefix(req.URL.Path, "/v1/objects"):
232 srv.ObjectsHandler(w, req)
233 return
235 http.Error(w, jsonError(errors.New(req.URL.Path+" unimplemented")), http.StatusNotFound)
238 func (f *fakeServer) Permissions(w http.ResponseWriter) {
239 fmt.Fprint(w, `{"results": [{
240 "info": "Fake Icinga2 server",
241 "permissions": ["*"],
242 "user": "icinga",
243 "version": "fake"
244 }]}`)
248 type apiResponse struct {
249 Results []apiResult `json:"results"`
250 Status string `json:"status,omitempty"`
253 type apiResult struct {
254 Name string `json:"name"`
255 Type string `json:"type"`
256 Attrs attributes `json:"attrs"`
259 // attributes represent configuration object attributes
260 type attributes map[string]interface{}
262 // objType returns the icinga2 object type name from an API request path.
263 // For example from "objects/services/test" the type name is "Service".
264 func objType(path string) string {
265 var t string
266 a := strings.Split(path, "/")
267 for i := range a {
268 if a[i] == "objects" {
269 t = a[i+1] // services
272 return strings.TrimSuffix(strings.Title(t), "s") // Services to Service
275 func (srv *fakeServer) ObjectsHandler(w http.ResponseWriter, req *http.Request) {
276 name := strings.TrimPrefix(req.URL.Path, "/v1/")
277 switch req.Method {
278 case http.MethodPut:
279 if _, ok := srv.objects[name]; ok {
280 http.Error(w, alreadyExistsResponse, http.StatusInternalServerError)
281 return
283 srv.CreateObject(w, req)
284 case http.MethodGet:
285 srv.GetObject(w, req)
286 case http.MethodDelete:
287 if _, ok := srv.objects[name]; !ok {
288 http.Error(w, notFoundResponse, http.StatusNotFound)
289 return
291 delete(srv.objects, name)
292 default:
293 err := fmt.Errorf("%s unimplemented", req.Method)
294 http.Error(w, jsonError(err), http.StatusMethodNotAllowed)
298 func (srv *fakeServer) GetObject(w http.ResponseWriter, req *http.Request) {
299 name := strings.TrimPrefix(req.URL.Path, "/v1/")
300 attrs, ok := srv.objects[name]
301 if !ok {
302 http.Error(w, notFoundResponse, http.StatusNotFound)
303 return
305 resp := apiResponse{
306 Results: []apiResult{
307 apiResult{
308 Name: path.Base(req.URL.Path),
309 Type: objType(req.URL.Path),
310 Attrs: attrs,
311 },
312 },
314 json.NewEncoder(w).Encode(&resp)
317 func (srv *fakeServer) CreateObject(w http.ResponseWriter, req *http.Request) {
318 defer req.Body.Close()
319 m := make(map[string]attributes)
320 if err := json.NewDecoder(req.Body).Decode(&m); err != nil {
321 panic(err)
323 name := strings.TrimPrefix(req.URL.Path, "/v1/")
324 srv.objects[name] = m["attrs"]
327 func TestDuplicateCreateDelete(t *testing.T) {
328 srv := newFakeServer()
329 defer srv.Close()
330 client, err := icinga.Dial(srv.Listener.Addr().String(), "root", "icinga", srv.Client())
331 if err != nil {
332 t.Fatal(err)
335 host := randomHosts(1, ".example.org")[0]
336 if err := client.CreateHost(host); err != nil {
337 t.Fatal(err)
339 if err := client.CreateHost(host); !errors.Is(err, icinga.ErrExist) {
340 t.Errorf("want %s got %v", icinga.ErrExist, err)
342 host, err = client.LookupHost(host.Name)
343 if err != nil {
344 t.Error(err)
346 if err := client.DeleteHost(host.Name, false); err != nil {
347 t.Error(err)
349 if err := client.DeleteHost(host.Name, false); !errors.Is(err, icinga.ErrNotExist) {
350 t.Errorf("want icinga.ErrNotExist got %s", err)
352 _, err = client.LookupHost(host.Name)
353 if !errors.Is(err, icinga.ErrNotExist) {
354 t.Errorf("want icinga.ErrNotExist got %s", err)
356 if err := client.CreateHost(host); err != nil {
357 t.Error(err)
359 host, err = client.LookupHost(host.Name)
360 if err != nil {
361 t.Error(err)