forked from lei.chen/myxce
use struct2json and import autoscaler
This commit is contained in:
parent
7b5f280ae5
commit
5036d77f04
62 changed files with 6628 additions and 10 deletions
15
src/autoscaler/Makefile
Normal file
15
src/autoscaler/Makefile
Normal 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
|
||||
24
src/autoscaler/meson.build
Normal file
24
src/autoscaler/meson.build
Normal 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)
|
||||
15
src/autoscaler/providers/Makefile
Normal file
15
src/autoscaler/providers/Makefile
Normal 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
|
||||
5
src/autoscaler/providers/baidu/BccTool.env
Normal file
5
src/autoscaler/providers/baidu/BccTool.env
Normal 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
|
||||
15
src/autoscaler/providers/baidu/Makefile
Normal file
15
src/autoscaler/providers/baidu/Makefile
Normal 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)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"LogLevel": "debug",
|
||||
"VPC": "default",
|
||||
"Region": "bd",
|
||||
"CredentialFile": "/opt/lsf/conf/resource_connector/baidu/conf/credentials"
|
||||
}
|
||||
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
src/autoscaler/providers/baidu/lsf/conf/credentials
Normal file
4
src/autoscaler/providers/baidu/lsf/conf/credentials
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"AK": "xxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"SK": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
}
|
||||
27
src/autoscaler/providers/baidu/lsf/conf/logger.yaml
Normal file
27
src/autoscaler/providers/baidu/lsf/conf/logger.yaml
Normal 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"
|
||||
61
src/autoscaler/providers/baidu/postprovision/lsf/example_user_data.sh
Executable file
61
src/autoscaler/providers/baidu/postprovision/lsf/example_user_data.sh
Executable 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
|
||||
13
src/autoscaler/providers/baidu/scripts/getAvailableMachines.sh
Executable file
13
src/autoscaler/providers/baidu/scripts/getAvailableMachines.sh
Executable 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
|
||||
12
src/autoscaler/providers/baidu/scripts/getAvailableTemplates.sh
Executable file
12
src/autoscaler/providers/baidu/scripts/getAvailableTemplates.sh
Executable 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
|
||||
12
src/autoscaler/providers/baidu/scripts/getRequestStatus.sh
Executable file
12
src/autoscaler/providers/baidu/scripts/getRequestStatus.sh
Executable 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
|
||||
12
src/autoscaler/providers/baidu/scripts/getReturnRequests.sh
Executable file
12
src/autoscaler/providers/baidu/scripts/getReturnRequests.sh
Executable 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
|
||||
12
src/autoscaler/providers/baidu/scripts/requestMachines.sh
Executable file
12
src/autoscaler/providers/baidu/scripts/requestMachines.sh
Executable 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
|
||||
12
src/autoscaler/providers/baidu/scripts/requestReturnMachines.sh
Executable file
12
src/autoscaler/providers/baidu/scripts/requestReturnMachines.sh
Executable 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
|
||||
5
src/autoscaler/providers/baidu/src/bcctool/BccTool.env
Normal file
5
src/autoscaler/providers/baidu/src/bcctool/BccTool.env
Normal 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
|
||||
397
src/autoscaler/providers/baidu/src/bcctool/client/bccClient.go
Normal file
397
src/autoscaler/providers/baidu/src/bcctool/client/bccClient.go
Normal 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)
|
||||
}
|
||||
117
src/autoscaler/providers/baidu/src/bcctool/enums/provCode.go
Normal file
117
src/autoscaler/providers/baidu/src/bcctool/enums/provCode.go
Normal 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
|
||||
)
|
||||
21
src/autoscaler/providers/baidu/src/bcctool/go.mod
Normal file
21
src/autoscaler/providers/baidu/src/bcctool/go.mod
Normal 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
|
||||
)
|
||||
23
src/autoscaler/providers/baidu/src/bcctool/go.sum
Normal file
23
src/autoscaler/providers/baidu/src/bcctool/go.sum
Normal 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=
|
||||
58
src/autoscaler/providers/baidu/src/bcctool/main.go
Normal file
58
src/autoscaler/providers/baidu/src/bcctool/main.go
Normal 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)
|
||||
}
|
||||
}
|
||||
607
src/autoscaler/providers/baidu/src/bcctool/model/types.go
Normal file
607
src/autoscaler/providers/baidu/src/bcctool/model/types.go
Normal 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()
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
90
src/autoscaler/providers/baidu/src/bcctool/utils/envvars.go
Normal file
90
src/autoscaler/providers/baidu/src/bcctool/utils/envvars.go
Normal 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
|
||||
}
|
||||
364
src/autoscaler/providers/baidu/src/bcctool/utils/jsonUtils.go
Normal file
364
src/autoscaler/providers/baidu/src/bcctool/utils/jsonUtils.go
Normal 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)
|
||||
}
|
||||
}
|
||||
190
src/autoscaler/providers/baidu/src/bcctool/utils/logger.go
Normal file
190
src/autoscaler/providers/baidu/src/bcctool/utils/logger.go
Normal 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)
|
||||
}
|
||||
}
|
||||
105
src/autoscaler/providers/baidu/src/bcctool/utils/provUtils.go
Normal file
105
src/autoscaler/providers/baidu/src/bcctool/utils/provUtils.go
Normal 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
|
||||
}
|
||||
65
src/autoscaler/scaler/Makefile
Normal file
65
src/autoscaler/scaler/Makefile
Normal 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
|
||||
10
src/autoscaler/scaler/conf/hostProviders.json
Normal file
10
src/autoscaler/scaler/conf/hostProviders.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"providers":[
|
||||
{
|
||||
"name": "baidu",
|
||||
"type": "googleProv",
|
||||
"confPath": "/tmp/autoscaler/baidu",
|
||||
"scriptPath": "/tmp/autoscaler/baidu"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
src/autoscaler/scaler/conf/hostProviders.yaml
Normal file
5
src/autoscaler/scaler/conf/hostProviders.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
providers:
|
||||
- name: baidu
|
||||
type: baiduProv
|
||||
confPath: /tmp/autoscaler/baidu
|
||||
scriptPath: /tmp/autoscaler/baidu
|
||||
35
src/autoscaler/scaler/include/obj_mgr.h
Normal file
35
src/autoscaler/scaler/include/obj_mgr.h
Normal 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
|
||||
13
src/autoscaler/scaler/include/plug_mgr.h
Normal file
13
src/autoscaler/scaler/include/plug_mgr.h
Normal 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
|
||||
23
src/autoscaler/scaler/include/prov_conf.h
Normal file
23
src/autoscaler/scaler/include/prov_conf.h
Normal 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
|
||||
58
src/autoscaler/scaler/include/scaler.h
Normal file
58
src/autoscaler/scaler/include/scaler.h
Normal 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
|
||||
143
src/autoscaler/scaler/include/scaler/types.h
Normal file
143
src/autoscaler/scaler/include/scaler/types.h
Normal 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
|
||||
11
src/autoscaler/scaler/include/scaler_ctx.h
Normal file
11
src/autoscaler/scaler/include/scaler_ctx.h
Normal 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
|
||||
20
src/autoscaler/scaler/include/scaler_json.h
Normal file
20
src/autoscaler/scaler/include/scaler_json.h
Normal 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
|
||||
22
src/autoscaler/scaler/include/scaler_misc.h
Normal file
22
src/autoscaler/scaler/include/scaler_misc.h
Normal 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
|
||||
28
src/autoscaler/scaler/include/scaler_obj.h
Normal file
28
src/autoscaler/scaler/include/scaler_obj.h
Normal 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
|
||||
478
src/autoscaler/scaler/obj_mgr.c
Normal file
478
src/autoscaler/scaler/obj_mgr.c
Normal 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);
|
||||
}
|
||||
500
src/autoscaler/scaler/plug_mgr.c
Normal file
500
src/autoscaler/scaler/plug_mgr.c
Normal 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;
|
||||
}
|
||||
303
src/autoscaler/scaler/prov_conf.c
Normal file
303
src/autoscaler/scaler/prov_conf.c
Normal 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;
|
||||
}
|
||||
687
src/autoscaler/scaler/scaler.c
Normal file
687
src/autoscaler/scaler/scaler.c
Normal 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;
|
||||
}
|
||||
82
src/autoscaler/scaler/scaler_ctx.c
Normal file
82
src/autoscaler/scaler/scaler_ctx.c
Normal 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;
|
||||
}
|
||||
|
||||
423
src/autoscaler/scaler/scaler_json.c
Normal file
423
src/autoscaler/scaler/scaler_json.c
Normal 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;
|
||||
}
|
||||
199
src/autoscaler/scaler/scaler_misc.c
Normal file
199
src/autoscaler/scaler/scaler_misc.c
Normal 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);
|
||||
}
|
||||
317
src/autoscaler/scaler/scaler_obj.c
Normal file
317
src/autoscaler/scaler/scaler_obj.c
Normal 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);
|
||||
}
|
||||
|
|
@ -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
|
||||
85
src/autoscaler/scaler/testsuite/scripts/getRequestStatus.sh
Normal file
85
src/autoscaler/scaler/testsuite/scripts/getRequestStatus.sh
Normal 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
|
||||
11
src/autoscaler/scaler/testsuite/scripts/getReturnRequests.sh
Normal file
11
src/autoscaler/scaler/testsuite/scripts/getReturnRequests.sh
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
cat <<EOF
|
||||
{
|
||||
"requests":[
|
||||
{
|
||||
"machine":"test-hostname-1",
|
||||
"machineId":"test-instance-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
cat <<EOF
|
||||
{
|
||||
"requestId": "test-request-id-create-1"
|
||||
}
|
||||
EOF
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
cat <<EOF
|
||||
{
|
||||
"message":"Request to terminate instances successful.",
|
||||
"requestId":"test-request-id-release-1"
|
||||
}
|
||||
EOF
|
||||
28
src/autoscaler/scaler/testsuite/test.c
Normal file
28
src/autoscaler/scaler/testsuite/test.c
Normal 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();
|
||||
}
|
||||
161
src/autoscaler/scaler/testsuite/test_plug_mgr.c
Normal file
161
src/autoscaler/scaler/testsuite/test_plug_mgr.c
Normal 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;
|
||||
}
|
||||
148
src/autoscaler/scaler/testsuite/test_scale_misc.c
Normal file
148
src/autoscaler/scaler/testsuite/test_scale_misc.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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
12
src/flake.lock
generated
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ stdenv.mkDerivation {
|
|||
libtirpc
|
||||
readline
|
||||
ncurses
|
||||
cjson
|
||||
struct2json
|
||||
capnpc
|
||||
which
|
||||
findutils
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ xcecd_worker_deps = [
|
|||
libuv_dep,
|
||||
elogind_dep,
|
||||
sqlite3_dep,
|
||||
libcjson_dep,
|
||||
struct2json_dep,
|
||||
yaml_deps
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue