github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "github.com/juju/errors" 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 13 "github.com/juju/juju/core/instance" 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.NewCloudCallContext() 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([]common.AvailabilityZone, 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) ([]common.AvailabilityZone, error) { 50 return availabilityZones, nil 51 } 52 } 53 54 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsAllInstances(c *gc.C) { 55 var called int 56 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) { 57 c.Assert(ids, gc.DeepEquals, []instance.Id{"inst0", "inst1", "inst2"}) 58 called++ 59 return []string{"az0", "az1", "az2"}, nil 60 }) 61 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 62 c.Assert(called, gc.Equals, 1) 63 c.Assert(err, jc.ErrorIsNil) 64 // az0 is unavailable, so az1 and az2 come out as equal best; 65 // az1 comes first due to lexicographical ordering on the name. 66 c.Assert(zoneInstances, gc.DeepEquals, []common.AvailabilityZoneInstances{{ 67 ZoneName: "az1", 68 Instances: []instance.Id{"inst1"}, 69 }, { 70 ZoneName: "az2", 71 Instances: []instance.Id{"inst2"}, 72 }}) 73 } 74 75 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsAllInstancesErrors(c *gc.C) { 76 resultErr := fmt.Errorf("oh noes") 77 s.PatchValue(&s.env.allInstances, func(context.ProviderCallContext) ([]instances.Instance, error) { 78 return nil, resultErr 79 }) 80 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 81 c.Assert(err, gc.Equals, resultErr) 82 c.Assert(zoneInstances, gc.HasLen, 0) 83 } 84 85 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsPartialInstances(c *gc.C) { 86 var called int 87 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) { 88 c.Assert(ids, gc.DeepEquals, []instance.Id{"nichts", "inst1", "null", "inst2"}) 89 called++ 90 return []string{"", "az1", "", "az1"}, environs.ErrPartialInstances 91 }) 92 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, []instance.Id{"nichts", "inst1", "null", "inst2"}) 93 c.Assert(called, gc.Equals, 1) 94 c.Assert(err, jc.ErrorIsNil) 95 // az2 has fewer instances, so comes first. 96 c.Assert(zoneInstances, gc.DeepEquals, []common.AvailabilityZoneInstances{{ 97 ZoneName: "az2", 98 }, { 99 ZoneName: "az1", 100 Instances: []instance.Id{"inst1", "inst2"}, 101 }}) 102 } 103 104 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsInstanceAvailabilityZonesErrors(c *gc.C) { 105 returnErr := fmt.Errorf("whatever") 106 var called int 107 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) { 108 called++ 109 return nil, returnErr 110 }) 111 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 112 c.Assert(called, gc.Equals, 1) 113 c.Assert(err, gc.Equals, returnErr) 114 c.Assert(zoneInstances, gc.HasLen, 0) 115 } 116 117 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsInstanceAvailabilityZonesNoInstances(c *gc.C) { 118 var called int 119 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) { 120 called++ 121 return nil, environs.ErrNoInstances 122 }) 123 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 124 c.Assert(called, gc.Equals, 1) 125 c.Assert(err, jc.ErrorIsNil) 126 c.Assert(zoneInstances, gc.HasLen, 2) 127 } 128 129 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsNoZones(c *gc.C) { 130 var calls []string 131 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) { 132 c.Assert(ids, gc.DeepEquals, []instance.Id{"inst0", "inst1", "inst2"}) 133 calls = append(calls, "InstanceAvailabilityZoneNames") 134 return []string{"", "", ""}, nil 135 }) 136 s.PatchValue(&s.env.availabilityZones, func(context.ProviderCallContext) ([]common.AvailabilityZone, error) { 137 calls = append(calls, "AvailabilityZones") 138 return []common.AvailabilityZone{}, nil 139 }) 140 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 141 c.Assert(calls, gc.DeepEquals, []string{"InstanceAvailabilityZoneNames", "AvailabilityZones"}) 142 c.Assert(err, jc.ErrorIsNil) 143 c.Assert(zoneInstances, gc.HasLen, 0) 144 } 145 146 func (s *AvailabilityZoneSuite) TestAvailabilityZoneAllocationsErrors(c *gc.C) { 147 var calls []string 148 s.PatchValue(&s.env.instanceAvailabilityZoneNames, func(ctx context.ProviderCallContext, ids []instance.Id) ([]string, error) { 149 c.Assert(ids, gc.DeepEquals, []instance.Id{"inst0", "inst1", "inst2"}) 150 calls = append(calls, "InstanceAvailabilityZoneNames") 151 return []string{"", "", ""}, nil 152 }) 153 resultErr := fmt.Errorf("u can haz no az") 154 s.PatchValue(&s.env.availabilityZones, func(context.ProviderCallContext) ([]common.AvailabilityZone, error) { 155 calls = append(calls, "AvailabilityZones") 156 return nil, resultErr 157 }) 158 zoneInstances, err := common.AvailabilityZoneAllocations(&s.env, s.callCtx, nil) 159 c.Assert(calls, gc.DeepEquals, []string{"InstanceAvailabilityZoneNames", "AvailabilityZones"}) 160 c.Assert(err, gc.Equals, resultErr) 161 c.Assert(zoneInstances, gc.HasLen, 0) 162 } 163 164 func (s *AvailabilityZoneSuite) TestValidateAvailabilityZone(c *gc.C) { 165 var calls []string 166 s.PatchValue(&s.env.availabilityZones, func(context.ProviderCallContext) ([]common.AvailabilityZone, error) { 167 availabilityZones := make([]common.AvailabilityZone, 2) 168 availabilityZones[0] = &mockAvailabilityZone{name: "az1", available: true} 169 availabilityZones[1] = &mockAvailabilityZone{name: "az2", available: false} 170 calls = append(calls, "AvailabilityZones") 171 return availabilityZones, nil 172 }) 173 tests := map[string]error{ 174 "az1": nil, 175 "az2": errors.Errorf("availability zone %q is unavailable", "az2"), 176 "az3": errors.NotValidf("availability zone %q", "az3"), 177 } 178 for i, t := range tests { 179 err := common.ValidateAvailabilityZone(&s.env, s.callCtx, i) 180 if t == nil { 181 c.Assert(err, jc.ErrorIsNil) 182 } else { 183 c.Assert(err, gc.ErrorMatches, err.Error()) 184 } 185 c.Assert(calls, gc.DeepEquals, []string{"AvailabilityZones"}) 186 calls = []string{} 187 } 188 } 189 190 func (s *AvailabilityZoneSuite) TestDistributeInstancesGroup(c *gc.C) { 191 expectedGroup := []instance.Id{"0", "1", "2"} 192 var called bool 193 s.PatchValue(common.InternalAvailabilityZoneAllocations, func(_ common.ZonedEnviron, ctx context.ProviderCallContext, group []instance.Id) ([]common.AvailabilityZoneInstances, error) { 194 c.Assert(group, gc.DeepEquals, expectedGroup) 195 called = true 196 return nil, nil 197 }) 198 _, err := common.DistributeInstances(&s.env, s.callCtx, nil, expectedGroup, nil) 199 c.Assert(err, jc.ErrorIsNil) 200 c.Assert(called, jc.IsTrue) 201 } 202 203 func (s *AvailabilityZoneSuite) TestDistributeInstancesGroupErrors(c *gc.C) { 204 resultErr := fmt.Errorf("whatever") 205 s.PatchValue(common.InternalAvailabilityZoneAllocations, func(_ common.ZonedEnviron, ctx context.ProviderCallContext, group []instance.Id) ([]common.AvailabilityZoneInstances, error) { 206 return nil, resultErr 207 }) 208 _, err := common.DistributeInstances(&s.env, s.callCtx, nil, nil, nil) 209 c.Assert(err, gc.Equals, resultErr) 210 } 211 212 func (s *AvailabilityZoneSuite) TestDistributeInstances(c *gc.C) { 213 var zoneInstances []common.AvailabilityZoneInstances 214 s.PatchValue(common.InternalAvailabilityZoneAllocations, func(_ common.ZonedEnviron, ctx context.ProviderCallContext, group []instance.Id) ([]common.AvailabilityZoneInstances, error) { 215 return zoneInstances, nil 216 }) 217 218 type distributeInstancesTest struct { 219 zoneInstances []common.AvailabilityZoneInstances 220 candidates []instance.Id 221 limitZones []string 222 eligible []instance.Id 223 } 224 225 defaultZoneInstances := []common.AvailabilityZoneInstances{{ 226 ZoneName: "az0", 227 Instances: []instance.Id{"i0"}, 228 }, { 229 ZoneName: "az1", 230 Instances: []instance.Id{"i1"}, 231 }, { 232 ZoneName: "az2", 233 Instances: []instance.Id{"i2"}, 234 }} 235 236 tests := []distributeInstancesTest{{ 237 zoneInstances: defaultZoneInstances, 238 candidates: []instance.Id{"i2", "i3", "i4"}, 239 eligible: []instance.Id{"i2"}, 240 }, { 241 zoneInstances: defaultZoneInstances, 242 candidates: []instance.Id{"i0", "i1", "i2"}, 243 eligible: []instance.Id{"i0", "i1", "i2"}, 244 }, { 245 zoneInstances: defaultZoneInstances, 246 candidates: []instance.Id{"i3", "i4", "i5"}, 247 eligible: []instance.Id{}, 248 }, { 249 zoneInstances: defaultZoneInstances, 250 candidates: []instance.Id{}, 251 eligible: []instance.Id{}, 252 }, { 253 zoneInstances: []common.AvailabilityZoneInstances{}, 254 candidates: []instance.Id{"i0"}, 255 eligible: []instance.Id{}, 256 }, { 257 // Limit to all zones; essentially the same as no limit. 258 zoneInstances: defaultZoneInstances, 259 candidates: []instance.Id{"i0", "i1", "i2"}, 260 limitZones: []string{"az0", "az1", "az2"}, 261 eligible: []instance.Id{"i0", "i1", "i2"}, 262 }, { 263 // Simple limit to a subset of zones. 264 zoneInstances: defaultZoneInstances, 265 candidates: []instance.Id{"i0", "i1", "i2"}, 266 limitZones: []string{"az0", "az1"}, 267 eligible: []instance.Id{"i0", "i1"}, 268 }, { 269 // Intersecting zone limit with equal distribution. 270 zoneInstances: defaultZoneInstances, 271 candidates: []instance.Id{"i0", "i1"}, 272 limitZones: []string{"az1", "az2", "az4"}, 273 eligible: []instance.Id{"i0", "i1"}, 274 }, { 275 // Intersecting zone limit with unequal distribution. 276 zoneInstances: []common.AvailabilityZoneInstances{{ 277 ZoneName: "az0", 278 Instances: []instance.Id{"i0"}, 279 }, { 280 ZoneName: "az1", 281 Instances: []instance.Id{"i1", "i2"}, 282 }}, 283 candidates: []instance.Id{"i0", "i1", "i2"}, 284 limitZones: []string{"az0", "az1", "az666"}, 285 eligible: []instance.Id{"i0"}, 286 }, { 287 // Limit filters out all zones - no eligible instances. 288 zoneInstances: []common.AvailabilityZoneInstances{{ 289 ZoneName: "az0", 290 Instances: []instance.Id{"i0"}, 291 }, { 292 ZoneName: "az1", 293 Instances: []instance.Id{"i1"}, 294 }}, 295 candidates: []instance.Id{"i0", "i1"}, 296 limitZones: []string{"az2", "az3"}, 297 eligible: []instance.Id{}, 298 }} 299 300 for i, test := range tests { 301 c.Logf("test %d", i) 302 zoneInstances = test.zoneInstances 303 eligible, err := common.DistributeInstances(&s.env, s.callCtx, test.candidates, nil, test.limitZones) 304 c.Assert(err, jc.ErrorIsNil) 305 c.Assert(eligible, jc.SameContents, test.eligible) 306 } 307 }