func (e *etcd) SaveRoute(route models.Route) error { key := generateHttpRouteKey(route) retries := 0 for retries <= maxRetries { response, err := e.keysAPI.Get(context.Background(), key, readOpts()) // Update if response != nil && err == nil { var existingRoute models.Route err = json.Unmarshal([]byte(response.Node.Value), &existingRoute) if err != nil { return err } route.ModificationTag = existingRoute.ModificationTag route.ModificationTag.Increment() routeJSON, _ := json.Marshal(route) _, err = e.keysAPI.Set(context.Background(), key, string(routeJSON), updateOptsWithTTL(route.TTL, response.Node.ModifiedIndex)) if err == nil { break } } else if cerr, ok := err.(client.Error); ok && cerr.Code == client.ErrorCodeKeyNotFound { //create // Delete came in between a read and an update if retries > 0 { return ErrorConflict } var tag models.ModificationTag tag, err = models.NewModificationTag() if err != nil { return err } route.ModificationTag = tag routeJSON, _ := json.Marshal(route) _, err = e.keysAPI.Set(ctx(), key, string(routeJSON), createOpts(route.TTL)) if err == nil { break } } // only retry on a compare and swap error if cerr, ok := err.(client.Error); ok && cerr.Code == client.ErrorCodeTestFailed { retries++ } else { return err } } if retries > maxRetries { return ErrorConflict } return nil }
fakeKeysAPI.GetReturns(nil, keyNotFoundError) }) It("Creates a route if none exist", func() { err := fakeEtcd.SaveRoute(route) Expect(err).NotTo(HaveOccurred()) Expect(fakeKeysAPI.GetCallCount()).To(Equal(1)) Expect(fakeKeysAPI.SetCallCount()).To(Equal(1)) _, _, json, _ := fakeKeysAPI.SetArgsForCall(0) Expect(json).To(ContainSubstring("\"index\":0")) }) }) Context("when an entry already exists", func() { BeforeEach(func() { route.ModificationTag = models.ModificationTag{Guid: "guid", Index: 5} routeJson, err := json.Marshal(&route) Expect(err).ToNot(HaveOccurred()) fakeResp := &client.Response{Node: &client.Node{Value: string(routeJson)}} fakeKeysAPI.GetReturns(fakeResp, nil) }) It("Updates the route and increments the tag index", func() { route2 := models.Route{ Route: "some-route/path", Port: 5500, IP: "3.1.5.7", TTL: 1000, LogGuid: "your-guid", RouteServiceUrl: "https://new-rs.com", }