diff --git a/consul/fsm.go b/consul/fsm.go index e4fcb2026f..d12e81578c 100644 --- a/consul/fsm.go +++ b/consul/fsm.go @@ -75,7 +75,18 @@ func (c *consulFSM) State() *StateStore { func (c *consulFSM) Apply(log *raft.Log) interface{} { buf := log.Data - switch structs.MessageType(buf[0]) { + msgType := structs.MessageType(buf[0]) + + // Check if this message type should be ignored when unknown. This is + // used so that new commands can be added with developer control if older + // versions can safely ignore the command, or if they should crash. + ignoreUnknown := false + if msgType&structs.IgnoreUnknownTypeFlag == structs.IgnoreUnknownTypeFlag { + msgType &= ^structs.IgnoreUnknownTypeFlag + ignoreUnknown = true + } + + switch msgType { case structs.RegisterRequestType: return c.decodeRegister(buf[1:], log.Index) case structs.DeregisterRequestType: @@ -89,7 +100,12 @@ func (c *consulFSM) Apply(log *raft.Log) interface{} { case structs.TombstoneRequestType: return c.applyTombstoneOperation(buf[1:], log.Index) default: - panic(fmt.Errorf("failed to apply request: %#v", buf)) + if ignoreUnknown { + c.logger.Printf("[WARN] consul.fsm: ignoring unknown message type (%d), upgrade to newer version", msgType) + return nil + } else { + panic(fmt.Errorf("failed to apply request: %#v", buf)) + } } } diff --git a/consul/fsm_test.go b/consul/fsm_test.go index 92e882f800..d135cad97e 100644 --- a/consul/fsm_test.go +++ b/consul/fsm_test.go @@ -1059,3 +1059,33 @@ func TestFSM_TombstoneReap(t *testing.T) { t.Fatalf("bad: %v", res) } } + +func TestFSM_IgnoreUnknown(t *testing.T) { + path, err := ioutil.TempDir("", "fsm") + if err != nil { + t.Fatalf("err: %v", err) + } + defer os.RemoveAll(path) + fsm, err := NewFSM(nil, path, os.Stderr) + if err != nil { + t.Fatalf("err: %v", err) + } + defer fsm.Close() + + // Create a new reap request + type UnknownRequest struct { + Foo string + } + req := UnknownRequest{Foo: "bar"} + msgType := structs.IgnoreUnknownTypeFlag | 64 + buf, err := structs.Encode(msgType, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Apply should work, even though not supported + resp := fsm.Apply(makeLog(buf)) + if err, ok := resp.(error); ok { + t.Fatalf("resp: %v", err) + } +} diff --git a/consul/structs/structs.go b/consul/structs/structs.go index b95711bc44..58f39387db 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -26,6 +26,15 @@ const ( TombstoneRequestType ) +const ( + // IgnoreUnknownTypeFlag is set along with a MessageType + // to indicate that the message type can be safely ignored + // if it is not recognized. This is for future proofing, so + // that new commands can be added in a way that won't cause + // old servers to crash when the FSM attempts to process them. + IgnoreUnknownTypeFlag MessageType = 128 +) + const ( // HealthAny is special, and is used as a wild card, // not as a specific state.