From bd2085ee8737f956f814e42d1e66b2adc87e088d Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Fri, 14 Mar 2025 23:09:08 -0400 Subject: [PATCH] implement leave command --- chord/client.go | 2 ++ chord/httpnode/client.go | 25 +++++++++++++++++++++++++ chord/httpnode/server.go | 11 ++++++++++- main.go | 12 +++++++++++- test.bash | 4 ++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/chord/client.go b/chord/client.go index 1cd4212..f6b52a0 100644 --- a/chord/client.go +++ b/chord/client.go @@ -18,6 +18,8 @@ type Client interface { // Bye tells p that its predecessor or successor is leaving // and had the given successor list. Bye(ctx context.Context, p, n Peer, succ []Peer) error + // SayBye tells p to leave the network. + SayBye(ctx context.Context, p Peer) error // Get asks s for a saved value. Get(ctx context.Context, s Peer, id ID) (string, error) diff --git a/chord/httpnode/client.go b/chord/httpnode/client.go index ef096db..9bf667c 100644 --- a/chord/httpnode/client.go +++ b/chord/httpnode/client.go @@ -203,3 +203,28 @@ func (cl *Client) Bye(ctx context.Context, p, n chord.Peer, succ []chord.Peer) e } return nil } + +// SayBye tells p to leave the network. +func (cl *Client) SayBye(ctx context.Context, p chord.Peer) error { + _, addr := p.Values() + if !addr.IsValid() { + return errors.New("SayBye with invalid peer") + } + url := url.URL{ + Scheme: "http", + Host: addr.String(), + Path: path.Join("/", cl.APIBase), + } + req, err := http.NewRequestWithContext(ctx, "DELETE", url.String(), nil) + if err != nil { + return err + } + resp, err := cl.HTTP.Do(req) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + return nil +} diff --git a/chord/httpnode/server.go b/chord/httpnode/server.go index 060c789..443d327 100644 --- a/chord/httpnode/server.go +++ b/chord/httpnode/server.go @@ -57,6 +57,7 @@ func (n *Node) Router() http.Handler { m.HandleFunc("POST /pred", n.notify) m.HandleFunc("GET /neighbors", n.neighbors) m.HandleFunc("POST /bye", n.bye) + m.HandleFunc("DELETE /", n.saybye) return m } @@ -170,6 +171,14 @@ func (n *Node) bye(w http.ResponseWriter, r *http.Request) { } n.self.Leave(p, s) w.WriteHeader(http.StatusNoContent) - // Close the listener so that the server will shut down. +} + +func (n *Node) saybye(w http.ResponseWriter, r *http.Request) { + err := chord.Leave(r.Context(), n.client, n.self) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + w.WriteHeader(http.StatusOK) n.l.Close() } diff --git a/main.go b/main.go index 8ec909b..eb13a6e 100644 --- a/main.go +++ b/main.go @@ -194,7 +194,17 @@ func cliJoin(ctx context.Context, cmd *cli.Command) error { return srv.Shutdown(ctx) } -func cliLeave(ctx context.Context, cmd *cli.Command) error { return errors.New("not implemented") } +func cliLeave(ctx context.Context, cmd *cli.Command) error { + // I'm not really clear on why this command takes a startup IP and port. + // All we need is the node to tell to leave. + n, err := netip.ParseAddrPort(cmd.String("n")) + if err != nil { + return err + } + cl := &httpnode.Client{HTTP: http.Client{Timeout: 5 * time.Second}} + return cl.SayBye(ctx, chord.Address(n)) +} + func cliLookup(ctx context.Context, cmd *cli.Command) error { return errors.New("not implemented") } func cliPut(ctx context.Context, cmd *cli.Command) error { return errors.New("not implemented") } func cliGet(ctx context.Context, cmd *cli.Command) error { return errors.New("not implemented") } diff --git a/test.bash b/test.bash index f884f98..30021b8 100755 --- a/test.bash +++ b/test.bash @@ -16,4 +16,8 @@ THIRD=$! sleep 5 # Each node logs its predecessor and successors. At this point, we see the ring. + +# Test leaving. +./chord-node leave -n 127.0.0.1:3000 + kill $FIRST $SECOND $THIRD