use struct2json and import autoscaler

This commit is contained in:
Rongsong Shen 2025-12-05 18:02:06 +08:00
parent 7b5f280ae5
commit 5036d77f04
62 changed files with 6628 additions and 10 deletions

15
src/autoscaler/Makefile Normal file
View file

@ -0,0 +1,15 @@
TOP = $(shell pwd)/../..
include $(TOP)/config/Make.config
DIRS = providers scaler
all: $(addsuffix .all,$(DIRS))
clean: $(addsuffix .clean, $(DIRS))
%.all: %
make -C $< all
%.clean: %
make -C $< clean

View file

@ -0,0 +1,24 @@
libscaler_srcs = [
'scaler/plug_mgr.c',
'scaler/scaler_json.c',
'scaler/scaler_obj.c',
'scaler/obj_mgr.c',
'scaler/scaler_ctx.c',
'scaler/scaler_misc.c',
'scaler/scaler.c',
'scaler/prov_conf.c',
]
incdir=include_directories('scaler/include', '../include', '../lib')
libscaler_deps=[
struct2json_dep,
libxce_dep,
libtirpc_dep,
]
libscaler = static_library('scaler',
libscaler_srcs,
include_directories: [incdir],
dependencies: libscaler_deps,
install: true)

View file

@ -0,0 +1,15 @@
TOP = $(shell pwd)/../../../
include $(TOP)/config/Make.config
DIRS = baidu
all: $(addsuffix .all,$(DIRS))
clean: $(addsuffix .clean, $(DIRS))
%.all: %
make -C $< all
%.clean: %
make -C $< clean

View file

@ -0,0 +1,5 @@
export PRO_CONF_DIR=/opt/lsf/conf/resource_connector/baidu
export PRO_DATA_DIR=/opt/lsf/work/cluster1/resource_connector
export PRO_LOG_DIR=/opt/lsf/log
export PROVIDER_NAME=baidu1
export PRO_HOME_DIR=/opt/lsf/10.1/resource_connector/baidu

View file

@ -0,0 +1,15 @@
TOP = $(shell pwd)/../../../..
include $(TOP)/config/Make.config
GO_SRC = main.go
# Target
TARGET = BccTool
all: $(TARGET)
$(TARGET):
cd src/bcctool; ${GO} build -o ../../bin/$(TARGET) $(GO_SRC)
clean:
rm -f bin/$(TARGET)

View file

@ -0,0 +1,7 @@
{
"LogLevel": "debug",
"VPC": "default",
"Region": "bd",
"CredentialFile": "/opt/lsf/conf/resource_connector/baidu/conf/credentials"
}

View file

@ -0,0 +1,44 @@
{
"templates": [
{
"templateId": "Template-instance-ondemand",
"maxNumber": 2,
"priority": 20,
"attributes": {
"type": ["String", "X86_64"],
"ncores": ["Numeric", "2"],
"ncpus": ["Numeric", "2"],
"mem": ["Numeric", "8192"],
"baiduhost": ["Boolean", "1"]
},
"imageId": "m-kFqdEWvk",
"subnetId": "sbn-nwy4847fqm9i",
"zone": "cn-bd-b",
"instanceSpec": "bcc.e2.c2m2",
"sshKeypairId": "k-Nxwz7Xux",
"securityGroupIds": ["g-3m0uksbk2bie"],
"instanceTags" : "group=project1",
"userData": "zone=cn-bd-b"
},
{
"templateId": "Template-instance-onmarketi-bid",
"maxNumber": 0,
"priority": 10,
"attributes": {
"type": ["String", "X86_64"],
"ncores": ["Numeric", "4"],
"ncpus": ["Numeric", "2"],
"mem": ["Numeric", "8192"],
"pricing": ["String", "bid"],
"baiduhost": ["Boolean", "1"]
},
"imageId": "m-kFqdEWvk",
"instanceType":"N6",
"zone": "cn-bd-b",
"subnetId": "sbn-nwy4847fqm9i",
"securityGroupIds": ["g-3m0uksbk2bie"],
"instanceTags" : "group=project1",
"userData": "zone=cn-bd-b"
}
]
}

View file

@ -0,0 +1,4 @@
{
"AK": "xxxxxxxxxxxxxxxxxxxxxxxxx",
"SK": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

View file

@ -0,0 +1,27 @@
logging:
# Log level: debug, info, warn, error, fatal
level: "debug"
# Log encoding: json or console
encoding: console
# standard output
outputPath: "BccTool.log"
# File rotation settings
file:
maxSize: 100 # megabytes before rotation
maxBackups: 10 # number of old files to keep
maxAge: 14 # days to keep old files
compress: true # compress rotated files
# Encoder configuration
encoderConfig:
messageKey: "message"
levelKey: "level"
timeKey: "timestamp"
nameKey: "logger"
callerKey: "caller"
functionKey: "func"
stacktraceKey: "stacktrace"
timeEncoder: "ISO8601"
levelEncoder: "capital"

View file

@ -0,0 +1,61 @@
#!/bin/sh
logfile=/tmp/user_data.log
echo START `date '+%Y-%m-%d %H:%M:%S'` >> $logfile
#
# Export user data, which is defined with the "UserData" attribute
# in the template
#
%EXPORT_USER_DATA%
#
# Add your customization script here
#
#
# Source LSF enviornment at the VM host
#
LSF_TOP=/opt/lsf
LSF_CONF_FILE=$LSF_TOP/conf/lsf.conf
. $LSF_TOP/conf/profile.lsf
env >> $logfile
#
# Support rc_account resource to enable RC_ACCOUNT policy
# Add additional local resources if needed
#
if [ -n "${rc_account}" ]; then
sed -i "s/\(LSF_LOCAL_RESOURCES=.*\)\"/\1 [resourcemap ${rc_account}*rc_account]\"/" $LSF_CONF_FILE
echo "update LSF_LOCAL_RESOURCES lsf.conf successfully, add [resourcemap ${rc_account}*rc_account]" >> $logfile
fi
#
# Include origin information for fault tolerance
#
instance_id=$(curl http://169.254.169.254/2009-04-04/meta-data/instance-shortid)
if [ -n "$instance_id" ]; then
sed -i "s/\(LSF_LOCAL_RESOURCES=.*\)\"/\1 [resourcemap $instance_id*instanceID]\"/" $LSF_CONF_FILE
fi
if [ -n "$template_id" ]; then
sed -i "s/\(LSF_LOCAL_RESOURCES=.*\)\"/\1 [resourcemap $template_id*templateID]\"/" $LSF_CONF_FILE
fi
if [ -n "$clustername" ]; then
sed -i "s/\(LSF_LOCAL_RESOURCES=.*\)\"/\1 [resourcemap $clustername*clusterName]\"/" $LSF_CONF_FILE
fi
if [ -n "$providerName" ]; then
sed -i "s/\(LSF_LOCAL_RESOURCES=.*\)\"/\1 [resourcemap $providerName*providerName]\"/" $LSF_CONF_FILE
fi
#
# Run lsreghost command to register the host to LSF master if no DNS update
#
#echo "lsfdev1.test.com" > $LSF_ENVDIR/hostregsetup
#lsreghost -s $LSF_ENVDIR/hostregsetup
#
# Start LSF Daemons
#
#$LSF_SERVERDIR/lsf_daemons start
echo END AT `date '+%Y-%m-%d %H:%M:%S'` >> $logfile

View file

@ -0,0 +1,13 @@
#!/bin/bash
# This script:
# - should be called as getAvailableMachines.sh -f input.json
# - exit with 0 if calling succeed and result will be in the stdOut
# - exit with 1 if calling failed and error message will be in the stdOut
#
inJson=$2
scriptDir=`dirname $0`
homeDir="$(cd "$scriptDir" && cd .. && pwd)"
export PRO_HOME_DIR=$homeDir
exit 0
$homeDir/bin/BccTool -s $inJson

View file

@ -0,0 +1,12 @@
#!/bin/bash
# This script:
# - should be called as getAvailableTemplates.sh -f input.json
# - exit with 0 if calling succeed and result will be in the stdOut
# - exit with 1 if calling failed and error message will be in the stdOut
#
inJson=$2
scriptDir=`dirname $0`
homeDir="$(cd "$scriptDir" && cd .. && pwd)"
export PRO_HOME_DIR=$homeDir
$homeDir/bin/BccTool -t $inJson

View file

@ -0,0 +1,12 @@
#!/bin/bash
# This script:
# - should be called as getAvailableTemplates.sh -f input.json
# - exit with 0 if calling succeed and result will be in the stdOut
# - exit with 1 if calling failed and error message will be in the stdOut
#
inJson=$2
scriptDir=`dirname $0`
homeDir="$(cd "$scriptDir" && cd .. && pwd)"
export PRO_HOME_DIR=$homeDir
$homeDir/bin/BccTool -r $inJson

View file

@ -0,0 +1,12 @@
#!/bin/bash
# This script:
# - should be called as getAvailableTemplates.sh -f input.json
# - exit with 0 if calling succeed and result will be in the stdOut
# - exit with 1 if calling failed and error message will be in the stdOut
#
inJson=$2
scriptDir=`dirname $0`
homeDir="$(cd "$scriptDir" && cd .. && pwd)"
export PRO_HOME_DIR=$homeDir
$homeDir/bin/BccTool -s $inJson

View file

@ -0,0 +1,12 @@
#!/bin/bash
# This script:
# - should be called as getAvailableTemplates.sh -f input.json
# - exit with 0 if calling succeed and result will be in the stdOut
# - exit with 1 if calling failed and error message will be in the stdOut
#
inJson=$2
scriptDir=`dirname $0`
homeDir="$(cd "$scriptDir" && cd .. && pwd)"
export PRO_HOME_DIR=$homeDir
$homeDir/bin/BccTool -c $inJson

View file

@ -0,0 +1,12 @@
#!/bin/bash
# This script:
# - should be called as getAvailableTemplates.sh -f input.json
# - exit with 0 if calling succeed and result will be in the stdOut
# - exit with 1 if calling failed and error message will be in the stdOut
#
inJson=$2
scriptDir=`dirname $0`
homeDir="$(cd "$scriptDir" && cd .. && pwd)"
export PRO_HOME_DIR=$homeDir
$homeDir/bin/BccTool -d $inJson

View file

@ -0,0 +1,5 @@
export PRO_CONF_DIR=/opt/lsf/conf/resource_connector/baidu
export PRO_DATA_DIR=/opt/lsf/work/cluster1/resource_connector
export PRO_LOG_DIR=/opt/lsf/log
export PROVIDER_NAME=baidu1
export PRO_HOME_DIR=/opt/lsf/10.1/resource_connector/baidu

View file

@ -0,0 +1,397 @@
package client
import (
"bcctool/enums"
"bcctool/model"
"bcctool/provconst"
"bcctool/utils"
logger "bcctool/utils"
"encoding/base64"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"sync"
bccmodel "github.com/baidubce/bce-sdk-go/model"
"github.com/baidubce/bce-sdk-go/services/bcc"
"github.com/baidubce/bce-sdk-go/services/bcc/api"
"go.uber.org/zap"
)
type ProvCloudClient struct {
BccClient *bcc.Client
}
var (
provCloudClientInstance *ProvCloudClient
once sync.Once
)
// Change it to once, let it be a singleton
func GetProvCloudClient() *ProvCloudClient {
once.Do(func() {
AK, SK := utils.Prov_Config.AK, utils.Prov_Config.SK
ENDPOINT := utils.Prov_Config.Endpoint
client, err := bcc.NewClient(AK, SK, ENDPOINT)
if err != nil {
utils.ExitWithError(enums.E_BD_FAILED_CREATE_BCC_CLIENT, err)
}
provCloudClientInstance = &ProvCloudClient{
BccClient: client,
}
})
return provCloudClientInstance
}
func (client *ProvCloudClient) CreateInstances(nodeCreation *model.ProvNodeCreation) (*model.ProvReq, error) {
var tpl model.ProvTemplate
var exists = true
logger.Info("Begin create instances request:", zap.String("request:", nodeCreation.String()))
if tpl, exists = utils.TemplatesMap[nodeCreation.Template.TemplateId]; !exists {
return nil, fmt.Errorf("template %s not found", nodeCreation.Template.TemplateId)
}
if tpl.IsBidTemplate() {
return client.createBidInstance(nodeCreation, &tpl)
} else {
return client.createOndemandInstance(nodeCreation, &tpl)
}
}
func (client *ProvCloudClient) createOndemandInstance(nodeCreation *model.ProvNodeCreation, tpl *model.ProvTemplate) (*model.ProvReq, error) {
RelationTag := false
IsOpenIpv6 := false
// This function is deprecated, use CreateInstances instead
logger.Info("Begin create ondemand instances request:", zap.String("template:", tpl.String()))
retReq := model.NewProvReq4Create()
numberToCreate := nodeCreation.Template.MachineCount
if numberToCreate > 50 {
logger.Warn("Machine count exceeds the limit of 50, adjusting to 50.", zap.Int("adjustedMachineCount", numberToCreate))
// Adjust the machine count to 50 if it exceeds the limit
numberToCreate = 50
retReq.Msg = "Machine count exceeds the limit of 50. Creating 50 instances instead."
}
tagList := createTagsList(retReq.ReqID, nodeCreation.RcAccount, tpl)
userData := getEncodedUserData(tpl, nodeCreation.RcAccount)
argsV2 := &api.CreateInstanceBySpecArgsV2{
ImageId: tpl.ImageId,
Spec: tpl.InstanceSpec,
NetWorkCapacityInMbps: 0,
PurchaseCount: numberToCreate,
IsOpenIpv6: &IsOpenIpv6,
KeypairId: tpl.SshKeyPairId,
Billing: api.Billing{
PaymentTiming: api.PaymentTimingPostPaid,
},
ZoneName: tpl.Zone,
SubnetId: tpl.SubnetId,
SecurityGroupIds: tpl.SecurityGroupIds,
RelationTag: &RelationTag,
Tags: tagList,
UserData: userData,
}
createResult, err := client.BccClient.CreateInstanceBySpecV2(argsV2)
if err != nil {
logger.Error("CreateInstanceBySpecV2 failed", zap.Error(err))
return nil, err
}
nodes := model.NewProvNodes()
for _, instanceId := range createResult.InstanceIds {
node := model.NewProvNode()
node.NodeID = instanceId
nodes.AddNode(node)
}
retReq.Nodes = nodes.Nodes
retReq.Msg = "Request to create instances success."
logger.Info("CreateInstanceBySpecV2 success", zap.String("result", fmt.Sprintf("%+v", createResult)))
return retReq, nil
}
func (client *ProvCloudClient) createBidInstance(nodeCreation *model.ProvNodeCreation, tpl *model.ProvTemplate) (*model.ProvReq, error) {
RelationTag := false
IsOpenIpv6 := false
//CdsAutoRenew := false
logger.Info("Begin create bind instances request:", zap.String("request:", nodeCreation.String()))
retReq := model.NewProvReq4Create()
numberToCreate := nodeCreation.Template.MachineCount
batchsize := provconst.CREATE_BATCH_SIZE
if numberToCreate > batchsize {
logger.Warn("Machine count exceeds the limit of 50, adjusting to 50.", zap.Int("adjustedMachineCount", numberToCreate))
// Adjust the machine count to 50 if it exceeds the limit
numberToCreate = batchsize
retReq.Msg = "Machine count exceeds the limit of 50. Creating 50 instances instead."
}
tagList := createTagsList(retReq.ReqID, nodeCreation.RcAccount, tpl)
userData := getEncodedUserData(tpl, nodeCreation.RcAccount)
args := &api.CreateInstanceArgs{
ImageId: tpl.ImageId,
InstanceType: api.InstanceType(tpl.InstanceType),
CpuCount: tpl.GetNcpus(),
MemoryCapacityInGB: tpl.GetMemory(),
BidModel: "market", // we only support market bid model
RootDiskSizeInGb: 40,
RootDiskStorageType: api.StorageTypeEnhancedPl1,
NetWorkCapacityInMbps: 0,
PurchaseCount: numberToCreate,
IsOpenIpv6: IsOpenIpv6,
KeypairId: tpl.SshKeyPairId,
Billing: api.Billing{
PaymentTiming: api.PaymentTimingPostPaid,
},
ZoneName: tpl.Zone,
SubnetId: tpl.SubnetId,
SecurityGroupIds: tpl.SecurityGroupIds,
RelationTag: RelationTag,
Tags: tagList,
UserData: userData,
}
createResult, err := client.BccClient.CreateBidInstance(args)
if err != nil {
return nil, err
}
nodes := model.NewProvNodes()
for _, instanceId := range createResult.InstanceIds {
node := model.NewProvNode()
node.NodeID = instanceId
nodes.AddNode(node)
}
retReq.Nodes = nodes.Nodes
retReq.Msg = "Request to create instances success."
logger.Info("CreateBidInstance success", zap.String("result", fmt.Sprintf("%+v", createResult)))
return retReq, nil
}
func createTagsList(reqId string, rcAccount string, tpl *model.ProvTemplate) []bccmodel.TagModel {
tags := []bccmodel.TagModel{
{
TagKey: "REQ_ID",
TagValue: reqId,
},
{
TagKey: "RC_ACCOUNT",
TagValue: rcAccount,
},
{
TagKey: "PROVIDER_NAME",
TagValue: utils.Global_ProviderName,
},
}
if tpl.InstanceTags != "" {
// Split the tags by comma and create a tag for each key-value pair
tagPairs := strings.Split(tpl.InstanceTags, ";")
for _, pair := range tagPairs {
kv := strings.SplitN(pair, "=", 2)
if len(kv) == 2 {
tags = append(tags, bccmodel.TagModel{
TagKey: strings.TrimSpace(kv[0]),
TagValue: strings.TrimSpace(kv[1]),
})
}
}
}
return tags
}
func getEncodedUserData(t *model.ProvTemplate, rcAccount string) string {
var exportCmd string
var encodedUserData string
userDataFile := filepath.Join(utils.Global_HomeDir, provconst.ProcUserDataFileName)
if _, err := os.Stat(userDataFile); err == nil {
usrDatafileBytes, err := os.ReadFile(userDataFile)
if err != nil {
log.Printf("Error reading user data file: %v", err)
return ""
}
usrDatafileToStr := string(usrDatafileBytes)
if t.UserData != "" {
exportCmd += "export " + strings.ReplaceAll(t.UserData, ";", " ") + ";"
}
if rcAccount != "" {
exportCmd += "export rc_account=" + rcAccount + ";"
}
if t.TemplateId != "" {
exportCmd += "export template_id=" + t.TemplateId + ";"
}
if utils.Global_ClusterName != "" {
exportCmd += "export clustername=" + utils.Global_ClusterName + ";"
}
if utils.Global_ProviderName != "" {
exportCmd += "export providerName=" + utils.Global_ProviderName + ";"
}
usrDatafileToStr = strings.ReplaceAll(usrDatafileToStr, "%EXPORT_USER_DATA%", exportCmd)
encodedUserData = base64.StdEncoding.EncodeToString([]byte(usrDatafileToStr))
}
return encodedUserData
}
func (client *ProvCloudClient) ReleaseInstances(nodesArray []*model.ProvNode) (*model.ProvReq, error) {
instanceIds := make([]string, 0, len(nodesArray))
for _, node := range nodesArray {
instanceIds = append(instanceIds, node.NodeID)
}
logger.Info("Release instances request:", zap.String("instanceIds", fmt.Sprintf("%+v", instanceIds)))
if len(instanceIds) == 0 {
logger.Warn("No instance IDs to release.")
return nil, nil
}
// limit by API at most 100 at a time
batchSize := provconst.DELETE_BATCH_SIZE
dryRun := os.Getenv("BCC_DRY_RUN")
var allErr error
for start := 0; start < len(instanceIds); start += batchSize {
end := min(start+batchSize, len(instanceIds))
batch := instanceIds[start:end]
deleteInstanceWithRelateResourceArgs := &api.BatchDeleteInstanceWithRelateResourceArgs{
RelatedReleaseFlag: true,
DeleteRelatedEnisFlag: true,
DeleteCdsSnapshotFlag: true,
BccRecycleFlag: false,
InstanceIds: batch,
}
if dryRun != "" {
logger.Info("=== Dry running Release instance success.", zap.String("instanceIds", fmt.Sprintf("%+v", batch)))
} else {
if err := client.BccClient.BatchDeleteInstanceWithRelateResource(deleteInstanceWithRelateResourceArgs); err != nil {
logger.Error("Release instance failed: ", zap.String("error", err.Error()))
allErr = err // record the last error
}
}
logger.Info("Release instance batch success.", zap.String("instanceIds", fmt.Sprintf("%+v", batch)))
}
req := model.NewProvReq4Delete()
req.Nodes = nodesArray
req.Msg = "Request to delete instances success."
if allErr != nil {
return nil, allErr
}
return req, nil
}
func (client *ProvCloudClient) listInstancesByIds(instanceIds []string) ([]api.InstanceModel, error) {
listArgs := &api.ListInstanceArgs{
InstanceIds: strings.Join(instanceIds, ","),
}
return client.baiduMarkerPageListInstances(listArgs)
}
func (client *ProvCloudClient) ListInstancesByTag(tagKey, tagValue string) ([]api.InstanceModel, error) {
listArgs := &api.ListInstanceArgs{
Tags: fmt.Sprintf("%s:%s", tagKey, tagValue),
}
return client.baiduMarkerPageListInstances(listArgs)
}
func (client *ProvCloudClient) ListAllInstances() ([]api.InstanceModel, error) {
listArgs := &api.ListInstanceArgs{}
return client.baiduMarkerPageListInstances(listArgs)
}
func (client *ProvCloudClient) GetInstancesByReq(req *model.ProvReq) ([]api.InstanceModel, error) {
logger.Debug("----- Entering GetInstancesByReq -----", zap.Any("req", req))
instanceIds := make([]string, 0, len(req.Nodes))
if len(req.Nodes) == 0 {
logger.Warn("No nodes in the request, returning empty instance list.")
return nil, nil
}
for _, node := range req.Nodes {
instanceIds = append(instanceIds, node.NodeID)
}
result, err := client.listInstancesByIds(instanceIds)
if err != nil {
logger.Error("----- FAILED GetInstancesByReq -----", zap.Error(err))
return nil, err
}
logger.Debug("----- Exiting GetInstancesByReq -----", zap.Any("result", result))
return result, nil
}
func (client *ProvCloudClient) baiduMarkerPageListInstances(listArgs *api.ListInstanceArgs) ([]api.InstanceModel, error) {
var marker string
maxKeys := provconst.LIST_PAGE_SIZE
var allInstances []api.InstanceModel
for {
listArgs.Marker = marker
listArgs.MaxKeys = maxKeys
result, err := client.BccClient.ListInstances(listArgs)
if err != nil {
logger.Error("Failed to list instances with marker pagination", zap.Error(err))
return nil, err
}
allInstances = append(allInstances, result.Instances...)
if !result.IsTruncated || result.NextMarker == "" {
break // No more pages
}
marker = result.NextMarker // Set marker for the next page
}
return allInstances, nil
}
/*
Reference model.go
InstanceStatusRunning InstanceStatus = "Running"
InstanceStatusStarting InstanceStatus = "Starting"
InstanceStatusStopping InstanceStatus = "Stopping"
InstanceStatusStopped InstanceStatus = "Stopped"
InstanceStatusDeleted InstanceStatus = "Deleted"
InstanceStatusScaling InstanceStatus = "Scaling"
InstanceStatusExpired InstanceStatus = "Expired"
InstanceStatusError InstanceStatus = "Error"
InstanceStatusSnapshotProcessing InstanceStatus = "SnapshotProcessing"
InstanceStatusImageProcessing InstanceStatus = "ImageProcessing"
InstanceStatusChangeVpcProcessing InstanceStatus = "ChangeVpc"
InstanceStatusRecycled InstanceStatus = "Recycled"
*/
func InstanceStateIsCreationFailed(state string) bool {
// Check if the node state is one of the failed states
return state == string(api.InstanceStatusStopping) ||
state == string(api.InstanceStatusStopped) ||
state == string(api.InstanceStatusDeleted) ||
state == string(api.InstanceStatusExpired) ||
state == string(api.InstanceStatusError) ||
state == string(api.InstanceStatusRecycled)
}
func InstanceStateIsStarted(state string) bool {
// Check if the node state is one of the started states
return state == string(api.InstanceStatusRunning) ||
state == string(api.InstanceStatusSnapshotProcessing) ||
state == string(api.InstanceStatusImageProcessing) ||
state == string(api.InstanceStatusScaling)
}
func InstanceStateIsTerminated(state string) bool {
// Check if the node state is one of the terminated states
return state == string(api.InstanceStatusDeleted) ||
state == string(api.InstanceStatusRecycled) ||
state == string(api.InstanceStatusExpired)
}
func InstanceStateIsDeleteFailed(state string) bool {
// Check if the node state is one of the terminated states
return state == string(api.InstanceStatusError)
}

View file

@ -0,0 +1,117 @@
package enums
// All error codes used in the Baidu Cloud provider plugin
// Each error code has a corresponding string message for easy identification
// If you need to add a new error code, please also add it to the ErrCode
type ErrCode int
const (
E_BD_SUCCESS ErrCode = iota
// required env vars
E_BD_NO_CONFDIR
E_BD_NO_DATADIR
E_BD_NO_LOGDIR
// config related error codes
E_BD_LOAD_TEMPLATE_FAILED
E_BD_LOAD_NODE_CREATION_FAILED
E_BD_LOAD_CLIENT_CREDENTIAL_FAILED
E_BD_REGION_NOT_SUPPORTED
E_BD_LOAD_NODE_RETURN_FAILED
E_BD_LOAD_REQUEST_FAILED
E_BD_EMPTY_REQUEST
E_BD_LOAD_DB_FAILED
E_BD_UNSUPPORTED_REQ
E_BD_LOAD_PROVNODES_FAILED
// client related error codes
E_BD_FAILED_CREATE_BCC_CLIENT
E_BD_CREATE_INSTANCE_FAILED
E_BD_DELETE_INSTANCE_FAILED
E_BD_INSTANCE_NOT_FOUND_ON_BAIDU
E_BD_INSTANCE_CREATION_TIMEOUT
E_BD_INSTANCE_TERMINATE_TIMEOUT
)
func (e ErrCode) String() string {
ErrMsgMap := map[ErrCode]string{
E_BD_SUCCESS: "Success",
E_BD_NO_CONFDIR: "No Configuration Directory Set. Please set the PRO_CONF_DIR environment variable.",
E_BD_NO_DATADIR: "No Data Directory Set. Please set the PRO_DATA_DIR environment variable.",
E_BD_NO_LOGDIR: "No Log Directory Set. Please set the PRO_LOG_DIR or PRO_LSF_LOGDIR environment variable.",
E_BD_LOAD_TEMPLATE_FAILED: "Failed to load Baidu Cloud templates. Please check the template file.",
E_BD_LOAD_NODE_CREATION_FAILED: "Failed to parse node creation input file. Please check the input file.",
E_BD_LOAD_CLIENT_CREDENTIAL_FAILED: "Failed to load client credential file. Please check the credential file.",
E_BD_REGION_NOT_SUPPORTED: "The specified region is not supported. Please check the region configuration.",
E_BD_LOAD_NODE_RETURN_FAILED: "Failed to parse request Return Machines input file. Please check the input file.",
E_BD_LOAD_REQUEST_FAILED: "Failed to parse request file. Please check the input file.",
E_BD_EMPTY_REQUEST: "The request file is empty or no requestId specified. Please check the input file.",
E_BD_LOAD_DB_FAILED: "Failed to load the provider db file. Please check <providerName>_db.json.",
E_BD_UNSUPPORTED_REQ: "Unsupported request type. Please check the request type.",
E_BD_LOAD_PROVNODES_FAILED: "No nodes in input file.",
// client related error codes
E_BD_FAILED_CREATE_BCC_CLIENT: "Failed to create bcc client.",
E_BD_CREATE_INSTANCE_FAILED: "Failed to create instance. Please check the request parameters.",
E_BD_DELETE_INSTANCE_FAILED: "Failed to delete instance. Please check the instance IDs.",
E_BD_INSTANCE_NOT_FOUND_ON_BAIDU: "Instance not found on Baidu Cloud.",
E_BD_INSTANCE_CREATION_TIMEOUT: "Instance creation timed out.",
E_BD_INSTANCE_TERMINATE_TIMEOUT: "Instance termination timed out.",
}
if msg, ok := ErrMsgMap[e]; ok {
return msg
}
return "Unknown Error"
}
// ProvStatus represents the provider status
type ProvStatus string
// All possible provider status values
const (
REQUEST_STATUS_COMPLETE ProvStatus = "complete"
REQUEST_STATUS_RUNNING ProvStatus = "running"
REQUEST_STATUS_COMPLETE_WITH_ERR ProvStatus = "complete_with_error"
)
// Value returns the string value of the status
func (ps ProvStatus) String() string {
return string(ps)
}
// NodeResult represents the possible results of a node operation
type NodeResult string
// All possible node result values
const (
NODE_RESULT_SUCCEED NodeResult = "succeed"
NODE_RESULT_EXECUTING NodeResult = "executing"
NODE_RESULT_FAIL NodeResult = "fail"
)
// Value returns the string value of the result
func (nr NodeResult) String() string {
return string(nr)
}
type NodeStatus string
// Additional node status values
const (
NODE_STATUS_DELETED NodeStatus = "deleted"
)
// Value returns the string value of the status
func (ns NodeStatus) String() string {
return string(ns)
}
type ActionCode int
const (
ACTION_CREATE_REQUEST ActionCode = iota
ACTION_DELETE_REQUEST
ACTION_CREATE_STATUS
ACTION_DELETE_STATUS
)

View file

@ -0,0 +1,21 @@
module bcctool
go 1.22.2
require (
github.com/baidubce/bce-sdk-go v0.9.233
github.com/xeipuuv/gojsonschema v1.2.0
)
require (
github.com/google/uuid v1.6.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
)
require (
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0
)

View file

@ -0,0 +1,23 @@
github.com/baidubce/bce-sdk-go v0.9.233 h1:nq5PL+G2+JIkox98GN0UEkNdh8G+j4cmyJxbzoFNgec=
github.com/baidubce/bce-sdk-go v0.9.233/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View file

@ -0,0 +1,58 @@
package main
import (
"fmt"
"os"
"bcctool/provservices"
"bcctool/utils"
)
func printUsage() {
fmt.Fprintln(os.Stderr, `Usage: [-t | -c | -d | -r | -s] json_file")
-c create instances based on the templateId and num from input json file.
-d delete instances based on the instance ids from input json file.
-t list all valid templates. Ignore the input json file.
-r get the status of a request based on the request id from the input json file.
-s check if any instance is reclaimed by bcc based on the instance ids list from the input json file.
-h show this help message.`)
}
func main() {
if len(os.Args) < 2 {
printUsage()
return
}
// Handle help
if os.Args[1] == "-h" || os.Args[1] == "--help" {
printUsage()
return
}
if len(os.Args) != 3 {
printUsage()
os.Exit(1)
}
opt := os.Args[1]
json_file := os.Args[2]
utils.InitProv()
switch opt {
case "-t":
provservices.GetAvailableTemplates(json_file)
case "-c":
provservices.CreateInstancesFromFile(json_file)
case "-d":
provservices.DeleteInstancesFromFile(json_file)
case "-r":
provservices.GetRequestStatusFromFile(json_file)
case "-s":
provservices.SyncInstancesStatusFromFile(json_file)
default:
printUsage()
os.Exit(1)
}
}

View file

@ -0,0 +1,607 @@
package model
import (
"bcctool/enums"
"bcctool/provconst"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/baidubce/bce-sdk-go/services/bcc/api"
"github.com/google/uuid"
)
type ClientCredential struct {
AK string `json:"AK"`
SK string `json:"SK"`
}
type ProvConfig struct {
LogLevel string `json:"LogLevel,omitempty"`
VPC string `json:"VPC,omitempty"`
Region string `json:"Region"`
CredentialFile string `json:"CredentialFile"`
AK string
SK string
Endpoint string
}
// --- ProvTemplate ---
type ProvTemplate struct {
TemplateId string `json:"templateId"`
MaxNumber int `json:"maxNumber,omitempty"`
Attributes map[string][]string `json:"attributes,omitempty"`
ImageId string `json:"imageId,omitempty"`
SubnetId string `json:"subnetId,omitempty"`
Zone string `json:"zone,omitempty"`
InstanceType string `json:"instanceType,omitempty"` // used for bid instances
InstanceSpec string `json:"instanceSpec,omitempty"` // used for normal ondemand instances
SshKeyPairId string `json:"sshKeypairId,omitempty"`
SecurityGroupIds []string `json:"securityGroupIds,omitempty"`
InstanceTags string `json:"instanceTags,omitempty"`
UserData string `json:"userData,omitempty"`
MachineCount int `json:"machineCount,omitempty"` // used for request creation
}
func (t ProvTemplate) IsBidTemplate() bool {
if t.InstanceType != "" {
return true
}
if t.Attributes == nil {
return false
}
if t.Attributes == nil {
return false
}
if pricing, ok := t.Attributes["pricing"]; ok && len(pricing) >= 2 {
if pricing[1] == "bid" || pricing[1] == "spot" {
return true
}
}
return false
}
func (t ProvTemplate) GetNcpus() int {
if t.Attributes == nil {
return 1
}
// "ncpus": ["Numeric","2"],
// ncpus[0] is the type, ncpus[1] is the value
if ncpus, ok := t.Attributes["ncpus"]; ok && len(ncpus) >= 2 {
n, err := strconv.Atoi(ncpus[1])
if err == nil {
return n
}
}
return 1
}
func (t ProvTemplate) GetMemory() int {
if t.Attributes == nil {
return 2
}
// "memory": ["Numeric","4096"],
// memory[0] is the type, memory[1] is the value
if memory, ok := t.Attributes["mem"]; ok && len(memory) >= 2 {
mem, err := strconv.Atoi(memory[1])
if err == nil {
mem = int(mem / 1024) // Convert to GB
if mem < 2 {
mem = 2 // Ensure minimum memory is at least 2GB
}
return mem
}
}
return 2
}
func (t ProvTemplate) String() string {
data, _ := json.MarshalIndent(t, "", " ")
return string(data)
}
type ProvTemplatesArray struct {
Templates []ProvTemplate `json:"templates"`
}
// --- ProvNode ---
type ProvNode struct {
NodeID string `json:"machineId"`
Result string `json:"result,omitempty"`
Status string `json:"status,omitempty"`
Msg string `json:"message,omitempty"`
ReqID string `json:"reqId,omitempty"`
RetID string `json:"retId,omitempty"`
Hostname string `json:"name,omitempty"`
InstanceName string `json:"instanceName,omitempty"`
PrivateIPAddr string `json:"privateIpAddress,omitempty"`
PublicIPAddr string `json:"publicIpAddress,omitempty"`
Template string `json:"template,omitempty"`
RCAccount string `json:"rcAccount,omitempty"`
LaunchTime *time.Time `json:"launchtime,omitempty"`
}
func NewProvNode() *ProvNode {
return &ProvNode{}
}
func converStringToTime(timestr string) *time.Time {
if timestr == "" {
return nil
}
t, err := time.Parse(time.RFC3339, timestr)
if err != nil {
return nil
}
t = t.Local().Truncate(time.Second)
return &t
}
func NewProvNodeFromNode(node *ProvNode) *ProvNode {
if node == nil {
return NewProvNode()
}
return &ProvNode{
NodeID: node.NodeID,
Result: node.Result,
Status: node.Status,
Msg: node.Msg,
ReqID: node.ReqID,
RetID: node.RetID,
Hostname: node.Hostname,
InstanceName: node.InstanceName,
PrivateIPAddr: node.PrivateIPAddr,
PublicIPAddr: node.PublicIPAddr,
Template: node.Template,
RCAccount: node.RCAccount,
LaunchTime: node.LaunchTime,
}
}
func NewProvNodeFromInstance(instance *api.InstanceModel) *ProvNode {
if instance == nil {
return NewProvNode()
}
return &ProvNode{
NodeID: instance.InstanceId,
Hostname: instance.Hostname,
InstanceName: instance.InstanceName,
Status: string(instance.Status),
PrivateIPAddr: instance.InternalIP,
PublicIPAddr: instance.PublicIP,
LaunchTime: converStringToTime(instance.CreationTime),
}
}
func TimeStr2UTCSeconds(timestr string) int64 {
t, err := time.Parse(time.RFC3339, timestr)
if err != nil {
return 0
}
return t.Unix()
}
func (pn *ProvNode) UpdateFromInstance(instance *api.InstanceModel) {
if pn == nil || instance == nil {
return
}
if pn.NodeID != instance.InstanceId {
return
}
pn.Status = string(instance.Status)
if pn.Hostname == "" {
pn.Hostname = instance.Hostname
}
if pn.InstanceName == "" {
pn.InstanceName = instance.InstanceName
}
if pn.PrivateIPAddr == "" {
pn.PrivateIPAddr = instance.InternalIP
}
if pn.PublicIPAddr == "" {
pn.PublicIPAddr = instance.PublicIP
}
if pn.LaunchTime == nil || pn.LaunchTime.IsZero() {
pn.LaunchTime = converStringToTime(instance.CreationTime)
}
}
func (pn *ProvNode) UpdateFromNode(other *ProvNode) {
if other == nil {
return
}
if pn.NodeID == "" {
pn.NodeID = other.NodeID
}
if pn.LaunchTime == nil || pn.LaunchTime.IsZero() {
pn.LaunchTime = other.LaunchTime
}
if pn.Result == "" {
pn.Result = other.Result
}
if pn.Status == "" {
pn.Status = other.Status
}
if pn.Msg == "" {
pn.Msg = other.Msg
}
if pn.ReqID == "" {
pn.ReqID = other.ReqID
}
if pn.RetID == "" {
pn.RetID = other.RetID
}
if pn.InstanceName == "" {
pn.InstanceName = other.InstanceName
}
if pn.Hostname == "" {
pn.Hostname = other.Hostname
}
if pn.PrivateIPAddr == "" {
pn.PrivateIPAddr = other.PrivateIPAddr
}
if pn.PublicIPAddr == "" {
pn.PublicIPAddr = other.PublicIPAddr
}
if pn.RCAccount == "" {
pn.RCAccount = other.RCAccount
}
if pn.Template == "" {
pn.Template = other.Template
}
}
func (pn *ProvNode) ToJSON() string {
data, _ := json.Marshal(pn)
return string(data)
}
func (pn *ProvNode) String() string {
return pn.ToJSON()
}
// --- ProvNodes ---
type ProvNodes struct {
Nodes []*ProvNode `json:"machines"`
}
func NewProvNodes() *ProvNodes {
return &ProvNodes{
Nodes: make([]*ProvNode, 0),
}
}
func (pn *ProvNodes) AddNode(node *ProvNode) {
pn.Nodes = append(pn.Nodes, node)
}
func (pn *ProvNodes) ToJSON() string {
data, _ := json.Marshal(pn)
return string(data)
}
func (pn *ProvNodes) String() string {
return pn.ToJSON()
}
// --- ProvNodeCreation ---
type ProvNodeCreation struct {
Template *ProvTemplate `json:"template,omitempty"`
RcAccount string `json:"rc_account,omitempty"`
UserData map[string]interface{} `json:"userData,omitempty"`
}
func (pn ProvNodeCreation) String() string {
data, _ := json.MarshalIndent(pn, "", " ")
return string(data)
}
// --- ProvReq ---
type ProvReq struct {
ReqID string `json:"requestId,omitempty"`
TemplateID string `json:"templateId,omitempty"`
Msg string `json:"message,omitempty"`
Status string `json:"status,omitempty"`
NodeID string `json:"machineId,omitempty"`
NodeName string `json:"machine,omitempty"`
GracePeriod int64 `json:"gracePeriod,omitempty"`
Nodes []*ProvNode `json:"machines,omitempty"`
RCAccount string `json:"rc_account,omitempty"`
BeginTime *time.Time `json:"beginTime,omitempty"`
EndTime *time.Time `json:"endTime,omitempty"`
}
func NewProvReq() *ProvReq {
return &ProvReq{}
}
func NewProvReqFromReq(req *ProvReq) *ProvReq {
if req == nil {
return NewProvReq()
}
return &ProvReq{
ReqID: req.ReqID,
Msg: req.Msg,
Status: req.Status,
BeginTime: req.BeginTime,
EndTime: req.EndTime,
TemplateID: req.TemplateID,
Nodes: req.Nodes,
}
}
func NewProvReqFromReq4Save(req *ProvReq) *ProvReq {
if req == nil {
return NewProvReq()
}
return &ProvReq{
ReqID: req.ReqID,
Msg: req.Msg,
Status: req.Status,
BeginTime: req.BeginTime,
EndTime: req.EndTime,
TemplateID: req.TemplateID,
RCAccount: req.RCAccount,
}
}
func NewProvReq4Create() *ProvReq {
now := time.Now().Truncate(time.Second)
return &ProvReq{
ReqID: strings.Join([]string{provconst.REQ_ID_CREATE, randomID()}, "-"),
BeginTime: &now,
Status: enums.REQUEST_STATUS_RUNNING.String(),
}
}
func NewProvReq4Delete() *ProvReq {
now := time.Now().Truncate(time.Second)
return &ProvReq{
ReqID: strings.Join([]string{provconst.REQ_ID_DELETE, randomID()}, "-"),
BeginTime: &now,
Status: enums.REQUEST_STATUS_RUNNING.String(),
}
}
func NewProvReqWithNodes(reqID string, nodes []*ProvNode) *ProvReq {
return &ProvReq{
ReqID: reqID,
Nodes: nodes,
}
}
// IsExpired checks if a request ID is expired
func IsExpired(reqID string) bool {
return false
}
func IsCreationReq(reqId string) bool {
return strings.HasPrefix(reqId, provconst.REQ_ID_CREATE)
}
func IsTerminationReq(reqId string) bool {
return strings.HasPrefix(reqId, provconst.REQ_ID_DELETE)
}
func randomID() string {
randomUUID := uuid.New().String()
return strings.ReplaceAll(randomUUID, "-", "")
}
// This is only applied to RetReq
func (pr ProvReq) CanBeCleaned() bool {
now := time.Now().Truncate(time.Second)
if pr.EndTime == nil {
return false
}
if int64(now.Sub(*pr.EndTime).Seconds()) < provconst.REQUEST_CLEAN_TIMEOUT {
return false
}
// A request can be cleaned if it is complete or has an error, and it has no nodes
return (pr.Status == enums.REQUEST_STATUS_COMPLETE.String() ||
pr.Status == enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String())
}
func (pr *ProvReq) GetLaunchTime() time.Time {
now := time.Now().Truncate(time.Second)
if pr.BeginTime == nil {
pr.BeginTime = &now
return now
}
return *pr.BeginTime
}
func (pr *ProvReq) AddNode(node *ProvNode) {
if node == nil {
return
}
if pr.Nodes == nil {
pr.Nodes = make([]*ProvNode, 0)
}
// Should no need to check duplicate
//for _, existingNode := range pr.Nodes {
// if existingNode.NodeID == node.NodeID {
// return // Node already exists in the request
// }
//}
pr.Nodes = append(pr.Nodes, node)
}
func NewProvReqWithStatus(reqID, msg, status string) *ProvReq {
return &ProvReq{
ReqID: reqID,
Msg: msg,
Status: status,
}
}
func (pr *ProvReq) UpdateRequest(other *ProvReq) {
if other == nil {
return
}
pr.ReqID = other.ReqID
pr.Msg = other.Msg
pr.Status = other.Status
pr.TemplateID = other.TemplateID
pr.BeginTime = other.BeginTime
pr.EndTime = other.EndTime
pr.Nodes = other.Nodes
if other.RCAccount != "" {
pr.RCAccount = other.RCAccount
}
}
func (pr *ProvReq) UpdateNodes(pnodes map[string]*ProvNode) {
if len(pnodes) == 0 || len(pr.Nodes) == 0 {
return
}
for _, pnode := range pr.Nodes {
if node, exists := pnodes[pnode.NodeID]; exists {
pnode.UpdateFromNode(node)
}
}
}
func (pr *ProvReq) ToJSON() string {
data, _ := json.Marshal(pr)
return string(data)
}
func (pr *ProvReq) String() string {
return pr.ToJSON()
}
// --- ProvReqs ---
type ProvReqs struct {
Reqs []*ProvReq `json:"requests"`
}
func NewProvReqs() *ProvReqs {
return &ProvReqs{
Reqs: make([]*ProvReq, 0),
}
}
func (pr *ProvReqs) AddReq(req *ProvReq) {
pr.Reqs = append(pr.Reqs, req)
}
func (pr *ProvReqs) ToJSON() string {
data, _ := json.Marshal(pr)
return string(data)
}
func (pr *ProvReqs) String() string {
return pr.ToJSON()
}
// --- ProvDBEntry ---
// ProvDBEntry is used to store the nodes and requests in the provider database.
// It contains a list of nodes and a list of requests & returnRequests.
type ProvDBEntry struct {
Nodes []*ProvNode `json:"machines,omitempty"`
Reqs []*ProvReq `json:"requests,omitempty"`
RetReqs []*ProvReq `json:"returnRequests,omitempty"`
NodesMap map[string]*ProvNode `json:"-"`
ReqMap map[string]*ProvReq `json:"-"`
RetReqMap map[string]*ProvReq `json:"-"`
}
func NewProvDBEntry() *ProvDBEntry {
return &ProvDBEntry{
Nodes: make([]*ProvNode, 0),
Reqs: make([]*ProvReq, 0),
RetReqs: make([]*ProvReq, 0),
NodesMap: make(map[string]*ProvNode),
ReqMap: make(map[string]*ProvReq),
RetReqMap: make(map[string]*ProvReq),
}
}
func (db *ProvDBEntry) GetProvReqFromDB(reqID string) (*ProvReq, error) {
// Find the request by ID
if IsCreationReq(reqID) {
// If it's a creation request, check the ReqMap
if req, exists := db.ReqMap[reqID]; exists {
return req, nil
}
} else if IsTerminationReq(reqID) {
// If it's a return request, check the RetReqMap
if retReq, exists := db.RetReqMap[reqID]; exists {
return retReq, nil
}
} else {
// Otherwise, unsupported request type
return nil, fmt.Errorf("unsupported request type for ID %s", reqID)
}
return nil, fmt.Errorf("request with ID %s not found in db", reqID)
}
// --- ProvResult ---
type ProvResult struct {
Code int `json:"code,omitempty"`
Status string `json:"status,omitempty"`
Message string `json:"message,omitempty"`
Details string `json:"details,omitempty"`
Templates []ProvTemplate `json:"templates,omitempty"`
RequestId string `json:"requestId,omitempty"`
ProvReqs []*ProvReq `json:"requests,omitempty"`
}
func NewProvResult4err(code int, message, details string) *ProvResult {
return &ProvResult{
Code: code,
Message: message,
Details: details,
}
}
func NewProvResult4templates(templates []ProvTemplate) *ProvResult {
return &ProvResult{
Templates: templates,
}
}
func NewProvResult4Create(reqId string, msg string) *ProvResult {
return &ProvResult{
RequestId: reqId,
Message: msg,
}
}
func NewProvResult4Delete(reqId string, msg string) *ProvResult {
return &ProvResult{
RequestId: reqId,
Message: msg,
}
}
func NewProvResult4Sync() *ProvResult {
return &ProvResult{}
}
func (r *ProvResult) PrintJSON() error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
fmt.Println(string(data))
return nil
}
func (pr *ProvResult) ToJSON() string {
data, _ := json.Marshal(pr)
return string(data)
}
func (pr *ProvResult) String() string {
return pr.ToJSON()
}

View file

@ -0,0 +1,31 @@
package provconst
var (
ProvName = "baiducloud"
ProvType = "baiducloud"
ProvDesc = "Baidu Cloud Provider Plugin for Cloud Provider Plugins"
ProvVersion = "v1.0.0"
ProvTemplateFilename = "baiduprov_templates.json"
ProvConfigFilename = "baiduprov_config.json"
ProvDataFileName = "_db.json"
ProcUserDataFileName = "scripts/user_data.sh"
BaiduEndpointMap = map[string]string{
"bj": "bcc.bj.baidubce.com",
"bd": "bcc.bd.baidubce.com",
"gz": "bcc.gz.baidubce.com",
"sh": "bcc.sh.baidubce.com",
"cd": "bcc.cd.baidubce.com",
"hk": "bcc.hk.baidubce.com",
}
REQ_ID_CREATE = "IDCREATE"
REQ_ID_DELETE = "IDDELETE"
REQUEST_CREATION_TIMEOUT int64 = 30 * 60 // 30 minutes in seconds
REQUEST_TERMINATION_TIMEOUT int64 = 30 * 60 // 30 minutes in seconds
REQUEST_CLEAN_TIMEOUT int64 = 5 * 60 // 5 minutes in seconds
DELETE_BATCH_SIZE int = 100 // Batch size for delete operations
CREATE_BATCH_SIZE int = 50 // Batch size for create operations
LIST_PAGE_SIZE int = 100 // Page size for listing operations
)

View file

@ -0,0 +1,412 @@
package provservices
import (
"bcctool/client"
"bcctool/enums"
"bcctool/model"
"bcctool/utils"
logger "bcctool/utils"
"time"
"github.com/baidubce/bce-sdk-go/services/bcc/api"
"go.uber.org/zap"
)
func GetAvailableTemplates(jsonFile string) (ret *model.ProvResult) {
logger.Info("----- Entering getAvailableTemplates -----")
// utils.Prov_Templates already loaded in utils.InitProv()
ret = model.NewProvResult4templates(utils.Prov_Templates)
utils.Result(ret)
logger.Info("----- Exiting getAvailableTemplates -----", zap.Any("result", ret))
return ret
}
func CreateInstancesFromFile(jsonFile string) (ret *model.ProvResult) {
logger.Info("----- Entering requestMachines -----")
nodeCreation, err := utils.LoadNodeCreationInputFile(jsonFile)
if err != nil {
utils.ExitWithError(enums.E_BD_LOAD_NODE_CREATION_FAILED, err)
}
// Invoke bccClient and crate instances
provClient := client.GetProvCloudClient()
req, err := provClient.CreateInstances(nodeCreation)
if err != nil {
utils.ExitWithError(enums.E_BD_CREATE_INSTANCE_FAILED, err)
}
ret = model.NewProvResult4Create(req.ReqID, "Request to create instances success.")
// Save the request and nodes to the db
utils.SaveProvReq2DB(req, enums.ACTION_CREATE_REQUEST)
logger.Info("----- Exiting requestMachines -----")
utils.Result(ret)
return ret
}
func DeleteInstancesFromFile(jsonFile string) (ret *model.ProvResult) {
logger.Info("----- Entering releaseMachines -----")
// concat the full path of baiduprov_config.json
nodes, err := utils.LoadNodeReturnInputFile(jsonFile)
if err != nil || len(nodes.Nodes) == 0 {
utils.ExitWithError(enums.E_BD_LOAD_NODE_RETURN_FAILED, err)
}
// Invoke bccClient and release instances
provClient := client.GetProvCloudClient()
req, err := provClient.ReleaseInstances(nodes.Nodes)
if err != nil {
utils.ExitWithError(enums.E_BD_DELETE_INSTANCE_FAILED, err)
}
utils.SaveProvReq2DB(req, enums.ACTION_DELETE_REQUEST)
ret = model.NewProvResult4Delete(req.ReqID, "Request to delete instances success.")
logger.Info("----- Exiting releaseMachines -----")
utils.Result(ret)
return ret
}
func SyncInstancesStatusFromFile(jsonFile string) (ret *model.ProvResult) {
logger.Info("----- Entering SyncInstancesStatusFromFile -----")
db := utils.GetDB()
// concat the full path of baiduprov_config.json
nodes, err := utils.LoadGetReturnRequestInputFile(jsonFile)
if err != nil {
utils.ExitWithError(enums.E_BD_LOAD_PROVNODES_FAILED, err)
}
logger.Info("Nodes input:", zap.Any("nodes", nodes.String()))
reqs := []*model.ProvReq{}
ret = model.NewProvResult4Sync()
instances, err := client.GetProvCloudClient().ListAllInstances()
if err != nil || len(instances) == 0 {
logger.Error("Failed to list all instances", zap.Error(err))
return ret
}
instancesMap := make(map[string]*api.InstanceModel)
for _, instance := range instances {
instancesMap[instance.InstanceId] = &instance
}
tnodes := []*model.ProvNode{}
for _, pnode := range nodes.Nodes {
req := model.NewProvReq()
req.NodeID = pnode.NodeID
req.NodeName = pnode.Hostname
dbnode := db.NodesMap[pnode.NodeID]
if dbnode != nil && dbnode.RetID != "" {
// ignore the nodes that is already requsted to return
continue
}
// The node does not exist on Cloud
instance := instancesMap[pnode.NodeID]
if instance == nil {
reqs = append(reqs, req)
continue
}
instanceState := string(instance.Status)
// The node has been terminated from baidu Cloud
if client.InstanceStateIsTerminated(instanceState) {
reqs = append(reqs, req)
continue
}
// Will terminate the failed node
if client.InstanceStateIsDeleteFailed(instanceState) {
tnodes = append(tnodes, pnode)
}
}
// Terminate the failed nodes
if len(tnodes) > 0 {
logger.Warn("========Delete the failed nodes", zap.Any("nodes", tnodes))
/*
// should not reach here, but just in case
provClient := client.GetProvCloudClient()
if _, err := provClient.ReleaseInstances(tnodes); err != nil {
// log error
logger.Error("Failed to release instances during creation status check", zap.Error(err))
}
*/
}
ret.ProvReqs = append(ret.ProvReqs, reqs...)
// clean up timeout requests
utils.CleanTimeoutRequests()
ret.Status = enums.REQUEST_STATUS_COMPLETE.String()
utils.Result(ret)
logger.Info("----- Exiting SyncInstancesStatusFromFile -----", zap.Any("result", ret))
return ret
}
// GetRequestStatusFromFile retrieves status of nodes creation and return
func GetRequestStatusFromFile(jsonFile string) (result *model.ProvResult) {
logger.Info("----- Entering GetRequestStatusFromFile -----")
result = &model.ProvResult{
ProvReqs: make([]*model.ProvReq, 0),
}
inputReqs, err := utils.LoadProvReqsFromInputFile(jsonFile)
if err != nil {
utils.ExitWithError(enums.E_BD_LOAD_REQUEST_FAILED, err)
}
if len(inputReqs.Reqs) == 0 {
utils.ExitWithError(enums.E_BD_EMPTY_REQUEST, err)
}
// Invoke bccClient and release instances
provClient := client.GetProvCloudClient()
db := utils.GetDB()
for _, inReq := range inputReqs.Reqs {
var retReq *model.ProvReq
reqId := inReq.ReqID
// Check if the request ID is valid in the database
dbreq, err := db.GetProvReqFromDB(reqId)
logger.Debug("----- GetProvReqFromDB -----", zap.String("reqId", reqId), zap.Any("dbreq", dbreq))
if err != nil {
result.ProvReqs = append(result.ProvReqs, &model.ProvReq{
ReqID: reqId,
Msg: err.Error(),
Status: enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String(),
})
continue
}
// Check if the request was already completed
if dbreq.Status == enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String() ||
dbreq.Status == enums.REQUEST_STATUS_COMPLETE.String() {
retReq = model.NewProvReqFromReq(dbreq)
result.ProvReqs = append(result.ProvReqs, retReq)
continue
}
// Retrieve instances from cloud based on the request related instances
instances, err := provClient.GetInstancesByReq(dbreq)
if err != nil {
result.ProvReqs = append(result.ProvReqs, &model.ProvReq{
ReqID: reqId,
Msg: err.Error(),
Status: enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String(),
})
continue
}
// default set it to complete, will change in nodeCreationStatus and nodeTerminationStatus
dbreq.Status = enums.REQUEST_STATUS_COMPLETE.String()
if model.IsCreationReq(reqId) {
nodeCreationStatus(instances, dbreq)
} else if model.IsTerminationReq(reqId) {
nodeTerminationStatus(instances, dbreq)
} else {
// should already handled inside GetProvReqFromDB
dbreq.Status = enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String()
dbreq.Msg = enums.E_BD_UNSUPPORTED_REQ.String()
retReq = model.NewProvReqFromReq(dbreq)
result.ProvReqs = append(result.ProvReqs, retReq)
logger.Error("Unsupported request type", zap.String("reqId", reqId))
continue
}
retReq = model.NewProvReqFromReq(dbreq)
result.ProvReqs = append(result.ProvReqs, retReq)
}
utils.Result(result)
logger.Info("----- Exiting GetRequestStatusFromFile -----")
return result
}
// nodeCreationStatus handles the status of node creation
func nodeCreationStatus(instances []api.InstanceModel, req *model.ProvReq) []*model.ProvNode {
logger.Debug("----- Entering nodeCreationStatus -----", zap.String("reqId", req.String()), zap.Any("instances", instances))
now := time.Now().Truncate(time.Second)
nodesMap := make(map[string]*model.ProvNode)
for _, pnode := range req.Nodes {
nodesMap[pnode.NodeID] = pnode
}
instancesMap := make(map[string]*api.InstanceModel)
for _, instance := range instances {
instancesMap[instance.InstanceId] = &instance
}
// the request related instances does not exists on cloud
if len(instances) == 0 {
req.Status = enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String()
for _, pnode := range req.Nodes {
pnode.Result = enums.NODE_RESULT_FAIL.String()
logger.Debug("----- Set pnode result to fail -----", zap.String("nodeId", pnode.NodeID))
pnode.Msg = enums.E_BD_INSTANCE_NOT_FOUND_ON_BAIDU.String()
}
req.EndTime = &now
utils.SaveProvReq2DB(req, enums.ACTION_CREATE_STATUS)
return req.Nodes
}
timeout := utils.CreationTimeout(*req)
// Failed nodes that need to be deleted
tnodes := make([]*model.ProvNode, 0)
for _, instance := range instances {
var pnode *model.ProvNode
nodeState := string(instance.Status)
existsNode, exists := nodesMap[instance.InstanceId]
if exists {
pnode = existsNode
pnode.UpdateFromInstance(&instance)
delete(nodesMap, pnode.NodeID)
} else {
pnode = model.NewProvNodeFromInstance(&instance)
pnode.ReqID = req.ReqID
pnode.RCAccount = req.RCAccount
req.Nodes = append(req.Nodes, pnode)
}
if client.InstanceStateIsCreationFailed(nodeState) {
pnode.Result = enums.NODE_RESULT_FAIL.String()
if req.Status != string(enums.REQUEST_STATUS_RUNNING) {
req.Status = enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String()
}
tnodes = append(tnodes, pnode)
continue
}
if client.InstanceStateIsStarted(nodeState) {
if pnode.PrivateIPAddr != "" {
pnode.Result = enums.NODE_RESULT_SUCCEED.String()
continue
}
}
if timeout {
if pnode.Result != string(enums.NODE_RESULT_SUCCEED) {
pnode.Result = enums.NODE_RESULT_FAIL.String()
}
pnode.Status = nodeState
pnode.Msg = enums.E_BD_INSTANCE_CREATION_TIMEOUT.String()
tnodes = append(tnodes, pnode)
continue
}
pnode.Result = enums.NODE_RESULT_EXECUTING.String()
pnode.Status = nodeState
req.Status = enums.REQUEST_STATUS_RUNNING.String()
}
if len(nodesMap) > 0 {
if req.Status != string(enums.REQUEST_STATUS_RUNNING) {
req.Status = enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String()
logger.Debug("----- Set request result to fail -----", zap.Any("req", req))
req.Msg = enums.E_BD_INSTANCE_NOT_FOUND_ON_BAIDU.String()
}
for _, pnode := range nodesMap {
logger.Debug("----- Set pnode result to fail -----", zap.Any("pnode", pnode))
pnode.Result = enums.NODE_RESULT_FAIL.String()
pnode.Msg = enums.E_BD_INSTANCE_NOT_FOUND_ON_BAIDU.String()
}
}
if timeout {
req.Status = enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String()
}
if req.Status == enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String() ||
req.Status == enums.REQUEST_STATUS_COMPLETE.String() {
req.EndTime = &now
}
if len(tnodes) > 0 {
logger.Warn("========Delete the failed nodes", zap.Any("nodes", tnodes))
/*
// should not reach here, but just in case
provClient := client.GetProvCloudClient()
if _, err := provClient.ReleaseInstances(tnodes); err != nil {
// log error
logger.Error("Failed to release instances during creation status check", zap.Error(err))
}
*/
}
utils.SaveProvReq2DB(req, enums.ACTION_CREATE_STATUS)
return req.Nodes
}
// nodeTerminationStatus handles the status of node termination
func nodeTerminationStatus(instances []api.InstanceModel, dbreq *model.ProvReq) []*model.ProvNode {
now := time.Now().Truncate(time.Second)
instanceMap := make(map[string]*api.InstanceModel)
for _, instance := range instances {
instanceMap[instance.InstanceId] = &instance
}
timeout := utils.TerminationTimeout(*dbreq)
tnodes := make([]*model.ProvNode, 0)
for _, pnode := range dbreq.Nodes {
instance, exists := instanceMap[pnode.NodeID]
if !exists {
pnode.Result = enums.NODE_RESULT_SUCCEED.String()
pnode.Status = enums.NODE_STATUS_DELETED.String()
continue
}
pnode.UpdateFromInstance(instance)
nodeState := string(instance.Status)
if client.InstanceStateIsTerminated(nodeState) {
pnode.Result = enums.NODE_RESULT_SUCCEED.String()
} else if timeout {
pnode.Result = enums.NODE_RESULT_FAIL.String()
pnode.Msg = enums.E_BD_INSTANCE_TERMINATE_TIMEOUT.String()
tnodes = append(tnodes, pnode)
} else if client.InstanceStateIsDeleteFailed(nodeState) {
pnode.Result = enums.NODE_RESULT_EXECUTING.String()
dbreq.Status = enums.REQUEST_STATUS_RUNNING.String()
tnodes = append(tnodes, pnode)
} else {
pnode.Result = enums.NODE_RESULT_EXECUTING.String()
dbreq.Status = enums.REQUEST_STATUS_RUNNING.String()
}
}
if timeout {
dbreq.Msg = enums.E_BD_INSTANCE_TERMINATE_TIMEOUT.String()
dbreq.Status = enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String()
}
if dbreq.Status == enums.REQUEST_STATUS_COMPLETE_WITH_ERR.String() ||
dbreq.Status == enums.REQUEST_STATUS_COMPLETE.String() {
dbreq.EndTime = &now
}
if len(tnodes) > 0 {
logger.Warn("======Delete the failed nodes", zap.Any("nodes", tnodes))
/*TODO, disable it for test now.
provClient := client.GetProvCloudClient()
if _, err := provClient.ReleaseInstances(tnodes); err != nil {
logger.Error("Failed to release instances during creation status check", zap.Error(err))
}
*/
}
utils.SaveProvReq2DB(dbreq, enums.ACTION_DELETE_STATUS)
return dbreq.Nodes
}

View file

@ -0,0 +1,90 @@
package utils
import (
"bcctool/enums"
"os"
)
/*
This file defines environment variables that can be used by the Baidu Cloud provider plugin.
Below are defined by LSF.
PRO_LSF_TOP : Top level directory where LSF is installed. This is the same value as LSF_TOP in the lsf.conf file.
PRO_DATA_DIR : The data file directory. In most cases, this is <LSF_TOP>/work/<cluster>/resource_connector/.
PRO_LSF_LOGDIR : The location of the provider log. This is the same value as LSF_LOGDIR in the lsf.conf file.
PROVIDER_NAME : The value of "name" in the hostProviders.json file.
PRO_CONF_DIR : The value of "confPath" in the hostProviders.json file.
SCRIPT_OPTIONS : The value of "scriptOpption" in the hostProviders.json file.
In the new scheduler, To be compatible with the LSF, we use the new environment variables:
PRO_TOP <---> PRO_LSF_TOP
PRO_LOG_DIR <---> PRO_LSF_LOGDIR
*/
var (
Global_TopDir string
Global_DataDir string
Global_LogDir string
Global_ConfDir string
Global_ProviderName string
Global_ScriptOptions string
Global_HomeDir string // Home directory of the user running user_data.sh
Global_ClusterName string
)
// Envs must exist
// PRO_CONF_DIR
// PRO_DATA_DIR
// PRO_LOG_DIR
// PROVIDER_NAME default to "baidu" if not set
func InitEnvVars() enums.ErrCode {
code := enums.E_BD_SUCCESS
Global_TopDir = os.Getenv("PRO_TOP")
if Global_TopDir == "" {
Global_TopDir = os.Getenv("PRO_LSF_TOP")
}
Global_ConfDir = os.Getenv("PRO_CONF_DIR")
if Global_ConfDir == "" {
if Global_TopDir != "" {
Global_ConfDir = Global_TopDir + "/conf/resource_connector/baidu"
}
}
if Global_ConfDir == "" {
code = enums.E_BD_NO_CONFDIR
return code
}
Global_DataDir = os.Getenv("PRO_DATA_DIR")
if Global_DataDir == "" {
code = enums.E_BD_NO_DATADIR
return code
}
Global_LogDir = os.Getenv("PRO_LOG_DIR")
if Global_LogDir == "" {
Global_LogDir = os.Getenv("PRO_LSF_LOGDIR")
if Global_LogDir == "" {
if Global_TopDir != "" {
Global_LogDir = Global_TopDir + "/log"
}
}
}
if Global_LogDir == "" {
code = enums.E_BD_NO_LOGDIR
return code
}
Global_ProviderName = os.Getenv("PROVIDER_NAME")
if Global_ProviderName == "" {
Global_ProviderName = "baidu"
}
Global_ScriptOptions = os.Getenv("SCRIPT_OPTIONS")
Global_HomeDir = os.Getenv("PRO_HOME_DIR")
Global_ClusterName = os.Getenv("CLUSTER_NAME")
return code
}

View file

@ -0,0 +1,364 @@
package utils
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"bcctool/enums"
"bcctool/model"
"bcctool/provconst"
"go.uber.org/zap"
)
var (
Global_DB *model.ProvDBEntry
)
// LoadBdTemplates loads and parses the baiduprov_templates.json file.
// Returns a slice of BdTemplate and error.
func LoadProvTemplates() ([]model.ProvTemplate, error) {
// concat the full path of baiduprov_templates.json
fullFilePath := filepath.Join(Global_ConfDir, "conf", provconst.ProvTemplateFilename)
templatesArray, err := loadJSONFile[model.ProvTemplatesArray](fullFilePath)
if err != nil {
return nil, err
}
return templatesArray.Templates, nil
}
// LoadBdProvConfig loads and parses the baiduprov_config.json file.
// Returns a BdProvConfig and error.
func LoadProvConfig() (*model.ProvConfig, error) {
// concat the full path of baiduprov_templates.json
fullFilePath := filepath.Join(Global_ConfDir, "conf", provconst.ProvConfigFilename)
config, err := loadJSONFile[model.ProvConfig](fullFilePath)
if err != nil {
return nil, err
}
if config.CredentialFile == "" {
return nil, fmt.Errorf("credential file is not specified in the configuration")
}
// Load the client credential from the specified file
credential, err := parseClientCredentialFile(config.CredentialFile)
if err != nil {
return nil, fmt.Errorf("failed to parse client credential file: %w", err)
}
config.AK = credential.AK
config.SK = credential.SK
// Set it to be lower case to be compatible with level in logger.yaml
if config.LogLevel != "" {
config.LogLevel = strings.ToLower(config.LogLevel)
}
return &config, nil
}
func parseClientCredentialFile(fullFilePath string) (*model.ClientCredential, error) {
credential, err := loadJSONFile[model.ClientCredential](fullFilePath)
if err != nil {
return nil, fmt.Errorf("failed to parse client credential file: %w", err)
}
if credential.AK == "" || credential.SK == "" {
return nil, fmt.Errorf("client credential file is missing AK or SK")
}
return &credential, nil
}
func LoadNodeCreationInputFile(fullFilePath string) (*model.ProvNodeCreation, error) {
nodeCreation, err := loadJSONFile[model.ProvNodeCreation](fullFilePath)
if err != nil {
return nil, err
}
return &nodeCreation, nil
}
func LoadGetReturnRequestInputFile(fullFilePath string) (*model.ProvNodes, error) {
nodes, err := loadJSONFile[model.ProvNodes](fullFilePath)
if err != nil {
return nil, err
}
return &nodes, nil
}
func LoadNodeReturnInputFile(fullFilePath string) (*model.ProvNodes, error) {
nodes, err := loadJSONFile[model.ProvNodes](fullFilePath)
if err != nil {
return nil, err
}
return &nodes, nil
}
func LoadProvReqsFromInputFile(fullFilePath string) (*model.ProvReqs, error) {
reqs, err := loadJSONFile[model.ProvReqs](fullFilePath)
if err != nil {
return nil, err
}
return &reqs, nil
}
func loadProvDBEntry(fullFilePath string) (*model.ProvDBEntry, error) {
db, err := loadJSONFile[model.ProvDBEntry](fullFilePath)
if err != nil {
return nil, err
}
return &db, nil
}
func GetDB() *model.ProvDBEntry {
if Global_DB == nil {
Global_DB, _ = loadDBFile()
if Global_DB == nil {
Global_DB = model.NewProvDBEntry()
}
}
return Global_DB
}
func loadDBFile() (*model.ProvDBEntry, error) {
// Get data file path
df := getDataFilePath()
// Load existing requests
db, err := loadProvDBEntry(df)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// If the file does not exist, return an empty ProvDBEntry
Info("File does not exist, returning an empty ProvDBEntry", zap.String("filePath", df))
// Initialize an empty ProvDBEntry
return model.NewProvDBEntry(), nil
}
return nil, fmt.Errorf("failed to load prov db entry from file %s: %w", df, err)
}
// Initialize maps if they are nil
if db.NodesMap == nil {
db.NodesMap = make(map[string]*model.ProvNode)
}
if db.ReqMap == nil {
db.ReqMap = make(map[string]*model.ProvReq)
}
if db.RetReqMap == nil {
db.RetReqMap = make(map[string]*model.ProvReq)
}
for _, req := range db.Reqs {
if req != nil && req.ReqID != "" {
db.ReqMap[req.ReqID] = req
}
}
for _, retReq := range db.RetReqs {
if retReq != nil && retReq.ReqID != "" {
db.RetReqMap[retReq.ReqID] = retReq
}
}
for _, node := range db.Nodes {
if node != nil && node.NodeID != "" {
db.NodesMap[node.NodeID] = node
if node.ReqID != "" {
// Associate the node with its request
if req, exists := db.ReqMap[node.ReqID]; exists {
req.AddNode(node)
}
}
if node.RetID != "" {
// Associate the node with its return request
if retReq, exists := db.RetReqMap[node.RetID]; exists {
retReq.AddNode(node)
}
}
}
}
return db, nil
}
func mapValues[K comparable, V any](m map[K]V) []V {
values := make([]V, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}
// LoadJSONFile loads and parses a JSON file into the specified type T.
// Usage: obj, err := LoadJSONFile[YourType](jsonFile)
func loadJSONFile[T any](jsonFile string) (T, error) {
var obj T
file, err := os.Open(jsonFile)
if err != nil {
return obj, err
}
defer file.Close()
decoder := json.NewDecoder(file)
if err := decoder.Decode(&obj); err != nil {
return obj, err
}
return obj, nil
}
// SaveProvReq2DB saves request data to database
func SaveProvReq2DB(req *model.ProvReq, acton enums.ActionCode) {
if req == nil {
return
}
db := GetDB()
switch acton {
case enums.ACTION_CREATE_REQUEST:
db.Nodes = append(db.Nodes, req.Nodes...)
for _, node := range req.Nodes {
// Associate the node with its request
node.ReqID = req.ReqID
db.NodesMap[node.NodeID] = node
}
db.Reqs = append(db.Reqs, req)
db.ReqMap[req.ReqID] = req
case enums.ACTION_DELETE_REQUEST:
for _, node := range req.Nodes {
// Associate the node with its return request
node.RetID = req.ReqID
pnode, exists := db.NodesMap[node.NodeID]
if exists {
pnode.UpdateFromNode(node)
}
}
db.RetReqs = append(db.RetReqs, req)
db.RetReqMap[req.ReqID] = req
case enums.ACTION_CREATE_STATUS:
for _, node := range req.Nodes {
pnode, exists := db.NodesMap[node.NodeID]
if exists {
pnode.UpdateFromNode(node)
} else {
node.ReqID = req.ReqID
db.Nodes = append(db.Nodes, node)
db.NodesMap[node.NodeID] = node
}
}
case enums.ACTION_DELETE_STATUS:
for _, node := range req.Nodes {
// Associate the node with its return request
node.RetID = req.ReqID
pnode, exists := db.NodesMap[node.NodeID]
if exists {
pnode.UpdateFromNode(node)
}
}
default:
Error("Unsupported action code for saving request data", zap.Int("actionCode", int(acton)))
}
SaveDB()
}
func CleanTimeoutRequests() {
Info("----- Entering cleanTimeoutRequests -----")
// Clean up delete requests that have timed out
cleanDeleteReqFromDB()
// Clean up create instances requests that have timed out
cleanCreateReqFromDB()
SaveDB()
Info("----- Exiting cleanTimeoutRequests -----")
}
func cleanDeleteReqFromDB() {
db := GetDB()
// Get all requests from the database
allRetReqs := db.RetReqs
deleted := false
for _, req := range allRetReqs {
if req.CanBeCleaned() {
Info("Cleaned up timeout request", zap.String("req", req.String()))
for _, node := range req.Nodes {
delete(db.NodesMap, node.NodeID)
}
// Remove the request from db.RetReqMap
delete(db.RetReqMap, req.ReqID)
deleted = true
}
}
if deleted {
db.Nodes = mapValues(db.NodesMap)
db.RetReqs = mapValues(db.RetReqMap)
}
}
func cleanCreateReqFromDB() {
db := GetDB()
deleted := false
for _, req := range db.Reqs {
reqNodesMap := make(map[string]*model.ProvNode)
for _, node := range req.Nodes {
if pnode, exists := db.NodesMap[node.NodeID]; exists {
reqNodesMap[pnode.NodeID] = pnode
}
}
req.Nodes = mapValues(reqNodesMap)
if req.CanBeCleaned() && len(req.Nodes) == 0 {
// If the request is complete but has no nodes, remove it from the map
Info("Cleaned up timeout request", zap.String("req", req.String()))
delete(db.ReqMap, req.ReqID)
deleted = true
}
}
if deleted {
db.Reqs = mapValues(db.ReqMap)
}
}
// GetDataFile returns the data file path
func getDataFilePath() string {
dataFileName := Global_ProviderName + provconst.ProvDataFileName
return filepath.Join(Global_DataDir, dataFileName)
}
func SaveDB() {
db := GetDB()
db_to_save := &model.ProvDBEntry{
Nodes: db.Nodes,
}
// set req.Nodes and RetReq.Nodes to nil to prevent dump them to db file
for _, req := range db.Reqs {
// only save the required fields
saveReq := model.NewProvReqFromReq4Save(req)
db_to_save.Reqs = append(db_to_save.Reqs, saveReq)
}
for _, retReq := range db.RetReqs {
saveReq := model.NewProvReqFromReq4Save(retReq)
db_to_save.RetReqs = append(db_to_save.RetReqs, saveReq)
}
// Save the current state of the database entry to a JSON file
data, err := json.MarshalIndent(db_to_save, "", " ")
if err != nil {
fmt.Printf("Error marshalling ProvDBEntry: %v\n", err)
return
}
// Get the data file path
dataFilePath := getDataFilePath()
// Write the data to the file
err = os.WriteFile(dataFilePath, data, 0644)
if err != nil {
fmt.Printf("Error writing ProvDBEntry to file %s: %v\n", dataFilePath, err)
}
}

View file

@ -0,0 +1,190 @@
package utils
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"gopkg.in/yaml.v2"
)
var (
Log *zap.Logger
once sync.Once
config Config
)
type Config struct {
Logging struct {
Level string `yaml:"level"`
Encoding string `yaml:"encoding"`
OutputPath string `yaml:"outputPath"`
File struct {
MaxSize int `yaml:"maxSize"`
MaxBackups int `yaml:"maxBackups"`
MaxAge int `yaml:"maxAge"`
Compress bool `yaml:"compress"`
} `yaml:"file"`
EncoderConfig struct {
MessageKey string `yaml:"messageKey"`
LevelKey string `yaml:"levelKey"`
TimeKey string `yaml:"timeKey"`
NameKey string `yaml:"nameKey"`
CallerKey string `yaml:"callerKey"`
FunctionKey string `yaml:"functionKey"`
StacktraceKey string `yaml:"stacktraceKey"`
EncodeTime string `yaml:"timeEncoder"`
EncodeLevel string `yaml:"levelEncoder"`
} `yaml:"encoderConfig"`
} `yaml:"logging"`
}
func InitLogger() error {
var err error
once.Do(func() {
err = loadLoggerConfig()
})
return err
}
func loadLoggerConfig() error {
// read logger configuration file
configPath := filepath.Join(Global_ConfDir, "conf", "logger.yaml")
// read the yaml file directly
data, err := os.ReadFile(configPath)
if err != nil {
DiagnoseLogCreationError("Failed to read logger.yaml file", err)
return err
}
if err := yaml.Unmarshal(data, &config); err != nil {
DiagnoseLogCreationError("Failed to parse logger configuration file", err)
return err
}
// LogLevel in Prov_Config overrides the one in logger.yaml
if Prov_Config.LogLevel != "" {
config.Logging.Level = Prov_Config.LogLevel
}
// Handle both formats for OutputPath
if Global_LogDir != "" && Global_ProviderName != "" {
logFileName := fmt.Sprintf("%s.log", Global_ProviderName)
config.Logging.OutputPath = filepath.Join(Global_LogDir, logFileName)
} else if !filepath.IsAbs(config.Logging.OutputPath) {
// If OutputPath is relative, prepend the log directory
cwd, _ := os.Getwd()
config.Logging.OutputPath = filepath.Join(cwd, "log", config.Logging.OutputPath)
}
// Get the directory from the absolute path
logFilePath := config.Logging.OutputPath // it store the absolute path
logDir := filepath.Dir(logFilePath) // Extract the directory
// Create the directory if it does not exist
if err := os.MkdirAll(logDir, 0755); err != nil {
DiagnoseLogCreationError("Failed to create logs directory", err)
return err
}
Log, err = createLogger(config)
if err != nil {
DiagnoseLogCreationError("Failed to create logger", err)
return err
}
return nil
}
func createLogger(config Config) (*zap.Logger, error) {
configLevel, err := zapcore.ParseLevel(config.Logging.Level)
if err != nil {
return nil, err
}
// create log file and set the permission
file, err := os.OpenFile(config.Logging.OutputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil, err
}
file.Close()
// configure lumberjack for log rotation
logWriter := &lumberjack.Logger{
Filename: config.Logging.OutputPath,
MaxSize: config.Logging.File.MaxSize,
MaxBackups: config.Logging.File.MaxBackups,
MaxAge: config.Logging.File.MaxAge,
Compress: config.Logging.File.Compress,
}
// Create cores for different log levels
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
MessageKey: config.Logging.EncoderConfig.MessageKey,
LevelKey: config.Logging.EncoderConfig.LevelKey,
TimeKey: config.Logging.EncoderConfig.TimeKey,
NameKey: config.Logging.EncoderConfig.NameKey,
CallerKey: config.Logging.EncoderConfig.CallerKey,
StacktraceKey: "", // disable stacktrace
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
ConsoleSeparator: " ",
}),
zapcore.AddSync(logWriter),
configLevel,
)
logger := zap.New(core,
zap.AddCaller(), // caller info
zap.AddStacktrace(zap.ErrorLevel), // show error stacktrace
zap.AddCallerSkip(1), // skip one layer of caller
)
return logger, nil
}
func Info(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(false)).Info(msg, fields...)
}
func Error(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(false)).Error(msg, fields...)
}
func Warn(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(false)).Warn(msg, fields...)
}
func Debug(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(true)).Debug(msg, fields...)
}
func Fatal(msg string, fields ...zap.Field) {
Log.WithOptions(zap.WithCaller(false)).Fatal(msg, fields...)
}
// DiagnoseLogCreationError is a helper function to diagnose the error when creating log file
func DiagnoseLogCreationError(context string, err error) {
// construct the error message
logMsg := fmt.Sprintf("[%s] %s: %v\n", time.Now().Format("2006-01-02 15:04:05"), context, err)
// define logger_error.log file path
logFilePath := filepath.Join(Global_TopDir, "log", "logger_error.log")
// open log file
f, fileErr := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if fileErr != nil {
fmt.Printf("Failed to open logger_error.log: %v\n", fileErr)
// if failed to open log file, print the error message to console
fmt.Print(logMsg)
return
}
defer f.Close()
// write the error message
if _, writeErr := f.WriteString(logMsg); writeErr != nil {
fmt.Printf("Failed to write to logger_error.log: %v\n", writeErr)
// if failed to write to log file, print the error message to console
fmt.Print(logMsg)
}
}

View file

@ -0,0 +1,105 @@
package utils
import (
"bcctool/enums"
"bcctool/model"
"bcctool/provconst"
"fmt"
"os"
"time"
"go.uber.org/zap"
)
var (
// Global config and templates
Prov_Config *model.ProvConfig
Prov_Templates []model.ProvTemplate
TemplatesMap map[string]model.ProvTemplate
regionMap = map[string]string{
"bj": "bcc.bd.baidubce.com",
"sh": "bcc.sh.baidubce.com",
"gz": "bcc.gz.baidubce.com",
"cd": "bcc.cd.baidubce.com",
"hk": "bcc.hk.baidubce.com",
"bd": "bcc.bd.baidubce.com",
}
)
func InitProv() {
errCode := InitEnvVars()
if errCode != enums.E_BD_SUCCESS {
// If the environment variables are not set correctly, return the error code
fmt.Println("Error initializing environment variables:", errCode.String())
os.Exit(-1)
}
var err error // add this line to declare err
Prov_Config, err = LoadProvConfig() // assign to package-level variable
if err != nil {
ExitWithError(enums.E_BD_LOAD_CLIENT_CREDENTIAL_FAILED, err)
}
// init logger
if err := InitLogger(); err != nil {
fmt.Println("Error initializing logger:", err)
os.Exit(-1)
}
Debug("********** Initialize Logger done!")
Prov_Config.Endpoint = regionMap[Prov_Config.Region]
if Prov_Config.Endpoint == "" {
ExitWithError(enums.E_BD_REGION_NOT_SUPPORTED, err)
}
Debug("********** LoadProvConfig done!")
Prov_Templates, err = LoadProvTemplates()
if err != nil {
ExitWithError(enums.E_BD_LOAD_TEMPLATE_FAILED, err)
}
TemplatesMap = make(map[string]model.ProvTemplate)
// fill TemplatesMap
for i := range Prov_Templates {
tpl := Prov_Templates[i]
TemplatesMap[tpl.TemplateId] = tpl
}
Debug("********** LoadProvTemplates done!")
}
func ExitWithError(code enums.ErrCode, err error) {
var errMsg string
if err != nil {
errMsg = err.Error()
} else {
errMsg = ""
}
ret := model.NewProvResult4err(int(code), code.String(), errMsg)
ret.PrintJSON()
os.Exit(-1)
}
func Result(r *model.ProvResult) {
// Print the result as JSON to stdout
err := r.PrintJSON()
if err != nil {
Error("Failed to parse result to json", zap.Error(err))
}
}
func CreationTimeout(req model.ProvReq) bool {
launchTime := req.GetLaunchTime()
timeout := provconst.REQUEST_CREATION_TIMEOUT
now := time.Now()
return int64(now.Sub(launchTime).Seconds()) >= timeout
}
func TerminationTimeout(req model.ProvReq) bool {
launchTime := req.GetLaunchTime()
timeout := provconst.REQUEST_TERMINATION_TIMEOUT
now := time.Now()
return int64(now.Sub(launchTime).Seconds()) >= timeout
}

View file

@ -0,0 +1,65 @@
TOP=$(shell pwd)/../../..
include $(TOP)/config/Make.config
UNITY_CFLAGS = -DUNITY_OUTPUT_COLOR=1 -I$(UNITY_PATH)
CFLAGS +=-I. -I./include -I../../include -I../../lib \
$(XCE_BASIC_CFLAGS) $(SSL_CFLAGS) $(JSON_CFLAGS) \
$(CYAML_CFLAGS) $(YAML_CFLAGS) $(UNITY_CFLAGS)
LDFLAGS += -L../../lib -lxce \
$(XCE_BASIC_LDFLAGS) $(SSL_LDFLAGS) $(JSON_LDFLAGS) \
$(CYAML_LDFLAGS) $(YAML_LDFLAGS)
ifneq (($WITH_NO_PIE),)
LDFLAGS += -no-pie
endif
TARGETS=libautoscaler.a test run_test
LIB_XCE = ../../lib/libxce.a
libscaler_SRCS=$(wildcard *.c)
libscaler_OBJS=$(libscaler_SRCS:.c=.o)
scaler_test_SRCS = $(wildcard testsuite/*.c) $(UNITY_PATH)/unity.c
scaler_test_OBJS = $(foreach var, $(scaler_test_SRCS:.c=.o), $(notdir $(var)))
.PHONY: all clean targets depend
all: depend $(TARGETS)
$(libscaler_SRCS): $(LIB_XCE)
libautoscaler.a: $(libscaler_OBJS)
$(AR) rcs $@ $(libscaler_OBJS)
$(RANLIB) $@
$(LIB_XCE):
$(MAKE) -C ../../lib
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
run_test: test
@echo
@echo "Running autoscaler unity tests"
@./test
@echo
test: libautoscaler.a ../../lib/libxce.a depend $(scaler_test_OBJS)
$(CC) -o $@ $(scaler_test_OBJS) \
-L. -lautoscaler $(LDFLAGS)
define mk_test_obj_rule
$(notdir $(1:.c=.o)): $(1) $(UNITY_PATH)/unity.h
$(CC) -c -o $$@ $(CFLAGS) $(1)
endef
$(foreach var,$(scaler_test_SRCS), $(eval $(call mk_test_obj_rule,$(var))))
depend: $(libscaler_SRCS)
CC="$(CC)" $(MKDEP) $(CFLAGS) $(libscaler_SRCS)
clean:
$(RM) $(TARGETS) *.o *~ .*~ .depend
-include .depend

View file

@ -0,0 +1,10 @@
{
"providers":[
{
"name": "baidu",
"type": "googleProv",
"confPath": "/tmp/autoscaler/baidu",
"scriptPath": "/tmp/autoscaler/baidu"
}
]
}

View file

@ -0,0 +1,5 @@
providers:
- name: baidu
type: baiduProv
confPath: /tmp/autoscaler/baidu
scriptPath: /tmp/autoscaler/baidu

View file

@ -0,0 +1,35 @@
#ifndef _OBJ_MGR_H_
#define _OBJ_MGR_H_
#include "scaler.h"
#define SC_PROVIDER "provider"
#define SC_TEMPLATE "template"
#define SC_REQUEST "request"
#define SC_INSTANCE "instance"
#define SC_DEMAND "demand"
extern int scaler_obj_mgr_init(scaler_obj_mgr_t*, zarray_item_killer_t);
extern void scaler_obj_mgr_fini(scaler_obj_mgr_t*);
extern int scaler_obj_mgr_count(scaler_obj_mgr_t*);
extern int provider_add(scaler_obj_mgr_t*, scaler_provider_t*);
extern scaler_provider_t* provider_get(scaler_obj_mgr_t*, char*);
extern int template_add(scaler_obj_mgr_t*, scaler_template_t*);
extern scaler_template_t* template_get(scaler_obj_mgr_t*, char*);
extern scaler_template_t* template_get_by_id(scaler_obj_mgr_t*, scaler_template_id_t*);
extern int template_del(scaler_obj_mgr_t*, char*);
extern int template_cmp(const void* a, const void* b);
extern int merge_templates(scaler_obj_mgr_t*, scaler_obj_mgr_t*, char*);
extern int request_add(scaler_obj_mgr_t*, scaler_request_t*);
extern scaler_request_t* request_get(scaler_obj_mgr_t*, char*);
extern int request_update_status(scaler_obj_mgr_t*, char*, scaler_req_status_t, time_t);
extern int request_del(scaler_obj_mgr_t*, char*);
extern int instance_add(scaler_obj_mgr_t*, scaler_instance_t*);
extern scaler_instance_t* instance_get(scaler_obj_mgr_t*, char*);
extern int instance_update_status(scaler_obj_mgr_t*, char*, scaler_alloc_status_t, char*, char*);
extern int instance_del(scaler_obj_mgr_t*, char*);
#endif

View file

@ -0,0 +1,13 @@
#ifndef _PLUG_MGR_H_
#define _PLUG_MGR_H_
#include <cjson/cJSON.h>
#include "scaler/types.h"
extern int get_available_templates(char* prov_name);
extern int trigger_create(scaler_demand_t* demand);
extern int trigger_release(char* prov_name, instance_id_list_t* inst_ids);
extern int get_request_status(scaler_request_t* req);
extern int get_return_request(char* prov_name, instance_id_list_t* inst_ids);
#endif

View file

@ -0,0 +1,23 @@
#ifndef _SCALER_CONF_H_
#define _SCALER_CONF_H_
#include "scaler.h"
// Structure for YAML provider configuration
typedef struct {
char* name;
char* type;
char* confPath;
char* scriptPath;
char* scriptOptions;
} yaml_provider_t;
// Structure for YAML providers configuration
typedef struct {
yaml_provider_t* providers;
size_t providers_count;
} yaml_providers_config_t;
extern int load_providers(const char*);
#endif

View file

@ -0,0 +1,58 @@
#ifndef _SCALER_H_
#define _SCALER_H_
#include <pthread.h>
#include <stdlib.h>
#include <time.h>
#include <basiclib/list.h>
#include "config.h"
#include "htab.h"
#include "mylock.h"
#include "scaler/types.h"
#include "xce/types.h"
#include "xlist.h"
#include "zarray.h"
// in seconds
#define DEFAULT_CHECK_INTERVAL 30
typedef struct {
char providers_file[XCE_MAXPATH_LEN];
int check_interval;
} scaler_conf_t;
typedef struct {
zarray_t array;
htab_t tab;
mylock_t lock;
} scaler_obj_mgr_t;
typedef struct demand_list {
xcelist_t* list;
mylock_t lock;
pthread_cond_t cond;
} scaler_demand_queue_t;
typedef struct {
scaler_conf_t configs;
scaler_obj_mgr_t providers;
scaler_obj_mgr_t templates;
scaler_obj_mgr_t requests;
scaler_obj_mgr_t instances;
scaler_demand_queue_t demands;
mylock_t lock;
pthread_t threads[2];
int running;
#define SCALER_MENU_CHANGED 0x1
int flag;
} autoscaler_context_t;
extern autoscaler_context_t g_scaler_ctx;
extern int scaler_init(char* providers_file);
extern int scaler_start();
extern void* scaler_monitor(void* arg);
extern void* scaler_demand_handler(void* arg);
extern int publish_scaler_demand(scaler_demand_array_t* demands);
#endif

View file

@ -0,0 +1,143 @@
#ifndef _SCALER_TYPES_H_
#define _SCALER_TYPES_H_
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
#define XCE_MAXIP_LEN XCE_MAXNAME_LEN
typedef enum {
GET_AVAILABLE_TEMPLATES = 0,
REQUEST_MACHINES,
RETURN_MACHINES,
GET_REQUEST_STATUS,
GET_RETURN_REQUEST,
NUMBER_OF_ACTIONS
} INTERFACE_ACTION;
typedef struct {
char* name;
char* type;
char* confPath;
char* scriptPath;
char* scripts[NUMBER_OF_ACTIONS];
int enabled;
char* scriptOptions;
} scaler_provider_t;
typedef struct {
char* provider_name;
char* template_id;
} scaler_template_id_t;
typedef enum {
SCALER_ATTR_TYPE_NULL = 0,
SCALER_ATTR_TYPE_BOOL,
SCALER_ATTR_TYPE_NUM,
SCALER_ATTR_TYPE_STR
} scaler_attr_type_t;
typedef struct {
char* name;
scaler_attr_type_t type;
union {
bool bool_val;
float num_val;
char* str_val;
} v;
} scaler_template_attr_t;
// Template available number of quota and current pricing (for spot/bid instances)
typedef struct {
char* id; // unique id: __as__provider#template
scaler_template_id_t tpl_id;
int num_attrs;
scaler_template_attr_t* attrs;
int max_number;
int priority;
int avail_number;
float price;
time_t last_update_time;
} scaler_template_t;
typedef enum {
ALLOC_STATUS_NONE = 0,
ALLOC_STATUS_ALLOCATED, /* get a running instance from cloud */
ALLOC_STATUS_RECLAIMED, /* cloud reclaimed this instance */
ALLOC_STATUS_RELINQUISH_REQ_SENT, /* request is sent to scheduler that it should relinquish this instance */
ALLOC_STATUS_RELINQUISHED, /* scheduler relinquished this instance */
ALLOC_STATUS_DEALLOCATE_REQ_SENT, /* release/deallocate request is sent to cloud to terminate this instance */
ALLOC_STATUS_DONE
} scaler_alloc_status_t;
// Allocated host tracking
typedef struct {
char* instance_id;
char* hostname;
char* private_ip;
char* public_ip;
time_t launch_time;
scaler_alloc_status_t alloc_status;
char* inst_state;
scaler_template_id_t tpl_id;
char* request_id;
char* relinquish_id;
time_t last_update_time;
} scaler_instance_t;
typedef enum {
SCALER_REQ_TYPE_REQ = 0,
SCALER_REQ_TYPE_RET
} scaler_req_type_t;
typedef enum {
REQ_STATUS_NEW = 0,
REQ_STATUS_RUNNING,
REQ_STATUS_COMPLETED,
REQ_STATUS_COMPLETE_WITH_ERROR
} scaler_req_status_t;
// Machine request tracking
typedef struct {
char* request_id;
scaler_req_type_t type;
scaler_req_status_t status;
time_t launch_time;
scaler_template_id_t tpl_id;
int count;
time_t last_update_time;
} scaler_request_t;
typedef struct {
scaler_template_id_t tpl_id;
int count;
} scaler_demand_t;
typedef struct {
int num;
scaler_demand_t* data;
} scaler_demand_array_t;
typedef struct _scaler_demand_ent {
struct _scaler_demand_ent* forw;
struct _scaler_demand_ent* back;
scaler_demand_array_t* demands;
} scaler_demand_ent_t;
typedef struct {
char* instance_id;
char* hostname;
} scaler_instance_id_t;
typedef struct {
int num;
scaler_instance_id_t* instance_ids;
} instance_id_list_t;
typedef struct {
int num;
scaler_instance_t* instances;
} scaler_instance_list_t;
#endif

View file

@ -0,0 +1,11 @@
#ifndef _SCALER_CTX_H_
#define _SCALER_CTX_H_
#include "scaler.h"
extern int scaler_ctx_init(char*);
extern int scaler_ctx_is_menu_changed();
extern int scaler_ctx_reset_menu_changed();
extern void scaler_ctx_fini(autoscaler_context_t* ctx);
#endif

View file

@ -0,0 +1,20 @@
#ifndef __JSON_UTILS_H__
#define __JSON_UTILS_H__
#include <cjson/cJSON.h>
#include <cjson/s2j.h>
#include "scaler/types.h"
extern int write_json_to_file(const char* filename, cJSON* json);
extern int read_json_file(const char* filename, cJSON** json);
extern cJSON* s2j_demand_t(scaler_demand_t* demand);
extern cJSON* s2j_instance_id_list_t(instance_id_list_t* instance_ids);
extern cJSON* s2j_request_id_list_t(scaler_request_t* req);
extern scaler_instance_t* j2s_instance_t(cJSON* json, scaler_req_type_t req_type);
extern scaler_instance_id_t* j2s_instance_id_t(cJSON* json);
extern scaler_template_t* j2s_template_t(char* provider_name, cJSON* json);
extern cJSON* s2j_create_empty_obj();
extern char* attrs_to_str(scaler_template_attr_t* attrs, int num_attr);
extern int str_to_attr(char* str, scaler_template_attr_t** out_attrs);
#endif

View file

@ -0,0 +1,22 @@
#ifndef _SCALER_MISC_H_
#define _SCALER_MISC_H_
#include "scaler/types.h"
#include "xlist.h"
extern int my_snprintf(char* buf, size_t size, const char* fmt, ...);
extern int create_temp_file(char* file_name);
extern int my_strcmp(const char* p1, const char* p2);
extern char* my_strdup(char*);
extern int cmp_template_id(scaler_template_id_t* p1, scaler_template_id_t* p2);
extern void print_demand_list(scaler_demand_array_t*);
extern void print_request(void*);
extern void print_request_list(xlist_t* r_list);
extern void print_instance(void*);
extern void print_instance_list(xlist_t* i_list);
extern int float_cmp_ex(float a, float b, float ep);
extern int float_cmp(float a, float b);
#endif

View file

@ -0,0 +1,28 @@
#ifndef _SCALER_OBJ_H_
#define _SCALER_OBJ_H_
#include "scaler/types.h"
extern int provider_init(scaler_provider_t* p, char* name, char* type);
extern void provider_fields_fini(void* p);
extern void provider_fini(void*);
extern void template_id_fields_fini(scaler_template_id_t*);
extern void template_fields_fini(void*);
extern void template_fini(void*);
extern int template_cpy(scaler_template_t*, scaler_template_t*);
extern void request_fini(void*);
extern scaler_request_t* new_create_request(const char*, const char*, int, const char*);
extern scaler_request_t* new_release_request(const char*, char*);
extern void instance_id_fields_fini(void*);
extern void instance_id_fini(void*);
extern void instance_id_list_fini(void* p);
extern void instance_fini(void*);
extern void demand_fini(void* p);
extern void demand_array_fini(void* p);
extern void demand_ent_fini(void* p);
#endif

View file

@ -0,0 +1,478 @@
#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include "obj_mgr.h"
#include "scaler_misc.h"
#include "scaler_obj.h"
#include "utils.h"
int scaler_obj_mgr_init(scaler_obj_mgr_t* mgr, zarray_item_killer_t killer)
{
if (!mgr) {
return -1;
}
zarray_init(&mgr->array, 16, killer);
ht_init(&mgr->tab, 16, string_hash_key);
mylock_init(&mgr->lock);
return 0;
}
void scaler_obj_mgr_fini(scaler_obj_mgr_t* mgr)
{
if (!mgr) {
return;
}
mylock_lock(&(mgr->lock));
zarray_fini(&(mgr->array));
ht_fini(&(mgr->tab));
mylock_unlock(&(mgr->lock));
}
int scaler_obj_mgr_count(scaler_obj_mgr_t* mgr)
{
int count = 0;
if (!mgr) {
return -EINVAL;
}
mylock_lock(&(mgr->lock));
count = mgr->array.count;
mylock_unlock(&(mgr->lock));
return count;
}
static int _obj_mgr_add(scaler_obj_mgr_t* mgr,
void* obj,
char* key,
int* idx)
{
hent_t* e = NULL;
int i;
if (!mgr || !key || strlen(key) == 0) {
return -EINVAL;
}
e = ht_get(&(mgr->tab), (void*)key);
if (e != NULL) {
*idx = e->data;
return -EEXIST;
}
i = zarray_store(&(mgr->array), obj);
if (i < 0) {
return i;
}
e = ht_add(&(mgr->tab), (void*)key);
if (e == NULL) {
zarray_del(&(mgr->array), i);
return -errno;
}
e->data = i;
*idx = i;
return i;
}
static void* _obj_mgr_get(scaler_obj_mgr_t* mgr, char* key)
{
hent_t* e = NULL;
int idx = -1;
if (!mgr || !key) {
return NULL;
}
if (mgr->array.data == NULL) {
return NULL;
}
e = ht_get(&(mgr->tab), (void*)key);
if (!e) {
return NULL;
}
idx = (int)(e->data);
if (idx < 0) {
SET_ERROR(E_XCE_BAD_JOB);
return NULL;
}
return zarray_get(&(mgr->array), idx);
}
static int _obj_mgr_del(scaler_obj_mgr_t* mgr, const char* key)
{
hent_t* e = NULL;
int idx = -1;
if (!mgr || !key) {
return -EINVAL;
}
e = ht_get(&(mgr->tab), (void*)key);
if (!e) {
return -ENOENT;
}
idx = (int)(e->data);
if (idx < 0) {
return -ENOENT;
}
zarray_del(&(mgr->array), idx);
ht_del(&(mgr->tab), (void*)key);
return 0;
}
static void* obj_mgr_get_ex(scaler_obj_mgr_t* mgr, char* key)
{
// NOTE:
// NO lock here, because for update operation,
// It needs lock and update, then unlock.
return _obj_mgr_get(mgr, key);
}
static int obj_mgr_add_ex(scaler_obj_mgr_t* mgr,
void* obj,
char* key,
char* type)
{
int idx = -1;
int ret;
mylock_lock(&(mgr->lock));
ret = _obj_mgr_add(mgr, obj, key, &idx);
mylock_unlock(&(mgr->lock));
if (ret < 0) {
if (idx == -EEXIST) {
TRACE("Add duplicate <%s:%s>, ignoring", type, key);
} else {
ERROR("Failed to add <%s:%s>", type, key);
}
} else {
INFO("Succeed to add <%s:%s>", type, key);
}
return idx;
}
static int obj_mgr_del_ex(scaler_obj_mgr_t* mgr, char* key, char* type)
{
int ret;
if (!type) {
type = "UNKNOWN";
return -EINVAL;
}
mylock_lock(&(mgr->lock));
ret = _obj_mgr_del(mgr, key);
mylock_unlock(&(mgr->lock));
if (ret == 0) {
INFO("Succeed to delete <%s:%s> from memory", type, key);
} else {
ERROR("Failed to elete <%s:%s> from memory", type, key);
}
return ret;
}
int provider_add(scaler_obj_mgr_t* mgr, scaler_provider_t* p)
{
return obj_mgr_add_ex(mgr, p, p->name, SC_PROVIDER);
}
scaler_provider_t* provider_get(scaler_obj_mgr_t* mgr, char* name)
{
return (scaler_provider_t*)obj_mgr_get_ex(mgr, name);
}
int template_add(scaler_obj_mgr_t* mgr, scaler_template_t* tpl)
{
return obj_mgr_add_ex(mgr, tpl, tpl->id, SC_TEMPLATE);
}
scaler_template_t* template_get(scaler_obj_mgr_t* mgr, char* key)
{
return (scaler_template_t*)obj_mgr_get_ex(mgr, key);
}
scaler_template_t* template_get_by_id(scaler_obj_mgr_t* mgr, scaler_template_id_t* tid)
{
if (!mgr || !tid) {
return NULL;
}
char key[256];
snprintf(key, sizeof(key), "%s#%s", tid->provider_name, tid->template_id);
return (scaler_template_t*)obj_mgr_get_ex(mgr, key);
}
int template_del(scaler_obj_mgr_t* mgr, char* id)
{
return obj_mgr_del_ex(mgr, id, SC_TEMPLATE);
}
static int attribute_cmp(const scaler_template_attr_t* a1, const scaler_template_attr_t* a2)
{
if (!a1 || !a2) {
return -EINVAL;
}
if (strcmp(a1->name, a2->name) != 0)
return -1;
if (a1->type != a2->type)
return a1->type - a2->type;
switch (a1->type) {
case SCALER_ATTR_TYPE_BOOL:
if (a1->v.bool_val != a2->v.bool_val)
return a1->v.bool_val - a2->v.bool_val;
break;
case SCALER_ATTR_TYPE_NUM:
if (float_cmp(a1->v.num_val, a2->v.num_val) != 0)
return 1;
break;
case SCALER_ATTR_TYPE_STR:
if (a1->v.str_val && a2->v.str_val) {
int str_cmp = strcmp(a1->v.str_val, a2->v.str_val);
if (str_cmp != 0)
return str_cmp;
} else if (a1->v.str_val || a2->v.str_val) {
return (a1->v.str_val ? 1 : -1);
}
break;
default:
break;
}
return 0;
}
int template_cmp(const void* a, const void* b)
{
const scaler_template_t* t1 = (const scaler_template_t*)a;
const scaler_template_t* t2 = (const scaler_template_t*)b;
if (!t1 || !t2) {
return -EINVAL;
}
if (strcmp(t1->id, t2->id) != 0)
return -1;
if (strcmp(t1->tpl_id.provider_name, t2->tpl_id.provider_name) != 0)
return -1;
if (strcmp(t1->tpl_id.template_id, t2->tpl_id.template_id) != 0)
return -1;
if (t1->num_attrs != t2->num_attrs)
return t1->num_attrs - t2->num_attrs;
// Compare attrs array if both non-NULL
if (t1->attrs && t2->attrs) {
for (int i = 0; i < t1->num_attrs; ++i) {
// Compare attribute
if (attribute_cmp(&t1->attrs[i], &t2->attrs[i]) != 0) {
return -1;
}
}
} else if (t1->attrs || t2->attrs) {
return (t1->attrs ? 1 : -1);
}
if (t1->max_number != t2->max_number)
return t1->max_number - t2->max_number;
if (t1->priority != t2->priority)
return t1->priority - t2->priority;
if (t1->avail_number != t2->avail_number)
return t1->avail_number - t2->avail_number;
if (float_cmp(t1->price, t2->price) != 0)
return -1;
return 0;
}
static int _template_update(void* arg, void* data)
{
scaler_obj_mgr_t* mgr = (scaler_obj_mgr_t*)arg;
scaler_template_t* src = (scaler_template_t*)data;
scaler_template_t* tgt;
if (!mgr || !src) {
return -EINVAL;
}
tgt = template_get(mgr, src->id);
if (!tgt) {
tgt = (scaler_template_t*)calloc(1, sizeof(scaler_template_t));
template_cpy(tgt, src);
template_add(mgr, tgt);
g_scaler_ctx.flag |= SCALER_MENU_CHANGED;
return 0;
}
if (template_cmp(tgt, src) == 0) {
return 0;
}
mylock_lock(&(mgr->lock));
template_cpy(tgt, src);
g_scaler_ctx.flag |= SCALER_MENU_CHANGED;
mylock_unlock(&(mgr->lock));
return 0;
}
typedef struct {
scaler_obj_mgr_t* mgr;
xlist_t* list;
char* provider_name;
} tpl_arg_t;
static int _add_not_found_template_id(void* arg, void* data)
{
tpl_arg_t* a = (tpl_arg_t*)arg;
scaler_template_t* t = (scaler_template_t*)data;
if (!a || !t) {
return -1;
}
// Pick the template id for a provider if it is not found
// in new get_available_templates result.
if (strcmp(t->tpl_id.provider_name, a->provider_name) == 0
&& template_get(a->mgr, t->id) == NULL) {
xlist_store(a->list, strdup(t->id));
}
return 0;
}
static int _clean_not_found_template(void* data, void* arg)
{
char* id = (char*)data;
if (! id) {
return 0;
}
template_del(&g_scaler_ctx.templates, id);
g_scaler_ctx.flag |= SCALER_MENU_CHANGED;
FREEUP(id);
return 0;
}
int merge_templates(scaler_obj_mgr_t* tgt, scaler_obj_mgr_t* src, char* provider_name)
{
tpl_arg_t tmp;
xlist_t l;
if (!tgt || !src) {
return -EINVAL;
}
mylock_lock(&g_scaler_ctx.lock);
// update existing template or add new in src
zarray_iter(&src->array, _template_update, tgt);
// clean the template from tgt if it is not found in src
tmp.provider_name = provider_name;
tmp.mgr = src;
tmp.list = &l;
xlist_init(&l, 16);
zarray_iter(&tgt->array, _add_not_found_template_id, &tmp);
xlist_iter(&l, _clean_not_found_template, &tmp);
mylock_unlock(&g_scaler_ctx.lock);
return 0;
}
int request_add(scaler_obj_mgr_t* mgr, scaler_request_t* req)
{
if (!mgr || !req || !req->request_id) {
return -EINVAL;
}
return obj_mgr_add_ex(mgr, req, req->request_id, SC_REQUEST);
}
scaler_request_t* request_get(scaler_obj_mgr_t* mgr, char* key)
{
return (scaler_request_t*)obj_mgr_get_ex(mgr, key);
}
int request_update_status(scaler_obj_mgr_t* mgr, char* req_id, scaler_req_status_t status, time_t now)
{
scaler_request_t* req_in_mgr;
if (!mgr || !req_id) {
return -EINVAL;
}
mylock_lock(&(mgr->lock));
req_in_mgr = request_get(mgr, req_id);
if (!req_in_mgr) {
mylock_unlock(&(mgr->lock));
return -ENOENT;
}
if (req_in_mgr->status >= status) {
mylock_unlock(&(mgr->lock));
return 0;
}
req_in_mgr->status = status;
req_in_mgr->last_update_time = now;
mylock_unlock(&(mgr->lock));
return 0;
}
int request_del(scaler_obj_mgr_t* mgr, char* req_id)
{
return obj_mgr_del_ex(mgr, (char*)req_id, SC_REQUEST);
}
int instance_add(scaler_obj_mgr_t* mgr, scaler_instance_t* inst)
{
return obj_mgr_add_ex(mgr, inst, inst->instance_id, SC_INSTANCE);
}
scaler_instance_t* instance_get(scaler_obj_mgr_t* mgr, char* key)
{
return (scaler_instance_t*)obj_mgr_get_ex(mgr, key);
}
int instance_update_status(scaler_obj_mgr_t* mgr,
char* instance_id,
scaler_alloc_status_t alloc_status,
char* inst_state,
char* relinquish_id)
{
scaler_instance_t* inst;
if (!mgr || !instance_id) {
return -EINVAL;
}
mylock_lock(&(mgr->lock));
inst = instance_get(mgr, instance_id);
if (!inst) {
mylock_unlock(&(mgr->lock));
return -ENOENT;
}
if (inst->alloc_status >= alloc_status) {
mylock_unlock(&(mgr->lock));
return 0;
}
inst->alloc_status = alloc_status;
FREEUP(inst->inst_state);
if (inst_state) {
FREEUP(inst->inst_state);
inst->inst_state = strdup(inst_state);
}
inst->last_update_time = time(NULL);
if (relinquish_id) {
FREEUP(inst->relinquish_id);
inst->relinquish_id = strdup(relinquish_id);
}
mylock_unlock(&(mgr->lock));
return 0;
}
int instance_del(scaler_obj_mgr_t* mgr, char* instance_id)
{
return obj_mgr_del_ex(mgr, instance_id, SC_INSTANCE);
}

View file

@ -0,0 +1,500 @@
// Main functions:
#include <cjson/cJSON.h>
#include "obj_mgr.h"
#include "scaler.h"
#include "scaler_json.h"
#include "scaler_misc.h"
#include "scaler_obj.h"
#include "utils.h"
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
static cJSON* invoke(scaler_provider_t* prov, INTERFACE_ACTION ACTION, cJSON* in_json);
/*
* This function call invoke() to execute shell script of provider->scripts[GET_AVAILABLE_TEMPLATES]
* Get all available templates from the provider. Add the templates to g_scaler_ctx.templates.
*/
int get_available_templates(char* prov_name)
{
scaler_obj_mgr_t tmp_obj_mgr;
scaler_provider_t* prov = NULL;
cJSON* in_json = NULL;
cJSON* out_json = NULL;
cJSON* tpl_arr = NULL;
int i, ret = 0;
if (!prov_name) {
ERROR("Null provider_name");
return -1;
}
prov = provider_get(&g_scaler_ctx.providers, prov_name);
if (!prov) {
ERROR("Provider not found: %s", prov_name);
return -1;
}
in_json = s2j_create_empty_obj();
if (!in_json) {
ERROR("Failed to create input JSON");
return -1;
}
out_json = invoke(prov, GET_AVAILABLE_TEMPLATES, in_json);
cJSON_Delete(in_json);
if (!out_json) {
ERROR("Provider script invocation failed");
return -1;
}
tpl_arr = cJSON_GetObjectItem(out_json, "templates");
if (!tpl_arr || !cJSON_IsArray(tpl_arr)) {
ERROR("No 'templates' array in output");
cJSON_Delete(out_json);
return -1;
}
scaler_obj_mgr_init(&tmp_obj_mgr, template_fini);
for (i = 0; i < cJSON_GetArraySize(tpl_arr); ++i) {
cJSON* tpl_json = cJSON_GetArrayItem(tpl_arr, i);
scaler_template_t* tpl = NULL;
if (!tpl_json)
continue;
tpl = j2s_template_t(prov_name, tpl_json);
if (!tpl) {
continue;
}
template_add(&tmp_obj_mgr, tpl);
}
merge_templates(&g_scaler_ctx.templates, &tmp_obj_mgr, prov_name);
scaler_obj_mgr_fini(&tmp_obj_mgr);
cJSON_Delete(out_json);
return ret;
}
int trigger_create(scaler_demand_t* demand)
{
scaler_provider_t* prov = NULL;
scaler_request_t* req = NULL;
cJSON* in_json;
cJSON* out_json;
cJSON* req_id_item;
char req_id[XCE_MAXKEY_LEN];
int ret = -1;
if (!demand) {
return -1;
}
memset(req_id, 0, sizeof(req_id));
prov = provider_get(&g_scaler_ctx.providers, demand->tpl_id.provider_name);
if (!prov) {
ERROR("Provider not found: %s", demand->tpl_id.provider_name);
return -1;
}
in_json = s2j_demand_t(demand);
if (!in_json) {
ERROR("Failed to build input JSON");
return -1;
}
out_json = invoke(prov, REQUEST_MACHINES, in_json);
cJSON_Delete(in_json);
if (!out_json) {
ERROR("Provider script invocation failed");
return -1;
}
req_id_item = cJSON_GetObjectItem(out_json, "requestId");
if (req_id_item && cJSON_IsString(req_id_item)) {
strncpy(req_id, req_id_item->valuestring, sizeof(req_id) - 1);
req = new_create_request(demand->tpl_id.provider_name,
demand->tpl_id.template_id,
demand->count,
req_id);
ret = request_add(&g_scaler_ctx.requests, req);
if (ret < 0) {
ERROR("Failed to add request: %s", req_id);
cJSON_Delete(out_json);
return ret;
}
INFO("Request created successfully with ID: %s", req_id);
ret = 0;
} else {
ERROR("requestId not found in JSON output: %s", out_json->string);
}
cJSON_Delete(out_json);
return ret;
}
int trigger_release(char* prov_name, instance_id_list_t* inst_ids)
{
scaler_provider_t* prov = NULL;
cJSON* in_json = NULL;
cJSON* out_json = NULL;
cJSON* req_id_item;
char req_id[XCE_MAXKEY_LEN];
scaler_request_t* req = NULL;
int ret = -1;
if (!inst_ids) {
return -1;
}
memset(req_id, 0, sizeof(req_id));
prov = provider_get(&g_scaler_ctx.providers, prov_name);
if (!prov) {
ERROR("Provider not found: %s", prov_name);
return -1;
}
in_json = s2j_instance_id_list_t(inst_ids);
if (!in_json) {
ERROR("Failed to build input JSON");
return -1;
}
out_json = invoke(prov, RETURN_MACHINES, in_json);
cJSON_Delete(in_json);
if (!out_json) {
ERROR("Provider script invocation failed");
return -1;
}
req_id_item = cJSON_GetObjectItem(out_json, "requestId");
if (req_id_item && cJSON_IsString(req_id_item)) {
strncpy(req_id, req_id_item->valuestring, sizeof(req_id) - 1);
req = new_release_request(req_id, prov_name);
ret = request_add(&g_scaler_ctx.requests, req);
if (ret < 0) {
ERROR("Failed to add request: %s", req_id);
cJSON_Delete(out_json);
return ret;
}
for (int i = 0; i < inst_ids->num; i++) {
ret = instance_update_status(&g_scaler_ctx.instances,
inst_ids->instance_ids[i].instance_id,
ALLOC_STATUS_DEALLOCATE_REQ_SENT,
NULL,
req_id);
if (ret < 0) {
ERROR("Failed to mark instance %s as RELINQUISHED",
inst_ids->instance_ids[i].instance_id);
continue;
}
}
INFO("Request created successfully with ID: %s", req_id);
ret = 0;
} else {
ERROR("requestId not found in JSON output: %s", out_json->string);
}
cJSON_Delete(out_json);
return ret;
}
int get_request_status(scaler_request_t* req)
{
scaler_provider_t* prov = NULL;
cJSON* in_json = NULL;
cJSON* out_json = NULL;
cJSON* req_arr = NULL;
int i, ret = 0;
if (!req) {
ERROR("Null request parameter");
return -1;
}
prov = provider_get(&g_scaler_ctx.providers, req->tpl_id.provider_name);
if (!prov) {
ERROR("Provider not found: %s", req->tpl_id.provider_name);
return -1;
}
in_json = s2j_request_id_list_t(req);
if (!in_json) {
ERROR("Failed to build input JSON");
return -1;
}
out_json = invoke(prov, GET_REQUEST_STATUS, in_json);
cJSON_Delete(in_json);
if (!out_json) {
ERROR("Provider script invocation failed");
return -1;
}
req_arr = cJSON_GetObjectItem(out_json, "requests");
if (!req_arr || !cJSON_IsArray(req_arr)) {
ERROR("No 'requests' array in output");
cJSON_Delete(out_json);
return -1;
}
for (i = 0; i < cJSON_GetArraySize(req_arr); ++i) {
cJSON* req_obj = cJSON_GetArrayItem(req_arr, i);
cJSON* status_item;
scaler_req_status_t status = REQ_STATUS_NEW;
time_t now = time(NULL);
if (!req_obj)
continue;
cJSON* mach_arr = cJSON_GetObjectItem(req_obj, "machines");
if (mach_arr && cJSON_IsArray(mach_arr)) {
int j;
for (j = 0; j < cJSON_GetArraySize(mach_arr); ++j) {
cJSON* mach_obj = cJSON_GetArrayItem(mach_arr, j);
scaler_instance_t* inst;
if (!mach_obj)
continue;
inst = j2s_instance_t(mach_obj, req->type);
if (!inst)
continue;
if (req->type == SCALER_REQ_TYPE_REQ) {
// Ensure clean initialization
memset(&inst->tpl_id, 0, sizeof(inst->tpl_id));
inst->request_id = NULL;
// Safe string duplication with null checks
if (req->tpl_id.provider_name) {
inst->tpl_id.provider_name = strdup(req->tpl_id.provider_name);
}
if (req->tpl_id.template_id) {
inst->tpl_id.template_id = strdup(req->tpl_id.template_id);
}
if (req->request_id) {
inst->request_id = strdup(req->request_id);
}
int ret = instance_add(&g_scaler_ctx.instances, inst);
if (ret < 0 && ret != -EEXIST) {
ERROR("Failed to add instance %s, ret %d", inst->instance_id, ret);
instance_fini(inst);
}
} else if (req->type == SCALER_REQ_TYPE_RET) {
inst->relinquish_id = strdup(req->request_id);
instance_update_status(&g_scaler_ctx.instances,
inst->instance_id,
inst->alloc_status,
inst->inst_state,
NULL);
instance_fini(inst);
}
}
}
status_item = cJSON_GetObjectItem(req_obj, "status");
if (status_item && cJSON_IsString(status_item)) {
if (strcmp(status_item->valuestring, "complete") == 0) {
status = REQ_STATUS_COMPLETED;
} else if (strcmp(status_item->valuestring, "running") == 0) {
status = REQ_STATUS_RUNNING;
} else if (strcmp(status_item->valuestring, "complete_with_error") == 0) {
status = REQ_STATUS_COMPLETE_WITH_ERROR;
} else {
ERROR("Invalid request status %s", status_item->valuestring);
continue;
}
}
if (status <= req->status) {
INFO("Skipping request %s with status %d, current status %d",
req->request_id, status, req->status);
continue;
}
ret = request_update_status(&g_scaler_ctx.requests, req->request_id, status, now);
if (ret < 0) {
ERROR("Failed to update request status for %s, ret %d",
req->request_id, ret);
}
}
cJSON_Delete(out_json);
return ret;
}
int get_return_request(char* prov_name, instance_id_list_t* inst_ids)
{
scaler_provider_t* prov = NULL;
cJSON* in_json = NULL;
cJSON* out_json = NULL;
cJSON* req_arr = NULL;
int i;
if (!inst_ids) {
ERROR("Null instance_ids parameter");
return -1;
}
prov = provider_get(&g_scaler_ctx.providers, prov_name);
if (!prov) {
ERROR("Provider not found: %s", prov_name);
return -1;
}
in_json = s2j_instance_id_list_t(inst_ids);
if (!in_json) {
ERROR("Failed to build input JSON");
return -1;
}
out_json = invoke(prov, GET_RETURN_REQUEST, in_json);
cJSON_Delete(in_json);
if (!out_json) {
ERROR("Provider script invocation failed");
return -1;
}
req_arr = cJSON_GetObjectItem(out_json, "requests");
if (!req_arr || !cJSON_IsArray(req_arr)) {
INFO("No 'requests' array in output. No instance is reclaimed.");
cJSON_Delete(out_json);
return -1;
}
for (i = 0; i < cJSON_GetArraySize(req_arr); ++i) {
cJSON* req_obj = cJSON_GetArrayItem(req_arr, i);
scaler_instance_id_t* inst_id;
if (!req_obj)
continue;
inst_id = j2s_instance_id_t(req_obj);
if (!inst_id) {
ERROR("Failed to parse instance ID from request object");
continue;
}
instance_update_status(&g_scaler_ctx.instances,
inst_id->instance_id,
ALLOC_STATUS_RECLAIMED,
NULL,
NULL);
instance_id_fini(inst_id);
}
cJSON_Delete(out_json);
return 0;
}
// PRO_CONF_DIR
// PRO_DATA_DIR
// PRO_LOG_DIR
// PROVIDER_NAME default to "baidu" if not set
// TODO zcg need to set PRO_LOG_DIR
static int export_env(scaler_provider_t* prov)
{
setenv("PRO_CONF_DIR", prov->confPath, 1);
setenv("PRO_DATA_DIR", "/tmp", 1);
setenv("PRO_LOG_DIR", "/tmp", 1);
setenv("PROVIDER_NAME", prov->name, 1);
return 0;
}
static int write_input_json(const char* filename, cJSON* json)
{
FILE* f = fopen(filename, "w");
if (!f) {
ERROR("Failed to open file %s for writing", filename);
return -1;
}
char* json_str = cJSON_PrintUnformatted(json);
if (!json_str) {
ERROR("Failed to convert JSON to string");
fclose(f);
return -1;
}
fprintf(f, "%s\n", json_str);
free(json_str);
fclose(f);
return 0;
}
/*
* This is the abstracted function to invoke the provider script.
* It takes a JSON input, invokes the script, and returns a JSON output.
* All 5 interfaces can be called in this way.
*/
static cJSON* invoke(scaler_provider_t* prov, INTERFACE_ACTION ACTION, cJSON* in_json)
{
char in_file[64] = "/tmp/sc_in_XXXXXX";
char out_file[64] = "/tmp/sc_out_XXXXXX";
cJSON* out_json = NULL;
int ret;
pid_t pid;
if (!prov || !in_json) {
ERROR("Invalid input JSON");
return NULL;
}
// Create temporary files for input and output
if (create_temp_file(in_file) < 0 || create_temp_file(out_file) < 0) {
ERROR("Failed to create temporary files");
goto _OUT_;
}
write_input_json(in_file, in_json);
// Fork and exec the cmd to run the provider script
pid = fork();
if (pid == -1) {
ERROR("fork() failed");
goto _OUT_;
} else if (pid == 0) {
char cmd[XCE_MAXPATH_LEN];
int fd;
// Open output file (overwrite mode)
fd = open(out_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
ERROR("Create %s failed", out_file);
exit(EXIT_FAILURE);
}
// Build command to call the script
my_snprintf(cmd,
sizeof(cmd),
"%s/scripts/%s",
prov->scriptPath,
prov->scripts[ACTION]);
// Redirect stdout and stderr to file
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
// export environment variables
export_env(prov);
// Execute script
ret = execl("/bin/sh", "sh", cmd, "-f", in_file, (char*)NULL);
if (ret == -1) {
ERROR("execl() %s failed", cmd);
exit(EXIT_FAILURE); // Exit child process on failure
}
// If exec succeeds, it won't return here
exit(EXIT_SUCCESS);
} else {
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
TRACE("Script exited with status: %d", WEXITSTATUS(status));
} else {
INFO("Script terminated abnormally");
}
}
read_json_file(out_file, &out_json);
if (!out_json) {
ERROR("Failed to read JSON output from file: %s", out_file);
}
_OUT_:
// Clean up temporary files
unlink(in_file);
unlink(out_file);
return out_json;
}

