Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pkg/manager/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pkg/provider/azure/azure.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package azure

import (
"fmt"
"os"
"slices"
"strings"
Expand Down Expand Up @@ -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
Expand Down
110 changes: 71 additions & 39 deletions pkg/provider/azure/data/spot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) "
)
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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"`
Expand All @@ -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(),
Expand All @@ -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 {
Expand All @@ -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"`
Expand All @@ -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(),
Expand All @@ -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,
Expand All @@ -350,7 +383,6 @@ func spotPricingAsync(location string, args spotPricingArgs, c chan hostingPlace
c <- hostingPlaces.HostingPlaceData[[]spotPricingResult]{
Region: location,
Value: results}

}

type spotChoiceArgs struct {
Expand Down