checkmgr_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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. "crypto/tls"
  7. "crypto/x509"
  8. "encoding/json"
  9. "errors"
  10. "fmt"
  11. "io/ioutil"
  12. "log"
  13. "net/http"
  14. "net/http/httptest"
  15. "net/url"
  16. "os"
  17. "strconv"
  18. "strings"
  19. "testing"
  20. "time"
  21. "github.com/circonus-labs/circonus-gometrics/api"
  22. "github.com/circonus-labs/circonus-gometrics/api/config"
  23. )
  24. func sslBroker() *httptest.Server {
  25. f := func(w http.ResponseWriter, r *http.Request) {
  26. w.WriteHeader(200)
  27. w.Header().Set("Content-Type", "application/json")
  28. fmt.Fprintln(w, r.Method)
  29. }
  30. return httptest.NewTLSServer(http.HandlerFunc(f))
  31. }
  32. var (
  33. testCMCheck = api.Check{
  34. CID: "/check/1234",
  35. Active: true,
  36. BrokerCID: "/broker/1234",
  37. CheckBundleCID: "/check_bundle/1234",
  38. CheckUUID: "abc123-a1b2-c3d4-e5f6-123abc",
  39. Details: map[config.Key]string{config.SubmissionURL: "https://127.0.0.1:43191/module/httptrap/abc123-a1b2-c3d4-e5f6-123abc/blah"},
  40. }
  41. testCMCheckBundle = api.CheckBundle{
  42. CheckUUIDs: []string{"abc123-a1b2-c3d4-e5f6-123abc"},
  43. Checks: []string{"/check/1234"},
  44. CID: "/check_bundle/1234",
  45. Created: 0,
  46. LastModified: 0,
  47. LastModifedBy: "",
  48. ReverseConnectURLs: []string{
  49. "mtev_reverse://127.0.0.1:43191/check/abc123-a1b2-c3d4-e5f6-123abc",
  50. },
  51. Brokers: []string{"/broker/1234"},
  52. DisplayName: "test check",
  53. Config: map[config.Key]string{
  54. config.SubmissionURL: "https://127.0.0.1:43191/module/httptrap/abc123-a1b2-c3d4-e5f6-123abc/blah",
  55. },
  56. // Config: api.CheckBundleConfig{
  57. // SubmissionURL: "https://127.0.0.1:43191/module/httptrap/abc123-a1b2-c3d4-e5f6-123abc/blah",
  58. // ReverseSecret: "blah",
  59. // },
  60. Metrics: []api.CheckBundleMetric{
  61. {
  62. Name: "elmo",
  63. Type: "numeric",
  64. Status: "active",
  65. },
  66. },
  67. MetricLimit: 0,
  68. Notes: nil,
  69. Period: 60,
  70. Status: "active",
  71. Target: "127.0.0.1",
  72. Timeout: 10,
  73. Type: "httptrap",
  74. Tags: []string{},
  75. }
  76. testCMBroker = api.Broker{
  77. CID: "/broker/1234",
  78. Name: "test broker",
  79. Type: "enterprise",
  80. Details: []api.BrokerDetail{
  81. {
  82. CN: "testbroker.example.com",
  83. ExternalHost: nil,
  84. ExternalPort: 43191,
  85. IP: &[]string{"127.0.0.1"}[0],
  86. Modules: []string{"httptrap"},
  87. Port: &[]uint16{43191}[0],
  88. Status: "active",
  89. },
  90. },
  91. }
  92. )
  93. func testCMServer() *httptest.Server {
  94. f := func(w http.ResponseWriter, r *http.Request) {
  95. // fmt.Printf("%s %s\n", r.Method, r.URL.String())
  96. switch r.URL.Path {
  97. case "/check_bundle/1234": // handle GET/PUT/DELETE
  98. switch r.Method {
  99. case "PUT": // update
  100. defer r.Body.Close()
  101. b, err := ioutil.ReadAll(r.Body)
  102. if err != nil {
  103. panic(err)
  104. }
  105. w.WriteHeader(200)
  106. w.Header().Set("Content-Type", "application/json")
  107. fmt.Fprintln(w, string(b))
  108. case "GET": // get by id/cid
  109. ret, err := json.Marshal(testCMCheckBundle)
  110. if err != nil {
  111. panic(err)
  112. }
  113. w.WriteHeader(200)
  114. w.Header().Set("Content-Type", "application/json")
  115. fmt.Fprintln(w, string(ret))
  116. default:
  117. w.WriteHeader(500)
  118. fmt.Fprintln(w, "unsupported method")
  119. }
  120. case "/check_bundle":
  121. switch r.Method {
  122. case "GET": // search
  123. if strings.HasPrefix(r.URL.String(), "/check_bundle?search=") {
  124. r := []api.CheckBundle{testCMCheckBundle}
  125. ret, err := json.Marshal(r)
  126. if err != nil {
  127. panic(err)
  128. }
  129. w.WriteHeader(200)
  130. w.Header().Set("Content-Type", "application/json")
  131. fmt.Fprintln(w, string(ret))
  132. } else {
  133. w.WriteHeader(200)
  134. w.Header().Set("Content-Type", "application/json")
  135. fmt.Fprintln(w, "[]")
  136. }
  137. case "POST": // create
  138. defer r.Body.Close()
  139. _, err := ioutil.ReadAll(r.Body)
  140. if err != nil {
  141. panic(err)
  142. }
  143. ret, err := json.Marshal(testCheckBundle)
  144. if err != nil {
  145. panic(err)
  146. }
  147. w.WriteHeader(200)
  148. w.Header().Set("Content-Type", "application/json")
  149. fmt.Fprintln(w, string(ret))
  150. default:
  151. w.WriteHeader(405)
  152. fmt.Fprintf(w, "method not allowed %s", r.Method)
  153. }
  154. case "/broker":
  155. switch r.Method {
  156. case "GET":
  157. r := []api.Broker{testCMBroker}
  158. ret, err := json.Marshal(r)
  159. if err != nil {
  160. panic(err)
  161. }
  162. w.WriteHeader(200)
  163. w.Header().Set("Content-Type", "application/json")
  164. fmt.Fprintln(w, string(ret))
  165. default:
  166. w.WriteHeader(405)
  167. fmt.Fprintf(w, "method not allowed %s", r.Method)
  168. }
  169. case "/broker/1234":
  170. switch r.Method {
  171. case "GET":
  172. ret, err := json.Marshal(testCMBroker)
  173. if err != nil {
  174. panic(err)
  175. }
  176. w.WriteHeader(200)
  177. w.Header().Set("Content-Type", "application/json")
  178. fmt.Fprintln(w, string(ret))
  179. default:
  180. w.WriteHeader(405)
  181. fmt.Fprintf(w, "method not allowed %s", r.Method)
  182. }
  183. case "/check":
  184. switch r.Method {
  185. case "GET":
  186. r := []api.Check{testCMCheck}
  187. ret, err := json.Marshal(r)
  188. if err != nil {
  189. panic(err)
  190. }
  191. w.WriteHeader(200)
  192. w.Header().Set("Content-Type", "application/json")
  193. fmt.Fprintln(w, string(ret))
  194. default:
  195. w.WriteHeader(405)
  196. fmt.Fprintf(w, "method not allowed %s", r.Method)
  197. }
  198. case "/check/1234":
  199. switch r.Method {
  200. case "GET":
  201. ret, err := json.Marshal(testCMCheck)
  202. if err != nil {
  203. panic(err)
  204. }
  205. w.WriteHeader(200)
  206. w.Header().Set("Content-Type", "application/json")
  207. fmt.Fprintln(w, string(ret))
  208. default:
  209. w.WriteHeader(405)
  210. fmt.Fprintf(w, "method not allowed %s", r.Method)
  211. }
  212. case "/pki/ca.crt":
  213. w.WriteHeader(200)
  214. w.Header().Set("Content-Type", "application/json")
  215. fmt.Fprintln(w, cert)
  216. default:
  217. msg := fmt.Sprintf("not found %s", r.URL.Path)
  218. w.WriteHeader(404)
  219. fmt.Fprintln(w, msg)
  220. }
  221. }
  222. return httptest.NewServer(http.HandlerFunc(f))
  223. }
  224. func TestNewCheckManager(t *testing.T) {
  225. t.Log("no config supplied")
  226. {
  227. expectedError := errors.New("invalid Check Manager configuration (nil)")
  228. _, err := NewCheckManager(nil)
  229. if err == nil || err.Error() != expectedError.Error() {
  230. t.Errorf("Expected an '%#v' error, got '%#v'", expectedError, err)
  231. }
  232. }
  233. t.Log("no API Token and no Submission URL supplied")
  234. {
  235. expectedError := errors.New("invalid check manager configuration (no API token AND no submission url)")
  236. cfg := &Config{}
  237. _, err := NewCheckManager(cfg)
  238. if err == nil || err.Error() != expectedError.Error() {
  239. t.Errorf("Expected an '%#v' error, got '%#v'", expectedError, err)
  240. }
  241. }
  242. t.Log("no API Token, Submission URL (http) only")
  243. {
  244. cfg := &Config{}
  245. cfg.Check.SubmissionURL = "http://127.0.0.1:56104"
  246. cm, err := NewCheckManager(cfg)
  247. if err != nil {
  248. t.Errorf("Expected no error, got '%v'", err)
  249. }
  250. cm.Initialize()
  251. for !cm.IsReady() {
  252. t.Log("\twaiting for cm to init")
  253. time.Sleep(1 * time.Second)
  254. }
  255. trap, err := cm.GetSubmissionURL()
  256. if err != nil {
  257. t.Errorf("Expected no error, got '%v'", err)
  258. }
  259. if trap.URL.String() != cfg.Check.SubmissionURL {
  260. t.Errorf("Expected '%s' == '%s'", trap.URL.String(), cfg.Check.SubmissionURL)
  261. }
  262. if trap.TLS != nil {
  263. t.Errorf("Expected nil found %#v", trap.TLS)
  264. }
  265. }
  266. t.Log("no API Token, Submission URL (https) only")
  267. {
  268. cfg := &Config{}
  269. cfg.Check.SubmissionURL = "https://127.0.0.1/v2"
  270. cm, err := NewCheckManager(cfg)
  271. if err != nil {
  272. t.Fatalf("Expected no error, got '%v'", err)
  273. }
  274. cm.Initialize()
  275. for !cm.IsReady() {
  276. t.Log("\twaiting for cm to init")
  277. time.Sleep(1 * time.Second)
  278. }
  279. trap, err := cm.GetSubmissionURL()
  280. if err != nil {
  281. t.Fatalf("Expected no error, got '%v'", err)
  282. }
  283. if trap.URL.String() != cfg.Check.SubmissionURL {
  284. t.Fatalf("Expected '%s' == '%s'", trap.URL.String(), cfg.Check.SubmissionURL)
  285. }
  286. if trap.TLS == nil {
  287. t.Fatalf("Expected a x509 cert pool, found nil")
  288. }
  289. }
  290. t.Log("Defaults")
  291. {
  292. server := testCMServer()
  293. defer server.Close()
  294. testURL, err := url.Parse(server.URL)
  295. if err != nil {
  296. t.Fatalf("Error parsing temporary url %v", err)
  297. }
  298. hostParts := strings.Split(testURL.Host, ":")
  299. hostPort, err := strconv.Atoi(hostParts[1])
  300. if err != nil {
  301. t.Fatalf("Error converting port to numeric %v", err)
  302. }
  303. testCMBroker.Details[0].ExternalHost = &hostParts[0]
  304. testCMBroker.Details[0].ExternalPort = uint16(hostPort)
  305. cfg := &Config{
  306. Log: log.New(os.Stderr, "", log.LstdFlags),
  307. API: api.Config{
  308. TokenKey: "1234",
  309. TokenApp: "abc",
  310. URL: server.URL,
  311. },
  312. }
  313. cm, err := NewCheckManager(cfg)
  314. if err != nil {
  315. t.Fatalf("Expected no error, got '%v'", err)
  316. }
  317. cm.Initialize()
  318. for !cm.IsReady() {
  319. t.Log("\twaiting for cm to init")
  320. time.Sleep(1 * time.Second)
  321. }
  322. trap, err := cm.GetSubmissionURL()
  323. if err != nil {
  324. t.Fatalf("Expected no error, got '%v'", err)
  325. }
  326. suburl, found := testCMCheckBundle.Config["submission_url"]
  327. if !found {
  328. t.Fatalf("Exected submission_url in check bundle config %+v", testCMCheckBundle)
  329. }
  330. if trap.URL.String() != suburl {
  331. t.Fatalf("Expected '%s' got '%s'", suburl, trap.URL.String())
  332. }
  333. }
  334. t.Log("Custom broker ssl config")
  335. {
  336. server := sslBroker()
  337. defer server.Close()
  338. cfg := &Config{
  339. Log: log.New(os.Stderr, "", log.LstdFlags),
  340. }
  341. c := server.Certificate()
  342. cp := x509.NewCertPool()
  343. cp.AddCert(c)
  344. cfg.Check.SubmissionURL = server.URL
  345. cfg.Broker.TLSConfig = &tls.Config{RootCAs: cp}
  346. cm, err := NewCheckManager(cfg)
  347. if err != nil {
  348. t.Fatalf("Expected no error, got '%v'", err)
  349. }
  350. cm.Initialize()
  351. for !cm.IsReady() {
  352. t.Log("\twaiting for cm to init")
  353. time.Sleep(1 * time.Second)
  354. }
  355. trap, err := cm.GetSubmissionURL()
  356. if err != nil {
  357. t.Fatalf("Expected no error, got '%v'", err)
  358. }
  359. if trap.URL.String() != server.URL {
  360. t.Fatalf("Expected '%s' got '%s'", server.URL, trap.URL.String())
  361. }
  362. // quick request to make sure the trap.TLS is the one passed and not the default
  363. client := &http.Client{Transport: &http.Transport{TLSClientConfig: trap.TLS}}
  364. if _, err := client.Get(trap.URL.String()); err != nil {
  365. t.Fatalf("expected no error, got (%s)", err)
  366. }
  367. t.Log("test ResetTrap")
  368. {
  369. err := cm.ResetTrap()
  370. if err != nil {
  371. t.Fatalf("expected no error, got (%s)", err)
  372. }
  373. trap, err := cm.GetSubmissionURL()
  374. if err != nil {
  375. t.Fatalf("Expected no error, got '%v'", err)
  376. }
  377. if trap.URL.String() != server.URL {
  378. t.Fatalf("Expected '%s' got '%s'", server.URL, trap.URL.String())
  379. }
  380. }
  381. t.Log("test RefreshTrap")
  382. {
  383. cm.trapLastUpdate = time.Now().Add(-(cm.trapMaxURLAge + 2*time.Second))
  384. cm.RefreshTrap()
  385. trap, err := cm.GetSubmissionURL()
  386. if err != nil {
  387. t.Fatalf("Expected no error, got '%v'", err)
  388. }
  389. if trap.URL.String() != server.URL {
  390. t.Fatalf("Expected '%s' got '%s'", server.URL, trap.URL.String())
  391. }
  392. }
  393. }
  394. }