View file

@ -0,0 +1,303 @@
#include "prov_conf.h"
#include "obj_mgr.h"
#include "scaler.h"
#include "scaler_misc.h"
#include "scaler_obj.h"
#include "utils.h"
#include <cjson/s2j.h>
#include <cjson/s2jdef.h>
#include <cyaml/cyaml.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define S2J_STRUCT_GET_var_string_ELEMENT(to_struct, from_json, _element) \
do { \
json_temp = cJSON_GetObjectItem(from_json, #_element); \
if (json_temp) { \
(to_struct)->_element = strdup(json_temp->valuestring); \
} \
} while (0)
const char* PROV_SCRIPTS[] = { "getAvailableTemplates.sh", "requestMachines.sh",
"requestReturnMachines.sh",
"getRequestStatus.sh", "getReturnRequests.sh" };
// CYAML schema definitions for YAML parsing
static const cyaml_schema_field_t yaml_provider_fields_schema[]
= { CYAML_FIELD_STRING_PTR("name", CYAML_FLAG_POINTER, yaml_provider_t,
name, 0, CYAML_UNLIMITED),
CYAML_FIELD_STRING_PTR("type", CYAML_FLAG_POINTER, yaml_provider_t,
type, 0, CYAML_UNLIMITED),
CYAML_FIELD_STRING_PTR("confPath", CYAML_FLAG_POINTER, yaml_provider_t,
confPath, 0, CYAML_UNLIMITED),
CYAML_FIELD_STRING_PTR("scriptPath", CYAML_FLAG_POINTER,
yaml_provider_t, scriptPath, 0, CYAML_UNLIMITED),
CYAML_FIELD_STRING_PTR(
"scriptOptions", CYAML_FLAG_OPTIONAL | CYAML_FLAG_POINTER,
yaml_provider_t, scriptOptions, 0, CYAML_UNLIMITED),
CYAML_FIELD_END };
static const cyaml_schema_value_t yaml_provider_schema = { CYAML_VALUE_MAPPING(
CYAML_FLAG_DEFAULT, yaml_provider_t, yaml_provider_fields_schema) };
static const cyaml_schema_field_t yaml_providers_config_fields_schema[]
= { CYAML_FIELD_SEQUENCE("providers", CYAML_FLAG_POINTER,
yaml_providers_config_t, providers,
&yaml_provider_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_END };
static const cyaml_schema_value_t yaml_providers_config_schema
= { CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, yaml_providers_config_t,
yaml_providers_config_fields_schema) };
static bool _isJsonFile(const char* filename)
{
if (!filename)
return false;
size_t len = strlen(filename);
if (len < 5)
return false; // ".json" is 5 chars
return (strcmp(filename + len - 5, ".json") == 0);
}
static bool _isYamlFile(const char* filename)
{
if (!filename)
return false;
size_t len = strlen(filename);
if (len < 5)
return false; // ".yaml" or ".yml" minimum
return (strcmp(filename + len - 5, ".yaml") == 0
|| (len >= 4 && strcmp(filename + len - 4, ".yml") == 0));
}
static char* _read_file_content(const char* filename)
{
long fsize;
char* buf;
FILE* fp;
fp = fopen(filename, "r");
if (!fp) {
ERROR("Cannot open file: %s", filename);
return NULL;
}
fseek(fp, 0, SEEK_END);
fsize = ftell(fp);
fseek(fp, 0, SEEK_SET);
buf = (char*)malloc(fsize + 1);
if (!buf) {
fclose(fp);
ERROR("Memory allocation failed");
return NULL;
}
fread(buf, 1, fsize, fp);
buf[fsize] = '\0';
fclose(fp);
return buf;
}
// Verify if the provider conf path and scripts path are valid
static int _verifyProvider(scaler_provider_t* prov)
{
char path[XCE_MAXPATH_LEN];
if (!prov)
return -1;
// 1. Check if <confPath>/conf and <scriptPath>/scripts directories exist
my_snprintf(path, sizeof(path), "%s/conf", prov->confPath);
if (access(path, F_OK) != 0) {
ERROR("Directory not found: %s", path);
return -ENOENT;
}
my_snprintf(path, sizeof(path), "%s/scripts", prov->scriptPath);
if (access(path, F_OK) != 0) {
ERROR("Directory not found: %s", path);
return -ENOENT;
}
// 2. Check scripts
for (int i = 0; i < NUMBER_OF_ACTIONS; ++i) {
my_snprintf(path, sizeof(path), "%s/scripts/%s", prov->scriptPath,
prov->scripts[i]);
if (access(path, F_OK) != 0) {
ERROR("Script file not found: %s", path);
return -ENOENT;
}
}
return 0;
}
/*
* Load scaler configuration from file
*/
static int _load_providers_json(const char* config_file)
{
cJSON* js_prov;
cJSON* root;
cJSON* providers;
char* buf;
buf = _read_file_content(config_file);
if (!buf) {
return -1;
}
// Parse JSON
root = cJSON_Parse(buf);
FREEUP(buf);
if (!root) {
ERROR("Failed to parse JSON config");
return -1;
}
providers = cJSON_GetObjectItem(root, "providers");
if (!providers || !cJSON_IsArray(providers)) {
cJSON_Delete(root);
ERROR("No 'providers' array in config");
return -1;
}
cJSON_ArrayForEach(js_prov, providers)
{
cJSON* json_temp;
// Use s2j to convert JSON to struct
scaler_provider_t* prov
= (scaler_provider_t*)calloc(1, sizeof(scaler_provider_t));
if (!prov) {
continue;
}
s2j_struct_get_basic_element(prov, js_prov, var_string, name);
s2j_struct_get_basic_element(prov, js_prov, var_string, type);
s2j_struct_get_basic_element(prov, js_prov, var_string, confPath);
s2j_struct_get_basic_element(prov, js_prov, var_string, scriptPath);
s2j_struct_get_basic_element(prov, js_prov, var_string, scriptOptions);
for (int i = 0; i < NUMBER_OF_ACTIONS; ++i) {
prov->scripts[i] = strdup(PROV_SCRIPTS[i]);
}
if (_verifyProvider(prov) < 0) {
ERROR("Failed to check provider %s. Make sure all scripts exists.",
prov->name);
provider_fields_fini(prov);
continue;
}
if (provider_add(&g_scaler_ctx.providers, prov) < 0) {
provider_fini(prov);
continue;
}
}
cJSON_Delete(root);
return 0;
}
static int _load_providers_yaml(const char* config_file)
{
yaml_providers_config_t* yaml_config = NULL;
cyaml_err_t err;
// CYAML configuration
static cyaml_config_t config = {
#if 0
/*FIXME*/
.log_fn = my_yaml_log, // Use custom logging function
#endif
.mem_fn = cyaml_mem,
.log_level = CYAML_LOG_WARNING,
.flags = CYAML_CFG_IGNORE_UNKNOWN_KEYS
};
// Load YAML file
err = cyaml_load_file(config_file, &config, &yaml_providers_config_schema,
(void**)&yaml_config, NULL);
if (err != CYAML_OK) {
ERROR("Failed to parse YAML config file %s: %s", config_file,
cyaml_strerror(err));
return -1;
}
if (!yaml_config || !yaml_config->providers) {
ERROR("No providers found in YAML config");
if (yaml_config) {
cyaml_free(&config, &yaml_providers_config_schema, yaml_config, 0);
}
return -1;
}
// Process each provider
for (size_t i = 0; i < yaml_config->providers_count; i++) {
yaml_provider_t* yaml_prov = &yaml_config->providers[i];
// Create scaler_provider_t structure
scaler_provider_t* prov
= (scaler_provider_t*)calloc(1, sizeof(scaler_provider_t));
if (!prov) {
ERROR("Failed to allocate memory for provider");
continue;
}
// Copy data from YAML structure to scaler_provider_t
if (yaml_prov->name) {
prov->name = strdup(yaml_prov->name);
}
if (yaml_prov->type) {
prov->type = strdup(yaml_prov->type);
}
if (yaml_prov->confPath) {
prov->confPath = strdup(yaml_prov->confPath);
}
if (yaml_prov->scriptPath) {
prov->scriptPath = strdup(yaml_prov->scriptPath);
}
if (yaml_prov->scriptOptions) {
prov->scriptOptions = strdup(yaml_prov->scriptOptions);
}
// Initialize scripts array
for (int j = 0; j < NUMBER_OF_ACTIONS; ++j) {
prov->scripts[j] = strdup(PROV_SCRIPTS[j]);
}
// Verify and add provider
if (_verifyProvider(prov) < 0) {
ERROR("Failed to check provider %s. Make sure all scripts exists.",
prov->name);
provider_fields_fini(prov);
continue;
}
if (provider_add(&g_scaler_ctx.providers, prov) < 0) {
provider_fini(prov);
continue;
}
}
// Cleanup YAML config
cyaml_free(&config, &yaml_providers_config_schema, yaml_config, 0);
return 0;
}
/*
* Load all cloud providers
*/
int load_providers(const char* config_file)
{
// check if the config_file is yaml or json
if (_isJsonFile(config_file)) {
return _load_providers_json(config_file);
} else if (_isYamlFile(config_file)) {
return _load_providers_yaml(config_file);
}
// neither json nor yaml
ERROR("Unknown config file format: %s. Supported formats are .json and "
".yaml/.yml",
config_file);
return -1;
}

View file

@ -0,0 +1,687 @@
#include <errno.h>
#include <xlist.h>
#include "obj_mgr.h"
#include "plug_mgr.h"
#include "prov_conf.h"
#include "scaler.h"
#include "scaler_ctx.h"
#include "scaler_misc.h"
#include "scaler_obj.h"
#include "utils.h"
/* =================== providers ========== */
static int _add_a_provider(void* arg, void* data)
{
xlist_t* l = (xlist_t*)arg;
scaler_provider_t* p = (scaler_provider_t*)data;
if (!p || !l) {
return -EINVAL;
}
xlist_store(l, p);
return 0;
}
static int _grab_all_providers_list(xlist_t* l)
{
if (!l) {
return -EINVAL;
}
mylock_lock(&(g_scaler_ctx.providers.lock));
zarray_iter(&(g_scaler_ctx.providers.array), _add_a_provider, l);
mylock_unlock(&(g_scaler_ctx.providers.lock));
return 0;
}
/* =================== template refresh ========== */
static int _refresh_available_templates(void* data, void* arg)
{
scaler_provider_t* prov = (scaler_provider_t*)data;
if (!prov) {
return -EINVAL;
}
// Call the provider script to refresh available templates
int ret = get_available_templates(prov->name);
if (ret < 0) {
ERROR("Failed to refresh templates for provider %s", prov->name);
return ret;
}
return ret;
}
static void load_templates()
{
xlist_t l;
xlist_init(&l, 128);
_grab_all_providers_list(&l);
xlist_iter(&l, _refresh_available_templates, NULL);
xlist_fini(&l);
}
/* =================== request monitoring========== */
typedef struct {
xlist_t run;
xlist_t done;
} req_collect_t;
static int _collect_request(void* arg, void* data)
{
req_collect_t* c = (req_collect_t*)arg;
scaler_request_t* req = (scaler_request_t*)data;
if (!req || !c) {
return -EINVAL;
}
if (req->status == REQ_STATUS_COMPLETED
|| req->status == REQ_STATUS_COMPLETE_WITH_ERROR) {
xlist_store(&c->done, strdup(req->request_id));
} else if (req->status == REQ_STATUS_RUNNING) {
xlist_store(&c->run, req);
}
return 0;
}
static int _grab_req_list(req_collect_t* l)
{
if (!l) {
return -EINVAL;
}
mylock_lock(&(g_scaler_ctx.requests.lock));
zarray_iter(&(g_scaler_ctx.requests.array), _collect_request, l);
mylock_unlock(&(g_scaler_ctx.requests.lock));
return 0;
}
static int _call_get_request_status(void* data, void* arg)
{
scaler_request_t* req = (scaler_request_t*)data;
if (!req) {
return -EINVAL;
}
// make it return as early as possible
if (!g_scaler_ctx.running) {
return 0;
}
// Call the provider script to get request status
int ret = get_request_status(req);
if (ret < 0) {
ERROR("Failed to get status for request %s", req->request_id);
return ret;
}
return ret;
}
static int _clean_done_requests(void* data, void* arg)
{
char* req_id = (char*)data;
if (!req_id) {
return -EINVAL;
}
int ret = request_del(&g_scaler_ctx.requests, req_id);
if (ret < 0) {
ERROR("Failed to delete request %s", req_id);
return ret;
}
FREEUP(req_id);
return ret;
}
/*
* Monitor pending requests and update their status
*/
static void request_monitor()
{
req_collect_t c;
xlist_init(&c.run, 16);
xlist_init(&c.done, 16);
// Grab all running requests and call get_request_status
_grab_req_list(&c);
xlist_iter(&c.run, _call_get_request_status, NULL);
xlist_iter(&c.done, _clean_done_requests, NULL);
xlist_fini(&c.run);
xlist_fini(&c.done);
return;
}
/* =================== host monitoring========== */
typedef struct {
char* provider_name;
instance_id_list_t* id_list;
} prov_inst_id_list_t;
static int
_add_relinquished_inst_id(void* arg, void* data)
{
prov_inst_id_list_t* l = (prov_inst_id_list_t*)arg;
scaler_instance_t* inst = (scaler_instance_t*)data;
instance_id_list_t* id_list;
if (!inst || !l) {
return -EINVAL;
}
if (inst->alloc_status != ALLOC_STATUS_RELINQUISHED) {
return 0;
}
if (strcmp(inst->tpl_id.provider_name, l->provider_name) != 0) {
return 0;
}
id_list = l->id_list;
id_list->instance_ids[id_list->num].instance_id = strdup(inst->instance_id);
id_list->instance_ids[id_list->num].hostname = strdup(inst->hostname);
id_list->num++;
return 0;
}
static int _grab_relinquished_hosts_list(prov_inst_id_list_t* l)
{
if (!l) {
return -EINVAL;
}
mylock_lock(&(g_scaler_ctx.instances.lock));
zarray_iter(&(g_scaler_ctx.instances.array), _add_relinquished_inst_id, l);
mylock_unlock(&(g_scaler_ctx.instances.lock));
return 0;
}
static int
_add_allocated_inst_id(void* arg, void* data)
{
prov_inst_id_list_t* l = (prov_inst_id_list_t*)arg;
scaler_instance_t* inst = (scaler_instance_t*)data;
instance_id_list_t* id_list;
if (!inst || !l) {
return -EINVAL;
}
if (inst->alloc_status == ALLOC_STATUS_DONE) {
return 0;
}
if (strcmp(inst->tpl_id.provider_name, l->provider_name) != 0) {
return 0;
}
id_list = l->id_list;
id_list->instance_ids[id_list->num].instance_id = strdup(inst->instance_id);
id_list->instance_ids[id_list->num].hostname = strdup(inst->hostname);
id_list->num++;
return 0;
}
static int _grab_allocated_hosts_list(prov_inst_id_list_t* l)
{
if (!l) {
return -EINVAL;
}
mylock_lock(&(g_scaler_ctx.instances.lock));
zarray_iter(&(g_scaler_ctx.instances.array), _add_allocated_inst_id, l);
mylock_unlock(&(g_scaler_ctx.instances.lock));
return 0;
}
static int _get_reclaimed_hosts(void* data, void* arg)
{
prov_inst_id_list_t l;
instance_id_list_t* id_list = NULL;
scaler_provider_t* p = (scaler_provider_t*)data;
if (!p) {
return -EINVAL;
}
id_list = (instance_id_list_t*)calloc(1, sizeof(instance_id_list_t));
if (!id_list) {
ERROR("Memory allocation failed for instance_id_list_t");
return -ENOMEM;
}
id_list->num = 0;
id_list->instance_ids = (scaler_instance_id_t*)calloc(g_scaler_ctx.instances.array.count,
sizeof(scaler_instance_id_t));
l.provider_name = p->name;
l.id_list = id_list;
_grab_allocated_hosts_list(&l);
if (l.id_list->num > 0) {
int ret = get_return_request(p->name, l.id_list);
if (ret < 0) {
ERROR("Failed to get return request for provider %s", p->name);
}
}
instance_id_list_fini(id_list);
return 0;
}
static int
_add_done_instance_id(void* arg, void* data)
{
xlist_t* l = (xlist_t*)arg;
scaler_instance_t* inst = (scaler_instance_t*)data;
if (!inst || !l) {
return -EINVAL;
}
if (inst->alloc_status != ALLOC_STATUS_DONE) {
return 0;
}
// duplicate here to avoid core if instance_t already freed
xlist_store(l, strdup(inst->instance_id));
return 0;
}
static int _clean_done_instances(void* data, void* arg)
{
char* inst_id = (char*)data;
if (!inst_id) {
return -EINVAL;
}
instance_del(&g_scaler_ctx.instances, inst_id);
FREEUP(inst_id);
return 0;
}
static int _grab_done_instance_id_list(xlist_t* l)
{
if (!l) {
return -EINVAL;
}
mylock_lock(&(g_scaler_ctx.instances.lock));
zarray_iter(&(g_scaler_ctx.instances.array), _add_done_instance_id, l);
mylock_unlock(&(g_scaler_ctx.instances.lock));
return 0;
}
/* =================== new_demand caculate ========== */
typedef struct {
scaler_demand_t* demand;
int allocated;
int requesting;
int reclaimed;
} demand_count_t;
static int
_count_allocated_instances(void* arg1, void* arg2)
{
demand_count_t* counts = (demand_count_t*)arg1;
scaler_instance_t* inst = (scaler_instance_t*)arg2;
if (!inst || !counts) {
return -EINVAL;
}
if (!cmp_template_id(&inst->tpl_id, &counts->demand->tpl_id)) {
return 0;
}
if (inst->alloc_status == ALLOC_STATUS_ALLOCATED) {
counts->allocated++;
} else if (inst->alloc_status == ALLOC_STATUS_RECLAIMED
|| inst->alloc_status == ALLOC_STATUS_RELINQUISH_REQ_SENT
|| inst->alloc_status == ALLOC_STATUS_RELINQUISHED
|| inst->alloc_status == ALLOC_STATUS_DEALLOCATE_REQ_SENT) {
counts->reclaimed++;
}
return 0;
}
static int _count_requesting_instances(void* arg1, void* arg2)
{
demand_count_t* counts = (demand_count_t*)arg1;
scaler_request_t* req = (scaler_request_t*)arg2;
if (!req || !counts) {
return -EINVAL;
}
if (req->status != REQ_STATUS_RUNNING) {
return 0;
}
if (cmp_template_id(&req->tpl_id, &counts->demand->tpl_id)) {
counts->requesting += req->count;
}
return 0;
}
#define DEFAULT_TEMPLATE_MAX_NUMBER 50
static int _get_template_max_number(scaler_template_id_t* tpl_id)
{
scaler_template_t* tpl = NULL;
if (!tpl_id) {
return -EINVAL;
}
tpl = template_get_by_id(&g_scaler_ctx.templates, tpl_id);
if (!tpl) {
return -ENOENT;
}
if (tpl->max_number <= 0) {
return DEFAULT_TEMPLATE_MAX_NUMBER;
}
return tpl->max_number;
}
static int calculate(scaler_demand_t* demand)
{
int new_demand = 0, total = 0;
demand_count_t t_count;
int template_max;
int left = 0;
if (!demand) {
ERROR("Null demand parameter");
return -EINVAL;
}
memset(&t_count, 0, sizeof(demand_count_t));
t_count.demand = demand;
// Count allocated instances
mylock_lock(&(g_scaler_ctx.instances.lock));
zarray_iter(&(g_scaler_ctx.instances.array), _count_allocated_instances, &t_count);
mylock_unlock(&(g_scaler_ctx.instances.lock));
// Count requesting instances
mylock_lock(&(g_scaler_ctx.requests.lock));
zarray_iter(&(g_scaler_ctx.requests.array), _count_requesting_instances, &t_count);
mylock_unlock(&(g_scaler_ctx.requests.lock));
template_max = _get_template_max_number(&demand->tpl_id);
total = t_count.allocated + t_count.requesting;
left = template_max - total;
if (left <= 0) {
INFO("Template %s reached max number %d, no new instances can be created",
demand->tpl_id.template_id, template_max);
demand->count = 0;
return 0;
}
if (total < demand->count) {
new_demand = demand->count - total;
if (new_demand > left) {
new_demand = left;
}
} else {
new_demand = 0;
}
INFO("Demand %s: requested %d, allocated %d, requesting %d, reclaimed %d, new demand %d",
demand->tpl_id.template_id, demand->count, t_count.allocated, t_count.requesting,
t_count.reclaimed, new_demand);
demand->count = new_demand;
return demand->count;
}
static int _return_relinquished_hosts(void* data, void* arg)
{
prov_inst_id_list_t l;
instance_id_list_t* id_list = NULL;
scaler_provider_t* p = (scaler_provider_t*)data;
if (!p) {
return -EINVAL;
}
id_list = (instance_id_list_t*)calloc(1, sizeof(instance_id_list_t));
if (!id_list) {
ERROR("Memory allocation failed for instance_id_list_t");
return -ENOMEM;
}
id_list->num = 0;
id_list->instance_ids = (scaler_instance_id_t*)calloc(g_scaler_ctx.instances.array.count,
sizeof(scaler_instance_id_t));
l.provider_name = p->name;
l.id_list = id_list;
_grab_relinquished_hosts_list(&l);
if (l.id_list->num > 0) {
int ret = trigger_release(p->name, l.id_list);
if (ret < 0) {
ERROR("Failed to get return request for provider %s", p->name);
}
}
instance_id_list_fini(id_list);
return 0;
}
/*
* Monitor allocated hosts and handle reclaimed instances
*/
static void host_monitor()
{
xlist_t l;
xlist_init(&l, 128);
_grab_all_providers_list(&l);
// for each provider, check if there are instances to be relinquished
xlist_iter(&l, _return_relinquished_hosts, NULL);
// for each provider, check if there are instances to be reclaimed
xlist_iter(&l, _get_reclaimed_hosts, NULL);
xlist_fini(&l);
// clean done hosts
xlist_init(&l, 128);
_grab_done_instance_id_list(&l);
xlist_iter(&l, _clean_done_instances, NULL);
xlist_fini(&l);
}
/* =================== new_demand caculate ========== */
int request_delete_instances(instance_id_list_t* inst_ids)
{
int ret;
if (!inst_ids) {
ERROR("Null instance ID list");
return -EINVAL;
}
// Do not directly trigger release. Instead, mark the instances as RELINQUISHED
for (int i = 0; i < inst_ids->num; i++) {
ret = instance_update_status(&g_scaler_ctx.instances,
inst_ids->instance_ids[i].instance_id,
ALLOC_STATUS_RELINQUISHED,
NULL,
NULL);
if (ret < 0) {
ERROR("Failed to mark instance %s as RELINQUISHED",
inst_ids->instance_ids[i].instance_id);
continue;
}
TRACE("Marked instance %s as RELINQUISHED", inst_ids->instance_ids[i].instance_id);
}
return 0;
}
static int request_create_instances(scaler_demand_array_t* demands)
{
int ret;
if (!demands) {
ERROR("Null demand list");
return -EINVAL;
}
if (!g_scaler_ctx.running) {
return 0;
}
for (int i = 0; i < demands->num; i++) {
scaler_demand_t* demand = &(demands->data[i]);
int new_num = calculate(demand);
if (new_num <= 0) {
INFO("No new instances needed for demand %s, count %d",
demand->tpl_id.template_id, demand->count);
continue;
}
demand->count = new_num;
ret = trigger_create(demand);
if (ret < 0) {
ERROR("Failed to trigger create for demand %s, count %d",
demand->tpl_id.template_id, demand->count);
continue;
}
}
return 0;
}
int scaler_init(char* providers_file)
{
int ret = -1;
ret = scaler_ctx_init(providers_file);
if (ret < 0) {
ERROR("Failed to initialize scaler context");
goto Err_Exit;
}
// Load provider configurations
ret = load_providers(g_scaler_ctx.configs.providers_file);
if (ret < 0) {
ERROR("Failed to load provider configurations from %s",
g_scaler_ctx.configs.providers_file);
goto Err_Exit;
}
Err_Exit:
return ret;
}
int scaler_start()
{
int ret = 0;
pthread_t* monitor_thread = NULL;
pthread_t* demand_thread = NULL;
g_scaler_ctx.running = 1;
monitor_thread = &g_scaler_ctx.threads[0];
ret = pthread_create(monitor_thread, NULL, scaler_monitor, NULL);
if (ret != 0) {
ERROR("Failed to create scaler monitor thread: %s", strerror(ret));
goto Err_Exit;
}
demand_thread = &g_scaler_ctx.threads[1];
ret = pthread_create(demand_thread, NULL, scaler_demand_handler, NULL);
if (ret != 0) {
ERROR("Failed to create demand handler thread: %s", strerror(ret));
goto Err_Exit;
}
INFO("Scaler initialized successfully with providers file %s",
g_scaler_ctx.configs.providers_file);
Err_Exit:
return ret;
}
/*
* Monitor template quotas and pricing
*/
void* scaler_monitor(void* arg)
{
int loop = 0;
#define TEMPLATE_REFRESH_N_LOOPS 5
while (g_scaler_ctx.running) {
if (loop % TEMPLATE_REFRESH_N_LOOPS == 0) {
// refresh template every 3 loops
load_templates();
}
request_monitor();
host_monitor();
loop = (loop + 1) % TEMPLATE_REFRESH_N_LOOPS;
sleep(g_scaler_ctx.configs.check_interval);
}
return NULL;
}
int publish_scaler_demand(scaler_demand_array_t* demands)
{
scaler_demand_ent_t* entry = NULL;
scaler_demand_queue_t* queue = &g_scaler_ctx.demands;
if (!demands || demands->num <= 0) {
TRACE("Invalid demand array");
return -EINVAL;
}
entry = (scaler_demand_ent_t*)calloc(1, sizeof(scaler_demand_ent_t));
if (!entry) {
ERROR("Failed to initialize demand entry");
return -ENOMEM;
}
entry->demands = demands;
mylock_lock(&queue->lock);
list_insert_entry_back(g_scaler_ctx.demands.list, (xcelist_entry_t*)entry);
pthread_cond_signal(&queue->cond);
mylock_unlock(&queue->lock);
return 0;
}
/*
* Monitor pending requests and update their status
*/
void* scaler_demand_handler(void* arg)
{
scaler_demand_queue_t* queue = &g_scaler_ctx.demands;
while (g_scaler_ctx.running) {
xcelist_entry_t* entry = NULL;
pthread_mutex_lock(&queue->lock);
while (queue->list->numEnts <= 0 && g_scaler_ctx.running) {
pthread_cond_wait(&queue->cond, &queue->lock);
}
if (!g_scaler_ctx.running) {
pthread_mutex_unlock(&queue->lock);
break;
}
// get the first demand entry
entry = queue->list->forw;
list_entry_remove(queue->list, entry);
pthread_mutex_unlock(&queue->lock);
INFO("Got demand -----");
print_demand_list(((scaler_demand_ent_t*)entry)->demands);
request_create_instances(((scaler_demand_ent_t*)entry)->demands);
demand_ent_fini(entry);
}
return NULL;
}

View file

@ -0,0 +1,82 @@
#include <string.h>
#include "obj_mgr.h"
#include "prov_conf.h"
#include "scaler_ctx.h"
#include "scaler_obj.h"
#include "utils.h"
autoscaler_context_t g_scaler_ctx = { 0 };
int scaler_ctx_init(char* prv_file)
{
memset(&g_scaler_ctx, 0, sizeof(g_scaler_ctx));
if (prv_file) {
strncpy(g_scaler_ctx.configs.providers_file, prv_file, XCE_MAXPATH_LEN - 1);
} else {
return -EINVAL;
}
g_scaler_ctx.configs.check_interval = DEFAULT_CHECK_INTERVAL;
scaler_obj_mgr_init(&g_scaler_ctx.providers, provider_fini);
scaler_obj_mgr_init(&g_scaler_ctx.templates, template_fini);
scaler_obj_mgr_init(&g_scaler_ctx.requests, request_fini);
scaler_obj_mgr_init(&g_scaler_ctx.instances, instance_fini);
g_scaler_ctx.demands.list = list_create("demand list");
if (!g_scaler_ctx.demands.list) {
ERROR("Failed to create demand list");
return -ENOMEM;
}
mylock_init(&(g_scaler_ctx.demands.lock));
pthread_cond_init(&(g_scaler_ctx.demands.cond), NULL);
mylock_init(&g_scaler_ctx.lock);
return 0;
}
static void scaler_conf_fini(scaler_conf_t* conf)
{
if (!conf) {
return;
}
}
void scaler_ctx_fini(autoscaler_context_t* ctx)
{
if (!ctx) {
return;
}
scaler_obj_mgr_fini(&ctx->providers);
scaler_obj_mgr_fini(&ctx->templates);
scaler_obj_mgr_fini(&ctx->requests);
scaler_obj_mgr_fini(&ctx->instances);
scaler_conf_fini(&g_scaler_ctx.configs);
mylock_fini(&g_scaler_ctx.lock);
}
int scaler_ctx_is_menu_changed()
{
int flag = 0;
mylock_lock(&g_scaler_ctx.lock);
flag = g_scaler_ctx.flag & SCALER_MENU_CHANGED;
mylock_unlock(&g_scaler_ctx.lock);
return flag;
}
int scaler_ctx_reset_menu_changed()
{
mylock_lock(&g_scaler_ctx.lock);
g_scaler_ctx.flag &= ~SCALER_MENU_CHANGED;
mylock_unlock(&g_scaler_ctx.lock);
return 0;
}

View file

@ -0,0 +1,423 @@
#include <stdio.h>
#include <stdlib.h>
#include "scaler_json.h"
#include "scaler_misc.h"
#include "scaler_obj.h"
#include "utils.h"
int write_json_to_file(const char* filename, cJSON* json)
{
FILE* fp = fopen(filename, "w");
if (!fp) {
ERROR("Failed to open file for writing: %s", filename);
return -1;
}
char* json_str = cJSON_Print(json);
if (!json_str) {
fclose(fp);
ERROR("Failed to print JSON to string\n");
return -1;
}
fprintf(fp, "%s", json_str);
free(json_str);
fclose(fp);
return 0;
}
int read_json_file(const char* filename, cJSON** json)
{
FILE* fp = fopen(filename, "r");
if (!fp) {
ERROR("Failed to open file for reading: %s", filename);
return -1;
}
// Get the file size and allocate buffer
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (file_size <= 0) {
ERROR("File is empty or invalid: %s", filename);
fclose(fp);
return -1;
}
// Allocate buffer and read the file content
char* buffer = malloc(file_size + 1);
if (!buffer) {
ERROR("Failed to allocate memory for buffer");
fclose(fp);
return -1;
}
size_t bytes_read = fread(buffer, 1, file_size, fp);
buffer[bytes_read] = '\0'; // Null-terminate the string
fclose(fp);
cJSON* json_obj = cJSON_Parse(buffer);
FREEUP(buffer);
if (!json_obj) {
ERROR("Failed to parse JSON from file: %s", filename);
return -1;
}
*json = json_obj;
return 0;
}
cJSON*
s2j_demand_t(scaler_demand_t* demand)
{
if (!demand)
return NULL;
cJSON* json = cJSON_CreateObject();
cJSON* template = cJSON_CreateObject();
if (!json || !template) {
ERROR("Failed to create JSON objects");
cJSON_Delete(json);
cJSON_Delete(template);
return NULL;
}
// Manually map struct fields to JSON keys
cJSON_AddStringToObject(template, "templateId", demand->tpl_id.template_id);
cJSON_AddNumberToObject(template, "machineCount", demand->count);
cJSON_AddItemToObject(json, "template", template);
cJSON_AddStringToObject(json, "rc_account", "default");
cJSON_AddItemToObject(json, "userData", cJSON_CreateObject());
return json;
}
cJSON*
s2j_instance_id_list_t(instance_id_list_t* instance_ids)
{
if (!instance_ids)
return NULL;
cJSON* json = cJSON_CreateObject();
cJSON* machines = cJSON_CreateArray();
if (!json || !machines) {
ERROR("Failed to create JSON objects");
cJSON_Delete(json);
cJSON_Delete(machines);
return NULL;
}
for (int i = 0; i < instance_ids->num; i++) {
cJSON* machine = cJSON_CreateObject();
if (!machine) {
cJSON_Delete(json);
ERROR("Failed to create machine JSON object");
return NULL;
}
cJSON_AddStringToObject(machine, "name", instance_ids->instance_ids[i].hostname);
cJSON_AddStringToObject(machine, "machineId", instance_ids->instance_ids[i].instance_id);
cJSON_AddItemToArray(machines, machine);
}
cJSON_AddItemToObject(json, "machines", machines);
return json;
}
/*
* This is the abstracted function to convert scaler_request_t to JSON. It only save the request_id.
*/
cJSON*
s2j_request_id_list_t(scaler_request_t* req)
{
if (!req)
return NULL;
cJSON* json = cJSON_CreateObject();
cJSON* arr = cJSON_CreateArray();
if (!json || !arr) {
cJSON_Delete(json);
cJSON_Delete(arr);
return NULL;
}
cJSON* req_obj = cJSON_CreateObject();
if (!req_obj) {
cJSON_Delete(json);
cJSON_Delete(arr);
return NULL;
}
cJSON_AddStringToObject(req_obj, "requestId", req->request_id);
cJSON_AddItemToArray(arr, req_obj);
cJSON_AddItemToObject(json, "requests", arr);
return json;
}
cJSON* s2j_create_empty_obj()
{
return cJSON_CreateObject();
}
// convert scaler_template_attr_t array to cJSON object
cJSON* s2j_attribute_t(scaler_template_attr_t* attrs, int num_attr)
{
if (!attrs || num_attr <= 0)
return cJSON_CreateObject();
cJSON* attrs_obj = cJSON_CreateObject();
if (!attrs_obj)
return NULL;
for (int i = 0; i < num_attr; i++) {
cJSON* attr_array = cJSON_CreateArray();
if (!attr_array) {
cJSON_Delete(attrs_obj);
return NULL;
}
// Add type
switch (attrs[i].type) {
case SCALER_ATTR_TYPE_BOOL:
cJSON_AddItemToArray(attr_array, cJSON_CreateString("Boolean"));
cJSON_AddItemToArray(attr_array, cJSON_CreateString(attrs[i].v.bool_val ? "1" : "0"));
break;
case SCALER_ATTR_TYPE_NUM: {
char buf[32];
snprintf(buf, sizeof(buf), "%f", attrs[i].v.num_val);
cJSON_AddItemToArray(attr_array, cJSON_CreateString("Numeric"));
cJSON_AddItemToArray(attr_array, cJSON_CreateString(buf));
} break;
case SCALER_ATTR_TYPE_STR:
cJSON_AddItemToArray(attr_array, cJSON_CreateString("String"));
cJSON_AddItemToArray(attr_array, cJSON_CreateString(attrs[i].v.str_val ? attrs[i].v.str_val : ""));
break;
default:
cJSON_AddItemToArray(attr_array, cJSON_CreateString("Null"));
cJSON_AddItemToArray(attr_array, cJSON_CreateString(""));
break;
}
cJSON_AddItemToObject(attrs_obj, attrs[i].name, attr_array);
}
return attrs_obj;
}
char* attrs_to_str(scaler_template_attr_t* attrs, int num_attr)
{
cJSON* attrs_obj = s2j_attribute_t(attrs, num_attr);
if (!attrs_obj)
return "{}";
char* attrs_str = cJSON_PrintUnformatted(attrs_obj);
cJSON_Delete(attrs_obj);
return attrs_str;
}
static int j2s_attribute_t(cJSON* attrs_obj, scaler_template_attr_t** out_attrs)
{
int num_attrs = 0;
scaler_template_attr_t* attrs = NULL;
if (!attrs_obj) {
*out_attrs = NULL;
return 0;
}
if (attrs_obj && cJSON_IsObject(attrs_obj)) {
// Count attributes
cJSON* attr = NULL;
int count = 0;
int idx = 0;
cJSON_ArrayForEach(attr, attrs_obj)
{
count++;
}
num_attrs = count;
attrs = (scaler_template_attr_t*)calloc(count, sizeof(scaler_template_attr_t));
cJSON_ArrayForEach(attr, attrs_obj)
{
const char* attr_name = attr->string;
if (!attr_name)
continue;
attrs[idx].name = strdup(attr_name);
// attr value is an array: [type, value]
if (cJSON_IsArray(attr)) {
cJSON* type_item = cJSON_GetArrayItem(attr, 0);
cJSON* value_item = cJSON_GetArrayItem(attr, 1);
if (type_item && cJSON_IsString(type_item)
&& value_item && cJSON_IsString(value_item)) {
const char* type_str = type_item->valuestring;
const char* value_str = value_item->valuestring;
if (strcmp(type_str, "Boolean") == 0) {
attrs[idx].type = SCALER_ATTR_TYPE_BOOL;
attrs[idx].v.bool_val = (strcmp(value_str, "1") == 0 || strcasecmp(value_str, "true") == 0);
} else if (strcmp(type_str, "Numeric") == 0) {
attrs[idx].type = SCALER_ATTR_TYPE_NUM;
attrs[idx].v.num_val = atoi(value_str);
} else if (strcmp(type_str, "String") == 0) {
attrs[idx].type = SCALER_ATTR_TYPE_STR;
attrs[idx].v.str_val = strdup(value_str);
} else {
attrs[idx].type = SCALER_ATTR_TYPE_NULL;
}
}
}
idx++;
}
}
*out_attrs = attrs;
return num_attrs;
}
int str_to_attr(char* str, scaler_template_attr_t** out_attrs)
{
if (!str || !out_attrs)
return -1;
cJSON* attrs_obj = cJSON_Parse(str);
if (!attrs_obj) {
ERROR("Failed to parse attributes JSON string");
return -1;
}
int ret = j2s_attribute_t(attrs_obj, out_attrs);
cJSON_Delete(attrs_obj);
return ret;
}
scaler_template_t* j2s_template_t(char* provider_name, cJSON* json)
{
scaler_template_t* tpl = NULL;
cJSON* item = NULL;
cJSON* attrs_obj = NULL;
if (!json || !provider_name)
return NULL;
tpl = (scaler_template_t*)calloc(1, sizeof(scaler_template_t));
if (!tpl)
return NULL;
// Set provider_name
tpl->tpl_id.provider_name = strdup(provider_name);
// templateId
item = cJSON_GetObjectItem(json, "templateId");
if (item && cJSON_IsString(item)) {
char key[256];
tpl->tpl_id.template_id = strdup(item->valuestring);
my_snprintf(key, sizeof(key), "%s#%s", tpl->tpl_id.provider_name, tpl->tpl_id.template_id);
tpl->id = strdup(key);
} else {
ERROR("No templateId define in json.");
template_fini(tpl);
}
// maxNumber
item = cJSON_GetObjectItem(json, "maxNumber");
if (item && cJSON_IsNumber(item))
tpl->max_number = item->valueint;
// price
item = cJSON_GetObjectItem(json, "price");
if (item && cJSON_IsNumber(item))
tpl->price = (float)item->valuedouble;
// priority
item = cJSON_GetObjectItem(json, "priority");
if (item && cJSON_IsNumber(item))
tpl->priority = item->valueint;
// attributes
attrs_obj = cJSON_GetObjectItem(json, "attributes");
tpl->num_attrs = j2s_attribute_t(attrs_obj, &tpl->attrs);
// avail (optional)
item = cJSON_GetObjectItem(json, "avail");
if (item && cJSON_IsNumber(item))
tpl->avail_number = item->valueint;
// last_updated (optional)
tpl->last_update_time = time(NULL);
return tpl;
}
scaler_instance_id_t* j2s_instance_id_t(cJSON* json)
{
cJSON* item;
if (!json)
return NULL;
scaler_instance_id_t* inst_id = (scaler_instance_id_t*)calloc(1, sizeof(scaler_instance_id_t));
if (!inst_id)
return NULL;
item = cJSON_GetObjectItem(json, "machineId");
if (item && cJSON_IsString(item))
inst_id->instance_id = strdup(item->valuestring);
// Check for hostname in "name" or "machine" for "hostname"
item = cJSON_GetObjectItem(json, "name");
if (item && cJSON_IsString(item)) {
inst_id->hostname = strdup(item->valuestring);
} else {
item = cJSON_GetObjectItem(json, "machine");
if (item && cJSON_IsString(item))
inst_id->hostname = strdup(item->valuestring);
}
return inst_id;
}
/* parse cJSON to instance_t object only when it is "succeed" */
scaler_instance_t* j2s_instance_t(cJSON* json, scaler_req_type_t req_type)
{
scaler_instance_t* inst = NULL;
cJSON* item;
/* sanity check */
if (!json)
return NULL;
item = cJSON_GetObjectItem(json, "result");
if (item && cJSON_IsString(item) && strcmp(item->valuestring, "succeed") == 0) {
inst = (scaler_instance_t*)calloc(1, sizeof(scaler_instance_t));
if (req_type == SCALER_REQ_TYPE_REQ) {
inst->alloc_status = ALLOC_STATUS_ALLOCATED;
} else if (req_type == SCALER_REQ_TYPE_RET) {
inst->alloc_status = ALLOC_STATUS_DONE;
}
} else {
return NULL;
}
item = cJSON_GetObjectItem(json, "machineId");
if (item && cJSON_IsString(item))
inst->instance_id = strdup(item->valuestring);
item = cJSON_GetObjectItem(json, "name");
if (item && cJSON_IsString(item))
inst->hostname = strdup(item->valuestring);
item = cJSON_GetObjectItem(json, "privateIpAddress");
if (item && cJSON_IsString(item))
inst->private_ip = strdup(item->valuestring);
item = cJSON_GetObjectItem(json, "publicIpAddress");
if (item && cJSON_IsString(item))
inst->public_ip = strdup(item->valuestring);
item = cJSON_GetObjectItem(json, "launchtime");
if (item && cJSON_IsNumber(item))
inst->launch_time = (time_t)item->valuedouble;
item = cJSON_GetObjectItem(json, "status");
if (item && cJSON_IsString(item))
inst->inst_state = strdup(item->valuestring);
if (!inst->instance_id || !inst->hostname || !inst->private_ip) {
WARN("Instance ID, hostname or private IP is empty. Skipping");
FREEUP(inst);
return NULL;
}
return inst;
}

View file

@ -0,0 +1,199 @@
#include <math.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "scaler_misc.h"
#include "utils.h"
int my_snprintf(char* buf, size_t size, const char* fmt, ...)
{
va_list args;
int ret;
va_start(args, fmt);
ret = vsnprintf(buf, size, fmt, args);
va_end(args);
if (ret < 0) {
WARN("my_snprintf: encoding error for format: %s", fmt);
return ret;
}
if ((size_t)ret >= size) {
WARN("my_snprintf: output truncated for format: %s (needed %d bytes, buffer size %zu)",
fmt, ret + 1, size);
}
return ret;
}
/*
* Create a temporary file with a unique name using mkstemp.
* The file name is passed in as a parameter and should be a template
* like "/tmp/as_in_XXXXXX.json". The 'X's will be replaced with a unique
* sequence to ensure the file name is unique.
*
* Returns 0 on success, -1 on failure. file_name will contain the name of the created file.
*/
int create_temp_file(char* file_name)
{
// Call mkstemp to create a temporary file
int fd = mkstemp(file_name);
if (fd == -1) {
ERROR("Failed to create temporary file, errno %d : %s.", errno, strerror(errno));
return -1;
}
close(fd);
return 0;
}
char* my_putstr(char* s)
{
size_t len;
char* p;
if (!s) {
return s = "";
}
len = strlen(s);
p = (char*)calloc(len + 1, sizeof(char));
if (!p) {
SET_ERROR(ENOMEM);
ERROR("Memory allocation failed");
return NULL;
}
strcpy(p, s);
return p;
}
/*
* Safe string, return TRUE if both NULL or both equal, FALSE otherwise
*/
int my_strcmp(const char* p1, const char* p2)
{
if (!p1 && !p2)
return true;
if (!p1 || !p2)
return false;
return strcmp(p1, p2) == 0;
}
void print_demand_list(scaler_demand_array_t* d_list)
{
if (!d_list || d_list->num <= 0) {
return;
}
INFO("Got DB demand List:");
INFO("Number of demands: %d", d_list->num);
for (int i = 0; i < d_list->num; i++) {
scaler_demand_t* demand = &d_list->data[i];
INFO("Demand %d: provider=%s, template=%s, count=%d",
i,
demand->tpl_id.provider_name,
demand->tpl_id.template_id,
demand->count);
}
}
int cmp_template_id(scaler_template_id_t* p1, scaler_template_id_t* p2)
{
if (!p1 && !p2)
return true;
if (!p1 || !p2)
return false;
return my_strcmp(p1->provider_name, p2->provider_name)
&& my_strcmp(p1->template_id, p2->template_id);
}
void print_request(void* data)
{
scaler_request_t* request = (scaler_request_t*)data;
if (!request) {
return;
}
INFO("Request: req_id=%s, type=%d, status=%d, provider=%s, template=%s, count=%d",
request->request_id ? request->request_id : "NULL",
request->type,
request->status,
request->tpl_id.provider_name ? request->tpl_id.provider_name : "NULL",
request->tpl_id.template_id ? request->tpl_id.template_id : "NULL",
request->count);
}
static int _print_request_4_xlist(void* data, void* arg)
{
print_request(data);
return 0;
}
void print_request_list(xlist_t* r_list)
{
if (!r_list) {
return;
}
INFO("Got DB request List:");
INFO("Number of requests: %d", r_list->count);
xlist_iter(r_list, (xlist_iter_fn_t)_print_request_4_xlist, NULL);
}
void print_instance(void* p)
{
scaler_instance_t* inst = (scaler_instance_t*)p;
if (!inst) {
return;
}
INFO("Instance: id=%s, hostname=%s, private_ip=%s, public_ip=%s, "
"launch_time=%ld, alloc_status=%d, inst_state=%s, "
"provider=%s, template=%s, request_id=%s, relinquish_id=%s, "
"last_update_time=%ld",
inst->instance_id ? inst->instance_id : "NULL",
inst->hostname ? inst->hostname : "NULL",
inst->private_ip ? inst->private_ip : "NULL",
inst->public_ip ? inst->public_ip : "NULL",
(long)inst->launch_time,
inst->alloc_status,
inst->inst_state ? inst->inst_state : "NULL",
inst->tpl_id.provider_name ? inst->tpl_id.provider_name : "NULL",
inst->tpl_id.template_id ? inst->tpl_id.template_id : "NULL",
inst->request_id ? inst->request_id : "NULL",
inst->relinquish_id ? inst->relinquish_id : "NULL",
(long)inst->last_update_time);
}
static int _print_instance_4_xlist(void* data, void* arg)
{
print_instance(data);
return 0;
}
void print_instance_list(xlist_t* i_list)
{
if (!i_list || i_list->count <= 0) {
return;
}
INFO("Got DB instance List:");
INFO("Number of instances: %d", i_list->count);
xlist_iter(i_list, (xlist_iter_fn_t)_print_instance_4_xlist, NULL);
}
// Compare two floating point numbers for equality
// If the two numbers difference is within a small epsilon, consider them equal
#define DEFAULT_EPSILON 0.001
int float_cmp_ex(float a, float b, float ep)
{
if (fabs(a - b) < ep) {
return 0;
}
return 1;
}
int float_cmp(float a, float b)
{
return float_cmp_ex(a, b, DEFAULT_EPSILON);
}

View file

@ -0,0 +1,317 @@
#include "scaler_obj.h"
#include "scaler/types.h"
#include "utils.h"
typedef void (*obj_fields_fini_t)(void*);
#define OBJ_ARRAY_FINI(arr_ptr, field_fini_func, type) \
do { \
type* _arr = (type*)(arr_ptr); \
if (!_arr) { \
break; \
} \
for (int i = 0; i < _arr->num; ++i) { \
field_fini_func(&(_arr->data[i])); \
} \
FREEUP(_arr->data); \
_arr->num = 0; \
} while (0)
int provider_init(scaler_provider_t* p, char* name, char* type)
{
if (!p) {
return -1;
}
memset(p, 0, sizeof(scaler_provider_t));
p->name = strdup(name);
p->type = strdup(type);
return 0;
}
void provider_fields_fini(void* p)
{
scaler_provider_t* prov = (scaler_provider_t*)p;
if (!prov) {
return;
}
FREEUP(prov->name);
FREEUP(prov->type);
FREEUP(prov->confPath);
FREEUP(prov->scriptPath);
for (int i = 0; i < NUMBER_OF_ACTIONS; i++) {
FREEUP(prov->scripts[i]);
}
FREEUP(prov->scriptOptions);
}
void provider_fini(void* p)
{
provider_fields_fini(p);
FREEUP(p);
}
void template_id_fields_fini(scaler_template_id_t* tpl_id)
{
if (!tpl_id) {
return;
}
FREEUP(tpl_id->provider_name);
FREEUP(tpl_id->template_id);
}
void template_id_fini(void* p)
{
scaler_template_id_t* tpl_id = (scaler_template_id_t*)p;
if (tpl_id) {
template_id_fields_fini(tpl_id);
}
FREEUP(tpl_id);
}
/* free up template fields
*/
void template_fields_fini(void* p)
{
scaler_template_t* tpl = (scaler_template_t*)p;
if (!tpl) {
return;
}
FREEUP(tpl->id);
template_id_fields_fini(&tpl->tpl_id);
// Free attributes
for (int i = 0; i < tpl->num_attrs; ++i) {
if (tpl->attrs[i].name) {
FREEUP(tpl->attrs[i].name);
}
if (tpl->attrs[i].type == SCALER_ATTR_TYPE_STR && tpl->attrs[i].v.str_val) {
FREEUP(tpl->attrs[i].v.str_val);
}
}
FREEUP(tpl->attrs);
tpl->num_attrs = 0;
}
void template_fini(void* p)
{
scaler_template_t* tpl = (scaler_template_t*)p;
if (tpl) {
template_fields_fini(tpl);
}
FREEUP(tpl);
}
int template_cpy(scaler_template_t* tgt, scaler_template_t* src)
{
if (!tgt || !src) {
return -EINVAL;
}
// Update fields
template_fields_fini(tgt);
tgt->id = strdup(src->id);
tgt->tpl_id.provider_name = strdup(src->tpl_id.provider_name);
tgt->tpl_id.template_id = strdup(src->tpl_id.template_id);
tgt->max_number = src->max_number;
tgt->priority = src->priority;
tgt->avail_number = src->avail_number;
tgt->price = src->price;
tgt->last_update_time = src->last_update_time;
// Allocate and copy attributes
tgt->num_attrs = src->num_attrs;
if (src->num_attrs > 0) {
tgt->attrs = (scaler_template_attr_t*)calloc(src->num_attrs, sizeof(scaler_template_attr_t));
if (!tgt->attrs) {
return -ENOMEM;
}
for (int i = 0; i < src->num_attrs; ++i) {
tgt->attrs[i].name = strdup(src->attrs[i].name);
tgt->attrs[i].type = src->attrs[i].type;
switch (src->attrs[i].type) {
case SCALER_ATTR_TYPE_BOOL:
tgt->attrs[i].v.bool_val = src->attrs[i].v.bool_val;
break;
case SCALER_ATTR_TYPE_NUM:
tgt->attrs[i].v.num_val = src->attrs[i].v.num_val;
break;
case SCALER_ATTR_TYPE_STR:
if (src->attrs[i].v.str_val) {
tgt->attrs[i].v.str_val = strdup(src->attrs[i].v.str_val);
} else {
tgt->attrs[i].v.str_val = NULL;
}
break;
default:
tgt->attrs[i].v.str_val = NULL; // Handle null type
break;
}
}
} else {
tgt->attrs = NULL;
}
return 0;
}
scaler_request_t* new_create_request(const char* provider_name,
const char* template_id,
int count,
const char* request_id)
{
scaler_request_t* r = (scaler_request_t*)calloc(1, sizeof(scaler_request_t));
time_t now = time(NULL);
if (!r) {
ERROR("Memory allocation failed for scaler_request_t");
return NULL;
}
// Validate input parameters
if (!provider_name || !template_id || !request_id) {
ERROR("Invalid parameters in new_create_request");
free(r);
return NULL;
}
r->type = SCALER_REQ_TYPE_REQ;
r->status = REQ_STATUS_RUNNING;
r->tpl_id.template_id = strdup(template_id);
r->tpl_id.provider_name = strdup(provider_name);
r->request_id = strdup(request_id);
r->count = count;
r->launch_time = now;
r->last_update_time = now;
// Check if any strdup failed
if (!r->tpl_id.template_id || !r->tpl_id.provider_name || !r->request_id) {
ERROR("Memory allocation failed for string fields");
request_fini(r);
return NULL;
}
return r;
}
scaler_request_t* new_release_request(const char* request_id, char* provider_name)
{
scaler_request_t* r = (scaler_request_t*)calloc(1, sizeof(scaler_request_t));
time_t now = time(NULL);
if (!r) {
ERROR("Memory allocation failed for scaler_request_t");
return NULL;
}
// Validate input parameters
if (!request_id || !provider_name) {
ERROR("Invalid parameters in new_release_request");
free(r);
return NULL;
}
r->type = SCALER_REQ_TYPE_RET;
r->status = REQ_STATUS_RUNNING;
r->request_id = strdup(request_id);
r->tpl_id.provider_name = strdup(provider_name);
r->launch_time = now;
r->last_update_time = now;
// Check if any strdup failed
if (!r->request_id || !r->tpl_id.provider_name) {
ERROR("Memory allocation failed for string fields");
request_fini(r);
return NULL;
}
return r;
}
void request_fini(void* p)
{
scaler_request_t* req = (scaler_request_t*)p;
if (!req) {
return;
}
FREEUP(req->request_id);
template_id_fields_fini(&req->tpl_id);
FREEUP(req);
}
void instance_id_fields_fini(void* p)
{
scaler_instance_id_t* inst_id = (scaler_instance_id_t*)p;
if (!inst_id) {
return;
}
FREEUP(inst_id->instance_id);
FREEUP(inst_id->hostname);
}
void instance_id_fini(void* p)
{
instance_id_fields_fini(p);
FREEUP(p);
}
void instance_id_list_fini(void* p)
{
instance_id_list_t* inst_id_list = (instance_id_list_t*)p;
if (!inst_id_list) {
return;
}
for (int i = 0; i < inst_id_list->num; ++i) {
instance_id_fields_fini(&(inst_id_list->instance_ids[i]));
}
FREEUP(inst_id_list->instance_ids);
inst_id_list->num = 0;
FREEUP(inst_id_list);
}
void instance_fini(void* p)
{
scaler_instance_t* inst = (scaler_instance_t*)p;
if (!inst) {
return;
}
FREEUP(inst->instance_id);
FREEUP(inst->hostname);
FREEUP(inst->private_ip);
FREEUP(inst->public_ip);
FREEUP(inst->inst_state);
FREEUP(inst->request_id);
FREEUP(inst->relinquish_id);
template_id_fields_fini(&inst->tpl_id);
FREEUP(inst);
}
void demand_fields_fini(void* p)
{
scaler_demand_t* demand = (scaler_demand_t*)p;
if (!demand) {
return;
}
template_id_fields_fini(&demand->tpl_id);
}
void demand_array_fini(void* p)
{
OBJ_ARRAY_FINI(p, demand_fields_fini, scaler_demand_array_t);
}
void demand_ent_fini(void* p)
{
scaler_demand_ent_t* ent = (scaler_demand_ent_t*)p;
if (!ent) {
return;
}
demand_array_fini(ent->demands);
FREEUP(ent->demands);
FREEUP(ent);
}

View file

@ -0,0 +1,38 @@
#!/bin/sh
cat <<EOF
{
"templates":[
{
"templateId":"test-template-1",
"maxNumber":200,
"attributes":{
"mem":[
"Numeric",
"8192"
],
"ncpus":[
"Numeric",
"1"
],
"zone":[
"String",
"asiasoutheast"
],
"baiduhost":[
"Boolean",
"1"
],
"ncores":[
"Numeric",
"1"
],
"type":[
"String",
"X86_64"
]
},
"instanceTags":"group=XCE2"
}
]
}
EOF

View file

@ -0,0 +1,85 @@
#!/bin/sh
# Parse command line arguments
INPUT_FILE=""
while [ $# -gt 0 ]; do
case $1 in
-f)
INPUT_FILE="$2"
shift 2
;;
*)
shift
;;
esac
done
# Read and parse the input JSON to get requestId
if [ -n "$INPUT_FILE" ] && [ -f "$INPUT_FILE" ]; then
REQUEST_ID=$(grep -o '"requestId":"[^"]*"' "$INPUT_FILE" | head -1 | cut -d'"' -f4)
else
REQUEST_ID=""
fi
# Check if requestId starts with test-request-id-release
if echo "$REQUEST_ID" | grep -q "^test-request-id-release"; then
# Output for release request
cat <<EOF
{
"requests":[
{
"status":"complete",
"machines":[
{
"machineId":"test-instance-1",
"name":"test-hostname-1",
"result":"succeed",
"status":"TERMINATED",
"privateIpAddress":"10.100.2.118",
"rcAccount":"default",
"message":"",
"launchtime":1494001568
}
],
"requestId":"test-request-id-release-1",
"message":""
}
]
}
EOF
else
# Default output for create request
cat <<EOF
{
"requests":[
{
"status":"complete",
"machines":[
{
"machineId":"test-instance-1",
"name":"test-hostname-1",
"result":"succeed",
"status":"RUNNING",
"privateIpAddress":"10.100.2.118",
"rcAccount":"default",
"message":"",
"launchtime":1494001568
},
{
"machineId":"test-instance-2",
"name":"test-hostname-2",
"result":"succeed",
"status":"RUNNING",
"privateIpAddress":"10.100.2.119",
"rcAccount":"default",
"message":"",
"launchtime":1494001568
}
],
"requestId":"test-request-id-create-1",
"message":""
}
]
}
EOF
fi

