check_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. // Copyright 2016 Circonus, Inc. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package checkmgr
  5. import (
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "log"
  11. "net/http"
  12. "net/http/httptest"
  13. "net/url"
  14. "os"
  15. "strconv"
  16. "strings"
  17. "testing"
  18. "time"
  19. "github.com/circonus-labs/circonus-gometrics/api"
  20. "github.com/circonus-labs/circonus-gometrics/api/config"
  21. )
  22. var (
  23. cert = "{\"contents\":\"-----BEGIN CERTIFICATE-----\\nMIID4zCCA0ygAwIBAgIJAMelf8skwVWPMA0GCSqGSIb3DQEBBQUAMIGoMQswCQYD\\nVQQGEwJVUzERMA8GA1UECBMITWFyeWxhbmQxETAPBgNVBAcTCENvbHVtYmlhMRcw\\nFQYDVQQKEw5DaXJjb251cywgSW5jLjERMA8GA1UECxMIQ2lyY29udXMxJzAlBgNV\\nBAMTHkNpcmNvbnVzIENlcnRpZmljYXRlIEF1dGhvcml0eTEeMBwGCSqGSIb3DQEJ\\nARYPY2FAY2lyY29udXMubmV0MB4XDTA5MTIyMzE5MTcwNloXDTE5MTIyMTE5MTcw\\nNlowgagxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDERMA8GA1UEBxMI\\nQ29sdW1iaWExFzAVBgNVBAoTDkNpcmNvbnVzLCBJbmMuMREwDwYDVQQLEwhDaXJj\\nb251czEnMCUGA1UEAxMeQ2lyY29udXMgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR4w\\nHAYJKoZIhvcNAQkBFg9jYUBjaXJjb251cy5uZXQwgZ8wDQYJKoZIhvcNAQEBBQAD\\ngY0AMIGJAoGBAKz2X0/0vJJ4ad1roehFyxUXHdkjJA9msEKwT2ojummdUB3kK5z6\\nPDzDL9/c65eFYWqrQWVWZSLQK1D+v9xJThCe93v6QkSJa7GZkCq9dxClXVtBmZH3\\nhNIZZKVC6JMA9dpRjBmlFgNuIdN7q5aJsv8VZHH+QrAyr9aQmhDJAmk1AgMBAAGj\\nggERMIIBDTAdBgNVHQ4EFgQUyNTsgZHSkhhDJ5i+6IFlPzKYxsUwgd0GA1UdIwSB\\n1TCB0oAUyNTsgZHSkhhDJ5i+6IFlPzKYxsWhga6kgaswgagxCzAJBgNVBAYTAlVT\\nMREwDwYDVQQIEwhNYXJ5bGFuZDERMA8GA1UEBxMIQ29sdW1iaWExFzAVBgNVBAoT\\nDkNpcmNvbnVzLCBJbmMuMREwDwYDVQQLEwhDaXJjb251czEnMCUGA1UEAxMeQ2ly\\nY29udXMgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9jYUBj\\naXJjb251cy5uZXSCCQDHpX/LJMFVjzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\\nBQUAA4GBAAHBtl15BwbSyq0dMEBpEdQYhHianU/rvOMe57digBmox7ZkPEbB/baE\\nsYJysziA2raOtRxVRtcxuZSMij2RiJDsLxzIp1H60Xhr8lmf7qF6Y+sZl7V36KZb\\nn2ezaOoRtsQl9dhqEMe8zgL76p9YZ5E69Al0mgiifTteyNjjMuIW\\n-----END CERTIFICATE-----\\n\"}"
  24. testCheck = api.Check{
  25. CID: "/check/1234",
  26. Active: true,
  27. BrokerCID: "/broker/1234",
  28. CheckBundleCID: "/check_bundle/1234",
  29. CheckUUID: "abc123-a1b2-c3d4-e5f6-123abc",
  30. Details: map[config.Key]string{config.SubmissionURL: "http://127.0.0.1:43191/module/httptrap/abc123-a1b2-c3d4-e5f6-123abc/blah"},
  31. }
  32. testCheckBundle = api.CheckBundle{
  33. CheckUUIDs: []string{"abc123-a1b2-c3d4-e5f6-123abc"},
  34. Checks: []string{"/check/1234"},
  35. CID: "/check_bundle/1234",
  36. Created: 0,
  37. LastModified: 0,
  38. LastModifedBy: "",
  39. ReverseConnectURLs: []string{
  40. "mtev_reverse://127.0.0.1:43191/check/abc123-a1b2-c3d4-e5f6-123abc",
  41. },
  42. Brokers: []string{"/broker/1234"},
  43. DisplayName: "test check",
  44. Config: map[config.Key]string{
  45. config.SubmissionURL: "https://127.0.0.1:43191/module/httptrap/abc123-a1b2-c3d4-e5f6-123abc/blah",
  46. config.ReverseSecretKey: "blah",
  47. },
  48. Metrics: []api.CheckBundleMetric{
  49. {
  50. Name: "elmo",
  51. Type: "numeric",
  52. Status: "active",
  53. },
  54. },
  55. MetricLimit: 0,
  56. Notes: nil,
  57. Period: 60,
  58. Status: "active",
  59. Target: "127.0.0.1",
  60. Timeout: 10,
  61. Type: "httptrap",
  62. Tags: []string{},
  63. }
  64. testBroker = api.Broker{
  65. CID: "/broker/1234",
  66. Name: "test broker",
  67. Type: "enterprise",
  68. Details: []api.BrokerDetail{
  69. {
  70. CN: "testbroker.example.com",
  71. ExternalHost: nil,
  72. ExternalPort: 43191,
  73. IP: &[]string{"127.0.0.1"}[0],
  74. Modules: []string{"httptrap"},
  75. Port: &[]uint16{43191}[0],
  76. Status: "active",
  77. },
  78. },
  79. }
  80. )
  81. func testCheckServer() *httptest.Server {
  82. f := func(w http.ResponseWriter, r *http.Request) {
  83. switch r.URL.Path {
  84. case "/check_bundle/1234": // handle GET/PUT/DELETE
  85. switch r.Method {
  86. case "PUT": // update
  87. defer r.Body.Close()
  88. b, err := ioutil.ReadAll(r.Body)
  89. if err != nil {
  90. panic(err)
  91. }
  92. w.WriteHeader(200)
  93. w.Header().Set("Content-Type", "application/json")
  94. fmt.Fprintln(w, string(b))
  95. case "GET": // get by id/cid
  96. ret, err := json.Marshal(testCheckBundle)
  97. if err != nil {
  98. panic(err)
  99. }
  100. w.WriteHeader(200)
  101. w.Header().Set("Content-Type", "application/json")
  102. fmt.Fprintln(w, string(ret))
  103. default:
  104. w.WriteHeader(500)
  105. fmt.Fprintln(w, "unsupported method")
  106. }
  107. case "/check_bundle":
  108. switch r.Method {
  109. case "GET": // search
  110. //fmt.Println(r.URL.String())
  111. if strings.HasPrefix(r.URL.String(), "/check_bundle?f_notes=") && strings.Contains(r.URL.String(), "found_notes") {
  112. r := []api.CheckBundle{testCheckBundle}
  113. ret, err := json.Marshal(r)
  114. if err != nil {
  115. panic(err)
  116. }
  117. w.WriteHeader(200)
  118. w.Header().Set("Content-Type", "application/json")
  119. fmt.Fprintln(w, string(ret))
  120. } else if strings.HasPrefix(r.URL.String(), "/check_bundle?search=") && strings.Contains(r.URL.String(), "found_target") {
  121. r := []api.CheckBundle{testCheckBundle}
  122. ret, err := json.Marshal(r)
  123. if err != nil {
  124. panic(err)
  125. }
  126. w.WriteHeader(200)
  127. w.Header().Set("Content-Type", "application/json")
  128. fmt.Fprintln(w, string(ret))
  129. } else {
  130. w.WriteHeader(200)
  131. w.Header().Set("Content-Type", "application/json")
  132. fmt.Fprintln(w, "[]")
  133. }
  134. case "POST": // create
  135. defer r.Body.Close()
  136. _, err := ioutil.ReadAll(r.Body)
  137. if err != nil {
  138. panic(err)
  139. }
  140. ret, err := json.Marshal(testCheckBundle)
  141. if err != nil {
  142. panic(err)
  143. }
  144. w.WriteHeader(200)
  145. w.Header().Set("Content-Type", "application/json")
  146. fmt.Fprintln(w, string(ret))
  147. default:
  148. w.WriteHeader(405)
  149. fmt.Fprintf(w, "method not allowed %s", r.Method)
  150. }
  151. case "/broker":
  152. switch r.Method {
  153. case "GET":
  154. r := []api.Broker{testBroker}
  155. ret, err := json.Marshal(r)
  156. if err != nil {
  157. panic(err)
  158. }
  159. w.WriteHeader(200)
  160. w.Header().Set("Content-Type", "application/json")
  161. fmt.Fprintln(w, string(ret))
  162. default:
  163. w.WriteHeader(405)
  164. fmt.Fprintf(w, "method not allowed %s", r.Method)
  165. }
  166. case "/broker/1234":
  167. switch r.Method {
  168. case "GET":
  169. ret, err := json.Marshal(testBroker)
  170. if err != nil {
  171. panic(err)
  172. }
  173. w.WriteHeader(200)
  174. w.Header().Set("Content-Type", "application/json")
  175. fmt.Fprintln(w, string(ret))
  176. default:
  177. w.WriteHeader(405)
  178. fmt.Fprintf(w, "method not allowed %s", r.Method)
  179. }
  180. case "/check":
  181. switch r.Method {
  182. case "GET":
  183. r := []api.Check{testCheck}
  184. ret, err := json.Marshal(r)
  185. if err != nil {
  186. panic(err)
  187. }
  188. w.WriteHeader(200)
  189. w.Header().Set("Content-Type", "application/json")
  190. fmt.Fprintln(w, string(ret))
  191. default:
  192. w.WriteHeader(405)
  193. fmt.Fprintf(w, "method not allowed %s", r.Method)
  194. }
  195. case "/check/1234":
  196. switch r.Method {
  197. case "GET":
  198. ret, err := json.Marshal(testCheck)
  199. if err != nil {
  200. panic(err)
  201. }
  202. w.WriteHeader(200)
  203. w.Header().Set("Content-Type", "application/json")
  204. fmt.Fprintln(w, string(ret))
  205. default:
  206. w.WriteHeader(405)
  207. fmt.Fprintf(w, "method not allowed %s", r.Method)
  208. }
  209. case "/pki/ca.crt":
  210. w.WriteHeader(200)
  211. w.Header().Set("Content-Type", "application/json")
  212. fmt.Fprintln(w, cert)
  213. default:
  214. msg := fmt.Sprintf("not found %s", r.URL.Path)
  215. w.WriteHeader(404)
  216. fmt.Fprintln(w, msg)
  217. }
  218. }
  219. return httptest.NewServer(http.HandlerFunc(f))
  220. }
  221. func TestUpdateCheck(t *testing.T) {
  222. server := testCheckServer()
  223. defer server.Close()
  224. cm := &CheckManager{
  225. enabled: true,
  226. }
  227. ac := &api.Config{
  228. TokenApp: "abcd",
  229. TokenKey: "1234",
  230. URL: server.URL,
  231. }
  232. apih, err := api.NewAPI(ac)
  233. if err != nil {
  234. t.Errorf("Expected no error, got '%v'", err)
  235. }
  236. cm.apih = apih
  237. newMetrics := make(map[string]*api.CheckBundleMetric)
  238. t.Log("check manager disabled")
  239. {
  240. cm.enabled = false
  241. cm.UpdateCheck(newMetrics)
  242. }
  243. t.Log("no check bundle")
  244. {
  245. cm.enabled = true
  246. cm.checkBundle = nil
  247. cm.UpdateCheck(newMetrics)
  248. }
  249. t.Log("nothing to update (!force metrics, 0 metrics, 0 tags)")
  250. {
  251. cm.enabled = true
  252. cm.checkBundle = &testCheckBundle
  253. cm.forceCheckUpdate = false
  254. cm.UpdateCheck(newMetrics)
  255. }
  256. newMetrics["test`metric"] = &api.CheckBundleMetric{
  257. Name: "test`metric",
  258. Type: "numeric",
  259. Status: "active",
  260. }
  261. t.Log("new metric")
  262. {
  263. cm.enabled = true
  264. cm.checkBundle = &testCheckBundle
  265. cm.forceCheckUpdate = false
  266. cm.UpdateCheck(newMetrics)
  267. }
  268. cm.metricTags = make(map[string][]string)
  269. cm.metricTags["elmo"] = []string{"cat:tag"}
  270. t.Log("metric tag")
  271. {
  272. cm.enabled = true
  273. cm.checkBundle = &testCheckBundle
  274. cm.forceCheckUpdate = false
  275. cm.UpdateCheck(newMetrics)
  276. }
  277. }
  278. func TestMakeSecret(t *testing.T) {
  279. cm := &CheckManager{}
  280. secret, err := cm.makeSecret()
  281. if err != nil {
  282. t.Fatalf("Expected no error, got %v", err)
  283. }
  284. if secret == "" {
  285. t.Fatal("Expected a secret, got ''")
  286. }
  287. }
  288. func TestCreateNewCheck(t *testing.T) {
  289. server := testCheckServer()
  290. defer server.Close()
  291. testURL, err := url.Parse(server.URL)
  292. if err != nil {
  293. t.Fatalf("Error parsing temporary url %v", err)
  294. }
  295. hostParts := strings.Split(testURL.Host, ":")
  296. hostPort, err := strconv.Atoi(hostParts[1])
  297. if err != nil {
  298. t.Fatalf("Error converting port to numeric %v", err)
  299. }
  300. testBroker.Details[0].ExternalHost = &hostParts[0]
  301. testBroker.Details[0].ExternalPort = uint16(hostPort)
  302. cm := &CheckManager{
  303. enabled: true,
  304. checkDisplayName: "test_dn",
  305. checkInstanceID: "test_id",
  306. checkSearchTag: api.TagType([]string{"test:test"}),
  307. checkTarget: "test",
  308. brokerMaxResponseTime: time.Duration(time.Millisecond * 500),
  309. checkType: "httptrap",
  310. }
  311. ac := &api.Config{
  312. TokenApp: "abcd",
  313. TokenKey: "1234",
  314. URL: server.URL,
  315. }
  316. apih, err := api.NewAPI(ac)
  317. if err != nil {
  318. t.Errorf("Expected no error, got '%v'", err)
  319. }
  320. cm.apih = apih
  321. _, _, err = cm.createNewCheck()
  322. if err != nil {
  323. t.Fatalf("Expected no error, got %v", err)
  324. }
  325. }
  326. func TestInitializeTrapURL(t *testing.T) {
  327. server := testCheckServer()
  328. defer server.Close()
  329. testURL, err := url.Parse(server.URL)
  330. if err != nil {
  331. t.Fatalf("Error parsing temporary url %v", err)
  332. }
  333. hostParts := strings.Split(testURL.Host, ":")
  334. hostPort, err := strconv.Atoi(hostParts[1])
  335. if err != nil {
  336. t.Fatalf("Error converting port to numeric %v", err)
  337. }
  338. testBroker.Details[0].ExternalHost = &hostParts[0]
  339. testBroker.Details[0].ExternalPort = uint16(hostPort)
  340. cm := &CheckManager{
  341. enabled: false,
  342. Debug: false,
  343. Log: log.New(os.Stdout, "", log.LstdFlags),
  344. // Log: log.New(ioutil.Discard, "", log.LstdFlags),
  345. }
  346. t.Log("invalid")
  347. {
  348. expectedError := errors.New("unable to initialize trap, check manager is disabled")
  349. err := cm.initializeTrapURL()
  350. if err == nil {
  351. t.Fatal("Expected an error")
  352. }
  353. if err.Error() != expectedError.Error() {
  354. t.Fatalf("Expected %v got '%v'", expectedError, err)
  355. }
  356. }
  357. cm.checkSubmissionURL = "http://127.0.0.1:43191/module/httptrap/abc123-a1b2-c3d4-e5f6-123abc/blah"
  358. t.Log("cm disabled, only submission URL")
  359. {
  360. if err := cm.initializeTrapURL(); err != nil {
  361. t.Fatalf("Expected no error, got %v", err)
  362. }
  363. }
  364. ac := &api.Config{
  365. TokenApp: "abcd",
  366. TokenKey: "1234",
  367. URL: server.URL,
  368. }
  369. if apih, err := api.NewAPI(ac); err == nil {
  370. cm.apih = apih
  371. } else {
  372. t.Errorf("Expected no error, got '%v'", err)
  373. }
  374. cm.trapURL = ""
  375. cm.enabled = true
  376. t.Log("cm enabled, submission URL")
  377. {
  378. err := cm.initializeTrapURL()
  379. if err != nil {
  380. t.Fatalf("Expected no error, got %v", err)
  381. }
  382. }
  383. cm.trapURL = ""
  384. cm.checkSubmissionURL = ""
  385. cm.checkID = 1234
  386. t.Log("cm enabled, check id")
  387. {
  388. err := cm.initializeTrapURL()
  389. if err != nil {
  390. t.Fatalf("Expected no error, got %v", err)
  391. }
  392. }
  393. cm.trapURL = ""
  394. cm.checkSubmissionURL = ""
  395. cm.checkID = 0
  396. cm.checkTarget = "test_t"
  397. cm.checkInstanceID = "test_id"
  398. cm.checkSearchTag = api.TagType([]string{"s:found_target"})
  399. cm.checkDisplayName = "test_dn"
  400. t.Log("cm enabled, search [found by target]")
  401. {
  402. if err := cm.initializeTrapURL(); err != nil {
  403. t.Fatalf("Expected no error, got %v", err)
  404. }
  405. }
  406. cm.trapURL = ""
  407. cm.checkSubmissionURL = ""
  408. cm.checkID = 0
  409. cm.checkTarget = "test_t"
  410. cm.checkInstanceID = "test_id"
  411. cm.checkSearchTag = api.TagType([]string{"s:found_notes"})
  412. cm.checkDisplayName = "test_dn"
  413. t.Log("cm enabled, search [found by notes]")
  414. {
  415. if err := cm.initializeTrapURL(); err != nil {
  416. t.Fatalf("Expected no error, got %v", err)
  417. }
  418. }
  419. cm.trapURL = ""
  420. cm.checkSubmissionURL = ""
  421. cm.checkID = 0
  422. cm.checkTarget = "foo_t"
  423. cm.checkInstanceID = "foo_id"
  424. cm.checkSearchTag = api.TagType([]string{"foo:bar"})
  425. cm.checkDisplayName = "foo_dn"
  426. cm.checkType = "httptrap"
  427. cm.brokerMaxResponseTime = time.Duration(time.Millisecond * 50)
  428. t.Log("cm enabled, search [not found, create check]")
  429. {
  430. err := cm.initializeTrapURL()
  431. if err != nil {
  432. t.Fatalf("Expected no error, got %v", err)
  433. }
  434. }
  435. cm.trapURL = ""
  436. cm.checkSubmissionURL = ""
  437. cm.checkID = 1234
  438. cm.checkTarget = "foo_t"
  439. cm.checkInstanceID = "foo_id"
  440. cm.checkSearchTag = api.TagType([]string{"foo:bar"})
  441. cm.checkDisplayName = "foo_dn"
  442. cm.checkType = "httptrap"
  443. testCheckBundle.Type = "json:nad"
  444. t.Log("cm enabled, id, non-httptrap check")
  445. {
  446. err := cm.initializeTrapURL()
  447. if err != nil {
  448. t.Fatalf("Expected no error, got %v", err)
  449. }
  450. }
  451. }