| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781 |
- package controller
- import (
- "fmt"
- "strconv"
- "strings"
- "time"
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/pkg/ionet"
- "github.com/gin-gonic/gin"
- )
- func getIoAPIKey(c *gin.Context) (string, bool) {
- common.OptionMapRWMutex.RLock()
- enabled := common.OptionMap["model_deployment.ionet.enabled"] == "true"
- apiKey := common.OptionMap["model_deployment.ionet.api_key"]
- common.OptionMapRWMutex.RUnlock()
- if !enabled || strings.TrimSpace(apiKey) == "" {
- common.ApiErrorMsg(c, "io.net model deployment is not enabled or api key missing")
- return "", false
- }
- return apiKey, true
- }
- func getIoClient(c *gin.Context) (*ionet.Client, bool) {
- apiKey, ok := getIoAPIKey(c)
- if !ok {
- return nil, false
- }
- return ionet.NewClient(apiKey), true
- }
- func getIoEnterpriseClient(c *gin.Context) (*ionet.Client, bool) {
- apiKey, ok := getIoAPIKey(c)
- if !ok {
- return nil, false
- }
- return ionet.NewEnterpriseClient(apiKey), true
- }
- func TestIoNetConnection(c *gin.Context) {
- var req struct {
- APIKey string `json:"api_key"`
- }
- if err := c.ShouldBindJSON(&req); err != nil {
- common.ApiErrorMsg(c, "invalid request payload")
- return
- }
- apiKey := strings.TrimSpace(req.APIKey)
- if apiKey == "" {
- common.ApiErrorMsg(c, "api_key is required")
- return
- }
- client := ionet.NewEnterpriseClient(apiKey)
- result, err := client.GetMaxGPUsPerContainer()
- if err != nil {
- if apiErr, ok := err.(*ionet.APIError); ok {
- message := strings.TrimSpace(apiErr.Message)
- if message == "" {
- message = "failed to validate api key"
- }
- common.ApiErrorMsg(c, message)
- return
- }
- common.ApiError(c, err)
- return
- }
- totalHardware := 0
- totalAvailable := 0
- if result != nil {
- totalHardware = len(result.Hardware)
- totalAvailable = result.Total
- if totalAvailable == 0 {
- for _, hw := range result.Hardware {
- totalAvailable += hw.Available
- }
- }
- }
- common.ApiSuccess(c, gin.H{
- "hardware_count": totalHardware,
- "total_available": totalAvailable,
- })
- }
- func requireDeploymentID(c *gin.Context) (string, bool) {
- deploymentID := strings.TrimSpace(c.Param("id"))
- if deploymentID == "" {
- common.ApiErrorMsg(c, "deployment ID is required")
- return "", false
- }
- return deploymentID, true
- }
- func requireContainerID(c *gin.Context) (string, bool) {
- containerID := strings.TrimSpace(c.Param("container_id"))
- if containerID == "" {
- common.ApiErrorMsg(c, "container ID is required")
- return "", false
- }
- return containerID, true
- }
- func mapIoNetDeployment(d ionet.Deployment) map[string]interface{} {
- var created int64
- if d.CreatedAt.IsZero() {
- created = time.Now().Unix()
- } else {
- created = d.CreatedAt.Unix()
- }
- timeRemainingHours := d.ComputeMinutesRemaining / 60
- timeRemainingMins := d.ComputeMinutesRemaining % 60
- var timeRemaining string
- if timeRemainingHours > 0 {
- timeRemaining = fmt.Sprintf("%d hour %d minutes", timeRemainingHours, timeRemainingMins)
- } else if timeRemainingMins > 0 {
- timeRemaining = fmt.Sprintf("%d minutes", timeRemainingMins)
- } else {
- timeRemaining = "completed"
- }
- hardwareInfo := fmt.Sprintf("%s %s x%d", d.BrandName, d.HardwareName, d.HardwareQuantity)
- return map[string]interface{}{
- "id": d.ID,
- "deployment_name": d.Name,
- "container_name": d.Name,
- "status": strings.ToLower(d.Status),
- "type": "Container",
- "time_remaining": timeRemaining,
- "time_remaining_minutes": d.ComputeMinutesRemaining,
- "hardware_info": hardwareInfo,
- "hardware_name": d.HardwareName,
- "brand_name": d.BrandName,
- "hardware_quantity": d.HardwareQuantity,
- "completed_percent": d.CompletedPercent,
- "compute_minutes_served": d.ComputeMinutesServed,
- "compute_minutes_remaining": d.ComputeMinutesRemaining,
- "created_at": created,
- "updated_at": created,
- "model_name": "",
- "model_version": "",
- "instance_count": d.HardwareQuantity,
- "resource_config": map[string]interface{}{
- "cpu": "",
- "memory": "",
- "gpu": strconv.Itoa(d.HardwareQuantity),
- },
- "description": "",
- "provider": "io.net",
- }
- }
- func computeStatusCounts(total int, deployments []ionet.Deployment) map[string]int64 {
- counts := map[string]int64{
- "all": int64(total),
- }
- for _, status := range []string{"running", "completed", "failed", "deployment requested", "termination requested", "destroyed"} {
- counts[status] = 0
- }
- for _, d := range deployments {
- status := strings.ToLower(strings.TrimSpace(d.Status))
- counts[status] = counts[status] + 1
- }
- return counts
- }
- func GetAllDeployments(c *gin.Context) {
- pageInfo := common.GetPageQuery(c)
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- status := c.Query("status")
- opts := &ionet.ListDeploymentsOptions{
- Status: strings.ToLower(strings.TrimSpace(status)),
- Page: pageInfo.GetPage(),
- PageSize: pageInfo.GetPageSize(),
- SortBy: "created_at",
- SortOrder: "desc",
- }
- dl, err := client.ListDeployments(opts)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- items := make([]map[string]interface{}, 0, len(dl.Deployments))
- for _, d := range dl.Deployments {
- items = append(items, mapIoNetDeployment(d))
- }
- data := gin.H{
- "page": pageInfo.GetPage(),
- "page_size": pageInfo.GetPageSize(),
- "total": dl.Total,
- "items": items,
- "status_counts": computeStatusCounts(dl.Total, dl.Deployments),
- }
- common.ApiSuccess(c, data)
- }
- func SearchDeployments(c *gin.Context) {
- pageInfo := common.GetPageQuery(c)
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- status := strings.ToLower(strings.TrimSpace(c.Query("status")))
- keyword := strings.TrimSpace(c.Query("keyword"))
- dl, err := client.ListDeployments(&ionet.ListDeploymentsOptions{
- Status: status,
- Page: pageInfo.GetPage(),
- PageSize: pageInfo.GetPageSize(),
- SortBy: "created_at",
- SortOrder: "desc",
- })
- if err != nil {
- common.ApiError(c, err)
- return
- }
- filtered := make([]ionet.Deployment, 0, len(dl.Deployments))
- if keyword == "" {
- filtered = dl.Deployments
- } else {
- kw := strings.ToLower(keyword)
- for _, d := range dl.Deployments {
- if strings.Contains(strings.ToLower(d.Name), kw) {
- filtered = append(filtered, d)
- }
- }
- }
- items := make([]map[string]interface{}, 0, len(filtered))
- for _, d := range filtered {
- items = append(items, mapIoNetDeployment(d))
- }
- total := dl.Total
- if keyword != "" {
- total = len(filtered)
- }
- data := gin.H{
- "page": pageInfo.GetPage(),
- "page_size": pageInfo.GetPageSize(),
- "total": total,
- "items": items,
- }
- common.ApiSuccess(c, data)
- }
- func GetDeployment(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- deploymentID, ok := requireDeploymentID(c)
- if !ok {
- return
- }
- details, err := client.GetDeployment(deploymentID)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- data := map[string]interface{}{
- "id": details.ID,
- "deployment_name": details.ID,
- "model_name": "",
- "model_version": "",
- "status": strings.ToLower(details.Status),
- "instance_count": details.TotalContainers,
- "hardware_id": details.HardwareID,
- "resource_config": map[string]interface{}{
- "cpu": "",
- "memory": "",
- "gpu": strconv.Itoa(details.TotalGPUs),
- },
- "created_at": details.CreatedAt.Unix(),
- "updated_at": details.CreatedAt.Unix(),
- "description": "",
- "amount_paid": details.AmountPaid,
- "completed_percent": details.CompletedPercent,
- "gpus_per_container": details.GPUsPerContainer,
- "total_gpus": details.TotalGPUs,
- "total_containers": details.TotalContainers,
- "hardware_name": details.HardwareName,
- "brand_name": details.BrandName,
- "compute_minutes_served": details.ComputeMinutesServed,
- "compute_minutes_remaining": details.ComputeMinutesRemaining,
- "locations": details.Locations,
- "container_config": details.ContainerConfig,
- }
- common.ApiSuccess(c, data)
- }
- func UpdateDeploymentName(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- deploymentID, ok := requireDeploymentID(c)
- if !ok {
- return
- }
- var req struct {
- Name string `json:"name" binding:"required"`
- }
- if err := c.ShouldBindJSON(&req); err != nil {
- common.ApiError(c, err)
- return
- }
- updateReq := &ionet.UpdateClusterNameRequest{
- Name: strings.TrimSpace(req.Name),
- }
- if updateReq.Name == "" {
- common.ApiErrorMsg(c, "deployment name cannot be empty")
- return
- }
- available, err := client.CheckClusterNameAvailability(updateReq.Name)
- if err != nil {
- common.ApiError(c, fmt.Errorf("failed to check name availability: %w", err))
- return
- }
- if !available {
- common.ApiErrorMsg(c, "deployment name is not available, please choose a different name")
- return
- }
- resp, err := client.UpdateClusterName(deploymentID, updateReq)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- data := gin.H{
- "status": resp.Status,
- "message": resp.Message,
- "id": deploymentID,
- "name": updateReq.Name,
- }
- common.ApiSuccess(c, data)
- }
- func UpdateDeployment(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- deploymentID, ok := requireDeploymentID(c)
- if !ok {
- return
- }
- var req ionet.UpdateDeploymentRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- common.ApiError(c, err)
- return
- }
- resp, err := client.UpdateDeployment(deploymentID, &req)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- data := gin.H{
- "status": resp.Status,
- "deployment_id": resp.DeploymentID,
- }
- common.ApiSuccess(c, data)
- }
- func ExtendDeployment(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- deploymentID, ok := requireDeploymentID(c)
- if !ok {
- return
- }
- var req ionet.ExtendDurationRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- common.ApiError(c, err)
- return
- }
- details, err := client.ExtendDeployment(deploymentID, &req)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- data := mapIoNetDeployment(ionet.Deployment{
- ID: details.ID,
- Status: details.Status,
- Name: deploymentID,
- CompletedPercent: float64(details.CompletedPercent),
- HardwareQuantity: details.TotalGPUs,
- BrandName: details.BrandName,
- HardwareName: details.HardwareName,
- ComputeMinutesServed: details.ComputeMinutesServed,
- ComputeMinutesRemaining: details.ComputeMinutesRemaining,
- CreatedAt: details.CreatedAt,
- })
- common.ApiSuccess(c, data)
- }
- func DeleteDeployment(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- deploymentID, ok := requireDeploymentID(c)
- if !ok {
- return
- }
- resp, err := client.DeleteDeployment(deploymentID)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- data := gin.H{
- "status": resp.Status,
- "deployment_id": resp.DeploymentID,
- "message": "Deployment termination requested successfully",
- }
- common.ApiSuccess(c, data)
- }
- func CreateDeployment(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- var req ionet.DeploymentRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- common.ApiError(c, err)
- return
- }
- resp, err := client.DeployContainer(&req)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- data := gin.H{
- "deployment_id": resp.DeploymentID,
- "status": resp.Status,
- "message": "Deployment created successfully",
- }
- common.ApiSuccess(c, data)
- }
- func GetHardwareTypes(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- hardwareTypes, totalAvailable, err := client.ListHardwareTypes()
- if err != nil {
- common.ApiError(c, err)
- return
- }
- data := gin.H{
- "hardware_types": hardwareTypes,
- "total": len(hardwareTypes),
- "total_available": totalAvailable,
- }
- common.ApiSuccess(c, data)
- }
- func GetLocations(c *gin.Context) {
- client, ok := getIoClient(c)
- if !ok {
- return
- }
- locationsResp, err := client.ListLocations()
- if err != nil {
- common.ApiError(c, err)
- return
- }
- total := locationsResp.Total
- if total == 0 {
- total = len(locationsResp.Locations)
- }
- data := gin.H{
- "locations": locationsResp.Locations,
- "total": total,
- }
- common.ApiSuccess(c, data)
- }
- func GetAvailableReplicas(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- hardwareIDStr := c.Query("hardware_id")
- gpuCountStr := c.Query("gpu_count")
- if hardwareIDStr == "" {
- common.ApiErrorMsg(c, "hardware_id parameter is required")
- return
- }
- hardwareID, err := strconv.Atoi(hardwareIDStr)
- if err != nil || hardwareID <= 0 {
- common.ApiErrorMsg(c, "invalid hardware_id parameter")
- return
- }
- gpuCount := 1
- if gpuCountStr != "" {
- if parsed, err := strconv.Atoi(gpuCountStr); err == nil && parsed > 0 {
- gpuCount = parsed
- }
- }
- replicas, err := client.GetAvailableReplicas(hardwareID, gpuCount)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- common.ApiSuccess(c, replicas)
- }
- func GetPriceEstimation(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- var req ionet.PriceEstimationRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- common.ApiError(c, err)
- return
- }
- priceResp, err := client.GetPriceEstimation(&req)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- common.ApiSuccess(c, priceResp)
- }
- func CheckClusterNameAvailability(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- clusterName := strings.TrimSpace(c.Query("name"))
- if clusterName == "" {
- common.ApiErrorMsg(c, "name parameter is required")
- return
- }
- available, err := client.CheckClusterNameAvailability(clusterName)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- data := gin.H{
- "available": available,
- "name": clusterName,
- }
- common.ApiSuccess(c, data)
- }
- func GetDeploymentLogs(c *gin.Context) {
- client, ok := getIoClient(c)
- if !ok {
- return
- }
- deploymentID, ok := requireDeploymentID(c)
- if !ok {
- return
- }
- containerID := c.Query("container_id")
- if containerID == "" {
- common.ApiErrorMsg(c, "container_id parameter is required")
- return
- }
- level := c.Query("level")
- stream := c.Query("stream")
- cursor := c.Query("cursor")
- limitStr := c.Query("limit")
- follow := c.Query("follow") == "true"
- var limit int = 100
- if limitStr != "" {
- if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
- limit = parsedLimit
- if limit > 1000 {
- limit = 1000
- }
- }
- }
- opts := &ionet.GetLogsOptions{
- Level: level,
- Stream: stream,
- Limit: limit,
- Cursor: cursor,
- Follow: follow,
- }
- if startTime := c.Query("start_time"); startTime != "" {
- if t, err := time.Parse(time.RFC3339, startTime); err == nil {
- opts.StartTime = &t
- }
- }
- if endTime := c.Query("end_time"); endTime != "" {
- if t, err := time.Parse(time.RFC3339, endTime); err == nil {
- opts.EndTime = &t
- }
- }
- rawLogs, err := client.GetContainerLogsRaw(deploymentID, containerID, opts)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- common.ApiSuccess(c, rawLogs)
- }
- func ListDeploymentContainers(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- deploymentID, ok := requireDeploymentID(c)
- if !ok {
- return
- }
- containers, err := client.ListContainers(deploymentID)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- items := make([]map[string]interface{}, 0)
- if containers != nil {
- items = make([]map[string]interface{}, 0, len(containers.Workers))
- for _, ctr := range containers.Workers {
- events := make([]map[string]interface{}, 0, len(ctr.ContainerEvents))
- for _, event := range ctr.ContainerEvents {
- events = append(events, map[string]interface{}{
- "time": event.Time.Unix(),
- "message": event.Message,
- })
- }
- items = append(items, map[string]interface{}{
- "container_id": ctr.ContainerID,
- "device_id": ctr.DeviceID,
- "status": strings.ToLower(strings.TrimSpace(ctr.Status)),
- "hardware": ctr.Hardware,
- "brand_name": ctr.BrandName,
- "created_at": ctr.CreatedAt.Unix(),
- "uptime_percent": ctr.UptimePercent,
- "gpus_per_container": ctr.GPUsPerContainer,
- "public_url": ctr.PublicURL,
- "events": events,
- })
- }
- }
- response := gin.H{
- "total": 0,
- "containers": items,
- }
- if containers != nil {
- response["total"] = containers.Total
- }
- common.ApiSuccess(c, response)
- }
- func GetContainerDetails(c *gin.Context) {
- client, ok := getIoEnterpriseClient(c)
- if !ok {
- return
- }
- deploymentID, ok := requireDeploymentID(c)
- if !ok {
- return
- }
- containerID, ok := requireContainerID(c)
- if !ok {
- return
- }
- details, err := client.GetContainerDetails(deploymentID, containerID)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- if details == nil {
- common.ApiErrorMsg(c, "container details not found")
- return
- }
- events := make([]map[string]interface{}, 0, len(details.ContainerEvents))
- for _, event := range details.ContainerEvents {
- events = append(events, map[string]interface{}{
- "time": event.Time.Unix(),
- "message": event.Message,
- })
- }
- data := gin.H{
- "deployment_id": deploymentID,
- "container_id": details.ContainerID,
- "device_id": details.DeviceID,
- "status": strings.ToLower(strings.TrimSpace(details.Status)),
- "hardware": details.Hardware,
- "brand_name": details.BrandName,
- "created_at": details.CreatedAt.Unix(),
- "uptime_percent": details.UptimePercent,
- "gpus_per_container": details.GPUsPerContainer,
- "public_url": details.PublicURL,
- "events": events,
- }
- common.ApiSuccess(c, data)
- }
|