github.com/cs3org/reva/v2@v2.27.7/pkg/mentix/exchangers/exporters/promsd.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package exporters
    20  
    21  import (
    22  	"encoding/json"
    23  	"fmt"
    24  	"net/url"
    25  	"os"
    26  	"path"
    27  	"path/filepath"
    28  	"strings"
    29  
    30  	"github.com/cs3org/reva/v2/pkg/mentix/utils"
    31  	"github.com/rs/zerolog"
    32  
    33  	"github.com/cs3org/reva/v2/pkg/mentix/config"
    34  	"github.com/cs3org/reva/v2/pkg/mentix/exchangers/exporters/prometheus"
    35  	"github.com/cs3org/reva/v2/pkg/mentix/meshdata"
    36  )
    37  
    38  type prometheusSDScrapeCreatorCallback = func(site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) *prometheus.ScrapeConfig
    39  type prometheusSDScrapeCreator struct {
    40  	outputFilename  string
    41  	creatorCallback prometheusSDScrapeCreatorCallback
    42  	serviceFilter   []string
    43  }
    44  
    45  // PrometheusSDExporter implements various Prometheus Service Discovery scrape config exporters.
    46  type PrometheusSDExporter struct {
    47  	BaseExporter
    48  
    49  	scrapeCreators map[string]prometheusSDScrapeCreator
    50  }
    51  
    52  const (
    53  	labelSiteName    = "__meta_mentix_site"
    54  	labelSiteID      = "__meta_mentix_site_id"
    55  	labelSiteCountry = "__meta_mentix_site_country"
    56  	labelType        = "__meta_mentix_type"
    57  	labelURL         = "__meta_mentix_url"
    58  	labelScheme      = "__meta_mentix_scheme"
    59  	labelHost        = "__meta_mentix_host"
    60  	labelPort        = "__meta_mentix_port"
    61  	labelPath        = "__meta_mentix_path"
    62  	labelServiceHost = "__meta_mentix_service_host"
    63  	labelServiceURL  = "__meta_mentix_service_url"
    64  )
    65  
    66  func createGenericScrapeConfig(site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) *prometheus.ScrapeConfig {
    67  	endpointURL, _ := url.Parse(endpoint.URL)
    68  	labels := getScrapeTargetLabels(site, service, endpoint)
    69  	return &prometheus.ScrapeConfig{
    70  		Targets: []string{endpointURL.Host},
    71  		Labels:  labels,
    72  	}
    73  }
    74  func getScrapeTargetLabels(site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) map[string]string {
    75  	endpointURL, _ := url.Parse(endpoint.URL)
    76  	labels := map[string]string{
    77  		labelSiteName:    site.Name,
    78  		labelSiteID:      site.ID,
    79  		labelSiteCountry: site.CountryCode,
    80  		labelType:        endpoint.Type.Name,
    81  		labelURL:         endpoint.URL,
    82  		labelScheme:      endpointURL.Scheme,
    83  		labelHost:        endpointURL.Hostname(),
    84  		labelPort:        endpointURL.Port(),
    85  		labelPath:        endpointURL.Path,
    86  		labelServiceHost: service.Host,
    87  		labelServiceURL:  service.URL,
    88  	}
    89  
    90  	return labels
    91  }
    92  
    93  func (exporter *PrometheusSDExporter) registerScrapeCreators(conf *config.Configuration) error {
    94  	exporter.scrapeCreators = make(map[string]prometheusSDScrapeCreator)
    95  
    96  	registerCreator := func(name string, outputFilename string, creator prometheusSDScrapeCreatorCallback, serviceFilter []string) error {
    97  		if len(outputFilename) > 0 { // Only register the creator if an output filename was configured
    98  			exporter.scrapeCreators[name] = prometheusSDScrapeCreator{
    99  				outputFilename:  outputFilename,
   100  				creatorCallback: creator,
   101  				serviceFilter:   serviceFilter,
   102  			}
   103  
   104  			// Create the output directory for the target file so it exists when exporting
   105  			if err := os.MkdirAll(filepath.Dir(outputFilename), 0755); err != nil {
   106  				return fmt.Errorf("unable to create output directory tree: %v", err)
   107  			}
   108  		}
   109  
   110  		return nil
   111  	}
   112  
   113  	// Register all scrape creators
   114  	for _, endpoint := range meshdata.GetServiceEndpoints() {
   115  		epName := strings.ToLower(endpoint)
   116  		filename := path.Join(conf.Exporters.PrometheusSD.OutputPath, "svc_"+epName+".json")
   117  
   118  		if err := registerCreator(epName, filename, createGenericScrapeConfig, []string{endpoint}); err != nil {
   119  			return fmt.Errorf("unable to register the '%v' scrape config creator: %v", epName, err)
   120  		}
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  // Activate activates the exporter.
   127  func (exporter *PrometheusSDExporter) Activate(conf *config.Configuration, log *zerolog.Logger) error {
   128  	if err := exporter.BaseExporter.Activate(conf, log); err != nil {
   129  		return err
   130  	}
   131  
   132  	if err := exporter.registerScrapeCreators(conf); err != nil {
   133  		return fmt.Errorf("unable to register the scrape creators: %v", err)
   134  	}
   135  
   136  	// Create all output directories
   137  	for _, creator := range exporter.scrapeCreators {
   138  		if err := os.MkdirAll(filepath.Dir(creator.outputFilename), 0755); err != nil {
   139  			return fmt.Errorf("unable to create directory tree: %v", err)
   140  		}
   141  	}
   142  
   143  	// Store PrometheusSD specifics
   144  	exporter.SetEnabledConnectors(conf.Exporters.PrometheusSD.EnabledConnectors)
   145  
   146  	return nil
   147  }
   148  
   149  // Update is called whenever the mesh data set has changed to reflect these changes.
   150  func (exporter *PrometheusSDExporter) Update(meshDataSet meshdata.Map) error {
   151  	if err := exporter.BaseExporter.Update(meshDataSet); err != nil {
   152  		return err
   153  	}
   154  
   155  	// Perform exporting the data asynchronously
   156  	go exporter.exportMeshData()
   157  	return nil
   158  }
   159  
   160  func (exporter *PrometheusSDExporter) exportMeshData() {
   161  	// Data is read, so acquire a read lock
   162  	exporter.Locker().RLock()
   163  	defer exporter.Locker().RUnlock()
   164  
   165  	for name, creator := range exporter.scrapeCreators {
   166  		scrapes := exporter.createScrapeConfigs(creator.creatorCallback, creator.serviceFilter)
   167  		if err := exporter.exportScrapeConfig(creator.outputFilename, scrapes); err != nil {
   168  			exporter.Log().Err(err).Str("kind", name).Str("file", creator.outputFilename).Msg("error exporting Prometheus SD scrape config")
   169  		} else {
   170  			exporter.Log().Debug().Str("kind", name).Str("file", creator.outputFilename).Msg("exported Prometheus SD scrape config")
   171  		}
   172  	}
   173  }
   174  
   175  func (exporter *PrometheusSDExporter) createScrapeConfigs(creatorCallback prometheusSDScrapeCreatorCallback, serviceFilter []string) []*prometheus.ScrapeConfig {
   176  	var scrapes []*prometheus.ScrapeConfig
   177  	var addScrape = func(site *meshdata.Site, service *meshdata.Service, endpoint *meshdata.ServiceEndpoint) {
   178  		if len(serviceFilter) == 0 || utils.FindInStringArray(endpoint.Type.Name, serviceFilter, false) != -1 {
   179  			if scrape := creatorCallback(site, service, endpoint); scrape != nil {
   180  				scrapes = append(scrapes, scrape)
   181  			}
   182  		}
   183  	}
   184  
   185  	// Create a scrape config for each service alongside any additional endpoints
   186  	for _, site := range exporter.MeshData().Sites {
   187  		for _, service := range site.Services {
   188  			if !service.IsMonitored {
   189  				continue
   190  			}
   191  
   192  			// Add the "main" service to the scrapes
   193  			addScrape(site, service, service.ServiceEndpoint)
   194  
   195  			// Add all additional endpoints as well
   196  			for _, endpoint := range service.AdditionalEndpoints {
   197  				if endpoint.IsMonitored {
   198  					addScrape(site, service, endpoint)
   199  				}
   200  			}
   201  		}
   202  	}
   203  
   204  	if scrapes == nil {
   205  		scrapes = []*prometheus.ScrapeConfig{}
   206  	}
   207  
   208  	return scrapes
   209  }
   210  
   211  func (exporter *PrometheusSDExporter) exportScrapeConfig(outputFilename string, v interface{}) error {
   212  	// Encode scrape config as JSON
   213  	data, err := json.MarshalIndent(v, "", "\t")
   214  	if err != nil {
   215  		return fmt.Errorf("unable to marshal scrape config: %v", err)
   216  	}
   217  
   218  	// Write the data to disk
   219  	if err := os.WriteFile(outputFilename, data, 0755); err != nil {
   220  		return fmt.Errorf("unable to write scrape config '%v': %v", outputFilename, err)
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  // GetID returns the ID of the exporter.
   227  func (exporter *PrometheusSDExporter) GetID() string {
   228  	return config.ExporterIDPrometheusSD
   229  }
   230  
   231  // GetName returns the display name of the exporter.
   232  func (exporter *PrometheusSDExporter) GetName() string {
   233  	return "Prometheus SD"
   234  }
   235  
   236  func init() {
   237  	registerExporter(&PrometheusSDExporter{})
   238  }