// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // +build !integration package status import ( "bufio" "net" "net/http" "net/http/httptest" "os" "path/filepath" "sync" "testing" "time" "github.com/elastic/beats/libbeat/common" mbtest "github.com/elastic/beats/metricbeat/mb/testing" "github.com/stretchr/testify/assert" ) // response is a raw response copied from an Apache web server. const response = `apache ServerVersion: Apache/2.4.18 (Unix) ServerMPM: event Server Built: Mar 2 2016 21:08:47 CurrentTime: Thursday, 12-May-2016 20:30:25 UTC RestartTime: Saturday, 30-Apr-2016 23:17:22 UTC ParentServerConfigGeneration: 1 ParentServerMPMGeneration: 0 ServerUptimeSeconds: 1026782 ServerUptime: 11 days 21 hours 13 minutes 2 seconds Load1: 0.02 Load5: 0.01 Load15: 0.05 Total Accesses: 167 Total kBytes: 63 CPUUser: 14076.6 CPUSystem: 6750.8 CPUChildrenUser: 10.1 CPUChildrenSystem: 11.2 CPULoad: 2.02841 Uptime: 1026782 ReqPerSec: .000162644 BytesPerSec: .0628293 BytesPerReq: 386.299 BusyWorkers: 1 IdleWorkers: 99 ConnsTotal: 6 ConnsAsyncWriting: 1 ConnsAsyncKeepAlive: 2 ConnsAsyncClosing: 3 Scoreboard: __________________________________________________________________________________W_________________............................................................................................................................................................................................................................................................................................................` // TestFetchEventContents verifies the contents of the returned event against // the raw Apache response. func TestFetchEventContents(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "text/plain; charset=ISO-8859-1") w.Write([]byte(response)) })) defer server.Close() config := map[string]interface{}{ "module": "apache", "metricsets": []string{"status"}, "hosts": []string{server.URL}, } f := mbtest.NewEventFetcher(t, config) event, err := f.Fetch() if !assert.NoError(t, err) { t.FailNow() } t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event.StringToPrint()) assert.Equal(t, 386.299, event["bytes_per_request"]) assert.Equal(t, .0628293, event["bytes_per_sec"]) workers := event["workers"].(common.MapStr) assert.EqualValues(t, 1, workers["busy"]) assert.EqualValues(t, 99, workers["idle"]) connections := event["connections"].(common.MapStr) async := connections["async"].(common.MapStr) assert.EqualValues(t, 3, async["closing"]) assert.EqualValues(t, 2, async["keep_alive"]) assert.EqualValues(t, 1, async["writing"]) assert.EqualValues(t, 6, connections["total"]) cpu := event["cpu"].(common.MapStr) assert.Equal(t, 11.2, cpu["children_system"]) assert.Equal(t, 10.1, cpu["children_user"]) assert.Equal(t, 2.02841, cpu["load"]) assert.Equal(t, 6750.8, cpu["system"]) assert.Equal(t, 14076.6, cpu["user"]) assert.Equal(t, server.URL[7:], event["hostname"]) load := event["load"].(common.MapStr) assert.Equal(t, .02, load["1"]) assert.Equal(t, .05, load["15"]) assert.Equal(t, .01, load["5"]) assert.Equal(t, .000162644, event["requests_per_sec"]) scoreboard := event["scoreboard"].(common.MapStr) assert.Equal(t, 0, scoreboard["closing_connection"]) assert.Equal(t, 0, scoreboard["dns_lookup"]) assert.Equal(t, 0, scoreboard["gracefully_finishing"]) assert.Equal(t, 0, scoreboard["idle_cleanup"]) assert.Equal(t, 0, scoreboard["keepalive"]) assert.Equal(t, 0, scoreboard["logging"]) assert.Equal(t, 300, scoreboard["open_slot"]) // Number of '.' assert.Equal(t, 0, scoreboard["reading_request"]) assert.Equal(t, 1, scoreboard["sending_reply"]) // Number of 'W' assert.Equal(t, 400, scoreboard["total"]) // Number of scorecard chars. assert.Equal(t, 99, scoreboard["waiting_for_connection"]) // Number of '_' assert.EqualValues(t, 167, event["total_accesses"]) assert.EqualValues(t, 63, event["total_kbytes"]) uptime := event["uptime"].(common.MapStr) assert.EqualValues(t, 1026782, uptime["uptime"]) assert.EqualValues(t, 1026782, uptime["server_uptime"]) } // TestFetchTimeout verifies that the HTTP request times out and an error is // returned. func TestFetchTimeout(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "text/plain; charset=ISO-8859-1") w.Write([]byte(response)) <-r.Context().Done() })) defer server.Close() config := map[string]interface{}{ "module": "apache", "metricsets": []string{"status"}, "hosts": []string{server.URL}, "timeout": "50ms", } f := mbtest.NewEventFetcher(t, config) start := time.Now() _, err := f.Fetch() elapsed := time.Since(start) if assert.Error(t, err) { assert.Contains(t, err.Error(), "request canceled (Client.Timeout exceeded") } // Elapsed should be ~50ms, sometimes it can be up to 1s assert.True(t, elapsed < 5*time.Second, "elapsed time: %s", elapsed.String()) } // TestMultipleFetches verifies that the server connection is reused when HTTP // keep-alive is supported by the server. func TestMultipleFetches(t *testing.T) { server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Header().Set("Content-Type", "text/plain; charset=ISO-8859-1") w.Write([]byte(response)) })) connLock := sync.Mutex{} conns := map[string]struct{}{} server.Config.ConnState = func(conn net.Conn, state http.ConnState) { connLock.Lock() conns[conn.RemoteAddr().String()] = struct{}{} connLock.Unlock() } server.Start() defer server.Close() config := map[string]interface{}{ "module": "apache", "metricsets": []string{"status"}, "hosts": []string{server.URL}, } f := mbtest.NewEventFetcher(t, config) for i := 0; i < 20; i++ { _, err := f.Fetch() if !assert.NoError(t, err) { t.FailNow() } } connLock.Lock() assert.Len(t, conns, 1, "only a single connection should exist because of keep-alives") connLock.Unlock() } func TestHostParser(t *testing.T) { var tests = []struct { host string url string err string }{ {"", "", "empty host"}, {":80", "", "empty host"}, {"localhost", "http://localhost/server-status?auto=", ""}, {"localhost/ServerStatus", "http://localhost/ServerStatus?auto=", ""}, {"127.0.0.1", "http://127.0.0.1/server-status?auto=", ""}, {"https://127.0.0.1", "https://127.0.0.1/server-status?auto=", ""}, {"[2001:db8:0:1]:80", "http://[2001:db8:0:1]:80/server-status?auto=", ""}, {"https://admin:secret@127.0.0.1", "https://admin:secret@127.0.0.1/server-status?auto=", ""}, } for _, test := range tests { hostData, err := hostParser(mbtest.NewTestModule(t, map[string]interface{}{}), test.host) if err != nil && test.err != "" { assert.Contains(t, err.Error(), test.err) } else if assert.NoError(t, err, "unexpected error") { assert.Equal(t, test.url, hostData.URI) } } } // Test event mapping for different apache status outputs func TestStatusOutputs(t *testing.T) { files, err := filepath.Glob("./_meta/test/status_*") assert.NoError(t, err) for _, filename := range files { f, err := os.Open(filename) assert.NoError(t, err, "cannot open test file "+filename) scanner := bufio.NewScanner(f) _, err = eventMapping(scanner, "localhost") assert.NoError(t, err, "error mapping "+filename) } }