diff --git a/pkg/manager/context/context.go b/pkg/manager/context/context.go index ae9bbf276..9f37d3c95 100644 --- a/pkg/manager/context/context.go +++ b/pkg/manager/context/context.go @@ -89,7 +89,9 @@ func Init(ca *ContextArgs, provider Provider) (*Context, error) { if err != nil { return nil, err } - c.targetHostingPlace = *hp + if hp != nil { + c.targetHostingPlace = *hp + } // Manage if err := provider.Init(ca.BackedURL); err != nil { return nil, err diff --git a/pkg/provider/azure/azure.go b/pkg/provider/azure/azure.go index 31b34f118..13d1d80c0 100644 --- a/pkg/provider/azure/azure.go +++ b/pkg/provider/azure/azure.go @@ -1,7 +1,6 @@ package azure import ( - "fmt" "os" "slices" "strings" @@ -39,7 +38,8 @@ func (a *Azure) DefaultHostingPlace() (*string, error) { if len(hp) > 0 { return &hp, nil } - return nil, fmt.Errorf("missing default value for Azure Location") + logging.Infof("missing default value for Azure Location, if needed it should set using ARM_LOCATION_NAME or AZURE_DEFAULTS_LOCATION") + return nil, nil } // Envs required for auth with go sdk diff --git a/pkg/provider/azure/data/spot.go b/pkg/provider/azure/data/spot.go index 5c82bfe1a..31c6442b4 100644 --- a/pkg/provider/azure/data/spot.go +++ b/pkg/provider/azure/data/spot.go @@ -23,14 +23,14 @@ import ( const ( querySpotPrice = "SpotResources | where type =~ 'microsoft.compute/skuspotpricehistory/ostype/location' " + "and sku.name in~ ({{range $index, $v := .ComputeSizes}}{{if $index}},{{end}}'{{$v}}'{{end}}) and properties.osType =~ '{{.OSType}}'" + - "and location =~ '{{.Location}}' " + + "and location in~ ({{range $index, $l := .Locations}}{{if $index}},{{end}}'{{$l}}'{{end}}) " + "| project skuName=tostring(sku.name),osType=tostring(properties.osType)," + "location,latestSpotPriceUSD=todouble(properties.spotPrices[0].priceUSD)" + "| order by latestSpotPriceUSD asc" queryEvictionRate = "SpotResources | where type =~ 'microsoft.compute/skuspotevictionrate/location' " + "and sku.name in~ ({{range $index, $v := .ComputeSizes}}{{if $index}},{{end}}'{{$v}}'{{end}})" + - "and location =~ '{{.Location}}' " + + "and location in~ ({{range $index, $l := .Locations}}{{if $index}},{{end}}'{{$l}}'{{end}}) " + "and tostring(properties.evictionRate) in~ ({{range $index, $e := .AllowedER}}{{if $index}},{{end}}'{{$e}}'{{end}}) " + "| project skuName=tostring(sku.name),location,spotEvictionRate=tostring(properties.evictionRate) " ) @@ -94,11 +94,18 @@ func SpotInfo(mCtx *mc.Context, args *SpotInfoArgs) (*spot.SpotResults, error) { if err != nil { return nil, err } - evictionRates, err := hostingPlaces.RunOnHostingPlaces(locations, - evictionRatesArgs{ + allEvictionRates, err := checkEvictionRates(locations, + checkEvictionRatesArgs{ computeSizes: args.ComputeSizes, clientFactory: clientFactory, allowedER: allowedER(*args.SpotTolerance), + }) + if err != nil { + return nil, err + } + evictionRates, err := hostingPlaces.RunOnHostingPlaces(locations, + evictionRatesArgs{ + evr: allEvictionRates, }, evictionRatesAsync) if err != nil { @@ -110,11 +117,18 @@ func SpotInfo(mCtx *mc.Context, args *SpotInfoArgs) (*spot.SpotResults, error) { locations = utilMaps.Keys(evictionRates) } // prices - spotPricings, err := hostingPlaces.RunOnHostingPlaces(locations, - spotPricingArgs{ + allSpotPricings, err := checkSpotPricing(mCtx, locations, + checkSpotPricingArgs{ computeSizes: args.ComputeSizes, clientFactory: clientFactory, osType: args.OSType, + }) + if err != nil { + return nil, err + } + spotPricings, err := hostingPlaces.RunOnHostingPlaces(locations, + spotPricingArgs{ + sp: allSpotPricings, }, spotPricingAsync) if err != nil { @@ -205,13 +219,17 @@ func allowedER(spotTolerance spot.Tolerance) []string { }) } -type evictionRatesArgs struct { +type checkEvictionRatesArgs struct { clientFactory *armresourcegraph.ClientFactory computeSizes []string allowedER []string // capacity int32 } +type evictionRatesArgs struct { + evr map[string][]evictionRateResult +} + type evictionRateResult struct { ComputeSize string `json:"skuName"` Location string `json:"location"` @@ -220,27 +238,23 @@ type evictionRateResult struct { type queryERData struct { ComputeSizes []string - Location string + Locations []string AllowedER []string } -// This will get evictionrates grouped on map per region -// only scores over tolerance will be added -func evictionRatesAsync(location string, args evictionRatesArgs, c chan hostingPlaces.HostingPlaceData[[]evictionRateResult]) { +func checkEvictionRates(locations []string, args checkEvictionRatesArgs) (map[string][]evictionRateResult, error) { tmpl, err := template.New("graphQuery").Parse(queryEvictionRate) if err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err } buffer := new(bytes.Buffer) err = tmpl.Execute(buffer, queryERData{ ComputeSizes: args.computeSizes, - Location: location, + Locations: locations, AllowedER: args.allowedER, }) if err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err } evrr := buffer.String() qr, err := args.clientFactory.NewClient().Resources(context.Background(), @@ -249,23 +263,31 @@ func evictionRatesAsync(location string, args evictionRatesArgs, c chan hostingP }, nil) if err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err } var results []evictionRateResult for _, r := range qr.Data.([]interface{}) { rJSON, err := json.Marshal(r) if err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err } rStruct := evictionRateResult{} if err := json.Unmarshal(rJSON, &rStruct); err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err } results = append(results, rStruct) } + return utilSlices.Split( + results, + func(e evictionRateResult) string { + return e.Location + }), nil +} + +// This will get evictionrates grouped on map per region +// only scores over tolerance will be added +func evictionRatesAsync(location string, args evictionRatesArgs, c chan hostingPlaces.HostingPlaceData[[]evictionRateResult]) { + results := args.evr[location] // Order by eviction rate slices.SortFunc(results, func(a, b evictionRateResult) int { @@ -277,13 +299,17 @@ func evictionRatesAsync(location string, args evictionRatesArgs, c chan hostingP Value: results} } -type spotPricingArgs struct { +type checkSpotPricingArgs struct { clientFactory *armresourcegraph.ClientFactory computeSizes []string osType string // capacity int32 } +type spotPricingArgs struct { + sp map[string][]spotPricingResult +} + type spotPricingResult struct { ComputeSize string `json:"skuName"` OSType string `json:"osType"` @@ -293,26 +319,24 @@ type spotPricingResult struct { type querySpotPriceData struct { ComputeSizes []string - Location string + Locations []string OSType string } // This function will return a slice of values with price ordered from minor prices to major -func spotPricingAsync(location string, args spotPricingArgs, c chan hostingPlaces.HostingPlaceData[[]spotPricingResult]) { +func checkSpotPricing(mCtx *mc.Context, locations []string, args checkSpotPricingArgs) (map[string][]spotPricingResult, error) { tmpl, err := template.New("graphQuery").Parse(querySpotPrice) if err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err } buffer := new(bytes.Buffer) err = tmpl.Execute(buffer, querySpotPriceData{ ComputeSizes: args.computeSizes, - Location: location, + Locations: locations, OSType: args.osType, }) if err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err } spr := buffer.String() qr, err := args.clientFactory.NewClient().Resources(context.Background(), @@ -321,25 +345,34 @@ func spotPricingAsync(location string, args spotPricingArgs, c chan hostingPlace }, nil) if err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err } var results []spotPricingResult for _, r := range qr.Data.([]interface{}) { rJSON, err := json.Marshal(r) if err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err } rStruct := spotPricingResult{} if err := json.Unmarshal(rJSON, &rStruct); err != nil { - hostingPlaces.SendAsyncErr(c, err) - return + return nil, err + } + if mCtx.Debug() { + logging.Debugf("Found ComputeSize %s at Location %s with spot price %.2f", + string(rStruct.ComputeSize), rStruct.Location, rStruct.Price) } - logging.Debugf("Found ComputeSize %s at Location %s with spot price %.2f", - string(rStruct.ComputeSize), rStruct.Location, rStruct.Price) results = append(results, rStruct) } + return utilSlices.Split( + results, + func(s spotPricingResult) string { + return s.Location + }), nil +} + +// This function will return a slice of values with price ordered from minor prices to major +func spotPricingAsync(location string, args spotPricingArgs, c chan hostingPlaces.HostingPlaceData[[]spotPricingResult]) { + results := args.sp[location] // Order by price if len(results) > 0 { utilSlices.SortbyFloat(results, @@ -350,7 +383,6 @@ func spotPricingAsync(location string, args spotPricingArgs, c chan hostingPlace c <- hostingPlaces.HostingPlaceData[[]spotPricingResult]{ Region: location, Value: results} - } type spotChoiceArgs struct {