-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
deployer: add a bunch of test coverage and fix a few panics #20694
Changes from all commits
94c5c75
60a6c1e
f13c915
b928ddf
68474d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,32 +13,42 @@ import ( | |
"sort" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"golang.org/x/exp/maps" | ||
|
||
"github.com/hashicorp/go-hclog" | ||
|
||
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" | ||
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" | ||
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" | ||
"github.com/hashicorp/consul/proto-public/pbresource" | ||
"github.com/hashicorp/go-hclog" | ||
"golang.org/x/exp/maps" | ||
|
||
"github.com/hashicorp/consul/testing/deployer/util" | ||
) | ||
|
||
const DockerPrefix = "cslc" // ConSuLCluster | ||
|
||
func Compile(logger hclog.Logger, raw *Config) (*Topology, error) { | ||
return compile(logger, raw, nil) | ||
return compile(logger, raw, nil, "") | ||
} | ||
|
||
func Recompile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would not want to expose the Possibly if deployer-meta-tests get written for stuff outside of the |
||
if prev == nil { | ||
return nil, errors.New("missing previous topology") | ||
} | ||
return compile(logger, raw, prev) | ||
return compile(logger, raw, prev, "") | ||
} | ||
|
||
func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error) { | ||
func compile(logger hclog.Logger, raw *Config, prev *Topology, testingID string) (*Topology, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding this so that we can control the id assignment during testing. |
||
if logger == nil { | ||
return nil, errors.New("logger is required") | ||
} | ||
if raw == nil { | ||
return nil, errors.New("config is required") | ||
} | ||
|
||
var id string | ||
if prev == nil { | ||
if testingID != "" { | ||
id = testingID | ||
} else if prev == nil { | ||
var err error | ||
id, err = newTopologyID() | ||
if err != nil { | ||
|
@@ -90,22 +100,17 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
nextIndex int // use a global index so any shared networks work properly with assignments | ||
) | ||
|
||
foundPeerNames := make(map[string]map[string]struct{}) | ||
for _, c := range raw.Clusters { | ||
compileCluster := func(c *Cluster) (map[string]struct{}, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A follow-on PR can extract these anonymous methods as real methods. I did it this way to make it more obvious where the bugfixes were, and to add error logging context. |
||
if c.Name == "" { | ||
return nil, fmt.Errorf("cluster has no name") | ||
} | ||
|
||
foundPeerNames[c.Name] = make(map[string]struct{}) | ||
foundPeerNames := make(map[string]struct{}) | ||
|
||
if !IsValidLabel(c.Name) { | ||
return nil, fmt.Errorf("cluster name is not valid: %s", c.Name) | ||
} | ||
|
||
if _, exists := clusters[c.Name]; exists { | ||
return nil, fmt.Errorf("cannot have two clusters with the same name %q; use unique names and override the Datacenter field if that's what you want", c.Name) | ||
} | ||
|
||
if c.Datacenter == "" { | ||
c.Datacenter = c.Name | ||
} else { | ||
|
@@ -114,7 +119,6 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
} | ||
} | ||
|
||
clusters[c.Name] = c | ||
if c.NetworkName == "" { | ||
c.NetworkName = c.Name | ||
} | ||
|
@@ -158,6 +162,7 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
m = make(map[string]struct{}) | ||
tenancies[partition] = m | ||
} | ||
m["default"] = struct{}{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One of the ways to introduce a tenancy would inject the default, but this one did not. Correcting this omission. The system will automatically create this anyway so this just accurately reflects the tenancies that will exist. |
||
m[namespace] = struct{}{} | ||
} | ||
|
||
|
@@ -203,8 +208,7 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
addTenancy(res.Id.Tenancy.Partition, res.Id.Tenancy.Namespace) | ||
} | ||
|
||
seenNodes := make(map[NodeID]struct{}) | ||
for _, n := range c.Nodes { | ||
compileClusterNode := func(n *Node) (map[string]struct{}, error) { | ||
if n.Name == "" { | ||
return nil, fmt.Errorf("cluster %q node has no name", c.Name) | ||
} | ||
|
@@ -238,10 +242,9 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
} | ||
addTenancy(n.Partition, "default") | ||
|
||
if _, exists := seenNodes[n.ID()]; exists { | ||
return nil, fmt.Errorf("cannot have two nodes in the same cluster %q with the same name %q", c.Name, n.ID()) | ||
if n.IsServer() && n.Partition != "default" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding this restriction. Practically this would have failed in consul itself, but this makes it fail up higher where it's more actionable. |
||
return nil, fmt.Errorf("server agents must always be in the default partition") | ||
} | ||
seenNodes[n.ID()] = struct{}{} | ||
|
||
if len(n.usedPorts) != 0 { | ||
return nil, fmt.Errorf("user cannot specify the usedPorts field") | ||
|
@@ -328,7 +331,10 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
return nil, fmt.Errorf("cluster %q node %q uses dataplane, but has more than one service", c.Name, n.Name) | ||
} | ||
|
||
seenServices := make(map[ID]struct{}) | ||
var ( | ||
foundPeerNames = make(map[string]struct{}) | ||
seenServices = make(map[ID]struct{}) | ||
) | ||
for _, wrk := range n.Workloads { | ||
if n.IsAgent() { | ||
// Default to that of the enclosing node. | ||
|
@@ -412,7 +418,7 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
dest.ID.Partition = "" // irrelevant here; we'll set it to the value of the OTHER side for plumbing purposes in tests | ||
} | ||
dest.ID.Namespace = NamespaceOrDefault(dest.ID.Namespace) | ||
foundPeerNames[c.Name][dest.Peer] = struct{}{} | ||
foundPeerNames[dest.Peer] = struct{}{} | ||
} | ||
|
||
addTenancy(dest.ID.Partition, dest.ID.Namespace) | ||
|
@@ -431,7 +437,7 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
} | ||
if dest.PortName == "" && n.IsV2() { | ||
// Assume this is a v1->v2 conversion and name it. | ||
dest.PortName = "legacy" | ||
dest.PortName = V1DefaultPortName | ||
} | ||
} | ||
|
||
|
@@ -476,6 +482,15 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
Protocol: cfg.ActualProtocol, | ||
}) | ||
} | ||
sort.Slice(svcPorts, func(i, j int) bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes the output deterministic to aid in unit test comparisons. |
||
a, b := svcPorts[i], svcPorts[j] | ||
if a.TargetPort < b.TargetPort { | ||
return true | ||
} else if a.TargetPort > b.TargetPort { | ||
return false | ||
} | ||
return a.Protocol < b.Protocol | ||
}) | ||
|
||
v2svc := &pbcatalog.Service{ | ||
Workloads: &pbcatalog.WorkloadSelector{}, | ||
|
@@ -520,6 +535,31 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
} | ||
} | ||
} | ||
return foundPeerNames, nil | ||
} | ||
|
||
seenNodes := make(map[NodeID]struct{}) | ||
for _, n := range c.Nodes { | ||
peerNames, err := compileClusterNode(n) | ||
if err != nil { | ||
return nil, fmt.Errorf("error compiling node %q: %w", n.Name, err) | ||
} | ||
|
||
if _, exists := seenNodes[n.ID()]; exists { | ||
return nil, fmt.Errorf("cannot have two nodes in the same cluster %q with the same name %q", c.Name, n.ID()) | ||
} | ||
seenNodes[n.ID()] = struct{}{} | ||
|
||
maps.Copy(foundPeerNames, peerNames) | ||
} | ||
|
||
// Default anything in the toplevel services map. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Omission: many of the pathways to implicitly create this v2 services map will default the protocol, but the BYO map does not. |
||
for _, svc := range c.Services { | ||
for _, port := range svc.Ports { | ||
if port.Protocol == pbcatalog.Protocol_PROTOCOL_UNSPECIFIED { | ||
port.Protocol = pbcatalog.Protocol_PROTOCOL_TCP | ||
} | ||
} | ||
} | ||
|
||
if err := assignVirtualIPs(c); err != nil { | ||
|
@@ -582,6 +622,24 @@ func compile(logger hclog.Logger, raw *Config, prev *Topology) (*Topology, error | |
return nil, fmt.Errorf("cluster %q references non-default partitions or namespaces but is CE", c.Name) | ||
} | ||
} | ||
|
||
return foundPeerNames, nil | ||
} | ||
|
||
foundPeerNames := make(map[string]map[string]struct{}) | ||
for _, c := range raw.Clusters { | ||
peerNames, err := compileCluster(c) | ||
if err != nil { | ||
return nil, fmt.Errorf("error building cluster %q: %w", c.Name, err) | ||
} | ||
|
||
foundPeerNames[c.Name] = peerNames | ||
|
||
if _, exists := clusters[c.Name]; exists { | ||
return nil, fmt.Errorf("cannot have two clusters with the same name %q; use unique names and override the Datacenter field if that's what you want", c.Name) | ||
} | ||
|
||
clusters[c.Name] = c | ||
} | ||
|
||
clusteredPeerings := make(map[string]map[string]*PeerCluster) // local-cluster -> local-peer -> info | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the
testingID
value be set at a high level? Like wherever compile is called?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests in
compile_test.go
call the function this way:I'm not calling
Compile
, but rather the unexportedcompile
and using thetestingID
field which is otherwise inaccessible.