client_test.go 50 KB

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