github.com/openshift/installer@v1.4.17/pkg/asset/machines/openstack/machines_test.go (about)

     1  package openstack
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	machinev1 "github.com/openshift/api/machine/v1"
     9  	"github.com/openshift/installer/pkg/types/openstack"
    10  )
    11  
    12  func mpWithZones(zones ...string) func(*openstack.MachinePool) {
    13  	return func(mpool *openstack.MachinePool) {
    14  		mpool.Zones = zones
    15  	}
    16  }
    17  
    18  func mpWithRootVolumeZones(zones ...string) func(*openstack.MachinePool) {
    19  	return func(mpool *openstack.MachinePool) {
    20  		if mpool.RootVolume != nil {
    21  			mpool.RootVolume.Zones = zones
    22  		} else {
    23  			mpool.RootVolume = &openstack.RootVolume{Zones: zones}
    24  		}
    25  	}
    26  }
    27  
    28  func mpWithRootVolumeTypes(types ...string) func(*openstack.MachinePool) {
    29  	return func(mpool *openstack.MachinePool) {
    30  		if mpool.RootVolume != nil {
    31  			mpool.RootVolume.Types = types
    32  		} else {
    33  			mpool.RootVolume = &openstack.RootVolume{Types: types}
    34  		}
    35  	}
    36  }
    37  
    38  func generateMachinePool(options ...func(*openstack.MachinePool)) openstack.MachinePool {
    39  	mpool := openstack.MachinePool{}
    40  	for _, apply := range options {
    41  		apply(&mpool)
    42  	}
    43  	return mpool
    44  }
    45  
    46  func TestFailureDomains(t *testing.T) {
    47  	type checkFunc func([]machinev1.OpenStackFailureDomain, error) error
    48  	check := func(fns ...checkFunc) []checkFunc { return fns }
    49  
    50  	hasNFailureDomains := func(want int) checkFunc {
    51  		return func(fds []machinev1.OpenStackFailureDomain, _ error) error {
    52  			if have := len(fds); want != have {
    53  				return fmt.Errorf("expected %d failure domains, got %d", want, have)
    54  			}
    55  			return nil
    56  		}
    57  	}
    58  
    59  	hasComputeZones := func(wantZones ...string) checkFunc {
    60  		return func(fds []machinev1.OpenStackFailureDomain, _ error) error {
    61  			haveZones := make([]string, len(fds))
    62  			for i := range fds {
    63  				haveZones[i] = fds[i].AvailabilityZone
    64  			}
    65  
    66  			if wantLen, haveLen := len(wantZones), len(haveZones); wantLen != haveLen {
    67  				return fmt.Errorf("expected compute zones %v (len %d), got %v (len %d)", wantZones, wantLen, haveZones, haveLen)
    68  			}
    69  
    70  			for i := range fds {
    71  				if want, have := wantZones[i], haveZones[i]; want != have {
    72  					return fmt.Errorf("expected compute zones %v, got %v", wantZones, haveZones)
    73  				}
    74  			}
    75  
    76  			return nil
    77  		}
    78  	}
    79  
    80  	hasNilRootVolume := func(fds []machinev1.OpenStackFailureDomain, _ error) error {
    81  		for i := range fds {
    82  			if fds[i].RootVolume != nil {
    83  				return fmt.Errorf("failure domain %d has unexpectedly non-nil RootVolume", i)
    84  			}
    85  		}
    86  		return nil
    87  	}
    88  
    89  	hasRootVolumeZones := func(wantZones ...string) checkFunc {
    90  		return func(fds []machinev1.OpenStackFailureDomain, _ error) error {
    91  			haveZones := make([]string, len(fds))
    92  			for i := range fds {
    93  				if fds[i].RootVolume == nil {
    94  					return fmt.Errorf("failure domain %d has unexpectedly nil RootVolume", i)
    95  				}
    96  				haveZones[i] = fds[i].RootVolume.AvailabilityZone
    97  			}
    98  
    99  			if wantLen, haveLen := len(wantZones), len(haveZones); wantLen != haveLen {
   100  				return fmt.Errorf("expected root volume zones %v, got %v", wantZones, haveZones)
   101  			}
   102  
   103  			for i := range fds {
   104  				if want, have := wantZones[i], haveZones[i]; want != have {
   105  					return fmt.Errorf("expected root volume zones %v, got %v", wantZones, haveZones)
   106  				}
   107  			}
   108  
   109  			return nil
   110  		}
   111  	}
   112  
   113  	hasRootVolumeTypes := func(wantTypes ...string) checkFunc {
   114  		return func(fds []machinev1.OpenStackFailureDomain, _ error) error {
   115  			haveTypes := make([]string, len(fds))
   116  			for i := range fds {
   117  				if fds[i].RootVolume == nil {
   118  					return fmt.Errorf("failure domain %d has unexpectedly nil RootVolume", i)
   119  				}
   120  				haveTypes[i] = fds[i].RootVolume.VolumeType
   121  			}
   122  
   123  			if wantLen, haveLen := len(wantTypes), len(haveTypes); wantLen != haveLen {
   124  				return fmt.Errorf("expected root volume types %v, got %v", wantTypes, haveTypes)
   125  			}
   126  
   127  			for i := range fds {
   128  				if want, have := wantTypes[i], haveTypes[i]; want != have {
   129  					return fmt.Errorf("expected root volume types %v, got %v", wantTypes, haveTypes)
   130  				}
   131  			}
   132  
   133  			return nil
   134  		}
   135  	}
   136  
   137  	doesNotPanic := func(_ []machinev1.OpenStackFailureDomain, have error) error {
   138  		if have != nil {
   139  			return fmt.Errorf("unexpected panic: %w", have)
   140  		}
   141  		return nil
   142  	}
   143  
   144  	panicsWith := func(want string) checkFunc {
   145  		return func(_ []machinev1.OpenStackFailureDomain, have error) error {
   146  			if have == nil {
   147  				return fmt.Errorf("unexpectedly, didn't panic")
   148  			}
   149  			if have := fmt.Sprintf("%v", have); !strings.Contains(have, want) {
   150  				return fmt.Errorf("expected panic with %q, got %q", want, have)
   151  			}
   152  			return nil
   153  		}
   154  	}
   155  
   156  	for _, tc := range [...]struct {
   157  		name   string
   158  		mpool  openstack.MachinePool
   159  		checks []checkFunc
   160  	}{
   161  		{
   162  			"no_zones",
   163  			generateMachinePool(),
   164  			check(
   165  				hasNFailureDomains(1),
   166  				hasComputeZones(""),
   167  				hasNilRootVolume,
   168  				doesNotPanic,
   169  			),
   170  		},
   171  		{
   172  			"one_compute_zone",
   173  			generateMachinePool(
   174  				mpWithZones("one"),
   175  			),
   176  			check(
   177  				hasNFailureDomains(1),
   178  				hasComputeZones("one"),
   179  				hasNilRootVolume,
   180  				doesNotPanic,
   181  			),
   182  		},
   183  		{
   184  			"three_compute_zones",
   185  			generateMachinePool(
   186  				mpWithZones("one", "two", "three"),
   187  			),
   188  			check(
   189  				hasNFailureDomains(3),
   190  				hasComputeZones("one", "two", "three"),
   191  				hasNilRootVolume,
   192  				doesNotPanic,
   193  			),
   194  		},
   195  		{
   196  			"three_compute_zones_one_root_volume_zone",
   197  			generateMachinePool(
   198  				mpWithZones("one", "two", "three"),
   199  				mpWithRootVolumeZones("volume_one"),
   200  				mpWithRootVolumeTypes("type-1"),
   201  			),
   202  			check(
   203  				hasNFailureDomains(3),
   204  				hasComputeZones("one", "two", "three"),
   205  				hasRootVolumeZones("volume_one", "volume_one", "volume_one"),
   206  				hasRootVolumeTypes("type-1", "type-1", "type-1"),
   207  				doesNotPanic,
   208  			),
   209  		},
   210  		{
   211  			"one_compute_zone_three_root_volume_zones",
   212  			generateMachinePool(
   213  				mpWithZones("one"),
   214  				mpWithRootVolumeZones("volume_one", "volume_two", "volume_three"),
   215  				mpWithRootVolumeTypes("type-1"),
   216  			),
   217  			check(
   218  				hasNFailureDomains(3),
   219  				hasComputeZones("one", "one", "one"),
   220  				hasRootVolumeZones("volume_one", "volume_two", "volume_three"),
   221  				hasRootVolumeTypes("type-1", "type-1", "type-1"),
   222  				doesNotPanic,
   223  			),
   224  		},
   225  		{
   226  			"three_compute_zone_two_root_volume_zones_panics",
   227  			generateMachinePool(
   228  				mpWithZones("one", "two", "three"),
   229  				mpWithRootVolumeZones("volume_one", "volume_two"),
   230  			),
   231  			check(
   232  				// We have to check for a partial result here, because the mapping
   233  				// of compute zones to root volume zones is handled in a map therefore
   234  				// the order is not deterministic.
   235  				panicsWith("availability zones should have equal length"),
   236  			),
   237  		},
   238  		{
   239  			"three_compute_zones_three_root_volume_types",
   240  			generateMachinePool(
   241  				mpWithZones("one", "two", "three"),
   242  				mpWithRootVolumeZones("volume_one", "volume_two", "volume_three"),
   243  				mpWithRootVolumeTypes("type-1", "type-2", "type-3"),
   244  			),
   245  			check(
   246  				hasNFailureDomains(3),
   247  				hasComputeZones("one", "two", "three"),
   248  				hasRootVolumeZones("volume_one", "volume_two", "volume_three"),
   249  				hasRootVolumeTypes("type-1", "type-2", "type-3"),
   250  				doesNotPanic,
   251  			),
   252  		},
   253  		{
   254  			"three_root_volume_types",
   255  			generateMachinePool(
   256  				mpWithRootVolumeTypes("type-1", "type-2", "type-3"),
   257  			),
   258  			check(
   259  				hasNFailureDomains(3),
   260  				hasComputeZones("", "", ""),
   261  				hasRootVolumeZones("", "", ""),
   262  				hasRootVolumeTypes("type-1", "type-2", "type-3"),
   263  				doesNotPanic,
   264  			),
   265  		},
   266  	} {
   267  		t.Run(tc.name, func(t *testing.T) {
   268  			failureDomains, recoveredPanic := func() (fds []machinev1.OpenStackFailureDomain, recoveredPanic error) {
   269  				defer func() {
   270  					if r := recover(); r != nil {
   271  						recoveredPanic = fmt.Errorf("%v", r)
   272  					}
   273  				}()
   274  
   275  				fds = failureDomainsFromSpec(tc.mpool)
   276  				return
   277  			}()
   278  
   279  			for _, check := range tc.checks {
   280  				if err := check(failureDomains, recoveredPanic); err != nil {
   281  					t.Error(err)
   282  				}
   283  			}
   284  		})
   285  	}
   286  }
   287  
   288  func TestPruneFailureDomains(t *testing.T) {
   289  }