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  }