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  }