github.com/minio/console@v1.3.0/api/admin_health_info.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"context"
    21  	b64 "encoding/base64"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"net/http"
    26  	"net/url"
    27  	"os"
    28  	"time"
    29  
    30  	"github.com/minio/console/pkg/logger"
    31  	"github.com/minio/console/pkg/utils"
    32  
    33  	subnet "github.com/minio/console/pkg/subnet"
    34  	"github.com/minio/madmin-go/v3"
    35  	mc "github.com/minio/mc/cmd"
    36  	"github.com/minio/websocket"
    37  )
    38  
    39  // startHealthInfo starts fetching mc.ServerHealthInfo and
    40  // sends messages with the corresponding data on the websocket connection
    41  func startHealthInfo(ctx context.Context, conn WSConn, client MinioAdmin, deadline *time.Duration) error {
    42  	if deadline == nil {
    43  		return errors.New("duration can't be nil on startHealthInfo")
    44  	}
    45  
    46  	// Fetch info of all servers (cluster or single server)
    47  	healthDataTypes := []madmin.HealthDataType{
    48  		madmin.HealthDataTypeMinioInfo,
    49  		madmin.HealthDataTypeMinioConfig,
    50  		madmin.HealthDataTypeSysCPU,
    51  		madmin.HealthDataTypeSysDriveHw,
    52  		madmin.HealthDataTypeSysDocker,
    53  		madmin.HealthDataTypeSysOsInfo,
    54  		madmin.HealthDataTypeSysLoad,
    55  		madmin.HealthDataTypeSysMem,
    56  		madmin.HealthDataTypeSysNet,
    57  		madmin.HealthDataTypeSysProcess,
    58  	}
    59  	var err error
    60  	// Fetch info of all servers (cluster or single server)
    61  	healthInfo, version, err := client.serverHealthInfo(ctx, healthDataTypes, *deadline)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	compressedDiag, err := mc.TarGZHealthInfo(healthInfo, version)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	encodedDiag := b64.StdEncoding.EncodeToString(compressedDiag)
    71  	type messageReport struct {
    72  		Encoded          string      `json:"encoded"`
    73  		ServerHealthInfo interface{} `json:"serverHealthInfo"`
    74  		SubnetResponse   string      `json:"subnetResponse"`
    75  	}
    76  
    77  	ctx = context.WithValue(ctx, utils.ContextClientIP, conn.remoteAddress())
    78  	err = sendHealthInfoToSubnet(ctx, healthInfo, client)
    79  	report := messageReport{
    80  		Encoded:          encodedDiag,
    81  		ServerHealthInfo: healthInfo,
    82  		SubnetResponse:   mc.SubnetBaseURL() + "/health",
    83  	}
    84  	if err != nil {
    85  		report.SubnetResponse = fmt.Sprintf("Error: %s", err.Error())
    86  	}
    87  
    88  	message, err := json.Marshal(report)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	// Send Message through websocket connection
    94  	return conn.writeMessage(websocket.TextMessage, message)
    95  }
    96  
    97  // getHealthInfoOptionsFromReq gets duration for startHealthInfo request
    98  // path come as : `/health-info?deadline=2h`
    99  func getHealthInfoOptionsFromReq(req *http.Request) (*time.Duration, error) {
   100  	deadlineDuration, err := time.ParseDuration(req.FormValue("deadline"))
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	return &deadlineDuration, nil
   105  }
   106  
   107  func updateMcGlobals(subnetTokenConfig subnet.LicenseTokenConfig) error {
   108  	mc.GlobalDevMode = getConsoleDevMode()
   109  	if len(subnetTokenConfig.Proxy) > 0 {
   110  		proxyURL, e := url.Parse(subnetTokenConfig.Proxy)
   111  		if e != nil {
   112  			return e
   113  		}
   114  		mc.GlobalSubnetProxyURL = proxyURL
   115  	}
   116  	return nil
   117  }
   118  
   119  func sendHealthInfoToSubnet(ctx context.Context, healthInfo interface{}, client MinioAdmin) error {
   120  	filename := fmt.Sprintf("health_%d.json.gz", time.Now().Unix())
   121  	subnetTokenConfig, e := GetSubnetKeyFromMinIOConfig(ctx, client)
   122  	if e != nil {
   123  		return e
   124  	}
   125  	e = updateMcGlobals(*subnetTokenConfig)
   126  	if e != nil {
   127  		return e
   128  	}
   129  	var apiKey string
   130  	if len(subnetTokenConfig.APIKey) != 0 {
   131  		apiKey = subnetTokenConfig.APIKey
   132  	} else {
   133  		apiKey, e = subnet.GetSubnetAPIKeyUsingLicense(subnetTokenConfig.License)
   134  		if e != nil {
   135  			return e
   136  		}
   137  	}
   138  	compressedHealthInfo, e := mc.TarGZHealthInfo(healthInfo, madmin.HealthInfoVersion)
   139  	if e != nil {
   140  		return e
   141  	}
   142  	e = os.WriteFile(filename, compressedHealthInfo, 0o666)
   143  	if e != nil {
   144  		return e
   145  	}
   146  	headers := mc.SubnetAPIKeyAuthHeaders(apiKey)
   147  	resp, e := (&mc.SubnetFileUploader{
   148  		FilePath:          filename,
   149  		ReqURL:            mc.SubnetUploadURL("health"),
   150  		Headers:           headers,
   151  		DeleteAfterUpload: true,
   152  	}).UploadFileToSubnet()
   153  	if e != nil {
   154  		// file gets deleted only if upload is successful
   155  		// so we delete explicitly here as we already have the bytes
   156  		logger.LogIf(ctx, os.Remove(filename))
   157  		return e
   158  	}
   159  
   160  	type SubnetResponse struct {
   161  		LicenseV2 string `json:"license_v2,omitempty"`
   162  		APIKey    string `json:"api_key,omitempty"`
   163  	}
   164  
   165  	var subnetResp SubnetResponse
   166  	e = json.Unmarshal([]byte(resp), &subnetResp)
   167  	if e != nil {
   168  		return e
   169  	}
   170  
   171  	return nil
   172  }