1 191337d0 2022-01-18 o package icinga_test
10 b717ce9b 2022-02-04 o "net/http/httptest"
18 191337d0 2022-01-18 o "olowe.co/icinga"
22 d762d1d1 2022-02-01 o rand.Seed(time.Now().Unix())
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))]
31 d762d1d1 2022-02-01 o return string(b) + suffix
34 b717ce9b 2022-02-04 o func randomTestAddr() string { return fmt.Sprintf("192.0.2.%d", rand.Intn(254)) }
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(),
44 7cb145ba 2022-01-18 o hosts = append(hosts, h)
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 {
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 {
60 b717ce9b 2022-02-04 o t.Skipf("cannot dial local icinga: %v", err)
64 191337d0 2022-01-18 o func compareStringSlice(a, b []string) bool {
65 191337d0 2022-01-18 o if len(a) != len(b) {
68 d762d1d1 2022-02-01 o for i := range a {
69 d762d1d1 2022-02-01 o if a[i] != b[i] {
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
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 {
85 b717ce9b 2022-02-04 o want = append(want, hosts[i].Name)
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 {
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 {
100 b717ce9b 2022-02-04 o for i := range hosts {
101 b717ce9b 2022-02-04 o got = append(got, hosts[i].Name)
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)
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) {
117 191337d0 2022-01-18 o defer func() {
118 02a1a100 2022-01-18 o if err := client.DeleteUser(want.Name, false); err != nil {
122 191337d0 2022-01-18 o got, err := client.LookupUser(want.Name)
123 191337d0 2022-01-18 o if err != nil {
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)
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 {
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",
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")
145 b717ce9b 2022-02-04 o if err := client.CreateService(svc); err != nil {
148 b717ce9b 2022-02-04 o if err := svc.Check(client); err != nil {
151 b717ce9b 2022-02-04 o if err := client.DeleteService(svc.Name, false); err != nil {
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) {
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 {
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 {
173 b717ce9b 2022-02-04 o defer client.DeleteHost(h.Name, false)
175 7cb145ba 2022-01-18 o if err := hostgroup.Check(client); err != nil {
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)
190 b717ce9b 2022-02-04 o type fakeServer struct {
191 b717ce9b 2022-02-04 o objects map[string]attributes
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)})
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())
203 b717ce9b 2022-02-04 o var notFoundResponse string = `{
205 b717ce9b 2022-02-04 o "status": "No objects found."
208 b717ce9b 2022-02-04 o var alreadyExistsResponse string = `
214 b717ce9b 2022-02-04 o "Object already exists."
216 b717ce9b 2022-02-04 o "status": "Object could not be created."
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)
228 b717ce9b 2022-02-04 o case path.Base(req.URL.Path) == "v1":
229 b717ce9b 2022-02-04 o srv.Permissions(w)
231 b717ce9b 2022-02-04 o case strings.HasPrefix(req.URL.Path, "/v1/objects"):
232 b717ce9b 2022-02-04 o srv.ObjectsHandler(w, req)
235 b717ce9b 2022-02-04 o http.Error(w, jsonError(errors.New(req.URL.Path+" unimplemented")), http.StatusNotFound)
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"
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"`
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"`
259 b717ce9b 2022-02-04 o // attributes represent configuration object attributes
260 b717ce9b 2022-02-04 o type attributes map[string]interface{}
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 {
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
272 b717ce9b 2022-02-04 o return strings.TrimSuffix(strings.Title(t), "s") // Services to Service
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)
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)
291 b717ce9b 2022-02-04 o delete(srv.objects, name)
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)
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]
302 b717ce9b 2022-02-04 o http.Error(w, notFoundResponse, http.StatusNotFound)
305 b717ce9b 2022-02-04 o resp := apiResponse{
306 b717ce9b 2022-02-04 o Results: []apiResult{
308 62bf235c 2022-02-04 o Name: path.Base(req.URL.Path),
309 62bf235c 2022-02-04 o Type: objType(req.URL.Path),
314 b717ce9b 2022-02-04 o json.NewEncoder(w).Encode(&resp)
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 {
323 b717ce9b 2022-02-04 o name := strings.TrimPrefix(req.URL.Path, "/v1/")
324 b717ce9b 2022-02-04 o srv.objects[name] = m["attrs"]
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 {
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 {
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)
342 b717ce9b 2022-02-04 o host, err = client.LookupHost(host.Name)
343 b717ce9b 2022-02-04 o if err != nil {
346 b717ce9b 2022-02-04 o if err := client.DeleteHost(host.Name, false); err != nil {
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)
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)
356 b717ce9b 2022-02-04 o if err := client.CreateHost(host); err != nil {
359 b717ce9b 2022-02-04 o host, err = client.LookupHost(host.Name)
360 b717ce9b 2022-02-04 o if err != nil {