Skip to content
Merged
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ conf
container-volume
./bin/**

config.yaml
config.yaml

# SSH keys (test artifacts)
id_rsa
id_rsa.pub
*.pem
2 changes: 1 addition & 1 deletion internal/core/cost/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (r *CostRepository) GetMatchingEstimateCostTx(ctx context.Context, param Re
err := r.execInTransaction(ctx, func(d *gorm.DB) error {
q := d.Model(&EstimateCostInfo{}).
Where(
"LOWER(provider_name) = ? AND LOWER(region_name) = ? AND instance_type = ? AND price_policy = ? AND last_updated_at >= ?",
"LOWER(provider_name) = ? AND LOWER(region_name) = ? AND LOWER(instance_type) = ? AND price_policy = ? AND last_updated_at >= ?",
strings.ToLower(param.ProviderName),
strings.ToLower(param.RegionName),
strings.ToLower(param.InstanceType),
Expand Down
6 changes: 3 additions & 3 deletions internal/core/cost/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (c *CostService) UpdateAndGetEstimateCost(param UpdateAndGetEstimateCostPar

var err error
var estimateCostInfos EstimateCostInfos
possibleFetch := true
possibleIbmOrAzureFetch := false

if p.ProviderName == "ibm" || p.ProviderName == "azure" {
r, err := c.costRepo.GetMatchingEstimateCostWithoutTypeTx(ctx, v, param.TimeStandard, param.PricePolicy)
Expand Down Expand Up @@ -124,7 +124,7 @@ func (c *CostService) UpdateAndGetEstimateCost(param UpdateAndGetEstimateCostPar
if len(temp) > 0 {
estimateCostInfos = temp
} else {
possibleFetch = false
possibleIbmOrAzureFetch = true
}
}
} else if strings.Contains(p.ProviderName, "ncp") {
Expand All @@ -139,7 +139,7 @@ func (c *CostService) UpdateAndGetEstimateCost(param UpdateAndGetEstimateCostPar
return
}

if len(estimateCostInfos) == 0 || possibleFetch {
if len(estimateCostInfos) == 0 || possibleIbmOrAzureFetch {
log.Info().Msgf("No matching estimate cost found from database for spec: %+v, fetching from price collector", p)

resList, err := c.priceCollector.FetchPriceInfos(ctx, p)
Expand Down
78 changes: 62 additions & 16 deletions internal/core/load/load_generator_install_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,16 +689,24 @@ func (l *LoadService) getFallbackImage(connectionName string) (string, error) {
}

// selectBestImage selects the most appropriate image from the search results
// v0.12.1: OSType 필드를 활용하여 더 정확한 이미지 선택
func (l *LoadService) selectBestImage(images []tumblebug.ImageInfo, preferredOS string) tumblebug.ImageInfo {
// 우선순위별로 이미지 분류
var basicImages []tumblebug.ImageInfo
var osMatchedImages []tumblebug.ImageInfo
var serverImages []tumblebug.ImageInfo
var otherImages []tumblebug.ImageInfo

for _, img := range images {
// v0.12.1: OSType 필드를 사용하여 더 정확한 매칭
osType := img.GetOSType() // v0.11.19 및 v0.12.1 호환

// isBasicImage=true인 이미지 우선
if img.IsBasicImage {
basicImages = append(basicImages, img)
} else if osType != "" && strings.Contains(strings.ToLower(osType), strings.ToLower(preferredOS)) {
// v0.12.1: OSType이 선호 OS와 일치하는 경우
osMatchedImages = append(osMatchedImages, img)
} else if strings.Contains(strings.ToLower(img.Name), "server") ||
strings.Contains(strings.ToLower(img.Name), "jammy") {
// server 또는 jammy가 포함된 이미지
Expand All @@ -711,31 +719,38 @@ func (l *LoadService) selectBestImage(images []tumblebug.ImageInfo, preferredOS

// 1순위: isBasicImage=true인 이미지
if len(basicImages) > 0 {
log.Info().Msgf("Found %d basic images, selecting first one", len(basicImages))
// 기본 이미지 중에서도 OSType이 선호 OS와 일치하는 것 우선
for _, img := range basicImages {
osType := img.GetOSType()
if osType != "" && strings.Contains(strings.ToLower(osType), strings.ToLower(preferredOS)) {
log.Info().Msgf("Found basic image with matching OSType '%s': %s", osType, img.Name)
return img
}
}
log.Info().Msgf("Found %d basic images, selecting first one: %s", len(basicImages), basicImages[0].Name)
return basicImages[0]
}

// 2순위: server/jammy 이미지 (daily, pro 제외)
// 2순위: v0.12.1 OSType이 선호 OS와 일치하는 이미지
if len(osMatchedImages) > 0 {
for _, img := range osMatchedImages {
// daily, pro, minimal, fips, k8s, deep-learning 등 제외
if l.isSuitableImage(img) {
log.Info().Msgf("Found OSType matched image: %s (OSType: %s)", img.Name, img.GetOSType())
return img
}
}
}

// 3순위: server/jammy 이미지 (daily, pro 제외)
for _, img := range serverImages {
// daily, pro, minimal, fips, k8s, deep-learning 등 제외
if !strings.Contains(strings.ToLower(img.Name), "daily") &&
!strings.Contains(strings.ToLower(img.Name), "pro") &&
!strings.Contains(strings.ToLower(img.Name), "minimal") &&
!strings.Contains(strings.ToLower(img.Name), "fips") &&
!strings.Contains(strings.ToLower(img.Name), "k8s") &&
!strings.Contains(strings.ToLower(img.Name), "kubernetes") &&
!strings.Contains(strings.ToLower(img.Name), "container") &&
!strings.Contains(strings.ToLower(img.Name), "deep") &&
!strings.Contains(strings.ToLower(img.Name), "learning") &&
!strings.Contains(strings.ToLower(img.Name), "neuron") &&
!strings.Contains(strings.ToLower(img.Name), "parallelcluster") &&
!strings.Contains(strings.ToLower(img.Name), "sql") {
if l.isSuitableImage(img) {
log.Info().Msgf("Found suitable server image: %s", img.Name)
return img
}
}

// 3순위: 첫 번째 이미지 (폴백)
// 4순위: 첫 번째 이미지 (폴백)
if len(images) > 0 {
log.Warn().Msgf("No optimal image found, using first available: %s", images[0].Name)
return images[0]
Expand All @@ -745,6 +760,37 @@ func (l *LoadService) selectBestImage(images []tumblebug.ImageInfo, preferredOS
return tumblebug.ImageInfo{}
}

// isSuitableImage checks if an image is suitable (excludes daily, pro, minimal, fips, k8s, etc.)
func (l *LoadService) isSuitableImage(img tumblebug.ImageInfo) bool {
name := strings.ToLower(img.Name)
osDistribution := strings.ToLower(img.OSDistribution)

// 제외할 패턴 목록
excludePatterns := []string{
"daily", "pro", "minimal", "fips", "k8s", "kubernetes",
"container", "deep", "learning", "neuron", "parallelcluster",
"sql", "ecs", "eks", "bottlerocket",
}

for _, pattern := range excludePatterns {
if strings.Contains(name, pattern) || strings.Contains(osDistribution, pattern) {
return false
}
}

// v0.12.1: Kubernetes 이미지 제외
if img.IsKubernetesImage {
return false
}

// v0.12.1: Deprecated 이미지 제외
if img.ImageStatus == tumblebug.ImageDeprecated {
return false
}

return true
}

// getAvailableImageTraditional uses the traditional image selection method
func (l *LoadService) getAvailableImageTraditional(ctx context.Context, connectionName string) (string, error) {
// CB-Tumblebug에서 사용 가능한 이미지 목록 조회 시도
Expand Down
2 changes: 1 addition & 1 deletion internal/infra/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
type zerologGormLogger struct{}

func (z zerologGormLogger) Printf(format string, v ...interface{}) {
log.Printf((fmt.Sprintf(format, v...)))
log.Printf("%s", (fmt.Sprintf(format, v...)))
}

func migrateDB(defaultDb *gorm.DB) error {
Expand Down
50 changes: 37 additions & 13 deletions internal/infra/outbound/tumblebug/req.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,20 +204,44 @@ type SshKeyInfo struct {
PrivateKey string `json:"privateKey,omitempty"`
}

// CB-Tumblebug v0.11.8+ 스마트 매칭 구조체
// CB-Tumblebug v0.12.1 스마트 매칭 구조체
type SearchImageRequest struct {
MatchedSpecId string `json:"matchedSpecId,omitempty"`
ProviderName string `json:"providerName"`
RegionName string `json:"regionName"`
OSType string `json:"osType"`
OSArchitecture string `json:"osArchitecture"`
IsGPUImage *bool `json:"isGPUImage,omitempty"`
IsKubernetesImage *bool `json:"isKubernetesImage,omitempty"`
IsRegisteredByAsset *bool `json:"isRegisteredByAsset,omitempty"`
IncludeDeprecatedImage *bool `json:"includeDeprecatedImage,omitempty"`
IncludeBasicImageOnly *bool `json:"includeBasicImageOnly,omitempty"`
MaxResults int `json:"maxResults,omitempty"`
DetailSearchKeys []string `json:"detailSearchKeys,omitempty"`
// MatchedSpecId is the ID of the matched spec.
// If specified, only the images that match this spec will be returned.
MatchedSpecId string `json:"matchedSpecId,omitempty"`

// Cloud Service Provider (ex: "aws", "azure", "gcp", etc.)
ProviderName string `json:"providerName,omitempty"`

// Cloud Service Provider Region (ex: "us-east-1", "us-west-2", etc.)
RegionName string `json:"regionName,omitempty"`

// Simplified OS name and version string. Space-separated for AND condition (ex: "ubuntu 22.04")
OSType string `json:"osType,omitempty"`

// The architecture of the operating system of the image. (ex: "x86_64", "arm64", etc.)
OSArchitecture string `json:"osArchitecture,omitempty"`

// Whether the image is ready for GPU usage or not.
IsGPUImage *bool `json:"isGPUImage,omitempty"`

// Whether the image is specialized image only for Kubernetes nodes.
IsKubernetesImage *bool `json:"isKubernetesImage,omitempty"`

// Whether the image is registered by CB-Tumblebug asset file or not.
IsRegisteredByAsset *bool `json:"isRegisteredByAsset,omitempty"`

// Whether the search results should include deprecated images or not.
IncludeDeprecatedImage *bool `json:"includeDeprecatedImage,omitempty"`

// Return basic OS distribution only without additional applications.
IncludeBasicImageOnly *bool `json:"includeBasicImageOnly,omitempty"`

// Maximum number of images to be returned in the search results.
MaxResults int `json:"maxResults,omitempty"`

// Keywords for searching images in detail.
DetailSearchKeys []string `json:"detailSearchKeys,omitempty"`
}

type SearchImageResponse struct {
Expand Down
102 changes: 92 additions & 10 deletions internal/infra/outbound/tumblebug/res.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,22 +221,104 @@ type SpecInfo struct {
type RecommendVmResList = SpecInfoList
type RecommendVmRes = SpecInfo

// CB-Tumblebug 이미지 정보 구조체
// OSArchitecture represents the architecture of the operating system
type OSArchitecture string

const (
ARM32 OSArchitecture = "arm32"
ARM64 OSArchitecture = "arm64"
ARM64_MAC OSArchitecture = "arm64_mac"
X86_32 OSArchitecture = "x86_32"
X86_64 OSArchitecture = "x86_64"
X86_32_MAC OSArchitecture = "x86_32_mac"
X86_64_MAC OSArchitecture = "x86_64_mac"
S390X OSArchitecture = "s390x"
ArchitectureNA OSArchitecture = "NA"
ArchitectureUnknown OSArchitecture = ""
)

// OSPlatform represents the platform of the operating system
type OSPlatform string

const (
Linux_UNIX OSPlatform = "Linux/UNIX"
Windows OSPlatform = "Windows"
PlatformNA OSPlatform = "NA"
)

// ImageStatus represents the status of an image
type ImageStatus string

const (
ImageAvailable ImageStatus = "Available"
ImageUnavailable ImageStatus = "Unavailable"
ImageDeprecated ImageStatus = "Deprecated"
ImageNA ImageStatus = "NA"
)

// ImageSourceCommandHistory represents a single remote command execution record
type ImageSourceCommandHistory struct {
Index int `json:"index"`
CommandExecuted string `json:"commandExecuted"`
}

// CB-Tumblebug 이미지 정보 구조체 (v0.11.19 및 v0.12.1 호환)
type ImageInfo struct {
Id string `json:"id"`
Uid string `json:"uid,omitempty"`
Name string `json:"name"`
ConnectionName string `json:"connectionName,omitempty"`
CspImageId string `json:"cspImageId,omitempty"`
CspImageName string `json:"cspImageName,omitempty"`
Description string `json:"description,omitempty"`
// 기본 필드
Id string `json:"id"`
Uid string `json:"uid,omitempty"`
Name string `json:"name"`
ConnectionName string `json:"connectionName,omitempty"`
CspImageId string `json:"cspImageId,omitempty"`
CspImageName string `json:"cspImageName,omitempty"`
Description string `json:"description,omitempty"`
SystemLabel string `json:"systemLabel,omitempty"`
IsBasicImage bool `json:"isBasicImage,omitempty"`

// v0.11.19 호환성 필드 (deprecated)
GuestOS string `json:"guestOS,omitempty"`
Status string `json:"status,omitempty"`
KeyValueList []string `json:"keyValueList,omitempty"`
AssociatedObjectList []string `json:"associatedObjectList,omitempty"`
IsAutoGenerated bool `json:"isAutoGenerated,omitempty"`
IsBasicImage bool `json:"isBasicImage,omitempty"`
SystemLabel string `json:"systemLabel,omitempty"`

// v0.12.1 호환성 필드
ResourceType string `json:"resourceType,omitempty"`
Namespace string `json:"namespace,omitempty"`
ProviderName string `json:"providerName,omitempty"`
RegionList []string `json:"regionList,omitempty"`
SourceVmUid string `json:"sourceVmUid,omitempty"`
SourceCspImageName string `json:"sourceCspImageName,omitempty"`
InfraType string `json:"infraType,omitempty"`
FetchedTime string `json:"fetchedTime,omitempty"`
CreationDate string `json:"creationDate,omitempty"`
IsGPUImage bool `json:"isGPUImage,omitempty"`
IsKubernetesImage bool `json:"isKubernetesImage,omitempty"`
OSType string `json:"osType,omitempty"`
OSArchitecture OSArchitecture `json:"osArchitecture,omitempty"`
OSPlatform OSPlatform `json:"osPlatform,omitempty"`
OSDistribution string `json:"osDistribution,omitempty"`
OSDiskType string `json:"osDiskType,omitempty"`
OSDiskSizeGB float64 `json:"osDiskSizeGB,omitempty"`
ImageStatus ImageStatus `json:"imageStatus,omitempty"`
Details []KeyValue `json:"details,omitempty"`
CommandHistory []ImageSourceCommandHistory `json:"commandHistory,omitempty"`
}

// GetOSType returns OSType if available, otherwise falls back to GuestOS (v0.11.19 compatibility)
func (i *ImageInfo) GetOSType() string {
if i.OSType != "" {
return i.OSType
}
return i.GuestOS
}

// GetImageStatus returns ImageStatus if available, otherwise falls back to Status (v0.11.19 compatibility)
func (i *ImageInfo) GetImageStatus() string {
if i.ImageStatus != "" {
return string(i.ImageStatus)
}
return i.Status
}

type CommandStatusInfo struct {
Expand Down