22 rand.Seed(time.Now().Unix())
25 func randomHostname(suffix string) string {
26 var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
29 b[i] = letters[rand.Intn(len(letters))]
31 return string(b) + suffix
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++ {
40 Name: randomHostname(suffix),
41 CheckCommand: "random",
42 Address: randomTestAddr(),
44 hosts = append(hosts, h)
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 {
56 client, err := icinga.Dial("127.0.0.1:5665", "icinga", "icinga", c)
60 t.Skipf("cannot dial local icinga: %v", err)
64 func compareStringSlice(a, b []string) bool {
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 {
85 want = append(want, hosts[i].Name)
88 for i := range hosts {
89 if err := client.DeleteHost(hosts[i].Name, true); err != nil {
95 filter := `match("*example.org", host.name)`
96 hosts, err := client.Hosts(filter)
100 for i := range hosts {
101 got = append(got, hosts[i].Name)
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) {
118 if err := client.DeleteUser(want.Name, false); err != nil {
122 got, err := client.LookupUser(want.Name)
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 {
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 {
148 if err := svc.Check(client); err != nil {
151 if err := client.DeleteService(svc.Name, false); err != nil {
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) {
162 defer client.DeleteHostGroup(hostgroup.Name, false)
163 hostgroup, err := client.LookupHostGroup(hostgroup.Name)
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 {
173 defer client.DeleteHost(h.Name, false)
175 if err := hostgroup.Check(client); err != nil {
180 func TestNonExistentService(t *testing.T) {
181 client := newTestClient(t)
182 filter := `match("blablabla", service.name)`
183 service, err := client.Services(filter)
185 t.Error("non-nil error TODO")
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 = `{
205 "status": "No objects found."
208 var alreadyExistsResponse string = `
214 "Object already exists."
216 "status": "Object could not be created."
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)
228 case path.Base(req.URL.Path) == "v1":
231 case strings.HasPrefix(req.URL.Path, "/v1/objects"):
232 srv.ObjectsHandler(w, req)
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": ["*"],
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 {
266 a := strings.Split(path, "/")
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/")
279 if _, ok := srv.objects[name]; ok {
280 http.Error(w, alreadyExistsResponse, http.StatusInternalServerError)
283 srv.CreateObject(w, req)
285 srv.GetObject(w, req)
286 case http.MethodDelete:
287 if _, ok := srv.objects[name]; !ok {
288 http.Error(w, notFoundResponse, http.StatusNotFound)
291 delete(srv.objects, name)
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]
302 http.Error(w, notFoundResponse, http.StatusNotFound)
306 Results: []apiResult{
308 Name: path.Base(req.URL.Path),
309 Type: objType(req.URL.Path),
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 {
323 name := strings.TrimPrefix(req.URL.Path, "/v1/")
324 srv.objects[name] = m["attrs"]
327 func TestDuplicateCreateDelete(t *testing.T) {
328 srv := newFakeServer()
330 client, err := icinga.Dial(srv.Listener.Addr().String(), "root", "icinga", srv.Client())
335 host := randomHosts(1, ".example.org")[0]
336 if err := client.CreateHost(host); err != nil {
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)
346 if err := client.DeleteHost(host.Name, false); err != nil {
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 {
359 host, err = client.LookupHost(host.Name)