Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relax cluster upgrade requirements around API extensions #1012

Merged
merged 4 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/incusd/api_1.0.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func api10Get(d *Daemon, r *http.Request) response.Response {
}

srv := api.ServerUntrusted{
APIExtensions: version.APIExtensions,
APIExtensions: version.APIExtensions[:d.apiExtensions],
APIStatus: "stable",
APIVersion: version.APIVersion,
Public: false,
Expand Down
8 changes: 8 additions & 0 deletions cmd/incusd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ type Daemon struct {
// OVN clients.
ovnnb *ovn.NB
ovnsb *ovn.SB

// API info.
apiExtensions int
}

// DaemonConfig holds configuration values for Daemon.
Expand Down Expand Up @@ -197,6 +200,7 @@ func newDaemon(config *DaemonConfig, os *sys.OS) *Daemon {
shutdownCtx: shutdownCtx,
shutdownCancel: shutdownCancel,
shutdownDoneCh: make(chan error),
apiExtensions: len(version.APIExtensions),
}

d.serverCert = func() *localtls.CertInfo { return d.serverCertInt }
Expand Down Expand Up @@ -2384,6 +2388,10 @@ func (d *Daemon) nodeRefreshTask(heartbeatData *cluster.APIHeartbeat, isLeader b
return
}

if heartbeatData.Version.MinAPIExtensions > 0 && heartbeatData.Version.MinAPIExtensions != d.apiExtensions {
d.apiExtensions = heartbeatData.Version.MinAPIExtensions
}

// If the max version of the cluster has changed, check whether we need to upgrade.
if d.lastNodeList == nil || d.lastNodeList.Version.APIExtensions != heartbeatData.Version.APIExtensions || d.lastNodeList.Version.Schema != heartbeatData.Version.Schema {
err := cluster.MaybeUpdate(s)
Expand Down
16 changes: 11 additions & 5 deletions internal/server/cluster/heartbeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ type APIHeartbeatMember struct {

// APIHeartbeatVersion contains max versions for all nodes in cluster.
type APIHeartbeatVersion struct {
Schema int
APIExtensions int
Schema int
APIExtensions int
MinAPIExtensions int
}

// NewAPIHearbeat returns initialized APIHeartbeat.
Expand All @@ -74,7 +75,7 @@ type APIHeartbeat struct {
// Update updates an existing APIHeartbeat struct with the raft and all node states supplied.
// If allNodes provided is an empty set then this is considered a non-full state list.
func (hbState *APIHeartbeat) Update(fullStateList bool, raftNodes []db.RaftNode, allNodes []db.NodeInfo, offlineThreshold time.Duration) {
var maxSchemaVersion, maxAPIExtensionsVersion int
var maxSchemaVersion, maxAPIExtensionsVersion, minAPIExtensionsVersion int

if hbState.Members == nil {
hbState.Members = make(map[int64]APIHeartbeatMember)
Expand Down Expand Up @@ -115,14 +116,19 @@ func (hbState *APIHeartbeat) Update(fullStateList bool, raftNodes []db.RaftNode,
maxAPIExtensionsVersion = node.APIExtensions
}

if minAPIExtensionsVersion == 0 || node.APIExtensions < minAPIExtensionsVersion {
minAPIExtensionsVersion = node.APIExtensions
}

if node.Schema > maxSchemaVersion {
maxSchemaVersion = node.Schema
}
}

hbState.Version = APIHeartbeatVersion{
Schema: maxSchemaVersion,
APIExtensions: maxAPIExtensionsVersion,
Schema: maxSchemaVersion,
APIExtensions: maxAPIExtensionsVersion,
MinAPIExtensions: minAPIExtensionsVersion,
}

if len(raftNodeMap) > 0 && hbState.cluster != nil {
Expand Down
3 changes: 2 additions & 1 deletion internal/server/db/cluster/open.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ func checkClusterIsUpgradable(ctx context.Context, tx *sql.Tx, target [2]int) er
}

for _, version := range versions {
n, err := daemonUtil.CompareVersions(target, version)
// Compare schema versions only.
n, err := daemonUtil.CompareVersions(target, version, false)
if err != nil {
return err
}
Expand Down
10 changes: 5 additions & 5 deletions internal/server/db/cluster/open_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ func TestEnsureSchema_ClusterNotUpgradable(t *testing.T) {
addNode(t, db, "1", schema, apiExtensions)
addNode(t, db, "2", schema, apiExtensions-1)
},
false, // The schema was not updated
"", // No error is returned
true, // The schema was not updated
"", // No error is returned
},
{
`this node's schema is behind`,
Expand All @@ -73,8 +73,8 @@ func TestEnsureSchema_ClusterNotUpgradable(t *testing.T) {
addNode(t, db, "1", schema, apiExtensions)
addNode(t, db, "2", schema, apiExtensions+1)
},
false,
"This cluster member's version is behind, please upgrade",
true,
"",
},
{
`inconsistent schema version and API extensions number`,
Expand All @@ -83,7 +83,7 @@ func TestEnsureSchema_ClusterNotUpgradable(t *testing.T) {
addNode(t, db, "2", schema+1, apiExtensions-1)
},
false,
"Cluster members have inconsistent versions",
"This cluster member's version is behind, please upgrade",
},
}

Expand Down
13 changes: 9 additions & 4 deletions internal/server/db/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ type NodeInfoArgs struct {
// ToAPI returns an API entry.
func (n NodeInfo) ToAPI(ctx context.Context, tx *ClusterTx, args NodeInfoArgs) (*api.ClusterMember, error) {
var err error
var maxVersion [2]int
var failureDomain string

domainID := args.MemberFailureDomains[n.Address]
Expand Down Expand Up @@ -155,13 +154,19 @@ func (n NodeInfo) ToAPI(ctx context.Context, tx *ClusterTx, args NodeInfoArgs) (
result.Status = "Offline"
result.Message = fmt.Sprintf("No heartbeat for %s (%s)", time.Since(n.Heartbeat), n.Heartbeat)
} else {
// Check for max DB schema and API extensions.
maxVersion, err := tx.GetNodeMaxVersion(ctx)
if err != nil {
return nil, err
}

// Check if up to date.
n, err := localUtil.CompareVersions(maxVersion, n.Version())
ret, err := localUtil.CompareVersions(maxVersion, n.Version(), true)
if err != nil {
return nil, err
}

if n == 1 {
if ret == 1 {
result.Status = "Blocked"
result.Message = "Needs updating to newer version"
}
Expand Down Expand Up @@ -336,7 +341,7 @@ func (c *ClusterTx) NodeIsOutdated(ctx context.Context) (bool, error) {
continue
}

n, err := localUtil.CompareVersions(node.Version(), version)
n, err := localUtil.CompareVersions(node.Version(), version, true)
if err != nil {
return false, fmt.Errorf("Failed to compare with version of member %s: %w", node.Name, err)
}
Expand Down
8 changes: 7 additions & 1 deletion internal/server/util/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ import (
// Return an error if inconsistent versions are detected, for example the first
// member's schema is greater than the second's, but the number of extensions is
// smaller.
func CompareVersions(version1, version2 [2]int) (int, error) {
func CompareVersions(version1, version2 [2]int, checkExtensions bool) (int, error) {
schema1, extensions1 := version1[0], version1[1]
schema2, extensions2 := version2[0], version2[1]

if !checkExtensions {
// Don't compare API extensions.
extensions1 = 0
extensions2 = 0
}

if schema1 == schema2 && extensions1 == extensions2 {
return 0, nil
}
Expand Down
Loading