/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package helpers import ( "hash/fnv" "testing" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" ) func TestZonesToSet(t *testing.T) { functionUnderTest := "ZonesToSet" // First part: want an error sliceOfZones := []string{"", ",", "us-east-1a, , us-east-1d", ", us-west-1b", "us-west-2b,"} for _, zones := range sliceOfZones { if got, err := ZonesToSet(zones); err == nil { t.Errorf("%v(%v) returned (%v), want (%v)", functionUnderTest, zones, got, "an error") } } // Second part: want no error tests := []struct { zones string want sets.String }{ { zones: "us-east-1a", want: sets.String{"us-east-1a": sets.Empty{}}, }, { zones: "us-east-1a, us-west-2a", want: sets.String{ "us-east-1a": sets.Empty{}, "us-west-2a": sets.Empty{}, }, }, } for _, tt := range tests { if got, err := ZonesToSet(tt.zones); err != nil || !got.Equal(tt.want) { t.Errorf("%v(%v) returned (%v), want (%v)", functionUnderTest, tt.zones, got, tt.want) } } } func TestChooseZonesForVolume(t *testing.T) { checkFnv32(t, "henley", 1180403676) // 1180403676 mod 3 == 0, so the offset from "henley" is 0, which makes it easier to verify this by inspection // A few others checkFnv32(t, "henley-", 2652299129) checkFnv32(t, "henley-a", 1459735322) checkFnv32(t, "", 2166136261) tests := []struct { Zones sets.String VolumeName string NumZones uint32 Expected sets.String }{ // Test for PVC names that don't have a dash { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley", NumZones: 1, Expected: sets.NewString("a" /* hash("henley") == 0 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley", NumZones: 2, Expected: sets.NewString("a" /* hash("henley") == 0 */, "b"), }, // Tests for PVC names that end in - number, but don't look like statefulset PVCs { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-0", NumZones: 1, Expected: sets.NewString("a" /* hash("henley") == 0 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-0", NumZones: 2, Expected: sets.NewString("a" /* hash("henley") == 0 */, "b"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-1", NumZones: 1, Expected: sets.NewString("b" /* hash("henley") + 1 == 1 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-1", NumZones: 2, Expected: sets.NewString("c" /* hash("henley") + 1 + 1(startingIndex) == 2 */, "a"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-2", NumZones: 1, Expected: sets.NewString("c" /* hash("henley") + 2 == 2 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-2", NumZones: 2, Expected: sets.NewString("b" /* hash("henley") + 2 + 2(startingIndex) == 4 */, "c"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-3", NumZones: 1, Expected: sets.NewString("a" /* hash("henley") + 3 == 3 === 0 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-3", NumZones: 2, Expected: sets.NewString("a" /* hash("henley") + 3 + 3(startingIndex) == 6 */, "b"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-4", NumZones: 1, Expected: sets.NewString("b" /* hash("henley") + 4 == 4 === 1 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-4", NumZones: 2, Expected: sets.NewString("c" /* hash("henley") + 4 + 4(startingIndex) == 8 */, "a"), }, // Tests for PVC names that are edge cases { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-", NumZones: 1, Expected: sets.NewString("c" /* hash("henley-") = 2652299129 === 2 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-", NumZones: 2, Expected: sets.NewString("c" /* hash("henley-") = 2652299129 === 2 mod 3 = 2 */, "a"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-a", NumZones: 1, Expected: sets.NewString("c" /* hash("henley-a") = 1459735322 === 2 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "henley-a", NumZones: 2, Expected: sets.NewString("c" /* hash("henley-a") = 1459735322 === 2 mod 3 = 2 */, "a"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium--1", NumZones: 1, Expected: sets.NewString("c" /* hash("") + 1 == 2166136261 + 1 === 2 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium--1", NumZones: 2, Expected: sets.NewString("a" /* hash("") + 1 + 1(startingIndex) == 2166136261 + 1 + 1 === 3 mod 3 = 0 */, "b"), }, // Tests for PVC names for simple StatefulSet cases { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley-1", NumZones: 1, Expected: sets.NewString("b" /* hash("henley") + 1 == 1 */), }, // Tests for PVC names for simple StatefulSet cases { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley-1", NumZones: 2, Expected: sets.NewString("c" /* hash("henley") + 1 + 1(startingIndex) == 2 */, "a"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "loud-henley-1", NumZones: 1, Expected: sets.NewString("b" /* hash("henley") + 1 == 1 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "loud-henley-1", NumZones: 2, Expected: sets.NewString("c" /* hash("henley") + 1 + 1(startingIndex) == 2 */, "a"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "quiet-henley-2", NumZones: 1, Expected: sets.NewString("c" /* hash("henley") + 2 == 2 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "quiet-henley-2", NumZones: 2, Expected: sets.NewString("b" /* hash("henley") + 2 + 2(startingIndex) == 4 */, "c"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley-2", NumZones: 1, Expected: sets.NewString("c" /* hash("henley") + 2 == 2 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley-2", NumZones: 2, Expected: sets.NewString("b" /* hash("henley") + 2 + 2(startingIndex) == 4 */, "c"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley-3", NumZones: 1, Expected: sets.NewString("a" /* hash("henley") + 3 == 3 === 0 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley-3", NumZones: 2, Expected: sets.NewString("a" /* hash("henley") + 3 + 3(startingIndex) == 6 === 6 mod 3 = 0 */, "b"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley-4", NumZones: 1, Expected: sets.NewString("b" /* hash("henley") + 4 == 4 === 1 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley-4", NumZones: 2, Expected: sets.NewString("c" /* hash("henley") + 4 + 4(startingIndex) == 8 === 2 mod 3 */, "a"), }, // Tests for statefulsets (or claims) with dashes in the names { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-alpha-henley-2", NumZones: 1, Expected: sets.NewString("c" /* hash("henley") + 2 == 2 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-alpha-henley-2", NumZones: 2, Expected: sets.NewString("b" /* hash("henley") + 2 + 2(startingIndex) == 4 */, "c"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-beta-henley-3", NumZones: 1, Expected: sets.NewString("a" /* hash("henley") + 3 == 3 === 0 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-beta-henley-3", NumZones: 2, Expected: sets.NewString("a" /* hash("henley") + 3 + 3(startingIndex) == 6 === 0 mod 3 */, "b"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-gamma-henley-4", NumZones: 1, Expected: sets.NewString("b" /* hash("henley") + 4 == 4 === 1 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-gamma-henley-4", NumZones: 2, Expected: sets.NewString("c" /* hash("henley") + 4 + 4(startingIndex) == 8 === 2 mod 3 */, "a"), }, // Tests for statefulsets name ending in - { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley--2", NumZones: 1, Expected: sets.NewString("a" /* hash("") + 2 == 0 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley--2", NumZones: 2, Expected: sets.NewString("c" /* hash("") + 2 + 2(startingIndex) == 2 mod 3 */, "a"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley--3", NumZones: 1, Expected: sets.NewString("b" /* hash("") + 3 == 1 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley--3", NumZones: 2, Expected: sets.NewString("b" /* hash("") + 3 + 3(startingIndex) == 1 mod 3 */, "c"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley--4", NumZones: 1, Expected: sets.NewString("c" /* hash("") + 4 == 2 mod 3 */), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley--4", NumZones: 2, Expected: sets.NewString("a" /* hash("") + 4 + 4(startingIndex) == 0 mod 3 */, "b"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley--4", NumZones: 3, Expected: sets.NewString("c" /* hash("") + 4 == 2 mod 3 */, "a", "b"), }, { Zones: sets.NewString("a", "b", "c"), VolumeName: "medium-henley--4", NumZones: 4, Expected: sets.NewString("c" /* hash("") + 4 + 9(startingIndex) == 2 mod 3 */, "a", "b", "c"), }, { Zones: sets.NewString("a", "b", "c", "d", "e", "f", "g", "h", "i"), VolumeName: "henley-0", NumZones: 2, Expected: sets.NewString("a" /* hash("henley") == 0 */, "b"), }, { Zones: sets.NewString("a", "b", "c", "d", "e", "f", "g", "h", "i"), VolumeName: "henley-1", NumZones: 2, Expected: sets.NewString("c" /* hash("henley") == 0 + 2 */, "d"), }, { Zones: sets.NewString("a", "b", "c", "d", "e", "f", "g", "h", "i"), VolumeName: "henley-2", NumZones: 2, Expected: sets.NewString("e" /* hash("henley") == 0 + 2 + 2(startingIndex) */, "f"), }, { Zones: sets.NewString("a", "b", "c", "d", "e", "f", "g", "h", "i"), VolumeName: "henley-3", NumZones: 2, Expected: sets.NewString("g" /* hash("henley") == 0 + 2 + 4(startingIndex) */, "h"), }, { Zones: sets.NewString("a", "b", "c", "d", "e", "f", "g", "h", "i"), VolumeName: "henley-0", NumZones: 3, Expected: sets.NewString("a" /* hash("henley") == 0 */, "b", "c"), }, { Zones: sets.NewString("a", "b", "c", "d", "e", "f", "g", "h", "i"), VolumeName: "henley-1", NumZones: 3, Expected: sets.NewString("d" /* hash("henley") == 0 + 1 + 2(startingIndex) */, "e", "f"), }, { Zones: sets.NewString("a", "b", "c", "d", "e", "f", "g", "h", "i"), VolumeName: "henley-2", NumZones: 3, Expected: sets.NewString("g" /* hash("henley") == 0 + 2 + 4(startingIndex) */, "h", "i"), }, { Zones: sets.NewString("a", "b", "c", "d", "e", "f", "g", "h", "i"), VolumeName: "henley-3", NumZones: 3, Expected: sets.NewString("a" /* hash("henley") == 0 + 3 + 6(startingIndex) */, "b", "c"), }, // Test for no zones { Zones: sets.NewString(), VolumeName: "henley-1", Expected: sets.NewString(), }, { Zones: nil, VolumeName: "henley-2", Expected: sets.NewString(), }, } for _, test := range tests { actual := ChooseZonesForVolume(test.Zones, test.VolumeName, test.NumZones) if !actual.Equal(test.Expected) { t.Errorf("Test %v failed, expected zone %#v, actual %#v", test, test.Expected, actual) } } } func TestSelectZoneForVolume(t *testing.T) { nodeWithZoneLabels := &v1.Node{} nodeWithZoneLabels.Labels = map[string]string{v1.LabelZoneFailureDomain: "zoneX"} nodeWithNoLabels := &v1.Node{} tests := []struct { // Parameters passed by test to SelectZoneForVolume Name string ZonePresent bool Zone string ZonesPresent bool Zones string ZonesWithNodes string Node *v1.Node AllowedTopologies []v1.TopologySelectorTerm // Expectations around returned zone from SelectZoneForVolume Reject bool // expect error due to validation failing ExpectSpecificZone bool // expect returned zone to specifically match a single zone (rather than one from a set) ExpectedZone string // single zone that should perfectly match returned zone (requires ExpectSpecificZone to be true) ExpectedZones string // set of zones one of whose members should match returned zone (requires ExpectSpecificZone to be false) }{ // NEGATIVE TESTS // Zone and Zones are both specified [Fail] // [1] Node irrelevant // [2] Zone and Zones parameters presents // [3] AllowedTopologies irrelevant { Name: "Nil_Node_with_Zone_Zones_parameters_present", ZonePresent: true, Zone: "zoneX", ZonesPresent: true, Zones: "zoneX,zoneY", Reject: true, }, // Node has no zone labels [Fail] // [1] Node with no zone labels // [2] Zone/Zones parameter irrelevant // [3] AllowedTopologies irrelevant { Name: "Node_with_no_Zone_labels", Node: nodeWithNoLabels, Reject: true, }, // Node with Zone labels as well as Zone parameter specified [Fail] // [1] Node with zone labels // [2] Zone parameter specified // [3] AllowedTopologies irrelevant { Name: "Node_with_Zone_labels_and_Zone_parameter_present", Node: nodeWithZoneLabels, ZonePresent: true, Zone: "zoneX", Reject: true, }, // Node with Zone labels as well as Zones parameter specified [Fail] // [1] Node with zone labels // [2] Zones parameter specified // [3] AllowedTopologies irrelevant { Name: "Node_with_Zone_labels_and_Zones_parameter_present", Node: nodeWithZoneLabels, ZonesPresent: true, Zones: "zoneX,zoneY", Reject: true, }, // Zone parameter as well as AllowedTopologies specified [Fail] // [1] nil Node // [2] Zone parameter specified // [3] AllowedTopologies specified { Name: "Nil_Node_and_Zone_parameter_and_Allowed_Topology_term", Node: nil, ZonePresent: true, Zone: "zoneX", AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, }, Reject: true, }, // Zones parameter as well as AllowedTopologies specified [Fail] // [1] nil Node // [2] Zones parameter specified // [3] AllowedTopologies specified { Name: "Nil_Node_and_Zones_parameter_and_Allowed_Topology_term", Node: nil, ZonesPresent: true, Zones: "zoneX,zoneY", AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, }, Reject: true, }, // Key specified in AllowedTopologies is not LabelZoneFailureDomain [Fail] // [1] nil Node // [2] no Zone/Zones parameter // [3] AllowedTopologies with invalid key specified { Name: "Nil_Node_and_Invalid_Allowed_Topology_Key", Node: nil, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: "invalid_key", Values: []string{"zoneX"}, }, { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneY"}, }, }, }, }, Reject: true, }, // AllowedTopologies without keys specifying LabelZoneFailureDomain [Fail] // [1] nil Node // [2] no Zone/Zones parameter // [3] Invalid AllowedTopologies { Name: "Nil_Node_and_Invalid_AllowedTopologies", Node: nil, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{}, }, }, Reject: true, }, // POSITIVE TESTS WITH VolumeScheduling ENABLED // Select zone from active zones [Pass] // [1] nil Node // [2] no Zone parameter specified // [3] no AllowedTopologies { Name: "Nil_Node_and_No_Zone_Zones_parameter_and_no_Allowed_topologies_and_VolumeScheduling_enabled", Node: nil, ZonesWithNodes: "zoneX,zoneY", Reject: false, ExpectedZones: "zoneX,zoneY", }, // Select zone from single zone parameter [Pass] // [1] nil Node // [2] Zone parameter specified // [3] no AllowedTopology specified { Name: "Nil_Node_and_Zone_parameter_present_and_VolumeScheduling_enabled", ZonePresent: true, Zone: "zoneX", Node: nil, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", }, // Select zone from zones parameter [Pass] // [1] nil Node // [2] Zones parameter specified // [3] no AllowedTopology { Name: "Nil_Node_and_Zones_parameter_present_and_VolumeScheduling_enabled", ZonesPresent: true, Zones: "zoneX,zoneY", Node: nil, Reject: false, ExpectedZones: "zoneX,zoneY", }, // Select zone from node label [Pass] // [1] Node with zone labels // [2] no zone/zones parameters // [3] no AllowedTopology { Name: "Node_with_Zone_labels_and_VolumeScheduling_enabled", Node: nodeWithZoneLabels, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", }, // Select zone from node label [Pass] // [1] Node with zone labels // [2] no Zone/Zones parameters // [3] AllowedTopology with single term with multiple values specified (ignored) { Name: "Node_with_Zone_labels_and_Multiple_Allowed_Topology_values_and_VolumeScheduling_enabled", Node: nodeWithZoneLabels, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneZ", "zoneY"}, }, }, }, }, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", }, // Select Zone from AllowedTopologies [Pass] // [1] nil Node // [2] no Zone/Zones parametes specified // [3] AllowedTopologies with single term with multiple values specified { Name: "Nil_Node_with_Multiple_Allowed_Topology_values_and_VolumeScheduling_enabled", Node: nil, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX", "zoneY"}, }, }, }, }, Reject: false, ExpectedZones: "zoneX,zoneY", }, // Select zone from AllowedTopologies [Pass] // [1] nil Node // [2] no Zone/Zones parametes specified // [3] AllowedTopologies with multiple terms specified { Name: "Nil_Node_and_Multiple_Allowed_Topology_terms_and_VolumeScheduling_enabled", Node: nil, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneY"}, }, }, }, }, Reject: false, ExpectedZones: "zoneX,zoneY", }, // Select Zone from AllowedTopologies [Pass] // Note: Dual replica with same AllowedTopologies will fail: Nil_Node_and_Single_Allowed_Topology_term_value_and_Dual_replicas // [1] nil Node // [2] no Zone/Zones parametes specified // [3] AllowedTopologies with single term and value specified { Name: "Nil_Node_and_Single_Allowed_Topology_term_value_and_VolumeScheduling_enabled", Node: nil, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, }, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { var zonesParameter, zonesWithNodes sets.String var err error if test.Zones != "" { zonesParameter, err = ZonesToSet(test.Zones) if err != nil { t.Fatalf("Could not convert Zones to a set: %s. This is a test error %s", test.Zones, test.Name) } } if test.ZonesWithNodes != "" { zonesWithNodes, err = ZonesToSet(test.ZonesWithNodes) if err != nil { t.Fatalf("Could not convert specified ZonesWithNodes to a set: %s. This is a test error %s", test.ZonesWithNodes, test.Name) } } zone, err := SelectZoneForVolume(test.ZonePresent, test.ZonesPresent, test.Zone, zonesParameter, zonesWithNodes, test.Node, test.AllowedTopologies, test.Name) if test.Reject && err == nil { t.Errorf("Unexpected zone from SelectZoneForVolume for %s", zone) } if !test.Reject { if err != nil { t.Errorf("Unexpected error from SelectZoneForVolume for %s; Error: %v", test.Name, err) } if test.ExpectSpecificZone == true { if zone != test.ExpectedZone { t.Errorf("Expected zone %v does not match obtained zone %v for %s", test.ExpectedZone, zone, test.Name) } } if test.ExpectedZones != "" { expectedZones, err := ZonesToSet(test.ExpectedZones) if err != nil { t.Fatalf("Could not convert ExpectedZones to a set: %s. This is a test error", test.ExpectedZones) } if !expectedZones.Has(zone) { t.Errorf("Obtained zone %s not member of expectedZones %s", zone, expectedZones) } } } }) } } func TestSelectZonesForVolume(t *testing.T) { nodeWithZoneLabels := &v1.Node{} nodeWithZoneLabels.Labels = map[string]string{v1.LabelZoneFailureDomain: "zoneX"} nodeWithNoLabels := &v1.Node{} tests := []struct { // Parameters passed by test to SelectZonesForVolume Name string ReplicaCount uint32 ZonePresent bool Zone string ZonesPresent bool Zones string ZonesWithNodes string Node *v1.Node AllowedTopologies []v1.TopologySelectorTerm // Expectations around returned zones from SelectZonesForVolume Reject bool // expect error due to validation failing ExpectSpecificZones bool // expect set of returned zones to be equal to ExpectedZones (rather than subset of ExpectedZones) ExpectedZones string // set of zones that is a superset of returned zones or equal to returned zones (if ExpectSpecificZones is set) ExpectSpecificZone bool // expect set of returned zones to include ExpectedZone as a member ExpectedZone string // zone that should be a member of returned zones }{ // NEGATIVE TESTS // Zone and Zones are both specified [Fail] // [1] Node irrelevant // [2] Zone and Zones parameters presents // [3] AllowedTopologies irrelevant // [4] ReplicaCount irrelevant // [5] ZonesWithNodes irrelevant { Name: "Nil_Node_with_Zone_Zones_parameters_present", ZonePresent: true, Zone: "zoneX", ZonesPresent: true, Zones: "zoneX,zoneY", Reject: true, }, // Node has no zone labels [Fail] // [1] Node with no zone labels // [2] Zone/Zones parameter irrelevant // [3] AllowedTopologies irrelevant // [4] ReplicaCount irrelevant // [5] ZonesWithNodes irrelevant { Name: "Node_with_no_Zone_labels", Node: nodeWithNoLabels, Reject: true, }, // Node with Zone labels as well as Zone parameter specified [Fail] // [1] Node with zone labels // [2] Zone parameter specified // [3] AllowedTopologies irrelevant // [4] ReplicaCount irrelevant // [5] ZonesWithNodes irrelevant { Name: "Node_with_Zone_labels_and_Zone_parameter_present", Node: nodeWithZoneLabels, ZonePresent: true, Zone: "zoneX", Reject: true, }, // Node with Zone labels as well as Zones parameter specified [Fail] // [1] Node with zone labels // [2] Zones parameter specified // [3] AllowedTopologies irrelevant // [4] ReplicaCount irrelevant // [5] ZonesWithNodes irrelevant { Name: "Node_with_Zone_labels_and_Zones_parameter_present", Node: nodeWithZoneLabels, ZonesPresent: true, Zones: "zoneX,zoneY", Reject: true, }, // Zone parameter as well as AllowedTopologies specified [Fail] // [1] nil Node // [2] Zone parameter specified // [3] AllowedTopologies specified // [4] ReplicaCount irrelevant // [5] ZonesWithNodes irrelevant { Name: "Nil_Node_and_Zone_parameter_and_Allowed_Topology_term", Node: nil, ZonePresent: true, Zone: "zoneX", AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, }, Reject: true, }, // Zones parameter as well as AllowedTopologies specified [Fail] // [1] nil Node // [2] Zones parameter specified // [3] AllowedTopologies specified // [4] ReplicaCount irrelevant // [5] ZonesWithNodes irrelevant { Name: "Nil_Node_and_Zones_parameter_and_Allowed_Topology_term", Node: nil, ZonesPresent: true, Zones: "zoneX,zoneY", AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, }, Reject: true, }, // Key specified in AllowedTopologies is not LabelZoneFailureDomain [Fail] // [1] nil Node // [2] no Zone/Zones parameter // [3] AllowedTopologies with invalid key specified // [4] ReplicaCount irrelevant // [5] ZonesWithNodes irrelevant { Name: "Nil_Node_and_Invalid_Allowed_Topology_Key", Node: nil, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: "invalid_key", Values: []string{"zoneX"}, }, { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneY"}, }, }, }, }, Reject: true, }, // AllowedTopologies without keys specifying LabelZoneFailureDomain [Fail] // [1] nil Node // [2] no Zone/Zones parameter // [3] Invalid AllowedTopologies // [4] ReplicaCount irrelevant // [5] ZonesWithNodes irrelevant { Name: "Nil_Node_and_Invalid_AllowedTopologies", Node: nil, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{}, }, }, Reject: true, }, // Zone specified with ReplicaCount > 1 [Fail] // [1] nil Node // [2] Zone/Zones parameter specified // [3] AllowedTopologies irrelevant // [4] ReplicaCount > 1 // [5] ZonesWithNodes irrelevant { Name: "Zone_and_Replicas_mismatched", Node: nil, ZonePresent: true, Zone: "zoneX", ReplicaCount: 2, Reject: true, }, // Not enough zones in Zones parameter to satisfy ReplicaCount [Fail] // [1] nil Node // [2] Zone/Zones parameter specified // [3] AllowedTopologies irrelevant // [4] ReplicaCount > zones // [5] ZonesWithNodes irrelevant { Name: "Zones_and_Replicas_mismatched", Node: nil, ZonesPresent: true, Zones: "zoneX", ReplicaCount: 2, Reject: true, }, // Not enough zones in ZonesWithNodes to satisfy ReplicaCount [Fail] // [1] nil Node // [2] no Zone/Zones parameter specified // [3] AllowedTopologies irrelevant // [4] ReplicaCount > ZonesWithNodes // [5] ZonesWithNodes specified but not enough { Name: "Zones_and_Replicas_mismatched", Node: nil, ZonesWithNodes: "zoneX", ReplicaCount: 2, Reject: true, }, // Not enough zones in AllowedTopologies to satisfy ReplicaCount [Fail] // [1] nil Node // [2] Zone/Zones parameter specified // [3] Invalid AllowedTopologies // [4] ReplicaCount > zones // [5] ZonesWithNodes irrelevant { Name: "AllowedTopologies_and_Replicas_mismatched", Node: nil, ReplicaCount: 2, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, }, Reject: true, }, // POSITIVE TESTS // Select zones from zones parameter [Pass] // [1] nil Node (Node irrelevant) // [2] Zones parameter specified // [3] no AllowedTopologies // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Zones_parameter_Dual_replica_Superset", ZonesPresent: true, Zones: "zoneW,zoneX,zoneY,zoneZ", ReplicaCount: 2, Reject: false, ExpectedZones: "zoneW,zoneX,zoneY,zoneZ", }, // Select zones from zones parameter [Pass] // [1] nil Node (Node irrelevant) // [2] Zones parameter specified // [3] no AllowedTopologies // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Zones_parameter_Dual_replica_Match", ZonesPresent: true, Zones: "zoneX,zoneY", ReplicaCount: 2, Reject: false, ExpectSpecificZones: true, ExpectedZones: "zoneX,zoneY", }, // Select zones from active zones with nodes [Pass] // [1] nil Node (Node irrelevant) // [2] no Zone parameter // [3] no AllowedTopologies // [4] ReplicaCount specified // [5] ZonesWithNodes specified { Name: "Active_zones_Nil_node_Dual_replica_Superset", ZonesWithNodes: "zoneW,zoneX,zoneY,zoneZ", ReplicaCount: 2, Reject: false, ExpectedZones: "zoneW,zoneX,zoneY,zoneZ", }, // Select zones from active zones [Pass] // [1] nil Node (Node irrelevant) // [2] no Zone[s] parameter // [3] no AllowedTopologies // [4] ReplicaCount specified // [5] ZonesWithNodes specified { Name: "Active_zones_Nil_node_Dual_replica_Match", ZonesWithNodes: "zoneW,zoneX", ReplicaCount: 2, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectSpecificZones: true, ExpectedZones: "zoneW,zoneX", }, // Select zones from node label and active zones [Pass] // [1] Node with zone labels // [2] no Zone parameter // [3] no AllowedTopologies // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Active_zones_Node_with_Zone_labels_Dual_replica_Superset", Node: nodeWithZoneLabels, ZonesWithNodes: "zoneW,zoneX,zoneY,zoneZ", ReplicaCount: 2, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectedZones: "zoneW,zoneX,zoneY,zoneZ", }, // Select zones from node label and active zones [Pass] // [1] Node with zone labels // [2] no Zone[s] parameter // [3] no AllowedTopologies // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Active_zones_Node_with_Zone_labels_Dual_replica_Match", Node: nodeWithZoneLabels, ZonesWithNodes: "zoneW,zoneX", ReplicaCount: 2, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectSpecificZones: true, ExpectedZones: "zoneW,zoneX", }, // Select zones from node label and AllowedTopologies [Pass] // [1] Node with zone labels // [2] no Zone/Zones parameters // [3] AllowedTopologies with single term with multiple values specified // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant // Note: the test Name suffix is used as the pvcname and it's suffix is important to influence ChooseZonesForVolume // to NOT pick zoneX from AllowedTopologies if zoneFromNode is incorrectly set or not set at all. { Name: "Node_with_Zone_labels_and_Multiple_Allowed_Topology_values_Superset-1", Node: nodeWithZoneLabels, ReplicaCount: 2, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneV", "zoneW", "zoneX", "zoneY", "zoneZ"}, }, }, }, }, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectedZones: "zoneV,zoneW,zoneX,zoneY,zoneZ", }, // Select zones from node label and AllowedTopologies [Pass] // [1] Node with zone labels // [2] no Zone/Zones parameters // [3] AllowedTopologies with single term with multiple values specified // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Node_with_Zone_labels_and_Multiple_Allowed_Topology_values_Match", Node: nodeWithZoneLabels, ReplicaCount: 2, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX", "zoneY"}, }, }, }, }, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectSpecificZones: true, ExpectedZones: "zoneX,zoneY", }, // Select Zones from AllowedTopologies [Pass] // [1] nil Node // [2] no Zone/Zones parametes specified // [3] AllowedTopologies with single term with multiple values specified // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Nil_Node_with_Multiple_Allowed_Topology_values_Superset", Node: nil, ReplicaCount: 2, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX", "zoneY", "zoneZ"}, }, }, }, }, Reject: false, ExpectedZones: "zoneX,zoneY,zoneZ", }, // Select Zones from AllowedTopologies [Pass] // [1] nil Node // [2] no Zone/Zones parametes specified // [3] AllowedTopologies with single term with multiple values specified // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Nil_Node_with_Multiple_Allowed_Topology_values_Match", Node: nil, ReplicaCount: 2, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX", "zoneY"}, }, }, }, }, Reject: false, ExpectSpecificZones: true, ExpectedZones: "zoneX,zoneY", }, // Select zones from node label and AllowedTopologies [Pass] // [1] Node with zone labels // [2] no Zone/Zones parametes specified // [3] AllowedTopologies with multiple terms specified // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant // Note: the test Name is used as the pvcname and it's hash is important to influence ChooseZonesForVolume // to NOT pick zoneX from AllowedTopologies of 5 zones. If zoneFromNode (zoneX) is incorrectly set or // not set at all in SelectZonesForVolume it will be detected. { Name: "Node_with_Zone_labels_and_Multiple_Allowed_Topology_terms_Superset-2", Node: nodeWithZoneLabels, ReplicaCount: 2, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneV"}, }, }, }, { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneW"}, }, }, }, { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneY"}, }, }, }, { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneZ"}, }, }, }, }, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectedZones: "zoneV,zoneW,zoneX,zoneY,zoneZ", }, // Select zones from node label and AllowedTopologies [Pass] // [1] Node with zone labels // [2] no Zone/Zones parametes specified // [3] AllowedTopologies with multiple terms specified // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Node_with_Zone_labels_and_Multiple_Allowed_Topology_terms_Match", Node: nodeWithZoneLabels, ReplicaCount: 2, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneY"}, }, }, }, }, Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectSpecificZones: true, ExpectedZones: "zoneX,zoneY", }, // Select zones from AllowedTopologies [Pass] // [1] nil Node // [2] no Zone/Zones parametes specified // [3] AllowedTopologies with multiple terms specified // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Nil_Node_and_Multiple_Allowed_Topology_terms_Superset", Node: nil, ReplicaCount: 2, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneY"}, }, }, }, { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneZ"}, }, }, }, }, Reject: false, ExpectedZones: "zoneX,zoneY,zoneZ", }, // Select zones from AllowedTopologies [Pass] // [1] nil Node // [2] no Zone/Zones parametes specified // [3] AllowedTopologies with multiple terms specified // [4] ReplicaCount specified // [5] ZonesWithNodes irrelevant { Name: "Nil_Node_and_Multiple_Allowed_Topology_terms_Match", Node: nil, ReplicaCount: 2, AllowedTopologies: []v1.TopologySelectorTerm{ { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneX"}, }, }, }, { MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ { Key: v1.LabelZoneFailureDomain, Values: []string{"zoneY"}, }, }, }, }, Reject: false, ExpectSpecificZones: true, ExpectedZones: "zoneX,zoneY", }, } for _, test := range tests { var zonesParameter, zonesWithNodes sets.String var err error if test.Zones != "" { zonesParameter, err = ZonesToSet(test.Zones) if err != nil { t.Errorf("Could not convert Zones to a set: %s. This is a test error %s", test.Zones, test.Name) continue } } if test.ZonesWithNodes != "" { zonesWithNodes, err = ZonesToSet(test.ZonesWithNodes) if err != nil { t.Errorf("Could not convert specified ZonesWithNodes to a set: %s. This is a test error %s", test.ZonesWithNodes, test.Name) continue } } zones, err := SelectZonesForVolume(test.ZonePresent, test.ZonesPresent, test.Zone, zonesParameter, zonesWithNodes, test.Node, test.AllowedTopologies, test.Name, test.ReplicaCount) if test.Reject && err == nil { t.Errorf("Unexpected zones from SelectZonesForVolume in %s. Zones: %v", test.Name, zones) continue } if !test.Reject { if err != nil { t.Errorf("Unexpected error from SelectZonesForVolume in %s; Error: %v", test.Name, err) continue } if uint32(zones.Len()) != test.ReplicaCount { t.Errorf("Number of elements in returned zones %v does not equal replica count %d in %s", zones, test.ReplicaCount, test.Name) continue } expectedZones, err := ZonesToSet(test.ExpectedZones) if err != nil { t.Errorf("Could not convert ExpectedZones to a set: %v. This is a test error in %s", test.ExpectedZones, test.Name) continue } if test.ExpectSpecificZones && !zones.Equal(expectedZones) { t.Errorf("Expected zones %v does not match obtained zones %v in %s", expectedZones, zones, test.Name) continue } if test.ExpectSpecificZone && !zones.Has(test.ExpectedZone) { t.Errorf("Expected zone %s not found in obtained zones %v in %s", test.ExpectedZone, zones, test.Name) continue } if !expectedZones.IsSuperset(zones) { t.Errorf("Obtained zones %v not subset of of expectedZones %v in %s", zones, expectedZones, test.Name) } } } } func TestChooseZonesForVolumeIncludingZone(t *testing.T) { tests := []struct { // Parameters passed by test to chooseZonesForVolumeIncludingZone Name string ReplicaCount uint32 Zones string ZoneToInclude string // Expectations around returned zones from chooseZonesForVolumeIncludingZone Reject bool // expect error due to validation failing ExpectSpecificZones bool // expect set of returned zones to be equal to ExpectedZones (rather than subset of ExpectedZones) ExpectedZones string // set of zones that is a superset of returned zones or equal to returned zones (if ExpectSpecificZones is set) ExpectSpecificZone bool // expect set of returned zones to include ExpectedZone as a member ExpectedZone string // zone that should be a member of returned zones }{ // NEGATIVE TESTS // Not enough zones specified to fit ReplicaCount [Fail] { Name: "Too_Few_Zones", ReplicaCount: 2, Zones: "zoneX", Reject: true, }, // Invalid ReplicaCount [Fail] { Name: "Zero_Replica_Count", ReplicaCount: 0, Zones: "zoneY,zoneZ", Reject: true, }, // Invalid ZoneToInclude [Fail] { Name: "ZoneToInclude_Not_In_Zones_Single_replica_Dual_zones", ReplicaCount: 1, ZoneToInclude: "zoneX", Zones: "zoneY,zoneZ", Reject: true, }, // Invalid ZoneToInclude [Fail] { Name: "ZoneToInclude_Not_In_Zones_Single_replica_Single_zone", ReplicaCount: 1, ZoneToInclude: "zoneX", Zones: "zoneY", Reject: true, }, // Invalid ZoneToInclude [Fail] { Name: "ZoneToInclude_Not_In_Zones_Dual_replica_Multiple_zones", ReplicaCount: 2, ZoneToInclude: "zoneX", Zones: "zoneY,zoneZ,zoneW", Reject: true, }, // POSITIVE TESTS // Pick any one zone from Zones { Name: "No_zones_to_include_and_Single_replica_and_Superset", ReplicaCount: 1, Zones: "zoneX,zoneY,zoneZ", Reject: false, ExpectedZones: "zoneX,zoneY,zoneZ", }, // Pick any two zones from Zones { Name: "No_zones_to_include_and_Dual_replicas_and_Superset", ReplicaCount: 2, Zones: "zoneW,zoneX,zoneY,zoneZ", Reject: false, ExpectedZones: "zoneW,zoneX,zoneY,zoneZ", }, // Pick the two zones from Zones { Name: "No_zones_to_include_and_Dual_replicas_and_Match", ReplicaCount: 2, Zones: "zoneX,zoneY", Reject: false, ExpectSpecificZones: true, ExpectedZones: "zoneX,zoneY", }, // Pick one zone from ZoneToInclude (other zones ignored) { Name: "Include_zone_and_Single_replica_and_Match", ReplicaCount: 1, Zones: "zoneW,zoneX,zoneY,zoneZ", ZoneToInclude: "zoneX", Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectSpecificZones: true, ExpectedZones: "zoneX", }, // Pick one zone from ZoneToInclude (other zones ignored) { Name: "Include_zone_and_single_replica_and_Match", ReplicaCount: 1, Zones: "zoneX", ZoneToInclude: "zoneX", Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectSpecificZones: true, ExpectedZones: "zoneX", }, // Pick one zone from Zones and the other from ZoneToInclude { Name: "Include_zone_and_dual_replicas_and_Superset", ReplicaCount: 2, Zones: "zoneW,zoneX,zoneY,zoneZ", ZoneToInclude: "zoneX", Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectedZones: "zoneW,zoneX,zoneY,zoneZ", }, // Pick one zone from Zones and the other from ZoneToInclude { Name: "Include_zone_and_dual_replicas_and_Match", ReplicaCount: 2, Zones: "zoneX,zoneY", ZoneToInclude: "zoneX", Reject: false, ExpectSpecificZone: true, ExpectedZone: "zoneX", ExpectSpecificZones: true, ExpectedZones: "zoneX,zoneY", }, } for _, test := range tests { zonesParameter, err := ZonesToSet(test.Zones) if err != nil { t.Errorf("Could not convert Zones to a set: %s. This is a test error %s", test.Zones, test.Name) continue } zones, err := chooseZonesForVolumeIncludingZone(zonesParameter, test.Name, test.ZoneToInclude, test.ReplicaCount) if test.Reject && err == nil { t.Errorf("Unexpected zones from chooseZonesForVolumeIncludingZone in %s. Zones: %v", test.Name, zones) continue } if !test.Reject { if err != nil { t.Errorf("Unexpected error from chooseZonesForVolumeIncludingZone in %s; Error: %v", test.Name, err) continue } if uint32(zones.Len()) != test.ReplicaCount { t.Errorf("Number of elements in returned zones %v does not equal replica count %d in %s", zones, test.ReplicaCount, test.Name) continue } expectedZones, err := ZonesToSet(test.ExpectedZones) if err != nil { t.Errorf("Could not convert ExpectedZones to a set: %v. This is a test error in %s", test.ExpectedZones, test.Name) continue } if test.ExpectSpecificZones && !zones.Equal(expectedZones) { t.Errorf("Expected zones %v does not match obtained zones %v in %s", expectedZones, zones, test.Name) continue } if test.ExpectSpecificZone && !zones.Has(test.ExpectedZone) { t.Errorf("Expected zone %s not found in obtained zones %v in %s", test.ExpectedZone, zones, test.Name) continue } if !expectedZones.IsSuperset(zones) { t.Errorf("Obtained zones %v not subset of of expectedZones %v in %s", zones, expectedZones, test.Name) } } } } func checkFnv32(t *testing.T, s string, expected uint32) { h := fnv.New32() h.Write([]byte(s)) h.Sum32() if h.Sum32() != expected { t.Fatalf("hash of %q was %v, expected %v", s, h.Sum32(), expected) } }