Skip to content

Commit e48fd3c

Browse files
committed
add: ocm spec appendix C compliance
@glpatcern recommendations moved types to spec.go used directory urls instead of a file Signed-off-by: Mahdi Baghbani <mahdi-baghbani@azadehafzar.io>
1 parent 54f2251 commit e48fd3c

4 files changed

Lines changed: 107 additions & 77 deletions

File tree

internal/http/services/ocmd/client.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,29 @@ func (c *OCMClient) discover(ctx context.Context, url string) ([]byte, error) {
114114
}
115115
return body, nil
116116
}
117+
118+
// GetDirectoryService fetches a directory service listing from the given URL per OCM spec Appendix C.
119+
func (c *OCMClient) GetDirectoryService(ctx context.Context, directoryURL string) (*DirectoryService, error) {
120+
log := appctx.GetLogger(ctx)
121+
122+
// TODO(@MahdiBaghbani): the discover() should be changed into a generic function that can be used to fetch any OCM endpoint. I'll do it in the security PR to minimize conflicts.
123+
body, err := c.discover(ctx, directoryURL)
124+
if err != nil {
125+
return nil, errors.Wrap(err, "error fetching directory service")
126+
}
127+
128+
var dirService DirectoryService
129+
if err := json.Unmarshal(body, &dirService); err != nil {
130+
log.Warn().Err(err).Str("url", directoryURL).Str("response", string(body)).Msg("malformed directory service response")
131+
return nil, errors.Wrap(err, "invalid directory service payload")
132+
}
133+
134+
// Validate required fields
135+
if dirService.Federation == "" {
136+
return nil, errtypes.InternalError("directory service missing required 'federation' field")
137+
}
138+
// Servers can be empty array, that's valid
139+
140+
log.Debug().Str("url", directoryURL).Str("federation", dirService.Federation).Int("servers", len(dirService.Servers)).Msg("fetched directory service")
141+
return &dirService, nil
142+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2025 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 ocmd
20+
21+
// DirectoryService represents a directory service listing per OCM spec Appendix C.
22+
type DirectoryService struct {
23+
Federation string `json:"federation"`
24+
Servers []DirectoryServiceServer `json:"servers"`
25+
}
26+
27+
// DirectoryServiceServer represents a single OCM server in a directory service.
28+
type DirectoryServiceServer struct {
29+
DisplayName string `json:"displayName"`
30+
URL string `json:"url"`
31+
// Added after discovery, not in raw response
32+
InviteAcceptDialog string `json:"inviteAcceptDialog,omitempty"`
33+
}

internal/http/services/sciencemesh/sciencemesh.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ func (s *svc) Close() error {
6060
}
6161

6262
type config struct {
63-
Prefix string `mapstructure:"prefix"`
64-
GatewaySvc string `mapstructure:"gatewaysvc" validate:"required"`
65-
ProviderDomain string `mapstructure:"provider_domain" validate:"required"`
66-
MeshDirectoryURL string `mapstructure:"mesh_directory_url"`
67-
OCMMountPoint string `mapstructure:"ocm_mount_point"`
68-
FederationsFile string `mapstructure:"federations_file"`
69-
OCMClientTimeout int `mapstructure:"ocm_client_timeout"`
70-
OCMClientInsecure bool `mapstructure:"ocm_client_insecure"`
71-
Events EventOptions `mapstructure:"events"`
63+
Prefix string `mapstructure:"prefix"`
64+
GatewaySvc string `mapstructure:"gatewaysvc" validate:"required"`
65+
ProviderDomain string `mapstructure:"provider_domain" validate:"required"`
66+
MeshDirectoryURL string `mapstructure:"mesh_directory_url"`
67+
OCMMountPoint string `mapstructure:"ocm_mount_point"`
68+
DirectoryServiceURLs string `mapstructure:"directory_service_urls"`
69+
OCMClientTimeout int `mapstructure:"ocm_client_timeout"`
70+
OCMClientInsecure bool `mapstructure:"ocm_client_insecure"`
71+
Events EventOptions `mapstructure:"events"`
7272
}
7373

7474
// EventOptions are the configurable options for events
@@ -89,9 +89,6 @@ func (c *config) ApplyDefaults() {
8989
if c.OCMMountPoint == "" {
9090
c.OCMMountPoint = "/ocm"
9191
}
92-
if c.FederationsFile == "" {
93-
c.FederationsFile = "/etc/revad/federations.json"
94-
}
9592
if c.OCMClientTimeout == 0 {
9693
c.OCMClientTimeout = 10
9794
}

internal/http/services/sciencemesh/wayf.go

Lines changed: 39 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"fmt"
2525
"net/http"
2626
"net/url"
27-
"os"
2827
"strings"
2928
"time"
3029

@@ -34,31 +33,8 @@ import (
3433
)
3534

3635
type wayfHandler struct {
37-
federations []Federation
38-
ocmClient *ocmd.OCMClient
39-
}
40-
41-
type Federation struct {
42-
Federation string `json:"federation"`
43-
Servers []FederationServer `json:"servers"`
44-
}
45-
46-
// FederationServer represents a single provider with discovery info
47-
type FederationServer struct {
48-
DisplayName string `json:"displayName"`
49-
URL string `json:"url"`
50-
InviteAcceptDialog string `json:"inviteAcceptDialog,omitempty"`
51-
}
52-
53-
// federationFile is the on-disk structure without inviteAcceptDialog
54-
type federationFile struct {
55-
Federation string `json:"federation"`
56-
Servers []federationServerFile `json:"servers"`
57-
}
58-
59-
type federationServerFile struct {
60-
DisplayName string `json:"displayName"`
61-
URL string `json:"url"`
36+
directoryServices []ocmd.DirectoryService
37+
ocmClient *ocmd.OCMClient
6238
}
6339

6440
type DiscoverRequest struct {
@@ -79,55 +55,52 @@ func (h *wayfHandler) init(c *config) error {
7955
Bool("insecure", c.OCMClientInsecure).
8056
Msg("Created OCM client for discovery")
8157

82-
log.Debug().Str("file", c.FederationsFile).Msg("Initializing WAYF handler with federations file")
83-
84-
data, err := os.ReadFile(c.FederationsFile)
85-
if err != nil {
86-
if os.IsNotExist(err) {
87-
log.Warn().Str("file", c.FederationsFile).Msg("Federations file not found, starting with empty list")
88-
h.federations = []Federation{}
89-
return nil
90-
}
91-
log.Error().Err(err).Str("file", c.FederationsFile).Msg("Failed to read federations file")
92-
return err
93-
}
94-
95-
var fileData []federationFile
96-
if err := json.Unmarshal(data, &fileData); err != nil {
97-
log.Error().Err(err).Str("file", c.FederationsFile).Msg("Failed to parse federations file")
98-
return err
58+
urls := strings.Fields(c.DirectoryServiceURLs)
59+
if len(urls) == 0 {
60+
log.Info().Msg("No directory service URLs configured, starting with empty list")
61+
h.directoryServices = []ocmd.DirectoryService{}
62+
return nil
9963
}
10064

101-
log.Debug().Int("federations_count", len(fileData)).Msg("Loaded federations from file")
65+
log.Debug().Int("url_count", len(urls)).Strs("urls", urls).Msg("Initializing WAYF handler with directory service URLs")
10266

10367
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
10468
defer cancel()
10569

106-
// Discover each server and populate inviteAcceptDialog
107-
h.federations = []Federation{}
70+
h.directoryServices = []ocmd.DirectoryService{}
10871
discoveryErrors := 0
10972
validServersCount := 0
73+
fetchErrors := 0
74+
75+
for _, directoryURL := range urls {
76+
log.Debug().Str("url", directoryURL).Msg("Fetching directory service")
77+
78+
directoryService, err := h.ocmClient.GetDirectoryService(ctx, directoryURL)
79+
if err != nil {
80+
log.Info().Err(err).Str("url", directoryURL).Msg("Failed to fetch directory service, skipping")
81+
fetchErrors++
82+
continue
83+
}
11084

111-
for _, fed := range fileData {
112-
log.Debug().Str("federation", fed.Federation).Int("servers_count", len(fed.Servers)).Msg("Processing federation")
113-
var validServers []FederationServer
85+
log.Debug().Str("federation", directoryService.Federation).Int("servers_count", len(directoryService.Servers)).Msg("Processing directory service")
11486

115-
for _, srv := range fed.Servers {
87+
var validServers []ocmd.DirectoryServiceServer
88+
for _, srv := range directoryService.Servers {
11689
if srv.DisplayName == "" || srv.URL == "" {
117-
log.Debug().Str("federation", fed.Federation).
90+
log.Debug().Str("federation", directoryService.Federation).
11891
Str("displayName", srv.DisplayName).
11992
Str("url", srv.URL).
12093
Msg("Skipping server with missing displayName or url")
12194
continue
12295
}
12396

124-
log.Debug().Str("federation", fed.Federation).Str("server", srv.DisplayName).Str("url", srv.URL).Msg("Discovering server")
97+
log.Debug().Str("federation", directoryService.Federation).Str("server", srv.DisplayName).Str("url", srv.URL).Msg("Discovering server")
12598

12699
// Discover inviteAcceptDialog from OCM endpoint
127100
disco, err := h.ocmClient.Discover(ctx, srv.URL)
128101
if err != nil {
129-
log.Warn().Err(err).
130-
Str("federation", fed.Federation).
102+
log.Debug().Err(err).
103+
Str("federation", directoryService.Federation).
131104
Str("server", srv.DisplayName).
132105
Str("url", srv.URL).
133106
Msg("Failed to discover server, skipping")
@@ -144,43 +117,44 @@ func (h *wayfHandler) init(c *config) error {
144117
inviteDialog = baseURL.Scheme + "://" + baseURL.Host + inviteDialog
145118
log.Debug().Str("original", disco.InviteAcceptDialog).Str("converted", inviteDialog).Msg("Converted relative path to absolute")
146119
} else {
147-
log.Warn().Err(parseErr).
120+
log.Debug().Err(parseErr).
148121
Str("url", srv.URL).
149122
Str("inviteDialog", disco.InviteAcceptDialog).
150123
Msg("Failed to parse server URL for relative path conversion")
151124
continue
152125
}
153126
}
154127

155-
validServers = append(validServers, FederationServer{
128+
validServers = append(validServers, ocmd.DirectoryServiceServer{
156129
DisplayName: srv.DisplayName,
157130
URL: srv.URL,
158131
InviteAcceptDialog: inviteDialog,
159132
})
160133
validServersCount++
161134

162135
log.Debug().
163-
Str("federation", fed.Federation).
136+
Str("federation", directoryService.Federation).
164137
Str("server", srv.DisplayName).
165138
Str("inviteAcceptDialog", inviteDialog).
166139
Msg("Successfully discovered server")
167140
}
168141

169142
if len(validServers) > 0 {
170-
h.federations = append(h.federations, Federation{
171-
Federation: fed.Federation,
143+
h.directoryServices = append(h.directoryServices, ocmd.DirectoryService{
144+
Federation: directoryService.Federation,
172145
Servers: validServers,
173146
})
174-
log.Debug().Str("federation", fed.Federation).Int("valid_servers", len(validServers)).Msg("Added federation with valid servers")
147+
log.Debug().Str("federation", directoryService.Federation).Int("valid_servers", len(validServers)).Msg("Added directory service with valid servers")
175148
} else {
176-
log.Warn().Str("federation", fed.Federation).
177-
Msg("Federation has no valid servers, skipping entirely")
149+
log.Info().Str("federation", directoryService.Federation).
150+
Msg("Directory service has no valid servers, skipping entirely")
178151
}
179152
}
180153

181154
log.Info().
182-
Int("federations", len(h.federations)).
155+
Int("directory_services", len(h.directoryServices)).
183156
Int("valid_servers", validServersCount).
157+
Int("fetch_errors", fetchErrors).
184158
Int("discovery_errors", discoveryErrors).
185159
Msg("WAYF handler initialization completed")
186160

@@ -191,7 +165,7 @@ func (h *wayfHandler) GetFederations(w http.ResponseWriter, r *http.Request) {
191165
w.Header().Set("Content-Type", "application/json")
192166
w.WriteHeader(http.StatusOK)
193167

194-
if err := json.NewEncoder(w).Encode(h.federations); err != nil {
168+
if err := json.NewEncoder(w).Encode(h.directoryServices); err != nil {
195169
reqres.WriteError(w, r, reqres.APIErrorServerError, "error encoding response", err)
196170
return
197171
}
@@ -226,7 +200,7 @@ func (h *wayfHandler) DiscoverProvider(w http.ResponseWriter, r *http.Request) {
226200
log.Debug().Str("domain", domain).Msg("Attempting OCM discovery")
227201
disco, err := h.ocmClient.Discover(ctx, domain)
228202
if err != nil {
229-
log.Warn().Err(err).Str("domain", domain).Msg("Discovery failed")
203+
log.Info().Err(err).Str("domain", domain).Msg("Discovery failed")
230204
reqres.WriteError(w, r, reqres.APIErrorNotFound,
231205
fmt.Sprintf("Provider at '%s' does not support OCM discovery", req.Domain), err)
232206
return

0 commit comments

Comments
 (0)