github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/common/availabilityzones_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common_test 5 6 import ( 7 "fmt" 8 9 jc "github.com/juju/testing/checkers" 10 gc "gopkg.in/check.v1" 11 12 "github.com/juju/juju/core/instance" 13 "github.com/juju/juju/core/network" 14 "github.com/juju/juju/environs" 15 "github.com/juju/juju/environs/context" 16 "github.com/juju/juju/environs/instances" 17 "github.com/juju/juju/provider/common" 18 coretesting "github.com/juju/juju/testing" 19 ) 20 21 type AvailabilityZoneSuite struct { 22 coretesting.FakeJujuXDGDataHomeSuite 23 env mockZonedEnviron 24 25 callCtx context.ProviderCallContext 26 } 27 28 var _ = gc.Suite(&AvailabilityZoneSuite{}) 29 30 func (s *AvailabilityZoneSuite) SetUpSuite(c *gc.C) { 31 s.FakeJujuXDGDataHomeSuite.SetUpSuite(c) 32 33 s.callCtx = context.NewEmptyCloudCallContext() 34 allInstances := make([]instances.Instance, 3) 35 for i := range allInstances { 36 allInstances[i] = &mockInstance{id: fmt.Sprintf("inst%d", i)} 37 } 38 s.env.allInstances = func(context.ProviderCallContext) ([]instances.Instance, error) { 39 return allInstances, nil 40 } 41 42 availabilityZones := make(network.AvailabilityZones, 3) 43 for i := range availabilityZones { 44 availabilityZones[i] = &mockAvailabilityZone{ 45 name: fmt.Sprintf("az%d", i), 46 available: i > 0, 47 } 48 } 49 s.env.availabilityZones = func(context.ProviderCallContext) (network.AvailabilityZones, error) { 50 return availabilityZones, nil 51 } 52 } 53 54 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsAllRunningInstances(c *gc.C) { 55 var called int 56 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) { 57 c.Assert(ids, gc.DeepEquals, []instance.Id{"inst0", "inst1", "inst2"}) 58 called++ 59 return map[instance.Id]string{ 60 "inst0": "az0", 61 "inst1": "az1", 62 "inst2": "az2", 63 }, nil 64 }) 65 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 66 c.Assert(called, gc.Equals, 1) 67 c.Assert(err, jc.ErrorIsNil) 68 // az0 is unavailable, so az1 and az2 come out as equal best; 69 // az1 comes first due to lexicographical ordering on the name. 70 c.Assert(zoneInstances, gc.DeepEquals, []common.AvailabilityZoneInstances{{ 71 ZoneName: "az1", 72 Instances: []instance.Id{"inst1"}, 73 }, { 74 ZoneName: "az2", 75 Instances: []instance.Id{"inst2"}, 76 }}) 77 } 78 79 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsAllRunningInstancesErrors(c *gc.C) { 80 resultErr := fmt.Errorf("oh noes") 81 s.PatchValue(&s.env.allInstances, func(context.ProviderCallContext) ([]instances.Instance, error) { 82 return nil, resultErr 83 }) 84 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 85 c.Assert(err, gc.Equals, resultErr) 86 c.Assert(zoneInstances, gc.HasLen, 0) 87 } 88 89 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsPartialInstances(c *gc.C) { 90 var called int 91 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) { 92 c.Assert(ids, gc.DeepEquals, []instance.Id{"nichts", "inst1", "null", "inst2"}) 93 called++ 94 return map[instance.Id]string{"inst1": "az1", "inst2": "az1"}, environs.ErrPartialInstances 95 }) 96 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, []instance.Id{"nichts", "inst1", "null", "inst2"}) 97 c.Assert(called, gc.Equals, 1) 98 c.Assert(err, jc.ErrorIsNil) 99 // az2 has fewer instances, so comes first. 100 c.Assert(zoneInstances, gc.DeepEquals, []common.AvailabilityZoneInstances{{ 101 ZoneName: "az2", 102 }, { 103 ZoneName: "az1", 104 Instances: []instance.Id{"inst1", "inst2"}, 105 }}) 106 } 107 108 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsInstanceAvailabilityZonesErrors(c *gc.C) { 109 returnErr := fmt.Errorf("whatever") 110 var called int 111 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) { 112 called++ 113 return nil, returnErr 114 }) 115 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 116 c.Assert(called, gc.Equals, 1) 117 c.Assert(err, gc.Equals, returnErr) 118 c.Assert(zoneInstances, gc.HasLen, 0) 119 } 120 121 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsInstanceAvailabilityZonesNoInstances(c *gc.C) { 122 var called int 123 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) { 124 called++ 125 return nil, environs.ErrNoInstances 126 }) 127 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 128 c.Assert(called, gc.Equals, 1) 129 c.Assert(err, jc.ErrorIsNil) 130 c.Assert(zoneInstances, gc.HasLen, 2) 131 } 132 133 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsNoZones(c *gc.C) { 134 var calls []string 135 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) { 136 c.Assert(ids, gc.DeepEquals, []instance.Id{"inst0", "inst1", "inst2"}) 137 calls = append(calls, "InstanceAvailabilityZoneNames") 138 return make(map[instance.Id]string, 3), nil 139 }) 140 s.PatchValue(&s.env.availabilityZones, func(context.ProviderCallContext) (network.AvailabilityZones, error) { 141 calls = append(calls, "AvailabilityZones") 142 return network.AvailabilityZones{}, nil 143 }) 144 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 145 c.Assert(calls, gc.DeepEquals, []string{"InstanceAvailabilityZoneNames", "AvailabilityZones"}) 146 c.Assert(err, jc.ErrorIsNil) 147 c.Assert(zoneInstances, gc.HasLen, 0) 148 } 149 150 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsErrors(c *gc.C) { 151 var calls []string 152 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) (map[instance.Id]string, error) { 153 c.Assert(ids, gc.DeepEquals, []instance.Id{"inst0", "inst1", "inst2"}) 154 calls = append(calls, "InstanceAvailabilityZoneNames") 155 return make(map[instance.Id]string, 3), nil 156 }) 157 resultErr := fmt.Errorf("u can haz no az") 158 s.PatchValue(&s.env.availabilityZones, func(context.ProviderCallContext) (network.AvailabilityZones, error) { 159 calls = append(calls, "AvailabilityZones") 160 return nil, resultErr 161 }) 162 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 163 c.Assert(calls, gc.DeepEquals, []string{"InstanceAvailabilityZoneNames", "AvailabilityZones"}) 164 c.Assert(err, gc.Equals, resultErr) 165 c.Assert(zoneInstances, gc.HasLen, 0) 166 } 167 168 func (s *AvailabilityZoneSuite) TestDistributeInstancesGroup(c *gc.C) { 169 expectedGroup := []instance.Id{"0", "1", "2"} 170 var called bool 171 s.PatchValue(common.InternalAvailabilityZoneAllocations, func(_ common.ZonedEnviron, ctx context.ProviderCallContext, group []instance.Id) ([]common.AvailabilityZoneInstances, error) { 172 c.Assert(group, gc.DeepEquals, expectedGroup) 173 called = true 174 return nil, nil 175 }) 176 _, err := common.DistributeInstances(&s.env, s.callCtx, nil, expectedGroup, nil) 177 c.Assert(err, jc.ErrorIsNil) 178 c.Assert(called, jc.IsTrue) 179 } 180 181 func (s *AvailabilityZoneSuite) TestDistributeInstancesGroupErrors(c *gc.C) { 182 resultErr := fmt.Errorf("whatever") 183 s.PatchValue(common.InternalAvailabilityZoneAllocations, func(_ common.ZonedEnviron, ctx context.ProviderCallContext, group []instance.Id) ([]common.AvailabilityZoneInstances, error) { 184 return nil, resultErr 185 }) 186 _, err := common.DistributeInstances(&s.env, s.callCtx, nil, nil, nil) 187 c.Assert(err, gc.Equals, resultErr) 188 } 189 190 func (s *AvailabilityZoneSuite) TestDistributeInstances(c *gc.C) { 191 var zoneInstances []common.AvailabilityZoneInstances 192 s.PatchValue(common.InternalAvailabilityZoneAllocations, func(_ common.ZonedEnviron, ctx context.ProviderCallContext, group []instance.Id) ([]common.AvailabilityZoneInstances, error) { 193 return zoneInstances, nil 194 }) 195 196 type distributeInstancesTest struct { 197 zoneInstances []common.AvailabilityZoneInstances 198 candidates []instance.Id 199 limitZones []string 200 eligible []instance.Id 201 } 202 203 defaultZoneInstances := []common.AvailabilityZoneInstances{{ 204 ZoneName: "az0", 205 Instances: []instance.Id{"i0"}, 206 }, { 207 ZoneName: "az1", 208 Instances: []instance.Id{"i1"}, 209 }, { 210 ZoneName: "az2", 211 Instances: []instance.Id{"i2"}, 212 }} 213 214 tests := []distributeInstancesTest{{ 215 zoneInstances: defaultZoneInstances, 216 candidates: []instance.Id{"i2", "i3", "i4"}, 217 eligible: []instance.Id{"i2"}, 218 }, { 219 zoneInstances: defaultZoneInstances, 220 candidates: []instance.Id{"i0", "i1", "i2"}, 221 eligible: []instance.Id{"i0", "i1", "i2"}, 222 }, { 223 zoneInstances: defaultZoneInstances, 224 candidates: []instance.Id{"i3", "i4", "i5"}, 225 eligible: []instance.Id{}, 226 }, { 227 zoneInstances: defaultZoneInstances, 228 candidates: []instance.Id{}, 229 eligible: []instance.Id{}, 230 }, { 231 zoneInstances: []common.AvailabilityZoneInstances{}, 232 candidates: []instance.Id{"i0"}, 233 eligible: []instance.Id{}, 234 }, { 235 // Limit to all zones; essentially the same as no limit. 236 zoneInstances: defaultZoneInstances, 237 candidates: []instance.Id{"i0", "i1", "i2"}, 238 limitZones: []string{"az0", "az1", "az2"}, 239 eligible: []instance.Id{"i0", "i1", "i2"}, 240 }, { 241 // Simple limit to a subset of zones. 242 zoneInstances: defaultZoneInstances, 243 candidates: []instance.Id{"i0", "i1", "i2"}, 244 limitZones: []string{"az0", "az1"}, 245 eligible: []instance.Id{"i0", "i1"}, 246 }, { 247 // Intersecting zone limit with equal distribution. 248 zoneInstances: defaultZoneInstances, 249 candidates: []instance.Id{"i0", "i1"}, 250 limitZones: []string{"az1", "az2", "az4"}, 251 eligible: []instance.Id{"i0", "i1"}, 252 }, { 253 // Intersecting zone limit with unequal distribution. 254 zoneInstances: []common.AvailabilityZoneInstances{{ 255 ZoneName: "az0", 256 Instances: []instance.Id{"i0"}, 257 }, { 258 ZoneName: "az1", 259 Instances: []instance.Id{"i1", "i2"}, 260 }}, 261 candidates: []instance.Id{"i0", "i1", "i2"}, 262 limitZones: []string{"az0", "az1", "az666"}, 263 eligible: []instance.Id{"i0"}, 264 }, { 265 // Limit filters out all zones - no eligible instances. 266 zoneInstances: []common.AvailabilityZoneInstances{{ 267 ZoneName: "az0", 268 Instances: []instance.Id{"i0"}, 269 }, { 270 ZoneName: "az1", 271 Instances: []instance.Id{"i1"}, 272 }}, 273 candidates: []instance.Id{"i0", "i1"}, 274 limitZones: []string{"az2", "az3"}, 275 eligible: []instance.Id{}, 276 }} 277 278 for i, test := range tests { 279 c.Logf("test %d", i) 280 zoneInstances = test.zoneInstances 281 eligible, err := common.DistributeInstances(&s.env, s.callCtx, test.candidates, nil, test.limitZones) 282 c.Assert(err, jc.ErrorIsNil) 283 c.Assert(eligible, jc.SameContents, test.eligible) 284 } 285 }