Skip to content

Commit

Permalink
refactor: move gce discovery config
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeldeib committed Oct 17, 2019
1 parent d63067e commit 95704a7
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 9 deletions.
99 changes: 92 additions & 7 deletions cluster-autoscaler/cloudprovider/gce/gce_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ limitations under the License.
package gce

import (
"errors"
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
"time"

Expand All @@ -41,10 +43,14 @@ import (
)

const (
refreshInterval = 1 * time.Minute
machinesRefreshInterval = 1 * time.Hour
httpTimeout = 30 * time.Second
scaleToZeroSupported = true
refreshInterval = 1 * time.Minute
machinesRefreshInterval = 1 * time.Hour
httpTimeout = 30 * time.Second
scaleToZeroSupported = true
autoDiscovererTypeMIG = "mig"
migAutoDiscovererKeyPrefix = "namePrefix"
migAutoDiscovererKeyMinNodes = "min"
migAutoDiscovererKeyMaxNodes = "max"
)

var (
Expand All @@ -54,6 +60,12 @@ var (
"https://www.googleapis.com/auth/service.management.readonly",
"https://www.googleapis.com/auth/servicecontrol",
}

validMIGAutoDiscovererKeys = strings.Join([]string{
migAutoDiscovererKeyPrefix,
migAutoDiscovererKeyMinNodes,
migAutoDiscovererKeyMaxNodes,
}, ", ")
)

// GceManager handles GCE communication and data caching.
Expand Down Expand Up @@ -97,7 +109,7 @@ type gceManagerImpl struct {
interrupt chan struct{}
regional bool
explicitlyConfigured map[GceRef]bool
migAutoDiscoverySpecs []cloudprovider.MIGAutoDiscoveryConfig
migAutoDiscoverySpecs []migAutoDiscoveryConfig
}

// CreateGceManager constructs GceManager object.
Expand Down Expand Up @@ -172,7 +184,7 @@ func CreateGceManager(configReader io.Reader, discoveryOpts cloudprovider.NodeGr
if err := manager.fetchExplicitMigs(discoveryOpts.NodeGroupSpecs); err != nil {
return nil, fmt.Errorf("failed to fetch MIGs: %v", err)
}
if manager.migAutoDiscoverySpecs, err = discoveryOpts.ParseMIGAutoDiscoverySpecs(); err != nil {
if manager.migAutoDiscoverySpecs, err = parseMIGAutoDiscoverySpecs(discoveryOpts); err != nil {
return nil, err
}

Expand Down Expand Up @@ -312,7 +324,7 @@ func (m *gceManagerImpl) buildMigFromFlag(flag string) (Mig, error) {
return m.buildMigFromSpec(s)
}

func (m *gceManagerImpl) buildMigFromAutoCfg(link string, cfg cloudprovider.MIGAutoDiscoveryConfig) (Mig, error) {
func (m *gceManagerImpl) buildMigFromAutoCfg(link string, cfg migAutoDiscoveryConfig) (Mig, error) {
s := &dynamic.NodeGroupSpec{
Name: link,
MinSize: cfg.MinSize,
Expand Down Expand Up @@ -505,3 +517,76 @@ func parseCustomMachineType(machineType string) (cpu, mem int64, err error) {
mem = mem * units.MiB
return
}

// parseMIGAutoDiscoverySpecs returns any provided NodeGroupAutoDiscoverySpecs
// parsed into configuration appropriate for MIG autodiscovery.
func parseMIGAutoDiscoverySpecs(o cloudprovider.NodeGroupDiscoveryOptions) ([]migAutoDiscoveryConfig, error) {
cfgs := make([]migAutoDiscoveryConfig, len(o.NodeGroupAutoDiscoverySpecs))
var err error
for i, spec := range o.NodeGroupAutoDiscoverySpecs {
cfgs[i], err = parseMIGAutoDiscoverySpec(spec)
if err != nil {
return nil, err
}
}
return cfgs, nil
}

// A migAutoDiscoveryConfig specifies how to autodiscover GCE MIGs.
type migAutoDiscoveryConfig struct {
// Re is a regexp passed using the eq filter to the GCE list API.
Re *regexp.Regexp
// MinSize specifies the minimum size for all MIGs that match Re.
MinSize int
// MaxSize specifies the maximum size for all MIGs that match Re.
MaxSize int
}

func parseMIGAutoDiscoverySpec(spec string) (migAutoDiscoveryConfig, error) {
cfg := migAutoDiscoveryConfig{}

tokens := strings.Split(spec, ":")
if len(tokens) != 2 {
return cfg, fmt.Errorf("spec \"%s\" should be discoverer:key=value,key=value", spec)
}
discoverer := tokens[0]
if discoverer != autoDiscovererTypeMIG {
return cfg, fmt.Errorf("unsupported discoverer specified: %s", discoverer)
}

for _, arg := range strings.Split(tokens[1], ",") {
kv := strings.Split(arg, "=")
if len(kv) != 2 {
return cfg, fmt.Errorf("invalid key=value pair %s", kv)
}
k, v := kv[0], kv[1]

var err error
switch k {
case migAutoDiscovererKeyPrefix:
if cfg.Re, err = regexp.Compile(fmt.Sprintf("^%s.+", v)); err != nil {
return cfg, fmt.Errorf("invalid instance group name prefix \"%s\" - \"^%s.+\" must be a valid RE2 regexp", v, v)
}
case migAutoDiscovererKeyMinNodes:
if cfg.MinSize, err = strconv.Atoi(v); err != nil {
return cfg, fmt.Errorf("invalid minimum nodes: %s", v)
}
case migAutoDiscovererKeyMaxNodes:
if cfg.MaxSize, err = strconv.Atoi(v); err != nil {
return cfg, fmt.Errorf("invalid maximum nodes: %s", v)
}
default:
return cfg, fmt.Errorf("unsupported key \"%s\" is specified for discoverer \"%s\". Supported keys are \"%s\"", k, discoverer, validMIGAutoDiscovererKeys)
}
}
if cfg.Re == nil || cfg.Re.String() == "^.+" {
return cfg, errors.New("empty instance group name prefix supplied")
}
if cfg.MinSize > cfg.MaxSize {
return cfg, fmt.Errorf("minimum size %d is greater than maximum size %d", cfg.MinSize, cfg.MaxSize)
}
if cfg.MaxSize < 1 {
return cfg, fmt.Errorf("maximum size %d must be at least 1", cfg.MaxSize)
}
return cfg, nil
}
108 changes: 106 additions & 2 deletions cluster-autoscaler/cloudprovider/gce/gce_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@ func TestFetchAutoMigsZonal(t *testing.T) {
g := newTestGceManager(t, server.URL, regional)

min, max := 0, 100
g.migAutoDiscoverySpecs = []cloudprovider.MIGAutoDiscoveryConfig{
g.migAutoDiscoverySpecs = []migAutoDiscoveryConfig{
{Re: regexp.MustCompile("UNUSED"), MinSize: min, MaxSize: max},
}

Expand Down Expand Up @@ -1213,7 +1213,7 @@ func TestFetchAutoMigsRegional(t *testing.T) {
g := newTestGceManager(t, server.URL, regional)

min, max := 0, 100
g.migAutoDiscoverySpecs = []cloudprovider.MIGAutoDiscoveryConfig{
g.migAutoDiscoverySpecs = []migAutoDiscoveryConfig{
{Re: regexp.MustCompile("UNUSED"), MinSize: min, MaxSize: max},
}

Expand Down Expand Up @@ -1411,3 +1411,107 @@ func validateMigExists(t *testing.T, migs []Mig, zone string, name string, minSi
}
assert.Failf(t, "Mig not found", "Mig %v not found among %v", ref, allRefs)
}

func TestParseMIGAutoDiscoverySpecs(t *testing.T) {
cases := []struct {
name string
specs []string
want []migAutoDiscoveryConfig
wantErr bool
}{
{
name: "GoodSpecs",
specs: []string{
"mig:namePrefix=pfx,min=0,max=10",
"mig:namePrefix=anotherpfx,min=1,max=2",
},
want: []migAutoDiscoveryConfig{
{Re: regexp.MustCompile("^pfx.+"), MinSize: 0, MaxSize: 10},
{Re: regexp.MustCompile("^anotherpfx.+"), MinSize: 1, MaxSize: 2},
},
},
{
name: "MissingMIGType",
specs: []string{"namePrefix=pfx,min=0,max=10"},
wantErr: true,
},
{
name: "WrongType",
specs: []string{"asg:namePrefix=pfx,min=0,max=10"},
wantErr: true,
},
{
name: "UnknownKey",
specs: []string{"mig:namePrefix=pfx,min=0,max=10,unknown=hi"},
wantErr: true,
},
{
name: "NonIntegerMin",
specs: []string{"mig:namePrefix=pfx,min=a,max=10"},
wantErr: true,
},
{
name: "NonIntegerMax",
specs: []string{"mig:namePrefix=pfx,min=1,max=donkey"},
wantErr: true,
},
{
name: "PrefixDoesNotCompileToRegexp",
specs: []string{"mig:namePrefix=a),min=1,max=10"},
wantErr: true,
},
{
name: "KeyMissingValue",
specs: []string{"mig:namePrefix=prefix,min=,max=10"},
wantErr: true,
},
{
name: "ValueMissingKey",
specs: []string{"mig:namePrefix=prefix,=0,max=10"},
wantErr: true,
},
{
name: "KeyMissingSeparator",
specs: []string{"mig:namePrefix=prefix,min,max=10"},
wantErr: true,
},
{
name: "TooManySeparators",
specs: []string{"mig:namePrefix=prefix,min=0,max=10=20"},
wantErr: true,
},
{
name: "PrefixIsEmpty",
specs: []string{"mig:namePrefix=,min=0,max=10"},
wantErr: true,
},
{
name: "PrefixIsMissing",
specs: []string{"mig:min=0,max=10"},
wantErr: true,
},
{
name: "MaxBelowMin",
specs: []string{"mig:namePrefix=prefix,min=10,max=1"},
wantErr: true,
},
{
name: "MaxIsZero",
specs: []string{"mig:namePrefix=prefix,min=0,max=0"},
wantErr: true,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
do := cloudprovider.NodeGroupDiscoveryOptions{NodeGroupAutoDiscoverySpecs: tc.specs}
got, err := parseMIGAutoDiscoverySpecs(do)
if tc.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.True(t, assert.ObjectsAreEqualValues(tc.want, got), "\ngot: %#v\nwant: %#v", got, tc.want)
})
}
}

0 comments on commit 95704a7

Please sign in to comment.