Handle breaking change for ServiceVirtualIP restore (#14149)
Consul 1.13.0 changed ServiceVirtualIP to use PeeredServiceName instead of ServiceName which was a breaking change for those using service mesh and wanted to restore their snapshot after upgrading to 1.13.0. This commit handles existing data with older ServiceName and converts it during restore so that there are no issues when restoring from older snapshots.
This commit is contained in:
parent
2b8e8280f5
commit
182399255b
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
agent: Fixed a compatibility issue when restoring snapshots from pre-1.13.0 versions of Consul [[GH-14107](https://github.com/hashicorp/consul/issues/14107)]
|
||||||
|
```
|
|
@ -1,8 +1,12 @@
|
||||||
package fsm
|
package fsm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/hashicorp/consul-net-rpc/go-msgpack/codec"
|
"github.com/hashicorp/consul-net-rpc/go-msgpack/codec"
|
||||||
"github.com/hashicorp/raft"
|
"github.com/hashicorp/raft"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/consul/state"
|
"github.com/hashicorp/consul/agent/consul/state"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
@ -886,11 +890,43 @@ func restoreSystemMetadata(header *SnapshotHeader, restore *state.Restore, decod
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreServiceVirtualIP(header *SnapshotHeader, restore *state.Restore, decoder *codec.Decoder) error {
|
func restoreServiceVirtualIP(header *SnapshotHeader, restore *state.Restore, decoder *codec.Decoder) error {
|
||||||
var req state.ServiceVirtualIP
|
// state.ServiceVirtualIP was changed in a breaking way in 1.13.0 (2e4cb6f77d2be36b02e9be0b289b24e5b0afb794).
|
||||||
|
// We attempt to reconcile the older type by decoding to a map then decoding that map into
|
||||||
|
// structs.PeeredServiceName first, and then structs.ServiceName.
|
||||||
|
var req struct {
|
||||||
|
Service map[string]interface{}
|
||||||
|
IP net.IP
|
||||||
|
|
||||||
|
structs.RaftIndex
|
||||||
|
}
|
||||||
if err := decoder.Decode(&req); err != nil {
|
if err := decoder.Decode(&req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := restore.ServiceVirtualIP(req); err != nil {
|
|
||||||
|
vip := state.ServiceVirtualIP{
|
||||||
|
IP: req.IP,
|
||||||
|
RaftIndex: req.RaftIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeeredServiceName is the expected primary key type.
|
||||||
|
var psn structs.PeeredServiceName
|
||||||
|
if err := mapstructure.Decode(req.Service, &psn); err != nil {
|
||||||
|
return fmt.Errorf("cannot decode to structs.PeeredServiceName: %w", err)
|
||||||
|
}
|
||||||
|
vip.Service = psn
|
||||||
|
|
||||||
|
// If the expected primary key field is empty, it must be the older ServiceName type.
|
||||||
|
if vip.Service.ServiceName.Name == "" {
|
||||||
|
var sn structs.ServiceName
|
||||||
|
if err := mapstructure.Decode(req.Service, &sn); err != nil {
|
||||||
|
return fmt.Errorf("cannot decode to structs.ServiceName: %w", err)
|
||||||
|
}
|
||||||
|
vip.Service = structs.PeeredServiceName{
|
||||||
|
ServiceName: sn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := restore.ServiceVirtualIP(vip); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -3,6 +3,7 @@ package fsm
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -962,3 +963,66 @@ func TestFSM_BadSnapshot_NilCAConfig(t *testing.T) {
|
||||||
require.EqualValues(t, 0, idx)
|
require.EqualValues(t, 0, idx)
|
||||||
require.Nil(t, config)
|
require.Nil(t, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test asserts that ServiceVirtualIP, which made a breaking change
|
||||||
|
// in 1.13.0, can still restore from older snapshots which use the old
|
||||||
|
// state.ServiceVirtualIP type.
|
||||||
|
func Test_restoreServiceVirtualIP(t *testing.T) {
|
||||||
|
psn := structs.PeeredServiceName{
|
||||||
|
ServiceName: structs.ServiceName{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
run := func(t *testing.T, input interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
// Encode input
|
||||||
|
encoder := codec.NewEncoder(buf, structs.MsgpackHandle)
|
||||||
|
require.NoError(t, encoder.Encode(input))
|
||||||
|
|
||||||
|
// Create a decoder
|
||||||
|
dec := codec.NewDecoder(buf, structs.MsgpackHandle)
|
||||||
|
|
||||||
|
logger := testutil.Logger(t)
|
||||||
|
fsm, err := New(nil, logger)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
restore := fsm.State().Restore()
|
||||||
|
|
||||||
|
// Call restore
|
||||||
|
require.NoError(t, restoreServiceVirtualIP(nil, restore, dec))
|
||||||
|
require.NoError(t, restore.Commit())
|
||||||
|
|
||||||
|
ip, err := fsm.State().VirtualIPForService(psn)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// 240->224 due to addIPOffset
|
||||||
|
require.Equal(t, "224.0.0.2", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("new ServiceVirtualIP with PeeredServiceName", func(t *testing.T) {
|
||||||
|
run(t, state.ServiceVirtualIP{
|
||||||
|
Service: psn,
|
||||||
|
IP: net.ParseIP("240.0.0.2"),
|
||||||
|
RaftIndex: structs.RaftIndex{},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("pre-1.13.0 ServiceVirtualIP with ServiceName", func(t *testing.T) {
|
||||||
|
type compatServiceVirtualIP struct {
|
||||||
|
Service structs.ServiceName
|
||||||
|
IP net.IP
|
||||||
|
RaftIndex structs.RaftIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
run(t, compatServiceVirtualIP{
|
||||||
|
Service: structs.ServiceName{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
IP: net.ParseIP("240.0.0.2"),
|
||||||
|
RaftIndex: structs.RaftIndex{},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -2211,8 +2211,8 @@ type PeeredServiceName struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceName struct {
|
type ServiceName struct {
|
||||||
Name string
|
Name string
|
||||||
acl.EnterpriseMeta
|
acl.EnterpriseMeta `mapstructure:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceName(name string, entMeta *acl.EnterpriseMeta) ServiceName {
|
func NewServiceName(name string, entMeta *acl.EnterpriseMeta) ServiceName {
|
||||||
|
|
Loading…
Reference in New Issue