From 5036d77f04cb4d9a11a3393c3cd6e5a058549826 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Fri, 5 Dec 2025 18:02:06 +0800 Subject: [PATCH] use struct2json and import autoscaler --- src/autoscaler/Makefile | 15 + src/autoscaler/meson.build | 24 + src/autoscaler/providers/Makefile | 15 + src/autoscaler/providers/baidu/BccTool.env | 5 + src/autoscaler/providers/baidu/Makefile | 15 + .../baidu/lsf/conf/baiduprov_config.json | 7 + .../baidu/lsf/conf/baiduprov_templates.json | 44 ++ .../providers/baidu/lsf/conf/credentials | 4 + .../providers/baidu/lsf/conf/logger.yaml | 27 + .../postprovision/lsf/example_user_data.sh | 61 ++ .../baidu/scripts/getAvailableMachines.sh | 13 + .../baidu/scripts/getAvailableTemplates.sh | 12 + .../baidu/scripts/getRequestStatus.sh | 12 + .../baidu/scripts/getReturnRequests.sh | 12 + .../baidu/scripts/requestMachines.sh | 12 + .../baidu/scripts/requestReturnMachines.sh | 12 + .../providers/baidu/src/bcctool/BccTool.env | 5 + .../baidu/src/bcctool/client/bccClient.go | 397 ++++++++++ .../baidu/src/bcctool/enums/provCode.go | 117 +++ .../providers/baidu/src/bcctool/go.mod | 21 + .../providers/baidu/src/bcctool/go.sum | 23 + .../providers/baidu/src/bcctool/main.go | 58 ++ .../baidu/src/bcctool/model/types.go | 607 ++++++++++++++++ .../baidu/src/bcctool/provconst/bdConst.go | 31 + .../src/bcctool/provservices/provService.go | 412 +++++++++++ .../baidu/src/bcctool/utils/envvars.go | 90 +++ .../baidu/src/bcctool/utils/jsonUtils.go | 364 ++++++++++ .../baidu/src/bcctool/utils/logger.go | 190 +++++ .../baidu/src/bcctool/utils/provUtils.go | 105 +++ src/autoscaler/scaler/Makefile | 65 ++ src/autoscaler/scaler/conf/hostProviders.json | 10 + src/autoscaler/scaler/conf/hostProviders.yaml | 5 + src/autoscaler/scaler/include/obj_mgr.h | 35 + src/autoscaler/scaler/include/plug_mgr.h | 13 + src/autoscaler/scaler/include/prov_conf.h | 23 + src/autoscaler/scaler/include/scaler.h | 58 ++ src/autoscaler/scaler/include/scaler/types.h | 143 ++++ src/autoscaler/scaler/include/scaler_ctx.h | 11 + src/autoscaler/scaler/include/scaler_json.h | 20 + src/autoscaler/scaler/include/scaler_misc.h | 22 + src/autoscaler/scaler/include/scaler_obj.h | 28 + src/autoscaler/scaler/obj_mgr.c | 478 ++++++++++++ src/autoscaler/scaler/plug_mgr.c | 500 +++++++++++++ src/autoscaler/scaler/prov_conf.c | 303 ++++++++ src/autoscaler/scaler/scaler.c | 687 ++++++++++++++++++ src/autoscaler/scaler/scaler_ctx.c | 82 +++ src/autoscaler/scaler/scaler_json.c | 423 +++++++++++ src/autoscaler/scaler/scaler_misc.c | 199 +++++ src/autoscaler/scaler/scaler_obj.c | 317 ++++++++ .../scripts/getAvailableTemplates.sh | 38 + .../testsuite/scripts/getRequestStatus.sh | 85 +++ .../testsuite/scripts/getReturnRequests.sh | 11 + .../testsuite/scripts/requestMachines.sh | 6 + .../scripts/requestReturnMachines.sh | 7 + src/autoscaler/scaler/testsuite/test.c | 28 + .../scaler/testsuite/test_plug_mgr.c | 161 ++++ .../scaler/testsuite/test_scale_misc.c | 148 ++++ src/controller/meson.build | 2 +- src/flake.lock | 12 +- src/meson.build | 4 +- src/nixSupport/xce.nix | 2 +- src/worker/meson.build | 2 +- 62 files changed, 6628 insertions(+), 10 deletions(-) create mode 100644 src/autoscaler/Makefile create mode 100644 src/autoscaler/meson.build create mode 100644 src/autoscaler/providers/Makefile create mode 100644 src/autoscaler/providers/baidu/BccTool.env create mode 100644 src/autoscaler/providers/baidu/Makefile create mode 100644 src/autoscaler/providers/baidu/lsf/conf/baiduprov_config.json create mode 100644 src/autoscaler/providers/baidu/lsf/conf/baiduprov_templates.json create mode 100644 src/autoscaler/providers/baidu/lsf/conf/credentials create mode 100644 src/autoscaler/providers/baidu/lsf/conf/logger.yaml create mode 100755 src/autoscaler/providers/baidu/postprovision/lsf/example_user_data.sh create mode 100755 src/autoscaler/providers/baidu/scripts/getAvailableMachines.sh create mode 100755 src/autoscaler/providers/baidu/scripts/getAvailableTemplates.sh create mode 100755 src/autoscaler/providers/baidu/scripts/getRequestStatus.sh create mode 100755 src/autoscaler/providers/baidu/scripts/getReturnRequests.sh create mode 100755 src/autoscaler/providers/baidu/scripts/requestMachines.sh create mode 100755 src/autoscaler/providers/baidu/scripts/requestReturnMachines.sh create mode 100644 src/autoscaler/providers/baidu/src/bcctool/BccTool.env create mode 100644 src/autoscaler/providers/baidu/src/bcctool/client/bccClient.go create mode 100644 src/autoscaler/providers/baidu/src/bcctool/enums/provCode.go create mode 100644 src/autoscaler/providers/baidu/src/bcctool/go.mod create mode 100644 src/autoscaler/providers/baidu/src/bcctool/go.sum create mode 100644 src/autoscaler/providers/baidu/src/bcctool/main.go create mode 100644 src/autoscaler/providers/baidu/src/bcctool/model/types.go create mode 100644 src/autoscaler/providers/baidu/src/bcctool/provconst/bdConst.go create mode 100644 src/autoscaler/providers/baidu/src/bcctool/provservices/provService.go create mode 100644 src/autoscaler/providers/baidu/src/bcctool/utils/envvars.go create mode 100644 src/autoscaler/providers/baidu/src/bcctool/utils/jsonUtils.go create mode 100644 src/autoscaler/providers/baidu/src/bcctool/utils/logger.go create mode 100644 src/autoscaler/providers/baidu/src/bcctool/utils/provUtils.go create mode 100644 src/autoscaler/scaler/Makefile create mode 100644 src/autoscaler/scaler/conf/hostProviders.json create mode 100644 src/autoscaler/scaler/conf/hostProviders.yaml create mode 100644 src/autoscaler/scaler/include/obj_mgr.h create mode 100644 src/autoscaler/scaler/include/plug_mgr.h create mode 100644 src/autoscaler/scaler/include/prov_conf.h create mode 100644 src/autoscaler/scaler/include/scaler.h create mode 100644 src/autoscaler/scaler/include/scaler/types.h create mode 100644 src/autoscaler/scaler/include/scaler_ctx.h create mode 100644 src/autoscaler/scaler/include/scaler_json.h create mode 100644 src/autoscaler/scaler/include/scaler_misc.h create mode 100644 src/autoscaler/scaler/include/scaler_obj.h create mode 100644 src/autoscaler/scaler/obj_mgr.c create mode 100644 src/autoscaler/scaler/plug_mgr.c create mode 100644 src/autoscaler/scaler/prov_conf.c create mode 100644 src/autoscaler/scaler/scaler.c create mode 100644 src/autoscaler/scaler/scaler_ctx.c create mode 100644 src/autoscaler/scaler/scaler_json.c create mode 100644 src/autoscaler/scaler/scaler_misc.c create mode 100644 src/autoscaler/scaler/scaler_obj.c create mode 100644 src/autoscaler/scaler/testsuite/scripts/getAvailableTemplates.sh create mode 100644 src/autoscaler/scaler/testsuite/scripts/getRequestStatus.sh create mode 100644 src/autoscaler/scaler/testsuite/scripts/getReturnRequests.sh create mode 100644 src/autoscaler/scaler/testsuite/scripts/requestMachines.sh create mode 100644 src/autoscaler/scaler/testsuite/scripts/requestReturnMachines.sh create mode 100644 src/autoscaler/scaler/testsuite/test.c create mode 100644 src/autoscaler/scaler/testsuite/test_plug_mgr.c create mode 100644 src/autoscaler/scaler/testsuite/test_scale_misc.c diff --git a/src/autoscaler/Makefile b/src/autoscaler/Makefile new file mode 100644 index 00000000..0cc524a4 --- /dev/null +++ b/src/autoscaler/Makefile @@ -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 diff --git a/src/autoscaler/meson.build b/src/autoscaler/meson.build new file mode 100644 index 00000000..ea12a1be --- /dev/null +++ b/src/autoscaler/meson.build @@ -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) diff --git a/src/autoscaler/providers/Makefile b/src/autoscaler/providers/Makefile new file mode 100644 index 00000000..6487e460 --- /dev/null +++ b/src/autoscaler/providers/Makefile @@ -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 diff --git a/src/autoscaler/providers/baidu/BccTool.env b/src/autoscaler/providers/baidu/BccTool.env new file mode 100644 index 00000000..fb7024ec --- /dev/null +++ b/src/autoscaler/providers/baidu/BccTool.env @@ -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 diff --git a/src/autoscaler/providers/baidu/Makefile b/src/autoscaler/providers/baidu/Makefile new file mode 100644 index 00000000..456456de --- /dev/null +++ b/src/autoscaler/providers/baidu/Makefile @@ -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) diff --git a/src/autoscaler/providers/baidu/lsf/conf/baiduprov_config.json b/src/autoscaler/providers/baidu/lsf/conf/baiduprov_config.json new file mode 100644 index 00000000..7a93b70e --- /dev/null +++ b/src/autoscaler/providers/baidu/lsf/conf/baiduprov_config.json @@ -0,0 +1,7 @@ +{ + "LogLevel": "debug", + "VPC": "default", + "Region": "bd", + "CredentialFile": "/opt/lsf/conf/resource_connector/baidu/conf/credentials" +} + diff --git a/src/autoscaler/providers/baidu/lsf/conf/baiduprov_templates.json b/src/autoscaler/providers/baidu/lsf/conf/baiduprov_templates.json new file mode 100644 index 00000000..34aff21e --- /dev/null +++ b/src/autoscaler/providers/baidu/lsf/conf/baiduprov_templates.json @@ -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" + } + ] +} diff --git a/src/autoscaler/providers/baidu/lsf/conf/credentials b/src/autoscaler/providers/baidu/lsf/conf/credentials new file mode 100644 index 00000000..18f40b6b --- /dev/null +++ b/src/autoscaler/providers/baidu/lsf/conf/credentials @@ -0,0 +1,4 @@ +{ + "AK": "xxxxxxxxxxxxxxxxxxxxxxxxx", + "SK": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +} diff --git a/src/autoscaler/providers/baidu/lsf/conf/logger.yaml b/src/autoscaler/providers/baidu/lsf/conf/logger.yaml new file mode 100644 index 00000000..39b269df --- /dev/null +++ b/src/autoscaler/providers/baidu/lsf/conf/logger.yaml @@ -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" diff --git a/src/autoscaler/providers/baidu/postprovision/lsf/example_user_data.sh b/src/autoscaler/providers/baidu/postprovision/lsf/example_user_data.sh new file mode 100755 index 00000000..223c4eca --- /dev/null +++ b/src/autoscaler/providers/baidu/postprovision/lsf/example_user_data.sh @@ -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 diff --git a/src/autoscaler/providers/baidu/scripts/getAvailableMachines.sh b/src/autoscaler/providers/baidu/scripts/getAvailableMachines.sh new file mode 100755 index 00000000..548c7c78 --- /dev/null +++ b/src/autoscaler/providers/baidu/scripts/getAvailableMachines.sh @@ -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 diff --git a/src/autoscaler/providers/baidu/scripts/getAvailableTemplates.sh b/src/autoscaler/providers/baidu/scripts/getAvailableTemplates.sh new file mode 100755 index 00000000..b0110b07 --- /dev/null +++ b/src/autoscaler/providers/baidu/scripts/getAvailableTemplates.sh @@ -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 diff --git a/src/autoscaler/providers/baidu/scripts/getRequestStatus.sh b/src/autoscaler/providers/baidu/scripts/getRequestStatus.sh new file mode 100755 index 00000000..8d5581bf --- /dev/null +++ b/src/autoscaler/providers/baidu/scripts/getRequestStatus.sh @@ -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 diff --git a/src/autoscaler/providers/baidu/scripts/getReturnRequests.sh b/src/autoscaler/providers/baidu/scripts/getReturnRequests.sh new file mode 100755 index 00000000..22d0aa44 --- /dev/null +++ b/src/autoscaler/providers/baidu/scripts/getReturnRequests.sh @@ -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 diff --git a/src/autoscaler/providers/baidu/scripts/requestMachines.sh b/src/autoscaler/providers/baidu/scripts/requestMachines.sh new file mode 100755 index 00000000..f1adc154 --- /dev/null +++ b/src/autoscaler/providers/baidu/scripts/requestMachines.sh @@ -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 diff --git a/src/autoscaler/providers/baidu/scripts/requestReturnMachines.sh b/src/autoscaler/providers/baidu/scripts/requestReturnMachines.sh new file mode 100755 index 00000000..71d302f2 --- /dev/null +++ b/src/autoscaler/providers/baidu/scripts/requestReturnMachines.sh @@ -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 diff --git a/src/autoscaler/providers/baidu/src/bcctool/BccTool.env b/src/autoscaler/providers/baidu/src/bcctool/BccTool.env new file mode 100644 index 00000000..fb7024ec --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/BccTool.env @@ -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 diff --git a/src/autoscaler/providers/baidu/src/bcctool/client/bccClient.go b/src/autoscaler/providers/baidu/src/bcctool/client/bccClient.go new file mode 100644 index 00000000..7e45c1b0 --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/client/bccClient.go @@ -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) +} diff --git a/src/autoscaler/providers/baidu/src/bcctool/enums/provCode.go b/src/autoscaler/providers/baidu/src/bcctool/enums/provCode.go new file mode 100644 index 00000000..bff2b97b --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/enums/provCode.go @@ -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 _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 +) diff --git a/src/autoscaler/providers/baidu/src/bcctool/go.mod b/src/autoscaler/providers/baidu/src/bcctool/go.mod new file mode 100644 index 00000000..cc915570 --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/go.mod @@ -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 +) diff --git a/src/autoscaler/providers/baidu/src/bcctool/go.sum b/src/autoscaler/providers/baidu/src/bcctool/go.sum new file mode 100644 index 00000000..f6471e5e --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/go.sum @@ -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= diff --git a/src/autoscaler/providers/baidu/src/bcctool/main.go b/src/autoscaler/providers/baidu/src/bcctool/main.go new file mode 100644 index 00000000..f93bb600 --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/main.go @@ -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) + } +} diff --git a/src/autoscaler/providers/baidu/src/bcctool/model/types.go b/src/autoscaler/providers/baidu/src/bcctool/model/types.go new file mode 100644 index 00000000..1fc077f5 --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/model/types.go @@ -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() +} diff --git a/src/autoscaler/providers/baidu/src/bcctool/provconst/bdConst.go b/src/autoscaler/providers/baidu/src/bcctool/provconst/bdConst.go new file mode 100644 index 00000000..3f7c6a5e --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/provconst/bdConst.go @@ -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 +) diff --git a/src/autoscaler/providers/baidu/src/bcctool/provservices/provService.go b/src/autoscaler/providers/baidu/src/bcctool/provservices/provService.go new file mode 100644 index 00000000..2eb27478 --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/provservices/provService.go @@ -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 +} diff --git a/src/autoscaler/providers/baidu/src/bcctool/utils/envvars.go b/src/autoscaler/providers/baidu/src/bcctool/utils/envvars.go new file mode 100644 index 00000000..7a7b2b70 --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/utils/envvars.go @@ -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 /work//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 +} diff --git a/src/autoscaler/providers/baidu/src/bcctool/utils/jsonUtils.go b/src/autoscaler/providers/baidu/src/bcctool/utils/jsonUtils.go new file mode 100644 index 00000000..faeabd84 --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/utils/jsonUtils.go @@ -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) + } +} diff --git a/src/autoscaler/providers/baidu/src/bcctool/utils/logger.go b/src/autoscaler/providers/baidu/src/bcctool/utils/logger.go new file mode 100644 index 00000000..87e4be1c --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/utils/logger.go @@ -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) + } +} diff --git a/src/autoscaler/providers/baidu/src/bcctool/utils/provUtils.go b/src/autoscaler/providers/baidu/src/bcctool/utils/provUtils.go new file mode 100644 index 00000000..f0bca4e5 --- /dev/null +++ b/src/autoscaler/providers/baidu/src/bcctool/utils/provUtils.go @@ -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 +} diff --git a/src/autoscaler/scaler/Makefile b/src/autoscaler/scaler/Makefile new file mode 100644 index 00000000..39d663d2 --- /dev/null +++ b/src/autoscaler/scaler/Makefile @@ -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 diff --git a/src/autoscaler/scaler/conf/hostProviders.json b/src/autoscaler/scaler/conf/hostProviders.json new file mode 100644 index 00000000..e6e6eaa7 --- /dev/null +++ b/src/autoscaler/scaler/conf/hostProviders.json @@ -0,0 +1,10 @@ +{ + "providers":[ + { + "name": "baidu", + "type": "googleProv", + "confPath": "/tmp/autoscaler/baidu", + "scriptPath": "/tmp/autoscaler/baidu" + } + ] +} diff --git a/src/autoscaler/scaler/conf/hostProviders.yaml b/src/autoscaler/scaler/conf/hostProviders.yaml new file mode 100644 index 00000000..4e74ec35 --- /dev/null +++ b/src/autoscaler/scaler/conf/hostProviders.yaml @@ -0,0 +1,5 @@ +providers: + - name: baidu + type: baiduProv + confPath: /tmp/autoscaler/baidu + scriptPath: /tmp/autoscaler/baidu \ No newline at end of file diff --git a/src/autoscaler/scaler/include/obj_mgr.h b/src/autoscaler/scaler/include/obj_mgr.h new file mode 100644 index 00000000..f9a80ba8 --- /dev/null +++ b/src/autoscaler/scaler/include/obj_mgr.h @@ -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 \ No newline at end of file diff --git a/src/autoscaler/scaler/include/plug_mgr.h b/src/autoscaler/scaler/include/plug_mgr.h new file mode 100644 index 00000000..1a2100a9 --- /dev/null +++ b/src/autoscaler/scaler/include/plug_mgr.h @@ -0,0 +1,13 @@ +#ifndef _PLUG_MGR_H_ +#define _PLUG_MGR_H_ + +#include +#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 diff --git a/src/autoscaler/scaler/include/prov_conf.h b/src/autoscaler/scaler/include/prov_conf.h new file mode 100644 index 00000000..9d7e1817 --- /dev/null +++ b/src/autoscaler/scaler/include/prov_conf.h @@ -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 diff --git a/src/autoscaler/scaler/include/scaler.h b/src/autoscaler/scaler/include/scaler.h new file mode 100644 index 00000000..207a70d4 --- /dev/null +++ b/src/autoscaler/scaler/include/scaler.h @@ -0,0 +1,58 @@ +#ifndef _SCALER_H_ +#define _SCALER_H_ + +#include +#include +#include + +#include +#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 diff --git a/src/autoscaler/scaler/include/scaler/types.h b/src/autoscaler/scaler/include/scaler/types.h new file mode 100644 index 00000000..6e62155a --- /dev/null +++ b/src/autoscaler/scaler/include/scaler/types.h @@ -0,0 +1,143 @@ +#ifndef _SCALER_TYPES_H_ +#define _SCALER_TYPES_H_ + +#include +#include +#include + +#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 diff --git a/src/autoscaler/scaler/include/scaler_ctx.h b/src/autoscaler/scaler/include/scaler_ctx.h new file mode 100644 index 00000000..72aa2bb6 --- /dev/null +++ b/src/autoscaler/scaler/include/scaler_ctx.h @@ -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 \ No newline at end of file diff --git a/src/autoscaler/scaler/include/scaler_json.h b/src/autoscaler/scaler/include/scaler_json.h new file mode 100644 index 00000000..fdf5e48c --- /dev/null +++ b/src/autoscaler/scaler/include/scaler_json.h @@ -0,0 +1,20 @@ +#ifndef __JSON_UTILS_H__ +#define __JSON_UTILS_H__ +#include +#include +#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 diff --git a/src/autoscaler/scaler/include/scaler_misc.h b/src/autoscaler/scaler/include/scaler_misc.h new file mode 100644 index 00000000..9ff501fa --- /dev/null +++ b/src/autoscaler/scaler/include/scaler_misc.h @@ -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 diff --git a/src/autoscaler/scaler/include/scaler_obj.h b/src/autoscaler/scaler/include/scaler_obj.h new file mode 100644 index 00000000..75a39338 --- /dev/null +++ b/src/autoscaler/scaler/include/scaler_obj.h @@ -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 diff --git a/src/autoscaler/scaler/obj_mgr.c b/src/autoscaler/scaler/obj_mgr.c new file mode 100644 index 00000000..7f866ee6 --- /dev/null +++ b/src/autoscaler/scaler/obj_mgr.c @@ -0,0 +1,478 @@ +#include +#include +#include + +#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); +} diff --git a/src/autoscaler/scaler/plug_mgr.c b/src/autoscaler/scaler/plug_mgr.c new file mode 100644 index 00000000..ec6a23d4 --- /dev/null +++ b/src/autoscaler/scaler/plug_mgr.c @@ -0,0 +1,500 @@ +// Main functions: +#include +#include "obj_mgr.h" +#include "scaler.h" +#include "scaler_json.h" +#include "scaler_misc.h" +#include "scaler_obj.h" +#include "utils.h" +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/src/autoscaler/scaler/prov_conf.c b/src/autoscaler/scaler/prov_conf.c new file mode 100644 index 00000000..b43f1284 --- /dev/null +++ b/src/autoscaler/scaler/prov_conf.c @@ -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 +#include + +#include +#include +#include +#include + +#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 /conf and /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; +} diff --git a/src/autoscaler/scaler/scaler.c b/src/autoscaler/scaler/scaler.c new file mode 100644 index 00000000..cf2008d6 --- /dev/null +++ b/src/autoscaler/scaler/scaler.c @@ -0,0 +1,687 @@ +#include +#include + +#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; +} diff --git a/src/autoscaler/scaler/scaler_ctx.c b/src/autoscaler/scaler/scaler_ctx.c new file mode 100644 index 00000000..c545376e --- /dev/null +++ b/src/autoscaler/scaler/scaler_ctx.c @@ -0,0 +1,82 @@ + +#include + +#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; +} + diff --git a/src/autoscaler/scaler/scaler_json.c b/src/autoscaler/scaler/scaler_json.c new file mode 100644 index 00000000..2c939faa --- /dev/null +++ b/src/autoscaler/scaler/scaler_json.c @@ -0,0 +1,423 @@ +#include +#include + +#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; +} diff --git a/src/autoscaler/scaler/scaler_misc.c b/src/autoscaler/scaler/scaler_misc.c new file mode 100644 index 00000000..4e3c0685 --- /dev/null +++ b/src/autoscaler/scaler/scaler_misc.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include + +#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); +} diff --git a/src/autoscaler/scaler/scaler_obj.c b/src/autoscaler/scaler/scaler_obj.c new file mode 100644 index 00000000..c0c55d0d --- /dev/null +++ b/src/autoscaler/scaler/scaler_obj.c @@ -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); +} diff --git a/src/autoscaler/scaler/testsuite/scripts/getAvailableTemplates.sh b/src/autoscaler/scaler/testsuite/scripts/getAvailableTemplates.sh new file mode 100644 index 00000000..acb19d1e --- /dev/null +++ b/src/autoscaler/scaler/testsuite/scripts/getAvailableTemplates.sh @@ -0,0 +1,38 @@ +#!/bin/sh +cat < +#include +#include + +#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(); +} diff --git a/src/autoscaler/scaler/testsuite/test_plug_mgr.c b/src/autoscaler/scaler/testsuite/test_plug_mgr.c new file mode 100644 index 00000000..b96174d4 --- /dev/null +++ b/src/autoscaler/scaler/testsuite/test_plug_mgr.c @@ -0,0 +1,161 @@ +#include +#include + +#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; +} diff --git a/src/autoscaler/scaler/testsuite/test_scale_misc.c b/src/autoscaler/scaler/testsuite/test_scale_misc.c new file mode 100644 index 00000000..6c557c55 --- /dev/null +++ b/src/autoscaler/scaler/testsuite/test_scale_misc.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +#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; +} diff --git a/src/controller/meson.build b/src/controller/meson.build index 6c56b66b..1be842ab 100644 --- a/src/controller/meson.build +++ b/src/controller/meson.build @@ -45,7 +45,7 @@ history_dep = dependency('history') xce_test_deps = [ libxce_dep, - libcjson_dep, + struct2json_dep, readline_dep, history_dep, ncurses_dep diff --git a/src/flake.lock b/src/flake.lock index 38679ce1..990c2c43 100644 --- a/src/flake.lock +++ b/src/flake.lock @@ -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": { diff --git a/src/meson.build b/src/meson.build index d3e9a124..65deebc3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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') diff --git a/src/nixSupport/xce.nix b/src/nixSupport/xce.nix index c1571a23..2fc6663d 100644 --- a/src/nixSupport/xce.nix +++ b/src/nixSupport/xce.nix @@ -28,7 +28,7 @@ stdenv.mkDerivation { libtirpc readline ncurses - cjson + struct2json capnpc which findutils diff --git a/src/worker/meson.build b/src/worker/meson.build index 3b6effc3..2c5c25aa 100644 --- a/src/worker/meson.build +++ b/src/worker/meson.build @@ -20,7 +20,7 @@ xcecd_worker_deps = [ libuv_dep, elogind_dep, sqlite3_dep, - libcjson_dep, + struct2json_dep, yaml_deps ]