Skip to content

Commit be89812

Browse files
committed
Add E2E test for Cache-Director health test interaction
This creates a new package for testing these types of E2E situations where we couldn't otherwise import a package (in this case, the test might live in the Director package, except we can't import fed_test_utils there b.c. of cyclic imports).
1 parent c8fcd22 commit be89812

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

e2e_fed_tests/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# End to End Fed Tests Package
2+
3+
This package is meant to be a repository of federation tests that test specific Pelican components
4+
when their interaction with other services is important. It has been created as its own package to
5+
avoid the potential for circular dependencies, and as such no functions here should ever be exported.
6+
7+
For example, the Director's health test utility API needs to function with both caches and origins.
8+
Testing these components together is most easily done by using `fed_test_utils` to spin up a new
9+
federation test. However, that package cannot be imported by Director, Cache, or Origin tests directly
10+
because the fed test itself _must_ import those packages, leading to a cyclical dependency.
11+
12+
The `github_scripts` directory contains a similar set of CI tests, but it's easier to write rigorous
13+
tests in go than it is to write them in bash.

e2e_fed_tests/director_test.go

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/***************************************************************
2+
*
3+
* Copyright (C) 2025, Pelican Project, Morgridge Institute for Research
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License"); you
6+
* may not use this file except in compliance with the License. You may
7+
* obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
***************************************************************/
18+
19+
package fed_tests
20+
21+
import (
22+
"io"
23+
24+
"context"
25+
"crypto/tls"
26+
_ "embed"
27+
"encoding/json"
28+
"net/http"
29+
"net/url"
30+
"testing"
31+
32+
"github.com/stretchr/testify/assert"
33+
"github.com/stretchr/testify/require"
34+
35+
"github.com/pelicanplatform/pelican/config"
36+
"github.com/pelicanplatform/pelican/fed_test_utils"
37+
"github.com/pelicanplatform/pelican/server_structs"
38+
39+
"github.com/pelicanplatform/pelican/server_utils"
40+
"github.com/pelicanplatform/pelican/test_utils"
41+
)
42+
43+
//go:embed resources/both-public.yml
44+
var bothPubNamespaces string
45+
46+
type serverAdUnmarshal struct {
47+
Type string `json:"type"`
48+
URL string `json:"url"`
49+
}
50+
51+
// Queries the cache for a director test file -- this mimics the way the Pelican
52+
// process at the cache behaves, as its responsible for requesting files from its
53+
// own XRootD component
54+
func TestDirectorCacheHealthTest(t *testing.T) {
55+
// Spin up a federation
56+
_ = fed_test_utils.NewFedTest(t, bothPubNamespaces)
57+
58+
ctx := context.Background()
59+
ctx, _, _ = test_utils.TestContext(ctx, t)
60+
fedInfo, err := config.GetFederation(ctx)
61+
require.NoError(t, err, "Failed to get federation service info")
62+
63+
directorUrlStr := fedInfo.DirectorEndpoint
64+
directorUrl, err := url.Parse(directorUrlStr)
65+
require.NoError(t, err, "Failed to parse director URL")
66+
67+
// There is no cache that will advertise the /pelican/monitoring namespace directly,
68+
// so we first discover a cache, then ask for the file. To do that, hit the Director's
69+
// server list endpoint and iterate through the servers until we find a cache.
70+
listPath, err := url.JoinPath("api", "v1.0", "director_ui", "servers")
71+
require.NoError(t, err, "Failed to join server list path")
72+
directorUrl.Path = listPath
73+
request, err := http.NewRequest("GET", directorUrl.String(), nil)
74+
require.NoError(t, err, "Failed to create HTTP request against server list path")
75+
76+
tr := &http.Transport{
77+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
78+
}
79+
client := http.Client{Transport: tr}
80+
resp, err := client.Do(request)
81+
assert.NoError(t, err, "Failed to get response")
82+
defer resp.Body.Close()
83+
require.Equal(t, resp.StatusCode, http.StatusOK, "Failed to get server list from director")
84+
dirBody, err := io.ReadAll(resp.Body)
85+
require.NoError(t, err, "Failed to read server list body")
86+
87+
// Unmarshal the body into a slice of dummy server ads. Can't use the actual server_structs.ServerAd
88+
// struct without a custom unmarshaler (because of string --> url conversion)
89+
var serverAds []serverAdUnmarshal
90+
err = json.Unmarshal(dirBody, &serverAds)
91+
require.NoError(t, err, "Failed to unmarshal server ads")
92+
var cacheUrlStr string
93+
found := false
94+
for _, serverAd := range serverAds {
95+
if serverAd.Type == server_structs.CacheType.String() {
96+
cacheUrlStr = serverAd.URL
97+
found = true
98+
break
99+
}
100+
}
101+
require.True(t, found, "Failed to find a cache server in the server list")
102+
cacheUrl, err := url.Parse(cacheUrlStr)
103+
require.NoError(t, err, "Failed to parse cache URL")
104+
105+
// Now ask the cache for the director test file. When it gets the request,
106+
// it'll turn around and ask the director for the file, exactly as it would
107+
// if the cache's own self-test utility requested the file.
108+
cachePath, err := url.JoinPath(server_utils.MonitoringBaseNs, "directorTest",
109+
server_utils.DirectorTest.String()+"-2006-01-02T15:04:10Z.txt")
110+
require.NoError(t, err, "Failed to join path")
111+
112+
cacheUrl.Path = cachePath
113+
request, err = http.NewRequest("GET", cacheUrl.String(), nil)
114+
require.NoError(t, err, "Failed to create HTTP request against cache")
115+
116+
resp, err = client.Do(request)
117+
assert.NoError(t, err, "Failed to get response")
118+
defer resp.Body.Close()
119+
cacheBody, err := io.ReadAll(resp.Body)
120+
require.NoError(t, err, "Failed to read cache body")
121+
assert.Equal(t, resp.StatusCode, http.StatusOK, "Failed to get director test file from cache")
122+
assert.Contains(t, string(cacheBody), "This object was created by the Pelican director-test functionality")
123+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Origin export configuration to test full multi-export capabilities
2+
3+
Origin:
4+
# Things that configure the origin itself
5+
StorageType: "posix"
6+
EnableDirectReads: true
7+
# The actual namespaces we export
8+
Exports:
9+
- StoragePrefix: /<SHOULD BE OVERRIDDEN>
10+
FederationPrefix: /first/namespace
11+
# Don't set Reads -- it should be toggled true by setting PublicReads
12+
Capabilities: ["PublicReads", "Writes", "DirectReads", "Listings"]
13+
- StoragePrefix: /<SHOULD BE OVERRIDDEN>
14+
FederationPrefix: /second/namespace
15+
Capabilities: ["PublicReads", "Writes", "DirectReads", "Listings"]

0 commit comments

Comments
 (0)