├── LICENSE ├── Makefile ├── README.md ├── agent.yaml ├── api └── api.yaml ├── build ├── agent │ ├── Dockerfile │ └── entrypoint.sh └── proxy │ ├── Dockerfile │ └── entrypoint.sh ├── cmd ├── agent │ └── main.go └── proxy │ └── main.go ├── go.mod ├── go.sum ├── pkg ├── client │ └── pool.go ├── concurrency │ ├── concurrency.go │ └── metric.go ├── config │ ├── config.go │ └── const_var.go ├── datastore │ ├── datastore.go │ ├── datastore_factory.go │ ├── ots.go │ ├── ots_test.go │ ├── sqlite.go │ ├── sqlite_test.go │ └── table_meta.go ├── handler │ ├── agent.go │ ├── proxy.go │ └── util.go ├── log │ ├── instance.go │ └── monitor.go ├── models │ └── sd.go ├── module │ ├── event.go │ ├── function.go │ ├── function_test.go │ ├── listen.go │ ├── oss.go │ ├── oss_test.go │ ├── proxy.go │ ├── sd.go │ └── user.go ├── server │ ├── agent.go │ └── proxy.go └── utils │ ├── bcrypt.go │ ├── exec.go │ ├── utils.go │ └── utils_test.go ├── proxy.yaml └── script ├── client-config.yaml ├── codegen.sh ├── models-config.yaml ├── request ├── extra_image.py ├── ima2img.py ├── login.py ├── resource.py └── txt2img.py └── server-config.yaml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Serverless Devs Registry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | IMAGE=registry.cn-beijing.aliyuncs.com/xxx/sd-api 2 | TAG=v1 3 | 4 | build-agent: 5 | sh script/codegen.sh 6 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/agent/agentServer cmd/agent/main.go 7 | build-proxy: 8 | sh script/codegen.sh 9 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/proxy/proxyServer cmd/proxy/main.go 10 | build-agent-image: build-agent 11 | chmod 755 build/agent/entrypoint.sh 12 | DOCKER_BUILDKIT=1 docker build -f build/agent/Dockerfile -t ${IMAGE}:agent_${TAG} . 13 | build-proxy-image: build-proxy 14 | chmod 755 build/proxy/entrypoint.sh 15 | DOCKER_BUILDKIT=1 docker build -f build/proxy/Dockerfile -t ${IMAGE}:proxy_${TAG} . 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-stable-diffusion-api -------------------------------------------------------------------------------- /agent.yaml: -------------------------------------------------------------------------------- 1 | otsEndpoint: https://sd-api-t.cn-beijing.ots.aliyuncs.com 2 | otsInstanceName: sd-api-t 3 | otsTimeToAlive: -1 4 | otsMaxVersion: 1 5 | ossEndpoint: oss-cn-beijing.aliyuncs.com 6 | bucket: sd-api-t 7 | ossMode: local 8 | ossPath: /mnt/oss 9 | dbSqlite: /mnt/auto/sd/sqlite3 10 | listenInterval: 1 11 | sdUrlPrefix: http://localhost:7861 12 | sdShell: python -u webui.py --listen --xformers --enable-insecure-extension-access --skip-version-check --no-download-sd-model --port 7861 13 | progressImageOutputSwitch: off 14 | sdPath: /stable-diffusion-webui 15 | useLocalModel: yes 16 | exposeToUser: No 17 | serverName: agent -------------------------------------------------------------------------------- /build/agent/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.cn-beijing.aliyuncs.com/aliyun-fc/fc-stable-diffusion:realman-v4 2 | COPY build/agent/agentServer /agent/agent 3 | COPY agent.yaml /agent/agent.yaml 4 | COPY build/agent/entrypoint.sh /docker/entrypoint.sh 5 | EXPOSE 7860 -------------------------------------------------------------------------------- /build/agent/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -Eeuo pipefail 4 | 5 | function mount_file() { 6 | echo Mount $1 to $2 7 | 8 | SRC="$1" 9 | DST="$2" 10 | 11 | rm -rf "${DST}" 12 | 13 | if [ ! -f "${SRC}" ]; then 14 | mkdir -pv "${SRC}" 15 | fi 16 | 17 | mkdir -pv "$(dirname "${DST}")" 18 | 19 | ln -sT "${SRC}" "${DST}" 20 | } 21 | 22 | 23 | NAS_DIR="/mnt/auto/sd" 24 | 25 | # 内置模型准备 26 | # 如果挂载了 NAS,软链接到 NAS 中 27 | # 如果未挂载 NAS,则尝试直接将内置模型过载 28 | NAS_MOUNTED=0 29 | if [ -d "/mnt/auto" ]; then 30 | NAS_MOUNTED=1 31 | fi 32 | 33 | if [ "$NAS_MOUNTED" == "0" ]; then 34 | echo "without NAS, mount $SD_BUILTIN to ${NAS_DIR}" 35 | mount_file "$SD_BUILTIN" "${NAS_DIR}" 36 | else 37 | mkdir -p "${NAS_DIR}" 38 | 39 | echo "with NAS, mount built-in files to ${NAS_DIR}" 40 | 41 | find ${SD_BUILTIN} | while read -r file; do 42 | SRC="${file}" 43 | DST="${NAS_DIR}/${file#$SD_BUILTIN/}" 44 | 45 | if [ ! -e "$DST" ] && [ ! -d "$SRC" ] && [ "$DST" != "${NAS_DIR}/config.json" ] && [ "$DST" != "${NAS_DIR}/ui-config.json" ]; then 46 | mount_file "$SRC" "$DST" 47 | fi 48 | done 49 | 50 | if [ ! -e "${NAS_DIR}/config.json" ]; then 51 | echo "no config.json, copy it" 52 | cp "${SD_BUILTIN}/config.json" "${NAS_DIR}/config.json" 53 | fi 54 | 55 | if [ "$(wc -c ${NAS_DIR}/config.json | cut -f 1 -d ' ')" == "0" ]; then 56 | echo "config.json is empty, copy it" 57 | rm -f "${SD_BUILTIN}/config.json" 58 | cp "${SD_BUILTIN}/config.json" "${NAS_DIR}/config.json" 59 | fi 60 | 61 | if [ ! -e "${NAS_DIR}/ui-config.json" ]; then 62 | echo "no ui-config.json, copy it" 63 | cp "${SD_BUILTIN}/ui-config.json" "${NAS_DIR}/ui-config.json" 64 | fi 65 | fi 66 | 67 | if [ ! -e "${NAS_DIR}/styles.csv" ]; then 68 | echo "no styles.csv, create it" 69 | touch "${NAS_DIR}/styles.csv" 70 | fi 71 | 72 | 73 | declare -A MOUNTS 74 | 75 | MOUNTS["/root"]="${NAS_DIR}/root" 76 | MOUNTS["${ROOT}/models"]="${NAS_DIR}/models" 77 | MOUNTS["${ROOT}/localizations"]="${NAS_DIR}/localizations" 78 | MOUNTS["${ROOT}/configs"]="${NAS_DIR}/configs" 79 | MOUNTS["${ROOT}/embeddings"]="${NAS_DIR}/embeddings" 80 | MOUNTS["${ROOT}/extensions-builtin"]="${NAS_DIR}/extensions-builtin" 81 | MOUNTS["${ROOT}/textual_inversion_templates"]="${NAS_DIR}/textual_inversion_templates" 82 | MOUNTS["${ROOT}/config.json"]="${NAS_DIR}/config.json" 83 | MOUNTS["${ROOT}/ui-config.json"]="${NAS_DIR}/ui-config.json" 84 | MOUNTS["${ROOT}/extensions"]="${NAS_DIR}/extensions" 85 | MOUNTS["${ROOT}/outputs"]="${NAS_DIR}/outputs" 86 | MOUNTS["${ROOT}/styles.csv"]="${NAS_DIR}/styles.csv" 87 | MOUNTS["${ROOT}/scripts"]="${NAS_DIR}/scripts" 88 | MOUNTS["${ROOT}/repositories/CodeFormer/weights/facelib"]="${NAS_DIR}/repositories/CodeFormer/weights/facelib" 89 | 90 | 91 | for to_path in "${!MOUNTS[@]}"; do 92 | mount_file "${MOUNTS[${to_path}]}" "${to_path}" 93 | done 94 | 95 | if [ -f "/mnt/auto/sd/startup.sh" ]; then 96 | pushd ${ROOT} 97 | . /mnt/auto/sd/startup.sh 98 | popd 99 | fi 100 | 101 | CLI_ARGS="${CLI_ARGS:---xformers --enable-insecure-extension-access --skip-version-check --no-download-sd-model}" 102 | EXTRA_ARGS="${EXTRA_ARGS:-}" 103 | 104 | export ARGS="${CLI_ARGS} ${EXTRA_ARGS}" 105 | export PYTHONPATH="${PYTHONPATH:-}:${NAS_DIR}/python" 106 | 107 | echo "args: $ARGS" 108 | 109 | /agent/agent -port=7860 -dbType=tableStore -config=/agent/agent.yaml -sd="python -u webui.py --listen --port 7861 ${ARGS}" 110 | 111 | #echo "------start webui---------" 112 | # 113 | #python -u webui.py --listen --port 7861 ${ARGS} -------------------------------------------------------------------------------- /build/proxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:latest 2 | COPY build/proxy/proxyServer /proxy/proxy 3 | COPY proxy.yaml /proxy/proxy.yaml 4 | COPY build/proxy/entrypoint.sh /docker/entrypoint.sh 5 | EXPOSE 80 6 | ENTRYPOINT ["/docker/entrypoint.sh"] -------------------------------------------------------------------------------- /build/proxy/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /proxy/proxy -port=80 -dbType=tableStore -config=/proxy/proxy.yaml -------------------------------------------------------------------------------- /cmd/agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 6 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/datastore" 7 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/server" 8 | "github.com/sirupsen/logrus" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | ) 14 | 15 | const ( 16 | defaultPort = "7860" 17 | defaultDBType = datastore.TableStore 18 | shutdownTimeout = 5 * time.Second // 5s 19 | defaultConfigPath = "config.yaml" 20 | ) 21 | 22 | func handleSignal() { 23 | // Wait for interrupt signal to gracefully shutdown the server with 24 | quit := make(chan os.Signal, 1) 25 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 26 | <-quit 27 | logrus.Info("Shutting down server...") 28 | } 29 | 30 | func logInit(logLevel string) { 31 | switch logLevel { 32 | case "debug": 33 | logrus.SetLevel(logrus.DebugLevel) 34 | // include function and file 35 | logrus.SetReportCaller(true) 36 | case "dev": 37 | logrus.SetLevel(logrus.InfoLevel) 38 | default: 39 | logrus.SetLevel(logrus.WarnLevel) 40 | } 41 | } 42 | 43 | func main() { 44 | port := flag.String("port", defaultPort, "server listen port, default 8010") 45 | dbType := flag.String("dbType", string(defaultDBType), "db type default sqlite") 46 | configFile := flag.String("config", defaultConfigPath, "default config path") 47 | mode := flag.String("mode", "dev", "service work mode debug|dev|product") 48 | sdShell := flag.String("sd", "", "sd start shell") 49 | flag.Parse() 50 | // init log 51 | logInit(*mode) 52 | logrus.Info("agent start") 53 | 54 | // init config 55 | if err := config.InitConfig(*configFile); err != nil { 56 | logrus.Fatal(err.Error()) 57 | } 58 | logrus.Info(*sdShell) 59 | config.ConfigGlobal.SdShell = *sdShell 60 | 61 | // init server and start 62 | agent, err := server.NewAgentServer(*port, datastore.DatastoreType(*dbType), *mode) 63 | if err != nil { 64 | logrus.Fatal("agent server init fail") 65 | } 66 | go agent.Start() 67 | 68 | // wait shutdown signal 69 | handleSignal() 70 | 71 | if err := agent.Close(shutdownTimeout); err != nil { 72 | logrus.Fatal("Shutdown server fail") 73 | } 74 | 75 | logrus.Info("Server exited") 76 | } 77 | -------------------------------------------------------------------------------- /cmd/proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 7 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/datastore" 8 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/server" 9 | "github.com/sirupsen/logrus" 10 | "os" 11 | "os/signal" 12 | "syscall" 13 | "time" 14 | ) 15 | 16 | const ( 17 | defaultPort = "7860" 18 | defaultDBType = datastore.TableStore 19 | shutdownTimeout = 5 * time.Second // 5s 20 | defaultConfigPath = "config.json" 21 | ) 22 | 23 | func handleSignal() { 24 | // Wait for interrupt signal to gracefully shutdown the server with 25 | quit := make(chan os.Signal, 1) 26 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 27 | <-quit 28 | logrus.Info("Shutting down server...") 29 | } 30 | 31 | func logInit(logLevel string) { 32 | switch logLevel { 33 | case "debug": 34 | logrus.SetLevel(logrus.DebugLevel) 35 | // include function and file 36 | logrus.SetReportCaller(true) 37 | case "dev": 38 | logrus.SetLevel(logrus.InfoLevel) 39 | default: 40 | logrus.SetLevel(logrus.WarnLevel) 41 | } 42 | } 43 | 44 | func main() { 45 | port := flag.String("port", defaultPort, "server listen port, default 8080") 46 | dbType := flag.String("dbType", string(defaultDBType), "db type default sqlite") 47 | configFile := flag.String("config", defaultConfigPath, "default config path") 48 | mode := flag.String("mode", "dev", "service work mode debug|dev|product") 49 | flag.Parse() 50 | 51 | logInit(*mode) 52 | logrus.Info(fmt.Sprintf("%s start", os.Getenv(config.SERVER_NAME))) 53 | 54 | // init config 55 | if err := config.InitConfig(*configFile); err != nil { 56 | logrus.Fatal(err.Error()) 57 | } 58 | 59 | // init server and start 60 | proxy, err := server.NewProxyServer(*port, datastore.DatastoreType(*dbType), *mode) 61 | if err != nil { 62 | logrus.Fatal("proxy server init fail") 63 | } 64 | go proxy.Start() 65 | 66 | // wait shutdown signal 67 | handleSignal() 68 | 69 | if err := proxy.Close(shutdownTimeout); err != nil { 70 | logrus.Fatal("Shutdown server fail") 71 | } 72 | 73 | logrus.Info("Server exited") 74 | } 75 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/devsapp/serverless-stable-diffusion-api 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4 7 | github.com/alibabacloud-go/fc-20230330 v1.0.0 8 | github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.9 9 | github.com/alibabacloud-go/tea-utils/v2 v2.0.4 10 | github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible 11 | github.com/aliyun/aliyun-tablestore-go-sdk v1.7.9 12 | github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3 13 | github.com/deepmap/oapi-codegen v1.13.4 14 | github.com/devsapp/goutils v0.0.0-20240105060413-8cc49aabfde9 15 | github.com/getkin/kin-openapi v0.118.0 16 | github.com/gin-contrib/cors v1.5.0 17 | github.com/gin-gonic/gin v1.9.1 18 | github.com/sirupsen/logrus v1.9.3 19 | github.com/stretchr/testify v1.8.4 20 | golang.org/x/crypto v0.14.0 21 | gopkg.in/yaml.v2 v2.4.0 22 | ) 23 | 24 | require ( 25 | github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect 26 | github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect 27 | github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect 28 | github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect 29 | github.com/alibabacloud-go/fc-20230330/v3 v3.0.2 // indirect 30 | github.com/alibabacloud-go/openapi-util v0.1.0 // indirect 31 | github.com/alibabacloud-go/tea v1.2.1 // indirect 32 | github.com/alibabacloud-go/tea-utils v1.3.1 // indirect 33 | github.com/alibabacloud-go/tea-xml v1.1.2 // indirect 34 | github.com/aliyun/credentials-go v1.2.6 // indirect 35 | github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect 36 | github.com/bytedance/sonic v1.10.1 // indirect 37 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect 38 | github.com/chenzhuoyu/iasm v0.9.0 // indirect 39 | github.com/clbanning/mxj/v2 v2.5.5 // indirect 40 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 41 | github.com/gin-contrib/sse v0.1.0 // indirect 42 | github.com/go-openapi/jsonpointer v0.20.0 // indirect 43 | github.com/go-openapi/swag v0.22.4 // indirect 44 | github.com/go-playground/locales v0.14.1 // indirect 45 | github.com/go-playground/universal-translator v0.18.1 // indirect 46 | github.com/go-playground/validator/v10 v10.15.5 // indirect 47 | github.com/goccy/go-json v0.10.2 // indirect 48 | github.com/golang/protobuf v1.5.0 // indirect 49 | github.com/google/flatbuffers v1.11.0 // indirect 50 | github.com/google/uuid v1.3.0 // indirect 51 | github.com/hashicorp/golang-lru v0.5.4 // indirect 52 | github.com/invopop/yaml v0.2.0 // indirect 53 | github.com/josharian/intern v1.0.0 // indirect 54 | github.com/json-iterator/go v1.1.12 // indirect 55 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect 56 | github.com/labstack/echo/v4 v4.11.1 // indirect 57 | github.com/labstack/gommon v0.4.0 // indirect 58 | github.com/leodido/go-urn v1.2.4 // indirect 59 | github.com/mailru/easyjson v0.7.7 // indirect 60 | github.com/mattn/go-colorable v0.1.13 // indirect 61 | github.com/mattn/go-isatty v0.0.19 // indirect 62 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 63 | github.com/modern-go/reflect2 v1.0.2 // indirect 64 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 65 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect 66 | github.com/perimeterx/marshmallow v1.1.5 // indirect 67 | github.com/pkg/errors v0.9.1 // indirect 68 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 // indirect 69 | github.com/tjfoc/gmsm v1.3.2 // indirect 70 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 71 | github.com/ugorji/go/codec v1.2.11 // indirect 72 | github.com/valyala/bytebufferpool v1.0.0 // indirect 73 | github.com/valyala/fasttemplate v1.2.2 // indirect 74 | golang.org/x/arch v0.5.0 // indirect 75 | golang.org/x/net v0.16.0 // indirect 76 | golang.org/x/sys v0.13.0 // indirect 77 | golang.org/x/text v0.13.0 // indirect 78 | golang.org/x/time v0.3.0 // indirect 79 | google.golang.org/protobuf v1.31.0 // indirect 80 | gopkg.in/ini.v1 v1.56.0 // indirect 81 | ) 82 | 83 | require ( 84 | github.com/davecgh/go-spew v1.1.1 // indirect 85 | github.com/mattn/go-sqlite3 v1.14.17 86 | github.com/pmezard/go-difflib v1.0.0 // indirect 87 | gopkg.in/yaml.v3 v3.0.1 // indirect 88 | ) 89 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= 3 | github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 h1:RDatRb9RG39HjkevgzTeiVoDDaamoB+12GHNairp3Ag= 4 | github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7/go.mod h1:H0RPHXHP/ICfEQrKzQcCqXI15jcV4zaDPCOAmh3U9O8= 5 | github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= 6 | github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= 7 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= 8 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4 h1:7Q2FEyqxeZeIkwYMwRC3uphxV4i7O2eV4ETe21d6lS4= 9 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= 10 | github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50= 11 | github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= 12 | github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= 13 | github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= 14 | github.com/alibabacloud-go/fc-20230330 v1.0.0 h1:dMAGmWITcnxC5EaiXvVWebDc+OV1UMj6M3H1miMEXiU= 15 | github.com/alibabacloud-go/fc-20230330 v1.0.0/go.mod h1:0B0Bii7z7TRyC+Lhn2J6dPyPqpZM13sxYggq1MX6OFw= 16 | github.com/alibabacloud-go/fc-20230330/v3 v3.0.2 h1:muBDoYIF/hK8zn37zxmLNaNsje5twB5CPEgevLU2jT4= 17 | github.com/alibabacloud-go/fc-20230330/v3 v3.0.2/go.mod h1:vG6NWEjZVsMLmShBYvYhmdkpl2hRdYfdyQ4tKetaAJs= 18 | github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.9 h1:VCfac54NMgBDqdaVU/sI2e8OP/+3vGhVad6lICZLExY= 19 | github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.9/go.mod h1:3wFBu69/VOm9NW7tKYDo+YGED60PbK7DdaymfSAL3w8= 20 | github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= 21 | github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= 22 | github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= 23 | github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= 24 | github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= 25 | github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= 26 | github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= 27 | github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= 28 | github.com/alibabacloud-go/tea v1.2.1 h1:rFF1LnrAdhaiPmKwH5xwYOKlMh66CqRwPUTzIK74ask= 29 | github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA= 30 | github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I= 31 | github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= 32 | github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= 33 | github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= 34 | github.com/alibabacloud-go/tea-utils/v2 v2.0.3/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ= 35 | github.com/alibabacloud-go/tea-utils/v2 v2.0.4 h1:SoFgjJuO7pze88j9RBJNbKb7AgTS52O+J5ITxc00lCs= 36 | github.com/alibabacloud-go/tea-utils/v2 v2.0.4/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ= 37 | github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M= 38 | github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= 39 | github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible h1:KXeJoM1wo9I/6xPTyt6qCxoSZnmASiAjlrr0dyTUKt8= 40 | github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= 41 | github.com/aliyun/aliyun-tablestore-go-sdk v1.7.9 h1:xB42R0axgNoXUhEPUKSXNNIb0S6l8iyhjug2ardZjak= 42 | github.com/aliyun/aliyun-tablestore-go-sdk v1.7.9/go.mod h1:aVqKjL2cmkgs0JOUf++ayBLI5ChiUdrbRp1vcA77c64= 43 | github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= 44 | github.com/aliyun/credentials-go v1.2.6 h1:dSMxpj4uXZj0MYOsEyljlssHzfdHw/M84iQ5QKF0Uxg= 45 | github.com/aliyun/credentials-go v1.2.6/go.mod h1:/KowD1cfGSLrLsH28Jr8W+xwoId0ywIy5lNzDz6O1vw= 46 | github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= 47 | github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3 h1:d0/nCeQMEqCokk7qCx4kssYtdck2R5Pe6xQzptuKfO8= 48 | github.com/awesome-fc/golang-runtime v0.0.0-20230119040721-3f65ab4b97d3/go.mod h1:W4bhz/v/p6E48AW5CH9j3kI1Xmp/fH/g6NNJXRvCtL4= 49 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 50 | github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= 51 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 52 | github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= 53 | github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= 54 | github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= 55 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 56 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 57 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 58 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= 59 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= 60 | github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= 61 | github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= 62 | github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= 63 | github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= 64 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 65 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 66 | github.com/deepmap/oapi-codegen v1.13.4/go.mod h1:/h5nFQbTAMz4S/WtBz8sBfamlGByYKDr21O2uoNgCYI= 67 | github.com/devsapp/goutils v0.0.0-20240105060413-8cc49aabfde9 h1:URXXDjV2xfrN4+GLFCE/HhvN8lk9ejCNBVypEnvOFv0= 68 | github.com/devsapp/goutils v0.0.0-20240105060413-8cc49aabfde9/go.mod h1:y4rWgFFINcr1oQ/wrREy0uQ8VyVg1Gl5K33fFzRvqS8= 69 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 70 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 71 | github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= 72 | github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= 73 | github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= 74 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 75 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 76 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 77 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 78 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 79 | github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= 80 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 81 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 82 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 83 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 84 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 85 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 86 | github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= 87 | github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 88 | github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 89 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 90 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 91 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 92 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 93 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= 94 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 95 | github.com/google/flatbuffers v1.11.0 h1:O7CEyB8Cb3/DmtxODGtLHcEvpr81Jm5qLg/hsHnxA2A= 96 | github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 97 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 98 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 99 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 100 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 101 | github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 102 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 103 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 104 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 105 | github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= 106 | github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= 107 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 108 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 109 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 110 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 111 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 112 | github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= 113 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 114 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= 115 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 116 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 117 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 118 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 119 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 120 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 121 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 122 | github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= 123 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 124 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 125 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 126 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 127 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 128 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 129 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 130 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 131 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 132 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 133 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 134 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 135 | github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= 136 | github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 137 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 138 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 139 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 140 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 141 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 142 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 143 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 144 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 145 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 146 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= 147 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 148 | github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= 149 | github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= 150 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 151 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 152 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 153 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 154 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 155 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 156 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 157 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 158 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 159 | github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 160 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 161 | github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= 162 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 163 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 164 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 165 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 166 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 167 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 168 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 169 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 170 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 171 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 172 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 173 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 174 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 175 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 176 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 177 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 h1:J6v8awz+me+xeb/cUTotKgceAYouhIB3pjzgRd6IlGk= 178 | github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816/go.mod h1:tzym/CEb5jnFI+Q0k4Qq3+LvRF4gO3E2pxS8fHP8jcA= 179 | github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= 180 | github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= 181 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 182 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 183 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 184 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 185 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 186 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 187 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 188 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 189 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 190 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 191 | github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 192 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 193 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 194 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 195 | go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 196 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 197 | go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 198 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 199 | golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= 200 | golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 201 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 202 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 203 | golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 204 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 205 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 206 | golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= 207 | golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= 208 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 209 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 210 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 211 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 212 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 213 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 214 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 215 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 216 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 217 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 218 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 219 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 220 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 221 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 222 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 223 | golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= 224 | golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= 225 | golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 226 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 227 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 228 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 229 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 230 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 231 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 232 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 233 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 234 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 235 | golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 236 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 237 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 238 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 239 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 240 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 241 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 242 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 243 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 244 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 245 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 246 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 247 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 248 | golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 249 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 250 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 251 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 252 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 253 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 254 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 255 | golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= 256 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 257 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 258 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 259 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 260 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 261 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 262 | golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 263 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 264 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 265 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 266 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 267 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 268 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 269 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 270 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 271 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 272 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 273 | golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 274 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 275 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 276 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 277 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 278 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 279 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 280 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 281 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 282 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 283 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 284 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 285 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 286 | gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y= 287 | gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 288 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 289 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 290 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 291 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 292 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 293 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 294 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 295 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 296 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 297 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 298 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 299 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 300 | -------------------------------------------------------------------------------- /pkg/client/pool.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "sync" 4 | 5 | var ManagerClientGlobal *ManagerClient = NewManagerClient() 6 | 7 | type ManagerClient struct { 8 | clients *sync.Map 9 | } 10 | 11 | func NewManagerClient() *ManagerClient { 12 | return &ManagerClient{ 13 | clients: new(sync.Map), 14 | } 15 | } 16 | 17 | func (c *ManagerClient) GetClient(endPoint string) *Client { 18 | val, existed := c.clients.Load(endPoint) 19 | if existed { 20 | return val.(*Client) 21 | } 22 | client, _ := NewClient(endPoint) 23 | val, _ = c.clients.LoadOrStore(endPoint, client) 24 | return val.(*Client) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/concurrency/concurrency.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | var ConCurrencyGlobal = NewConcurrency() 10 | 11 | type Concurrency struct { 12 | metrics *sync.Map 13 | curColdNum *int32 14 | } 15 | 16 | func NewConcurrency() *Concurrency { 17 | var curColdNum int32 = 0 18 | return &Concurrency{ 19 | metrics: new(sync.Map), 20 | curColdNum: &curColdNum, 21 | } 22 | } 23 | 24 | // WaitToValid Avoid excessive cold start concurrency 25 | func (c *Concurrency) WaitToValid(metric string) bool { 26 | metricItem, _ := c.metrics.LoadOrStore(metric, NewMetric()) 27 | return metricItem.(*Metric).waitToValid(c.curColdNum) 28 | } 29 | 30 | func (c *Concurrency) DoneTask(metric, taskId string) { 31 | if metricItem, ok := c.metrics.Load(metric); ok { 32 | metricItem.(*Metric).doneTask() 33 | //logrus.Info(fmt.Sprintf("finish: %V, coldNum:%d", metricItem.(*Metric).window), *c.curColdNum) 34 | return 35 | } 36 | logrus.WithFields(logrus.Fields{"taskId": taskId}).Errorf("done task err: metric %s not exist", metric) 37 | } 38 | 39 | func (c *Concurrency) DecColdNum(metric, taskId string) { 40 | if metricItem, ok := c.metrics.Load(metric); ok { 41 | metricItem.(*Metric).SetColdFlag(false) 42 | } else { 43 | logrus.WithFields(logrus.Fields{"taskId": taskId}).Errorf("decColdNum task err: metric %s not exist", metric) 44 | } 45 | atomic.AddInt32(c.curColdNum, -1) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/concurrency/metric.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | import ( 4 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 5 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/utils" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | ) 10 | 11 | const ( 12 | windowExpired = 3 * 60 // 5min 13 | windowLength = 10240 14 | period = 2 // 1s 15 | Retry = 150 16 | ) 17 | 18 | type Point struct { 19 | time int64 20 | val int32 21 | } 22 | 23 | type Metric struct { 24 | lock sync.Mutex 25 | window []*Point 26 | coldFlag atomic.Bool 27 | concurrency *int32 28 | } 29 | 30 | func NewMetric() *Metric { 31 | var initConcurrency int32 = 0 32 | coldFlag := atomic.Bool{} 33 | coldFlag.Store(false) 34 | return &Metric{ 35 | window: make([]*Point, 0, windowLength), 36 | concurrency: &initConcurrency, 37 | coldFlag: coldFlag, 38 | } 39 | } 40 | 41 | // DoneTask 42 | // update window and concurrency 43 | func (m *Metric) doneTask() { 44 | m.lock.Lock() 45 | defer m.lock.Unlock() 46 | len := len(m.window) 47 | curPoint := &Point{utils.TimestampS(), *m.concurrency} 48 | if len == 0 || m.window[len-1].val > *m.concurrency { 49 | m.window = append(m.window, curPoint) 50 | } else { 51 | idx := m.findLeftNearestConcurrency(*m.concurrency) 52 | m.window[idx] = curPoint 53 | m.window = m.window[:idx+1] 54 | } 55 | atomic.AddInt32(m.concurrency, -1) 56 | } 57 | 58 | // WaitToValid 59 | // judge request valid, if invalid wait for valid 60 | // update curColdNum and conCurrency 61 | func (m *Metric) waitToValid(curColdNum *int32) bool { 62 | //logrus.Infof("start: %V", m.window) 63 | retry := 0 64 | for retry < Retry { 65 | retry-- 66 | m.lock.Lock() 67 | isCold := false 68 | threshold := utils.TimestampS() - windowExpired 69 | if len(m.window) == 0 || m.window[len(m.window)-1].time < threshold { 70 | m.window = make([]*Point, 0, windowLength) 71 | isCold = true 72 | } else { 73 | idx := m.findLeftNearestTime(threshold) 74 | preMaxConcurrency := m.window[idx].val 75 | m.window = m.window[idx:] 76 | if *m.concurrency >= preMaxConcurrency { 77 | isCold = true 78 | } 79 | } 80 | m.lock.Unlock() 81 | 82 | if !isCold { 83 | atomic.AddInt32(m.concurrency, 1) 84 | return false 85 | } else { 86 | if atomic.AddInt32(curColdNum, 1) <= config.ConfigGlobal.ColdStartConcurrency && 87 | (!config.ConfigGlobal.ModelColdStartSerial || 88 | (config.ConfigGlobal.ModelColdStartSerial && !m.coldFlag.Swap(true))) { 89 | atomic.AddInt32(m.concurrency, 1) 90 | return true 91 | } else { 92 | atomic.AddInt32(curColdNum, -1) 93 | } 94 | } 95 | // sleep period 96 | time.Sleep(time.Duration(period) * time.Second) 97 | } 98 | return false 99 | } 100 | 101 | func (m *Metric) SetColdFlag(flag bool) { 102 | if config.ConfigGlobal.ModelColdStartSerial { 103 | m.coldFlag.Store(flag) 104 | } 105 | } 106 | 107 | func (m *Metric) findLeftNearestTime(val int64) int { 108 | low := 0 109 | high := len(m.window) - 1 110 | for low <= high { 111 | mid := (low + high) / 2 112 | if m.window[mid].time < val { 113 | low = mid + 1 114 | } else { 115 | high = mid - 1 116 | } 117 | } 118 | return low 119 | } 120 | 121 | func (m *Metric) findLeftNearestConcurrency(val int32) int { 122 | low := 0 123 | high := len(m.window) - 1 124 | for low <= high { 125 | mid := (low + high) / 2 126 | if m.window[mid].val <= val { 127 | high = mid - 1 128 | } else { 129 | low = mid + 1 130 | } 131 | } 132 | return low 133 | } 134 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/sirupsen/logrus" 12 | "gopkg.in/yaml.v2" 13 | ) 14 | 15 | var ConfigGlobal *Config 16 | 17 | type ConfigYaml struct { 18 | // ots 19 | OtsEndpoint string `yaml:"otsEndpoint"` 20 | OtsTimeToAlive int `yaml:"otsTimeToAlive"` 21 | OtsInstanceName string `yaml:"otsInstanceName"` 22 | OtsMaxVersion int `yaml:"otsMaxVersion"` 23 | // oss 24 | OssEndpoint string `yaml:"ossEndpoint"` 25 | Bucket string `yaml:"bucket"` 26 | OssPath string `yaml:"ossPath""` 27 | OssMode string `yaml:"ossMode"` 28 | 29 | // db 30 | DbSqlite string `yaml:"dbSqlite"` 31 | 32 | // listen 33 | ListenInterval int32 `yaml:"listenInterval"` 34 | 35 | // function 36 | Image string `yaml:"image"` 37 | CAPort int32 `yaml:"caPort"` 38 | Timeout int32 `yaml:"timeout"` 39 | CPU float32 `yaml:"CPU"` 40 | GpuMemorySize int32 `yaml:"gpuMemorySize"` 41 | ExtraArgs string `yaml:"extraArgs"` 42 | DiskSize int32 `yaml:"diskSize"` 43 | MemorySize int32 `yaml:"memorySize"` 44 | InstanceConcurrency int32 `yaml:"instanceConcurrency"` 45 | InstanceType string `yaml:"instanceType"` 46 | 47 | // user 48 | SessionExpire int64 `yaml:"sessionExpire"` 49 | LoginSwitch string `yaml:"loginSwitch"` 50 | ProgressImageOutputSwitch string `yaml:"progressImageOutputSwitch"` 51 | 52 | // sd 53 | SdUrlPrefix string `yaml:"sdUrlPrefix"` 54 | SdPath string `yaml:"sdPath"` 55 | SdShell string `yaml:"sdShell"` 56 | 57 | // model 58 | UseLocalModels string `yaml:"useLocalModel"` 59 | 60 | // flex mode 61 | FlexMode string `yaml:"flexMode"` 62 | 63 | // agent expose to user or not 64 | LogRemoteService string `yaml:"logRemoteService"` 65 | EnableCollect string `yaml:"enableCollect"` 66 | DisableHealthCheck string `yaml:"disableHealthCheck"` 67 | 68 | // proxy or control or agent 69 | ServerName string `yaml:"serverName"` 70 | Downstream string `yaml:"downstream"` 71 | UseIntranet bool `yaml:"useIntranet"` 72 | } 73 | 74 | type ConfigEnv struct { 75 | // account 76 | AccountId string 77 | AccessKeyId string 78 | AccessKeySecret string 79 | AccessKeyToken string 80 | Region string 81 | ServiceName string 82 | FunctionName string 83 | ColdStartConcurrency int32 84 | ModelColdStartSerial bool 85 | } 86 | 87 | type Config struct { 88 | ConfigYaml 89 | ConfigEnv 90 | } 91 | 92 | func (c *Config) SendLogToRemote() bool { 93 | return c.LogRemoteService != "" && (c.EnableCollect == "true" || c.EnableCollect == "1") 94 | } 95 | 96 | // IsServerTypeMatch Identify whether serverName == name 97 | func (c *Config) IsServerTypeMatch(name string) bool { 98 | if c.GetFlexMode() == SingleFunc { 99 | return true 100 | } 101 | return c.ServerName == name 102 | } 103 | 104 | func (c *Config) ExposeToUser() bool { 105 | return os.Getenv(MODEL_SD) == "" 106 | } 107 | 108 | // GetFlexMode flex mode 109 | func (c *Config) GetFlexMode() FlexMode { 110 | if c.FlexMode == "singleFunc" { 111 | return SingleFunc 112 | } 113 | return MultiFunc 114 | } 115 | 116 | func (c *Config) UseLocalModel() bool { 117 | return c.UseLocalModels == "yes" 118 | } 119 | func (c *Config) EnableLogin() bool { 120 | return c.LoginSwitch == "on" 121 | } 122 | 123 | func (c *Config) DisableProgress() bool { 124 | return os.Getenv("DISABLE_PROGRESS") != "" 125 | } 126 | 127 | func (c *Config) GetSDPort() string { 128 | if c.SdUrlPrefix == "" { 129 | return DefaultSdPort 130 | } 131 | items := strings.Split(c.SdUrlPrefix, ":") 132 | if len(items) == 3 { 133 | return items[2] 134 | } 135 | return "" 136 | } 137 | 138 | func (c *Config) EnableProgressImg() bool { 139 | return c.ProgressImageOutputSwitch == "on" 140 | } 141 | 142 | func (c *Config) GetDisableHealthCheck() bool { 143 | return c.DisableHealthCheck == "true" || c.DisableHealthCheck == "1" 144 | } 145 | 146 | func (c *Config) updateFromEnv() { 147 | // ots 148 | otsEndpoint := os.Getenv(OTS_ENDPOINT) 149 | otsInstanceName := os.Getenv(OTS_INSTANCE) 150 | if otsEndpoint != "" && otsInstanceName != "" { 151 | c.OtsEndpoint = otsEndpoint 152 | c.OtsInstanceName = otsInstanceName 153 | } 154 | // oss 155 | ossEndpoint := os.Getenv(OSS_ENDPOINT) 156 | bucket := os.Getenv(OSS_BUCKET) 157 | if path := os.Getenv(OSS_PATH); path != "" { 158 | c.OssPath = path 159 | } 160 | if mode := os.Getenv(OSS_MODE); mode != "" { 161 | c.OssMode = mode 162 | } 163 | if ossEndpoint != "" && bucket != "" { 164 | c.OssEndpoint = ossEndpoint 165 | c.Bucket = bucket 166 | } 167 | 168 | // login 169 | loginSwitch := os.Getenv(LOGINSWITCH) 170 | if loginSwitch != "" { 171 | c.LoginSwitch = loginSwitch 172 | } 173 | 174 | // use local model or not 175 | useLocalModel := os.Getenv(USER_LOCAL_MODEL) 176 | if useLocalModel != "" { 177 | c.UseLocalModels = useLocalModel 178 | } 179 | 180 | // sd image cover 181 | sdImage := os.Getenv(SD_IMAGE) 182 | if sdImage != "" { 183 | c.Image = sdImage 184 | } 185 | gpuMemorySize := os.Getenv(GPU_MEMORY_SIZE) 186 | if gpuMemorySize != "" { 187 | if size, err := strconv.Atoi(gpuMemorySize); err == nil { 188 | c.GpuMemorySize = int32(size) 189 | } 190 | } 191 | 192 | // flex mode 193 | flexMode := os.Getenv(FLEX_MODE) 194 | if flexMode != "" { 195 | c.FlexMode = flexMode 196 | } 197 | 198 | // proxy 199 | serverName := os.Getenv(SERVER_NAME) 200 | downstream := os.Getenv(DOWNSTREAM) 201 | if serverName != "" { 202 | c.ServerName = serverName 203 | } 204 | if downstream != "" { 205 | c.Downstream = downstream 206 | } 207 | 208 | coldStartConcurrency := os.Getenv(COLD_START_CONCURRENCY) 209 | c.ColdStartConcurrency = ColdStartConcurrency 210 | if coldStartConcurrency != "" { 211 | if concurrency, err := strconv.Atoi(coldStartConcurrency); err == nil { 212 | c.ColdStartConcurrency = int32(concurrency) 213 | } 214 | } 215 | modelColdStartSerial := os.Getenv(MODEL_COLD_START_SERIAL) 216 | c.ModelColdStartSerial = ModelColdStartSerial 217 | if modelColdStartSerial != "" { 218 | if serial, err := strconv.ParseBool(modelColdStartSerial); err == nil { 219 | c.ModelColdStartSerial = serial 220 | } 221 | } 222 | 223 | // sd agent 224 | logRemoteService := os.Getenv(LOG_REMOTE_SERVICE) 225 | if logRemoteService != "" { 226 | c.LogRemoteService = logRemoteService 227 | } 228 | 229 | enableCollect := os.Getenv(ENABLE_COLLECT) 230 | if enableCollect != "" { 231 | c.EnableCollect = enableCollect 232 | } 233 | 234 | disableHealthCheck := os.Getenv(DISABLE_HF_CHECK) 235 | if disableHealthCheck != "" { 236 | c.DisableHealthCheck = disableHealthCheck 237 | } 238 | 239 | useIntranet := true 240 | switch strings.ToLower(os.Getenv("USE_INTRANET")) { 241 | case "", "false", "0": 242 | useIntranet = false 243 | } 244 | c.UseIntranet = useIntranet 245 | } 246 | 247 | // check config valid 248 | func (c *Config) check() error { 249 | // ExtraArgs 250 | if !strings.Contains(c.ExtraArgs, "--api") { 251 | c.ExtraArgs = fmt.Sprintf("%s %s", c.ExtraArgs, "--api") 252 | } 253 | if !strings.Contains(c.ExtraArgs, "--nowebui") { 254 | c.ExtraArgs = fmt.Sprintf("%s %s", c.ExtraArgs, "--nowebui") 255 | } 256 | if strings.Contains(c.ExtraArgs, "--api-auth") { 257 | c.ExtraArgs = strings.ReplaceAll(c.ExtraArgs, "--api-auth", "") 258 | } 259 | if (c.ServerName == CONTROL || c.ServerName == AGENT) && c.OssMode == REMOTE { 260 | if c.Bucket == "" || c.OssEndpoint == "" { 261 | logrus.Error("oss remote mode need set oss bucket and endpoint, please check it") 262 | return errors.New("oss remote mode need set oss bucket and endpoint, please check it") 263 | } 264 | } 265 | return nil 266 | } 267 | 268 | // set default 269 | func (c *Config) setDefaults() { 270 | if c.OtsTimeToAlive == 0 { 271 | c.OtsTimeToAlive = -1 272 | } 273 | if c.ListenInterval == 0 { 274 | c.ListenInterval = 1 275 | } 276 | if c.SessionExpire == 0 { 277 | c.SessionExpire = DefaultSessionExpire 278 | } 279 | if c.ExtraArgs == "" { 280 | c.ExtraArgs = DefaultExtraArgs 281 | } 282 | if c.LoginSwitch == "" { 283 | c.LoginSwitch = DefaultLoginSwitch 284 | } 285 | if c.UseLocalModels == "" { 286 | c.UseLocalModels = DefaultUseLocalModel 287 | } 288 | if c.FlexMode == "" { 289 | c.FlexMode = DefaultFlexMode 290 | } 291 | if c.OssMode == "" { 292 | c.OssMode = DefaultOssMode 293 | } 294 | if c.OssPath == "" { 295 | c.OssPath = DefaultOssPath 296 | } 297 | if c.LogRemoteService == "" { 298 | c.LogRemoteService = DefaultLogService 299 | } 300 | if c.SdPath == "" { 301 | if os.Getenv(SERVER_NAME) == PROXY || os.Getenv(SERVER_NAME) == CONTROL { 302 | c.SdPath = DefaultSdPathProxy 303 | } else { 304 | c.SdPath = DefaultSdPath 305 | } 306 | } 307 | if c.CAPort == 0 { 308 | c.CAPort = DefaultCaPort 309 | } 310 | if c.CPU == 0 { 311 | c.CPU = DefaultCpu 312 | } 313 | if c.DiskSize == 0 { 314 | c.DiskSize = DefaultDisk 315 | } 316 | if c.InstanceConcurrency == 0 { 317 | c.InstanceConcurrency = DefaultInstanceConcurrency 318 | } 319 | if c.InstanceType == "" { 320 | c.InstanceType = DefaultInstanceType 321 | } 322 | if c.MemorySize == 0 { 323 | c.MemorySize = DefaultMemorySize 324 | } 325 | if c.GpuMemorySize == 0 { 326 | c.GpuMemorySize = DefaultGpuMemorySize 327 | } 328 | if c.Timeout == 0 { 329 | c.Timeout = DefaultTimeout 330 | } 331 | if c.SdUrlPrefix == "" { 332 | c.SdUrlPrefix = fmt.Sprintf("http://localhost:%s", DefaultSdPort) 333 | } 334 | } 335 | 336 | func InitConfig(fn string) error { 337 | configEnv := new(ConfigEnv) 338 | configEnv.AccountId = os.Getenv(ACCOUNT_ID) 339 | configEnv.AccessKeyId = os.Getenv(ACCESS_KEY_ID) 340 | configEnv.AccessKeySecret = os.Getenv(ACCESS_KEY_SECRET) 341 | configEnv.AccessKeyToken = os.Getenv(ACCESS_KET_TOKEN) 342 | configEnv.Region = os.Getenv(REGION) 343 | configEnv.ServiceName = os.Getenv(SERVICE_NAME) 344 | configEnv.FunctionName = os.Getenv(FC_FUNCTION_NAME) 345 | //// check valid 346 | //for _, val := range []string{configEnv.AccountId, configEnv.AccessKeyId, 347 | // configEnv.AccessKeySecret, configEnv.Region} { 348 | // if val == "" { 349 | // return errors.New("env not set ACCOUNT_ID || ACCESS_KEY_Id || " + 350 | // "ACCESS_KEY_SECRET || REGION, please check") 351 | // } 352 | //} 353 | configYaml := new(ConfigYaml) 354 | yamlFile, err := ioutil.ReadFile(fn) 355 | if err == nil { 356 | err = yaml.Unmarshal(yamlFile, &configYaml) 357 | if err != nil { 358 | return err 359 | } 360 | } 361 | ConfigGlobal = &Config{ 362 | *configYaml, 363 | *configEnv, 364 | } 365 | // set default 366 | ConfigGlobal.setDefaults() 367 | 368 | // env cover yaml 369 | ConfigGlobal.updateFromEnv() 370 | if ConfigGlobal.GetFlexMode() == MultiFunc && ConfigGlobal.ServerName == PROXY && ConfigGlobal.Downstream == "" { 371 | return errors.New("proxy need set downstream") 372 | } 373 | // check 374 | if err := ConfigGlobal.check(); err != nil { 375 | return err 376 | } 377 | return nil 378 | } 379 | -------------------------------------------------------------------------------- /pkg/config/const_var.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "time" 4 | 5 | const ( 6 | // model status 7 | MODEL_REGISTERING = "registering" 8 | MODEL_LOADING = "loading" 9 | MODEL_LOADED = "loaded" 10 | MODEL_UNLOADED = "unloaded" 11 | MODEL_DELETE = "deleted" 12 | 13 | // task status 14 | TASK_INPROGRESS = "running" 15 | TASK_FAILED = "failed" 16 | TASK_QUEUE = "waiting" 17 | TASK_FINISH = "succeeded" 18 | 19 | HTTPTIMEOUT = 10 * 60 * time.Second 20 | 21 | // cancel val 22 | CANCEL_INIT = 0 23 | CANCEL_VALID = 1 24 | 25 | PROGRESS_INTERVAL = 500 26 | ) 27 | 28 | // error message 29 | const ( 30 | OTSPUTERROR = "put ots error" 31 | OTSGETERROR = "get ots error" 32 | INTERNALERROR = "an internal error" 33 | BADREQUEST = "bad request body" 34 | NOTFOUND = "not found" 35 | NOFOUNDENDPOINT = "not found sd endpoint, please retry" 36 | MODELUPDATEFCERROR = "model update fc error" 37 | ) 38 | 39 | // model type 40 | const ( 41 | SD_MODEL = "stableDiffusion" 42 | SD_VAE = "sdVae" 43 | LORA_MODEL = "lora" 44 | CONTORLNET_MODEL = "controlNet" 45 | ) 46 | 47 | // sd api path 48 | const ( 49 | //REFRESH_LORAS = "/sdapi/v1/refresh-loras" 50 | //GET_LORAS = "/sdapi/v1/loras" 51 | GET_SD_MODEL = "/sdapi/v1/sd-models" 52 | REFRESH_SD_MODEL = "/sdapi/v1/refresh-checkpoints" 53 | GET_SD_VAE = "/sdapi/v1/sd-vae" 54 | REFRESH_VAE = "/sdapi/v1/refresh-vae" 55 | REFRESH_CONTROLNET = "/controlnet/model_list" 56 | CANCEL = "/sdapi/v1/interrupt" 57 | TXT2IMG = "/sdapi/v1/txt2img" 58 | IMG2IMG = "/sdapi/v1/img2img" 59 | PROGRESS = "/sdapi/v1/progress" 60 | EXTRAIMAGES = "/sdapi/v1/extra-single-image" 61 | ) 62 | 63 | // ots 64 | const ( 65 | COLPK = "PK" 66 | ) 67 | 68 | // env 69 | const ( 70 | ACCOUNT_ID = "FC_ACCOUNT_ID" 71 | ACCESS_KEY_ID = "ALIBABA_CLOUD_ACCESS_KEY_ID" 72 | ACCESS_KEY_SECRET = "ALIBABA_CLOUD_ACCESS_KEY_SECRET" 73 | ACCESS_KET_TOKEN = "ALIBABA_CLOUD_SECURITY_TOKEN" 74 | REGION = "FC_REGION" 75 | SERVICE_NAME = "FC_SERVICE_NAME" 76 | OTS_ENDPOINT = "OTS_ENDPOINT" 77 | OTS_INSTANCE = "OTS_INSTANCE" 78 | OSS_ENDPOINT = "OSS_ENDPOINT" 79 | OSS_BUCKET = "OSS_BUCKET" 80 | OSS_PATH = "OSS_PATH" 81 | OSS_MODE = "OSS_MODE" 82 | LOGINSWITCH = "LOGIN_SWITCH" 83 | USER_LOCAL_MODEL = "USE_LOCAL_MODEL" 84 | SD_IMAGE = "SD_IMAGE" 85 | FLEX_MODE = "FLEX_MODE" 86 | EXPOSE_TO_USER = "EXPOSE_TO_USER" 87 | SERVER_NAME = "SERVER_NAME" 88 | DOWNSTREAM = "DOWNSTREAM" 89 | GPU_MEMORY_SIZE = "GPU_MEMORY_SIZE" 90 | COLD_START_CONCURRENCY = "COLD_START_CONCURRENCY" 91 | MODEL_COLD_START_SERIAL = "MODEL_COLD_START_SERIAL" 92 | LOG_REMOTE_SERVICE = "LOG_REMOTE_SERVICE" 93 | FC_ACCOUNT_ID = "FC_ACCOUNT_ID" 94 | FC_FUNCTION_NAME = "FC_FUNCTION_NAME" 95 | ENABLE_COLLECT = "ENABLE_COLLECT" 96 | DISABLE_HF_CHECK = "DISABLE_HF_CHECK" 97 | CHECK_MODEL_LOAD = "CHECK_MODEL_LOAD" 98 | DISABLE_PROGRESS = "DISABLE_PROGRESS" 99 | ) 100 | 101 | // default value 102 | const ( 103 | DefaultSdPort = "7861" 104 | DefaultSdPath = "/stable-diffusion-webui" 105 | DefaultSdPathProxy = "/mnt/auto/sd" 106 | DefaultExtraArgs = "--api" 107 | DefaultSessionExpire = 3600 108 | DefaultLoginSwitch = "off" // value: off|on 109 | DefaultUseLocalModel = "yes" // value: yes|no 110 | DefaultFlexMode = "multiFunc" // value: singleFunc|multiFunc 111 | DefaultOssPath = "/mnt/oss" 112 | DefaultLogService = "http://server-ai-backend-agwwspzdwb.cn-hangzhou.devsapp.net" 113 | DefaultCaPort = 7860 114 | DefaultCpu = 8 115 | DefaultDisk = 512 116 | DefaultInstanceConcurrency = 1 117 | DefaultInstanceType = "fc.gpu.tesla.1" 118 | DefaultMemorySize = 32768 119 | DefaultGpuMemorySize = 16384 120 | DefaultTimeout = 600 121 | DefaultOssMode = REMOTE 122 | ) 123 | 124 | // function http trigger 125 | const ( 126 | TRIGGER_TYPE = "http" 127 | TRIGGER_NAME = "defaultTrigger" 128 | HTTP_GET = "GET" 129 | HTTP_POST = "POST" 130 | HTTP_PUT = "PUT" 131 | AUTH_TYPE = "anonymous" 132 | MODEL_REFRESH_SIGNAL = "MODEL_REFRESH_SIGNAL" 133 | MODEL_SD = "SD_MODEL" 134 | MODEL_SD_VAE = "SD_VAE" 135 | SD_START_PARAMS = "EXTRA_ARGS" 136 | ) 137 | 138 | // oss mode 139 | const ( 140 | LOCAL = "local" 141 | REMOTE = "remote" 142 | ) 143 | 144 | type FlexMode int32 145 | 146 | const ( 147 | SingleFunc FlexMode = iota 148 | MultiFunc 149 | ) 150 | 151 | const ( 152 | PROXY = "proxy" 153 | AGENT = "agent" 154 | CONTROL = "control" 155 | ) 156 | 157 | const ( 158 | ColdStartConcurrency = 10 159 | ModelColdStartSerial = false 160 | ) 161 | 162 | const ( 163 | TrackerKeyStableDiffusionStartup = "stable_diffusion_startup" 164 | FcRequestID = "x-fc-request-id" 165 | ) 166 | -------------------------------------------------------------------------------- /pkg/datastore/datastore.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | type DatastoreType string 4 | 5 | const ( 6 | SQLite DatastoreType = "sqlite" 7 | MySQL DatastoreType = "mysql" 8 | TableStore DatastoreType = "tableStore" 9 | ) 10 | 11 | type Config struct { 12 | Type DatastoreType // the datastore type 13 | DBName string // the database name 14 | TableName string 15 | ColumnConfig map[string]string // map of column name to column type 16 | PrimaryKeyColumnName string 17 | TimeToAlive int 18 | MaxVersion int 19 | } 20 | 21 | type Datastore interface { 22 | // Put inserts or updates the column values in the datastore. 23 | // It takes a key and a map of column names to values, and returns an error if the operation failed. 24 | Put(key string, values map[string]interface{}) error 25 | 26 | // Update the partial column values. 27 | // It tasks a key and a map of column names to values, and returns an error if the operation failed. 28 | Update(key string, values map[string]interface{}) error 29 | 30 | // Get retrieves the column values from the datastore. 31 | // It takes a key and a slice of column names, and returns a map of column names to values, 32 | // along with an error if the operation failed. 33 | // If the key does not exist, the returned map and error are both nil. 34 | Get(key string, columns []string) (map[string]interface{}, error) 35 | 36 | //Put(key string, value string) error 37 | //Get(key string) (string, error) 38 | 39 | // Delete removes a value from the datastore. 40 | // It takes a key, and returns an error if the operation failed. 41 | // Note: delete a non-existent key will not return an error. 42 | Delete(key string) error 43 | 44 | // ListAll read all data from the datastore. 45 | // It takes a list of column name, and return a nested map, which means map[primaryKey]map[columanName]columanValue. 46 | // Note: since it reads all data and store them in memory, so do not call this function on a large datastore. 47 | ListAll(columns []string) (map[string]map[string]interface{}, error) 48 | 49 | // Close close the datastore. 50 | Close() error 51 | } 52 | -------------------------------------------------------------------------------- /pkg/datastore/datastore_factory.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | import ( 4 | "fmt" 5 | config2 "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 6 | ) 7 | 8 | type DatastoreFactory struct{} 9 | 10 | func (f *DatastoreFactory) NewTable(dbType DatastoreType, tableName string) Datastore { 11 | switch dbType { 12 | case SQLite: 13 | cfg := NewSQLiteConfig(tableName) 14 | return NewSQLiteDatastore(cfg) 15 | case TableStore: 16 | cfg := NewOtsConfig(tableName) 17 | otsStore, err := NewOtsDatastore(cfg) 18 | if err != nil { 19 | panic(fmt.Sprintf("init ots fail, err=%s", err.Error())) 20 | return nil 21 | } 22 | return otsStore 23 | default: 24 | panic(fmt.Sprintf("not support db type=%s", dbType)) 25 | } 26 | return nil 27 | } 28 | 29 | func NewSQLiteConfig(tableName string) *Config { 30 | config := &Config{ 31 | Type: SQLite, 32 | DBName: config2.ConfigGlobal.DbSqlite, 33 | TableName: tableName, 34 | } 35 | switch tableName { 36 | case KTaskTableName: 37 | config.ColumnConfig = map[string]string{ 38 | KTaskIdColumnName: "TEXT PRIMARY KEY NOT NULL", 39 | KTaskProgressColumnName: "TEXT", 40 | KTaskUser: "TEXT", 41 | KTaskImage: "TEXT", 42 | KTaskCode: "INT", 43 | KTaskCancel: "INT", 44 | KTaskParams: "TEXT", 45 | KTaskInfo: "TEXT", 46 | KTaskStatus: "TEXT", 47 | KTaskCreateTime: "TEXT", 48 | KTaskModifyTime: "TEXT", 49 | } 50 | config.PrimaryKeyColumnName = KTaskIdColumnName 51 | case KModelTableName: 52 | config.ColumnConfig = map[string]string{ 53 | KModelName: "TEXT PRIMARY KEY NOT NULL", 54 | KModelType: "TEXT", 55 | KModelOssPath: "TEXT", 56 | KModelEtag: "TEXT", 57 | KModelStatus: "TEXT", 58 | KModelCreateTime: "TEXT", 59 | KModelModifyTime: "TEXT", 60 | } 61 | config.PrimaryKeyColumnName = KModelName 62 | case KModelServiceTableName: 63 | config.ColumnConfig = map[string]string{ 64 | KModelServiceKey: "TEXT PRIMARY KEY NOT NULL", 65 | KModelServiceFunctionName: "TEXT", 66 | KModelServiceSdModel: "TEXT", 67 | KModelServiceEndPoint: "TEXT", 68 | KModelServerImage: "TEXT", 69 | KModelServiceCreateTime: "TEXT", 70 | KModelServiceLastModifyTime: "TEXT", 71 | KModelServiceMessage: "TEXT", 72 | } 73 | config.PrimaryKeyColumnName = KModelServiceKey 74 | case KUserTableName: 75 | config.ColumnConfig = map[string]string{ 76 | KUserName: "TEXT PRIMARY KEY NOT NULL", 77 | KUserSession: "TEXT", 78 | KUserSessionValidTime: "TEXT", 79 | KUserConfig: "TEXT", 80 | KUserConfigVer: "TEXT", 81 | KUserCreateTime: "TEXT", 82 | KUserModifyTime: "TEXT", 83 | KUserPassword: "TEXT", 84 | } 85 | config.PrimaryKeyColumnName = KUserName 86 | case KConfigTableName: 87 | config.ColumnConfig = map[string]string{ 88 | KConfigKey: "TEXT PRIMARY KEY NOT NULL", 89 | KConfigVal: "TEXT", 90 | KConfigVer: "TEXT", 91 | KConfigMd5: "TEXT", 92 | KConfigCreateTime: "TEXT", 93 | KConfigModifyTime: "TEXT", 94 | } 95 | config.PrimaryKeyColumnName = KConfigKey 96 | } 97 | return config 98 | } 99 | 100 | func NewOtsConfig(tableName string) *Config { 101 | config := &Config{ 102 | Type: TableStore, 103 | TableName: tableName, 104 | TimeToAlive: -1, 105 | MaxVersion: 1, 106 | } 107 | switch tableName { 108 | case KTaskTableName: 109 | config.ColumnConfig = map[string]string{ 110 | KTaskIdColumnName: "TEXT", 111 | KTaskProgressColumnName: "TEXT", 112 | KTaskUser: "TEXT", 113 | KTaskImage: "TEXT", 114 | KTaskCode: "INT", 115 | KTaskCancel: "INT", 116 | KTaskParams: "TEXT", 117 | KTaskInfo: "TEXT", 118 | KTaskStatus: "TEXT", 119 | KTaskCreateTime: "TEXT", 120 | KTaskModifyTime: "TEXT", 121 | } 122 | config.PrimaryKeyColumnName = KTaskIdColumnName 123 | case KModelTableName: 124 | config.ColumnConfig = map[string]string{ 125 | KModelName: "TEXT", 126 | KModelType: "TEXT", 127 | KModelOssPath: "TEXT", 128 | KModelEtag: "TEXT", 129 | KModelStatus: "TEXT", 130 | KModelCreateTime: "TEXT", 131 | KModelModifyTime: "TEXT", 132 | } 133 | config.PrimaryKeyColumnName = KModelName 134 | case KModelServiceTableName: 135 | config.ColumnConfig = map[string]string{ 136 | KModelServiceKey: "TEXT", 137 | KModelServiceFunctionName: "TEXT", 138 | KModelServiceSdModel: "TEXT", 139 | KModelServiceEndPoint: "TEXT", 140 | KModelServerImage: "TEXT", 141 | KModelServiceCreateTime: "TEXT", 142 | KModelServiceLastModifyTime: "TEXT", 143 | KModelServiceMessage: "TEXT", 144 | } 145 | config.PrimaryKeyColumnName = KModelServiceKey 146 | case KUserTableName: 147 | config.ColumnConfig = map[string]string{ 148 | KUserName: "TEXT", 149 | KUserSession: "TEXT", 150 | KUserSessionValidTime: "TEXT", 151 | KUserConfig: "TEXT", 152 | KUserConfigVer: "TEXT", 153 | KUserCreateTime: "TEXT", 154 | KUserModifyTime: "TEXT", 155 | KUserPassword: "TEXT", 156 | } 157 | config.PrimaryKeyColumnName = KUserName 158 | case KConfigTableName: 159 | config.ColumnConfig = map[string]string{ 160 | KConfigKey: "TEXT", 161 | KConfigVal: "TEXT", 162 | KConfigVer: "TEXT", 163 | KConfigMd5: "TEXT", 164 | KConfigCreateTime: "TEXT", 165 | KConfigModifyTime: "TEXT", 166 | } 167 | config.PrimaryKeyColumnName = KConfigKey 168 | } 169 | return config 170 | } 171 | -------------------------------------------------------------------------------- /pkg/datastore/ots.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | import ( 4 | "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore" 5 | conf "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | otsClient *tablestore.TableStoreClient 11 | once sync.Once 12 | strToOtsType = map[string]tablestore.DefinedColumnType{ 13 | "TEXT": tablestore.DefinedColumn_STRING, 14 | "INT": tablestore.DefinedColumn_INTEGER, 15 | } 16 | ) 17 | 18 | // example: "TEXT" to tablestore.DefinedColumn_STRING 19 | func getOtsType(s string) tablestore.DefinedColumnType { 20 | return strToOtsType[s] 21 | } 22 | 23 | // InitOtsClient init ots client 24 | func InitOtsClient() { 25 | otsClient = tablestore.NewClientWithConfig(conf.ConfigGlobal.OtsEndpoint, conf.ConfigGlobal.OtsInstanceName, 26 | conf.ConfigGlobal.AccessKeyId, conf.ConfigGlobal.AccessKeySecret, conf.ConfigGlobal.AccessKeyToken, nil) 27 | } 28 | 29 | type OtsStore struct { 30 | config *Config 31 | } 32 | 33 | func NewOtsDatastore(config *Config) (*OtsStore, error) { 34 | // init otsClient, only first call valid 35 | once.Do(InitOtsClient) 36 | 37 | // check table is exist; if not create 38 | describeTableRequest := &tablestore.DescribeTableRequest{ 39 | TableName: config.TableName, 40 | } 41 | // check table is exist or not 42 | if tableInfo, err := otsClient.DescribeTable(describeTableRequest); err == nil && tableInfo.TableMeta != nil { 43 | return &OtsStore{config: config}, nil 44 | } 45 | // create table 46 | createTableRequest := new(tablestore.CreateTableRequest) 47 | tableMeta := new(tablestore.TableMeta) 48 | tableMeta.TableName = config.TableName 49 | tableMeta.AddPrimaryKeyColumn(conf.COLPK, tablestore.PrimaryKeyType_STRING) 50 | for field, cate := range config.ColumnConfig { 51 | tableMeta.AddDefinedColumn(field, getOtsType(cate)) 52 | } 53 | tableOption := new(tablestore.TableOption) 54 | tableOption.TimeToAlive = config.TimeToAlive 55 | tableOption.MaxVersion = config.MaxVersion 56 | reservedThroughput := new(tablestore.ReservedThroughput) 57 | reservedThroughput.Readcap = 0 58 | reservedThroughput.Writecap = 0 59 | createTableRequest.TableMeta = tableMeta 60 | createTableRequest.TableOption = tableOption 61 | createTableRequest.ReservedThroughput = reservedThroughput 62 | 63 | if _, err := otsClient.CreateTable(createTableRequest); err != nil { 64 | return nil, err 65 | } 66 | return &OtsStore{config: config}, nil 67 | } 68 | 69 | func (o *OtsStore) Get(key string, columns []string) (map[string]interface{}, error) { 70 | getRowRequest := new(tablestore.GetRowRequest) 71 | pk := new(tablestore.PrimaryKey) 72 | pk.AddPrimaryKeyColumn(conf.COLPK, key) 73 | getRowRequest.SingleRowQueryCriteria = &tablestore.SingleRowQueryCriteria{ 74 | PrimaryKey: pk, 75 | ColumnsToGet: columns, 76 | TableName: o.config.TableName, 77 | MaxVersion: 1, 78 | } 79 | resp, err := otsClient.GetRow(getRowRequest) 80 | if err != nil { 81 | return nil, err 82 | } 83 | columnMap := resp.GetColumnMap() 84 | if len(columnMap.Columns) == 0 { 85 | return nil, nil 86 | } 87 | ret := make(map[string]interface{}) 88 | for key, items := range columnMap.Columns { 89 | ret[key] = items[0].Value 90 | } 91 | return ret, nil 92 | } 93 | 94 | func (o *OtsStore) Put(key string, datas map[string]interface{}) error { 95 | putRowRequest := new(tablestore.PutRowRequest) 96 | putRowChange := new(tablestore.PutRowChange) 97 | putRowChange.TableName = o.config.TableName 98 | putPk := new(tablestore.PrimaryKey) 99 | putPk.AddPrimaryKeyColumn(conf.COLPK, key) 100 | 101 | putRowChange.PrimaryKey = putPk 102 | for col, data := range datas { 103 | putRowChange.AddColumn(col, data) 104 | } 105 | putRowChange.SetCondition(tablestore.RowExistenceExpectation_IGNORE) 106 | putRowRequest.PutRowChange = putRowChange 107 | if _, err := otsClient.PutRow(putRowRequest); err != nil { 108 | return err 109 | } 110 | return nil 111 | } 112 | 113 | func (o *OtsStore) Update(key string, datas map[string]interface{}) error { 114 | updateRowRequest := new(tablestore.UpdateRowRequest) 115 | updateRowChange := new(tablestore.UpdateRowChange) 116 | updateRowChange.TableName = o.config.TableName 117 | updatePk := new(tablestore.PrimaryKey) 118 | updatePk.AddPrimaryKeyColumn(conf.COLPK, key) 119 | updateRowChange.PrimaryKey = updatePk 120 | for col, data := range datas { 121 | updateRowChange.PutColumn(col, data) 122 | } 123 | updateRowChange.SetCondition(tablestore.RowExistenceExpectation_EXPECT_EXIST) 124 | updateRowRequest.UpdateRowChange = updateRowChange 125 | if _, err := otsClient.UpdateRow(updateRowRequest); err != nil { 126 | return err 127 | } 128 | return nil 129 | } 130 | 131 | func (o *OtsStore) Delete(key string) error { 132 | deletePk := new(tablestore.PrimaryKey) 133 | deletePk.AddPrimaryKeyColumn(conf.COLPK, key) 134 | deleteRowReq := new(tablestore.DeleteRowRequest) 135 | deleteRowReq.DeleteRowChange = new(tablestore.DeleteRowChange) 136 | deleteRowReq.DeleteRowChange.TableName = o.config.TableName 137 | deleteRowReq.DeleteRowChange.PrimaryKey = deletePk 138 | deleteRowReq.DeleteRowChange.SetCondition(tablestore.RowExistenceExpectation_EXPECT_EXIST) 139 | if _, err := otsClient.DeleteRow(deleteRowReq); err != nil { 140 | return err 141 | } 142 | return nil 143 | } 144 | 145 | func (o *OtsStore) ListAll(columns []string) (map[string]map[string]interface{}, error) { 146 | startPK := new(tablestore.PrimaryKey) 147 | startPK.AddPrimaryKeyColumnWithMinValue(conf.COLPK) 148 | endPK := new(tablestore.PrimaryKey) 149 | endPK.AddPrimaryKeyColumnWithMaxValue(conf.COLPK) 150 | 151 | rangeRowQueryCriteria := &tablestore.RangeRowQueryCriteria{ 152 | TableName: o.config.TableName, 153 | StartPrimaryKey: startPK, 154 | EndPrimaryKey: endPK, 155 | Direction: tablestore.FORWARD, 156 | MaxVersion: 1, 157 | Limit: 1000, 158 | ColumnsToGet: columns, 159 | } 160 | getRangeRequest := &tablestore.GetRangeRequest{ 161 | RangeRowQueryCriteria: rangeRowQueryCriteria, 162 | } 163 | 164 | getRangeResp, err := otsClient.GetRange(getRangeRequest) 165 | if err != nil { 166 | return nil, err 167 | } 168 | resp := make(map[string]map[string]interface{}) 169 | for _, row := range getRangeResp.Rows { 170 | result := make(map[string]interface{}) 171 | key := row.PrimaryKey.PrimaryKeys[0].Value.(string) 172 | for _, col := range row.Columns { 173 | result[col.ColumnName] = col.Value 174 | } 175 | resp[key] = result 176 | } 177 | return resp, nil 178 | } 179 | 180 | func (o *OtsStore) Close() error { 181 | // do nothing 182 | return nil 183 | } 184 | -------------------------------------------------------------------------------- /pkg/datastore/ots_test.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestOts(t *testing.T) { 9 | // create table 10 | tableName := "sd_test1" 11 | config := &Config{ 12 | Type: TableStore, 13 | TableName: tableName, 14 | ColumnConfig: map[string]string{ 15 | "user": "TEXT", 16 | "info": "TEXT", 17 | }, 18 | TimeToAlive: -1, 19 | MaxVersion: 1, 20 | } 21 | otsStore, err := NewOtsDatastore(config) 22 | assert.Nil(t, err) 23 | assert.NotNil(t, otsStore) 24 | 25 | // put 26 | err = otsStore.Put("lll", map[string]interface{}{ 27 | "info": "test", 28 | }) 29 | assert.Nil(t, err) 30 | data, err := otsStore.Get("lll", []string{"info"}) 31 | assert.Nil(t, err) 32 | assert.Equal(t, data["info"].(string), "test") 33 | 34 | // update 35 | err = otsStore.Update("lll", map[string]interface{}{ 36 | "info": "test1", 37 | "user": "admin", 38 | }) 39 | assert.Nil(t, err) 40 | data, err = otsStore.Get("lll", []string{"info", "user"}) 41 | assert.Nil(t, err) 42 | assert.Equal(t, data["info"].(string), "test1") 43 | assert.Equal(t, data["user"].(string), "admin") 44 | 45 | // ListAll 46 | 47 | datas, err := otsStore.ListAll([]string{"info", "user"}) 48 | assert.Nil(t, err) 49 | assert.Equal(t, len(datas), 1) 50 | } 51 | -------------------------------------------------------------------------------- /pkg/datastore/sqlite.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | 9 | _ "github.com/mattn/go-sqlite3" 10 | ) 11 | 12 | type SQLiteDatastore struct { 13 | db *sql.DB 14 | config *Config 15 | } 16 | 17 | func NewSQLiteDatastore(config *Config) *SQLiteDatastore { 18 | db, err := sql.Open("sqlite3", config.DBName) 19 | if err != nil { 20 | panic(fmt.Errorf("failed to open database: %v", err)) 21 | } 22 | 23 | // Create table if it doesn't exist. 24 | columnDefs := make([]string, 0, len(config.ColumnConfig)) 25 | for name, typ := range config.ColumnConfig { 26 | columnDefs = append(columnDefs, fmt.Sprintf("%s %s", name, typ)) 27 | } 28 | query := fmt.Sprintf( 29 | "CREATE TABLE IF NOT EXISTS %s (%s)", 30 | config.TableName, 31 | strings.Join(columnDefs, ", "), 32 | ) 33 | _, err = db.Exec(query) 34 | if err != nil { 35 | panic(fmt.Errorf("failed to create table %s: %v", config.TableName, err)) 36 | } 37 | return &SQLiteDatastore{ 38 | db: db, 39 | config: config, 40 | } 41 | } 42 | 43 | func (ds *SQLiteDatastore) Close() error { 44 | return ds.db.Close() 45 | } 46 | 47 | func (ds *SQLiteDatastore) Get(key string, columns []string) (map[string]interface{}, error) { 48 | row := ds.db.QueryRow( 49 | fmt.Sprintf("SELECT %s FROM %s WHERE %s = ?", 50 | strings.Join(columns, ", "), ds.config.TableName, ds.config.PrimaryKeyColumnName), 51 | key, 52 | ) 53 | 54 | // Prepare a slice to hold the values. 55 | values := make([]interface{}, len(columns)) 56 | for i, column := range columns { 57 | // We use the type information stored in the Config to create a variable of the correct type. 58 | var value interface{} 59 | switch ds.config.ColumnConfig[column] { 60 | case "TEXT": 61 | value = new(string) 62 | case "INT": 63 | // For simplicity, we use int64 for all integers. 64 | value = new(int64) 65 | case "FLOAT": 66 | value = new(float64) 67 | default: 68 | // If the column type is not supported, we return an error. 69 | return nil, fmt.Errorf("unsupported column type: %s", ds.config.ColumnConfig[column]) 70 | } 71 | values[i] = value 72 | } 73 | 74 | // Scan the result into the values slice. 75 | err := row.Scan(values...) 76 | if err != nil { 77 | if err == sql.ErrNoRows { 78 | // There is no row with the given key. 79 | return nil, nil 80 | } 81 | return nil, err 82 | } 83 | 84 | // Prepare the result map and fill it with values. 85 | result := make(map[string]interface{}) 86 | for i, column := range columns { 87 | // We use the reflect package to dereference the pointer. 88 | value := reflect.ValueOf(values[i]).Elem().Interface() 89 | result[column] = value 90 | } 91 | 92 | return result, nil 93 | } 94 | 95 | func (ds *SQLiteDatastore) Put(key string, values map[string]interface{}) error { 96 | columns := []string{ds.config.PrimaryKeyColumnName} 97 | placeholders := []string{"?"} 98 | args := []interface{}{key} 99 | for column, value := range values { 100 | columns = append(columns, column) 101 | placeholders = append(placeholders, "?") 102 | args = append(args, value) 103 | } 104 | query := fmt.Sprintf( 105 | "INSERT OR REPLACE INTO %s (%s) VALUES (%s)", 106 | ds.config.TableName, 107 | strings.Join(columns, ", "), 108 | strings.Join(placeholders, ", "), 109 | ) 110 | _, err := ds.db.Exec(query, args...) 111 | return err 112 | } 113 | 114 | func (ds *SQLiteDatastore) Update(key string, values map[string]interface{}) error { 115 | columns := make([]string, 0) 116 | args := make([]interface{}, 0) 117 | for column, value := range values { 118 | columns = append(columns, fmt.Sprintf("%s=?", column)) 119 | args = append(args, value) 120 | } 121 | args = append(args, key) 122 | query := fmt.Sprintf( 123 | "UPDATE %s SET %s WHERE %s = ?", 124 | ds.config.TableName, 125 | strings.Join(columns, ", "), 126 | ds.config.PrimaryKeyColumnName, 127 | ) 128 | _, err := ds.db.Exec(query, args...) 129 | return err 130 | } 131 | 132 | func (ds *SQLiteDatastore) Delete(key string) error { 133 | _, err := ds.db.Exec( 134 | fmt.Sprintf( 135 | "DELETE FROM %s WHERE %s = ?", ds.config.TableName, ds.config.PrimaryKeyColumnName), 136 | key) 137 | return err 138 | } 139 | 140 | func (ds *SQLiteDatastore) ListAll(columns []string) (map[string]map[string]interface{}, error) { 141 | rows, err := ds.db.Query(fmt.Sprintf("SELECT %s FROM %s", strings.Join(columns, ","), ds.config.TableName)) 142 | if err != nil { 143 | return nil, err 144 | } 145 | defer rows.Close() 146 | 147 | cols, err := rows.Columns() 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | results := make(map[string]map[string]interface{}) 153 | for rows.Next() { 154 | columns := make([]interface{}, len(cols)) 155 | columnPointers := make([]interface{}, len(cols)) 156 | for i := range columns { 157 | columnPointers[i] = &columns[i] 158 | } 159 | 160 | err := rows.Scan(columnPointers...) 161 | if err != nil { 162 | return nil, err 163 | } 164 | 165 | m := make(map[string]interface{}) 166 | for i, colName := range cols { 167 | val := columnPointers[i].(*interface{}) 168 | m[colName] = *val 169 | } 170 | 171 | key := m[ds.config.PrimaryKeyColumnName].(string) 172 | results[key] = m 173 | } 174 | 175 | if err := rows.Err(); err != nil { 176 | return nil, err 177 | } 178 | 179 | return results, nil 180 | } 181 | -------------------------------------------------------------------------------- /pkg/datastore/sqlite_test.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestSQLiteDatastore(t *testing.T) { 10 | primaryKeyColumnName := "primaryKey" 11 | config := &Config{ 12 | DBName: ":memory:", // the memory database for testing purposes 13 | TableName: "TestSQLiteDatastore", 14 | ColumnConfig: map[string]string{ 15 | primaryKeyColumnName: "TEXT primary key not null", 16 | "value": "TEXT", 17 | "intCol": "INT", 18 | "floatCol": "FLOAT", 19 | }, 20 | PrimaryKeyColumnName: primaryKeyColumnName, 21 | } 22 | ds := NewSQLiteDatastore(config) 23 | defer ds.Close() 24 | 25 | key := "testKey" 26 | value := "testValue" 27 | intValue := 123 28 | floatValue := 123.45 29 | 30 | // Test Put. 31 | err := ds.Put(key, map[string]interface{}{"value": value, "intCol": intValue, "floatCol": floatValue}) 32 | assert.NoError(t, err) 33 | 34 | // Test Get. 35 | result, err := ds.Get(key, []string{"value", "intCol", "floatCol"}) 36 | assert.NoError(t, err) 37 | assert.Equal(t, value, result["value"].(string)) 38 | assert.Equal(t, int64(intValue), result["intCol"].(int64)) 39 | assert.Equal(t, floatValue, result["floatCol"].(float64)) 40 | 41 | // Test Delete. 42 | err = ds.Delete(key) 43 | assert.NoError(t, err) 44 | 45 | // Test that the key is indeed deleted. 46 | result, err = ds.Get(key, []string{"value", "intCol", "floatCol"}) 47 | assert.NoError(t, err) 48 | assert.Nil(t, result) 49 | 50 | // Test deleting a non-existent key. 51 | err = ds.Delete("non-existent key") 52 | assert.NoError(t, err) 53 | 54 | // Test Put with non-existent column. 55 | err = ds.Put(key, map[string]interface{}{"non_existent_column": value}) 56 | assert.Error(t, err) 57 | 58 | // Test Put with wrong value type. 59 | // Note: we do not expect wrong value type will result in error, since Go database/sql will try to convert it automatically. 60 | err = ds.Put(key, map[string]interface{}{"value": 123, "intCol": "123", "floatCol": "123.45"}) 61 | assert.NoError(t, err) 62 | 63 | // Test get with value=123 64 | ret, err := ds.Get(key, []string{"value"}) 65 | assert.NoError(t, err) 66 | assert.NotNil(t, ret) 67 | assert.Equal(t, "123", ret["value"].(string)) 68 | 69 | // Test Update 70 | err = ds.Update(key, map[string]interface{}{"value": "234"}) 71 | assert.NoError(t, err) 72 | 73 | // Test get with value=123 74 | ret, err = ds.Get(key, []string{"value"}) 75 | assert.NoError(t, err) 76 | assert.NotNil(t, ret) 77 | assert.Equal(t, "234", ret["value"].(string)) 78 | 79 | // Test Get with non-existent column. 80 | _, err = ds.Get(key, []string{"non_existent_column"}) 81 | assert.Error(t, err) 82 | 83 | // Test Get with non-existent key. 84 | _, err = ds.Get("non-existent key", []string{"value", "intCol", "floatCol"}) 85 | assert.NoError(t, err) 86 | } 87 | 88 | func TestListAll(t *testing.T) { 89 | primaryKeyColumnName := "primaryKey" 90 | config := &Config{ 91 | DBName: ":memory:", // the memory database for testing purposes 92 | TableName: "TestListAll", 93 | ColumnConfig: map[string]string{ 94 | primaryKeyColumnName: "text primary key not null", 95 | "value": "text", 96 | "intCol": "int", 97 | "floatCol": "float", 98 | }, 99 | PrimaryKeyColumnName: primaryKeyColumnName, 100 | } 101 | ds := NewSQLiteDatastore(config) 102 | defer ds.Close() 103 | 104 | // Insert some test data. 105 | testData := map[string]map[string]interface{}{ 106 | "key1": {"value": "value1", "intCol": 1, "floatCol": 1.1}, 107 | "key2": {"value": "value2", "intCol": 2, "floatCol": 2.2}, 108 | "key3": {"value": "value3", "intCol": 3, "floatCol": 3.3}, 109 | } 110 | for k, v := range testData { 111 | err := ds.Put(k, v) 112 | assert.NoError(t, err) 113 | } 114 | 115 | // Call ListAll and check the result. 116 | result, err := ds.ListAll([]string{primaryKeyColumnName, "value", "intCol", "floatCol"}) 117 | assert.NoError(t, err) 118 | for k, v := range testData { 119 | r, ok := result[k] 120 | assert.True(t, ok) 121 | assert.Equal(t, v["value"], r["value"].(string)) 122 | assert.Equal(t, int64(v["intCol"].(int)), r["intCol"].(int64)) 123 | assert.Equal(t, v["floatCol"].(float64), r["floatCol"].(float64)) 124 | } 125 | 126 | // Delete all data. 127 | for k := range testData { 128 | err = ds.Delete(k) 129 | assert.NoError(t, err) 130 | } 131 | 132 | // Call ListAll again and check the result. 133 | result, err = ds.ListAll([]string{primaryKeyColumnName, "value", "intCol", "floatCol"}) 134 | assert.NoError(t, err) 135 | assert.Equal(t, 0, len(result)) 136 | 137 | } 138 | -------------------------------------------------------------------------------- /pkg/datastore/table_meta.go: -------------------------------------------------------------------------------- 1 | package datastore 2 | 3 | // function table 4 | const ( 5 | KModelServiceTableName = "function" 6 | KModelServiceKey = "PRIMARY_KEY" 7 | KModelServiceFunctionName = "FUNCTION" 8 | KModelServiceSdModel = "SD_MODEL" 9 | KModelServiceEndPoint = "END_POINT" 10 | KModelServerImage = "IMAGE" 11 | KModelServiceMessage = "MESSAGE" 12 | KModelServiceCreateTime = "FUNC_CREATE_TIME" 13 | KModelServiceLastModifyTime = "FUNC_LAST_MODIFY_TIME" 14 | ) 15 | 16 | // models table 17 | const ( 18 | KModelTableName = "models" 19 | KModelType = "MODEL_TYPE" 20 | KModelName = "MODEL_NAME" 21 | KModelOssPath = "MODEL_OSS_PATH" 22 | KModelEtag = "MODEL_ETAG" 23 | KModelStatus = "MODEL_STATUS" 24 | KModelLocalPath = "MODEL_LOCAL_PATH" 25 | KModelCreateTime = "MODEL_REGISTERED" 26 | KModelModifyTime = "MODEL_MODIFY" 27 | ) 28 | 29 | // tasks table 30 | const ( 31 | KTaskTableName = "tasks" 32 | KTaskIdColumnName = "TASK_ID" 33 | KTaskUser = "TASK_USER" 34 | KTaskProgressColumnName = "TASK_PROGRESS" 35 | KTaskInfo = "TASK_INFO" 36 | KTaskImage = "TASK_IMAGE" 37 | KTaskCode = "TASK_CODE" 38 | KTaskCancel = "TASK_CANCEL" 39 | KTaskParams = "TASK_PARAMS" 40 | KTaskStatus = "TASK_STATUS" 41 | KTaskCreateTime = "TASK_CREATE_TIME" 42 | KTaskModifyTime = "TASK_MODIFY_TIME" 43 | ) 44 | 45 | // user table 46 | const ( 47 | KUserTableName = "users" 48 | KUserName = "USER_NAME" 49 | KUserPassword = "USER_PASSWORD" 50 | KUserSession = "USER_SESSION" 51 | KUserSessionValidTime = "USER_SESSION_VALID" 52 | KUserConfig = "USER_CONFIG" 53 | KUserConfigVer = "USER_CONFIG_VERSION" 54 | KUserCreateTime = "USER_CREATE_TIME" 55 | KUserModifyTime = "USER_MODIFY_TIME" 56 | ) 57 | 58 | // config 59 | const ( 60 | KConfigTableName = "config" 61 | KConfigKey = "CONFIG_KEY" 62 | KConfigVal = "CONFIG_VAL" 63 | KConfigMd5 = "CONFIG_MD5" 64 | KConfigVer = "CONFIG_VERSION" 65 | KConfigCreateTime = "CONFIG_CREATE_TIME" 66 | KConfigModifyTime = "CONFIG_MODIFY_TIME" 67 | ) 68 | -------------------------------------------------------------------------------- /pkg/handler/util.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 9 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/datastore" 10 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/models" 11 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/module" 12 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/utils" 13 | "github.com/gin-gonic/gin" 14 | "github.com/gin-gonic/gin/binding" 15 | "github.com/sirupsen/logrus" 16 | "io" 17 | "io/ioutil" 18 | "net/http" 19 | "os" 20 | "strings" 21 | "time" 22 | ) 23 | 24 | const ( 25 | taskIdLength = 10 26 | userKey = "username" 27 | requestType = "Request-Type" 28 | taskKey = "taskId" 29 | FcAsyncKey = "X-Fc-Invocation-Type" 30 | versionKey = "version" 31 | requestOk = 200 32 | requestFail = 422 33 | asyncSuccessCode = 202 34 | syncSuccessCode = 200 35 | base64MinLen = 2048 36 | ) 37 | 38 | func getBindResult(c *gin.Context, in interface{}) error { 39 | if err := binding.JSON.Bind(c.Request, in); err != nil { 40 | return err 41 | } 42 | return nil 43 | } 44 | 45 | func outputImage(fileName, base64Str *string) error { 46 | decode, err := base64.StdEncoding.DecodeString(*base64Str) 47 | if err != nil { 48 | return fmt.Errorf("base64 decode err=%s", err.Error()) 49 | } 50 | if err := ioutil.WriteFile(*fileName, decode, 0666); err != nil { 51 | return fmt.Errorf("writer image err=%s", err.Error()) 52 | } 53 | return nil 54 | } 55 | 56 | func downloadModelsFromOss(modelsType, ossPath, modelName string) (string, error) { 57 | path := "" 58 | switch modelsType { 59 | case config.SD_MODEL: 60 | path = fmt.Sprintf("%s/models/%s/%s", config.ConfigGlobal.SdPath, "Stable-diffusion", modelName) 61 | case config.SD_VAE: 62 | path = fmt.Sprintf("%s/models/%s/%s", config.ConfigGlobal.SdPath, "VAE", modelName) 63 | case config.LORA_MODEL: 64 | path = fmt.Sprintf("%s/models/%s/%s", config.ConfigGlobal.SdPath, "Lora", modelName) 65 | case config.CONTORLNET_MODEL: 66 | path = fmt.Sprintf("%s/models/%s/%s", config.ConfigGlobal.SdPath, "ControlNet", modelName) 67 | default: 68 | return "", fmt.Errorf("modeltype: %s not support", modelsType) 69 | } 70 | if err := module.OssGlobal.DownloadFile(ossPath, path); err != nil { 71 | return "", err 72 | } 73 | return path, nil 74 | } 75 | 76 | func uploadImages(ossPath, imageBody *string) error { 77 | decode, err := base64.StdEncoding.DecodeString(*imageBody) 78 | if err != nil { 79 | return fmt.Errorf("base64 decode err=%s", err.Error()) 80 | } 81 | return module.OssGlobal.UploadFileByByte(*ossPath, decode) 82 | } 83 | 84 | // delete local file 85 | func deleteLocalModelFile(localFile string) (bool, error) { 86 | _, err := os.Stat(localFile) 87 | if err == nil { 88 | if err := os.Remove(localFile); err == nil { 89 | return true, nil 90 | } else { 91 | return false, errors.New("delete model fail") 92 | } 93 | } 94 | if os.IsNotExist(err) { 95 | return false, errors.New("model not exist") 96 | } 97 | return false, err 98 | } 99 | 100 | func handleError(c *gin.Context, code int, err string) { 101 | c.JSON(code, gin.H{"message": err}) 102 | } 103 | 104 | func isImgPath(str string) bool { 105 | return strings.HasSuffix(str, ".png") || strings.HasSuffix(str, ".jpg") || 106 | strings.HasSuffix(str, ".jpeg") 107 | } 108 | 109 | func listModelFile(path, modelType string) (modelAttrs []*models.ModelAttributes) { 110 | files := utils.ListFile(path) 111 | for _, name := range files { 112 | if strings.HasSuffix(name, ".pt") || strings.HasSuffix(name, ".ckpt") || 113 | strings.HasSuffix(name, ".safetensors") || strings.HasSuffix(name, ".pth") { 114 | modelAttrs = append(modelAttrs, &models.ModelAttributes{ 115 | Type: modelType, 116 | Name: name, 117 | Status: config.MODEL_LOADED, 118 | }) 119 | } 120 | } 121 | return 122 | } 123 | 124 | // Stat cost code 125 | func Stat() gin.HandlerFunc { 126 | return func(c *gin.Context) { 127 | startTime := time.Now() 128 | c.Next() 129 | endTime := time.Now() 130 | latencyTime := endTime.Sub(startTime) 131 | reqMethod := c.Request.Method 132 | reqUri := c.Request.RequestURI 133 | statusCode := c.Writer.Status() 134 | clientIP := c.ClientIP() 135 | logrus.Infof("%s | %3d | %13v | %15s | %s | %s | %s | %s", 136 | config.ConfigGlobal.ServerName, 137 | statusCode, 138 | latencyTime, 139 | clientIP, 140 | reqMethod, 141 | reqUri, 142 | func() string { 143 | if taskId := c.Writer.Header().Get("taskId"); taskId != "" { 144 | return fmt.Sprintf("taskId=%s", taskId) 145 | } else { 146 | return "" 147 | } 148 | }(), 149 | func() string { 150 | if model := c.Writer.Header().Get("model"); model != "" { 151 | return fmt.Sprintf("model=%s", model) 152 | } else { 153 | return "" 154 | } 155 | }(), 156 | ) 157 | } 158 | } 159 | 160 | func convertImgToBase64(body []byte) ([]byte, error) { 161 | var request map[string]interface{} 162 | if err := json.Unmarshal(body, &request); err != nil { 163 | return body, err 164 | } 165 | parseMap(request, "", "", nil) 166 | if newRequest, err := json.Marshal(request); err != nil { 167 | return body, err 168 | } else { 169 | return newRequest, nil 170 | } 171 | } 172 | 173 | func convertBase64ToImg(body []byte, taskId, user string) ([]byte, error) { 174 | idx := 1 175 | var request map[string]interface{} 176 | if err := json.Unmarshal(body, &request); err == nil { 177 | newRequest := parseMap(request, taskId, user, &idx) 178 | if newBody, err := json.Marshal(newRequest); err != nil { 179 | return body, err 180 | } else { 181 | return newBody, nil 182 | } 183 | } else { 184 | var request []interface{} 185 | if err := json.Unmarshal(body, &request); err == nil { 186 | newRequest := parseArray(request, taskId, user, &idx) 187 | if newBody, err := json.Marshal(newRequest); err != nil { 188 | return body, err 189 | } else { 190 | return newBody, nil 191 | } 192 | } 193 | } 194 | return body, nil 195 | } 196 | 197 | func parseMap(aMap map[string]interface{}, taskId, user string, idx *int) map[string]interface{} { 198 | for key, val := range aMap { 199 | switch concreteVal := val.(type) { 200 | case map[string]interface{}: 201 | aMap[key] = parseMap(val.(map[string]interface{}), taskId, user, idx) 202 | case []interface{}: 203 | aMap[key] = parseArray(val.([]interface{}), taskId, user, idx) 204 | case string: 205 | if isImgPath(concreteVal) { 206 | base64, err := module.OssGlobal.DownloadFileToBase64(concreteVal) 207 | if err == nil { 208 | aMap[key] = *base64 209 | } 210 | } else if idx != nil && len(concreteVal) > base64MinLen { 211 | if taskId == "" { 212 | taskId = utils.RandStr(taskIdLength) 213 | } 214 | ossPath := fmt.Sprintf("images/%s/%s_%d.png", user, taskId, *idx) 215 | // check base64 216 | if err := uploadImages(&ossPath, &concreteVal); err == nil { 217 | *idx += 1 218 | aMap[key] = ossPath 219 | } 220 | } 221 | } 222 | } 223 | return aMap 224 | } 225 | 226 | func parseArray(anArray []interface{}, taskId, user string, idx *int) []interface{} { 227 | for i, val := range anArray { 228 | switch concreteVal := val.(type) { 229 | case map[string]interface{}: 230 | anArray[i] = parseMap(val.(map[string]interface{}), taskId, user, idx) 231 | case []interface{}: 232 | anArray[i] = parseArray(val.([]interface{}), taskId, user, idx) 233 | case string: 234 | if isImgPath(concreteVal) { 235 | base64, err := module.OssGlobal.DownloadFileToBase64(concreteVal) 236 | if err == nil { 237 | anArray[i] = *base64 238 | } 239 | } else if idx != nil && len(concreteVal) > base64MinLen { 240 | if taskId == "" { 241 | taskId = utils.RandStr(taskIdLength) 242 | } 243 | ossPath := fmt.Sprintf("images/%s/%s_%d.png", user, taskId, *idx) 244 | // check base64 245 | if err := uploadImages(&ossPath, &concreteVal); err == nil { 246 | *idx += 1 247 | anArray[i] = ossPath 248 | } 249 | 250 | } 251 | } 252 | } 253 | return anArray 254 | } 255 | 256 | func getFunctionDatas(store datastore.Datastore, 257 | request *models.BatchUpdateSdResourceRequest) (map[string]*module.FuncResource, error) { 258 | // get function resource 259 | Datas := make(map[string]*module.FuncResource) 260 | if request.Models == nil || len(*request.Models) == 0 { 261 | funcDatas, err := store.ListAll([]string{datastore.KModelServiceKey, datastore.KModelServiceFunctionName}) 262 | if err != nil { 263 | return nil, err 264 | } 265 | for _, funcData := range funcDatas { 266 | functionName := funcData[datastore.KModelServiceFunctionName].(string) 267 | if resource := module.FuncManagerGlobal.GetFuncResource(functionName); resource != nil { 268 | funcDataNew, err := updateFuncResource(request, resource) 269 | if err != nil { 270 | return nil, err 271 | } 272 | if funcDataNew != nil { 273 | Datas[funcData[datastore.KModelServiceKey].(string)] = funcDataNew 274 | } 275 | } 276 | } 277 | } else { 278 | for _, model := range *request.Models { 279 | functionName := module.GetFunctionName(model) 280 | if resource := module.FuncManagerGlobal.GetFuncResource(functionName); resource != nil { 281 | funcDataNew, err := updateFuncResource(request, resource) 282 | if err != nil { 283 | return nil, err 284 | } 285 | if funcDataNew != nil { 286 | Datas[model] = funcDataNew 287 | } 288 | } 289 | } 290 | } 291 | return Datas, nil 292 | } 293 | 294 | func updateFuncResource(request *models.BatchUpdateSdResourceRequest, 295 | res *module.FuncResource) (*module.FuncResource, error) { 296 | isDiff := false 297 | // update resource 298 | // image 299 | if request.Image != nil && *request.Image != "" && *request.Image != res.Image { 300 | res.Image = *request.Image 301 | isDiff = true 302 | } 303 | // cpu 304 | if request.Cpu != nil && *request.Cpu > 0 && *request.Cpu != res.CPU { 305 | res.CPU = *request.Cpu 306 | isDiff = true 307 | } 308 | // env 309 | if request.Env != nil { 310 | if res.Env == nil { 311 | res.Env = make(map[string]*string) 312 | } 313 | for key, val := range *request.Env { 314 | res.Env[key] = utils.String(val.(string)) 315 | } 316 | isDiff = true 317 | } 318 | // extraArgs 319 | if request.ExtraArgs != nil && *request.ExtraArgs != "" && *request.ExtraArgs != *res.Env["EXTRA_ARGS"] { 320 | res.Env["EXTRA_ARGS"] = request.ExtraArgs 321 | isDiff = true 322 | } 323 | // gpuMemorySize 324 | if request.GpuMemorySize != nil && *request.GpuMemorySize > 0 && *request.GpuMemorySize != int64(res.GpuMemorySize) { 325 | res.GpuMemorySize = int32(*request.GpuMemorySize) 326 | isDiff = true 327 | } 328 | // MemorySize 329 | if request.MemorySize != nil && *request.MemorySize > 0 && *request.MemorySize != int64(res.MemorySize) { 330 | res.MemorySize = int32(*request.MemorySize) 331 | isDiff = true 332 | } 333 | // instanceType 334 | if request.InstanceType != nil && *request.InstanceType != "" && *request.InstanceType != res.InstanceType { 335 | res.InstanceType = *request.InstanceType 336 | isDiff = true 337 | } 338 | // timeout 339 | if request.Timeout != nil && *request.Timeout > 0 && *request.Timeout != int64(res.Timeout) { 340 | res.Timeout = int32(*request.Timeout) 341 | isDiff = true 342 | } 343 | if request.VpcConfig != nil { 344 | res.VpcConfig = request.VpcConfig 345 | isDiff = true 346 | } 347 | if request.NasConfig != nil { 348 | res.NasConfig = request.NasConfig 349 | isDiff = true 350 | } 351 | if request.OssMountConfig != nil { 352 | res.OssMountConfig = request.OssMountConfig 353 | isDiff = true 354 | } 355 | if isDiff { 356 | return res, nil 357 | } else { 358 | return nil, nil 359 | } 360 | } 361 | 362 | // check stable_diffusion_model val not "" or nil 363 | func checkSdModelValid(sdModel string) bool { 364 | return sdModel != "" 365 | } 366 | 367 | // extra ossUrl 368 | func extraOssUrl(resp *http.Response) *[]string { 369 | in, err := io.ReadAll(resp.Body) 370 | defer resp.Body.Close() 371 | if err != nil { 372 | logrus.Warn("get oss url error") 373 | return nil 374 | } 375 | var m models.SubmitTaskResponse 376 | if err := json.Unmarshal(in, &m); err != nil { 377 | return nil 378 | } else { 379 | return m.OssUrl 380 | } 381 | } 382 | 383 | // extra err message 384 | func extraErrorMsg(resp *http.Response) *string { 385 | in, err := io.ReadAll(resp.Body) 386 | defer resp.Body.Close() 387 | if err != nil { 388 | logrus.Warn("extra resp error") 389 | return nil 390 | } 391 | var m map[string]interface{} 392 | if err := json.Unmarshal(in, &m); err != nil { 393 | return nil 394 | } else { 395 | if val, ok := m["message"]; ok { 396 | msg := val.(string) 397 | return &msg 398 | } 399 | } 400 | return utils.String(string(in)) 401 | } 402 | 403 | func handleRespError(c *gin.Context, err error, resp *http.Response, taskId string) { 404 | msg := "" 405 | if err != nil { 406 | msg = err.Error() 407 | logrus.WithFields(logrus.Fields{"taskId": taskId}).Errorf("%v", err) 408 | } else { 409 | if v := extraErrorMsg(resp); v != nil { 410 | msg = *v 411 | } else { 412 | msg = config.INTERNALERROR 413 | } 414 | logrus.WithFields(logrus.Fields{"taskId": taskId}).Errorf("%v", resp) 415 | } 416 | c.JSON(resp.StatusCode, models.SubmitTaskResponse{ 417 | TaskId: taskId, 418 | Status: config.TASK_FAILED, 419 | Message: utils.String(msg), 420 | }) 421 | } 422 | -------------------------------------------------------------------------------- /pkg/log/instance.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 6 | "github.com/sirupsen/logrus" 7 | "os" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | // send log && trace 13 | const ( 14 | defaultCacheCount = 64 15 | defaultCacheSize = 16 * 1024 // 16KB 16 | logPath = "collect/log" 17 | tracePath = "collect/tracker" 18 | ) 19 | 20 | var SDLogInstance = NewSDLog() 21 | 22 | // Log ... 23 | type Log struct { 24 | AccountID string `json:"accountID"` 25 | Level string `json:"level"` 26 | Ts int64 `json:"ts"` 27 | Msg string `json:"msg"` 28 | RequestID string `json:"requestID"` 29 | Source string `json:"source"` 30 | } 31 | 32 | func (l *Log) Size() int { 33 | return len(l.Msg) 34 | } 35 | 36 | // Tracker ... 37 | type Tracker struct { 38 | Key string `json:"key"` 39 | AccountID string `json:"accountID"` 40 | Ts int64 `json:"ts"` 41 | Payload interface{} `json:"payload"` 42 | Source string `json:"source"` 43 | } 44 | 45 | // SDLog sd log instance 46 | type SDLog struct { 47 | taskId string 48 | requestId sync.Map 49 | cacheLog []*Log 50 | cacheTrace []*Tracker 51 | LogFlow chan string 52 | TraceFlow chan []string 53 | closeLog chan struct{} 54 | closeTrace chan struct{} 55 | accountId string 56 | functionName string 57 | } 58 | 59 | func NewSDLog() *SDLog { 60 | sdLogInstance := &SDLog{ 61 | LogFlow: make(chan string, 8192), 62 | TraceFlow: make(chan []string, 8192), 63 | cacheLog: make([]*Log, 0, defaultCacheCount), 64 | cacheTrace: make([]*Tracker, 0, defaultCacheCount), 65 | closeLog: make(chan struct{}), 66 | closeTrace: make(chan struct{}), 67 | accountId: os.Getenv(config.FC_ACCOUNT_ID), 68 | functionName: os.Getenv(config.FC_FUNCTION_NAME), 69 | requestId: sync.Map{}, 70 | } 71 | go sdLogInstance.consumeLog() 72 | go sdLogInstance.consumeTrace() 73 | return sdLogInstance 74 | } 75 | 76 | func (s *SDLog) getRequestId() string { 77 | requestId := strings.Builder{} 78 | s.requestId.Range(func(key, value any) bool { 79 | req := key.(string) 80 | if requestId.Len() != 0 { 81 | requestId.WriteString(",") 82 | } 83 | requestId.WriteString(req) 84 | return true 85 | }) 86 | return requestId.String() 87 | } 88 | 89 | func (s *SDLog) consumeLog() { 90 | cacheSize := 0 91 | for { 92 | select { 93 | case logStr := <-s.LogFlow: 94 | if s.taskId != "" { 95 | logrus.WithFields(logrus.Fields{ 96 | "taskId": s.taskId, 97 | }).Info(logStr) 98 | } else if requestId := s.getRequestId(); requestId != "" { 99 | logrus.WithFields(logrus.Fields{ 100 | "requestId": requestId, 101 | }).Info(logStr) 102 | if config.ConfigGlobal.SendLogToRemote() { 103 | logObj := &Log{ 104 | AccountID: config.ConfigGlobal.AccountId, 105 | Msg: logStr, 106 | RequestID: requestId, 107 | Source: config.ConfigGlobal.ServerName, 108 | Level: "info", 109 | } 110 | if cacheSize >= defaultCacheSize || len(s.cacheLog) >= defaultCacheCount { 111 | if body, err := json.Marshal(s.cacheLog); err == nil { 112 | go monitor.Post(body, logPath) 113 | } 114 | s.cacheLog = s.cacheLog[:0] 115 | cacheSize = 0 116 | } 117 | s.cacheLog = append(s.cacheLog, logObj) 118 | cacheSize += logObj.Size() 119 | } 120 | } else { 121 | logrus.Info(logStr) 122 | } 123 | case <-s.closeLog: 124 | return 125 | } 126 | } 127 | } 128 | 129 | func (s *SDLog) consumeTrace() { 130 | for { 131 | select { 132 | case traceSlice := <-s.TraceFlow: 133 | if config.ConfigGlobal.SendLogToRemote() { 134 | traceObj := &Tracker{ 135 | AccountID: config.ConfigGlobal.AccountId, 136 | Key: traceSlice[0], 137 | Ts: 0, 138 | Payload: traceSlice[1], 139 | Source: config.ConfigGlobal.ServerName, 140 | } 141 | if len(s.cacheTrace) >= defaultCacheCount { 142 | if body, err := json.Marshal(s.cacheTrace); err == nil { 143 | go monitor.Post(body, tracePath) 144 | } 145 | s.cacheTrace = s.cacheTrace[:0] 146 | } 147 | s.cacheTrace = append(s.cacheTrace, traceObj) 148 | 149 | } 150 | case <-s.closeTrace: 151 | return 152 | } 153 | } 154 | } 155 | 156 | func (s *SDLog) SetTaskId(taskId string) { 157 | s.taskId = taskId 158 | } 159 | 160 | func (s *SDLog) AddRequestId(requestId string) { 161 | s.requestId.Store(requestId, struct{}{}) 162 | } 163 | 164 | func (s *SDLog) DelRequestId(requestId string) { 165 | s.requestId.Delete(requestId) 166 | } 167 | 168 | func (s *SDLog) Close() { 169 | s.closeLog <- struct{}{} 170 | s.closeTrace <- struct{}{} 171 | // send trace 172 | if len(s.cacheTrace) > 0 { 173 | if body, err := json.Marshal(s.cacheTrace); err == nil { 174 | monitor.Post(body, tracePath) 175 | } 176 | } 177 | // send log 178 | if len(s.cacheLog) > 0 { 179 | if body, err := json.Marshal(s.cacheLog); err == nil { 180 | monitor.Post(body, logPath) 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /pkg/log/monitor.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | var monitor = NewMonitor() 12 | 13 | const ( 14 | timeout = 2 * time.Second 15 | ) 16 | 17 | type Monitor struct { 18 | client *http.Client 19 | } 20 | 21 | func NewMonitor() *Monitor { 22 | return &Monitor{ 23 | client: &http.Client{Timeout: timeout}, 24 | } 25 | } 26 | 27 | func (m *Monitor) Post(body []byte, path string) error { 28 | url := fmt.Sprintf("%s/%s", config.ConfigGlobal.LogRemoteService, path) 29 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | _, err = m.client.Do(req) 35 | return err 36 | } 37 | -------------------------------------------------------------------------------- /pkg/models/sd.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Txt2ImgResult struct { 4 | Images []string `json:"images"` 5 | Parameters map[string]interface{} `json:"parameters"` 6 | Info string `json:"info"` 7 | } 8 | 9 | type ExtraImageResult struct { 10 | HTMLInfo string `json:"html_info"` 11 | Image string `json:"image"` 12 | } 13 | 14 | type ProgressResult struct { 15 | CurrentImage string `json:"current_image"` 16 | EtaRelative float64 `json:"eta_relative"` 17 | Progress float64 `json:"progress"` 18 | State State `json:"state"` 19 | } 20 | 21 | type State struct { 22 | Interrupted bool `json:"interrupted"` 23 | Job string `json:"job"` 24 | JobCount int `json:"job_count"` 25 | JobNo int `json:"job_no"` 26 | JobTimestamp string `json:"job_timestamp"` 27 | SamplingStep int `json:"sampling_step"` 28 | SamplingSteps int `json:"sampling_steps"` 29 | Skipped bool `json:"skipped"` 30 | } 31 | 32 | type ControlNet struct { 33 | Args []Args `json:"args"` 34 | } 35 | type Args struct { 36 | ControlMode int `json:"control_mode"` 37 | Enabled bool `json:"enabled"` 38 | GuidanceEnd float64 `json:"guidance_end"` 39 | GuidanceStart float64 `json:"guidance_start"` 40 | Image string `json:"image"` 41 | Lowvram bool `json:"lowvram"` 42 | Model string `json:"model"` 43 | Module string `json:"module"` 44 | PixelPerfect bool `json:"pixel_perfect"` 45 | ProcessorRes int `json:"processor_res"` 46 | ResizeMode int `json:"resize_mode"` 47 | ThresholdA int `json:"threshold_a"` 48 | ThresholdB int `json:"threshold_b"` 49 | Weight float64 `json:"weight"` 50 | } 51 | -------------------------------------------------------------------------------- /pkg/module/event.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "fmt" 5 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 6 | "github.com/sirupsen/logrus" 7 | "net/http" 8 | ) 9 | 10 | var client = &http.Client{} 11 | 12 | // ModelChangeEvent models change callback func 13 | func ModelChangeEvent(v any) { 14 | modelType := v.(string) 15 | path := "" 16 | method := "GET" 17 | switch modelType { 18 | case config.SD_MODEL: 19 | path = config.REFRESH_SD_MODEL 20 | method = "POST" 21 | case config.CONTORLNET_MODEL: 22 | path = config.REFRESH_CONTROLNET 23 | method = "GET" 24 | case config.SD_VAE: 25 | path = config.REFRESH_VAE 26 | method = "POST" 27 | default: 28 | logrus.Infof("[ModelChangeEvent] modelType=%s no need reload", modelType) 29 | return 30 | } 31 | url := fmt.Sprintf("%s%s", config.ConfigGlobal.SdUrlPrefix, path) 32 | req, _ := http.NewRequest(method, url, nil) 33 | _, err := client.Do(req) 34 | if err != nil { 35 | logrus.Info("[ModelChangeEvent] listen model refresh do fail") 36 | return 37 | } 38 | } 39 | 40 | // CancelEvent tasks cancel signal callback 41 | func CancelEvent(v any) { 42 | path := config.CANCEL 43 | url := fmt.Sprintf("%s%s", config.ConfigGlobal.SdUrlPrefix, path) 44 | req, _ := http.NewRequest("POST", url, nil) 45 | _, err := client.Do(req) 46 | if err != nil { 47 | logrus.Info("[CancelEvent] listen cancel do fail") 48 | } 49 | logrus.Info("[CancelEvent] listen cancel signal, url=", url) 50 | } 51 | 52 | // ConfigEvent config.json 53 | func ConfigEvent(v any) { 54 | // update all function env 55 | retry := 2 56 | for retry > 0 { 57 | if err := FuncManagerGlobal.UpdateAllFunctionEnv(); err == nil { 58 | return 59 | } 60 | retry-- 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pkg/module/function.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" 12 | fc3 "github.com/alibabacloud-go/fc-20230330/client" 13 | fc "github.com/alibabacloud-go/fc-open-20210406/v2/client" 14 | fcService "github.com/alibabacloud-go/tea-utils/v2/service" 15 | gr "github.com/awesome-fc/golang-runtime" 16 | "github.com/devsapp/goutils/aigc/project" 17 | fcUtils "github.com/devsapp/goutils/fc" 18 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 19 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/datastore" 20 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/utils" 21 | "github.com/sirupsen/logrus" 22 | ) 23 | 24 | const ( 25 | RETRY_INTERVALMS = time.Duration(10) * time.Millisecond 26 | ) 27 | 28 | type SdModels struct { 29 | sdModel string 30 | sdVae string 31 | endpoint string 32 | } 33 | 34 | // FuncResource Fc resource 35 | type FuncResource struct { 36 | Image string `json:"image"` 37 | CPU float32 `json:"cpu"` 38 | GpuMemorySize int32 `json:"gpuMemorySize"` 39 | InstanceType string `json:"InstanceType"` 40 | MemorySize int32 `json:"memorySize"` 41 | Timeout int32 `json:"timeout"` 42 | Env map[string]*string `json:"env"` 43 | VpcConfig *map[string]interface{} `json:"vpcConfig"` 44 | NasConfig *map[string]interface{} `json:"nasConfig"` 45 | OssMountConfig *map[string]interface{} `json:"ossMountConfig"` 46 | } 47 | 48 | var FuncManagerGlobal *FuncManager 49 | 50 | // FuncManager manager fc function 51 | // create function and http trigger 52 | // update instance env 53 | type FuncManager struct { 54 | endpoints map[string][]string 55 | //modelToInfo map[string][]*SdModels 56 | funcStore datastore.Datastore 57 | fcClient *fc.Client 58 | fc3Client *fc3.Client 59 | lock sync.RWMutex 60 | lastInvokeEndpoint string 61 | prefix string 62 | } 63 | 64 | func isFc3() bool { 65 | return config.ConfigGlobal.ServiceName == "" 66 | } 67 | 68 | func InitFuncManager(funcStore datastore.Datastore) error { 69 | // init fc client 70 | fcEndpoint := fmt.Sprintf("%s.%s.fc.aliyuncs.com", config.ConfigGlobal.AccountId, 71 | config.ConfigGlobal.Region) 72 | FuncManagerGlobal = &FuncManager{ 73 | endpoints: make(map[string][]string), 74 | funcStore: funcStore, 75 | } 76 | // extra prefix 77 | if parts := strings.Split(config.ConfigGlobal.FunctionName, project.PrefixDelimiter); len(parts) >= 2 { 78 | FuncManagerGlobal.prefix = fmt.Sprintf("%s%s", parts[0], project.PrefixDelimiter) 79 | } 80 | var err error 81 | if isFc3() { 82 | FuncManagerGlobal.fc3Client, err = fc3.NewClient(new(openapi.Config).SetAccessKeyId(config.ConfigGlobal.AccessKeyId). 83 | SetAccessKeySecret(config.ConfigGlobal.AccessKeySecret).SetSecurityToken(config.ConfigGlobal.AccessKeyToken). 84 | SetProtocol("HTTP").SetEndpoint(fcEndpoint)) 85 | } else { 86 | FuncManagerGlobal.fcClient, err = fc.NewClient(new(openapi.Config).SetAccessKeyId(config.ConfigGlobal.AccessKeyId). 87 | SetAccessKeySecret(config.ConfigGlobal.AccessKeySecret).SetSecurityToken(config.ConfigGlobal.AccessKeyToken). 88 | SetProtocol("HTTP").SetEndpoint(fcEndpoint)) 89 | } 90 | 91 | if err != nil { 92 | return err 93 | } 94 | if funcStore != nil { 95 | // load func endpoint to cache 96 | FuncManagerGlobal.loadFunc() 97 | //FuncManagerGlobal.checkDbAndFcMatch() 98 | } 99 | return nil 100 | } 101 | 102 | // check ots table function list match fc function or not 103 | func (f *FuncManager) checkDbAndFcMatch() { 104 | for sdModel, _ := range f.endpoints { 105 | functionName := GetFunctionName(sdModel) 106 | if f.GetFcFunc(functionName) == nil { 107 | logrus.Errorf("sdModel:%s function in db, not in FC, auto delete ots table fucntion key=%s", 108 | sdModel, sdModel) 109 | // function in db not in FC 110 | f.funcStore.Delete(sdModel) 111 | } 112 | } 113 | } 114 | 115 | // GetLastInvokeEndpoint get last invoke endpoint 116 | func (f *FuncManager) GetLastInvokeEndpoint(sdModel *string) string { 117 | f.lock.RLock() 118 | defer f.lock.RUnlock() 119 | if sdModel == nil || *sdModel == "" { 120 | return f.lastInvokeEndpoint 121 | } else if endpoint := f.getEndpointFromCache(*sdModel); endpoint != "" { 122 | f.lastInvokeEndpoint = endpoint 123 | return endpoint 124 | } 125 | return f.lastInvokeEndpoint 126 | } 127 | 128 | // GetEndpoint get endpoint, key=sdModel 129 | // retry and read from db if create function fail 130 | // first get from cache 131 | // second get from db 132 | // third create function and return endpoint 133 | func (f *FuncManager) GetEndpoint(sdModel string) (string, error) { 134 | key := "default" 135 | if config.ConfigGlobal.GetFlexMode() == config.MultiFunc && sdModel != "" { 136 | key = sdModel 137 | } 138 | var err error 139 | endpoint := "" 140 | // retry 141 | reTry := 2 142 | for reTry > 0 { 143 | // first get cache 144 | if endpoint = f.getEndpointFromCache(key); endpoint != "" { 145 | f.lastInvokeEndpoint = endpoint 146 | return endpoint, nil 147 | } 148 | 149 | f.lock.Lock() 150 | // second get from db 151 | if endpoint, err = f.getEndpointFromDb(key); endpoint != "" { 152 | f.lastInvokeEndpoint = endpoint 153 | f.lock.Unlock() 154 | return endpoint, nil 155 | } 156 | // third create function 157 | if endpoint, err = f.createFunc(key, sdModel, getEnv(sdModel)); endpoint != "" { 158 | f.lastInvokeEndpoint = endpoint 159 | f.lock.Unlock() 160 | return endpoint, nil 161 | } 162 | // four create fail get function 163 | functionName := GetFunctionName(sdModel) 164 | if f.GetFcFunc(functionName) != nil { 165 | if endpoint = GetHttpTrigger(functionName); endpoint != "" { 166 | f.lastInvokeEndpoint = endpoint 167 | f.endpoints[key] = []string{endpoint, sdModel} 168 | logrus.Warnf("function %s sdModel %s in FC not in db, please check。Solution:del %s in FC", 169 | functionName, sdModel, functionName) 170 | f.lock.Unlock() 171 | return endpoint, nil 172 | } 173 | } 174 | f.lock.Unlock() 175 | reTry-- 176 | time.Sleep(RETRY_INTERVALMS) 177 | } 178 | return "", err 179 | } 180 | 181 | // UpdateAllFunctionEnv update instance env, restart agent function 182 | func (f *FuncManager) UpdateAllFunctionEnv() error { 183 | // reload from db 184 | f.lock.Lock() 185 | f.loadFunc() 186 | f.lock.Unlock() 187 | // update all function env 188 | for key, _ := range f.endpoints { 189 | if err := f.UpdateFunctionEnv(key); err != nil { 190 | return err 191 | } 192 | } 193 | return nil 194 | } 195 | 196 | // UpdateFunctionEnv update instance env 197 | // input modelName and env 198 | func (f *FuncManager) UpdateFunctionEnv(key string) error { 199 | functionName := GetFunctionName(key) 200 | res := f.GetFuncResource(functionName) 201 | if res == nil { 202 | return nil 203 | } 204 | res.Env[config.MODEL_REFRESH_SIGNAL] = utils.String(fmt.Sprintf("%d", utils.TimestampS())) // value = now timestamp 205 | //compatible fc3.0 206 | if isFc3() { 207 | if _, err := f.fc3Client.UpdateFunction(&functionName, 208 | new(fc3.UpdateFunctionRequest).SetRequest(new(fc3.UpdateFunctionInput).SetRuntime("custom-container"). 209 | SetEnvironmentVariables(res.Env).SetGpuConfig(new(fc3.GPUConfig). 210 | SetGpuMemorySize(res.GpuMemorySize).SetGpuType(res.InstanceType)))); err != nil { 211 | logrus.Info(err.Error()) 212 | return err 213 | } 214 | } else { 215 | if _, err := f.fcClient.UpdateFunction(&config.ConfigGlobal.ServiceName, &functionName, 216 | new(fc.UpdateFunctionRequest).SetRuntime("custom-container").SetGpuMemorySize(res.GpuMemorySize). 217 | SetEnvironmentVariables(res.Env)); err != nil { 218 | logrus.Info(err.Error()) 219 | return err 220 | } 221 | } 222 | return nil 223 | } 224 | 225 | // UpdateFunctionResource update function resource 226 | func (f *FuncManager) UpdateFunctionResource(resources map[string]*FuncResource) ([]string, []string, []string) { 227 | success := make([]string, 0, len(resources)) 228 | fail := make([]string, 0, len(resources)) 229 | errs := make([]string, 0, len(resources)) 230 | for key, resource := range resources { 231 | functionName := GetFunctionName(key) 232 | if isFc3() { 233 | if _, err := f.fc3Client.UpdateFunction(&functionName, getFC3UpdateFunctionRequest(resource)); err != nil { 234 | fail = append(fail, functionName) 235 | errs = append(errs, err.Error()) 236 | 237 | } else { 238 | success = append(success, key) 239 | } 240 | } else { 241 | if _, err := f.fcClient.UpdateFunction(&config.ConfigGlobal.ServiceName, &functionName, 242 | new(fc.UpdateFunctionRequest).SetRuntime("custom-container").SetGpuMemorySize(resource.GpuMemorySize). 243 | SetMemorySize(resource.MemorySize).SetCpu(resource.CPU).SetInstanceType(resource.InstanceType). 244 | SetTimeout(resource.Timeout).SetCustomContainerConfig(new(fc.CustomContainerConfig). 245 | SetImage(resource.Image)).SetEnvironmentVariables(resource.Env)); err != nil { 246 | fail = append(fail, functionName) 247 | errs = append(errs, err.Error()) 248 | 249 | } else { 250 | success = append(success, key) 251 | } 252 | } 253 | } 254 | return success, fail, errs 255 | } 256 | 257 | // DeleteFunction delete function 258 | func (f *FuncManager) DeleteFunction(functions []string) ([]string, []string) { 259 | if isFc3() { 260 | return f.delFunctionFC3(functions) 261 | } else { 262 | return f.delFunction(functions) 263 | } 264 | } 265 | 266 | // get endpoint from cache 267 | func (f *FuncManager) getEndpointFromCache(key string) string { 268 | f.lock.RLock() 269 | defer f.lock.RUnlock() 270 | if val, ok := f.endpoints[key]; ok { 271 | return val[0] 272 | } 273 | return "" 274 | } 275 | 276 | // get endpoint from db 277 | func (f *FuncManager) getEndpointFromDb(key string) (string, error) { 278 | if data, err := f.funcStore.Get(key, []string{datastore.KModelServiceSdModel, 279 | datastore.KModelServiceEndPoint}); err == nil && len(data) > 0 { 280 | // update cache 281 | f.endpoints[key] = []string{data[datastore.KModelServiceEndPoint].(string), 282 | data[datastore.KModelServiceSdModel].(string)} 283 | return data[datastore.KModelServiceEndPoint].(string), nil 284 | } else { 285 | return "", err 286 | } 287 | } 288 | 289 | func (f *FuncManager) createFunc(key, sdModel string, env map[string]*string) (string, error) { 290 | functionName := GetFunctionName(key) 291 | var endpoint string 292 | var err error 293 | if isFc3() { 294 | endpoint, err = f.createFc3Function(functionName, env) 295 | } else { 296 | serviceName := config.ConfigGlobal.ServiceName 297 | endpoint, err = f.createFCFunction(serviceName, functionName, env) 298 | } 299 | if err == nil && endpoint != "" { 300 | // update cache 301 | f.endpoints[key] = []string{endpoint, sdModel} 302 | // put func to db 303 | f.putFunc(key, functionName, sdModel, endpoint) 304 | return endpoint, nil 305 | } else { 306 | logrus.Info(err.Error()) 307 | return "", err 308 | } 309 | } 310 | 311 | // GetFcFuncEnv get fc function env info 312 | func (f *FuncManager) GetFcFuncEnv(functionName string) *map[string]*string { 313 | if funcBody := f.GetFcFunc(functionName); funcBody != nil { 314 | switch funcBody.(type) { 315 | case *fc.GetFunctionResponse: 316 | return &funcBody.(*fc.GetFunctionResponse).Body.EnvironmentVariables 317 | case *fc3.GetFunctionResponse: 318 | return &funcBody.(*fc3.GetFunctionResponse).Body.EnvironmentVariables 319 | } 320 | } 321 | return nil 322 | } 323 | 324 | func (f *FuncManager) GetFuncResource(functionName string) *FuncResource { 325 | if funcBody := f.GetFcFunc(functionName); funcBody != nil { 326 | switch funcBody.(type) { 327 | case *fc.GetFunctionResponse: 328 | info := funcBody.(*fc.GetFunctionResponse) 329 | return &FuncResource{ 330 | Image: *info.Body.CustomContainerConfig.Image, 331 | CPU: *info.Body.Cpu, 332 | MemorySize: *info.Body.MemorySize, 333 | GpuMemorySize: *info.Body.GpuMemorySize, 334 | Timeout: *info.Body.Timeout, 335 | InstanceType: *info.Body.InstanceType, 336 | Env: info.Body.EnvironmentVariables, 337 | } 338 | case *fc3.GetFunctionResponse: 339 | info := funcBody.(*fc3.GetFunctionResponse) 340 | return &FuncResource{ 341 | Image: *info.Body.CustomContainerConfig.Image, 342 | CPU: *info.Body.Cpu, 343 | MemorySize: *info.Body.MemorySize, 344 | GpuMemorySize: *info.Body.GpuConfig.GpuMemorySize, 345 | Timeout: *info.Body.Timeout, 346 | InstanceType: *info.Body.GpuConfig.GpuType, 347 | Env: info.Body.EnvironmentVariables, 348 | } 349 | } 350 | } 351 | return nil 352 | } 353 | 354 | // GetFcFunc get fc function info 355 | func (f *FuncManager) GetFcFunc(functionName string) interface{} { 356 | if isFc3() { 357 | if resp, err := f.fc3Client.GetFunction(&functionName, &fc3.GetFunctionRequest{}); err == nil { 358 | return resp 359 | } 360 | } else { 361 | serviceName := config.ConfigGlobal.ServiceName 362 | if resp, err := f.fcClient.GetFunction(&serviceName, &functionName, &fc.GetFunctionRequest{}); err == nil { 363 | return resp 364 | } 365 | } 366 | return nil 367 | } 368 | 369 | // load endpoint from db 370 | func (f *FuncManager) loadFunc() { 371 | // load func from db 372 | funcAll, _ := f.funcStore.ListAll([]string{datastore.KModelServiceKey, datastore.KModelServiceEndPoint, 373 | datastore.KModelServiceSdModel, datastore.KModelServerImage}) 374 | for _, data := range funcAll { 375 | key := data[datastore.KModelServiceKey].(string) 376 | sdModel := data[datastore.KModelServiceSdModel].(string) 377 | // check fc && db match 378 | functionName := GetFunctionName(sdModel) 379 | if f.GetFcFunc(functionName) == nil { 380 | logrus.Errorf("functionName:%s, sdModel:%s function in db, not in FC, please delete ots table fucntion "+ 381 | "key=%s", functionName, sdModel, sdModel) 382 | // function in db not in FC, del ots data 383 | //f.funcStore.Delete(sdModel) 384 | continue 385 | } 386 | //image := data[datastore.KModelServerImage].(string) 387 | //if image != "" && config.ConfigGlobal.Image != "" && 388 | // image != config.ConfigGlobal.Image { 389 | // // update function image 390 | // if err := f.UpdateFunctionImage(key); err != nil { 391 | // logrus.Info("update function image err=", err.Error()) 392 | // } 393 | // // update db 394 | // f.funcStore.Update(key, map[string]interface{}{ 395 | // datastore.KModelServerImage: config.ConfigGlobal.Image, 396 | // datastore.KModelModifyTime: fmt.Sprintf("%d", utils.TimestampS()), 397 | // }) 398 | //} 399 | endpoint := data[datastore.KModelServiceEndPoint].(string) 400 | // init lastInvokeEndpoint 401 | if f.lastInvokeEndpoint == "" { 402 | f.lastInvokeEndpoint = endpoint 403 | } 404 | f.endpoints[key] = []string{endpoint, sdModel} 405 | } 406 | } 407 | 408 | // write func into db 409 | func (f *FuncManager) putFunc(key, functionName, sdModel, endpoint string) { 410 | f.funcStore.Put(key, map[string]interface{}{ 411 | datastore.KModelServiceKey: key, 412 | datastore.KModelServiceSdModel: sdModel, 413 | datastore.KModelServiceFunctionName: functionName, 414 | datastore.KModelServiceEndPoint: endpoint, 415 | datastore.KModelServiceCreateTime: fmt.Sprintf("%d", utils.TimestampS()), 416 | datastore.KModelServiceLastModifyTime: fmt.Sprintf("%d", utils.TimestampS()), 417 | }) 418 | } 419 | 420 | func (f *FuncManager) GetSd() *fcUtils.Function { 421 | return f.ListFunction().StableDiffusion 422 | } 423 | 424 | func (f *FuncManager) GetFileMgr() *fcUtils.Function { 425 | return f.ListFunction().Filemgr 426 | } 427 | 428 | func (f *FuncManager) ListFunction() *project.T { 429 | ctx := &gr.FCContext{ 430 | Credentials: gr.Credentials{ 431 | AccessKeyID: config.ConfigGlobal.AccessKeyId, 432 | AccessKeySecret: config.ConfigGlobal.AccessKeySecret, 433 | SecurityToken: config.ConfigGlobal.AccessKeyToken, 434 | }, 435 | Region: config.ConfigGlobal.Region, 436 | AccountID: config.ConfigGlobal.AccountId, 437 | Service: gr.ServiceMeta{ 438 | ServiceName: config.ConfigGlobal.ServiceName, 439 | }, 440 | Function: gr.FunctionMeta{ 441 | Name: config.ConfigGlobal.FunctionName, 442 | }, 443 | } 444 | functions := project.Get(ctx) 445 | return &functions 446 | } 447 | 448 | func GetHttpTrigger(functionName string) string { 449 | if isFc3() { 450 | if result, err := FuncManagerGlobal.fc3Client.ListTriggers(&functionName, new(fc3.ListTriggersRequest)); err == nil { 451 | for _, trigger := range result.Body.Triggers { 452 | if trigger.HttpTrigger != nil { 453 | return *trigger.HttpTrigger.UrlIntranet 454 | } 455 | } 456 | } 457 | } else { 458 | if result, err := FuncManagerGlobal.fcClient.ListTriggers(&config.ConfigGlobal.ServiceName, 459 | &functionName, new(fc.ListTriggersRequest)); err == nil { 460 | for _, trigger := range result.Body.Triggers { 461 | if trigger.UrlInternet != nil { 462 | return *trigger.UrlIntranet 463 | } 464 | } 465 | } 466 | } 467 | return "" 468 | } 469 | 470 | // ---------fc2.0---------- 471 | // create fc function 472 | func (f *FuncManager) createFCFunction(serviceName, functionName string, 473 | env map[string]*string) (endpoint string, err error) { 474 | createRequest := getCreateFuncRequest(functionName, env) 475 | header := &fc.CreateFunctionHeaders{ 476 | XFcAccountId: utils.String(config.ConfigGlobal.AccountId), 477 | } 478 | // create function 479 | if _, err := f.fcClient.CreateFunctionWithOptions(&serviceName, createRequest, 480 | header, &fcService.RuntimeOptions{}); err != nil { 481 | return "", err 482 | } 483 | // create http triggers 484 | httpTriggerRequest := getHttpTrigger() 485 | resp, err := f.fcClient.CreateTrigger(&serviceName, &functionName, httpTriggerRequest) 486 | if err != nil { 487 | return "", err 488 | } 489 | return *resp.Body.UrlIntranet, nil 490 | } 491 | 492 | // get create function request 493 | func getCreateFuncRequest(functionName string, env map[string]*string) *fc.CreateFunctionRequest { 494 | defaultReq := &fc.CreateFunctionRequest{ 495 | FunctionName: utils.String(functionName), 496 | CaPort: utils.Int32(config.ConfigGlobal.CAPort), 497 | Cpu: utils.Float32(config.ConfigGlobal.CPU), 498 | Timeout: utils.Int32(config.ConfigGlobal.Timeout), 499 | InstanceType: utils.String(config.ConfigGlobal.InstanceType), 500 | Runtime: utils.String("custom-container"), 501 | InstanceConcurrency: utils.Int32(config.ConfigGlobal.InstanceConcurrency), 502 | MemorySize: utils.Int32(config.ConfigGlobal.MemorySize), 503 | DiskSize: utils.Int32(config.ConfigGlobal.DiskSize), 504 | Handler: utils.String("index.handler"), 505 | GpuMemorySize: utils.Int32(config.ConfigGlobal.GpuMemorySize), 506 | EnvironmentVariables: env, 507 | CustomContainerConfig: &fc.CustomContainerConfig{ 508 | AccelerationType: utils.String("Default"), 509 | Image: utils.String(config.ConfigGlobal.Image), 510 | WebServerMode: utils.Bool(true), 511 | }, 512 | } 513 | if sd := FuncManagerGlobal.GetSd(); sd != nil { 514 | if config.ConfigGlobal.Image == "" { 515 | defaultReq.CustomContainerConfig.Image = sd.CustomContainerConfig.Image 516 | } 517 | defaultReq.CustomContainerConfig.Command = func() *string { 518 | if sd.CustomContainerConfig.Entrypoint != nil && len(sd.CustomContainerConfig.Entrypoint) > 0 { 519 | return sd.CustomContainerConfig.Entrypoint[0] 520 | } 521 | return nil 522 | }() 523 | defaultReq.CustomContainerConfig.Args = func() *string { 524 | if sd.CustomContainerConfig.Command != nil && len(sd.CustomContainerConfig.Command) > 0 { 525 | return sd.CustomContainerConfig.Command[0] 526 | } 527 | return nil 528 | }() 529 | if sd.EnvironmentVariables != nil { 530 | allEnv := make(map[string]*string) 531 | for k, v := range sd.EnvironmentVariables { 532 | allEnv[k] = v 533 | } 534 | for k, v := range defaultReq.EnvironmentVariables { 535 | allEnv[k] = v 536 | } 537 | defaultReq.EnvironmentVariables = allEnv 538 | } 539 | } 540 | return defaultReq 541 | } 542 | 543 | func (f *FuncManager) delFunction(functionNames []string) (fails []string, errs []string) { 544 | func2Model := make(map[string]string) 545 | funcs, err := f.funcStore.ListAll([]string{datastore.KModelServiceFunctionName}) 546 | if err == nil && funcs != nil { 547 | for model, data := range funcs { 548 | func2Model[data[datastore.KModelServiceFunctionName].(string)] = model 549 | } 550 | } 551 | 552 | for _, functionName := range functionNames { 553 | if modelName, exist := func2Model[functionName]; exist { 554 | if err := f.funcStore.Delete(modelName); err != nil { 555 | logrus.Warnf("%s delete fail, err: %s", functionName, err.Error()) 556 | fails = append(fails, functionName) 557 | errs = append(errs, err.Error()) 558 | } 559 | } 560 | 561 | if _, err := f.fcClient.DeleteTrigger(&config.ConfigGlobal.ServiceName, &functionName, utils.String(config.TRIGGER_NAME)); err != nil { 562 | logrus.Warnf("%s delete fail, err: %s", functionName, err.Error()) 563 | fails = append(fails, functionName) 564 | errs = append(errs, err.Error()) 565 | continue 566 | } 567 | if _, err := f.fcClient.DeleteFunction(&config.ConfigGlobal.ServiceName, &functionName); err != nil { 568 | logrus.Warnf("%s delete fail, err: %s", functionName, err.Error()) 569 | fails = append(fails, functionName) 570 | errs = append(errs, err.Error()) 571 | } 572 | } 573 | return 574 | } 575 | 576 | // get trigger request 577 | func getHttpTrigger() *fc.CreateTriggerRequest { 578 | triggerConfig := make(map[string]interface{}) 579 | triggerConfig["authType"] = config.AUTH_TYPE 580 | triggerConfig["methods"] = []string{config.HTTP_GET, config.HTTP_POST, config.HTTP_PUT} 581 | triggerConfig["disableURLInternet"] = true 582 | byteConfig, _ := json.Marshal(triggerConfig) 583 | return &fc.CreateTriggerRequest{ 584 | TriggerName: utils.String(config.TRIGGER_NAME), 585 | TriggerType: utils.String(config.TRIGGER_TYPE), 586 | TriggerConfig: utils.String(string(byteConfig)), 587 | } 588 | } 589 | 590 | // ------------end fc2.0---------- 591 | 592 | // --------------fc3.0-------------- 593 | func (f *FuncManager) createFc3Function(functionName string, 594 | env map[string]*string) (endpoint string, err error) { 595 | createRequest := f.getCreateFuncRequestFc3(functionName, env) 596 | if createRequest == nil { 597 | return "", errors.New("get createFunctionRequest error") 598 | } 599 | // create function 600 | if _, err := f.fc3Client.CreateFunction(createRequest); err != nil { 601 | return "", err 602 | } 603 | // create http triggers 604 | httpTriggerRequest := getHttpTriggerFc3() 605 | resp, err := f.fc3Client.CreateTrigger(&functionName, httpTriggerRequest) 606 | if err != nil { 607 | return "", err 608 | } 609 | 610 | return *resp.Body.HttpTrigger.UrlIntranet, nil 611 | } 612 | 613 | // fc3.0 get create function request 614 | func (f *FuncManager) getCreateFuncRequestFc3(functionName string, env map[string]*string) *fc3.CreateFunctionRequest { 615 | // get current function 616 | function := f.GetFcFunc(config.ConfigGlobal.FunctionName) 617 | if function == nil { 618 | return nil 619 | } 620 | curFunction := function.(*fc3.GetFunctionResponse) 621 | input := &fc3.CreateFunctionInput{ 622 | FunctionName: utils.String(functionName), 623 | Cpu: utils.Float32(config.ConfigGlobal.CPU), 624 | Timeout: utils.Int32(config.ConfigGlobal.Timeout), 625 | Runtime: utils.String("custom-container"), 626 | InstanceConcurrency: utils.Int32(config.ConfigGlobal.InstanceConcurrency), 627 | MemorySize: utils.Int32(config.ConfigGlobal.MemorySize), 628 | DiskSize: utils.Int32(config.ConfigGlobal.DiskSize), 629 | EnvironmentVariables: env, 630 | Handler: utils.String("index.handler"), 631 | CustomContainerConfig: &fc3.CustomContainerConfig{ 632 | AccelerationType: utils.String("Default"), 633 | Image: utils.String(config.ConfigGlobal.Image), 634 | Port: utils.Int32(config.ConfigGlobal.CAPort), 635 | }, 636 | GpuConfig: &fc3.GPUConfig{ 637 | GpuMemorySize: utils.Int32(config.ConfigGlobal.GpuMemorySize), 638 | GpuType: utils.String(config.ConfigGlobal.InstanceType), 639 | }, 640 | Role: curFunction.Body.Role, 641 | VpcConfig: curFunction.Body.VpcConfig, 642 | NasConfig: curFunction.Body.NasConfig, 643 | OssMountConfig: curFunction.Body.OssMountConfig, 644 | } 645 | if sd := FuncManagerGlobal.GetSd(); sd != nil { 646 | if config.ConfigGlobal.Image == "" { 647 | input.CustomContainerConfig.Image = sd.CustomContainerConfig.Image 648 | } 649 | if sd.CustomContainerConfig.Entrypoint != nil && len(sd.CustomContainerConfig.Entrypoint) > 0 { 650 | input.CustomContainerConfig.Entrypoint = sd.CustomContainerConfig.Entrypoint 651 | } 652 | if sd.CustomContainerConfig.Command != nil && len(sd.CustomContainerConfig.Command) > 0 { 653 | input.CustomContainerConfig.Command = sd.CustomContainerConfig.Command 654 | } 655 | if sd.EnvironmentVariables != nil { 656 | allEnv := make(map[string]*string) 657 | for k, v := range sd.EnvironmentVariables { 658 | allEnv[k] = v 659 | } 660 | for k, v := range input.EnvironmentVariables { 661 | allEnv[k] = v 662 | } 663 | input.EnvironmentVariables = allEnv 664 | } 665 | } 666 | return &fc3.CreateFunctionRequest{ 667 | Request: input, 668 | } 669 | } 670 | 671 | // delete function 672 | func (f *FuncManager) delFunctionFC3(functionNames []string) (fails []string, errs []string) { 673 | func2Model := make(map[string]string) 674 | funcs, err := f.funcStore.ListAll([]string{datastore.KModelServiceFunctionName}) 675 | if err == nil && funcs != nil { 676 | for model, data := range funcs { 677 | func2Model[data[datastore.KModelServiceFunctionName].(string)] = model 678 | } 679 | } 680 | 681 | for _, functionName := range functionNames { 682 | if modelName, exist := func2Model[functionName]; exist { 683 | if err := f.funcStore.Delete(modelName); err != nil { 684 | logrus.Warnf("%s delete fail, err: %s", functionName, err.Error()) 685 | fails = append(fails, functionName) 686 | errs = append(errs, err.Error()) 687 | } 688 | } 689 | 690 | if _, err := f.fc3Client.DeleteTrigger(&functionName, utils.String(config.TRIGGER_NAME)); err != nil { 691 | logrus.Warnf("%s delete fail, err: %s", functionName, err.Error()) 692 | fails = append(fails, functionName) 693 | errs = append(errs, err.Error()) 694 | continue 695 | } 696 | if _, err := f.fc3Client.DeleteFunction(&functionName); err != nil { 697 | logrus.Warnf("%s delete fail, err: %s", functionName, err.Error()) 698 | fails = append(fails, functionName) 699 | errs = append(errs, err.Error()) 700 | } 701 | } 702 | return 703 | } 704 | 705 | // get trigger request 706 | func getHttpTriggerFc3() *fc3.CreateTriggerRequest { 707 | triggerConfig := make(map[string]interface{}) 708 | triggerConfig["authType"] = config.AUTH_TYPE 709 | triggerConfig["methods"] = []string{config.HTTP_GET, config.HTTP_POST, config.HTTP_PUT} 710 | triggerConfig["disableURLInternet"] = true 711 | byteConfig, _ := json.Marshal(triggerConfig) 712 | input := &fc3.CreateTriggerInput{ 713 | TriggerName: utils.String(config.TRIGGER_NAME), 714 | TriggerType: utils.String(config.TRIGGER_TYPE), 715 | TriggerConfig: utils.String(string(byteConfig)), 716 | } 717 | return &fc3.CreateTriggerRequest{ 718 | Request: input, 719 | } 720 | } 721 | 722 | // ----------end fc3----------- 723 | 724 | // GetFunctionName hash key, avoid generating invalid characters 725 | func GetFunctionName(key string) string { 726 | return fmt.Sprintf("%ssd_%s", FuncManagerGlobal.prefix, utils.Hash(key)) 727 | } 728 | 729 | func getEnv(sdModel string) map[string]*string { 730 | env := map[string]*string{ 731 | config.SD_START_PARAMS: utils.String(config.ConfigGlobal.ExtraArgs), 732 | config.MODEL_SD: utils.String(sdModel), 733 | config.MODEL_REFRESH_SIGNAL: utils.String(fmt.Sprintf("%d", utils.TimestampS())), // value = now timestamp 734 | config.OTS_INSTANCE: utils.String(config.ConfigGlobal.OtsInstanceName), 735 | config.OTS_ENDPOINT: utils.String(config.ConfigGlobal.OtsEndpoint), 736 | } 737 | if config.ConfigGlobal.OssMode == config.REMOTE { 738 | env[config.OSS_ENDPOINT] = utils.String(config.ConfigGlobal.OssEndpoint) 739 | env[config.OSS_BUCKET] = utils.String(config.ConfigGlobal.Bucket) 740 | } 741 | return env 742 | } 743 | 744 | func getFC3UpdateFunctionRequest(resource *FuncResource) *fc3.UpdateFunctionRequest { 745 | req := new(fc3.UpdateFunctionInput).SetRuntime("custom-container"). 746 | SetMemorySize(resource.MemorySize).SetCpu(resource.CPU).SetGpuConfig(new(fc3.GPUConfig). 747 | SetGpuType(resource.InstanceType).SetGpuMemorySize(resource.GpuMemorySize)). 748 | SetTimeout(resource.Timeout).SetCustomContainerConfig(new(fc3.CustomContainerConfig). 749 | SetImage(resource.Image)).SetEnvironmentVariables(resource.Env) 750 | if resource.VpcConfig != nil { 751 | vpcConfig := &fc3.VPCConfig{} 752 | if err := utils.MapToStruct(*resource.VpcConfig, vpcConfig); err == nil { 753 | req.SetVpcConfig(vpcConfig) 754 | } 755 | } 756 | if resource.NasConfig != nil { 757 | nasConfig := &fc3.NASConfig{} 758 | if err := utils.MapToStruct(*resource.NasConfig, nasConfig); err == nil { 759 | req.SetNasConfig(nasConfig) 760 | } 761 | } 762 | if resource.OssMountConfig != nil { 763 | ossConfig := &fc3.OSSMountConfig{} 764 | if err := utils.MapToStruct(*resource.OssMountConfig, ossConfig); err == nil { 765 | req.SetOssMountConfig(ossConfig) 766 | } 767 | } 768 | return new(fc3.UpdateFunctionRequest).SetRequest(req) 769 | } 770 | -------------------------------------------------------------------------------- /pkg/module/function_test.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 5 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/utils" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func TestFunction(t *testing.T) { 11 | config.InitConfig("") 12 | err := InitFuncManager(nil) 13 | assert.Nil(t, err) 14 | functionName := "sd-test" 15 | env := map[string]*string{ 16 | "EXTRA_ARGS": utils.String("--api"), 17 | } 18 | endpoint, err := FuncManagerGlobal.createFCFunction(config.ConfigGlobal.ServiceName, functionName, env) 19 | assert.Nil(t, err) 20 | assert.NotEqual(t, endpoint, "") 21 | } 22 | -------------------------------------------------------------------------------- /pkg/module/listen.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 8 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/datastore" 9 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/utils" 10 | "github.com/sirupsen/logrus" 11 | "io" 12 | "io/ioutil" 13 | "net/http" 14 | "strings" 15 | "sync" 16 | "time" 17 | ) 18 | 19 | type CallBack func(v any) 20 | 21 | type ListenType int32 22 | 23 | const ( 24 | CancelListen ListenType = iota 25 | ModelListen 26 | ConfigListen 27 | ) 28 | 29 | const ( 30 | ConfigDefaultKey = "default" 31 | ) 32 | 33 | // config change signal 34 | type configSignal struct { 35 | configStore datastore.Datastore 36 | md5 string 37 | config string 38 | } 39 | 40 | type TaskItem struct { 41 | listenType ListenType 42 | callBack CallBack 43 | curVal any 44 | } 45 | 46 | // ListenDbTask listen db value change and call callback func 47 | // for example: tasks cancel signal and models register/update 48 | type ListenDbTask struct { 49 | taskStore datastore.Datastore 50 | modelStore datastore.Datastore 51 | configStore datastore.Datastore 52 | intervalSecond int32 53 | tasks *sync.Map 54 | stop chan struct{} 55 | } 56 | 57 | func NewListenDbTask(intervalSecond int32, taskStore datastore.Datastore, 58 | modelStore datastore.Datastore, configStore datastore.Datastore) *ListenDbTask { 59 | listenTask := &ListenDbTask{ 60 | taskStore: taskStore, 61 | modelStore: modelStore, 62 | configStore: configStore, 63 | intervalSecond: intervalSecond, 64 | tasks: new(sync.Map), 65 | stop: make(chan struct{}), 66 | } 67 | go listenTask.init() 68 | return listenTask 69 | } 70 | 71 | // init listen 72 | func (l *ListenDbTask) init() { 73 | for { 74 | select { 75 | case <-l.stop: 76 | break 77 | default: 78 | // go on next 79 | } 80 | l.tasks.Range(func(key, value any) bool { 81 | taskId := key.(string) 82 | taskItem := value.(*TaskItem) 83 | switch taskItem.listenType { 84 | case CancelListen: 85 | l.cancelTask(taskId, taskItem) 86 | case ModelListen: 87 | l.modelTask(taskItem) 88 | case ConfigListen: 89 | l.configTask(taskItem) 90 | } 91 | return true 92 | }) 93 | time.Sleep(time.Duration(l.intervalSecond) * time.Second) 94 | } 95 | } 96 | 97 | // listen config 98 | func (l *ListenDbTask) configTask(item *TaskItem) { 99 | confSignal := item.curVal.(*configSignal) 100 | md5Old := confSignal.md5 101 | configOld := confSignal.config 102 | // read config from disk 103 | configPath := fmt.Sprintf("%s/%s", config.ConfigGlobal.SdPath, SD_CONFIG) 104 | md5New := "" 105 | if !utils.FileExists(configPath) { 106 | logrus.Infof("[configTask] file %s not exist", configPath) 107 | } else { 108 | md5New, _ = utils.FileMD5(configPath) 109 | } 110 | // diff 111 | if md5New != "" && md5New != md5Old { 112 | configNew, _ := ioutil.ReadFile(configPath) 113 | // diff config content 114 | if configOld == "" || !isSameConfig([]byte(configOld), configNew) { 115 | logrus.Info("[configTask] update config") 116 | // update success 117 | if err := updateConfig(configNew, md5New, l.configStore); err == nil { 118 | confSignal.md5 = md5New 119 | confSignal.config = string(configNew) 120 | // change, call callback and update db 121 | item.callBack(nil) 122 | } 123 | } 124 | } 125 | } 126 | 127 | // listen model task 128 | func (l *ListenDbTask) modelTask(item *TaskItem) { 129 | // controlNet 130 | oldVal := *(item.curVal.(*map[string]struct{})) 131 | path := fmt.Sprintf("%s/models/%s", config.ConfigGlobal.SdPath, "ControlNet") 132 | curVal := listModelFile(path) 133 | add, del := utils.DiffSet(oldVal, curVal) 134 | if len(add) != 0 || len(del) != 0 { 135 | logrus.Infof("[modelTask] controlnet model change add: %s, del: %s", 136 | strings.Join(add, ","), strings.Join(del, ",")) 137 | item.curVal = &curVal 138 | item.callBack(config.CONTORLNET_MODEL) 139 | } 140 | // vae 141 | if oldVal, err := getVaeFromSD(); err == nil { 142 | path := fmt.Sprintf("%s/models/%s", config.ConfigGlobal.SdPath, "VAE") 143 | curVal := listModelFile(path) 144 | add, del := utils.DiffSet(oldVal, curVal) 145 | if len(add) != 0 || len(del) != 0 { 146 | logrus.Infof("[modelTask] vae model change add: %s, del: %s", 147 | strings.Join(add, ","), strings.Join(del, ",")) 148 | item.callBack(config.SD_VAE) 149 | } 150 | } 151 | // checkpoint 152 | if oldVal, err := getCheckPointFromSD(); err == nil { 153 | path := fmt.Sprintf("%s/models/%s", config.ConfigGlobal.SdPath, "Stable-diffusion") 154 | curVal := listModelFile(path) 155 | add, del := utils.DiffSet(oldVal, curVal) 156 | if len(add) != 0 || len(del) != 0 { 157 | logrus.Infof("[modelTask] Stable-diffusion model change add: %s, del: %s", 158 | strings.Join(add, ","), strings.Join(del, ",")) 159 | item.callBack(config.SD_MODEL) 160 | } 161 | } 162 | } 163 | 164 | // listen task cancel 165 | func (l *ListenDbTask) cancelTask(taskId string, item *TaskItem) { 166 | ret, err := l.taskStore.Get(taskId, []string{datastore.KTaskCancel, datastore.KTaskStatus}) 167 | if err != nil { 168 | l.tasks.Delete(taskId) 169 | return 170 | } 171 | // check task finish delete db listen task 172 | status := ret[datastore.KTaskStatus].(string) 173 | if status == config.TASK_FINISH || status == config.TASK_FAILED { 174 | l.tasks.Delete(taskId) 175 | return 176 | } 177 | // cancel val == 1 178 | cancelVal := ret[datastore.KTaskCancel].(int64) 179 | if cancelVal == int64(config.CANCEL_VALID) { 180 | item.callBack(nil) 181 | l.tasks.Delete(taskId) 182 | return 183 | } 184 | 185 | } 186 | 187 | // AddTask add listen task 188 | func (l *ListenDbTask) AddTask(key string, listenType ListenType, callBack CallBack) { 189 | var curVal interface{} 190 | if listenType == ModelListen { 191 | // controlNet load and other type model not need 192 | path := fmt.Sprintf("%s/models/%s", config.ConfigGlobal.SdPath, "ControlNet") 193 | datas := listModelFile(path) 194 | val := make(map[string]struct{}) 195 | for data := range datas { 196 | modelName := data 197 | val[modelName] = struct{}{} 198 | } 199 | curVal = &val 200 | } else if listenType == ConfigListen { 201 | // read db 202 | data, err := l.configStore.Get(ConfigDefaultKey, []string{datastore.KConfigMd5, datastore.KConfigVal}) 203 | if err != nil { 204 | logrus.Fatal("[AddTask] read config db error:", err.Error()) 205 | } 206 | md5Old := "" 207 | configOld := "" 208 | if data != nil && len(data) > 0 { 209 | md5Old = data[datastore.KConfigMd5].(string) 210 | configOld = data[datastore.KConfigVal].(string) 211 | } 212 | md5New := "" 213 | configPath := fmt.Sprintf("%s/%s", config.ConfigGlobal.SdPath, SD_CONFIG) 214 | if !utils.FileExists(configPath) { 215 | logrus.Infof("file %s not exist", configPath) 216 | } else { 217 | md5New, _ = utils.FileMD5(configPath) 218 | } 219 | // diff 220 | if md5New != "" && md5New != md5Old { 221 | configNew, _ := ioutil.ReadFile(configPath) 222 | if configOld == "" { 223 | if err := putConfig(configNew, md5New, l.configStore); err == nil { 224 | logrus.Info("[AddTask] put config") 225 | md5Old = md5New 226 | configOld = string(configNew) 227 | } 228 | } else if !isSameConfig([]byte(configOld), configNew) { 229 | // diff config content 230 | logrus.Info("[AddTask] update config") 231 | // update success 232 | if err := updateConfig(configNew, md5New, l.configStore); err == nil { 233 | md5Old = md5New 234 | configOld = string(configNew) 235 | } 236 | } 237 | } 238 | 239 | curVal = &configSignal{ 240 | configStore: l.configStore, 241 | md5: md5Old, 242 | config: configOld, 243 | } 244 | 245 | } 246 | l.tasks.Store(key, &TaskItem{ 247 | listenType: listenType, 248 | callBack: callBack, 249 | curVal: curVal, 250 | }) 251 | } 252 | 253 | // Close listen 254 | func (l *ListenDbTask) Close() { 255 | l.stop <- struct{}{} 256 | } 257 | 258 | func listModelFile(path string) map[string]struct{} { 259 | files := utils.ListFile(path) 260 | ret := make(map[string]struct{}) 261 | for _, name := range files { 262 | if strings.HasSuffix(name, ".pt") || strings.HasSuffix(name, ".ckpt") || 263 | strings.HasSuffix(name, ".safetensors") || strings.HasSuffix(name, ".pth") { 264 | ret[name] = struct{}{} 265 | } 266 | } 267 | return ret 268 | } 269 | 270 | func updateConfig(data []byte, md5 string, configStore datastore.Datastore) error { 271 | // check data valid 272 | if !json.Valid(data) { 273 | logrus.Info("[updateConfig] config json not valid, please check") 274 | return errors.New("config not valid json") 275 | } 276 | if err := configStore.Update(ConfigDefaultKey, map[string]interface{}{ 277 | datastore.KConfigMd5: md5, 278 | datastore.KConfigVal: string(data), 279 | datastore.KConfigModifyTime: fmt.Sprintf("%d", utils.TimestampS()), 280 | }); err != nil { 281 | logrus.Info("[updateConfig] update db error") 282 | return err 283 | } 284 | return nil 285 | } 286 | 287 | func putConfig(data []byte, md5 string, configStore datastore.Datastore) error { 288 | // check data valid 289 | if !json.Valid(data) { 290 | logrus.Info("[putConfig] config json not valid, please check") 291 | return errors.New("config not valid json") 292 | } 293 | if err := configStore.Put(ConfigDefaultKey, map[string]interface{}{ 294 | datastore.KConfigMd5: md5, 295 | datastore.KConfigVal: string(data), 296 | datastore.KConfigModifyTime: fmt.Sprintf("%d", utils.TimestampS()), 297 | datastore.KConfigCreateTime: fmt.Sprintf("%d", utils.TimestampS()), 298 | }); err != nil { 299 | logrus.Info("[putConfig] put db error") 300 | return err 301 | } 302 | return nil 303 | } 304 | 305 | func getVaeFromSD() (map[string]struct{}, error) { 306 | url := fmt.Sprintf("%s%s", config.ConfigGlobal.SdUrlPrefix, config.GET_SD_VAE) 307 | req, _ := http.NewRequest("GET", url, nil) 308 | resp, err := client.Do(req) 309 | if err != nil { 310 | return nil, err 311 | } 312 | body, err := io.ReadAll(resp.Body) 313 | defer resp.Body.Close() 314 | if err != nil { 315 | return nil, err 316 | } 317 | var result []map[string]string 318 | if err := json.Unmarshal(body, &result); err != nil { 319 | return nil, err 320 | } 321 | ret := make(map[string]struct{}) 322 | for _, one := range result { 323 | k := one["model_name"] 324 | ret[k] = struct{}{} 325 | } 326 | return ret, nil 327 | } 328 | 329 | func getCheckPointFromSD() (map[string]struct{}, error) { 330 | url := fmt.Sprintf("%s%s", config.ConfigGlobal.SdUrlPrefix, config.GET_SD_MODEL) 331 | req, err := http.NewRequest("GET", url, nil) 332 | resp, err := client.Do(req) 333 | if err != nil { 334 | return nil, err 335 | } 336 | 337 | body, err := io.ReadAll(resp.Body) 338 | defer resp.Body.Close() 339 | if err != nil { 340 | return nil, err 341 | } 342 | var result []map[string]string 343 | if err := json.Unmarshal(body, &result); err != nil { 344 | return nil, err 345 | } 346 | ret := make(map[string]struct{}) 347 | for _, one := range result { 348 | k := one["title"] 349 | modelSlice := strings.Split(k, " ") 350 | ret[modelSlice[0]] = struct{}{} 351 | } 352 | return ret, nil 353 | } 354 | 355 | // check config old and new same 356 | func isSameConfig(old, new []byte) bool { 357 | notCheckField := map[string]struct{}{ 358 | "sd_checkpoint_hash": {}, 359 | "sd_model_checkpoint": {}, 360 | "sd_vae": {}, 361 | "upscaler_for_img2img": {}, 362 | } 363 | var oldMap map[string]interface{} 364 | if err := json.Unmarshal(old, &oldMap); err != nil { 365 | logrus.Info(err.Error()) 366 | return true 367 | } 368 | var newMap map[string]interface{} 369 | if err := json.Unmarshal(new, &newMap); err != nil { 370 | logrus.Info(err.Error()) 371 | return true 372 | } 373 | for key, val := range newMap { 374 | if _, ok := notCheckField[key]; ok { 375 | continue 376 | } 377 | if oldVal, ok := oldMap[key]; !ok { 378 | return false 379 | } else if !utils.IsSame(key, val, oldVal) { 380 | return false 381 | } 382 | } 383 | return true 384 | } 385 | -------------------------------------------------------------------------------- /pkg/module/oss.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "errors" 7 | "fmt" 8 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 9 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 10 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/utils" 11 | "io/ioutil" 12 | "log" 13 | "os" 14 | "os/exec" 15 | "strings" 16 | ) 17 | 18 | const ( 19 | expiredInSec = 3 * 60 * 60 * 1000 20 | ) 21 | 22 | type OssOp interface { 23 | UploadFile(ossKey, localFile string) error 24 | UploadFileByByte(ossKey string, body []byte) error 25 | DownloadFile(ossKey, localFile string) error 26 | DeleteFile(ossKey string) error 27 | DownloadFileToBase64(ossPath string) (*string, error) 28 | GetUrl(ossPath []string) ([]string, error) 29 | } 30 | 31 | // OssGlobal oss manager 32 | var OssGlobal OssOp 33 | 34 | func NewOssManager() error { 35 | switch config.ConfigGlobal.OssMode { 36 | case config.LOCAL: 37 | // read/write with disk 38 | OssGlobal = new(OssManagerLocal) 39 | case config.REMOTE: 40 | client, err := oss.New(config.ConfigGlobal.OssEndpoint, config.ConfigGlobal.AccessKeyId, 41 | config.ConfigGlobal.AccessKeySecret, oss.SecurityToken(config.ConfigGlobal.AccessKeyToken)) 42 | if err != nil { 43 | return err 44 | } 45 | bucket, err := client.Bucket(config.ConfigGlobal.Bucket) 46 | if err != nil { 47 | return err 48 | } 49 | OssGlobal = &OssManagerRemote{ 50 | bucket: bucket, 51 | } 52 | default: 53 | log.Fatal("oss mode err") 54 | } 55 | return nil 56 | } 57 | 58 | type OssManagerRemote struct { 59 | bucket *oss.Bucket 60 | } 61 | 62 | func (o *OssManagerRemote) GetUrl(ossKeys []string) ([]string, error) { 63 | ossUrl := make([]string, 0, len(ossKeys)) 64 | for _, key := range ossKeys { 65 | url, err := o.bucket.SignURL(key, oss.HTTPGet, expiredInSec) 66 | if err != nil { 67 | return nil, errors.New("get") 68 | } 69 | ossUrl = append(ossUrl, url) 70 | } 71 | return ossUrl, nil 72 | } 73 | 74 | // UploadFile upload file to oss 75 | func (o *OssManagerRemote) UploadFile(ossKey, localFile string) error { 76 | // mode: remote 77 | return o.bucket.PutObjectFromFile(ossKey, localFile) 78 | } 79 | 80 | // UploadFileByByte UploadFile upload file to oss 81 | func (o *OssManagerRemote) UploadFileByByte(ossKey string, body []byte) error { 82 | return o.bucket.PutObject(ossKey, bytes.NewReader(body)) 83 | } 84 | 85 | // DownloadFile download file from oss 86 | func (o *OssManagerRemote) DownloadFile(ossKey, localFile string) error { 87 | return o.bucket.GetObjectToFile(ossKey, localFile) 88 | } 89 | 90 | // DeleteFile delete file from oss 91 | func (o *OssManagerRemote) DeleteFile(ossKey string) error { 92 | return o.bucket.DeleteObject(ossKey) 93 | } 94 | 95 | func (o *OssManagerRemote) DownloadFileToBase64(ossKey string) (*string, error) { 96 | // get image from oss 97 | body, err := o.bucket.GetObject(ossKey) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | data, err := ioutil.ReadAll(body) 103 | body.Close() 104 | if err != nil { 105 | return nil, err 106 | } 107 | // image to base64 108 | imageBase64 := base64.StdEncoding.EncodeToString(data) 109 | return &imageBase64, nil 110 | } 111 | 112 | type OssManagerLocal struct { 113 | } 114 | 115 | func (o *OssManagerLocal) GetUrl(ossKey []string) ([]string, error) { 116 | return nil, errors.New("not support") 117 | } 118 | 119 | func (o *OssManagerLocal) UploadFile(ossKey, localFile string) error { 120 | destFile := fmt.Sprintf("%s/%s", config.ConfigGlobal.OssPath, ossKey) 121 | cmd := exec.Command(fmt.Sprintf("cp %s %s", localFile, destFile)) 122 | err := cmd.Run() 123 | return err 124 | } 125 | func (o *OssManagerLocal) UploadFileByByte(ossKey string, body []byte) error { 126 | destFile := fmt.Sprintf("%s/%s", config.ConfigGlobal.OssPath, ossKey) 127 | pathSlice := strings.Split(destFile, "/") 128 | path := strings.Join(pathSlice[:len(pathSlice)-1], "/") 129 | if !utils.FileExists(path) { 130 | os.MkdirAll(path, os.ModePerm) 131 | } 132 | fn, err := os.OpenFile(destFile, os.O_WRONLY|os.O_CREATE, 0666) 133 | if err != nil { 134 | return errors.New(fmt.Sprintf("upload fail because open file error, err=%s", err.Error())) 135 | } 136 | defer fn.Close() 137 | _, err = fn.Write(body) 138 | if err != nil { 139 | return errors.New("upload fail because write file error") 140 | } 141 | return nil 142 | } 143 | func (o *OssManagerLocal) DownloadFile(ossKey, localFile string) error { 144 | destFile := fmt.Sprintf("%s/%s", config.ConfigGlobal.OssPath, ossKey) 145 | cmd := exec.Command("cp", destFile, localFile) 146 | err := cmd.Run() 147 | return err 148 | } 149 | func (o *OssManagerLocal) DeleteFile(ossKey string) error { 150 | destFile := fmt.Sprintf("%s/%s", config.ConfigGlobal.OssPath, ossKey) 151 | _, err := utils.DeleteLocalFile(destFile) 152 | return err 153 | } 154 | 155 | // DownloadFileToBase64 : support png/jpg/jpeg 156 | func (o *OssManagerLocal) DownloadFileToBase64(ossKey string) (*string, error) { 157 | destFile := fmt.Sprintf("%s/%s", config.ConfigGlobal.OssPath, ossKey) 158 | if !utils.FileExists(destFile) { 159 | return nil, fmt.Errorf("ossKey:%s not exist", ossKey) 160 | } 161 | data, err := ioutil.ReadFile(destFile) 162 | if err != nil { 163 | return nil, err 164 | } 165 | imageBase64 := base64.StdEncoding.EncodeToString(data) 166 | return &imageBase64, nil 167 | } 168 | -------------------------------------------------------------------------------- /pkg/module/oss_test.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestOss(t *testing.T) { 10 | NewOssManager() 11 | objKey := "sd/test" 12 | // upload 13 | err := OssGlobal.UploadFileByByte(objKey, []byte("oss test")) 14 | assert.Nil(t, err) 15 | 16 | // download 17 | downloadFile := "downloadFile" 18 | err = OssGlobal.DownloadFile(objKey, downloadFile) 19 | assert.Nil(t, err) 20 | 21 | // delete 22 | err = OssGlobal.DeleteFile(objKey) 23 | assert.Nil(t, err) 24 | err = os.Remove(downloadFile) 25 | assert.Nil(t, err) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/module/proxy.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 7 | "io" 8 | "net" 9 | "net/http" 10 | "os" 11 | "strings" 12 | "sync" 13 | "time" 14 | 15 | "github.com/sirupsen/logrus" 16 | ) 17 | 18 | const ( 19 | huggingFaceUrl = "http://huggingface.co/" 20 | huggingFaceTimeout = 2 * time.Second 21 | ) 22 | 23 | var ( 24 | httpClient = http.DefaultClient 25 | ProxyGlobal = NewProxy() 26 | ) 27 | 28 | type Proxy struct { 29 | server *http.Server 30 | isHuggingFaceOk bool 31 | stop chan struct{} 32 | } 33 | 34 | func NewProxy() *Proxy { 35 | proxy := &Proxy{ 36 | isHuggingFaceOk: true, 37 | stop: make(chan struct{}, 1), 38 | } 39 | go proxy.init() 40 | proxy.server = &http.Server{ 41 | Addr: ":1080", 42 | Handler: http.HandlerFunc(proxy.handleHTTP), 43 | // Disable HTTP/2. 44 | TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), 45 | } 46 | go proxy.server.ListenAndServe() 47 | return proxy 48 | } 49 | 50 | func (p *Proxy) init() { 51 | // check Hugging Face connectedness 52 | if os.Getenv(config.DISABLE_HF_CHECK) == "" { 53 | c := &http.Client{Timeout: huggingFaceTimeout} 54 | resp, err := c.Get(huggingFaceUrl) 55 | 56 | p.isHuggingFaceOk = err == nil && resp != nil 57 | } 58 | } 59 | 60 | func (p *Proxy) handleHTTP(w http.ResponseWriter, req *http.Request) { 61 | if !p.isHuggingFaceOk { 62 | switch strings.ToLower(req.URL.Hostname()) { 63 | case "huggingface.co": 64 | logrus.Infof("connect to %s (%s), reject it.", req.URL.Hostname(), req.URL.String()) 65 | w.WriteHeader(http.StatusRequestTimeout) 66 | w.Write([]byte(fmt.Sprintf("can not connect to %s", req.URL.Hostname()))) 67 | return 68 | } 69 | } 70 | 71 | if req.Method == http.MethodConnect { 72 | // https 73 | dstConn, err := net.DialTimeout("tcp", req.Host, 10*time.Second) 74 | if err != nil { 75 | http.Error(w, err.Error(), http.StatusServiceUnavailable) 76 | return 77 | } 78 | defer dstConn.Close() 79 | 80 | w.WriteHeader(http.StatusOK) 81 | hijacker, ok := w.(http.Hijacker) 82 | if !ok { 83 | http.Error(w, "Hijacking not supported", http.StatusInternalServerError) 84 | return 85 | } 86 | 87 | cliConn, _, err := hijacker.Hijack() 88 | if err != nil { 89 | http.Error(w, err.Error(), http.StatusServiceUnavailable) 90 | } 91 | defer cliConn.Close() 92 | 93 | wg := new(sync.WaitGroup) 94 | wg.Add(2) 95 | 96 | go func() { 97 | defer wg.Done() 98 | io.Copy(dstConn, cliConn) 99 | }() 100 | 101 | go func() { 102 | defer wg.Done() 103 | io.Copy(cliConn, dstConn) 104 | }() 105 | 106 | wg.Wait() 107 | } else { 108 | // http 109 | resp, err := http.DefaultTransport.RoundTrip(req) 110 | if err != nil { 111 | http.Error(w, err.Error(), http.StatusServiceUnavailable) 112 | return 113 | } 114 | defer resp.Body.Close() 115 | 116 | // Copy header 117 | for k, v := range resp.Header { 118 | for _, vv := range v { 119 | w.Header().Add(k, vv) 120 | } 121 | } 122 | w.WriteHeader(resp.StatusCode) 123 | io.Copy(w, resp.Body) 124 | } 125 | } 126 | 127 | func (p *Proxy) Close() { 128 | if p.server != nil { 129 | p.server.Close() 130 | } 131 | p.stop <- struct{}{} 132 | } 133 | -------------------------------------------------------------------------------- /pkg/module/sd.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 10 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/datastore" 11 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/log" 12 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/utils" 13 | "github.com/sirupsen/logrus" 14 | "io" 15 | "io/ioutil" 16 | "net/http" 17 | "os" 18 | "strconv" 19 | "strings" 20 | "sync" 21 | "syscall" 22 | "time" 23 | ) 24 | 25 | const ( 26 | SD_CONFIG = "config.json" 27 | SD_START_TIMEOUT = 5 * 60 * 1000 // 5min 28 | SD_DETECT_TIMEOUT = 500 // 500ms 29 | SD_REQUEST_WAIT = 30 * 1000 // 30s 30 | ) 31 | 32 | var ( 33 | SDManageObj *SDManager 34 | once sync.Once 35 | ) 36 | 37 | type SDManager struct { 38 | pid int 39 | port string 40 | modelLoadedFlag bool 41 | restartFlag bool 42 | stdout io.ReadCloser 43 | endChan chan struct{} 44 | signalIn chan struct{} 45 | signalOut chan struct{} 46 | } 47 | 48 | func NewSDManager(port string) *SDManager { 49 | SDManageObj = new(SDManager) 50 | SDManageObj.port = port 51 | SDManageObj.endChan = make(chan struct{}, 1) 52 | SDManageObj.signalIn = make(chan struct{}, 1) 53 | SDManageObj.signalOut = make(chan struct{}) 54 | if err := SDManageObj.init(); err != nil { 55 | logrus.Error(err.Error()) 56 | } 57 | return SDManageObj 58 | } 59 | 60 | func (s *SDManager) getEnv() []string { 61 | env := make([]string, 0) 62 | fileMgrToken := "" 63 | fileMgrEndpoint := "" 64 | if fileMgr := FuncManagerGlobal.GetFileMgr(); fileMgr != nil && fileMgr.EnvironmentVariables != nil { 65 | adminEnv := fileMgr.EnvironmentVariables 66 | if token := adminEnv["TOKEN"]; token != nil { 67 | fileMgrToken = *token 68 | } 69 | fileMgrEndpoint = GetHttpTrigger(*fileMgr.FunctionName) 70 | } 71 | env = append( 72 | os.Environ(), 73 | fmt.Sprintf("SERVERLESS_SD_FILEMGR_TOKEN=%s", fileMgrToken), 74 | fmt.Sprintf("SERVERLESS_SD_FILEMGR_DOMAIN=%s", fileMgrEndpoint)) 75 | 76 | // not set DISABLE_HF_CHECK, default proxy enable 77 | if !config.ConfigGlobal.GetDisableHealthCheck() { 78 | env = append(env, 79 | "HTTP_PROXY=http://127.0.0.1:1080", 80 | "HTTPS_PROXY=http://127.0.0.1:1080", 81 | ) 82 | } 83 | return env 84 | } 85 | 86 | func (s *SDManager) init() error { 87 | s.modelLoadedFlag = false 88 | sdStartTs := utils.TimestampMS() 89 | defer func() { 90 | sdEndTs := utils.TimestampMS() 91 | log.SDLogInstance.TraceFlow <- []string{config.TrackerKeyStableDiffusionStartup, 92 | fmt.Sprintf("sd start cost=%d", sdEndTs-sdStartTs)} 93 | }() 94 | // start sd 95 | execItem, err := utils.DoExecAsync(config.ConfigGlobal.SdShell, config.ConfigGlobal.SdPath, s.getEnv()) 96 | if err != nil { 97 | return err 98 | } 99 | // init read sd log 100 | go func() { 101 | stdout := bufio.NewScanner(execItem.Stdout) 102 | defer execItem.Stdout.Close() 103 | for stdout.Scan() { 104 | select { 105 | case <-s.endChan: 106 | return 107 | default: 108 | logStr := stdout.Text() 109 | if !s.modelLoadedFlag && strings.HasPrefix(logStr, "Model loaded in") { 110 | s.modelLoadedFlag = true 111 | } 112 | log.SDLogInstance.LogFlow <- logStr 113 | } 114 | } 115 | }() 116 | s.pid = execItem.Pid 117 | s.stdout = execItem.Stdout 118 | // make sure sd started(port exist) 119 | if !utils.PortCheck(s.port, SD_START_TIMEOUT) { 120 | return errors.New("sd not start after 5min") 121 | } 122 | if os.Getenv(config.CHECK_MODEL_LOAD) != "" && strings.Contains(os.Getenv(config.SD_START_PARAMS), "--api") { 123 | // if api mode need blocking model loaded 124 | s.waitModelLoaded(SD_START_TIMEOUT) 125 | } 126 | once.Do(func() { 127 | go s.detectSdAlive() 128 | }) 129 | return nil 130 | } 131 | 132 | // idle charge mode need check model 133 | func (s *SDManager) waitModelLoaded(timeout int) { 134 | timeoutChan := time.After(time.Duration(timeout) * time.Millisecond) 135 | for { 136 | select { 137 | case <-timeoutChan: 138 | return 139 | default: 140 | if s.modelLoadedFlag && PredictProbe() { 141 | return 142 | } 143 | } 144 | } 145 | } 146 | 147 | // PredictProbe predict one task, return true always 148 | func PredictProbe() bool { 149 | payload := map[string]interface{}{ 150 | "prompt": "", 151 | "steps": 1, 152 | "height": 8, 153 | "width": 8, 154 | } 155 | body, _ := json.Marshal(payload) 156 | req, _ := http.NewRequest(config.HTTP_POST, 157 | fmt.Sprintf("%s%s", config.ConfigGlobal.SdUrlPrefix, 158 | config.TXT2IMG), bytes.NewBuffer(body)) 159 | client := &http.Client{} 160 | _, err := client.Do(req) 161 | if err != nil { 162 | logrus.Errorf("predict first image err=%s", err.Error()) 163 | return false 164 | } 165 | return true 166 | } 167 | 168 | func (s *SDManager) detectSdAlive() { 169 | // SD_DETECT_TIMEOUT ms 170 | for { 171 | //s.KillAgentWithoutSd() 172 | s.WaitPortWork() 173 | time.Sleep(time.Duration(SD_DETECT_TIMEOUT) * time.Millisecond) 174 | } 175 | } 176 | 177 | func (s *SDManager) KillAgentWithoutSd() { 178 | if !checkSdExist(strconv.Itoa(s.pid)) && !utils.PortCheck(s.port, SD_DETECT_TIMEOUT) { 179 | syscall.Kill(syscall.Getpid(), syscall.SIGTERM) 180 | } 181 | } 182 | 183 | func (s *SDManager) WaitPortWork() { 184 | // sd not exist, kill 185 | if !checkSdExist(strconv.Itoa(s.pid)) && !utils.PortCheck(s.port, SD_DETECT_TIMEOUT) { 186 | logrus.Info("restart process....") 187 | s.init() 188 | } 189 | } 190 | 191 | // WaitSDRestartFinish blocking until sd restart finish 192 | func (s *SDManager) WaitSDRestartFinish() { 193 | //select { 194 | //case <-s.signalIn: 195 | // logrus.Info("blocking until restart finish") 196 | // s.signalOut <- struct{}{} 197 | //default: 198 | //} 199 | if utils.PortCheck(s.port, SD_REQUEST_WAIT) { 200 | time.Sleep(time.Duration(1) * time.Second) 201 | } 202 | } 203 | 204 | func (s *SDManager) Close() { 205 | syscall.Kill(-s.pid, syscall.SIGKILL) 206 | s.endChan <- struct{}{} 207 | } 208 | 209 | // UpdateSdConfig modify sd config.json sd_model_checkpoint and sd_vae 210 | func UpdateSdConfig(configStore datastore.Datastore) error { 211 | // sdModel/sdVae from env 212 | sdModel := os.Getenv(config.MODEL_SD) 213 | if sdModel == "" { 214 | return errors.New("sd model not set in env") 215 | } 216 | var data []byte 217 | configPath := fmt.Sprintf("%s/%s", config.ConfigGlobal.SdPath, SD_CONFIG) 218 | // get sd config from remote 219 | configData, err := configStore.Get(ConfigDefaultKey, []string{datastore.KConfigVal}) 220 | if err == nil && configData != nil && len(configData) > 0 { 221 | data = []byte(configData[datastore.KConfigVal].(string)) 222 | } else { 223 | // get sd config from local 224 | fd, err := os.Open(configPath) 225 | if err != nil { 226 | return err 227 | } 228 | data, _ = ioutil.ReadAll(fd) 229 | fd.Close() 230 | } 231 | var m map[string]interface{} 232 | if err := json.Unmarshal(data, &m); err != nil { 233 | return err 234 | } 235 | m["sd_model_checkpoint"] = sdModel 236 | m["sd_vae"] = "None" 237 | m["sd_checkpoint_hash"] = "" 238 | output, err := json.MarshalIndent(m, "", " ") 239 | if err != nil { 240 | return err 241 | } 242 | // delete first 243 | utils.DeleteLocalFile(configPath) 244 | fdOut, err := os.OpenFile(configPath, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0775) 245 | defer fdOut.Close() 246 | 247 | fdOut.WriteString(string(output)) 248 | return nil 249 | } 250 | 251 | func checkSdExist(pid string) bool { 252 | execItem := utils.DoExec("ps -ef|grep webui|grep -v agent|grep -v grep|awk '{print $2}'", "", nil) 253 | if strings.Trim(execItem.Output, "\n") == pid { 254 | return true 255 | } 256 | return false 257 | } 258 | -------------------------------------------------------------------------------- /pkg/module/user.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "fmt" 5 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 6 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/datastore" 7 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/utils" 8 | "strconv" 9 | "sync" 10 | ) 11 | 12 | const ( 13 | SESSIONLENGTH = 64 14 | DefaultUser = "admin" 15 | DefaultPasswd = "123" 16 | ) 17 | 18 | var UserManagerGlobal *userManager 19 | 20 | // user session info 21 | type userSession struct { 22 | userName string 23 | session string 24 | expired int 25 | } 26 | 27 | type userManager struct { 28 | userStore datastore.Datastore 29 | cache *sync.Map 30 | } 31 | 32 | func InitUserManager(userStore datastore.Datastore) error { 33 | UserManagerGlobal = &userManager{ 34 | userStore: userStore, 35 | cache: new(sync.Map), 36 | } 37 | return UserManagerGlobal.loadUserFromDb() 38 | } 39 | 40 | // load user info from db 41 | func (u *userManager) loadUserFromDb() error { 42 | datas, err := u.userStore.ListAll([]string{datastore.KUserSession, datastore.KUserName, 43 | datastore.KUserSessionValidTime}) 44 | if err != nil { 45 | return err 46 | } 47 | needInit := true 48 | for _, data := range datas { 49 | if name, ok := data[datastore.KUserName]; ok && name.(string) == DefaultUser { 50 | needInit = false 51 | } 52 | if len(data) != 3 { 53 | continue 54 | } 55 | userName := data[datastore.KUserName].(string) 56 | session := data[datastore.KUserSession].(string) 57 | expired, _ := strconv.Atoi(data[datastore.KUserSessionValidTime].(string)) 58 | u.cache.Store(session, &userSession{ 59 | userName: userName, 60 | session: session, 61 | expired: expired, 62 | }) 63 | } 64 | if needInit { 65 | defaultEncodePassword, _ := utils.EncryptPassword(DefaultPasswd) 66 | u.userStore.Put(DefaultUser, map[string]interface{}{ 67 | datastore.KUserName: DefaultUser, 68 | datastore.KUserPassword: defaultEncodePassword, 69 | }) 70 | } 71 | return nil 72 | } 73 | 74 | // VerifyUserValid verify user valid 75 | func (u *userManager) VerifyUserValid(userName, password string) (string, int, bool) { 76 | data, err := u.userStore.Get(userName, []string{datastore.KUserName, datastore.KUserPassword}) 77 | if err != nil || data == nil || len(data) == 0 { 78 | return "", 0, false 79 | } 80 | passwordDb := data[datastore.KUserPassword].(string) 81 | if utils.MatchPassword(password, passwordDb) { 82 | session := utils.RandStr(SESSIONLENGTH) 83 | expired := int(utils.TimestampS() + config.ConfigGlobal.SessionExpire) 84 | u.cache.Store(session, &userSession{ 85 | userName: userName, 86 | session: session, 87 | expired: expired, 88 | }) 89 | return session, expired, true 90 | } 91 | return "", 0, false 92 | } 93 | 94 | func (u *userManager) VerifySessionValid(session string) (string, bool) { 95 | if session == "" || len(session) != SESSIONLENGTH { 96 | return "", false 97 | } 98 | curTime := utils.TimestampS() 99 | alreadyLoadDb := false 100 | for { 101 | // check cache 102 | if val, ok := u.cache.Load(session); ok { 103 | info := val.(*userSession) 104 | if curTime <= int64(info.expired) { 105 | // Renewal expired 106 | info.expired = int(curTime + config.ConfigGlobal.SessionExpire) 107 | u.userStore.Update(info.userName, map[string]interface{}{ 108 | datastore.KUserSessionValidTime: fmt.Sprintf("%d", info.expired), 109 | datastore.KUserModifyTime: fmt.Sprintf("%d", utils.TimestampS()), 110 | }) 111 | return info.userName, true 112 | } 113 | } 114 | if alreadyLoadDb { 115 | return "", false 116 | } 117 | // expired or not in cache 118 | // update all user info 119 | u.loadUserFromDb() 120 | alreadyLoadDb = true 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pkg/server/agent.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 6 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/datastore" 7 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/handler" 8 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/log" 9 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/module" 10 | "github.com/gin-contrib/cors" 11 | "github.com/gin-gonic/gin" 12 | "github.com/sirupsen/logrus" 13 | "net" 14 | "net/http" 15 | "time" 16 | ) 17 | 18 | type AgentServer struct { 19 | srv *http.Server 20 | listenTask *module.ListenDbTask 21 | taskDataStore datastore.Datastore 22 | modelDataStore datastore.Datastore 23 | configDataStore datastore.Datastore 24 | sdManager *module.SDManager 25 | } 26 | 27 | func NewAgentServer(port string, dbType datastore.DatastoreType, mode string) (*AgentServer, error) { 28 | agentServer := new(AgentServer) 29 | // init router 30 | if mode == gin.DebugMode { 31 | gin.SetMode(gin.DebugMode) 32 | } else { 33 | gin.SetMode(gin.ReleaseMode) 34 | } 35 | router := gin.New() 36 | router.Use(cors.Default()) 37 | router.Use(gin.Logger(), gin.Recovery()) 38 | router.Use(handler.Stat()) 39 | tableFactory := datastore.DatastoreFactory{} 40 | if config.ConfigGlobal.ExposeToUser() { 41 | // init func manager 42 | if err := module.InitFuncManager(nil); err != nil { 43 | return nil, err 44 | } 45 | agentServer.sdManager = module.NewSDManager(config.ConfigGlobal.GetSDPort()) 46 | // enable ReverserProxy 47 | router.POST("/initialize", handler.InitializeHandler) 48 | router.NoRoute(handler.ReverseProxy) 49 | } else { 50 | // only api 51 | // init function table 52 | funcDataStore := tableFactory.NewTable(dbType, datastore.KModelServiceTableName) 53 | // init func manager 54 | if err := module.InitFuncManager(funcDataStore); err != nil { 55 | logrus.Errorf("func manage init error %v", err) 56 | return nil, err 57 | } 58 | // init oss manager 59 | if err := module.NewOssManager(); err != nil { 60 | logrus.Errorf("oss init error %v", err) 61 | logrus.Fatal("oss init fail") 62 | } 63 | // init task table 64 | taskDataStore := tableFactory.NewTable(dbType, datastore.KTaskTableName) 65 | // init model table 66 | modelDataStore := tableFactory.NewTable(dbType, datastore.KModelTableName) 67 | // init config table 68 | configDataStore := tableFactory.NewTable(dbType, datastore.KConfigTableName) 69 | // init listen event 70 | listenTask := module.NewListenDbTask(config.ConfigGlobal.ListenInterval, taskDataStore, modelDataStore, 71 | configDataStore) 72 | // add listen model change 73 | listenTask.AddTask("modelTask", module.ModelListen, module.ModelChangeEvent) 74 | // init handler 75 | agentHandler := handler.NewAgentHandler(taskDataStore, modelDataStore, configDataStore, listenTask) 76 | 77 | // update sd config.json 78 | if !config.ConfigGlobal.ExposeToUser() { 79 | if err := module.UpdateSdConfig(configDataStore); err != nil { 80 | logrus.Fatal("sd config update fail") 81 | } 82 | } 83 | agentServer.sdManager = module.NewSDManager(config.ConfigGlobal.GetSDPort()) 84 | 85 | handler.RegisterHandlers(router, agentHandler) 86 | router.POST("/initialize", handler.InitializeHandler) 87 | router.NoRoute(agentHandler.NoRouterAgentHandler) 88 | agentServer.listenTask = listenTask 89 | agentServer.taskDataStore = taskDataStore 90 | agentServer.modelDataStore = modelDataStore 91 | agentServer.configDataStore = configDataStore 92 | } 93 | 94 | agentServer.srv = &http.Server{ 95 | Addr: net.JoinHostPort("0.0.0.0", port), 96 | Handler: router, 97 | } 98 | return agentServer, nil 99 | 100 | } 101 | 102 | // Start proxy server 103 | func (p *AgentServer) Start() error { 104 | if err := p.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 105 | logrus.Fatalf("listen: %s\n", err) 106 | return err 107 | } 108 | return nil 109 | } 110 | 111 | // Close shutdown proxy server, timeout=shutdownTimeout 112 | func (p *AgentServer) Close(shutdownTimeout time.Duration) error { 113 | // close listen task 114 | if p.listenTask != nil { 115 | p.listenTask.Close() 116 | } 117 | if p.taskDataStore != nil { 118 | p.taskDataStore.Close() 119 | } 120 | if p.modelDataStore != nil { 121 | p.modelDataStore.Close() 122 | } 123 | if p.configDataStore != nil { 124 | p.configDataStore.Close() 125 | } 126 | if p.sdManager != nil { 127 | p.sdManager.Close() 128 | } 129 | 130 | if log.SDLogInstance != nil { 131 | log.SDLogInstance.Close() 132 | } 133 | 134 | if module.ProxyGlobal != nil { 135 | module.ProxyGlobal.Close() 136 | } 137 | 138 | // shutdown server 139 | ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) 140 | defer cancel() 141 | if err := p.srv.Shutdown(ctx); err != nil { 142 | logrus.Fatal("Server forced to shutdown: ", err) 143 | return err 144 | } 145 | return nil 146 | } 147 | -------------------------------------------------------------------------------- /pkg/server/proxy.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/config" 6 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/datastore" 7 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/handler" 8 | "github.com/devsapp/serverless-stable-diffusion-api/pkg/module" 9 | "github.com/gin-gonic/gin" 10 | "github.com/sirupsen/logrus" 11 | "net" 12 | "net/http" 13 | "time" 14 | ) 15 | 16 | type ProxyServer struct { 17 | srv *http.Server 18 | taskDataStore datastore.Datastore 19 | modelDataStore datastore.Datastore 20 | userDataStore datastore.Datastore 21 | funcDataStore datastore.Datastore 22 | configStore datastore.Datastore 23 | } 24 | 25 | func NewProxyServer(port string, dbType datastore.DatastoreType, mode string) (*ProxyServer, error) { 26 | // init oss manager 27 | if err := module.NewOssManager(); err != nil { 28 | logrus.Errorf("oss init error %v", err) 29 | return nil, err 30 | } 31 | tableFactory := datastore.DatastoreFactory{} 32 | // init task table 33 | taskDataStore := tableFactory.NewTable(dbType, datastore.KTaskTableName) 34 | // init model table 35 | modelDataStore := tableFactory.NewTable(dbType, datastore.KModelTableName) 36 | // init user table 37 | userDataStore := tableFactory.NewTable(dbType, datastore.KUserTableName) 38 | if err := module.InitUserManager(userDataStore); err != nil { 39 | logrus.Errorf("user init error %v", err) 40 | return nil, err 41 | } 42 | // init config table 43 | configDataStore := tableFactory.NewTable(dbType, datastore.KConfigTableName) 44 | // init function table 45 | funcDataStore := tableFactory.NewTable(dbType, datastore.KModelServiceTableName) 46 | // init func manager 47 | if err := module.InitFuncManager(funcDataStore); err != nil { 48 | logrus.Errorf("func manage init error %v", err) 49 | return nil, err 50 | } 51 | if config.ConfigGlobal.IsServerTypeMatch(config.CONTROL) { 52 | // init listen event 53 | listenTask := module.NewListenDbTask(config.ConfigGlobal.ListenInterval, taskDataStore, modelDataStore, 54 | configDataStore) 55 | // add config listen task 56 | listenTask.AddTask("configTask", module.ConfigListen, module.ConfigEvent) 57 | } 58 | // init handler 59 | proxyHandler := handler.NewProxyHandler(taskDataStore, modelDataStore, userDataStore, 60 | configDataStore, funcDataStore) 61 | 62 | // init router 63 | if mode == gin.DebugMode { 64 | gin.SetMode(gin.DebugMode) 65 | } else { 66 | gin.SetMode(gin.ReleaseMode) 67 | } 68 | router := gin.New() 69 | router.Use(CORSMiddleware()) 70 | router.Use(gin.Logger(), gin.Recovery()) 71 | router.Use(handler.Stat()) 72 | 73 | // auth permission check 74 | if config.ConfigGlobal.EnableLogin() { 75 | router.Use(handler.ApiAuth()) 76 | } 77 | handler.RegisterHandlers(router, proxyHandler) 78 | router.NoRoute(proxyHandler.NoRouterHandler) 79 | 80 | return &ProxyServer{ 81 | srv: &http.Server{ 82 | Addr: net.JoinHostPort("0.0.0.0", port), 83 | Handler: router, 84 | }, 85 | taskDataStore: taskDataStore, 86 | userDataStore: userDataStore, 87 | modelDataStore: modelDataStore, 88 | funcDataStore: funcDataStore, 89 | configStore: configDataStore, 90 | }, nil 91 | } 92 | 93 | // Start proxy server 94 | func (p *ProxyServer) Start() error { 95 | if err := p.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 96 | logrus.Fatalf("listen: %s\n", err) 97 | return err 98 | } 99 | return nil 100 | } 101 | 102 | // Close shutdown proxy server, timeout=shutdownTimeout 103 | func (p *ProxyServer) Close(shutdownTimeout time.Duration) error { 104 | if p.userDataStore != nil { 105 | p.userDataStore.Close() 106 | } 107 | if p.taskDataStore != nil { 108 | p.taskDataStore.Close() 109 | } 110 | if p.modelDataStore != nil { 111 | p.modelDataStore.Close() 112 | } 113 | if p.funcDataStore != nil { 114 | p.funcDataStore.Close() 115 | } 116 | if p.configStore != nil { 117 | p.configStore.Close() 118 | } 119 | ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) 120 | defer cancel() 121 | if err := p.srv.Shutdown(ctx); err != nil { 122 | logrus.Fatal("Server forced to shutdown: ", err) 123 | return err 124 | } 125 | return nil 126 | } 127 | 128 | func CORSMiddleware() gin.HandlerFunc { 129 | return func(c *gin.Context) { 130 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 131 | c.Writer.Header().Set("Access-Control-Allow-Methods", "*") 132 | c.Writer.Header().Set("Access-Control-Allow-Headers", "*") 133 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "false") 134 | c.Next() 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /pkg/utils/bcrypt.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "golang.org/x/crypto/bcrypt" 5 | ) 6 | 7 | func EncryptPassword(password string) (string, error) { 8 | hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 9 | if err != nil { 10 | return "", err 11 | } 12 | return string(hash), nil 13 | } 14 | 15 | func MatchPassword(password, encryptedPassword string) bool { 16 | err := bcrypt.CompareHashAndPassword([]byte(encryptedPassword), []byte(password)) 17 | return err == nil 18 | } 19 | -------------------------------------------------------------------------------- /pkg/utils/exec.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/sirupsen/logrus" 7 | "io" 8 | "io/ioutil" 9 | "os/exec" 10 | "syscall" 11 | ) 12 | 13 | type ExecItem struct { 14 | Pid int 15 | Status int 16 | Args []string 17 | Output string 18 | Stdout io.ReadCloser 19 | } 20 | 21 | func DoExec(shell, dir string, env []string) *ExecItem { 22 | execItem := &ExecItem{ 23 | Status: 0, 24 | } 25 | cmd := exec.Command("/bin/bash", "-c", shell) 26 | stdout, _ := cmd.StdoutPipe() 27 | defer stdout.Close() 28 | if dir != "" { 29 | cmd.Dir = dir 30 | } 31 | if env != nil { 32 | cmd.Env = env 33 | } 34 | if err := cmd.Start(); err != nil { 35 | fmt.Println("cmd.Start err: ", err.Error()) 36 | } 37 | 38 | execItem.Args = cmd.Args 39 | execItem.Pid = cmd.Process.Pid 40 | result, _ := ioutil.ReadAll(stdout) 41 | execItem.Output = string(result) 42 | if err := cmd.Wait(); err != nil { 43 | if ex, ok := err.(*exec.ExitError); ok { 44 | execItem.Status = ex.Sys().(syscall.WaitStatus).ExitStatus() 45 | } 46 | } 47 | return execItem 48 | } 49 | 50 | func DoExecAsync(shell, dir string, env []string) (*ExecItem, error) { 51 | execItem := &ExecItem{ 52 | Status: 0, 53 | } 54 | cmd := exec.Command("/bin/bash", "-c", shell) 55 | cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 56 | stdout, _ := cmd.StdoutPipe() 57 | cmd.Stderr = cmd.Stdout 58 | if env != nil { 59 | cmd.Env = env 60 | } 61 | //cmd.Stderr = os.Stderr 62 | //cmd.Stdout = os.Stdout 63 | if dir != "" { 64 | cmd.Dir = dir 65 | } 66 | if err := cmd.Start(); err != nil { 67 | logrus.Errorf("cmd.Start err: %s", err.Error()) 68 | return nil, errors.New("sd start error") 69 | } 70 | 71 | execItem.Args = cmd.Args 72 | execItem.Pid = cmd.Process.Pid 73 | execItem.Stdout = stdout 74 | return execItem, nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "github.com/sirupsen/logrus" 11 | "io" 12 | "io/ioutil" 13 | "math/rand" 14 | "net" 15 | "os" 16 | "time" 17 | ) 18 | 19 | // RandStr product random string 20 | func RandStr(length int) string { 21 | str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 22 | bytes := []byte(str) 23 | result := []byte{} 24 | rand.Seed(time.Now().UnixNano() + int64(rand.Intn(100))) 25 | for i := 0; i < length; i++ { 26 | result = append(result, bytes[rand.Intn(len(bytes))]) 27 | } 28 | return string(result) 29 | } 30 | 31 | func FileMD5(filePath string) (string, error) { 32 | file, err := os.Open(filePath) 33 | defer file.Close() 34 | if err != nil { 35 | return "", err 36 | } 37 | hash := md5.New() 38 | _, _ = io.Copy(hash, file) 39 | return hex.EncodeToString(hash.Sum(nil)), nil 40 | } 41 | 42 | func Hash(s string) string { 43 | h := sha256.New() 44 | h.Write([]byte(s)) 45 | bs := h.Sum(nil) 46 | return fmt.Sprintf("%x", bs[:32]) 47 | } 48 | 49 | func TimestampS() int64 { 50 | return time.Now().Unix() 51 | } 52 | 53 | func TimestampMS() int64 { 54 | return time.Now().UnixNano() / 1e6 55 | } 56 | 57 | func String(s string) *string { 58 | return &s 59 | } 60 | 61 | func Int32(v int32) *int32 { 62 | return &v 63 | } 64 | 65 | func Float32(v float32) *float32 { 66 | return &v 67 | } 68 | 69 | func Bool(v bool) *bool { 70 | return &v 71 | } 72 | 73 | // PortCheck port usable 74 | func PortCheck(port string, timeout int) bool { 75 | if port == "" { 76 | return false 77 | } 78 | timeoutChan := time.After(time.Duration(timeout) * time.Millisecond) 79 | for { 80 | select { 81 | case <-timeoutChan: 82 | return false 83 | default: 84 | conn, err := net.DialTimeout("tcp", fmt.Sprintf("localhost:%s", port), 85 | time.Duration(10)*time.Millisecond) 86 | if err == nil && conn != nil { 87 | conn.Close() 88 | return true 89 | } 90 | } 91 | } 92 | return false 93 | } 94 | 95 | func DeleteLocalFile(localFile string) (bool, error) { 96 | _, err := os.Stat(localFile) 97 | if err == nil { 98 | if err := os.Remove(localFile); err == nil { 99 | return true, nil 100 | } else { 101 | return false, errors.New("delete model fail") 102 | } 103 | } 104 | if os.IsNotExist(err) { 105 | return false, errors.New("model not exist") 106 | } 107 | return false, err 108 | } 109 | 110 | // FileExists check file exist 111 | func FileExists(path string) bool { 112 | _, err := os.Stat(path) 113 | if err != nil { 114 | if os.IsExist(err) { 115 | return true 116 | } 117 | return false 118 | } 119 | return true 120 | } 121 | 122 | func ListFile(path string) []string { 123 | fileSlice := make([]string, 0) 124 | files, _ := ioutil.ReadDir(path) 125 | for _, f := range files { 126 | fileSlice = append(fileSlice, f.Name()) 127 | 128 | } 129 | return fileSlice 130 | } 131 | 132 | // DiffSet check a == b ? return diff 133 | func DiffSet(old, new map[string]struct{}) ([]string, []string) { 134 | del := make([]string, 0) 135 | add := make([]string, 0) 136 | for k := range old { 137 | if _, ok := new[k]; !ok { 138 | del = append(del, k) 139 | } 140 | } 141 | for k := range new { 142 | if _, ok := old[k]; !ok { 143 | add = append(add, k) 144 | } 145 | } 146 | return add, del 147 | } 148 | 149 | func IsSame(key string, a, b interface{}) bool { 150 | switch a.(type) { 151 | case []interface{}: 152 | aT := a.([]interface{}) 153 | bT := b.([]interface{}) 154 | if len(aT) != len(bT) { 155 | return false 156 | } 157 | for i, _ := range aT { 158 | if aT[i] != bT[i] { 159 | return false 160 | } 161 | } 162 | return true 163 | case int64, int32, int, int16, int8, string, float64, float32, bool: 164 | return a == b 165 | default: 166 | logrus.Info(key, a, b) 167 | logrus.Fatal("type not support") 168 | } 169 | return true 170 | } 171 | 172 | // MapToStruct map to struct 173 | func MapToStruct(m map[string]interface{}, s interface{}) error { 174 | jsonData, err := json.Marshal(m) 175 | if err != nil { 176 | return err 177 | } 178 | logrus.Info(string(jsonData)) 179 | err = json.Unmarshal(jsonData, s) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | return nil 185 | } 186 | -------------------------------------------------------------------------------- /pkg/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "log" 7 | "testing" 8 | ) 9 | 10 | func TestRandStr(t *testing.T) { 11 | length := 10 12 | randStr := RandStr(length) 13 | assert.Equal(t, length, len(randStr)) 14 | } 15 | 16 | func TestHash(t *testing.T) { 17 | s := "dddddd" 18 | hash := Hash(s) 19 | log.Println(hash) 20 | assert.Equal(t, 64, len(hash)) 21 | } 22 | 23 | func TestTimestampMS(t *testing.T) { 24 | cur := TimestampMS() 25 | assert.Equal(t, len(fmt.Sprintf("%d", cur)), 13) 26 | log.Println(find([]int{5, 6, 7, 8, 9, 10}, 8)) 27 | log.Println(find([]int{5, 6, 7, 8, 9, 10}, 11)) 28 | log.Println(find([]int{5, 6, 7, 8, 9, 10}, 5)) 29 | log.Println(find([]int{5, 6, 7, 8, 9, 10}, 3)) 30 | } 31 | 32 | func find(L []int, val int) int { 33 | low := 0 34 | high := len(L) - 1 35 | for low <= high { 36 | mid := (low + high) / 2 37 | if L[mid] < val { 38 | low = mid + 1 39 | } else { 40 | high = mid - 1 41 | } 42 | } 43 | return low 44 | } 45 | -------------------------------------------------------------------------------- /proxy.yaml: -------------------------------------------------------------------------------- 1 | dbSqlite: /mnt/auto/sd/sqlite3 2 | ossEndpoint: oss-cn-beijing.aliyuncs.com 3 | bucket: sd-api-t 4 | ossMode: local 5 | ossPath: /mnt/oss 6 | sdPath: /mnt/auto/sd 7 | otsEndpoint: https://sd-api-test.cn-beijing.ots.aliyuncs.com 8 | otsInstanceName: sd-api-t 9 | otsMaxVersion: 1 10 | otsTimeToAlive: -1 11 | listenInterval: 1 12 | # FC 13 | caPort: 7860 14 | CPU: 8 15 | diskSize: 512 16 | instanceConcurrency: 1 17 | instanceType: fc.gpu.tesla.1 18 | memorySize: 32768 19 | timeout: 600 20 | gpuMemorySize: 16384 21 | extraArgs: --api --nowebui 22 | sessionExpire: 3600 23 | loginSwitch: off #value: off|on 24 | useLocalModel: yes #value: yes|no 25 | flexMode: multiFunc # value: singleFunc|multiFunc 26 | serverName: proxy # value: proxy|agent|control -------------------------------------------------------------------------------- /script/client-config.yaml: -------------------------------------------------------------------------------- 1 | package: client 2 | generate: 3 | client: true 4 | # models: true 5 | additional-imports: 6 | - package: github.com/devsapp/serverless-stable-diffusion-api/pkg/models 7 | alias: . 8 | output: pkg/client/client.go -------------------------------------------------------------------------------- /script/codegen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # step 1: go get github.com/deepmap/oapi-codegen/cmd/oapi-codegen 3 | # step 2: check output exist 4 | home=$(pwd) 5 | models=$home/pkg/models 6 | handler=$home/pkg/handler 7 | client=$home/pkg/client 8 | if [ ! -d "$models" ]; then 9 | mkdir -p $models 10 | fi 11 | if [ ! -d "$handler" ]; then 12 | mkdir -p $handler 13 | fi 14 | if [ ! -d "$client" ]; then 15 | mkdir -p $client 16 | fi 17 | # step 3: gen code 18 | oapi-codegen --config=script/models-config.yaml api/api.yaml 19 | oapi-codegen --config=script/server-config.yaml api/api.yaml 20 | oapi-codegen --config=script/client-config.yaml api/api.yaml -------------------------------------------------------------------------------- /script/models-config.yaml: -------------------------------------------------------------------------------- 1 | package: models 2 | generate: 3 | models: true 4 | output: pkg/models/proxy.go -------------------------------------------------------------------------------- /script/request/extra_image.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import base64 3 | import requests 4 | 5 | class Request: 6 | def __init__(self, prompt, path): 7 | self.url = "https://xxxx/extra_images" 8 | self.prompt = prompt 9 | self.img_path = path 10 | self.body = None 11 | 12 | def build_body(self): 13 | self.body = { 14 | # "stable_diffusion_model":"chilloutmix_NiPrunedFp16Fix.safetensors", 15 | "resize_mode": 0, 16 | "show_extras_results": True, 17 | "gfpgan_visibility": 0, 18 | "codeformer_visibility": 0, 19 | "codeformer_weight": 0, 20 | "upscaling_resize": 4, 21 | # "upscaling_crop": True, 22 | "upscaler_1": "Lanczos", 23 | "upscaler_2": "None", 24 | "extras_upscaler_2_visibility": 0, 25 | "upscale_first": False, 26 | # "image":self.file_to_base64(), 27 | "image" : "images/default/mjDxugVFDr_1.png" 28 | } 29 | 30 | 31 | def send_request(self): 32 | headers = {"Request-Type":"sync", "content-type": "application/json", "token":"aSV8Ro3qwbMy3qla7fNGORNonmj6KxbvUIVtE5kNl58BwYUUQq0uap6MODxhGqfq"} 33 | response = requests.post(url=self.url, json=self.body, headers=headers) 34 | return response.json() 35 | 36 | def read_image(self): 37 | img = cv2.imread(self.img_path) 38 | retval, bytes = cv2.imencode('.png', img) 39 | encoded_image = base64.b64encode(bytes).decode('utf-8') 40 | return encoded_image 41 | 42 | def file_to_base64(self): 43 | with open(self.img_path, "rb") as file: 44 | data = file.read() 45 | 46 | base64_str = str(base64.b64encode(data), "utf-8") 47 | return "data:image/png;base64," + base64_str 48 | 49 | 50 | if __name__ == '__main__': 51 | path = '/Users/xxxx/Downloads/mjDxugVFDr_1.png' 52 | prompt = 'a large avalanche' 53 | 54 | control_net = Request(prompt, path) 55 | control_net.build_body() 56 | output = control_net.send_request() 57 | print(output) -------------------------------------------------------------------------------- /script/request/ima2img.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import base64 3 | import requests 4 | 5 | 6 | class ControlnetRequest: 7 | def __init__(self, prompt, path): 8 | self.url = "https://xxxxxxxxxxx/img2img" 9 | self.prompt = prompt 10 | self.img_path = path 11 | self.body = None 12 | 13 | def build_body(self): 14 | self.body = { 15 | "stable_diffusion_model": "xxmix9realistic_v40.safetensors", 16 | "sd_vae": "None", 17 | "prompt": "product place on table,((kitchen)), food, snacks,photorealistic, realistic, photography, masterpiece, best quality, no human", 18 | "negative_prompt": "unrealistic, poor texture, poor quality,clear edges, bad material, soft detail, bad pictures, (bad hand), ((low quality)), ((worst quality)), nsfw", 19 | "width": 512, 20 | "height": 512, 21 | "seed": 112233, 22 | "steps": 30, 23 | "cfg_scale": 7, 24 | "sampler_name": "Euler a", 25 | "batch_size": 1, 26 | "init_images": [ 27 | # "images/default/8A8088848B128072018B1280744F0001.png" 28 | self.file_to_base64() 29 | ], 30 | # "mask": "images/default/8A8088848B128072018B1280744F0001.png", 31 | "mask": self.file_to_base64(), 32 | "mask_blur": 4, 33 | "inpainting_mask_invert": 1, 34 | "denoising_strength": 1, 35 | "inpainting_fill": 2, 36 | "inpaint_full_res": False, 37 | "inpaint_full_res_padding": 32, 38 | "resize_mode": 0, 39 | "override_settings": {}, 40 | "alwayson_scripts": { 41 | "controlnet": { 42 | "args": [ 43 | { 44 | "enabled":True, 45 | "module": "canny", 46 | "model": "control_v11p_sd15_canny", 47 | "weight": 1, 48 | # "image": "images/default/8A8088848B128072018B1280744F0001.png", 49 | "image": self.file_to_base64(), 50 | "resize_mode": 0, 51 | "rgbbgr_mode": False, 52 | "lowvram": False, 53 | "processor_res": 512, 54 | "threshold_a": 100, 55 | "threshold_b": 200, 56 | "guidance_start": 0, 57 | "guidance_end": 1, 58 | "control_mode": 0, 59 | "pixel_perfect": True 60 | } 61 | ] 62 | } 63 | } 64 | } 65 | 66 | def send_request(self): 67 | headers = {"Request-Type":"sync", "content-type": "application/json", "token":"MUEGw51wmaXZhYFpjn5lIUx6nIWnp3enzagiMaWAb1flAZEjbYWa2QFFZ1CxbS6g"} 68 | response = requests.post(url=self.url, json=self.body, headers=headers) 69 | return response.json() 70 | 71 | def read_image(self): 72 | img = cv2.imread(self.img_path) 73 | retval, bytes = cv2.imencode('.png', img) 74 | encoded_image = base64.b64encode(bytes).decode('utf-8') 75 | return encoded_image 76 | 77 | def file_to_base64(self): 78 | with open(self.img_path, "rb") as file: 79 | data = file.read() 80 | 81 | base64_str = str(base64.b64encode(data), "utf-8") 82 | return "data:image/png;base64," + base64_str 83 | # return base64_str 84 | 85 | 86 | if __name__ == '__main__': 87 | path = '/Users/xxxx/Desktop/8A8088848B128072018B1280744F0001.png' 88 | prompt = 'a large avalanche' 89 | 90 | control_net = ControlnetRequest(prompt, path) 91 | control_net.build_body() 92 | output = control_net.send_request() 93 | print(output) -------------------------------------------------------------------------------- /script/request/login.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | url = "https://localhost:8000/login" 5 | header = {"Request-Type": "sync", "Task-Flag": "true"} 6 | data={ 7 | "UserName":"xxxx", 8 | "Password":"xxxx" 9 | } 10 | r = requests.post(url, data=json.dumps(data)) 11 | print(r.content) -------------------------------------------------------------------------------- /script/request/resource.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | url = "http://localhost:7860/batch_update_sd_resource" 4 | headers = {"Request-Type":"sync", "content-type": "application/json", "token":"MUEGw51wmaXZhYFpjn5lIUx6nIWnp3enzagiMaWAb1flAZEjbYWa2QFFZ1CxbS6g"} 5 | s = json.dumps({ 6 | "models":[ 7 | "xxmix9realistic_v40.safetensors" 8 | ], 9 | "extraArgs":"--api --nowebui", 10 | "vpcConfig":{ 11 | "securityGroupId":"xxx", 12 | "vSwitchIds":[ 13 | "xxx" 14 | ], 15 | "vpcId":"xxx" 16 | } 17 | }) 18 | r = requests.post(url, data=s, headers=headers) 19 | print(r.json()) -------------------------------------------------------------------------------- /script/request/txt2img.py: -------------------------------------------------------------------------------- 1 | import random 2 | import cv2 3 | import base64 4 | import requests 5 | 6 | class ControlnetRequest: 7 | def __init__(self, prompt, path): 8 | self.url = "https://xxxxxxxxxx/txt2img" 9 | self.prompt = prompt 10 | self.img_path = path 11 | self.body = None 12 | self.L_models = ["xxmix9realistic_v40.safetensors", "chilloutmix_NiPrunedFp16Fix.safetensors", 13 | "Realistic_Vision_V2.0.ckpt", "v1-5-pruned-emaonly.ckpt", 14 | "墨幽人造人_v1010.safetensors","dreamshaper_7.safetensors", "GuoFeng3_v3.4.safetensors"] 15 | self.L_lora = ["","","","", 16 | ""] 17 | self.L_controlnet = ["control_v11p_sd15_canny", "control_v11p_sd15_scribble","control_v11f1p_sd15_depth", 18 | "control_v11p_sd15_lineart","control_v11p_sd15_openpose"] 19 | 20 | def select_models(self, L): 21 | idx = random.randint(0, len(L)-1) 22 | model = L[idx] 23 | return model 24 | # return "xxmix9realistic_v40.safetensors" 25 | 26 | def build_body(self): 27 | self.body = { 28 | "stable_diffusion_model": "xxmix9realistic_v40.safetensors", 29 | "sd_vae": "None", 30 | "prompt": "'masterpiece, best quality, very detailed, extremely detailed beautiful, super detailed, " 31 | "tousled hair, illustration, dynamic angles, girly, fashion clothing, standing, mannequin, " 32 | "looking at viewer, interview, beach, beautiful detailed eyes," 33 | " exquisitely beautiful face, floating, high saturation, " 34 | "beautiful and detailed light and shadow" + self.select_models(self.L_lora), 35 | "negative_prompt": "loli,nsfw,logo,text,badhandv4,EasyNegative,ng_deepnegative_v1_75t," 36 | "rev2-badprompt,verybadimagenegative_v1.3,negative_hand-neg,mutated hands and fingers," 37 | "poorly drawn face,extra limb,missing limb,disconnected limbs,malformed hands,ugly", 38 | "batch_size": 1, 39 | "steps": 30, 40 | "cfg_scale": 7, 41 | "alwayson_scripts": { 42 | "controlnet": { 43 | "args": [ 44 | { 45 | # "image":"images/default/mjDxugVFDr_1.png", 46 | "image":self.read_image(), 47 | "enabled":True, 48 | "module":"canny", 49 | "model": "control_v11p_sd15_canny", 50 | # "model":self.select_models(self.L_controlnet), 51 | "weight":1, 52 | "resize_mode":"Crop and Resize", 53 | "low_vram":False, 54 | "processor_res":512, 55 | "threshold_a":100, 56 | "threshold_b":200, 57 | "guidance_start":0, 58 | "guidance_end":1, 59 | "pixel_perfect":True, 60 | "control_mode":"Balanced", 61 | "input_mode":"simple", 62 | "batch_images":"", 63 | "output_dir":"", 64 | "loopback":False 65 | } 66 | ] 67 | } 68 | } 69 | } 70 | 71 | def send_request(self): 72 | headers = {"Request-Type":"sync", "content-type": "application/json", "token":"MUEGw51wmaXZhYFpjn5lIUx6nIWnp3enzagiMaWAb1flAZEjbYWa2QFFZ1CxbS6g"} 73 | response = requests.post(url=self.url, json=self.body, headers=headers) 74 | return response.json() 75 | 76 | def read_image(self): 77 | img = cv2.imread(self.img_path) 78 | retval, bytes = cv2.imencode('.png', img) 79 | encoded_image = base64.b64encode(bytes).decode('utf-8') 80 | return encoded_image 81 | 82 | def file_to_base64(self): 83 | with open(self.img_path, "rb") as file: 84 | data = file.read() 85 | 86 | base64_str = str(base64.b64encode(data), "utf-8") 87 | return "data:image/png;base64," + base64_str 88 | 89 | 90 | if __name__ == '__main__': 91 | path = '/Users/xxxx/Downloads/mjDxugVFDr_1.png' 92 | prompt = 'a large avalanche' 93 | 94 | control_net = ControlnetRequest(prompt, path) 95 | control_net.build_body() 96 | output = control_net.send_request() 97 | print(output) -------------------------------------------------------------------------------- /script/server-config.yaml: -------------------------------------------------------------------------------- 1 | package: handler 2 | generate: 3 | gin-server: true 4 | embedded-spec: true 5 | #additional-imports: 6 | # - package: github.com/devsapp/serverless-stable-diffusion-api/pkg/models 7 | # alias: . 8 | output: pkg/handler/interface.go --------------------------------------------------------------------------------