github.com/cs3org/reva/v2@v2.27.7/pkg/mentix/connectors/gocdb.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 connectors
    20  
    21  import (
    22  	"encoding/xml"
    23  	"fmt"
    24  	"net/url"
    25  	"path"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/cs3org/reva/v2/pkg/mentix/utils"
    30  	"github.com/rs/zerolog"
    31  
    32  	"github.com/cs3org/reva/v2/pkg/mentix/config"
    33  	"github.com/cs3org/reva/v2/pkg/mentix/connectors/gocdb"
    34  	"github.com/cs3org/reva/v2/pkg/mentix/meshdata"
    35  	"github.com/cs3org/reva/v2/pkg/mentix/utils/network"
    36  )
    37  
    38  // GOCDBConnector is used to read mesh data from a GOCDB instance.
    39  type GOCDBConnector struct {
    40  	BaseConnector
    41  
    42  	gocdbAddress string
    43  }
    44  
    45  // Activate activates the connector.
    46  func (connector *GOCDBConnector) Activate(conf *config.Configuration, log *zerolog.Logger) error {
    47  	if err := connector.BaseConnector.Activate(conf, log); err != nil {
    48  		return err
    49  	}
    50  
    51  	// Check and store GOCDB specific settings
    52  	connector.gocdbAddress = conf.Connectors.GOCDB.Address
    53  	if len(connector.gocdbAddress) == 0 {
    54  		return fmt.Errorf("no GOCDB address configured")
    55  	}
    56  
    57  	return nil
    58  }
    59  
    60  // RetrieveMeshData fetches new mesh data.
    61  func (connector *GOCDBConnector) RetrieveMeshData() (*meshdata.MeshData, error) {
    62  	meshData := new(meshdata.MeshData)
    63  
    64  	// Query all data from GOCDB
    65  	if err := connector.queryServiceTypes(meshData); err != nil {
    66  		return nil, fmt.Errorf("could not query service types: %v", err)
    67  	}
    68  
    69  	if err := connector.querySites(meshData); err != nil {
    70  		return nil, fmt.Errorf("could not query sites: %v", err)
    71  	}
    72  
    73  	for _, site := range meshData.Sites {
    74  		// Get services associated with the current site
    75  		if err := connector.queryServices(meshData, site); err != nil {
    76  			return nil, fmt.Errorf("could not query services of site '%v': %v", site.Name, err)
    77  		}
    78  
    79  		// Get downtimes scheduled for the current site
    80  		if err := connector.queryDowntimes(meshData, site); err != nil {
    81  			return nil, fmt.Errorf("could not query downtimes of site '%v': %v", site.Name, err)
    82  		}
    83  	}
    84  
    85  	meshData.InferMissingData()
    86  	return meshData, nil
    87  }
    88  
    89  func (connector *GOCDBConnector) query(v interface{}, method string, isPrivate bool, hasScope bool, params network.URLParams) error {
    90  	var scope string
    91  	if hasScope {
    92  		scope = connector.conf.Connectors.GOCDB.Scope
    93  	}
    94  
    95  	// Get the data from GOCDB
    96  	data, err := gocdb.QueryGOCDB(connector.gocdbAddress, method, isPrivate, scope, connector.conf.Connectors.GOCDB.APIKey, params)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	// Unmarshal it
   102  	if err := xml.Unmarshal(data, v); err != nil {
   103  		return fmt.Errorf("unable to unmarshal data: %v", err)
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  func (connector *GOCDBConnector) queryServiceTypes(meshData *meshdata.MeshData) error {
   110  	var serviceTypes gocdb.ServiceTypes
   111  	if err := connector.query(&serviceTypes, "get_service_types", false, false, network.URLParams{}); err != nil {
   112  		return err
   113  	}
   114  
   115  	// Copy retrieved data into the mesh data
   116  	meshData.ServiceTypes = nil
   117  	for _, serviceType := range serviceTypes.Types {
   118  		meshData.ServiceTypes = append(meshData.ServiceTypes, &meshdata.ServiceType{
   119  			Name:        serviceType.Name,
   120  			Description: serviceType.Description,
   121  		})
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func (connector *GOCDBConnector) querySites(meshData *meshdata.MeshData) error {
   128  	var sites gocdb.Sites
   129  	if err := connector.query(&sites, "get_site", false, true, network.URLParams{}); err != nil {
   130  		return err
   131  	}
   132  
   133  	// Copy retrieved data into the mesh data
   134  	meshData.Sites = nil
   135  	for _, site := range sites.Sites {
   136  		properties := connector.extensionsToMap(&site.Extensions)
   137  
   138  		// The site ID can be set through a property; by default, the site short name will be used
   139  		siteID := meshdata.GetPropertyValue(properties, meshdata.PropertySiteID, site.ShortName)
   140  
   141  		// See if an organization has been defined using properties; otherwise, use the official name
   142  		organization := meshdata.GetPropertyValue(properties, meshdata.PropertyOrganization, site.OfficialName)
   143  
   144  		meshsite := &meshdata.Site{
   145  			ID:           siteID,
   146  			Name:         site.ShortName,
   147  			FullName:     site.OfficialName,
   148  			Organization: organization,
   149  			Domain:       site.Domain,
   150  			Homepage:     site.Homepage,
   151  			Email:        site.Email,
   152  			Description:  site.Description,
   153  			Country:      site.Country,
   154  			CountryCode:  site.CountryCode,
   155  			Longitude:    site.Longitude,
   156  			Latitude:     site.Latitude,
   157  			Services:     nil,
   158  			Properties:   properties,
   159  			Downtimes:    meshdata.Downtimes{},
   160  		}
   161  		meshData.Sites = append(meshData.Sites, meshsite)
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  func (connector *GOCDBConnector) queryServices(meshData *meshdata.MeshData, site *meshdata.Site) error {
   168  	var services gocdb.Services
   169  	if err := connector.query(&services, "get_service", false, true, network.URLParams{"sitename": site.Name}); err != nil {
   170  		return err
   171  	}
   172  
   173  	getServiceURLString := func(service *gocdb.Service, endpoint *gocdb.ServiceEndpoint, host string) string {
   174  		urlstr := "https://" + host // Fall back to the provided hostname
   175  		if svcURL, err := connector.getServiceURL(service, endpoint); err == nil {
   176  			urlstr = svcURL.String()
   177  		}
   178  		return urlstr
   179  	}
   180  
   181  	// Copy retrieved data into the mesh data
   182  	site.Services = nil
   183  	for _, service := range services.Services {
   184  		host := service.Host
   185  
   186  		// If a URL is provided, extract the port from it and append it to the host
   187  		if len(service.URL) > 0 {
   188  			if hostURL, err := url.Parse(service.URL); err == nil {
   189  				if port := hostURL.Port(); len(port) > 0 {
   190  					host += ":" + port
   191  				}
   192  			}
   193  		}
   194  
   195  		// Assemble additional endpoints
   196  		var endpoints []*meshdata.ServiceEndpoint
   197  		for _, endpoint := range service.Endpoints.Endpoints {
   198  			endpoints = append(endpoints, &meshdata.ServiceEndpoint{
   199  				Type:        connector.findServiceType(meshData, endpoint.Type),
   200  				Name:        endpoint.Name,
   201  				RawURL:      endpoint.URL,
   202  				URL:         getServiceURLString(service, endpoint, host),
   203  				IsMonitored: strings.EqualFold(endpoint.IsMonitored, "Y"),
   204  				Properties:  connector.extensionsToMap(&endpoint.Extensions),
   205  			})
   206  		}
   207  
   208  		// Add the service to the site
   209  		site.Services = append(site.Services, &meshdata.Service{
   210  			ServiceEndpoint: &meshdata.ServiceEndpoint{
   211  				Type:        connector.findServiceType(meshData, service.Type),
   212  				Name:        service.Type,
   213  				RawURL:      service.URL,
   214  				URL:         getServiceURLString(service, nil, host),
   215  				IsMonitored: strings.EqualFold(service.IsMonitored, "Y"),
   216  				Properties:  connector.extensionsToMap(&service.Extensions),
   217  			},
   218  			Host:                host,
   219  			AdditionalEndpoints: endpoints,
   220  		})
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  func (connector *GOCDBConnector) queryDowntimes(meshData *meshdata.MeshData, site *meshdata.Site) error {
   227  	var downtimes gocdb.Downtimes
   228  	if err := connector.query(&downtimes, "get_downtime_nested_services", false, true, network.URLParams{"topentity": site.Name, "ongoing_only": "yes"}); err != nil {
   229  		return err
   230  	}
   231  
   232  	// Copy retrieved data into the mesh data
   233  	site.Downtimes.Clear()
   234  	for _, dt := range downtimes.Downtimes {
   235  		if !strings.EqualFold(dt.Severity, "outage") { // Only take real outages into account
   236  			continue
   237  		}
   238  
   239  		services := make([]string, 0, len(dt.AffectedServices.Services))
   240  		for _, service := range dt.AffectedServices.Services {
   241  			// Only add critical services to the list of affected services
   242  			if utils.FindInStringArray(service.Type, connector.conf.Services.CriticalTypes, false) != -1 {
   243  				services = append(services, service.Type)
   244  			}
   245  		}
   246  
   247  		_, _ = site.Downtimes.ScheduleDowntime(time.Unix(dt.StartDate, 0), time.Unix(dt.EndDate, 0), services)
   248  	}
   249  
   250  	return nil
   251  }
   252  
   253  func (connector *GOCDBConnector) findServiceType(meshData *meshdata.MeshData, name string) *meshdata.ServiceType {
   254  	for _, serviceType := range meshData.ServiceTypes {
   255  		if strings.EqualFold(serviceType.Name, name) {
   256  			return serviceType
   257  		}
   258  	}
   259  
   260  	// If the service type doesn't exist, create a default one
   261  	return &meshdata.ServiceType{Name: name, Description: ""}
   262  }
   263  
   264  func (connector *GOCDBConnector) extensionsToMap(extensions *gocdb.Extensions) map[string]string {
   265  	properties := make(map[string]string)
   266  	for _, ext := range extensions.Extensions {
   267  		properties[ext.Key] = ext.Value
   268  	}
   269  	return properties
   270  }
   271  
   272  func (connector *GOCDBConnector) getServiceURL(service *gocdb.Service, endpoint *gocdb.ServiceEndpoint) (*url.URL, error) {
   273  	urlstr := service.URL
   274  	if len(urlstr) == 0 {
   275  		// The URL defaults to the hostname using the HTTPS protocol
   276  		urlstr = "https://" + service.Host
   277  	}
   278  
   279  	svcURL, err := url.ParseRequestURI(urlstr)
   280  	if err != nil {
   281  		return nil, fmt.Errorf("unable to parse URL '%v': %v", urlstr, err)
   282  	}
   283  
   284  	// If an endpoint was provided, use its path
   285  	if endpoint != nil {
   286  		// If the endpoint URL is an absolute one, just use that; otherwise, make an absolute one out of it
   287  		if endpointURL, err := url.ParseRequestURI(endpoint.URL); err == nil && len(endpointURL.Scheme) > 0 {
   288  			svcURL = endpointURL
   289  		} else {
   290  			// Replace entire URL path if the relative path starts with a slash; otherwise, just append
   291  			if strings.HasPrefix(endpoint.URL, "/") {
   292  				svcURL.Path = endpoint.URL
   293  			} else {
   294  				svcURL.Path = path.Join(svcURL.Path, endpoint.URL)
   295  				if strings.HasSuffix(endpoint.URL, "/") { // Restore trailing slash if necessary
   296  					svcURL.Path += "/"
   297  				}
   298  			}
   299  		}
   300  	}
   301  
   302  	return svcURL, nil
   303  }
   304  
   305  // GetID returns the ID of the connector.
   306  func (connector *GOCDBConnector) GetID() string {
   307  	return config.ConnectorIDGOCDB
   308  }
   309  
   310  // GetName returns the display name of the connector.
   311  func (connector *GOCDBConnector) GetName() string {
   312  	return "GOCDB"
   313  }
   314  
   315  func init() {
   316  	registerConnector(&GOCDBConnector{})
   317  }