broker_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  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. "log"
  10. "net/http"
  11. "net/http/httptest"
  12. "net/url"
  13. "os"
  14. "strconv"
  15. "strings"
  16. "testing"
  17. "time"
  18. "github.com/circonus-labs/circonus-gometrics/api"
  19. )
  20. var (
  21. invalidBroker = api.Broker{
  22. CID: "/broker/1",
  23. Longitude: nil,
  24. Latitude: nil,
  25. Name: "test broker",
  26. Tags: []string{},
  27. Type: "foo",
  28. Details: []api.BrokerDetail{
  29. {
  30. CN: "testbroker.example.com",
  31. ExternalHost: &[]string{"testbroker.example.com"}[0],
  32. ExternalPort: 43191,
  33. IP: &[]string{"127.0.0.1"}[0],
  34. MinVer: 0,
  35. Modules: []string{"a", "b", "c"},
  36. Port: &[]uint16{43191}[0],
  37. Skew: nil,
  38. Status: "unprovisioned",
  39. Version: nil,
  40. },
  41. },
  42. }
  43. noIPorHostBroker = api.Broker{
  44. CID: "/broker/2",
  45. Longitude: nil,
  46. Latitude: nil,
  47. Name: "no ip or external host broker",
  48. Tags: []string{},
  49. Type: "enterprise",
  50. Details: []api.BrokerDetail{
  51. {
  52. CN: "foobar",
  53. ExternalHost: nil,
  54. ExternalPort: 43191,
  55. IP: nil,
  56. MinVer: 0,
  57. Modules: []string{"httptrap"},
  58. Port: &[]uint16{43191}[0],
  59. Skew: nil,
  60. Status: "active",
  61. Version: nil,
  62. },
  63. },
  64. }
  65. validBroker = api.Broker{
  66. CID: "/broker/2",
  67. Longitude: nil,
  68. Latitude: nil,
  69. Name: "test broker",
  70. Tags: []string{},
  71. Type: "enterprise",
  72. Details: []api.BrokerDetail{
  73. {
  74. CN: "testbroker.example.com",
  75. ExternalHost: nil,
  76. ExternalPort: 43191,
  77. IP: &[]string{"127.0.0.1"}[0],
  78. MinVer: 0,
  79. Modules: []string{"httptrap"},
  80. Port: &[]uint16{43191}[0],
  81. Skew: nil,
  82. Status: "active",
  83. Version: nil,
  84. },
  85. },
  86. }
  87. validBrokerNonEnterprise = api.Broker{
  88. CID: "/broker/3",
  89. Longitude: nil,
  90. Latitude: nil,
  91. Name: "test broker",
  92. Tags: []string{},
  93. Type: "foo",
  94. Details: []api.BrokerDetail{
  95. {
  96. CN: "testbroker.example.com",
  97. ExternalHost: nil,
  98. ExternalPort: 43191,
  99. IP: &[]string{"127.0.0.1"}[0],
  100. MinVer: 0,
  101. Modules: []string{"httptrap"},
  102. Port: &[]uint16{43191}[0],
  103. Skew: nil,
  104. Status: "active",
  105. Version: nil,
  106. },
  107. },
  108. }
  109. )
  110. func testBrokerServer() *httptest.Server {
  111. f := func(w http.ResponseWriter, r *http.Request) {
  112. switch r.URL.Path {
  113. case "/broker/1":
  114. switch r.Method {
  115. case "GET": // get by id/cid
  116. ret, err := json.Marshal(invalidBroker)
  117. if err != nil {
  118. panic(err)
  119. }
  120. w.WriteHeader(200)
  121. w.Header().Set("Content-Type", "application/json")
  122. fmt.Fprintln(w, string(ret))
  123. default:
  124. w.WriteHeader(500)
  125. fmt.Fprintln(w, "unsupported")
  126. }
  127. case "/broker/2":
  128. switch r.Method {
  129. case "GET": // get by id/cid
  130. ret, err := json.Marshal(validBroker)
  131. if err != nil {
  132. panic(err)
  133. }
  134. w.WriteHeader(200)
  135. w.Header().Set("Content-Type", "application/json")
  136. fmt.Fprintln(w, string(ret))
  137. default:
  138. w.WriteHeader(500)
  139. fmt.Fprintln(w, "unsupported")
  140. }
  141. case "/broker":
  142. switch r.Method {
  143. case "GET": // search or filter
  144. var c []api.Broker
  145. if strings.Contains(r.URL.String(), "f__tags_has=no%3Abroker") {
  146. c = []api.Broker{}
  147. } else if strings.Contains(r.URL.String(), "f__tags_has=multi%3Abroker") {
  148. c = []api.Broker{invalidBroker, invalidBroker}
  149. } else {
  150. c = []api.Broker{validBroker, validBrokerNonEnterprise}
  151. }
  152. ret, err := json.Marshal(c)
  153. if err != nil {
  154. panic(err)
  155. }
  156. w.WriteHeader(200)
  157. w.Header().Set("Content-Type", "application/json")
  158. fmt.Fprintln(w, string(ret))
  159. default:
  160. w.WriteHeader(500)
  161. fmt.Fprintln(w, "unsupported")
  162. }
  163. default:
  164. w.WriteHeader(500)
  165. fmt.Fprintln(w, "unsupported")
  166. }
  167. }
  168. return httptest.NewServer(http.HandlerFunc(f))
  169. }
  170. func TestBrokerSupportsCheckType(t *testing.T) {
  171. detail := &api.BrokerDetail{
  172. Modules: []string{"httptrap"},
  173. }
  174. cm := CheckManager{}
  175. t.Log("supports 'httptrap' check type?")
  176. {
  177. ok := cm.brokerSupportsCheckType("httptrap", detail)
  178. if !ok {
  179. t.Fatal("Expected OK")
  180. }
  181. }
  182. t.Log("supports 'foo' check type?")
  183. {
  184. ok := cm.brokerSupportsCheckType("foo", detail)
  185. if ok {
  186. t.Fatal("Expected not OK")
  187. }
  188. }
  189. }
  190. func TestGetBrokerCN(t *testing.T) {
  191. t.Log("URL with IP")
  192. {
  193. submissionURL := api.URLType("http://127.0.0.1:43191/blah/blah/blah")
  194. cm := CheckManager{}
  195. _, err := cm.getBrokerCN(&validBroker, submissionURL)
  196. if err != nil {
  197. t.Fatalf("Expected no error, got %+v", err)
  198. }
  199. }
  200. t.Log("URL with FQDN")
  201. {
  202. submissionURL := api.URLType("http://test.example.com:43191/blah/blah/blah")
  203. cm := CheckManager{}
  204. _, err := cm.getBrokerCN(&validBroker, submissionURL)
  205. if err != nil {
  206. t.Fatalf("Expected no error, got %+v", err)
  207. }
  208. }
  209. t.Log("URL with invalid IP")
  210. {
  211. submissionURL := api.URLType("http://127.0.0.2:43191/blah/blah/blah")
  212. cm := CheckManager{}
  213. expectedError := errors.New("[ERROR] Unable to match URL host (127.0.0.2:43191) to Broker")
  214. _, err := cm.getBrokerCN(&validBroker, submissionURL)
  215. if err == nil {
  216. t.Fatal("Expected error")
  217. }
  218. if err.Error() != expectedError.Error() {
  219. t.Fatalf("Expected %v got '%v'", expectedError, err)
  220. }
  221. }
  222. }
  223. func TestSelectBroker(t *testing.T) {
  224. server := testBrokerServer()
  225. defer server.Close()
  226. testURL, err := url.Parse(server.URL)
  227. if err != nil {
  228. t.Fatalf("Error parsing temporary url %v", err)
  229. }
  230. hostParts := strings.Split(testURL.Host, ":")
  231. hostPort, err := strconv.Atoi(hostParts[1])
  232. if err != nil {
  233. t.Fatalf("Error converting port to numeric %v", err)
  234. }
  235. validBroker.Details[0].ExternalHost = &hostParts[0]
  236. validBroker.Details[0].ExternalPort = uint16(hostPort)
  237. validBroker.Details[0].IP = &hostParts[0]
  238. validBroker.Details[0].Port = &[]uint16{uint16(hostPort)}[0]
  239. validBrokerNonEnterprise.Details[0].ExternalHost = &hostParts[0]
  240. validBrokerNonEnterprise.Details[0].ExternalPort = uint16(hostPort)
  241. validBrokerNonEnterprise.Details[0].IP = &hostParts[0]
  242. validBrokerNonEnterprise.Details[0].Port = &[]uint16{uint16(hostPort)}[0]
  243. t.Log("default broker selection")
  244. {
  245. cm := &CheckManager{
  246. checkType: "httptrap",
  247. brokerMaxResponseTime: time.Duration(time.Millisecond * 500),
  248. }
  249. ac := &api.Config{
  250. TokenApp: "abcd",
  251. TokenKey: "1234",
  252. URL: server.URL,
  253. }
  254. apih, err := api.New(ac)
  255. if err != nil {
  256. t.Errorf("Expected no error, got '%v'", err)
  257. }
  258. cm.apih = apih
  259. _, err = cm.selectBroker()
  260. if err != nil {
  261. t.Fatal("Expected no error")
  262. }
  263. }
  264. t.Log("tag, no brokers matching")
  265. {
  266. cm := &CheckManager{
  267. checkType: "httptrap",
  268. brokerMaxResponseTime: time.Duration(time.Millisecond * 500),
  269. brokerSelectTag: api.TagType([]string{"no:broker"}),
  270. }
  271. ac := &api.Config{
  272. TokenApp: "abcd",
  273. TokenKey: "1234",
  274. URL: server.URL,
  275. }
  276. apih, err := api.New(ac)
  277. if err != nil {
  278. t.Errorf("Expected no error, got '%v'", err)
  279. }
  280. cm.apih = apih
  281. expectedError := errors.New("zero brokers found")
  282. _, err = cm.selectBroker()
  283. if err == nil {
  284. t.Fatal("Expected an error")
  285. }
  286. if expectedError.Error() != err.Error() {
  287. t.Errorf("Expected %v got '%v'", expectedError, err)
  288. }
  289. }
  290. t.Log("multiple brokers with tag, none valid")
  291. {
  292. cm := &CheckManager{
  293. checkType: "httptrap",
  294. brokerMaxResponseTime: time.Duration(time.Millisecond * 500),
  295. brokerSelectTag: api.TagType([]string{"multi:broker"}),
  296. }
  297. ac := &api.Config{
  298. TokenApp: "abcd",
  299. TokenKey: "1234",
  300. URL: server.URL,
  301. }
  302. apih, err := api.NewAPI(ac)
  303. if err != nil {
  304. t.Errorf("Expected no error, got '%v'", err)
  305. }
  306. cm.apih = apih
  307. expectedError := errors.New("found 2 broker(s), zero are valid")
  308. _, err = cm.selectBroker()
  309. if err == nil {
  310. t.Fatalf("Expected an error")
  311. }
  312. if expectedError.Error() != err.Error() {
  313. t.Fatalf("Expected %v got '%v'", expectedError, err)
  314. }
  315. }
  316. }
  317. func TestIsValidBroker(t *testing.T) {
  318. cm := &CheckManager{
  319. Log: log.New(os.Stderr, "", log.LstdFlags),
  320. checkType: "httptrap",
  321. brokerMaxResponseTime: time.Duration(time.Millisecond * 50),
  322. }
  323. broker := api.Broker{
  324. CID: "/broker/2",
  325. Name: "test broker",
  326. Type: "enterprise",
  327. Details: []api.BrokerDetail{
  328. {
  329. CN: "testbroker.example.com",
  330. ExternalHost: nil,
  331. ExternalPort: 43191,
  332. IP: &[]string{"127.0.0.1"}[0],
  333. Modules: []string{"httptrap"},
  334. Port: &[]uint16{43191}[0],
  335. Status: "unprovisioned",
  336. },
  337. },
  338. }
  339. t.Log("status unprovisioned")
  340. {
  341. if cm.isValidBroker(&broker) {
  342. t.Fatal("Expected invalid broker")
  343. }
  344. }
  345. t.Log("no ip or host")
  346. {
  347. if cm.isValidBroker(&noIPorHostBroker) {
  348. t.Fatal("Expected invalid broker")
  349. }
  350. }
  351. t.Log("does not have required module")
  352. {
  353. broker.Details[0].Modules = []string{"foo"}
  354. broker.Details[0].Status = "active"
  355. if cm.isValidBroker(&broker) {
  356. t.Fatal("Expected invalid broker")
  357. }
  358. }
  359. }
  360. func TestIsValidBrokerTimeout(t *testing.T) {
  361. if os.Getenv("CIRCONUS_BROKER_TEST_TIMEOUT") == "" {
  362. t.Skip("not testing timeouts, CIRCONUS_BROKER_TEST_TIMEOUT not set")
  363. }
  364. cm := &CheckManager{
  365. Log: log.New(os.Stderr, "", log.LstdFlags),
  366. checkType: "httptrap",
  367. brokerMaxResponseTime: time.Duration(time.Millisecond * 50),
  368. }
  369. broker := api.Broker{
  370. CID: "/broker/2",
  371. Name: "test broker",
  372. Type: "enterprise",
  373. Details: []api.BrokerDetail{
  374. {
  375. CN: "testbroker.example.com",
  376. ExternalHost: nil,
  377. ExternalPort: 43191,
  378. IP: &[]string{"127.0.0.1"}[0],
  379. Modules: []string{"httptrap"},
  380. Port: &[]uint16{43191}[0],
  381. Status: "unprovisioned",
  382. },
  383. },
  384. }
  385. t.Log("unable to connect, broker.ExternalPort")
  386. {
  387. broker.Name = "test"
  388. broker.Details[0].Modules = []string{"httptrap"}
  389. broker.Details[0].Status = "active"
  390. if cm.isValidBroker(&broker) {
  391. t.Fatal("Expected invalid broker")
  392. }
  393. }
  394. t.Log("unable to connect, broker.Port")
  395. {
  396. broker.Name = "test"
  397. broker.Details[0].ExternalPort = 0
  398. broker.Details[0].Modules = []string{"httptrap"}
  399. broker.Details[0].Status = "active"
  400. if cm.isValidBroker(&broker) {
  401. t.Fatal("Expected invalid broker")
  402. }
  403. }
  404. t.Log("unable to connect, default port")
  405. {
  406. broker.Name = "test"
  407. broker.Details[0].ExternalPort = 0
  408. broker.Details[0].Port = &[]uint16{0}[0]
  409. broker.Details[0].Modules = []string{"httptrap"}
  410. broker.Details[0].Status = "active"
  411. if cm.isValidBroker(&broker) {
  412. t.Fatal("Expected invalid broker")
  413. }
  414. }
  415. }
  416. func TestGetBroker(t *testing.T) {
  417. server := testBrokerServer()
  418. defer server.Close()
  419. testURL, err := url.Parse(server.URL)
  420. if err != nil {
  421. t.Fatalf("Error parsing temporary url %v", err)
  422. }
  423. hostParts := strings.Split(testURL.Host, ":")
  424. hostPort, err := strconv.Atoi(hostParts[1])
  425. if err != nil {
  426. t.Fatalf("Error converting port to numeric %v", err)
  427. }
  428. validBroker.Details[0].ExternalHost = &hostParts[0]
  429. validBroker.Details[0].ExternalPort = uint16(hostPort)
  430. validBroker.Details[0].IP = &hostParts[0]
  431. validBroker.Details[0].Port = &[]uint16{uint16(hostPort)}[0]
  432. t.Log("invalid custom broker")
  433. {
  434. cm := &CheckManager{}
  435. ac := &api.Config{
  436. TokenApp: "abcd",
  437. TokenKey: "1234",
  438. URL: server.URL,
  439. }
  440. apih, err := api.NewAPI(ac)
  441. if err != nil {
  442. t.Errorf("Expected no error, got '%v'", err)
  443. }
  444. cm.apih = apih
  445. cm.brokerID = 1
  446. expectedError := errors.New("[ERROR] designated broker 1 [test broker] is invalid (not active, does not support required check type, or connectivity issue)")
  447. _, err = cm.getBroker()
  448. if err == nil || err.Error() != expectedError.Error() {
  449. t.Errorf("Expected an '%#v' error, got '%#v'", expectedError, err)
  450. }
  451. }
  452. t.Log("valid custom broker")
  453. {
  454. cm := &CheckManager{
  455. checkType: "httptrap",
  456. brokerMaxResponseTime: time.Duration(time.Millisecond * 500),
  457. }
  458. ac := &api.Config{
  459. TokenApp: "abcd",
  460. TokenKey: "1234",
  461. URL: server.URL,
  462. }
  463. apih, err := api.NewAPI(ac)
  464. if err != nil {
  465. t.Errorf("Expected no error, got '%v'", err)
  466. }
  467. cm.apih = apih
  468. cm.brokerID = 2
  469. _, err = cm.getBroker()
  470. if err != nil {
  471. t.Errorf("Expected no error, got '%v'", err)
  472. }
  473. }
  474. }