diff --git a/pkg/provider/azure/action/aks/aks.go b/pkg/provider/azure/action/aks/aks.go index cf466278f..8a18d3b09 100644 --- a/pkg/provider/azure/action/aks/aks.go +++ b/pkg/provider/azure/action/aks/aks.go @@ -209,9 +209,9 @@ func (r *AKSRequest) valuesCheckingSpot() (*string, *float64, error) { if r.Spot { bsc, err := spotAzure.GetBestSpotChoice(spotAzure.BestSpotChoiceRequest{ - VMTypes: []string{r.VMSize}, - OSType: "linux", - EvictioRateTolerance: r.SpotTolerance, + VMTypes: []string{r.VMSize}, + OSType: "linux", + EvictionRateTolerance: r.SpotTolerance, }) logging.Debugf("Best spot price option found: %v", bsc) if err != nil { diff --git a/pkg/provider/azure/action/linux/linux.go b/pkg/provider/azure/action/linux/linux.go index 7b3c4116b..768257213 100644 --- a/pkg/provider/azure/action/linux/linux.go +++ b/pkg/provider/azure/action/linux/linux.go @@ -175,11 +175,16 @@ func (r *LinuxRequest) deployer(ctx *pulumi.Context) error { func (r *LinuxRequest) valuesCheckingSpot() (*string, string, *float64, error) { if r.Spot { + ir, err := data.GetImageRef(r.OSType, r.Arch, r.Version) + if err != nil { + return nil, "", nil, err + } bsc, err := spotAzure.GetBestSpotChoice(spotAzure.BestSpotChoiceRequest{ - VMTypes: util.If(len(r.VMSizes) > 0, r.VMSizes, []string{defaultVMSize}), - OSType: "linux", - EvictioRateTolerance: r.SpotTolerance, + VMTypes: util.If(len(r.VMSizes) > 0, r.VMSizes, []string{defaultVMSize}), + OSType: "linux", + EvictionRateTolerance: r.SpotTolerance, + ImageRef: *ir, }) logging.Debugf("Best spot price option found: %v", bsc) if err != nil { diff --git a/pkg/provider/azure/action/windows/windows.go b/pkg/provider/azure/action/windows/windows.go index 9947e89e0..3d082746d 100644 --- a/pkg/provider/azure/action/windows/windows.go +++ b/pkg/provider/azure/action/windows/windows.go @@ -172,9 +172,9 @@ func (r *WindowsRequest) valuesCheckingSpot() (*string, string, *float64, error) if r.Spot { bsc, err := spotAzure.GetBestSpotChoice(spotAzure.BestSpotChoiceRequest{ - VMTypes: util.If(len(r.VMSizes) > 0, r.VMSizes, []string{defaultVMSize}), - OSType: "windows", - EvictioRateTolerance: r.SpotTolerance, + VMTypes: util.If(len(r.VMSizes) > 0, r.VMSizes, []string{defaultVMSize}), + OSType: "windows", + EvictionRateTolerance: r.SpotTolerance, }) logging.Debugf("Best spot price option found: %v", bsc) if err != nil { diff --git a/pkg/provider/azure/data/images.go b/pkg/provider/azure/data/images.go new file mode 100644 index 000000000..55d932a97 --- /dev/null +++ b/pkg/provider/azure/data/images.go @@ -0,0 +1,59 @@ +package data + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6" + maptAzIdentity "github.com/redhat-developer/mapt/pkg/provider/azure/module/identity" + "github.com/redhat-developer/mapt/pkg/util/logging" +) + +type ImageRequest struct { + Region string + ImageReference +} + +func GetImage(req ImageRequest) (*armcompute.CommunityGalleryImagesClientGetResponse, error) { + maptAzIdentity.SetAZIdentityEnvs() + + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return nil, err + } + ctx := context.Background() + subscriptionId := os.Getenv("AZURE_SUBSCRIPTION_ID") + + clientFactory, err := armcompute.NewClientFactory(subscriptionId, cred, nil) + if err != nil { + return nil, err + } + // for community gallary images + if len(req.ID) > 0 { + // extract gallary ID and image name from ID url which looks like: + // /CommunityGalleries/Fedora-5e266ba4-2250-406d-adad-5d73860d958f/Images/Fedora-Cloud-40-Arm64/Versions/latest + parts := strings.Split(req.ID, "/") + if len(parts) != 7 { + return nil, fmt.Errorf("invalid community gallary image ID: %s", req.ID) + } + res, err := clientFactory.NewCommunityGalleryImagesClient().Get(ctx, req.Region, parts[2], parts[4], nil) + if err != nil { + return nil, err + } + return &res, nil + } + // for azure offered VM images: https://learn.microsoft.com/en-us/rest/api/compute/virtual-machine-images/get + // there's a different API to check but currently we only check availability of community images + return nil, nil +} + +func IsImageOffered(req ImageRequest) bool { + if _, err := GetImage(req); err != nil { + logging.Debugf("error while checking if image available at location: %v", err) + return false + } + return true +} diff --git a/pkg/spot/azure/spot.go b/pkg/spot/azure/spot.go index 69c8905fb..cdbacab32 100644 --- a/pkg/spot/azure/spot.go +++ b/pkg/spot/azure/spot.go @@ -13,6 +13,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph" + "github.com/redhat-developer/mapt/pkg/provider/azure/data" maptAzIdentity "github.com/redhat-developer/mapt/pkg/provider/azure/module/identity" "github.com/redhat-developer/mapt/pkg/util" "github.com/redhat-developer/mapt/pkg/util/logging" @@ -42,9 +43,10 @@ const ( type EvictionRate int type BestSpotChoiceRequest struct { - VMTypes []string - OSType string - EvictioRateTolerance EvictionRate + VMTypes []string + OSType string + EvictionRateTolerance EvictionRate + ImageRef data.ImageReference } type BestSpotChoiceResponse struct { @@ -103,7 +105,7 @@ func GetBestSpotChoice(r BestSpotChoiceRequest) (*BestSpotChoiceResponse, error) return nil, fmt.Errorf("error eviction rates are returning empty") } // Compare prices and evictions - return getBestSpotChoice(phr, evrr, Lowest, r.EvictioRateTolerance) + return getBestSpotChoice(phr, evrr, Lowest, r.EvictionRateTolerance, r.ImageRef.ID) } func getGraphClient() (*armresourcegraph.Client, error) { @@ -204,7 +206,7 @@ func getEvictionRateInfoByVMTypes(ctx context.Context, client *armresourcegraph. return results, nil } -func getBestSpotChoice(s []priceHistory, e []evictionRate, currentERT EvictionRate, maxERT EvictionRate) (*BestSpotChoiceResponse, error) { +func getBestSpotChoice(s []priceHistory, e []evictionRate, currentERT EvictionRate, maxERT EvictionRate, imageID string) (*BestSpotChoiceResponse, error) { var evm map[string]string = make(map[string]string) for _, ev := range e { evm[fmt.Sprintf("%s%s", ev.Location, ev.VMType)] = ev.EvictionRate @@ -216,12 +218,21 @@ func getBestSpotChoice(s []priceHistory, e []evictionRate, currentERT EvictionRa // and pick one randomly to improve distribution of instances // across locations if ok && er == getEvictionRateValue(currentERT) { - spotChoices = append(spotChoices, - &BestSpotChoiceResponse{ - VMType: sv.VMType, - Location: sv.Location, - Price: sv.Price, - }) + ir := data.ImageRequest{ + Region: sv.Location, + ImageReference: data.ImageReference{ + ID: imageID, + }, + } + if data.IsImageOffered(ir) { + spotChoices = append(spotChoices, + &BestSpotChoiceResponse{ + VMType: sv.VMType, + Location: sv.Location, + Price: sv.Price, + }) + + } } } if len(spotChoices) > 0 { @@ -237,7 +248,7 @@ func getBestSpotChoice(s []priceHistory, e []evictionRate, currentERT EvictionRa if !ok { return nil, fmt.Errorf("could not find any spot") } - return getBestSpotChoice(s, e, *higherERT, maxERT) + return getBestSpotChoice(s, e, *higherERT, maxERT, imageID) } // Get previous higher evicition rate for a giving eviction rate diff --git a/pkg/spot/spot.go b/pkg/spot/spot.go index c3284a2d7..d99a3ea6b 100644 --- a/pkg/spot/spot.go +++ b/pkg/spot/spot.go @@ -126,9 +126,9 @@ func (sr *SpotRequest) GetAzureLowestPrice() (SpotPrice, error) { return SpotPrice{}, nil } spr := azure.BestSpotChoiceRequest{ - VMTypes: vms, - OSType: sr.getAzureOsType(), - EvictioRateTolerance: azure.EvictionRate(sr.EvictionRateTolerance), + VMTypes: vms, + OSType: sr.getAzureOsType(), + EvictionRateTolerance: azure.EvictionRate(sr.EvictionRateTolerance), } prices, err := azure.GetBestSpotChoice(spr)