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 Groups: []string{"example"},
43 Address: randomTestAddr(),
45 hosts = append(hosts, h)
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 {
57 client, err := icinga.Dial("127.0.0.1:5665", "icinga", "icinga", c)
61 t.Skipf("cannot dial local icinga: %v", err)
65 func compareStringSlice(a, b []string) bool {
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 {
86 want = append(want, hosts[i].Name)
89 for i := range hosts {
90 if err := client.DeleteHost(hosts[i].Name, true); err != nil {
96 filter := `match("*example.org", host.name)`
97 hosts, err := client.Hosts(filter)
101 for i := range hosts {
102 got = append(got, hosts[i].Name)
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) {
119 if err := client.DeleteUser(want.Name, false); err != nil {
123 got, err := client.LookupUser(want.Name)
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 {
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 {
149 if err := svc.Check(client); err != nil {
152 if err := client.DeleteService(svc.Name, false); err != nil {
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) {
163 defer client.DeleteHostGroup(hostgroup.Name, false)
164 hostgroup, err := client.LookupHostGroup(hostgroup.Name)
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 {
174 defer client.DeleteHost(h.Name, false)
176 if err := hostgroup.Check(client); err != nil {
181 func TestNonExistentService(t *testing.T) {
182 client := newTestClient(t)
183 filter := `match("blablabla", service.name)`
184 service, err := client.Services(filter)
186 t.Error("non-nil error TODO")
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 = `{
206 "status": "No objects found."
209 var alreadyExistsResponse string = `
215 "Object already exists."
217 "status": "Object could not be created."
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)
229 case path.Base(req.URL.Path) == "v1":
232 case strings.HasPrefix(req.URL.Path, "/v1/objects"):
233 srv.ObjectsHandler(w, req)
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": ["*"],
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 {
267 a := strings.Split(path, "/")
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/")
280 if _, ok := srv.objects[name]; ok {
281 http.Error(w, alreadyExistsResponse, http.StatusInternalServerError)
284 srv.CreateObject(w, req)
286 srv.GetObject(w, req)
287 case http.MethodDelete:
288 if _, ok := srv.objects[name]; !ok {
289 http.Error(w, notFoundResponse, http.StatusNotFound)
292 delete(srv.objects, name)
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]
303 http.Error(w, notFoundResponse, http.StatusNotFound)
307 Results: []apiResult{
309 Name: path.Base(req.URL.Path),
310 Type: objType(req.URL.Path),
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 {
324 name := strings.TrimPrefix(req.URL.Path, "/v1/")
325 srv.objects[name] = m["attrs"]
328 func TestDuplicateCreateDelete(t *testing.T) {
329 srv := newFakeServer()
331 client, err := icinga.Dial(srv.Listener.Addr().String(), "root", "icinga", srv.Client())
336 host := randomHosts(1, ".example.org")[0]
337 if err := client.CreateHost(host); err != nil {
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)
347 if err := client.DeleteHost(host.Name, false); err != nil {
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 {
360 host, err = client.LookupHost(host.Name)