View file

@ -0,0 +1,11 @@
#!/bin/sh
cat <<EOF
{
"requests":[
{
"machine":"test-hostname-1",
"machineId":"test-instance-1"
}
]
}
EOF

View file

@ -0,0 +1,6 @@
#!/bin/sh
cat <<EOF
{
"requestId": "test-request-id-create-1"
}
EOF

View file

@ -0,0 +1,7 @@
#!/bin/sh
cat <<EOF
{
"message":"Request to terminate instances successful.",
"requestId":"test-request-id-release-1"
}
EOF

View file

@ -0,0 +1,28 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "unity.h"
extern void test_plug_mgr(void);
extern void test_scale_misc(void);
void setUp(void)
{
return;
}
void tearDown(void)
{
return;
}
int main(int argc, char* argv[])
{
UNITY_BEGIN();
test_plug_mgr();
test_scale_misc();
return UNITY_END();
}

View file

@ -0,0 +1,161 @@
#include <stdlib.h>
#include <string.h>
#include "obj_mgr.h"
#include "plug_mgr.h"
#include "prov_conf.h"
#include "scaler.h"
#include "scaler_misc.h"
#include "scaler_obj.h"
#include "unity.h"
#define TEST_PROVIDER "test-provider"
#define TEST_PROVIDER_TYPE "testProv"
#define TEST_TEMPLATE "test-template-1"
#define TEST_REQUEST_ID_CREATE "test-request-id-create-1"
#define TEST_REQUEST_ID_RELEASE "test-request-id-release-1"
#define TEST_INSTANCE_ID "test-instance-1"
#define TEST_HOSTNAME "test-hostname-1"
#define TEST_PRIVATE_IP "10.100.2.118"
autoscaler_context_t g_scaler_ctx;
extern const char* PROV_SCRIPTS[];
static void global_init(void)
{
scaler_obj_mgr_init(&g_scaler_ctx.providers, provider_fini);
scaler_obj_mgr_init(&g_scaler_ctx.templates, template_fini);
scaler_obj_mgr_init(&g_scaler_ctx.requests, request_fini);
scaler_obj_mgr_init(&g_scaler_ctx.instances, instance_fini);
scaler_provider_t* prov = (scaler_provider_t*)malloc(sizeof(scaler_provider_t));
provider_init(prov, TEST_PROVIDER, TEST_PROVIDER_TYPE);
prov->confPath = strdup("./testsuite");
prov->scriptPath = strdup("./testsuite");
prov->scripts[GET_AVAILABLE_TEMPLATES] = strdup(PROV_SCRIPTS[GET_AVAILABLE_TEMPLATES]);
prov->scripts[REQUEST_MACHINES] = strdup(PROV_SCRIPTS[REQUEST_MACHINES]);
prov->scripts[GET_REQUEST_STATUS] = strdup(PROV_SCRIPTS[GET_REQUEST_STATUS]);
prov->scripts[GET_RETURN_REQUEST] = strdup(PROV_SCRIPTS[GET_RETURN_REQUEST]);
prov->scripts[RETURN_MACHINES] = strdup(PROV_SCRIPTS[RETURN_MACHINES]);
provider_add(&g_scaler_ctx.providers, prov);
}
static void global_cleanup(void)
{
scaler_obj_mgr_fini(&g_scaler_ctx.instances);
scaler_obj_mgr_fini(&g_scaler_ctx.requests);
scaler_obj_mgr_fini(&g_scaler_ctx.templates);
scaler_obj_mgr_fini(&g_scaler_ctx.providers);
}
static void test_get_available_templates(void)
{
int ret = get_available_templates(TEST_PROVIDER);
char key[256];
my_snprintf(key, sizeof(key), "%s#%s", TEST_PROVIDER, TEST_TEMPLATE);
TEST_ASSERT_EQUAL_INT(0, ret);
TEST_ASSERT_EQUAL_INT(1, g_scaler_ctx.templates.array.count);
scaler_template_t* tpl = template_get(&g_scaler_ctx.templates, key);
TEST_ASSERT_NOT_NULL(tpl);
TEST_ASSERT_EQUAL_INT(6, tpl->num_attrs);
TEST_ASSERT_EQUAL_STRING("zone", tpl->attrs[2].name);
TEST_ASSERT_EQUAL_STRING("asiasoutheast", tpl->attrs[2].v.str_val);
}
static void test_trigger_create(void)
{
scaler_demand_t demand = {
.tpl_id = { TEST_PROVIDER, TEST_TEMPLATE },
.count = 1
};
int ret = trigger_create(&demand);
TEST_ASSERT_EQUAL_INT(0, ret);
TEST_ASSERT_EQUAL_INT(1, g_scaler_ctx.requests.array.count);
scaler_request_t* req = request_get(&g_scaler_ctx.requests, TEST_REQUEST_ID_CREATE);
TEST_ASSERT_NOT_NULL(req);
TEST_ASSERT_EQUAL_STRING(TEST_PROVIDER, req->tpl_id.provider_name);
}
static void test_trigger_release(void)
{
scaler_instance_id_t inst_id = {
.instance_id = TEST_INSTANCE_ID,
.hostname = TEST_HOSTNAME
};
instance_id_list_t inst_ids;
inst_ids.num = 1;
inst_ids.instance_ids = &inst_id;
int ret = trigger_release(TEST_PROVIDER, &inst_ids);
TEST_ASSERT_EQUAL_INT(0, ret);
TEST_ASSERT_EQUAL_INT(2, g_scaler_ctx.requests.array.count);
scaler_request_t* req = request_get(&g_scaler_ctx.requests, TEST_REQUEST_ID_RELEASE);
TEST_ASSERT_NOT_NULL(req);
TEST_ASSERT_EQUAL(SCALER_REQ_TYPE_RET, req->type);
}
static void test_get_create_request_status(void)
{
scaler_request_t* req = new_create_request(TEST_PROVIDER, TEST_TEMPLATE, 1, TEST_REQUEST_ID_CREATE);
int ret = get_request_status(req);
request_fini(req);
req = request_get(&g_scaler_ctx.requests, TEST_REQUEST_ID_CREATE);
TEST_ASSERT_EQUAL_INT(0, ret);
TEST_ASSERT_EQUAL(REQ_STATUS_COMPLETED, req->status);
TEST_ASSERT_EQUAL_INT(2, g_scaler_ctx.instances.array.count);
scaler_instance_t* inst = instance_get(&g_scaler_ctx.instances, TEST_INSTANCE_ID);
TEST_ASSERT_NOT_NULL(inst);
TEST_ASSERT_EQUAL_STRING(TEST_PRIVATE_IP, inst->private_ip);
TEST_ASSERT_EQUAL(ALLOC_STATUS_ALLOCATED, inst->alloc_status);
}
static void test_get_release_request_status(void)
{
scaler_request_t* req = new_release_request(TEST_REQUEST_ID_RELEASE, TEST_PROVIDER);
int ret = get_request_status(req);
request_fini(req);
req = request_get(&g_scaler_ctx.requests, TEST_REQUEST_ID_CREATE);
TEST_ASSERT_EQUAL_INT(0, ret);
TEST_ASSERT_EQUAL(REQ_STATUS_COMPLETED, req->status);
TEST_ASSERT_EQUAL_INT(2, g_scaler_ctx.instances.array.count);
scaler_instance_t* inst = instance_get(&g_scaler_ctx.instances, TEST_INSTANCE_ID);
TEST_ASSERT_NOT_NULL(inst);
TEST_ASSERT_EQUAL_STRING(TEST_PRIVATE_IP, inst->private_ip);
TEST_ASSERT_EQUAL(ALLOC_STATUS_DONE, inst->alloc_status);
}
static void test_get_return_request(void)
{
instance_id_list_t* inst_id_list = (instance_id_list_t*)malloc(sizeof(instance_id_list_t));
inst_id_list->num = 1;
inst_id_list->instance_ids = (scaler_instance_id_t*)malloc(sizeof(scaler_instance_id_t));
inst_id_list->instance_ids[0].instance_id = strdup(TEST_INSTANCE_ID);
inst_id_list->instance_ids[0].hostname = strdup(TEST_HOSTNAME);
int ret = get_return_request(TEST_PROVIDER, inst_id_list);
instance_id_list_fini(inst_id_list);
TEST_ASSERT_EQUAL_INT(0, ret);
scaler_instance_t* inst = instance_get(&g_scaler_ctx.instances, TEST_INSTANCE_ID);
TEST_ASSERT_NOT_NULL(inst);
TEST_ASSERT_EQUAL_STRING(TEST_HOSTNAME, inst->hostname);
TEST_ASSERT_EQUAL_STRING(TEST_INSTANCE_ID, inst->instance_id);
TEST_ASSERT_EQUAL(ALLOC_STATUS_RECLAIMED, inst->alloc_status);
}
void test_plug_mgr(void)
{
global_init();
RUN_TEST(test_get_available_templates);
RUN_TEST(test_trigger_create);
RUN_TEST(test_get_create_request_status);
RUN_TEST(test_get_return_request);
RUN_TEST(test_trigger_release);
RUN_TEST(test_get_release_request_status);
global_cleanup();
return;
}

