client_test.go 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969
  1. // Copyright 2015 CoreOS, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package etcdhttp
  15. import (
  16. "bytes"
  17. "encoding/json"
  18. "errors"
  19. "io/ioutil"
  20. "net/http"
  21. "net/http/httptest"
  22. "net/url"
  23. "path"
  24. "reflect"
  25. "strings"
  26. "testing"
  27. "time"
  28. "github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/go-semver/semver"
  29. "github.com/coreos/etcd/Godeps/_workspace/src/github.com/jonboulle/clockwork"
  30. "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
  31. etcdErr "github.com/coreos/etcd/error"
  32. "github.com/coreos/etcd/etcdserver"
  33. "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
  34. "github.com/coreos/etcd/etcdserver/etcdserverpb"
  35. "github.com/coreos/etcd/pkg/testutil"
  36. "github.com/coreos/etcd/pkg/types"
  37. "github.com/coreos/etcd/raft/raftpb"
  38. "github.com/coreos/etcd/store"
  39. "github.com/coreos/etcd/version"
  40. )
  41. func mustMarshalEvent(t *testing.T, ev *store.Event) string {
  42. b := new(bytes.Buffer)
  43. if err := json.NewEncoder(b).Encode(ev); err != nil {
  44. t.Fatalf("error marshalling event %#v: %v", ev, err)
  45. }
  46. return b.String()
  47. }
  48. // mustNewForm takes a set of Values and constructs a PUT *http.Request,
  49. // with a URL constructed from appending the given path to the standard keysPrefix
  50. func mustNewForm(t *testing.T, p string, vals url.Values) *http.Request {
  51. u := testutil.MustNewURL(t, path.Join(keysPrefix, p))
  52. req, err := http.NewRequest("PUT", u.String(), strings.NewReader(vals.Encode()))
  53. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  54. if err != nil {
  55. t.Fatalf("error creating new request: %v", err)
  56. }
  57. return req
  58. }
  59. // mustNewPostForm takes a set of Values and constructs a POST *http.Request,
  60. // with a URL constructed from appending the given path to the standard keysPrefix
  61. func mustNewPostForm(t *testing.T, p string, vals url.Values) *http.Request {
  62. u := testutil.MustNewURL(t, path.Join(keysPrefix, p))
  63. req, err := http.NewRequest("POST", u.String(), strings.NewReader(vals.Encode()))
  64. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  65. if err != nil {
  66. t.Fatalf("error creating new request: %v", err)
  67. }
  68. return req
  69. }
  70. // mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs
  71. // a GET *http.Request referencing the resulting URL
  72. func mustNewRequest(t *testing.T, p string) *http.Request {
  73. return mustNewMethodRequest(t, "GET", p)
  74. }
  75. func mustNewMethodRequest(t *testing.T, m, p string) *http.Request {
  76. return &http.Request{
  77. Method: m,
  78. URL: testutil.MustNewURL(t, path.Join(keysPrefix, p)),
  79. }
  80. }
  81. type serverRecorder struct {
  82. actions []action
  83. }
  84. func (s *serverRecorder) Start() {}
  85. func (s *serverRecorder) Stop() {}
  86. func (s *serverRecorder) Leader() types.ID { return types.ID(1) }
  87. func (s *serverRecorder) ID() types.ID { return types.ID(1) }
  88. func (s *serverRecorder) Do(_ context.Context, r etcdserverpb.Request) (etcdserver.Response, error) {
  89. s.actions = append(s.actions, action{name: "Do", params: []interface{}{r}})
  90. return etcdserver.Response{}, nil
  91. }
  92. func (s *serverRecorder) Process(_ context.Context, m raftpb.Message) error {
  93. s.actions = append(s.actions, action{name: "Process", params: []interface{}{m}})
  94. return nil
  95. }
  96. func (s *serverRecorder) AddMember(_ context.Context, m etcdserver.Member) error {
  97. s.actions = append(s.actions, action{name: "AddMember", params: []interface{}{m}})
  98. return nil
  99. }
  100. func (s *serverRecorder) RemoveMember(_ context.Context, id uint64) error {
  101. s.actions = append(s.actions, action{name: "RemoveMember", params: []interface{}{id}})
  102. return nil
  103. }
  104. func (s *serverRecorder) UpdateMember(_ context.Context, m etcdserver.Member) error {
  105. s.actions = append(s.actions, action{name: "UpdateMember", params: []interface{}{m}})
  106. return nil
  107. }
  108. func (s *serverRecorder) ClusterVersion() *semver.Version { return nil }
  109. type action struct {
  110. name string
  111. params []interface{}
  112. }
  113. // flushingRecorder provides a channel to allow users to block until the Recorder is Flushed()
  114. type flushingRecorder struct {
  115. *httptest.ResponseRecorder
  116. ch chan struct{}
  117. }
  118. func (fr *flushingRecorder) Flush() {
  119. fr.ResponseRecorder.Flush()
  120. fr.ch <- struct{}{}
  121. }
  122. // resServer implements the etcd.Server interface for testing.
  123. // It returns the given responsefrom any Do calls, and nil error
  124. type resServer struct {
  125. res etcdserver.Response
  126. }
  127. func (rs *resServer) Start() {}
  128. func (rs *resServer) Stop() {}
  129. func (rs *resServer) ID() types.ID { return types.ID(1) }
  130. func (rs *resServer) Leader() types.ID { return types.ID(1) }
  131. func (rs *resServer) Do(_ context.Context, _ etcdserverpb.Request) (etcdserver.Response, error) {
  132. return rs.res, nil
  133. }
  134. func (rs *resServer) Process(_ context.Context, _ raftpb.Message) error { return nil }
  135. func (rs *resServer) AddMember(_ context.Context, _ etcdserver.Member) error { return nil }
  136. func (rs *resServer) RemoveMember(_ context.Context, _ uint64) error { return nil }
  137. func (rs *resServer) UpdateMember(_ context.Context, _ etcdserver.Member) error { return nil }
  138. func (rs *resServer) ClusterVersion() *semver.Version { return nil }
  139. func boolp(b bool) *bool { return &b }
  140. type dummyRaftTimer struct{}
  141. func (drt dummyRaftTimer) Index() uint64 { return uint64(100) }
  142. func (drt dummyRaftTimer) Term() uint64 { return uint64(5) }
  143. type dummyWatcher struct {
  144. echan chan *store.Event
  145. sidx uint64
  146. }
  147. func (w *dummyWatcher) EventChan() chan *store.Event {
  148. return w.echan
  149. }
  150. func (w *dummyWatcher) StartIndex() uint64 { return w.sidx }
  151. func (w *dummyWatcher) Remove() {}
  152. func TestBadParseRequest(t *testing.T) {
  153. tests := []struct {
  154. in *http.Request
  155. wcode int
  156. }{
  157. {
  158. // parseForm failure
  159. &http.Request{
  160. Body: nil,
  161. Method: "PUT",
  162. },
  163. etcdErr.EcodeInvalidForm,
  164. },
  165. {
  166. // bad key prefix
  167. &http.Request{
  168. URL: testutil.MustNewURL(t, "/badprefix/"),
  169. },
  170. etcdErr.EcodeInvalidForm,
  171. },
  172. // bad values for prevIndex, waitIndex, ttl
  173. {
  174. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"garbage"}}),
  175. etcdErr.EcodeIndexNaN,
  176. },
  177. {
  178. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"1.5"}}),
  179. etcdErr.EcodeIndexNaN,
  180. },
  181. {
  182. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"-1"}}),
  183. etcdErr.EcodeIndexNaN,
  184. },
  185. {
  186. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"garbage"}}),
  187. etcdErr.EcodeIndexNaN,
  188. },
  189. {
  190. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"??"}}),
  191. etcdErr.EcodeIndexNaN,
  192. },
  193. {
  194. mustNewForm(t, "foo", url.Values{"ttl": []string{"-1"}}),
  195. etcdErr.EcodeTTLNaN,
  196. },
  197. // bad values for recursive, sorted, wait, prevExist, dir, stream
  198. {
  199. mustNewForm(t, "foo", url.Values{"recursive": []string{"hahaha"}}),
  200. etcdErr.EcodeInvalidField,
  201. },
  202. {
  203. mustNewForm(t, "foo", url.Values{"recursive": []string{"1234"}}),
  204. etcdErr.EcodeInvalidField,
  205. },
  206. {
  207. mustNewForm(t, "foo", url.Values{"recursive": []string{"?"}}),
  208. etcdErr.EcodeInvalidField,
  209. },
  210. {
  211. mustNewForm(t, "foo", url.Values{"sorted": []string{"?"}}),
  212. etcdErr.EcodeInvalidField,
  213. },
  214. {
  215. mustNewForm(t, "foo", url.Values{"sorted": []string{"x"}}),
  216. etcdErr.EcodeInvalidField,
  217. },
  218. {
  219. mustNewForm(t, "foo", url.Values{"wait": []string{"?!"}}),
  220. etcdErr.EcodeInvalidField,
  221. },
  222. {
  223. mustNewForm(t, "foo", url.Values{"wait": []string{"yes"}}),
  224. etcdErr.EcodeInvalidField,
  225. },
  226. {
  227. mustNewForm(t, "foo", url.Values{"prevExist": []string{"yes"}}),
  228. etcdErr.EcodeInvalidField,
  229. },
  230. {
  231. mustNewForm(t, "foo", url.Values{"prevExist": []string{"#2"}}),
  232. etcdErr.EcodeInvalidField,
  233. },
  234. {
  235. mustNewForm(t, "foo", url.Values{"dir": []string{"no"}}),
  236. etcdErr.EcodeInvalidField,
  237. },
  238. {
  239. mustNewForm(t, "foo", url.Values{"dir": []string{"file"}}),
  240. etcdErr.EcodeInvalidField,
  241. },
  242. {
  243. mustNewForm(t, "foo", url.Values{"quorum": []string{"no"}}),
  244. etcdErr.EcodeInvalidField,
  245. },
  246. {
  247. mustNewForm(t, "foo", url.Values{"quorum": []string{"file"}}),
  248. etcdErr.EcodeInvalidField,
  249. },
  250. {
  251. mustNewForm(t, "foo", url.Values{"stream": []string{"zzz"}}),
  252. etcdErr.EcodeInvalidField,
  253. },
  254. {
  255. mustNewForm(t, "foo", url.Values{"stream": []string{"something"}}),
  256. etcdErr.EcodeInvalidField,
  257. },
  258. // prevValue cannot be empty
  259. {
  260. mustNewForm(t, "foo", url.Values{"prevValue": []string{""}}),
  261. etcdErr.EcodePrevValueRequired,
  262. },
  263. // wait is only valid with GET requests
  264. {
  265. mustNewMethodRequest(t, "HEAD", "foo?wait=true"),
  266. etcdErr.EcodeInvalidField,
  267. },
  268. // query values are considered
  269. {
  270. mustNewRequest(t, "foo?prevExist=wrong"),
  271. etcdErr.EcodeInvalidField,
  272. },
  273. {
  274. mustNewRequest(t, "foo?ttl=wrong"),
  275. etcdErr.EcodeTTLNaN,
  276. },
  277. // but body takes precedence if both are specified
  278. {
  279. mustNewForm(
  280. t,
  281. "foo?ttl=12",
  282. url.Values{"ttl": []string{"garbage"}},
  283. ),
  284. etcdErr.EcodeTTLNaN,
  285. },
  286. {
  287. mustNewForm(
  288. t,
  289. "foo?prevExist=false",
  290. url.Values{"prevExist": []string{"yes"}},
  291. ),
  292. etcdErr.EcodeInvalidField,
  293. },
  294. }
  295. for i, tt := range tests {
  296. got, err := parseKeyRequest(tt.in, clockwork.NewFakeClock())
  297. if err == nil {
  298. t.Errorf("#%d: unexpected nil error!", i)
  299. continue
  300. }
  301. ee, ok := err.(*etcdErr.Error)
  302. if !ok {
  303. t.Errorf("#%d: err is not etcd.Error!", i)
  304. continue
  305. }
  306. if ee.ErrorCode != tt.wcode {
  307. t.Errorf("#%d: code=%d, want %v", i, ee.ErrorCode, tt.wcode)
  308. t.Logf("cause: %#v", ee.Cause)
  309. }
  310. if !reflect.DeepEqual(got, etcdserverpb.Request{}) {
  311. t.Errorf("#%d: unexpected non-empty Request: %#v", i, got)
  312. }
  313. }
  314. }
  315. func TestGoodParseRequest(t *testing.T) {
  316. fc := clockwork.NewFakeClock()
  317. fc.Advance(1111)
  318. tests := []struct {
  319. in *http.Request
  320. w etcdserverpb.Request
  321. }{
  322. {
  323. // good prefix, all other values default
  324. mustNewRequest(t, "foo"),
  325. etcdserverpb.Request{
  326. Method: "GET",
  327. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  328. },
  329. },
  330. {
  331. // value specified
  332. mustNewForm(
  333. t,
  334. "foo",
  335. url.Values{"value": []string{"some_value"}},
  336. ),
  337. etcdserverpb.Request{
  338. Method: "PUT",
  339. Val: "some_value",
  340. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  341. },
  342. },
  343. {
  344. // prevIndex specified
  345. mustNewForm(
  346. t,
  347. "foo",
  348. url.Values{"prevIndex": []string{"98765"}},
  349. ),
  350. etcdserverpb.Request{
  351. Method: "PUT",
  352. PrevIndex: 98765,
  353. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  354. },
  355. },
  356. {
  357. // recursive specified
  358. mustNewForm(
  359. t,
  360. "foo",
  361. url.Values{"recursive": []string{"true"}},
  362. ),
  363. etcdserverpb.Request{
  364. Method: "PUT",
  365. Recursive: true,
  366. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  367. },
  368. },
  369. {
  370. // sorted specified
  371. mustNewForm(
  372. t,
  373. "foo",
  374. url.Values{"sorted": []string{"true"}},
  375. ),
  376. etcdserverpb.Request{
  377. Method: "PUT",
  378. Sorted: true,
  379. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  380. },
  381. },
  382. {
  383. // quorum specified
  384. mustNewForm(
  385. t,
  386. "foo",
  387. url.Values{"quorum": []string{"true"}},
  388. ),
  389. etcdserverpb.Request{
  390. Method: "PUT",
  391. Quorum: true,
  392. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  393. },
  394. },
  395. {
  396. // wait specified
  397. mustNewRequest(t, "foo?wait=true"),
  398. etcdserverpb.Request{
  399. Method: "GET",
  400. Wait: true,
  401. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  402. },
  403. },
  404. {
  405. // empty TTL specified
  406. mustNewRequest(t, "foo?ttl="),
  407. etcdserverpb.Request{
  408. Method: "GET",
  409. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  410. Expiration: 0,
  411. },
  412. },
  413. {
  414. // non-empty TTL specified
  415. mustNewRequest(t, "foo?ttl=5678"),
  416. etcdserverpb.Request{
  417. Method: "GET",
  418. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  419. Expiration: fc.Now().Add(5678 * time.Second).UnixNano(),
  420. },
  421. },
  422. {
  423. // zero TTL specified
  424. mustNewRequest(t, "foo?ttl=0"),
  425. etcdserverpb.Request{
  426. Method: "GET",
  427. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  428. Expiration: fc.Now().UnixNano(),
  429. },
  430. },
  431. {
  432. // dir specified
  433. mustNewRequest(t, "foo?dir=true"),
  434. etcdserverpb.Request{
  435. Method: "GET",
  436. Dir: true,
  437. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  438. },
  439. },
  440. {
  441. // dir specified negatively
  442. mustNewRequest(t, "foo?dir=false"),
  443. etcdserverpb.Request{
  444. Method: "GET",
  445. Dir: false,
  446. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  447. },
  448. },
  449. {
  450. // prevExist should be non-null if specified
  451. mustNewForm(
  452. t,
  453. "foo",
  454. url.Values{"prevExist": []string{"true"}},
  455. ),
  456. etcdserverpb.Request{
  457. Method: "PUT",
  458. PrevExist: boolp(true),
  459. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  460. },
  461. },
  462. {
  463. // prevExist should be non-null if specified
  464. mustNewForm(
  465. t,
  466. "foo",
  467. url.Values{"prevExist": []string{"false"}},
  468. ),
  469. etcdserverpb.Request{
  470. Method: "PUT",
  471. PrevExist: boolp(false),
  472. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  473. },
  474. },
  475. // mix various fields
  476. {
  477. mustNewForm(
  478. t,
  479. "foo",
  480. url.Values{
  481. "value": []string{"some value"},
  482. "prevExist": []string{"true"},
  483. "prevValue": []string{"previous value"},
  484. },
  485. ),
  486. etcdserverpb.Request{
  487. Method: "PUT",
  488. PrevExist: boolp(true),
  489. PrevValue: "previous value",
  490. Val: "some value",
  491. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  492. },
  493. },
  494. // query parameters should be used if given
  495. {
  496. mustNewForm(
  497. t,
  498. "foo?prevValue=woof",
  499. url.Values{},
  500. ),
  501. etcdserverpb.Request{
  502. Method: "PUT",
  503. PrevValue: "woof",
  504. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  505. },
  506. },
  507. // but form values should take precedence over query parameters
  508. {
  509. mustNewForm(
  510. t,
  511. "foo?prevValue=woof",
  512. url.Values{
  513. "prevValue": []string{"miaow"},
  514. },
  515. ),
  516. etcdserverpb.Request{
  517. Method: "PUT",
  518. PrevValue: "miaow",
  519. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  520. },
  521. },
  522. }
  523. for i, tt := range tests {
  524. got, err := parseKeyRequest(tt.in, fc)
  525. if err != nil {
  526. t.Errorf("#%d: err = %v, want %v", i, err, nil)
  527. }
  528. if !reflect.DeepEqual(got, tt.w) {
  529. t.Errorf("#%d: request=%#v, want %#v", i, got, tt.w)
  530. }
  531. }
  532. }
  533. func TestServeMembers(t *testing.T) {
  534. memb1 := etcdserver.Member{ID: 12, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080"}}}
  535. memb2 := etcdserver.Member{ID: 13, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8081"}}}
  536. cluster := &fakeCluster{
  537. id: 1,
  538. members: map[uint64]*etcdserver.Member{1: &memb1, 2: &memb2},
  539. }
  540. h := &membersHandler{
  541. server: &serverRecorder{},
  542. clock: clockwork.NewFakeClock(),
  543. cluster: cluster,
  544. }
  545. wmc := string(`{"members":[{"id":"c","name":"","peerURLs":[],"clientURLs":["http://localhost:8080"]},{"id":"d","name":"","peerURLs":[],"clientURLs":["http://localhost:8081"]}]}`)
  546. tests := []struct {
  547. path string
  548. wcode int
  549. wct string
  550. wbody string
  551. }{
  552. {membersPrefix, http.StatusOK, "application/json", wmc + "\n"},
  553. {membersPrefix + "/", http.StatusOK, "application/json", wmc + "\n"},
  554. {path.Join(membersPrefix, "100"), http.StatusNotFound, "application/json", `{"message":"Not found"}`},
  555. {path.Join(membersPrefix, "foobar"), http.StatusNotFound, "application/json", `{"message":"Not found"}`},
  556. }
  557. for i, tt := range tests {
  558. req, err := http.NewRequest("GET", testutil.MustNewURL(t, tt.path).String(), nil)
  559. if err != nil {
  560. t.Fatal(err)
  561. }
  562. rw := httptest.NewRecorder()
  563. h.ServeHTTP(rw, req)
  564. if rw.Code != tt.wcode {
  565. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  566. }
  567. if gct := rw.Header().Get("Content-Type"); gct != tt.wct {
  568. t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct)
  569. }
  570. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  571. wcid := cluster.ID().String()
  572. if gcid != wcid {
  573. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  574. }
  575. if rw.Body.String() != tt.wbody {
  576. t.Errorf("#%d: body = %q, want %q", i, rw.Body.String(), tt.wbody)
  577. }
  578. }
  579. }
  580. // TODO: consolidate **ALL** fake server implementations and add no leader test case.
  581. func TestServeLeader(t *testing.T) {
  582. memb1 := etcdserver.Member{ID: 1, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080"}}}
  583. memb2 := etcdserver.Member{ID: 2, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8081"}}}
  584. cluster := &fakeCluster{
  585. id: 1,
  586. members: map[uint64]*etcdserver.Member{1: &memb1, 2: &memb2},
  587. }
  588. h := &membersHandler{
  589. server: &serverRecorder{},
  590. clock: clockwork.NewFakeClock(),
  591. cluster: cluster,
  592. }
  593. wmc := string(`{"id":"1","name":"","peerURLs":[],"clientURLs":["http://localhost:8080"]}`)
  594. tests := []struct {
  595. path string
  596. wcode int
  597. wct string
  598. wbody string
  599. }{
  600. {membersPrefix + "leader", http.StatusOK, "application/json", wmc + "\n"},
  601. // TODO: add no leader case
  602. }
  603. for i, tt := range tests {
  604. req, err := http.NewRequest("GET", testutil.MustNewURL(t, tt.path).String(), nil)
  605. if err != nil {
  606. t.Fatal(err)
  607. }
  608. rw := httptest.NewRecorder()
  609. h.ServeHTTP(rw, req)
  610. if rw.Code != tt.wcode {
  611. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  612. }
  613. if gct := rw.Header().Get("Content-Type"); gct != tt.wct {
  614. t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct)
  615. }
  616. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  617. wcid := cluster.ID().String()
  618. if gcid != wcid {
  619. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  620. }
  621. if rw.Body.String() != tt.wbody {
  622. t.Errorf("#%d: body = %q, want %q", i, rw.Body.String(), tt.wbody)
  623. }
  624. }
  625. }
  626. func TestServeMembersCreate(t *testing.T) {
  627. u := testutil.MustNewURL(t, membersPrefix)
  628. b := []byte(`{"peerURLs":["http://127.0.0.1:1"]}`)
  629. req, err := http.NewRequest("POST", u.String(), bytes.NewReader(b))
  630. if err != nil {
  631. t.Fatal(err)
  632. }
  633. req.Header.Set("Content-Type", "application/json")
  634. s := &serverRecorder{}
  635. h := &membersHandler{
  636. server: s,
  637. clock: clockwork.NewFakeClock(),
  638. cluster: &fakeCluster{id: 1},
  639. }
  640. rw := httptest.NewRecorder()
  641. h.ServeHTTP(rw, req)
  642. wcode := http.StatusCreated
  643. if rw.Code != wcode {
  644. t.Errorf("code=%d, want %d", rw.Code, wcode)
  645. }
  646. wct := "application/json"
  647. if gct := rw.Header().Get("Content-Type"); gct != wct {
  648. t.Errorf("content-type = %s, want %s", gct, wct)
  649. }
  650. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  651. wcid := h.cluster.ID().String()
  652. if gcid != wcid {
  653. t.Errorf("cid = %s, want %s", gcid, wcid)
  654. }
  655. wb := `{"id":"2a86a83729b330d5","name":"","peerURLs":["http://127.0.0.1:1"],"clientURLs":[]}` + "\n"
  656. g := rw.Body.String()
  657. if g != wb {
  658. t.Errorf("got body=%q, want %q", g, wb)
  659. }
  660. wm := etcdserver.Member{
  661. ID: 3064321551348478165,
  662. RaftAttributes: etcdserver.RaftAttributes{
  663. PeerURLs: []string{"http://127.0.0.1:1"},
  664. },
  665. }
  666. wactions := []action{{name: "AddMember", params: []interface{}{wm}}}
  667. if !reflect.DeepEqual(s.actions, wactions) {
  668. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  669. }
  670. }
  671. func TestServeMembersDelete(t *testing.T) {
  672. req := &http.Request{
  673. Method: "DELETE",
  674. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "BEEF")),
  675. }
  676. s := &serverRecorder{}
  677. h := &membersHandler{
  678. server: s,
  679. cluster: &fakeCluster{id: 1},
  680. }
  681. rw := httptest.NewRecorder()
  682. h.ServeHTTP(rw, req)
  683. wcode := http.StatusNoContent
  684. if rw.Code != wcode {
  685. t.Errorf("code=%d, want %d", rw.Code, wcode)
  686. }
  687. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  688. wcid := h.cluster.ID().String()
  689. if gcid != wcid {
  690. t.Errorf("cid = %s, want %s", gcid, wcid)
  691. }
  692. g := rw.Body.String()
  693. if g != "" {
  694. t.Errorf("got body=%q, want %q", g, "")
  695. }
  696. wactions := []action{{name: "RemoveMember", params: []interface{}{uint64(0xBEEF)}}}
  697. if !reflect.DeepEqual(s.actions, wactions) {
  698. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  699. }
  700. }
  701. func TestServeMembersUpdate(t *testing.T) {
  702. u := testutil.MustNewURL(t, path.Join(membersPrefix, "1"))
  703. b := []byte(`{"peerURLs":["http://127.0.0.1:1"]}`)
  704. req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(b))
  705. if err != nil {
  706. t.Fatal(err)
  707. }
  708. req.Header.Set("Content-Type", "application/json")
  709. s := &serverRecorder{}
  710. h := &membersHandler{
  711. server: s,
  712. clock: clockwork.NewFakeClock(),
  713. cluster: &fakeCluster{id: 1},
  714. }
  715. rw := httptest.NewRecorder()
  716. h.ServeHTTP(rw, req)
  717. wcode := http.StatusNoContent
  718. if rw.Code != wcode {
  719. t.Errorf("code=%d, want %d", rw.Code, wcode)
  720. }
  721. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  722. wcid := h.cluster.ID().String()
  723. if gcid != wcid {
  724. t.Errorf("cid = %s, want %s", gcid, wcid)
  725. }
  726. wm := etcdserver.Member{
  727. ID: 1,
  728. RaftAttributes: etcdserver.RaftAttributes{
  729. PeerURLs: []string{"http://127.0.0.1:1"},
  730. },
  731. }
  732. wactions := []action{{name: "UpdateMember", params: []interface{}{wm}}}
  733. if !reflect.DeepEqual(s.actions, wactions) {
  734. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  735. }
  736. }
  737. func TestServeMembersFail(t *testing.T) {
  738. tests := []struct {
  739. req *http.Request
  740. server etcdserver.Server
  741. wcode int
  742. }{
  743. {
  744. // bad method
  745. &http.Request{
  746. Method: "CONNECT",
  747. },
  748. &resServer{},
  749. http.StatusMethodNotAllowed,
  750. },
  751. {
  752. // bad method
  753. &http.Request{
  754. Method: "TRACE",
  755. },
  756. &resServer{},
  757. http.StatusMethodNotAllowed,
  758. },
  759. {
  760. // parse body error
  761. &http.Request{
  762. URL: testutil.MustNewURL(t, membersPrefix),
  763. Method: "POST",
  764. Body: ioutil.NopCloser(strings.NewReader("bad json")),
  765. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  766. },
  767. &resServer{},
  768. http.StatusBadRequest,
  769. },
  770. {
  771. // bad content type
  772. &http.Request{
  773. URL: testutil.MustNewURL(t, membersPrefix),
  774. Method: "POST",
  775. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  776. Header: map[string][]string{"Content-Type": []string{"application/bad"}},
  777. },
  778. &errServer{},
  779. http.StatusUnsupportedMediaType,
  780. },
  781. {
  782. // bad url
  783. &http.Request{
  784. URL: testutil.MustNewURL(t, membersPrefix),
  785. Method: "POST",
  786. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://a"]}`)),
  787. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  788. },
  789. &errServer{},
  790. http.StatusBadRequest,
  791. },
  792. {
  793. // etcdserver.AddMember error
  794. &http.Request{
  795. URL: testutil.MustNewURL(t, membersPrefix),
  796. Method: "POST",
  797. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  798. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  799. },
  800. &errServer{
  801. errors.New("Error while adding a member"),
  802. },
  803. http.StatusInternalServerError,
  804. },
  805. {
  806. // etcdserver.AddMember error
  807. &http.Request{
  808. URL: testutil.MustNewURL(t, membersPrefix),
  809. Method: "POST",
  810. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  811. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  812. },
  813. &errServer{
  814. etcdserver.ErrIDExists,
  815. },
  816. http.StatusConflict,
  817. },
  818. {
  819. // etcdserver.AddMember error
  820. &http.Request{
  821. URL: testutil.MustNewURL(t, membersPrefix),
  822. Method: "POST",
  823. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  824. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  825. },
  826. &errServer{
  827. etcdserver.ErrPeerURLexists,
  828. },
  829. http.StatusConflict,
  830. },
  831. {
  832. // etcdserver.RemoveMember error with arbitrary server error
  833. &http.Request{
  834. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "1")),
  835. Method: "DELETE",
  836. },
  837. &errServer{
  838. errors.New("Error while removing member"),
  839. },
  840. http.StatusInternalServerError,
  841. },
  842. {
  843. // etcdserver.RemoveMember error with previously removed ID
  844. &http.Request{
  845. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  846. Method: "DELETE",
  847. },
  848. &errServer{
  849. etcdserver.ErrIDRemoved,
  850. },
  851. http.StatusGone,
  852. },
  853. {
  854. // etcdserver.RemoveMember error with nonexistent ID
  855. &http.Request{
  856. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  857. Method: "DELETE",
  858. },
  859. &errServer{
  860. etcdserver.ErrIDNotFound,
  861. },
  862. http.StatusNotFound,
  863. },
  864. {
  865. // etcdserver.RemoveMember error with badly formed ID
  866. &http.Request{
  867. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "bad_id")),
  868. Method: "DELETE",
  869. },
  870. nil,
  871. http.StatusNotFound,
  872. },
  873. {
  874. // etcdserver.RemoveMember with no ID
  875. &http.Request{
  876. URL: testutil.MustNewURL(t, membersPrefix),
  877. Method: "DELETE",
  878. },
  879. nil,
  880. http.StatusMethodNotAllowed,
  881. },
  882. {
  883. // parse body error
  884. &http.Request{
  885. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  886. Method: "PUT",
  887. Body: ioutil.NopCloser(strings.NewReader("bad json")),
  888. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  889. },
  890. &resServer{},
  891. http.StatusBadRequest,
  892. },
  893. {
  894. // bad content type
  895. &http.Request{
  896. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  897. Method: "PUT",
  898. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  899. Header: map[string][]string{"Content-Type": []string{"application/bad"}},
  900. },
  901. &errServer{},
  902. http.StatusUnsupportedMediaType,
  903. },
  904. {
  905. // bad url
  906. &http.Request{
  907. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  908. Method: "PUT",
  909. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://a"]}`)),
  910. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  911. },
  912. &errServer{},
  913. http.StatusBadRequest,
  914. },
  915. {
  916. // etcdserver.UpdateMember error
  917. &http.Request{
  918. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  919. Method: "PUT",
  920. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  921. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  922. },
  923. &errServer{
  924. errors.New("blah"),
  925. },
  926. http.StatusInternalServerError,
  927. },
  928. {
  929. // etcdserver.UpdateMember error
  930. &http.Request{
  931. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  932. Method: "PUT",
  933. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  934. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  935. },
  936. &errServer{
  937. etcdserver.ErrPeerURLexists,
  938. },
  939. http.StatusConflict,
  940. },
  941. {
  942. // etcdserver.UpdateMember error
  943. &http.Request{
  944. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  945. Method: "PUT",
  946. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  947. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  948. },
  949. &errServer{
  950. etcdserver.ErrIDNotFound,
  951. },
  952. http.StatusNotFound,
  953. },
  954. {
  955. // etcdserver.UpdateMember error with badly formed ID
  956. &http.Request{
  957. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "bad_id")),
  958. Method: "PUT",
  959. },
  960. nil,
  961. http.StatusNotFound,
  962. },
  963. {
  964. // etcdserver.UpdateMember with no ID
  965. &http.Request{
  966. URL: testutil.MustNewURL(t, membersPrefix),
  967. Method: "PUT",
  968. },
  969. nil,
  970. http.StatusMethodNotAllowed,
  971. },
  972. }
  973. for i, tt := range tests {
  974. h := &membersHandler{
  975. server: tt.server,
  976. cluster: &fakeCluster{id: 1},
  977. clock: clockwork.NewFakeClock(),
  978. }
  979. rw := httptest.NewRecorder()
  980. h.ServeHTTP(rw, tt.req)
  981. if rw.Code != tt.wcode {
  982. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  983. }
  984. if rw.Code != http.StatusMethodNotAllowed {
  985. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  986. wcid := h.cluster.ID().String()
  987. if gcid != wcid {
  988. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  989. }
  990. }
  991. }
  992. }
  993. func TestWriteEvent(t *testing.T) {
  994. // nil event should not panic
  995. rec := httptest.NewRecorder()
  996. writeKeyEvent(rec, nil, dummyRaftTimer{})
  997. h := rec.Header()
  998. if len(h) > 0 {
  999. t.Fatalf("unexpected non-empty headers: %#v", h)
  1000. }
  1001. b := rec.Body.String()
  1002. if len(b) > 0 {
  1003. t.Fatalf("unexpected non-empty body: %q", b)
  1004. }
  1005. tests := []struct {
  1006. ev *store.Event
  1007. idx string
  1008. // TODO(jonboulle): check body as well as just status code
  1009. code int
  1010. err error
  1011. }{
  1012. // standard case, standard 200 response
  1013. {
  1014. &store.Event{
  1015. Action: store.Get,
  1016. Node: &store.NodeExtern{},
  1017. PrevNode: &store.NodeExtern{},
  1018. },
  1019. "0",
  1020. http.StatusOK,
  1021. nil,
  1022. },
  1023. // check new nodes return StatusCreated
  1024. {
  1025. &store.Event{
  1026. Action: store.Create,
  1027. Node: &store.NodeExtern{},
  1028. PrevNode: &store.NodeExtern{},
  1029. },
  1030. "0",
  1031. http.StatusCreated,
  1032. nil,
  1033. },
  1034. }
  1035. for i, tt := range tests {
  1036. rw := httptest.NewRecorder()
  1037. writeKeyEvent(rw, tt.ev, dummyRaftTimer{})
  1038. if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
  1039. t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
  1040. }
  1041. if gri := rw.Header().Get("X-Raft-Index"); gri != "100" {
  1042. t.Errorf("case %d: bad X-Raft-Index header: got %s, want %s", i, gri, "100")
  1043. }
  1044. if grt := rw.Header().Get("X-Raft-Term"); grt != "5" {
  1045. t.Errorf("case %d: bad X-Raft-Term header: got %s, want %s", i, grt, "5")
  1046. }
  1047. if gei := rw.Header().Get("X-Etcd-Index"); gei != tt.idx {
  1048. t.Errorf("case %d: bad X-Etcd-Index header: got %s, want %s", i, gei, tt.idx)
  1049. }
  1050. if rw.Code != tt.code {
  1051. t.Errorf("case %d: bad response code: got %d, want %v", i, rw.Code, tt.code)
  1052. }
  1053. }
  1054. }
  1055. func TestV2DeprecatedMachinesEndpoint(t *testing.T) {
  1056. tests := []struct {
  1057. method string
  1058. wcode int
  1059. }{
  1060. {"GET", http.StatusOK},
  1061. {"HEAD", http.StatusOK},
  1062. {"POST", http.StatusMethodNotAllowed},
  1063. }
  1064. m := &deprecatedMachinesHandler{cluster: &fakeCluster{}}
  1065. s := httptest.NewServer(m)
  1066. defer s.Close()
  1067. for _, tt := range tests {
  1068. req, err := http.NewRequest(tt.method, s.URL+deprecatedMachinesPrefix, nil)
  1069. if err != nil {
  1070. t.Fatal(err)
  1071. }
  1072. resp, err := http.DefaultClient.Do(req)
  1073. if err != nil {
  1074. t.Fatal(err)
  1075. }
  1076. if resp.StatusCode != tt.wcode {
  1077. t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode)
  1078. }
  1079. }
  1080. }
  1081. func TestServeMachines(t *testing.T) {
  1082. cluster := &fakeCluster{
  1083. clientURLs: []string{"http://localhost:8080", "http://localhost:8081", "http://localhost:8082"},
  1084. }
  1085. writer := httptest.NewRecorder()
  1086. req, err := http.NewRequest("GET", "", nil)
  1087. if err != nil {
  1088. t.Fatal(err)
  1089. }
  1090. h := &deprecatedMachinesHandler{cluster: cluster}
  1091. h.ServeHTTP(writer, req)
  1092. w := "http://localhost:8080, http://localhost:8081, http://localhost:8082"
  1093. if g := writer.Body.String(); g != w {
  1094. t.Errorf("body = %s, want %s", g, w)
  1095. }
  1096. if writer.Code != http.StatusOK {
  1097. t.Errorf("code = %d, want %d", writer.Code, http.StatusOK)
  1098. }
  1099. }
  1100. func TestGetID(t *testing.T) {
  1101. tests := []struct {
  1102. path string
  1103. wok bool
  1104. wid types.ID
  1105. wcode int
  1106. }{
  1107. {
  1108. "123",
  1109. true, 0x123, http.StatusOK,
  1110. },
  1111. {
  1112. "bad_id",
  1113. false, 0, http.StatusNotFound,
  1114. },
  1115. {
  1116. "",
  1117. false, 0, http.StatusMethodNotAllowed,
  1118. },
  1119. }
  1120. for i, tt := range tests {
  1121. w := httptest.NewRecorder()
  1122. id, ok := getID(tt.path, w)
  1123. if id != tt.wid {
  1124. t.Errorf("#%d: id = %d, want %d", i, id, tt.wid)
  1125. }
  1126. if ok != tt.wok {
  1127. t.Errorf("#%d: ok = %t, want %t", i, ok, tt.wok)
  1128. }
  1129. if w.Code != tt.wcode {
  1130. t.Errorf("#%d code = %d, want %d", i, w.Code, tt.wcode)
  1131. }
  1132. }
  1133. }
  1134. type dummyStats struct {
  1135. data []byte
  1136. }
  1137. func (ds *dummyStats) SelfStats() []byte { return ds.data }
  1138. func (ds *dummyStats) LeaderStats() []byte { return ds.data }
  1139. func (ds *dummyStats) StoreStats() []byte { return ds.data }
  1140. func (ds *dummyStats) UpdateRecvApp(_ types.ID, _ int64) {}
  1141. func TestServeSelfStats(t *testing.T) {
  1142. wb := []byte("some statistics")
  1143. w := string(wb)
  1144. sh := &statsHandler{
  1145. stats: &dummyStats{data: wb},
  1146. }
  1147. rw := httptest.NewRecorder()
  1148. sh.serveSelf(rw, &http.Request{Method: "GET"})
  1149. if rw.Code != http.StatusOK {
  1150. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  1151. }
  1152. wct := "application/json"
  1153. if gct := rw.Header().Get("Content-Type"); gct != wct {
  1154. t.Errorf("Content-Type = %q, want %q", gct, wct)
  1155. }
  1156. if g := rw.Body.String(); g != w {
  1157. t.Errorf("body = %s, want %s", g, w)
  1158. }
  1159. }
  1160. func TestSelfServeStatsBad(t *testing.T) {
  1161. for _, m := range []string{"PUT", "POST", "DELETE"} {
  1162. sh := &statsHandler{}
  1163. rw := httptest.NewRecorder()
  1164. sh.serveSelf(
  1165. rw,
  1166. &http.Request{
  1167. Method: m,
  1168. },
  1169. )
  1170. if rw.Code != http.StatusMethodNotAllowed {
  1171. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  1172. }
  1173. }
  1174. }
  1175. func TestLeaderServeStatsBad(t *testing.T) {
  1176. for _, m := range []string{"PUT", "POST", "DELETE"} {
  1177. sh := &statsHandler{}
  1178. rw := httptest.NewRecorder()
  1179. sh.serveLeader(
  1180. rw,
  1181. &http.Request{
  1182. Method: m,
  1183. },
  1184. )
  1185. if rw.Code != http.StatusMethodNotAllowed {
  1186. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  1187. }
  1188. }
  1189. }
  1190. func TestServeLeaderStats(t *testing.T) {
  1191. wb := []byte("some statistics")
  1192. w := string(wb)
  1193. sh := &statsHandler{
  1194. stats: &dummyStats{data: wb},
  1195. }
  1196. rw := httptest.NewRecorder()
  1197. sh.serveLeader(rw, &http.Request{Method: "GET"})
  1198. if rw.Code != http.StatusOK {
  1199. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  1200. }
  1201. wct := "application/json"
  1202. if gct := rw.Header().Get("Content-Type"); gct != wct {
  1203. t.Errorf("Content-Type = %q, want %q", gct, wct)
  1204. }
  1205. if g := rw.Body.String(); g != w {
  1206. t.Errorf("body = %s, want %s", g, w)
  1207. }
  1208. }
  1209. func TestServeStoreStats(t *testing.T) {
  1210. wb := []byte("some statistics")
  1211. w := string(wb)
  1212. sh := &statsHandler{
  1213. stats: &dummyStats{data: wb},
  1214. }
  1215. rw := httptest.NewRecorder()
  1216. sh.serveStore(rw, &http.Request{Method: "GET"})
  1217. if rw.Code != http.StatusOK {
  1218. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  1219. }
  1220. wct := "application/json"
  1221. if gct := rw.Header().Get("Content-Type"); gct != wct {
  1222. t.Errorf("Content-Type = %q, want %q", gct, wct)
  1223. }
  1224. if g := rw.Body.String(); g != w {
  1225. t.Errorf("body = %s, want %s", g, w)
  1226. }
  1227. }
  1228. func TestServeVersion(t *testing.T) {
  1229. req, err := http.NewRequest("GET", "", nil)
  1230. if err != nil {
  1231. t.Fatalf("error creating request: %v", err)
  1232. }
  1233. rw := httptest.NewRecorder()
  1234. serveVersion(rw, req, "2.1.0")
  1235. if rw.Code != http.StatusOK {
  1236. t.Errorf("code=%d, want %d", rw.Code, http.StatusOK)
  1237. }
  1238. vs := version.Versions{
  1239. Server: version.Version,
  1240. Cluster: "2.1.0",
  1241. }
  1242. w, err := json.Marshal(&vs)
  1243. if err != nil {
  1244. t.Fatal(err)
  1245. }
  1246. if g := rw.Body.String(); g != string(w) {
  1247. t.Fatalf("body = %q, want %q", g, string(w))
  1248. }
  1249. }
  1250. func TestServeVersionFails(t *testing.T) {
  1251. for _, m := range []string{
  1252. "CONNECT", "TRACE", "PUT", "POST", "HEAD",
  1253. } {
  1254. req, err := http.NewRequest(m, "", nil)
  1255. if err != nil {
  1256. t.Fatalf("error creating request: %v", err)
  1257. }
  1258. rw := httptest.NewRecorder()
  1259. serveVersion(rw, req, "2.1.0")
  1260. if rw.Code != http.StatusMethodNotAllowed {
  1261. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  1262. }
  1263. }
  1264. }
  1265. func TestBadServeKeys(t *testing.T) {
  1266. testBadCases := []struct {
  1267. req *http.Request
  1268. server etcdserver.Server
  1269. wcode int
  1270. wbody string
  1271. }{
  1272. {
  1273. // bad method
  1274. &http.Request{
  1275. Method: "CONNECT",
  1276. },
  1277. &resServer{},
  1278. http.StatusMethodNotAllowed,
  1279. "Method Not Allowed",
  1280. },
  1281. {
  1282. // bad method
  1283. &http.Request{
  1284. Method: "TRACE",
  1285. },
  1286. &resServer{},
  1287. http.StatusMethodNotAllowed,
  1288. "Method Not Allowed",
  1289. },
  1290. {
  1291. // parseRequest error
  1292. &http.Request{
  1293. Body: nil,
  1294. Method: "PUT",
  1295. },
  1296. &resServer{},
  1297. http.StatusBadRequest,
  1298. `{"errorCode":210,"message":"Invalid POST form","cause":"missing form body","index":0}`,
  1299. },
  1300. {
  1301. // etcdserver.Server error
  1302. mustNewRequest(t, "foo"),
  1303. &errServer{
  1304. errors.New("Internal Server Error"),
  1305. },
  1306. http.StatusInternalServerError,
  1307. `{"errorCode":300,"message":"Raft Internal Error","cause":"Internal Server Error","index":0}`,
  1308. },
  1309. {
  1310. // etcdserver.Server etcd error
  1311. mustNewRequest(t, "foo"),
  1312. &errServer{
  1313. etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/1/pant", 0),
  1314. },
  1315. http.StatusNotFound,
  1316. `{"errorCode":100,"message":"Key not found","cause":"/pant","index":0}`,
  1317. },
  1318. {
  1319. // non-event/watcher response from etcdserver.Server
  1320. mustNewRequest(t, "foo"),
  1321. &resServer{
  1322. etcdserver.Response{},
  1323. },
  1324. http.StatusInternalServerError,
  1325. `{"errorCode":300,"message":"Raft Internal Error","cause":"received response with no Event/Watcher!","index":0}`,
  1326. },
  1327. }
  1328. for i, tt := range testBadCases {
  1329. h := &keysHandler{
  1330. timeout: 0, // context times out immediately
  1331. server: tt.server,
  1332. cluster: &fakeCluster{id: 1},
  1333. }
  1334. rw := httptest.NewRecorder()
  1335. h.ServeHTTP(rw, tt.req)
  1336. if rw.Code != tt.wcode {
  1337. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  1338. }
  1339. if rw.Code != http.StatusMethodNotAllowed {
  1340. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1341. wcid := h.cluster.ID().String()
  1342. if gcid != wcid {
  1343. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  1344. }
  1345. }
  1346. if g := strings.TrimSuffix(rw.Body.String(), "\n"); g != tt.wbody {
  1347. t.Errorf("#%d: body = %s, want %s", i, g, tt.wbody)
  1348. }
  1349. }
  1350. }
  1351. func TestServeKeysGood(t *testing.T) {
  1352. tests := []struct {
  1353. req *http.Request
  1354. wcode int
  1355. }{
  1356. {
  1357. mustNewMethodRequest(t, "HEAD", "foo"),
  1358. http.StatusOK,
  1359. },
  1360. {
  1361. mustNewMethodRequest(t, "GET", "foo"),
  1362. http.StatusOK,
  1363. },
  1364. {
  1365. mustNewForm(t, "foo", url.Values{"value": []string{"bar"}}),
  1366. http.StatusOK,
  1367. },
  1368. {
  1369. mustNewMethodRequest(t, "DELETE", "foo"),
  1370. http.StatusOK,
  1371. },
  1372. {
  1373. mustNewPostForm(t, "foo", url.Values{"value": []string{"bar"}}),
  1374. http.StatusOK,
  1375. },
  1376. }
  1377. server := &resServer{
  1378. etcdserver.Response{
  1379. Event: &store.Event{
  1380. Action: store.Get,
  1381. Node: &store.NodeExtern{},
  1382. },
  1383. },
  1384. }
  1385. for i, tt := range tests {
  1386. h := &keysHandler{
  1387. timeout: time.Hour,
  1388. server: server,
  1389. timer: &dummyRaftTimer{},
  1390. cluster: &fakeCluster{id: 1},
  1391. }
  1392. rw := httptest.NewRecorder()
  1393. h.ServeHTTP(rw, tt.req)
  1394. if rw.Code != tt.wcode {
  1395. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  1396. }
  1397. }
  1398. }
  1399. func TestServeKeysEvent(t *testing.T) {
  1400. req := mustNewRequest(t, "foo")
  1401. server := &resServer{
  1402. etcdserver.Response{
  1403. Event: &store.Event{
  1404. Action: store.Get,
  1405. Node: &store.NodeExtern{},
  1406. },
  1407. },
  1408. }
  1409. h := &keysHandler{
  1410. timeout: time.Hour,
  1411. server: server,
  1412. cluster: &fakeCluster{id: 1},
  1413. timer: &dummyRaftTimer{},
  1414. }
  1415. rw := httptest.NewRecorder()
  1416. h.ServeHTTP(rw, req)
  1417. wcode := http.StatusOK
  1418. wbody := mustMarshalEvent(
  1419. t,
  1420. &store.Event{
  1421. Action: store.Get,
  1422. Node: &store.NodeExtern{},
  1423. },
  1424. )
  1425. if rw.Code != wcode {
  1426. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1427. }
  1428. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1429. wcid := h.cluster.ID().String()
  1430. if gcid != wcid {
  1431. t.Errorf("cid = %s, want %s", gcid, wcid)
  1432. }
  1433. g := rw.Body.String()
  1434. if g != wbody {
  1435. t.Errorf("got body=%#v, want %#v", g, wbody)
  1436. }
  1437. }
  1438. func TestServeKeysWatch(t *testing.T) {
  1439. req := mustNewRequest(t, "/foo/bar")
  1440. ec := make(chan *store.Event)
  1441. dw := &dummyWatcher{
  1442. echan: ec,
  1443. }
  1444. server := &resServer{
  1445. etcdserver.Response{
  1446. Watcher: dw,
  1447. },
  1448. }
  1449. h := &keysHandler{
  1450. timeout: time.Hour,
  1451. server: server,
  1452. cluster: &fakeCluster{id: 1},
  1453. timer: &dummyRaftTimer{},
  1454. }
  1455. go func() {
  1456. ec <- &store.Event{
  1457. Action: store.Get,
  1458. Node: &store.NodeExtern{},
  1459. }
  1460. }()
  1461. rw := httptest.NewRecorder()
  1462. h.ServeHTTP(rw, req)
  1463. wcode := http.StatusOK
  1464. wbody := mustMarshalEvent(
  1465. t,
  1466. &store.Event{
  1467. Action: store.Get,
  1468. Node: &store.NodeExtern{},
  1469. },
  1470. )
  1471. if rw.Code != wcode {
  1472. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1473. }
  1474. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1475. wcid := h.cluster.ID().String()
  1476. if gcid != wcid {
  1477. t.Errorf("cid = %s, want %s", gcid, wcid)
  1478. }
  1479. g := rw.Body.String()
  1480. if g != wbody {
  1481. t.Errorf("got body=%#v, want %#v", g, wbody)
  1482. }
  1483. }
  1484. type recordingCloseNotifier struct {
  1485. *httptest.ResponseRecorder
  1486. cn chan bool
  1487. }
  1488. func (rcn *recordingCloseNotifier) CloseNotify() <-chan bool {
  1489. return rcn.cn
  1490. }
  1491. func TestHandleWatch(t *testing.T) {
  1492. defaultRwRr := func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1493. r := httptest.NewRecorder()
  1494. return r, r
  1495. }
  1496. noopEv := func(chan *store.Event) {}
  1497. tests := []struct {
  1498. getCtx func() context.Context
  1499. getRwRr func() (http.ResponseWriter, *httptest.ResponseRecorder)
  1500. doToChan func(chan *store.Event)
  1501. wbody string
  1502. }{
  1503. {
  1504. // Normal case: one event
  1505. context.Background,
  1506. defaultRwRr,
  1507. func(ch chan *store.Event) {
  1508. ch <- &store.Event{
  1509. Action: store.Get,
  1510. Node: &store.NodeExtern{},
  1511. }
  1512. },
  1513. mustMarshalEvent(
  1514. t,
  1515. &store.Event{
  1516. Action: store.Get,
  1517. Node: &store.NodeExtern{},
  1518. },
  1519. ),
  1520. },
  1521. {
  1522. // Channel is closed, no event
  1523. context.Background,
  1524. defaultRwRr,
  1525. func(ch chan *store.Event) {
  1526. close(ch)
  1527. },
  1528. "",
  1529. },
  1530. {
  1531. // Simulate a timed-out context
  1532. func() context.Context {
  1533. ctx, cancel := context.WithCancel(context.Background())
  1534. cancel()
  1535. return ctx
  1536. },
  1537. defaultRwRr,
  1538. noopEv,
  1539. "",
  1540. },
  1541. {
  1542. // Close-notifying request
  1543. context.Background,
  1544. func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1545. rw := &recordingCloseNotifier{
  1546. ResponseRecorder: httptest.NewRecorder(),
  1547. cn: make(chan bool, 1),
  1548. }
  1549. rw.cn <- true
  1550. return rw, rw.ResponseRecorder
  1551. },
  1552. noopEv,
  1553. "",
  1554. },
  1555. }
  1556. for i, tt := range tests {
  1557. rw, rr := tt.getRwRr()
  1558. wa := &dummyWatcher{
  1559. echan: make(chan *store.Event, 1),
  1560. sidx: 10,
  1561. }
  1562. tt.doToChan(wa.echan)
  1563. handleKeyWatch(tt.getCtx(), rw, wa, false, dummyRaftTimer{})
  1564. wcode := http.StatusOK
  1565. wct := "application/json"
  1566. wei := "10"
  1567. wri := "100"
  1568. wrt := "5"
  1569. if rr.Code != wcode {
  1570. t.Errorf("#%d: got code=%d, want %d", i, rr.Code, wcode)
  1571. }
  1572. h := rr.Header()
  1573. if ct := h.Get("Content-Type"); ct != wct {
  1574. t.Errorf("#%d: Content-Type=%q, want %q", i, ct, wct)
  1575. }
  1576. if ei := h.Get("X-Etcd-Index"); ei != wei {
  1577. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, ei, wei)
  1578. }
  1579. if ri := h.Get("X-Raft-Index"); ri != wri {
  1580. t.Errorf("#%d: X-Raft-Index=%q, want %q", i, ri, wri)
  1581. }
  1582. if rt := h.Get("X-Raft-Term"); rt != wrt {
  1583. t.Errorf("#%d: X-Raft-Term=%q, want %q", i, rt, wrt)
  1584. }
  1585. g := rr.Body.String()
  1586. if g != tt.wbody {
  1587. t.Errorf("#%d: got body=%#v, want %#v", i, g, tt.wbody)
  1588. }
  1589. }
  1590. }
  1591. func TestHandleWatchStreaming(t *testing.T) {
  1592. rw := &flushingRecorder{
  1593. httptest.NewRecorder(),
  1594. make(chan struct{}, 1),
  1595. }
  1596. wa := &dummyWatcher{
  1597. echan: make(chan *store.Event),
  1598. }
  1599. // Launch the streaming handler in the background with a cancellable context
  1600. ctx, cancel := context.WithCancel(context.Background())
  1601. done := make(chan struct{})
  1602. go func() {
  1603. handleKeyWatch(ctx, rw, wa, true, dummyRaftTimer{})
  1604. close(done)
  1605. }()
  1606. // Expect one Flush for the headers etc.
  1607. select {
  1608. case <-rw.ch:
  1609. case <-time.After(time.Second):
  1610. t.Fatalf("timed out waiting for flush")
  1611. }
  1612. // Expect headers but no body
  1613. wcode := http.StatusOK
  1614. wct := "application/json"
  1615. wbody := ""
  1616. if rw.Code != wcode {
  1617. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1618. }
  1619. h := rw.Header()
  1620. if ct := h.Get("Content-Type"); ct != wct {
  1621. t.Errorf("Content-Type=%q, want %q", ct, wct)
  1622. }
  1623. g := rw.Body.String()
  1624. if g != wbody {
  1625. t.Errorf("got body=%#v, want %#v", g, wbody)
  1626. }
  1627. // Now send the first event
  1628. select {
  1629. case wa.echan <- &store.Event{
  1630. Action: store.Get,
  1631. Node: &store.NodeExtern{},
  1632. }:
  1633. case <-time.After(time.Second):
  1634. t.Fatal("timed out waiting for send")
  1635. }
  1636. // Wait for it to be flushed...
  1637. select {
  1638. case <-rw.ch:
  1639. case <-time.After(time.Second):
  1640. t.Fatalf("timed out waiting for flush")
  1641. }
  1642. // And check the body is as expected
  1643. wbody = mustMarshalEvent(
  1644. t,
  1645. &store.Event{
  1646. Action: store.Get,
  1647. Node: &store.NodeExtern{},
  1648. },
  1649. )
  1650. g = rw.Body.String()
  1651. if g != wbody {
  1652. t.Errorf("got body=%#v, want %#v", g, wbody)
  1653. }
  1654. // Rinse and repeat
  1655. select {
  1656. case wa.echan <- &store.Event{
  1657. Action: store.Get,
  1658. Node: &store.NodeExtern{},
  1659. }:
  1660. case <-time.After(time.Second):
  1661. t.Fatal("timed out waiting for send")
  1662. }
  1663. select {
  1664. case <-rw.ch:
  1665. case <-time.After(time.Second):
  1666. t.Fatalf("timed out waiting for flush")
  1667. }
  1668. // This time, we expect to see both events
  1669. wbody = wbody + wbody
  1670. g = rw.Body.String()
  1671. if g != wbody {
  1672. t.Errorf("got body=%#v, want %#v", g, wbody)
  1673. }
  1674. // Finally, time out the connection and ensure the serving goroutine returns
  1675. cancel()
  1676. select {
  1677. case <-done:
  1678. case <-time.After(time.Second):
  1679. t.Fatalf("timed out waiting for done")
  1680. }
  1681. }
  1682. func TestTrimEventPrefix(t *testing.T) {
  1683. pre := "/abc"
  1684. tests := []struct {
  1685. ev *store.Event
  1686. wev *store.Event
  1687. }{
  1688. {
  1689. nil,
  1690. nil,
  1691. },
  1692. {
  1693. &store.Event{},
  1694. &store.Event{},
  1695. },
  1696. {
  1697. &store.Event{Node: &store.NodeExtern{Key: "/abc/def"}},
  1698. &store.Event{Node: &store.NodeExtern{Key: "/def"}},
  1699. },
  1700. {
  1701. &store.Event{PrevNode: &store.NodeExtern{Key: "/abc/ghi"}},
  1702. &store.Event{PrevNode: &store.NodeExtern{Key: "/ghi"}},
  1703. },
  1704. {
  1705. &store.Event{
  1706. Node: &store.NodeExtern{Key: "/abc/def"},
  1707. PrevNode: &store.NodeExtern{Key: "/abc/ghi"},
  1708. },
  1709. &store.Event{
  1710. Node: &store.NodeExtern{Key: "/def"},
  1711. PrevNode: &store.NodeExtern{Key: "/ghi"},
  1712. },
  1713. },
  1714. }
  1715. for i, tt := range tests {
  1716. ev := trimEventPrefix(tt.ev, pre)
  1717. if !reflect.DeepEqual(ev, tt.wev) {
  1718. t.Errorf("#%d: event = %+v, want %+v", i, ev, tt.wev)
  1719. }
  1720. }
  1721. }
  1722. func TestTrimNodeExternPrefix(t *testing.T) {
  1723. pre := "/abc"
  1724. tests := []struct {
  1725. n *store.NodeExtern
  1726. wn *store.NodeExtern
  1727. }{
  1728. {
  1729. nil,
  1730. nil,
  1731. },
  1732. {
  1733. &store.NodeExtern{Key: "/abc/def"},
  1734. &store.NodeExtern{Key: "/def"},
  1735. },
  1736. {
  1737. &store.NodeExtern{
  1738. Key: "/abc/def",
  1739. Nodes: []*store.NodeExtern{
  1740. {Key: "/abc/def/1"},
  1741. {Key: "/abc/def/2"},
  1742. },
  1743. },
  1744. &store.NodeExtern{
  1745. Key: "/def",
  1746. Nodes: []*store.NodeExtern{
  1747. {Key: "/def/1"},
  1748. {Key: "/def/2"},
  1749. },
  1750. },
  1751. },
  1752. }
  1753. for i, tt := range tests {
  1754. n := trimNodeExternPrefix(tt.n, pre)
  1755. if !reflect.DeepEqual(n, tt.wn) {
  1756. t.Errorf("#%d: node = %+v, want %+v", i, n, tt.wn)
  1757. }
  1758. }
  1759. }
  1760. func TestTrimPrefix(t *testing.T) {
  1761. tests := []struct {
  1762. in string
  1763. prefix string
  1764. w string
  1765. }{
  1766. {"/v2/members", "/v2/members", ""},
  1767. {"/v2/members/", "/v2/members", ""},
  1768. {"/v2/members/foo", "/v2/members", "foo"},
  1769. }
  1770. for i, tt := range tests {
  1771. if g := trimPrefix(tt.in, tt.prefix); g != tt.w {
  1772. t.Errorf("#%d: trimPrefix = %q, want %q", i, g, tt.w)
  1773. }
  1774. }
  1775. }
  1776. func TestNewMemberCollection(t *testing.T) {
  1777. fixture := []*etcdserver.Member{
  1778. &etcdserver.Member{
  1779. ID: 12,
  1780. Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"}},
  1781. RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"}},
  1782. },
  1783. &etcdserver.Member{
  1784. ID: 13,
  1785. Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:9090", "http://localhost:9091"}},
  1786. RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:9092", "http://localhost:9093"}},
  1787. },
  1788. }
  1789. got := newMemberCollection(fixture)
  1790. want := httptypes.MemberCollection([]httptypes.Member{
  1791. httptypes.Member{
  1792. ID: "c",
  1793. ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"},
  1794. PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"},
  1795. },
  1796. httptypes.Member{
  1797. ID: "d",
  1798. ClientURLs: []string{"http://localhost:9090", "http://localhost:9091"},
  1799. PeerURLs: []string{"http://localhost:9092", "http://localhost:9093"},
  1800. },
  1801. })
  1802. if !reflect.DeepEqual(&want, got) {
  1803. t.Fatalf("newMemberCollection failure: want=%#v, got=%#v", &want, got)
  1804. }
  1805. }
  1806. func TestNewMember(t *testing.T) {
  1807. fixture := &etcdserver.Member{
  1808. ID: 12,
  1809. Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"}},
  1810. RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"}},
  1811. }
  1812. got := newMember(fixture)
  1813. want := httptypes.Member{
  1814. ID: "c",
  1815. ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"},
  1816. PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"},
  1817. }
  1818. if !reflect.DeepEqual(want, got) {
  1819. t.Fatalf("newMember failure: want=%#v, got=%#v", want, got)
  1820. }
  1821. }