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 }