View file

@ -0,0 +1,148 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <unistd.h>
#include "scaler/types.h"
#include "scaler_misc.h"
#include "unity.h"
#ifndef TEST_ASSERT_NOT_EQUAL_STRING
#define TEST_ASSERT_NOT_EQUAL_STRING(a, b) \
do { \
if (strcmp(a, b) == 0) { \
TEST_FAIL_MESSAGE(#a " and " #b " are equal"); \
} \
} while (0)
#endif
static void
test_my_snprintf(void)
{
char buffer[50];
int result;
// Test normal formatting
result = my_snprintf(buffer, sizeof(buffer), "Hello %s, number: %d", "World", 42);
TEST_ASSERT_EQUAL_STRING("Hello World, number: 42", buffer);
TEST_ASSERT_EQUAL_INT(strlen("Hello World, number: 42"), result);
// Test buffer size limit
char small_buffer[10];
result = my_snprintf(small_buffer, sizeof(small_buffer), "This is a very long string");
TEST_ASSERT_EQUAL_INT(strlen("This is a very long string"), result);
// Test empty format
result = my_snprintf(buffer, sizeof(buffer), "");
TEST_ASSERT_EQUAL_STRING("", buffer);
TEST_ASSERT_EQUAL_INT(0, result);
}
void test_create_temp_file(void)
{
char filename1[32] = "/tmp/test_XXXXXX";
char filename2[32] = "/tmp/test_XXXXXX";
// Test successful creation
int result1 = create_temp_file(filename1);
TEST_ASSERT_EQUAL_INT(0, result1);
// Test that file exists
TEST_ASSERT_EQUAL_INT(0, access(filename1, F_OK));
// Test that creating another file gives different name
int result2 = create_temp_file(filename2);
TEST_ASSERT_EQUAL_INT(0, result2);
TEST_ASSERT_NOT_EQUAL_STRING(filename1, filename2);
// Cleanup
unlink(filename1);
unlink(filename2);
#if 0
// Test with invalid template (should fail)
char bad_template[32] = "/invalid_path/test_XXXXXX";
int result3 = create_temp_file(bad_template);
TEST_ASSERT_EQUAL_INT(-1, result3);
#endif
}
static void test_my_strcmp(void)
{
// Test both NULL
TEST_ASSERT_TRUE(my_strcmp(NULL, NULL));
// Test one NULL
TEST_ASSERT_FALSE(my_strcmp("test", NULL));
TEST_ASSERT_FALSE(my_strcmp(NULL, "test"));
// Test equal strings
TEST_ASSERT_TRUE(my_strcmp("hello", "hello"));
TEST_ASSERT_TRUE(my_strcmp("", ""));
// Test different strings
TEST_ASSERT_FALSE(my_strcmp("hello", "world"));
TEST_ASSERT_FALSE(my_strcmp("hello", "Hello"));
TEST_ASSERT_FALSE(my_strcmp("hello", "hell"));
TEST_ASSERT_FALSE(my_strcmp("hell", "hello"));
}
static void test_cmp_template_id(void)
{
scaler_template_id_t id1 = {
.provider_name = "aws",
.template_id = "t2.micro"
};
scaler_template_id_t id2 = {
.provider_name = "aws",
.template_id = "t2.micro"
};
scaler_template_id_t id3 = {
.provider_name = "azure",
.template_id = "t2.micro"
};
scaler_template_id_t id4 = {
.provider_name = "aws",
.template_id = "t2.small"
};
// Test both NULL
TEST_ASSERT_TRUE(cmp_template_id(NULL, NULL));
// Test one NULL
TEST_ASSERT_FALSE(cmp_template_id(&id1, NULL));
TEST_ASSERT_FALSE(cmp_template_id(NULL, &id1));
// Test equal template IDs
TEST_ASSERT_TRUE(cmp_template_id(&id1, &id2));
// Test different provider names
TEST_ASSERT_FALSE(cmp_template_id(&id1, &id3));
// Test different template IDs
TEST_ASSERT_FALSE(cmp_template_id(&id1, &id4));
// Test with NULL fields
scaler_template_id_t id_null1 = { .provider_name = NULL, .template_id = "test" };
scaler_template_id_t id_null2 = { .provider_name = "test", .template_id = NULL };
scaler_template_id_t id_null3 = { .provider_name = NULL, .template_id = NULL };
TEST_ASSERT_FALSE(cmp_template_id(&id1, &id_null1));
TEST_ASSERT_FALSE(cmp_template_id(&id1, &id_null2));
TEST_ASSERT_TRUE(cmp_template_id(&id_null3, &id_null3));
}
void test_scale_misc(void)
{
printf("\n>> scale misc functions test\n\n");
RUN_TEST(test_my_snprintf);
RUN_TEST(test_create_temp_file);
RUN_TEST(test_my_strcmp);
RUN_TEST(test_cmp_template_id);
return;
}

View file

@ -45,7 +45,7 @@ history_dep = dependency('history')
xce_test_deps = [
libxce_dep,
libcjson_dep,
struct2json_dep,
readline_dep,
history_dep,
ncurses_dep

12
src/flake.lock generated
View file

@ -108,11 +108,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1762111121,
"narHash": "sha256-4vhDuZ7OZaZmKKrnDpxLZZpGIJvAeMtK6FKLJYUtAdw=",
"lastModified": 1764667669,
"narHash": "sha256-7WUCZfmqLAssbDqwg9cUDAXrSoXN79eEEq17qhTNM/Y=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b3d51a0365f6695e7dd5cdf3e180604530ed33b4",
"rev": "418468ac9527e799809c900eda37cbff999199b6",
"type": "github"
},
"original": {
@ -181,11 +181,11 @@
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1764733489,
"narHash": "sha256-y0WYBG5Yx6Ipo6EiNqw60h3X9M5XMeesZfrew3srcNg=",
"lastModified": 1764926264,
"narHash": "sha256-1N8BKVwxESYUjU4SwDFhb7moCwT9lvkqiq0kmdrjeg4=",
"owner": "shen390s",
"repo": "my-nix-channel",
"rev": "3bc72b826a5d38120df24ee7910bb991eae4d3e7",
"rev": "597aa03bdb99a95853744acd349f113a9cd2b3cd",
"type": "github"
},
"original": {

View file

@ -37,9 +37,10 @@ cc = meson.get_compiler('c')
math_dep = cc.find_library('m', required: false)
rt_dep = cc.find_library('rt', required: false)
libcjson_dep = dependency('libcjson')
struct2json_dep = dependency('struct2json')
libcyaml_dep = dependency('libcyaml')
libyaml_dep = dependency('yaml-0.1')
libtirpc_dep = dependency('libtirpc')
yaml_deps = [
libcyaml_dep,
@ -49,4 +50,5 @@ yaml_deps = [
subdir('lib')
subdir('controller')
subdir('worker')
subdir('autoscaler')
# subdir('scheduler')

View file

@ -28,7 +28,7 @@ stdenv.mkDerivation {
libtirpc
readline
ncurses
cjson
struct2json
capnpc
which
findutils

View file

@ -20,7 +20,7 @@ xcecd_worker_deps = [
libuv_dep,
elogind_dep,
sqlite3_dep,
libcjson_dep,
struct2json_dep,
yaml_deps
]