Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a CS3API compliant data exporter to Mentix #955

Merged
merged 13 commits into from
Jul 9, 2020
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Supported values are:

- **webapi**
Mentix exposes its data via an HTTP endpoint using the `webapi` exporter. Data can be retrieved at the configured relative endpoint (see [here](webapi)). The web API currently doesn't support any parameters but will most likely be extended in the future.
- **cs3api** Similar to the WebAPI exporter, the `cs3api` exporter exposes its data via an HTTP endpoint. Data can be retrieved at the configured relative endpoint (see [here](cs3api)). The data is compliant with the CS3API `ProviderInfo` structure.
- **prom_filesd**
[Prometheus](https://prometheus.io/) supports discovering new services it should monitor via external configuration files (hence, this is called _file-based service discovery_). Mentix can create such files using the `prom_filesd` exporter. To use this exporter, you have to specify the target output file in the configuration (see [here](prom_filesd)). You also have to set up the discovery service in Prometheus by adding a scrape configuration like the following example to the Prometheus configuration (for more information, visit the official [Prometheus documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config)):
``` scrape_configs:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: "cs3api"
linkTitle: "cs3api"
weight: 10
description: >
Configuration for the CS3API of the Mentix service
---

{{% pageinfo %}}
The CS3API exporter exposes Mentix data in a format that is compliant with the CS3API `ProviderInfo` structure via an HTTP endpoint.
{{% /pageinfo %}}

{{% dir name="endpoint" type="string" default="/" %}}
The endpoint where the mesh data can be queried.
{{< highlight toml >}}
[http.services.mentix.cs3api]
endpoint = "/data"
{{< /highlight >}}
{{% /dir %}}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ description: >
---

{{% pageinfo %}}
The WebAPI exporter supports multiple endpoints for exporting data. As there currently is only one such endpoint, the WebAPI settings should not be modified.
The WebAPI exporter exposes the _plain_ Mentix data via an HTTP endpoint.
{{% /pageinfo %}}

{{% dir name="endpoint" type="string" default="/" %}}
The endpoint where the mesh data can be queried.
{{< highlight toml >}}
[http.services.mentix.webapi]
endpoint = "data"
endpoint = "/data"
{{< /highlight >}}
{{% /dir %}}
7 changes: 5 additions & 2 deletions examples/mentix/mentix.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ enabled_services = ["mentix"]

[http.services.mentix]
connector = "gocdb"
exporters = ["webapi"]
exporters = ["webapi", "cs3api"]
# Enable the Prometheus File Service Discovery:
# exporters = ["webapi", "prom_filesd"]
# exporters = ["webapi", "cs3api", "prom_filesd"]
update_interval = "15m"

[http.services.mentix.gocdb]
Expand All @@ -18,6 +18,9 @@ address = "http://sciencemesh-test.uni-muenster.de"
[http.services.mentix.webapi]
endpoint = "/"

[http.services.mentix.cs3api]
endpoint = "/cs3"

# Configure the Prometheus File Service Discovery:
# [http.services.mentix.prom_filesd]
# Prometheus must be configured to read the following file:
Expand Down
4 changes: 4 additions & 0 deletions internal/http/services/mentix/mentix.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ func applyDefaultConfig(conf *config.Configuration) {
if conf.WebAPI.Endpoint == "" {
conf.WebAPI.Endpoint = "/"
}

if conf.CS3API.Endpoint == "" {
conf.CS3API.Endpoint = "/cs3"
}
}

// New returns a new Mentix service.
Expand Down
4 changes: 4 additions & 0 deletions pkg/mentix/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type Configuration struct {
Endpoint string `mapstructure:"endpoint"`
} `yaml:"webapi"`

CS3API struct {
Endpoint string `mapstructure:"endpoint"`
} `yaml:"cs3api"`

PrometheusFileSD struct {
OutputFile string `mapstructure:"output_file"`
} `mapstructure:"prom_filesd"`
Expand Down
7 changes: 6 additions & 1 deletion pkg/mentix/config/ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@
package config

const (
// ConnectorIDGOCDB is the connector identifier for GOCDB.
ConnectorIDGOCDB = "gocdb"
)

const (
ExporterIDWebAPI = "webapi"
// ExporterIDWebAPI is the identifier for the WebAPI exporter.
ExporterIDWebAPI = "webapi"
// ExporterIDCS3API is the identifier for the CS3API exporter.
ExporterIDCS3API = "cs3api"
// ExporterIDPrometheusFileSD is the identifier for the Prometheus File SD exporter.
ExporterIDPrometheusFileSD = "prom_filesd"
)
2 changes: 2 additions & 0 deletions pkg/mentix/connectors/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type BaseConnector struct {
log *zerolog.Logger
}

// Activate activates the connector.
func (connector *BaseConnector) Activate(conf *config.Configuration, log *zerolog.Logger) error {
if conf == nil {
return fmt.Errorf("no configuration provided")
Expand All @@ -63,6 +64,7 @@ func (connector *BaseConnector) Activate(conf *config.Configuration, log *zerolo
return nil
}

// FindConnector searches for the given connector ID in all globally registered connectors.
func FindConnector(connectorID string) (Connector, error) {
for id, connector := range registeredConnectors {
if strings.EqualFold(id, connectorID) {
Expand Down
5 changes: 4 additions & 1 deletion pkg/mentix/connectors/gocdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type GOCDBConnector struct {
gocdbAddress string
}

// Activate activates the connector.
func (connector *GOCDBConnector) Activate(conf *config.Configuration, log *zerolog.Logger) error {
if err := connector.BaseConnector.Activate(conf, log); err != nil {
return err
Expand All @@ -53,6 +54,7 @@ func (connector *GOCDBConnector) Activate(conf *config.Configuration, log *zerol
return nil
}

// RetrieveMeshData fetches new mesh data.
func (connector *GOCDBConnector) RetrieveMeshData() (*meshdata.MeshData, error) {
meshData := new(meshdata.MeshData)

Expand Down Expand Up @@ -188,7 +190,7 @@ func (connector *GOCDBConnector) queryServices(meshData *meshdata.MeshData, site

// Add the service to the site
site.Services = append(site.Services, &meshdata.Service{
ServiceEndpoint: meshdata.ServiceEndpoint{
ServiceEndpoint: &meshdata.ServiceEndpoint{
Type: connector.findServiceType(meshData, service.Type),
Name: fmt.Sprintf("%v - %v", service.Host, service.Type),
URL: getServiceURLString(service, nil, host),
Expand Down Expand Up @@ -252,6 +254,7 @@ func (connector *GOCDBConnector) getServiceURL(service *gocdb.Service, endpoint
return svcURL, nil
}

// GetName returns the display name of the connector.
func (connector *GOCDBConnector) GetName() string {
return "GOCDB"
}
Expand Down
73 changes: 73 additions & 0 deletions pkg/mentix/exporters/cs3api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2018-2020 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package exporters

import (
"fmt"
"net/http"

"github.com/rs/zerolog"

"github.com/cs3org/reva/pkg/mentix/config"
"github.com/cs3org/reva/pkg/mentix/exporters/cs3api"
)

// CS3APIExporter implements the CS3API exporter.
type CS3APIExporter struct {
BaseRequestExporter
}

// Activate activates the exporter.
func (exporter *CS3APIExporter) Activate(conf *config.Configuration, log *zerolog.Logger) error {
if err := exporter.BaseExporter.Activate(conf, log); err != nil {
return err
}

// Store CS3API specific settings
exporter.endpoint = conf.CS3API.Endpoint

return nil
}

// HandleRequest handles the actual HTTP request.
func (exporter *CS3APIExporter) HandleRequest(resp http.ResponseWriter, req *http.Request) error {
// Data is read, so acquire a read lock
exporter.locker.RLock()
defer exporter.locker.RUnlock()

data, err := cs3api.HandleQuery(exporter.meshData, req.URL.Query())
if err == nil {
if _, err := resp.Write(data); err != nil {
return fmt.Errorf("error writing the API request response: %v", err)
}
} else {
return fmt.Errorf("error while serving API request: %v", err)
}

return nil
}

// GetName returns the display name of the exporter.
func (exporter *CS3APIExporter) GetName() string {
return "CS3API"
}

func init() {
registerExporter(config.ExporterIDCS3API, &CS3APIExporter{})
}
113 changes: 113 additions & 0 deletions pkg/mentix/exporters/cs3api/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2018-2020 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package cs3api

import (
"encoding/json"
"fmt"
"net/url"
"strings"

ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1"

"github.com/cs3org/reva/pkg/mentix/meshdata"
)

const (
queryMethodDefault = ""
)

// HandleQuery handles an HTTP request based on the provided 'method' parameter.
func HandleQuery(meshData *meshdata.MeshData, params url.Values) ([]byte, error) {
method := params.Get("method")
switch strings.ToLower(method) {
case queryMethodDefault:
return handleDefaultQuery(meshData, params)

default:
return []byte{}, fmt.Errorf("unknown API method '%v'", method)
}
}

func handleDefaultQuery(meshData *meshdata.MeshData, params url.Values) ([]byte, error) {
// Convert the mesh data
ocmData, err := convertMeshDataToOCMData(meshData)
if err != nil {
return []byte{}, fmt.Errorf("unable to convert the mesh data to OCM data structures: %v", err)
}

// Marshal the OCM data as JSON
data, err := json.MarshalIndent(ocmData, "", "\t")
if err != nil {
return []byte{}, fmt.Errorf("unable to marshal the mesh data: %v", err)
}

return data, nil
}

func convertMeshDataToOCMData(meshData *meshdata.MeshData) ([]*ocmprovider.ProviderInfo, error) {
// Convert the mesh data into the corresponding OCM data structures
providers := make([]*ocmprovider.ProviderInfo, 0, len(meshData.Sites))
for _, site := range meshData.Sites {
// Gather all services from the site
services := make([]*ocmprovider.Service, 0, len(site.Services))
for _, service := range site.Services {
// Gather all additional endpoints of the service
addEndpoints := make([]*ocmprovider.ServiceEndpoint, 0, len(service.AdditionalEndpoints))
for _, endpoint := range service.AdditionalEndpoints {
addEndpoints = append(addEndpoints, convertServiceEndpointToOCMData(endpoint))
}

services = append(services, &ocmprovider.Service{
Host: service.Host,
Endpoint: convertServiceEndpointToOCMData(service.ServiceEndpoint),
AdditionalEndpoints: addEndpoints,
ApiVersion: meshdata.GetPropertyValue(service.Properties, meshdata.PropertyAPIVersion, ""),
})
}

// Copy the site info into a ProviderInfo
providers = append(providers, &ocmprovider.ProviderInfo{
Name: site.Name,
FullName: site.FullName,
Description: site.Description,
Organization: site.Organization,
Domain: site.Domain,
Homepage: site.Homepage,
Email: site.Email,
Services: services,
Properties: site.Properties,
})
}

return providers, nil
}

func convertServiceEndpointToOCMData(endpoint *meshdata.ServiceEndpoint) *ocmprovider.ServiceEndpoint {
return &ocmprovider.ServiceEndpoint{
Type: &ocmprovider.ServiceType{
Name: endpoint.Type.Name,
Description: endpoint.Type.Description,
},
Name: endpoint.Name,
Path: endpoint.URL,
IsMonitored: endpoint.IsMonitored,
Properties: endpoint.Properties,
}
}
4 changes: 4 additions & 0 deletions pkg/mentix/exporters/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type BaseExporter struct {
locker sync.RWMutex
}

// Activate activates the exporter.
func (exporter *BaseExporter) Activate(conf *config.Configuration, log *zerolog.Logger) error {
if conf == nil {
return fmt.Errorf("no configuration provided")
Expand All @@ -71,14 +72,17 @@ func (exporter *BaseExporter) Activate(conf *config.Configuration, log *zerolog.
return nil
}

// Start starts the exporter; only exporters which perform periodical background tasks should do something here.
func (exporter *BaseExporter) Start() error {
return nil
}

// Stop stops any running background activities of the exporter.
func (exporter *BaseExporter) Stop() {

}

// UpdateMeshData is called whenever the mesh data has changed to reflect these changes.
func (exporter *BaseExporter) UpdateMeshData(meshData *meshdata.MeshData) error {
// Update the stored mesh data
if err := exporter.storeMeshData(meshData); err != nil {
Expand Down
Loading