package main import ( "context" "encoding/hex" "strings" "time" rgw "github.com/myENA/radosgwadmin" rcl "github.com/myENA/restclient" "github.com/prometheus/client_golang/prometheus" "github.com/remeh/sizedwaitgroup" datamodels "gitlab.com/cyclops-utilities/datamodels" udrModels "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/udr/models" l "gitlab.com/cyclops-utilities/logging" ) var ( collector = "Objects" objects = "object" collectionStart int64 ) // collect handles the process of retrieving the information from the system. func collect() { l.Trace.Printf("[COLLECTION] The collection process has been started.\n") collectionStart = time.Now().UnixNano() metricTime.With(prometheus.Labels{"type": "Collection Start Time"}).Set(float64(collectionStart)) // Here comes the logic to retrieve the information from the system. rcfg := &rgw.Config{ AdminPath: cfg.RGW.AdminPath, AccessKeyID: cfg.RGW.AccessKeyID, ClientConfig: rcl.ClientConfig{ ClientTimeout: rcl.Duration(time.Second * 30), }, SecretAccessKey: cfg.RGW.SecretAccessKey, ServerURL: cfg.RGW.ServerURL, } a, e := rgw.NewAdminAPI(rcfg) if e != nil { l.Warning.Printf("[COLLECTION] Could not authenticate with RadosGW. Not obtaining data for this period. Error: %v\n", e.Error()) return } users, e := a.MListUsers(context.Background()) if e != nil { l.Warning.Printf("[COLLECTION] Error reading project lists from RGW. Not obtaining data for this period. Error: %v\n", e.Error()) return } apiCount := 0 dropCount := 0 swg := sizedwaitgroup.New(8) // fmt.Printf("Users = %v\n", users) for _, user := range users { // Goroutines start swg.Add() go func(u string) { defer swg.Done() // filter out users which are not Openstack projects if filterOpenstackProject(u) { // get all buckets for given user bucketList, e := a.BucketList(context.Background(), u) if e != nil { l.Warning.Printf("[COLLECTION] Error reading bucket list for user [ %v ] from RGW. Error: %v\n", u, e.Error()) } // l.Info.Printf("bucketlist = %v\n", bucketList) totalBuckets := len(bucketList) aggregateBucketSize := float64(0.0) apiCount += len(bucketList) for _, b := range bucketList { bucketStats, e := a.BucketStats(context.Background(), u, b) if e != nil { l.Warning.Printf("[COLLECTION] Error reading bucket list for user [ %v ] from RGW. Error: %v\n", u, e.Error()) } if len(bucketStats) == 0 { l.Warning.Printf("[COLLECTION] The length of bucket stats = 0 for the bucket [ %v ]\n", b) } else { d := datamodels.JSONdb{ "region": cfg.RGW.Region, "bucket": b, } // the Usage struct contains support for a number of different RGWs... // RGWMain, RGWShadow and a couple of others; here we just take // RGWMain as the base - if it's null, we don't publish anything to UDR // RGWMain contains SizeKbActual and SizeKb - SizeKBActual is used here; // this is the usage as perceived by the user, SizeKb is a bit larger with // the delta dependent on the basic minimum object size allocation if bucketStats[0].Usage.RGWMain != nil { usage := float64(bucketStats[0].Usage.RGWMain.SizeKb) if usage != 0 { // Here comes the transformation of the information retrieved into either // events or usage reports to be sent. usageReport := udrModels.Usage{ Account: u, Metadata: d, ResourceType: "objectstorage", Time: time.Now().Unix(), Unit: "GB", Usage: float64((usage / float64(1024)) / float64(1024)), } report(usageReport) aggregateBucketSize += usageReport.Usage } } else { l.Warning.Printf("[COLLECTION] There's no information for RGWMain for the bucket [ %v ]. Ignoring...\n", b) dropCount++ } } } l.Info.Printf("[COLLECTION] Wrote data for user [ %v ]. AggregateBucketSize [ %v ] distributed over [ %v ] buckets.\n", u, aggregateBucketSize, totalBuckets) } }(user) } swg.Wait() metricCount.With(prometheus.Labels{"type": "Total Objects reported by OS API"}).Set(float64(apiCount)) metricCount.With(prometheus.Labels{"type": "Total Objects DROPPED due to missing information"}).Set(float64(dropCount)) metricTime.With(prometheus.Labels{"type": "Collection Processing Time"}).Set(float64(time.Now().UnixNano()-collectionStart) / float64(time.Millisecond)) l.Warning.Printf("[COLLECTION] Completed.\n OS Report: %v\n, Dropped: %v\n, Processing Time: %v[ms]\n", apiCount, dropCount, float64(time.Now().UnixNano()-collectionStart)/float64(time.Millisecond)) l.Trace.Printf("[COLLECTION] The collection process has been finished.\n") return } // filterOpenstackProject checks if the user name looks like an Openstack // project. // Parameters: // - p: string representing the user. // Returns: // - a bool with the result of the check. func filterOpenstackProject(p string) bool { _, e := hex.DecodeString(p) return (len(p) == 32) && (e == nil) } // getStatus job is to normalize the event state returned by the collectors. // Parameters: // - state: string returned by the system. // Returns: // - status: normalized state to be returned. func getStatus(state string) (status string) { switch strings.ToUpper(state) { case "ACTIVE": status = "active" case "ATTACHING": status = "active" case "AVAILABLE": status = "active" case "BUILD": status = "inactive" case "CREATING": status = "active" case "DELETED": status = "terminated" case "DELETING": status = "terminated" case "DETACHING": status = "active" case "DOWN": status = "inactive" case "ERROR": status = "error" case "ERROR_DELETING": status = "error" case "EXTENDING": status = "inactive" case "HARD_DELETED": status = "terminated" case "IN-USE": status = "active" case "MAINTENANCE": status = "active" case "PAUSED": status = "inactive" case "RESCUED": status = "active" case "RESIZE": status = "active" case "RESIZED": status = "active" case "RESERVED": status = "active" case "RETYPING": status = "inactive" case "SHUTOFF": status = "inactive" case "SOFT_DELETED": status = "terminated" case "STOPPED": status = "inactive" case "SUSPENDED": status = "inactive" case "TERMINATED": status = "terminated" case "VERIFY_RESIZE": status = "active" } l.Trace.Printf("[REPORT] State received from the system [ %v ] normalized to [ %v ]", state, status) return status }