diff --git a/Makefile b/Makefile index b65b6aab0..85c1e3775 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,22 @@ ENVTEST_VER = v0.0.0-20240317073005-bd9ea79e8d18 ENVTEST_BIN := setup-envtest ENVTEST := $(abspath $(TOOLS_BIN_DIR)/$(ENVTEST_BIN)-$(ENVTEST_VER)) +PROTOC_GEN_GO_VER := v1.36.10 +PROTOC_GEN_GO_BIN := protoc-gen-go +PROTOC_GEN_GO := $(abspath $(TOOLS_BIN_DIR)/$(PROTOC_GEN_GO_BIN)-$(PROTOC_GEN_GO_VER)) + +PROTOC_GEN_GO_GRPC_VER := v1.5.1 +PROTOC_GEN_GO_GRPC_BIN := protoc-gen-go-grpc +PROTOC_GEN_GO_GRPC := $(abspath $(TOOLS_BIN_DIR)/$(PROTOC_GEN_GO_GRPC_BIN)-$(PROTOC_GEN_GO_GRPC_VER)) + +PROTOC_GEN_GRPC_GATEWAY_VER := v2.27.3 +PROTOC_GEN_GRPC_GATEWAY_BIN := protoc-gen-grpc-gateway +PROTOC_GEN_GRPC_GATEWAY := $(abspath $(TOOLS_BIN_DIR)/$(PROTOC_GEN_GRPC_GATEWAY_BIN)-$(PROTOC_GEN_GRPC_GATEWAY_VER)) + +PROTOC_VER := 28.0 +PROTOC_BIN := protoc +PROTOC := $(abspath $(TOOLS_BIN_DIR)/$(PROTOC_BIN)-$(PROTOC_VER)) + # Scripts GO_INSTALL := ./hack/go-install.sh @@ -108,6 +124,23 @@ $(GOIMPORTS): $(ENVTEST): GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) sigs.k8s.io/controller-runtime/tools/setup-envtest $(ENVTEST_BIN) $(ENVTEST_VER) +# PROTOC_GEN_GO +$(PROTOC_GEN_GO): + GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) google.golang.org/protobuf/cmd/protoc-gen-go $(PROTOC_GEN_GO_BIN) $(PROTOC_GEN_GO_VER) + +# PROTOC_GEN_GO_GRPC +$(PROTOC_GEN_GO_GRPC): + GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) google.golang.org/grpc/cmd/protoc-gen-go-grpc $(PROTOC_GEN_GO_GRPC_BIN) $(PROTOC_GEN_GO_GRPC_VER) + +# PROTOC_GEN_GRPC_GATEWAY +$(PROTOC_GEN_GRPC_GATEWAY): + GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway $(PROTOC_GEN_GRPC_GATEWAY_BIN) $(PROTOC_GEN_GRPC_GATEWAY_VER) + +# PROTOC +$(PROTOC): + curl -L -o $(TOOLS_BIN_DIR)/protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VER)/protoc-$(PROTOC_VER)-linux-x86_64.zip && \ + unzip $(TOOLS_BIN_DIR)/protoc.zip -d $(TOOLS_BIN_DIR)/protoc_tmp && mv $(TOOLS_BIN_DIR)/protoc_tmp/bin/protoc $(PROTOC) && rm -rf $(TOOLS_BIN_DIR)/protoc.zip $(TOOLS_BIN_DIR)/protoc_tmp + .PHONY: help help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) @@ -285,8 +318,16 @@ manifests: $(CONTROLLER_GEN) $(CONTROLLER_GEN) \ $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./apis/..." output:crd:artifacts:config=config/crd/bases +# Generate protobuf code +.PHONY: protos +protos: $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_GRPC) $(PROTOC_GEN_GRPC_GATEWAY) $(PROTOC) + PATH=$$PATH:$(TOOLS_BIN_DIR) $(PROTOC) --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + --grpc-gateway_out=grpc_api_configuration=apis/protos/azure/compute/v1/vmsizerecommender_http.yaml,logtostderr=true:. --grpc-gateway_opt=paths=source_relative,generate_unbound_methods=true \ + apis/protos/azure/compute/v1/vmsizerecommender.proto + # Generate code -generate: $(CONTROLLER_GEN) +generate: $(CONTROLLER_GEN) protos $(CONTROLLER_GEN) \ object:headerFile="hack/boilerplate.go.txt" paths="./..." diff --git a/apis/protos/azure/compute/v1/vmsizerecommender.pb.go b/apis/protos/azure/compute/v1/vmsizerecommender.pb.go new file mode 100644 index 000000000..543b75155 --- /dev/null +++ b/apis/protos/azure/compute/v1/vmsizerecommender.pb.go @@ -0,0 +1,768 @@ +// +//Copyright (c) Microsoft Corporation. +//Licensed under the MIT license. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc v5.28.0 +// source: apis/protos/azure/compute/v1/vmsizerecommender.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Capacity unit types +type CapacityUnitType int32 + +const ( + CapacityUnitType_CAPACITY_UNIT_TYPE_UNSPECIFIED CapacityUnitType = 0 + CapacityUnitType_CAPACITY_UNIT_TYPE_VM_INSTANCE_COUNT CapacityUnitType = 1 +) + +// Enum value maps for CapacityUnitType. +var ( + CapacityUnitType_name = map[int32]string{ + 0: "CAPACITY_UNIT_TYPE_UNSPECIFIED", + 1: "CAPACITY_UNIT_TYPE_VM_INSTANCE_COUNT", + } + CapacityUnitType_value = map[string]int32{ + "CAPACITY_UNIT_TYPE_UNSPECIFIED": 0, + "CAPACITY_UNIT_TYPE_VM_INSTANCE_COUNT": 1, + } +) + +func (x CapacityUnitType) Enum() *CapacityUnitType { + p := new(CapacityUnitType) + *p = x + return p +} + +func (x CapacityUnitType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CapacityUnitType) Descriptor() protoreflect.EnumDescriptor { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_enumTypes[0].Descriptor() +} + +func (CapacityUnitType) Type() protoreflect.EnumType { + return &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_enumTypes[0] +} + +func (x CapacityUnitType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CapacityUnitType.Descriptor instead. +func (CapacityUnitType) EnumDescriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{0} +} + +// Restrictions filter +type RecommendationProperties_RestrictionsFilter int32 + +const ( + RecommendationProperties_RESTRICTIONS_FILTER_UNSPECIFIED RecommendationProperties_RestrictionsFilter = 0 + RecommendationProperties_RESTRICTIONS_FILTER_NONE RecommendationProperties_RestrictionsFilter = 1 + RecommendationProperties_RESTRICTIONS_FILTER_OFFER_RESTRICTIONS RecommendationProperties_RestrictionsFilter = 2 + RecommendationProperties_RESTRICTIONS_FILTER_QUOTA_RESTRICTIONS RecommendationProperties_RestrictionsFilter = 3 + RecommendationProperties_RESTRICTIONS_FILTER_QUOTA_AND_OFFER_RESTRICTIONS RecommendationProperties_RestrictionsFilter = 4 +) + +// Enum value maps for RecommendationProperties_RestrictionsFilter. +var ( + RecommendationProperties_RestrictionsFilter_name = map[int32]string{ + 0: "RESTRICTIONS_FILTER_UNSPECIFIED", + 1: "RESTRICTIONS_FILTER_NONE", + 2: "RESTRICTIONS_FILTER_OFFER_RESTRICTIONS", + 3: "RESTRICTIONS_FILTER_QUOTA_RESTRICTIONS", + 4: "RESTRICTIONS_FILTER_QUOTA_AND_OFFER_RESTRICTIONS", + } + RecommendationProperties_RestrictionsFilter_value = map[string]int32{ + "RESTRICTIONS_FILTER_UNSPECIFIED": 0, + "RESTRICTIONS_FILTER_NONE": 1, + "RESTRICTIONS_FILTER_OFFER_RESTRICTIONS": 2, + "RESTRICTIONS_FILTER_QUOTA_RESTRICTIONS": 3, + "RESTRICTIONS_FILTER_QUOTA_AND_OFFER_RESTRICTIONS": 4, + } +) + +func (x RecommendationProperties_RestrictionsFilter) Enum() *RecommendationProperties_RestrictionsFilter { + p := new(RecommendationProperties_RestrictionsFilter) + *p = x + return p +} + +func (x RecommendationProperties_RestrictionsFilter) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RecommendationProperties_RestrictionsFilter) Descriptor() protoreflect.EnumDescriptor { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_enumTypes[1].Descriptor() +} + +func (RecommendationProperties_RestrictionsFilter) Type() protoreflect.EnumType { + return &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_enumTypes[1] +} + +func (x RecommendationProperties_RestrictionsFilter) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RecommendationProperties_RestrictionsFilter.Descriptor instead. +func (RecommendationProperties_RestrictionsFilter) EnumDescriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{4, 0} +} + +// VM attributes specification +type VMAttributes struct { + state protoimpl.MessageState `protogen:"open.v1"` + // optional + // List of Azure Vm Sizes only to filter based on above attributes provided + AllowedVmSizes []string `protobuf:"bytes,1,rep,name=allowed_vm_sizes,json=allowedVmSizes,proto3" json:"allowed_vm_sizes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VMAttributes) Reset() { + *x = VMAttributes{} + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VMAttributes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VMAttributes) ProtoMessage() {} + +func (x *VMAttributes) ProtoReflect() protoreflect.Message { + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VMAttributes.ProtoReflect.Descriptor instead. +func (*VMAttributes) Descriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{0} +} + +func (x *VMAttributes) GetAllowedVmSizes() []string { + if x != nil { + return x.AllowedVmSizes + } + return nil +} + +// Resource properties +type ResourceProperties struct { + state protoimpl.MessageState `protogen:"open.v1"` + // optional + // Object contains specific requirements for Vm sizes attributes to filter. + VmAttributes *VMAttributes `protobuf:"bytes,1,opt,name=vm_attributes,json=vmAttributes,proto3" json:"vm_attributes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResourceProperties) Reset() { + *x = ResourceProperties{} + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceProperties) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceProperties) ProtoMessage() {} + +func (x *ResourceProperties) ProtoReflect() protoreflect.Message { + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceProperties.ProtoReflect.Descriptor instead. +func (*ResourceProperties) Descriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{1} +} + +func (x *ResourceProperties) GetVmAttributes() *VMAttributes { + if x != nil { + return x.VmAttributes + } + return nil +} + +// Priority profile for regular VMs +type RegularPriorityProfile struct { + state protoimpl.MessageState `protogen:"open.v1"` + // optional + CapacityUnitType CapacityUnitType `protobuf:"varint,1,opt,name=capacity_unit_type,json=capacityUnitType,proto3,enum=azure.compute.v1.CapacityUnitType" json:"capacity_unit_type,omitempty"` + // optional + // Expected values: >0 + TargetCapacity uint32 `protobuf:"varint,2,opt,name=target_capacity,json=targetCapacity,proto3" json:"target_capacity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegularPriorityProfile) Reset() { + *x = RegularPriorityProfile{} + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegularPriorityProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegularPriorityProfile) ProtoMessage() {} + +func (x *RegularPriorityProfile) ProtoReflect() protoreflect.Message { + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegularPriorityProfile.ProtoReflect.Descriptor instead. +func (*RegularPriorityProfile) Descriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{2} +} + +func (x *RegularPriorityProfile) GetCapacityUnitType() CapacityUnitType { + if x != nil { + return x.CapacityUnitType + } + return CapacityUnitType_CAPACITY_UNIT_TYPE_UNSPECIFIED +} + +func (x *RegularPriorityProfile) GetTargetCapacity() uint32 { + if x != nil { + return x.TargetCapacity + } + return 0 +} + +// Priority profile for spot VMs +type SpotPriorityProfile struct { + state protoimpl.MessageState `protogen:"open.v1"` + // optional + CapacityUnitType CapacityUnitType `protobuf:"varint,1,opt,name=capacity_unit_type,json=capacityUnitType,proto3,enum=azure.compute.v1.CapacityUnitType" json:"capacity_unit_type,omitempty"` + // optional + // Note: SDK is using float32 while internal doc is using double. + // maxPricePerVM for spot instance in USD + MaxPricePerVm float32 `protobuf:"fixed32,2,opt,name=max_price_per_vm,json=maxPricePerVm,proto3" json:"max_price_per_vm,omitempty"` + // optional + // Expected values: >0 + TargetCapacity uint32 `protobuf:"varint,3,opt,name=target_capacity,json=targetCapacity,proto3" json:"target_capacity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SpotPriorityProfile) Reset() { + *x = SpotPriorityProfile{} + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SpotPriorityProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SpotPriorityProfile) ProtoMessage() {} + +func (x *SpotPriorityProfile) ProtoReflect() protoreflect.Message { + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SpotPriorityProfile.ProtoReflect.Descriptor instead. +func (*SpotPriorityProfile) Descriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{3} +} + +func (x *SpotPriorityProfile) GetCapacityUnitType() CapacityUnitType { + if x != nil { + return x.CapacityUnitType + } + return CapacityUnitType_CAPACITY_UNIT_TYPE_UNSPECIFIED +} + +func (x *SpotPriorityProfile) GetMaxPricePerVm() float32 { + if x != nil { + return x.MaxPricePerVm + } + return 0 +} + +func (x *SpotPriorityProfile) GetTargetCapacity() uint32 { + if x != nil { + return x.TargetCapacity + } + return 0 +} + +// Recommendation properties +type RecommendationProperties struct { + state protoimpl.MessageState `protogen:"open.v1"` + RestrictionsFilter RecommendationProperties_RestrictionsFilter `protobuf:"varint,1,opt,name=restrictions_filter,json=restrictionsFilter,proto3,enum=azure.compute.v1.RecommendationProperties_RestrictionsFilter" json:"restrictions_filter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RecommendationProperties) Reset() { + *x = RecommendationProperties{} + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecommendationProperties) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecommendationProperties) ProtoMessage() {} + +func (x *RecommendationProperties) ProtoReflect() protoreflect.Message { + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecommendationProperties.ProtoReflect.Descriptor instead. +func (*RecommendationProperties) Descriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{4} +} + +func (x *RecommendationProperties) GetRestrictionsFilter() RecommendationProperties_RestrictionsFilter { + if x != nil { + return x.RestrictionsFilter + } + return RecommendationProperties_RESTRICTIONS_FILTER_UNSPECIFIED +} + +// Properties of a recommended VM size +// Note: it only exposes whatever fields we need for now. +type RecommendedVMSizeProperties struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Family of the VM size + Family string `protobuf:"bytes,1,opt,name=family,proto3" json:"family,omitempty"` + // Name of the VM size + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Size of the VM + Size string `protobuf:"bytes,3,opt,name=size,proto3" json:"size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RecommendedVMSizeProperties) Reset() { + *x = RecommendedVMSizeProperties{} + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecommendedVMSizeProperties) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecommendedVMSizeProperties) ProtoMessage() {} + +func (x *RecommendedVMSizeProperties) ProtoReflect() protoreflect.Message { + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecommendedVMSizeProperties.ProtoReflect.Descriptor instead. +func (*RecommendedVMSizeProperties) Descriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{5} +} + +func (x *RecommendedVMSizeProperties) GetFamily() string { + if x != nil { + return x.Family + } + return "" +} + +func (x *RecommendedVMSizeProperties) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RecommendedVMSizeProperties) GetSize() string { + if x != nil { + return x.Size + } + return "" +} + +// Collection of recommended VM sizes +type RecommendedVMSizes struct { + state protoimpl.MessageState `protogen:"open.v1"` + // List of recommended regular VM sizes + RegularVmSizes []*RecommendedVMSizeProperties `protobuf:"bytes,1,rep,name=regular_vm_sizes,json=regularVmSizes,proto3" json:"regular_vm_sizes,omitempty"` + // List of recommended spot VM sizes + SpotVmSizes []*RecommendedVMSizeProperties `protobuf:"bytes,2,rep,name=spot_vm_sizes,json=spotVmSizes,proto3" json:"spot_vm_sizes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RecommendedVMSizes) Reset() { + *x = RecommendedVMSizes{} + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecommendedVMSizes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecommendedVMSizes) ProtoMessage() {} + +func (x *RecommendedVMSizes) ProtoReflect() protoreflect.Message { + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecommendedVMSizes.ProtoReflect.Descriptor instead. +func (*RecommendedVMSizes) Descriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{6} +} + +func (x *RecommendedVMSizes) GetRegularVmSizes() []*RecommendedVMSizeProperties { + if x != nil { + return x.RegularVmSizes + } + return nil +} + +func (x *RecommendedVMSizes) GetSpotVmSizes() []*RecommendedVMSizeProperties { + if x != nil { + return x.SpotVmSizes + } + return nil +} + +// Request to generate attribute-based VM size recommendations +type GenerateAttributeBasedRecommendationsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // subscription_id - target managed cluster subscription id + SubscriptionId string `protobuf:"bytes,1,opt,name=subscription_id,json=subscriptionId,proto3" json:"subscription_id,omitempty"` + // location - target managed cluster location + Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` + // Object of VMPriorityProfile for regular vms. + RegularPriorityProfile *RegularPriorityProfile `protobuf:"bytes,3,opt,name=regular_priority_profile,json=regularPriorityProfile,proto3" json:"regular_priority_profile,omitempty"` + // Object of VMPriorityProfile for spot vms. + SpotPriorityProfile *SpotPriorityProfile `protobuf:"bytes,4,opt,name=spot_priority_profile,json=spotPriorityProfile,proto3" json:"spot_priority_profile,omitempty"` + // optional + // Object of different recommendationProperties + RecommendationProperties *RecommendationProperties `protobuf:"bytes,5,opt,name=recommendation_properties,json=recommendationProperties,proto3" json:"recommendation_properties,omitempty"` + // Object of resourceProperties + // Note: it only exposes whatever fields are required for now. + ResourceProperties *ResourceProperties `protobuf:"bytes,6,opt,name=resource_properties,json=resourceProperties,proto3" json:"resource_properties,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GenerateAttributeBasedRecommendationsRequest) Reset() { + *x = GenerateAttributeBasedRecommendationsRequest{} + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GenerateAttributeBasedRecommendationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenerateAttributeBasedRecommendationsRequest) ProtoMessage() {} + +func (x *GenerateAttributeBasedRecommendationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenerateAttributeBasedRecommendationsRequest.ProtoReflect.Descriptor instead. +func (*GenerateAttributeBasedRecommendationsRequest) Descriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{7} +} + +func (x *GenerateAttributeBasedRecommendationsRequest) GetSubscriptionId() string { + if x != nil { + return x.SubscriptionId + } + return "" +} + +func (x *GenerateAttributeBasedRecommendationsRequest) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *GenerateAttributeBasedRecommendationsRequest) GetRegularPriorityProfile() *RegularPriorityProfile { + if x != nil { + return x.RegularPriorityProfile + } + return nil +} + +func (x *GenerateAttributeBasedRecommendationsRequest) GetSpotPriorityProfile() *SpotPriorityProfile { + if x != nil { + return x.SpotPriorityProfile + } + return nil +} + +func (x *GenerateAttributeBasedRecommendationsRequest) GetRecommendationProperties() *RecommendationProperties { + if x != nil { + return x.RecommendationProperties + } + return nil +} + +func (x *GenerateAttributeBasedRecommendationsRequest) GetResourceProperties() *ResourceProperties { + if x != nil { + return x.ResourceProperties + } + return nil +} + +// Response from generating attribute-based VM size recommendations +type GenerateAttributeBasedRecommendationsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + RecommendedVmSizes *RecommendedVMSizes `protobuf:"bytes,1,opt,name=recommended_vm_sizes,json=recommendedVmSizes,proto3" json:"recommended_vm_sizes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GenerateAttributeBasedRecommendationsResponse) Reset() { + *x = GenerateAttributeBasedRecommendationsResponse{} + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GenerateAttributeBasedRecommendationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GenerateAttributeBasedRecommendationsResponse) ProtoMessage() {} + +func (x *GenerateAttributeBasedRecommendationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GenerateAttributeBasedRecommendationsResponse.ProtoReflect.Descriptor instead. +func (*GenerateAttributeBasedRecommendationsResponse) Descriptor() ([]byte, []int) { + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP(), []int{8} +} + +func (x *GenerateAttributeBasedRecommendationsResponse) GetRecommendedVmSizes() *RecommendedVMSizes { + if x != nil { + return x.RecommendedVmSizes + } + return nil +} + +var File_apis_protos_azure_compute_v1_vmsizerecommender_proto protoreflect.FileDescriptor + +const file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDesc = "" + + "\n" + + "4apis/protos/azure/compute/v1/vmsizerecommender.proto\x12\x10azure.compute.v1\"8\n" + + "\fVMAttributes\x12(\n" + + "\x10allowed_vm_sizes\x18\x01 \x03(\tR\x0eallowedVmSizes\"Y\n" + + "\x12ResourceProperties\x12C\n" + + "\rvm_attributes\x18\x01 \x01(\v2\x1e.azure.compute.v1.VMAttributesR\fvmAttributes\"\x93\x01\n" + + "\x16RegularPriorityProfile\x12P\n" + + "\x12capacity_unit_type\x18\x01 \x01(\x0e2\".azure.compute.v1.CapacityUnitTypeR\x10capacityUnitType\x12'\n" + + "\x0ftarget_capacity\x18\x02 \x01(\rR\x0etargetCapacity\"\xb9\x01\n" + + "\x13SpotPriorityProfile\x12P\n" + + "\x12capacity_unit_type\x18\x01 \x01(\x0e2\".azure.compute.v1.CapacityUnitTypeR\x10capacityUnitType\x12'\n" + + "\x10max_price_per_vm\x18\x02 \x01(\x02R\rmaxPricePerVm\x12'\n" + + "\x0ftarget_capacity\x18\x03 \x01(\rR\x0etargetCapacity\"\xf2\x02\n" + + "\x18RecommendationProperties\x12n\n" + + "\x13restrictions_filter\x18\x01 \x01(\x0e2=.azure.compute.v1.RecommendationProperties.RestrictionsFilterR\x12restrictionsFilter\"\xe5\x01\n" + + "\x12RestrictionsFilter\x12#\n" + + "\x1fRESTRICTIONS_FILTER_UNSPECIFIED\x10\x00\x12\x1c\n" + + "\x18RESTRICTIONS_FILTER_NONE\x10\x01\x12*\n" + + "&RESTRICTIONS_FILTER_OFFER_RESTRICTIONS\x10\x02\x12*\n" + + "&RESTRICTIONS_FILTER_QUOTA_RESTRICTIONS\x10\x03\x124\n" + + "0RESTRICTIONS_FILTER_QUOTA_AND_OFFER_RESTRICTIONS\x10\x04\"]\n" + + "\x1bRecommendedVMSizeProperties\x12\x16\n" + + "\x06family\x18\x01 \x01(\tR\x06family\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" + + "\x04size\x18\x03 \x01(\tR\x04size\"\xc0\x01\n" + + "\x12RecommendedVMSizes\x12W\n" + + "\x10regular_vm_sizes\x18\x01 \x03(\v2-.azure.compute.v1.RecommendedVMSizePropertiesR\x0eregularVmSizes\x12Q\n" + + "\rspot_vm_sizes\x18\x02 \x03(\v2-.azure.compute.v1.RecommendedVMSizePropertiesR\vspotVmSizes\"\xf2\x03\n" + + ",GenerateAttributeBasedRecommendationsRequest\x12'\n" + + "\x0fsubscription_id\x18\x01 \x01(\tR\x0esubscriptionId\x12\x1a\n" + + "\blocation\x18\x02 \x01(\tR\blocation\x12b\n" + + "\x18regular_priority_profile\x18\x03 \x01(\v2(.azure.compute.v1.RegularPriorityProfileR\x16regularPriorityProfile\x12Y\n" + + "\x15spot_priority_profile\x18\x04 \x01(\v2%.azure.compute.v1.SpotPriorityProfileR\x13spotPriorityProfile\x12g\n" + + "\x19recommendation_properties\x18\x05 \x01(\v2*.azure.compute.v1.RecommendationPropertiesR\x18recommendationProperties\x12U\n" + + "\x13resource_properties\x18\x06 \x01(\v2$.azure.compute.v1.ResourcePropertiesR\x12resourceProperties\"\x87\x01\n" + + "-GenerateAttributeBasedRecommendationsResponse\x12V\n" + + "\x14recommended_vm_sizes\x18\x01 \x01(\v2$.azure.compute.v1.RecommendedVMSizesR\x12recommendedVmSizes*`\n" + + "\x10CapacityUnitType\x12\"\n" + + "\x1eCAPACITY_UNIT_TYPE_UNSPECIFIED\x10\x00\x12(\n" + + "$CAPACITY_UNIT_TYPE_VM_INSTANCE_COUNT\x10\x012\xd3\x01\n" + + "&AttributeBasedVMSizeRecommenderService\x12\xa8\x01\n" + + "%GenerateAttributeBasedRecommendations\x12>.azure.compute.v1.GenerateAttributeBasedRecommendationsRequest\x1a?.azure.compute.v1.GenerateAttributeBasedRecommendationsResponseB.Z,go.goms.io/fleet/pkg/protos/azure/compute/v1b\x06proto3" + +var ( + file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescOnce sync.Once + file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescData []byte +) + +func file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescGZIP() []byte { + file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescOnce.Do(func() { + file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDesc), len(file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDesc))) + }) + return file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDescData +} + +var file_apis_protos_azure_compute_v1_vmsizerecommender_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_apis_protos_azure_compute_v1_vmsizerecommender_proto_goTypes = []any{ + (CapacityUnitType)(0), // 0: azure.compute.v1.CapacityUnitType + (RecommendationProperties_RestrictionsFilter)(0), // 1: azure.compute.v1.RecommendationProperties.RestrictionsFilter + (*VMAttributes)(nil), // 2: azure.compute.v1.VMAttributes + (*ResourceProperties)(nil), // 3: azure.compute.v1.ResourceProperties + (*RegularPriorityProfile)(nil), // 4: azure.compute.v1.RegularPriorityProfile + (*SpotPriorityProfile)(nil), // 5: azure.compute.v1.SpotPriorityProfile + (*RecommendationProperties)(nil), // 6: azure.compute.v1.RecommendationProperties + (*RecommendedVMSizeProperties)(nil), // 7: azure.compute.v1.RecommendedVMSizeProperties + (*RecommendedVMSizes)(nil), // 8: azure.compute.v1.RecommendedVMSizes + (*GenerateAttributeBasedRecommendationsRequest)(nil), // 9: azure.compute.v1.GenerateAttributeBasedRecommendationsRequest + (*GenerateAttributeBasedRecommendationsResponse)(nil), // 10: azure.compute.v1.GenerateAttributeBasedRecommendationsResponse +} +var file_apis_protos_azure_compute_v1_vmsizerecommender_proto_depIdxs = []int32{ + 2, // 0: azure.compute.v1.ResourceProperties.vm_attributes:type_name -> azure.compute.v1.VMAttributes + 0, // 1: azure.compute.v1.RegularPriorityProfile.capacity_unit_type:type_name -> azure.compute.v1.CapacityUnitType + 0, // 2: azure.compute.v1.SpotPriorityProfile.capacity_unit_type:type_name -> azure.compute.v1.CapacityUnitType + 1, // 3: azure.compute.v1.RecommendationProperties.restrictions_filter:type_name -> azure.compute.v1.RecommendationProperties.RestrictionsFilter + 7, // 4: azure.compute.v1.RecommendedVMSizes.regular_vm_sizes:type_name -> azure.compute.v1.RecommendedVMSizeProperties + 7, // 5: azure.compute.v1.RecommendedVMSizes.spot_vm_sizes:type_name -> azure.compute.v1.RecommendedVMSizeProperties + 4, // 6: azure.compute.v1.GenerateAttributeBasedRecommendationsRequest.regular_priority_profile:type_name -> azure.compute.v1.RegularPriorityProfile + 5, // 7: azure.compute.v1.GenerateAttributeBasedRecommendationsRequest.spot_priority_profile:type_name -> azure.compute.v1.SpotPriorityProfile + 6, // 8: azure.compute.v1.GenerateAttributeBasedRecommendationsRequest.recommendation_properties:type_name -> azure.compute.v1.RecommendationProperties + 3, // 9: azure.compute.v1.GenerateAttributeBasedRecommendationsRequest.resource_properties:type_name -> azure.compute.v1.ResourceProperties + 8, // 10: azure.compute.v1.GenerateAttributeBasedRecommendationsResponse.recommended_vm_sizes:type_name -> azure.compute.v1.RecommendedVMSizes + 9, // 11: azure.compute.v1.AttributeBasedVMSizeRecommenderService.GenerateAttributeBasedRecommendations:input_type -> azure.compute.v1.GenerateAttributeBasedRecommendationsRequest + 10, // 12: azure.compute.v1.AttributeBasedVMSizeRecommenderService.GenerateAttributeBasedRecommendations:output_type -> azure.compute.v1.GenerateAttributeBasedRecommendationsResponse + 12, // [12:13] is the sub-list for method output_type + 11, // [11:12] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_apis_protos_azure_compute_v1_vmsizerecommender_proto_init() } +func file_apis_protos_azure_compute_v1_vmsizerecommender_proto_init() { + if File_apis_protos_azure_compute_v1_vmsizerecommender_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDesc), len(file_apis_protos_azure_compute_v1_vmsizerecommender_proto_rawDesc)), + NumEnums: 2, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_apis_protos_azure_compute_v1_vmsizerecommender_proto_goTypes, + DependencyIndexes: file_apis_protos_azure_compute_v1_vmsizerecommender_proto_depIdxs, + EnumInfos: file_apis_protos_azure_compute_v1_vmsizerecommender_proto_enumTypes, + MessageInfos: file_apis_protos_azure_compute_v1_vmsizerecommender_proto_msgTypes, + }.Build() + File_apis_protos_azure_compute_v1_vmsizerecommender_proto = out.File + file_apis_protos_azure_compute_v1_vmsizerecommender_proto_goTypes = nil + file_apis_protos_azure_compute_v1_vmsizerecommender_proto_depIdxs = nil +} diff --git a/apis/protos/azure/compute/v1/vmsizerecommender.pb.gw.go b/apis/protos/azure/compute/v1/vmsizerecommender.pb.gw.go new file mode 100644 index 000000000..99bcbd9bc --- /dev/null +++ b/apis/protos/azure/compute/v1/vmsizerecommender.pb.gw.go @@ -0,0 +1,191 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: apis/protos/azure/compute/v1/vmsizerecommender.proto + +/* +Package v1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package v1 + +import ( + "context" + "errors" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var ( + _ codes.Code + _ io.Reader + _ status.Status + _ = errors.New + _ = runtime.String + _ = utilities.NewDoubleArray + _ = metadata.Join +) + +func request_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0(ctx context.Context, marshaler runtime.Marshaler, client AttributeBasedVMSizeRecommenderServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GenerateAttributeBasedRecommendationsRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["subscription_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subscription_id") + } + protoReq.SubscriptionId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subscription_id", err) + } + val, ok = pathParams["location"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "location") + } + protoReq.Location, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "location", err) + } + msg, err := client.GenerateAttributeBasedRecommendations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0(ctx context.Context, marshaler runtime.Marshaler, server AttributeBasedVMSizeRecommenderServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GenerateAttributeBasedRecommendationsRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["subscription_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "subscription_id") + } + protoReq.SubscriptionId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "subscription_id", err) + } + val, ok = pathParams["location"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "location") + } + protoReq.Location, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "location", err) + } + msg, err := server.GenerateAttributeBasedRecommendations(ctx, &protoReq) + return msg, metadata, err +} + +// RegisterAttributeBasedVMSizeRecommenderServiceHandlerServer registers the http handlers for service AttributeBasedVMSizeRecommenderService to "mux". +// UnaryRPC :call AttributeBasedVMSizeRecommenderServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAttributeBasedVMSizeRecommenderServiceHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterAttributeBasedVMSizeRecommenderServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AttributeBasedVMSizeRecommenderServiceServer) error { + mux.Handle(http.MethodPost, pattern_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/azure.compute.v1.AttributeBasedVMSizeRecommenderService/GenerateAttributeBasedRecommendations", runtime.WithHTTPPathPattern("/subscriptions/{subscription_id}/providers/Microsoft.Compute/locations/{location}/vmSizeRecommendations/vmAttributeBased/generate")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + + return nil +} + +// RegisterAttributeBasedVMSizeRecommenderServiceHandlerFromEndpoint is same as RegisterAttributeBasedVMSizeRecommenderServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterAttributeBasedVMSizeRecommenderServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + return RegisterAttributeBasedVMSizeRecommenderServiceHandler(ctx, mux, conn) +} + +// RegisterAttributeBasedVMSizeRecommenderServiceHandler registers the http handlers for service AttributeBasedVMSizeRecommenderService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterAttributeBasedVMSizeRecommenderServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterAttributeBasedVMSizeRecommenderServiceHandlerClient(ctx, mux, NewAttributeBasedVMSizeRecommenderServiceClient(conn)) +} + +// RegisterAttributeBasedVMSizeRecommenderServiceHandlerClient registers the http handlers for service AttributeBasedVMSizeRecommenderService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AttributeBasedVMSizeRecommenderServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AttributeBasedVMSizeRecommenderServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "AttributeBasedVMSizeRecommenderServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterAttributeBasedVMSizeRecommenderServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AttributeBasedVMSizeRecommenderServiceClient) error { + mux.Handle(http.MethodPost, pattern_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/azure.compute.v1.AttributeBasedVMSizeRecommenderService/GenerateAttributeBasedRecommendations", runtime.WithHTTPPathPattern("/subscriptions/{subscription_id}/providers/Microsoft.Compute/locations/{location}/vmSizeRecommendations/vmAttributeBased/generate")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + return nil +} + +var ( + pattern_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 2, 7, 2, 8}, []string{"subscriptions", "subscription_id", "providers", "Microsoft.Compute", "locations", "location", "vmSizeRecommendations", "vmAttributeBased", "generate"}, "")) +) + +var ( + forward_AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_0 = runtime.ForwardResponseMessage +) diff --git a/apis/protos/azure/compute/v1/vmsizerecommender.proto b/apis/protos/azure/compute/v1/vmsizerecommender.proto new file mode 100644 index 000000000..f515ba897 --- /dev/null +++ b/apis/protos/azure/compute/v1/vmsizerecommender.proto @@ -0,0 +1,114 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +syntax = "proto3"; + +package azure.compute.v1; + +option go_package = "go.goms.io/fleet/pkg/protos/azure/compute/v1"; + +// Capacity unit types +enum CapacityUnitType { + CAPACITY_UNIT_TYPE_UNSPECIFIED = 0; + CAPACITY_UNIT_TYPE_VM_INSTANCE_COUNT = 1; +} + +// VM attributes specification +message VMAttributes { + // optional + // List of Azure Vm Sizes only to filter based on above attributes provided + repeated string allowed_vm_sizes = 1; +} + +// Resource properties +message ResourceProperties { + // optional + // Object contains specific requirements for Vm sizes attributes to filter. + VMAttributes vm_attributes = 1; +} + +// Priority profile for regular VMs +message RegularPriorityProfile { + // optional + CapacityUnitType capacity_unit_type = 1; + // optional + // Expected values: >0 + uint32 target_capacity = 2; +} + +// Priority profile for spot VMs +message SpotPriorityProfile { + // optional + CapacityUnitType capacity_unit_type = 1; + // optional + // Note: SDK is using float32 while internal doc is using double. + // maxPricePerVM for spot instance in USD + float max_price_per_vm = 2; + // optional + // Expected values: >0 + uint32 target_capacity = 3; +} + +// Recommendation properties +message RecommendationProperties { + // Restrictions filter + enum RestrictionsFilter { + RESTRICTIONS_FILTER_UNSPECIFIED = 0; + RESTRICTIONS_FILTER_NONE = 1; + RESTRICTIONS_FILTER_OFFER_RESTRICTIONS = 2; + RESTRICTIONS_FILTER_QUOTA_RESTRICTIONS = 3; + RESTRICTIONS_FILTER_QUOTA_AND_OFFER_RESTRICTIONS = 4; + } + + RestrictionsFilter restrictions_filter = 1; +} + +// Properties of a recommended VM size +// Note: it only exposes whatever fields we need for now. +message RecommendedVMSizeProperties { + // Family of the VM size + string family = 1; + // Name of the VM size + string name = 2; + // Size of the VM + string size = 3; +} + +// Collection of recommended VM sizes +message RecommendedVMSizes { + // List of recommended regular VM sizes + repeated RecommendedVMSizeProperties regular_vm_sizes = 1; + // List of recommended spot VM sizes + repeated RecommendedVMSizeProperties spot_vm_sizes = 2; +} + +// Request to generate attribute-based VM size recommendations +message GenerateAttributeBasedRecommendationsRequest { + // subscription_id - target managed cluster subscription id + string subscription_id = 1; + // location - target managed cluster location + string location = 2; + // Object of VMPriorityProfile for regular vms. + RegularPriorityProfile regular_priority_profile = 3; + // Object of VMPriorityProfile for spot vms. + SpotPriorityProfile spot_priority_profile = 4; + // optional + // Object of different recommendationProperties + RecommendationProperties recommendation_properties = 5; + // Object of resourceProperties + // Note: it only exposes whatever fields are required for now. + ResourceProperties resource_properties = 6; +} + +// Response from generating attribute-based VM size recommendations +message GenerateAttributeBasedRecommendationsResponse { + RecommendedVMSizes recommended_vm_sizes = 1; +} + +// Service for generating attribute-based VM size recommendations +service AttributeBasedVMSizeRecommenderService { + // Generate VM size recommendations based on specified attributes and requirements. + rpc GenerateAttributeBasedRecommendations(GenerateAttributeBasedRecommendationsRequest) returns (GenerateAttributeBasedRecommendationsResponse); +} diff --git a/apis/protos/azure/compute/v1/vmsizerecommender_grpc.pb.go b/apis/protos/azure/compute/v1/vmsizerecommender_grpc.pb.go new file mode 100644 index 000000000..9d00f1924 --- /dev/null +++ b/apis/protos/azure/compute/v1/vmsizerecommender_grpc.pb.go @@ -0,0 +1,132 @@ +// +//Copyright (c) Microsoft Corporation. +//Licensed under the MIT license. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.28.0 +// source: apis/protos/azure/compute/v1/vmsizerecommender.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_FullMethodName = "/azure.compute.v1.AttributeBasedVMSizeRecommenderService/GenerateAttributeBasedRecommendations" +) + +// AttributeBasedVMSizeRecommenderServiceClient is the client API for AttributeBasedVMSizeRecommenderService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// Service for generating attribute-based VM size recommendations +type AttributeBasedVMSizeRecommenderServiceClient interface { + // Generate VM size recommendations based on specified attributes and requirements. + GenerateAttributeBasedRecommendations(ctx context.Context, in *GenerateAttributeBasedRecommendationsRequest, opts ...grpc.CallOption) (*GenerateAttributeBasedRecommendationsResponse, error) +} + +type attributeBasedVMSizeRecommenderServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAttributeBasedVMSizeRecommenderServiceClient(cc grpc.ClientConnInterface) AttributeBasedVMSizeRecommenderServiceClient { + return &attributeBasedVMSizeRecommenderServiceClient{cc} +} + +func (c *attributeBasedVMSizeRecommenderServiceClient) GenerateAttributeBasedRecommendations(ctx context.Context, in *GenerateAttributeBasedRecommendationsRequest, opts ...grpc.CallOption) (*GenerateAttributeBasedRecommendationsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GenerateAttributeBasedRecommendationsResponse) + err := c.cc.Invoke(ctx, AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AttributeBasedVMSizeRecommenderServiceServer is the server API for AttributeBasedVMSizeRecommenderService service. +// All implementations must embed UnimplementedAttributeBasedVMSizeRecommenderServiceServer +// for forward compatibility. +// +// Service for generating attribute-based VM size recommendations +type AttributeBasedVMSizeRecommenderServiceServer interface { + // Generate VM size recommendations based on specified attributes and requirements. + GenerateAttributeBasedRecommendations(context.Context, *GenerateAttributeBasedRecommendationsRequest) (*GenerateAttributeBasedRecommendationsResponse, error) + mustEmbedUnimplementedAttributeBasedVMSizeRecommenderServiceServer() +} + +// UnimplementedAttributeBasedVMSizeRecommenderServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAttributeBasedVMSizeRecommenderServiceServer struct{} + +func (UnimplementedAttributeBasedVMSizeRecommenderServiceServer) GenerateAttributeBasedRecommendations(context.Context, *GenerateAttributeBasedRecommendationsRequest) (*GenerateAttributeBasedRecommendationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GenerateAttributeBasedRecommendations not implemented") +} +func (UnimplementedAttributeBasedVMSizeRecommenderServiceServer) mustEmbedUnimplementedAttributeBasedVMSizeRecommenderServiceServer() { +} +func (UnimplementedAttributeBasedVMSizeRecommenderServiceServer) testEmbeddedByValue() {} + +// UnsafeAttributeBasedVMSizeRecommenderServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AttributeBasedVMSizeRecommenderServiceServer will +// result in compilation errors. +type UnsafeAttributeBasedVMSizeRecommenderServiceServer interface { + mustEmbedUnimplementedAttributeBasedVMSizeRecommenderServiceServer() +} + +func RegisterAttributeBasedVMSizeRecommenderServiceServer(s grpc.ServiceRegistrar, srv AttributeBasedVMSizeRecommenderServiceServer) { + // If the following call pancis, it indicates UnimplementedAttributeBasedVMSizeRecommenderServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AttributeBasedVMSizeRecommenderService_ServiceDesc, srv) +} + +func _AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenerateAttributeBasedRecommendationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AttributeBasedVMSizeRecommenderServiceServer).GenerateAttributeBasedRecommendations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AttributeBasedVMSizeRecommenderServiceServer).GenerateAttributeBasedRecommendations(ctx, req.(*GenerateAttributeBasedRecommendationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AttributeBasedVMSizeRecommenderService_ServiceDesc is the grpc.ServiceDesc for AttributeBasedVMSizeRecommenderService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AttributeBasedVMSizeRecommenderService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "azure.compute.v1.AttributeBasedVMSizeRecommenderService", + HandlerType: (*AttributeBasedVMSizeRecommenderServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GenerateAttributeBasedRecommendations", + Handler: _AttributeBasedVMSizeRecommenderService_GenerateAttributeBasedRecommendations_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "apis/protos/azure/compute/v1/vmsizerecommender.proto", +} diff --git a/apis/protos/azure/compute/v1/vmsizerecommender_http.yaml b/apis/protos/azure/compute/v1/vmsizerecommender_http.yaml new file mode 100644 index 000000000..71c83e414 --- /dev/null +++ b/apis/protos/azure/compute/v1/vmsizerecommender_http.yaml @@ -0,0 +1,8 @@ +type: google.api.Service +config_version: 3 + +http: + rules: + - selector: azure.compute.v1.AttributeBasedVMSizeRecommenderService.GenerateAttributeBasedRecommendations + post: "/subscriptions/{subscription_id}/providers/Microsoft.Compute/locations/{location}/vmSizeRecommendations/vmAttributeBased/generate" + body: "*" diff --git a/go.mod b/go.mod index b441c34ef..41d7e276c 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 github.com/go-logr/logr v1.4.3 github.com/google/go-cmp v0.7.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/onsi/ginkgo/v2 v2.23.4 github.com/onsi/gomega v1.37.0 github.com/prometheus/client_golang v1.22.0 @@ -26,6 +27,8 @@ require ( golang.org/x/sync v0.15.0 golang.org/x/time v0.11.0 gomodules.xyz/jsonpatch/v2 v2.4.0 + google.golang.org/grpc v1.70.0 + google.golang.org/protobuf v1.36.6 k8s.io/api v0.32.3 k8s.io/apiextensions-apiserver v0.32.3 k8s.io/apimachinery v0.32.3 @@ -112,7 +115,8 @@ require ( golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.25.0 // indirect golang.org/x/tools v0.31.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 711e9a79b..7c2428d0b 100644 --- a/go.sum +++ b/go.sum @@ -179,6 +179,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jongio/azidext/go/azidext v0.5.0 h1:uPInXD4NZ3J0k79FPwIA0YXknFn+WcqZqSgs3/jPgvQ= @@ -358,6 +360,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= +google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= diff --git a/pkg/clients/azure/compute/vmsizerecommenderclient.go b/pkg/clients/azure/compute/vmsizerecommenderclient.go new file mode 100644 index 000000000..a34a100d0 --- /dev/null +++ b/pkg/clients/azure/compute/vmsizerecommenderclient.go @@ -0,0 +1,128 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +// Package compute provides clients for interacting with Azure Compute services. +package compute + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "google.golang.org/protobuf/encoding/protojson" + + computev1 "go.goms.io/fleet/apis/protos/azure/compute/v1" + "go.goms.io/fleet/pkg/clients/httputil" + "go.goms.io/fleet/pkg/utils/controller" +) + +const ( + // recommendationsPathTemplate is the URL path template for VM size recommendations API. + recommendationsPathTemplate = "/subscriptions/%s/providers/Microsoft.Compute/locations/%s/vmSizeRecommendations/vmAttributeBased/generate" +) + +// AttributeBasedVMSizeRecommenderClient accesses Azure Attribute-Based VM Size Recommender API +// to provide VM size recommendations based on specified attributes. +type AttributeBasedVMSizeRecommenderClient struct { + // baseURL is the base URL of the http(s) requests to the attribute-based VM size recommender service endpoint. + baseURL string + // httpClient is the HTTP client used for making requests. + httpClient *http.Client +} + +// NewAttributeBasedVMSizeRecommenderClient creates a new AttributeBasedVMSizeRecommenderClient. +// The serverAddress is the remote Azure Attribute-Based VM Size Recommender service endpoint. +// Both serverAddress and httpClient must be provided. +func NewAttributeBasedVMSizeRecommenderClient( + serverAddress string, + httpClient *http.Client, +) (*AttributeBasedVMSizeRecommenderClient, error) { + if len(serverAddress) == 0 { + return nil, fmt.Errorf("serverAddress cannot be empty") + } + if httpClient == nil { + return nil, fmt.Errorf("httpClient cannot be nil") + } + return &AttributeBasedVMSizeRecommenderClient{ + baseURL: serverAddress, + httpClient: httpClient, + }, nil +} + +// GenerateAttributeBasedRecommendations generates VM size recommendations based on attributes. +func (c *AttributeBasedVMSizeRecommenderClient) GenerateAttributeBasedRecommendations( + ctx context.Context, + req *computev1.GenerateAttributeBasedRecommendationsRequest, +) (*computev1.GenerateAttributeBasedRecommendationsResponse, error) { + if req == nil { + return nil, controller.NewUnexpectedBehaviorError(errors.New("request cannot be nil")) + } + if req.SubscriptionId == "" { + return nil, controller.NewUnexpectedBehaviorError(errors.New("subscription ID is required")) + } + if req.Location == "" { + return nil, controller.NewUnexpectedBehaviorError(errors.New("location is required")) + } + if req.GetRegularPriorityProfile() == nil && req.GetSpotPriorityProfile() == nil { + return nil, controller.NewUnexpectedBehaviorError(errors.New("either regular priority profile or spot priority profile must be provided")) + } + + // Build the URL + path := fmt.Sprintf(recommendationsPathTemplate, req.SubscriptionId, req.Location) + url := c.baseURL + path + + // Marshal request body using protojson for proper proto3 oneof support + marshaler := protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: false, + } + body, err := marshaler.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + // Create HTTP request + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + + // Set headers + httpReq.Header.Set(httputil.HeaderContentTypeKey, httputil.HeaderContentTypeJSON) + httpReq.Header.Set(httputil.HeaderAcceptKey, httputil.HeaderContentTypeJSON) + + // Execute the request + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("failed to execute request: %w", err) + } + defer resp.Body.Close() + + // Read response body + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + // Check status code + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("request failed with status %d: %w", resp.StatusCode, runtime.NewResponseError(resp)) + } + + // Unmarshal response using protojson for proper proto3 support + var response computev1.GenerateAttributeBasedRecommendationsResponse + unmarshaler := protojson.UnmarshalOptions{ + DiscardUnknown: true, + } + if err := unmarshaler.Unmarshal(respBody, &response); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + return &response, nil +} diff --git a/pkg/clients/azure/compute/vmsizerecommenderclient_test.go b/pkg/clients/azure/compute/vmsizerecommenderclient_test.go new file mode 100644 index 000000000..f61c5b275 --- /dev/null +++ b/pkg/clients/azure/compute/vmsizerecommenderclient_test.go @@ -0,0 +1,280 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +package compute + +import ( + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + + computev1 "go.goms.io/fleet/apis/protos/azure/compute/v1" +) + +func TestNewAttributeBasedVMSizeRecommenderClient(t *testing.T) { + tests := []struct { + name string + serverAddress string + httpClient *http.Client + wantClient *AttributeBasedVMSizeRecommenderClient + wantErr bool + }{ + { + name: "with empty server address", + serverAddress: "", + httpClient: http.DefaultClient, + wantClient: nil, + wantErr: true, + }, + { + name: "with nil HTTP client", + serverAddress: "http://localhost:8080", + httpClient: nil, + wantClient: nil, + wantErr: true, + }, + { + name: "with both server address and HTTP client", + serverAddress: "https://example.com", + httpClient: http.DefaultClient, + wantClient: &AttributeBasedVMSizeRecommenderClient{ + baseURL: "https://example.com", + httpClient: http.DefaultClient, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, gotErr := NewAttributeBasedVMSizeRecommenderClient(tt.serverAddress, tt.httpClient) + if (gotErr != nil) != tt.wantErr { + t.Errorf("NewAttributeBasedVMSizeRecommenderClient() error = %v, wantErr %v", gotErr, tt.wantErr) + } + if diff := cmp.Diff(tt.wantClient, got, + cmp.AllowUnexported(AttributeBasedVMSizeRecommenderClient{})); diff != "" { + t.Errorf("NewAttributeBasedVMSizeRecommenderClient() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestClient_GenerateAttributeBasedRecommendations(t *testing.T) { + tests := []struct { + name string + request *computev1.GenerateAttributeBasedRecommendationsRequest + mockStatusCode int + mockResponse string + wantResponse *computev1.GenerateAttributeBasedRecommendationsResponse + wantErr bool + wantErrMsg string + }{ + { + name: "successful request with regular priority profile", + request: &computev1.GenerateAttributeBasedRecommendationsRequest{ + SubscriptionId: "sub-123", + Location: "eastus", + RegularPriorityProfile: &computev1.RegularPriorityProfile{ + TargetCapacity: 5, + }, + ResourceProperties: &computev1.ResourceProperties{}, + }, + mockStatusCode: http.StatusOK, + mockResponse: `{"recommended_vm_sizes":{"regular_vm_sizes": [{"name":"Standard_D2s_v3"}]}}`, + wantResponse: &computev1.GenerateAttributeBasedRecommendationsResponse{ + RecommendedVmSizes: &computev1.RecommendedVMSizes{ + RegularVmSizes: []*computev1.RecommendedVMSizeProperties{ + {Name: "Standard_D2s_v3"}, + }, + }, + }, + wantErr: false, + }, + { + name: "successful request with spot priority profile", + request: &computev1.GenerateAttributeBasedRecommendationsRequest{ + SubscriptionId: "sub-123", + Location: "westus2", + RegularPriorityProfile: &computev1.RegularPriorityProfile{ + TargetCapacity: 5, + }, + ResourceProperties: &computev1.ResourceProperties{}, + }, + mockStatusCode: http.StatusOK, + mockResponse: `{"recommended_vm_sizes":{"spot_vm_sizes": [{"name":"Standard_D4s_v3"}]}}`, + wantResponse: &computev1.GenerateAttributeBasedRecommendationsResponse{ + RecommendedVmSizes: &computev1.RecommendedVMSizes{ + SpotVmSizes: []*computev1.RecommendedVMSizeProperties{ + {Name: "Standard_D4s_v3"}, + }, + }, + }, + wantErr: false, + }, + { + name: "nil request", + request: nil, + wantErr: true, + wantErrMsg: "request cannot be nil", + }, + { + name: "missing subscription ID", + request: &computev1.GenerateAttributeBasedRecommendationsRequest{ + Location: "eastus", + RegularPriorityProfile: &computev1.RegularPriorityProfile{ + TargetCapacity: 5, + }, + }, + wantErr: true, + wantErrMsg: "subscription ID is required", + }, + { + name: "missing location", + request: &computev1.GenerateAttributeBasedRecommendationsRequest{ + SubscriptionId: "sub-123", + RegularPriorityProfile: &computev1.RegularPriorityProfile{ + TargetCapacity: 5, + }, + }, + wantErr: true, + wantErrMsg: "location is required", + }, + { + name: "missing both priority profiles", + request: &computev1.GenerateAttributeBasedRecommendationsRequest{ + SubscriptionId: "sub-123", + Location: "eastus", + }, + wantErr: true, + wantErrMsg: "either regular priority profile or spot priority profile must be provided", + }, + { + name: "HTTP 400 error", + request: &computev1.GenerateAttributeBasedRecommendationsRequest{ + SubscriptionId: "sub-123", + Location: "eastus", + RegularPriorityProfile: &computev1.RegularPriorityProfile{ + TargetCapacity: 5, + }, + }, + mockStatusCode: http.StatusBadRequest, + mockResponse: `{"error":"invalid request"}`, + wantErr: true, + wantErrMsg: "request failed with status 400", + }, + { + name: "HTTP 500 error", + request: &computev1.GenerateAttributeBasedRecommendationsRequest{ + SubscriptionId: "sub-123", + Location: "eastus", + RegularPriorityProfile: &computev1.RegularPriorityProfile{ + TargetCapacity: 5, + }, + }, + mockStatusCode: http.StatusInternalServerError, + mockResponse: `{"error":"internal server error"}`, + wantErr: true, + wantErrMsg: "request failed with status 500", + }, + { + name: "invalid JSON response", + request: &computev1.GenerateAttributeBasedRecommendationsRequest{ + SubscriptionId: "sub-123", + Location: "eastus", + RegularPriorityProfile: &computev1.RegularPriorityProfile{ + TargetCapacity: 5, + }, + }, + mockStatusCode: http.StatusOK, + mockResponse: `invalid json`, + wantErr: true, + wantErrMsg: "failed to unmarshal response", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create mock server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify request method + if r.Method != http.MethodPost { + t.Errorf("got %s, want POST request", r.Method) + } + + // Verify headers + if r.Header.Get("Content-Type") != "application/json" { + t.Errorf("got %s, want Content-Type: application/json", r.Header.Get("Content-Type")) + } + if r.Header.Get("Accept") != "application/json" { + t.Errorf("got %s, want Accept: application/json", r.Header.Get("Accept")) + } + + // Verify URL path if request is not nil + if tt.request != nil && tt.request.SubscriptionId != "" && tt.request.Location != "" { + wantPath := fmt.Sprintf(recommendationsPathTemplate, tt.request.SubscriptionId, tt.request.Location) + if r.URL.Path != wantPath { + t.Errorf("got %s, want path %s", r.URL.Path, wantPath) + } + + // Verify request body using protojson for proper proto3 oneof support + body, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("failed to read request body: %v", err) + } + var req computev1.GenerateAttributeBasedRecommendationsRequest + unmarshaler := protojson.UnmarshalOptions{ + DiscardUnknown: true, + } + if err := unmarshaler.Unmarshal(body, &req); err != nil { + t.Fatalf("failed to unmarshal request body: %v", err) + } + if !proto.Equal(tt.request, &req) { + t.Errorf("request body mismatch: got %+v, want %+v", &req, tt.request) + } + } + + // Write mock response + w.WriteHeader(tt.mockStatusCode) + if _, err := w.Write([]byte(tt.mockResponse)); err != nil { + t.Fatalf("failed to write response: %v", err) + } + })) + defer server.Close() + + // Create client + client, err := NewAttributeBasedVMSizeRecommenderClient(server.URL, http.DefaultClient) + if err != nil { + t.Errorf("failed to create client: %v", err) + } + + // Execute request + got, err := client.GenerateAttributeBasedRecommendations(context.Background(), tt.request) + + // Check error + if (err != nil) != tt.wantErr { + t.Errorf("GenerateAttributeBasedRecommendations() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.wantErr && !strings.Contains(err.Error(), tt.wantErrMsg) { + t.Errorf("GenerateAttributeBasedRecommendations() error = %v, want error containing %q", err, tt.wantErrMsg) + return + } + + // Compare response + if !proto.Equal(tt.wantResponse, got) { + t.Errorf("GenerateAttributeBasedRecommendations() = %+v, want %+v", got, tt.wantResponse) + } + }) + } +} diff --git a/pkg/clients/httputil/httputil.go b/pkg/clients/httputil/httputil.go new file mode 100644 index 000000000..1ddc4b95e --- /dev/null +++ b/pkg/clients/httputil/httputil.go @@ -0,0 +1,32 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +// Package httputil provides common utilities for HTTP clients. +package httputil + +import ( + "net/http" + "time" +) + +// Common HTTP constants. +const ( + // HTTPTimeoutAzure is the timeout for HTTP requests to Azure services. + // Setting to 60 seconds, following ARM client request timeout conventions: + // https://github.com/Azure/azure-resource-manager-rpc/blob/master/v1.0/common-api-details.md#client-request-timeout. + HTTPTimeoutAzure = 60 * time.Second + + // HeaderContentTypeKey is the HTTP header key for Content-Type. + HeaderContentTypeKey = "Content-Type" + // HeaderAcceptKey is the HTTP header key for Accept. + HeaderAcceptKey = "Accept" + // HeaderContentTypeJSON is the Content-Type header value for JSON payloads. + HeaderContentTypeJSON = "application/json" +) + +var ( + // DefaultClientForAzure is the default HTTP client to access Azure services. + DefaultClientForAzure = &http.Client{Timeout: HTTPTimeoutAzure} +)