Skip to content

Commit

Permalink
Merge pull request #1003 from haoming29/xrd-sentinel-file
Browse files Browse the repository at this point in the history
Add the option for a sentinel file for the origin
  • Loading branch information
haoming29 authored Apr 4, 2024
2 parents 332d180 + c79908b commit dd9cbae
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 22 deletions.
23 changes: 19 additions & 4 deletions docs/parameters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -416,12 +416,27 @@ name: Origin.Exports
description: >-
A list describing the origin's exports. Each item in the list describes a single namespace the origin exports:
- StoragePrefix: <the relevant path from the object store, eg for posix /my/dir>
FederationPrefix: <the namespace prefix that data from StoragePrefix is made available under within the federation>
- StoragePrefix: The relevant path from the object store, e.g. for posix /my/dir
FederationPrefix: The namespace prefix that data from StoragePrefix is made available under within the federation
Capabilities: A list of the capabilities the origin is willing to support for the given export. Capabilities include:
["Reads", "PublicReads", "Writes", "Listings", "DirectReads"]
where each of these has the same effect as the corresponding "Origin.Enable*" configuration, except scoped to the
given export. If "PublicReads" is included, "Reads" is inferred.
where each of these has the same effect as the corresponding "Origin.Enable*" configuration, except scoped to the
given export. If "PublicReads" is included, "Reads" is inferred.
SentinelLocation: A filename under `StoragePrefix` path for Pelican to check the storage directory exists and is correctly mounted.
Leave it empty to skip the check. You should always choose a distinguished name for `SentinelLocation`. It should not be reused for other servers.
If running in a containerized environment it should not be the name of the underlying physical host as that may change and lead to confusion.
You need to manually create a file under path to `StoragePrefix` with the same name as `SentinelLocation`. Note that this parameter will be ignored
if the origin StorageType is S3.
Example:
```yaml
Origin.Exports
- StoragePrefix: /home/foo/bar
FederationPrefix: /demo/project
Capabilities: ["Reads", "PublicReads", "Writes", "Listings", "DirectReads"]
SentinelLocation: demoproject_origin_A
```
type: object
default: none
components: ["origin"]
Expand Down
2 changes: 1 addition & 1 deletion fed_test_utils/fed.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import (

type (
FedTest struct {
Exports *[]server_utils.OriginExports
Exports *[]server_utils.OriginExport
Token string
Ctx context.Context
Egrp *errgroup.Group
Expand Down
5 changes: 5 additions & 0 deletions launchers/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ func LaunchModules(ctx context.Context, modules config.ServerType) (context.Canc
return shutdownCancel, err
}

ok, err := server_utils.CheckSentinelLocation(originExports)
if err != nil && !ok {
return shutdownCancel, err
}

switch mode {
case "posix":
if len(*originExports) == 0 {
Expand Down
36 changes: 28 additions & 8 deletions server_utils/origin.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package server_utils

import (
"fmt"
"os"
"path"
"path/filepath"
"reflect"
"strings"
Expand All @@ -33,13 +35,14 @@ import (
"github.com/pelicanplatform/pelican/server_structs"
)

var originExports *[]OriginExports
var originExports *[]OriginExport

type (
OriginExports struct {
OriginExport struct {
StoragePrefix string
FederationPrefix string
Capabilities server_structs.Capabilities
SentinelLocation string
}
)

Expand Down Expand Up @@ -115,12 +118,12 @@ func StringListToCapsHookFunc() mapstructure.DecodeHookFuncType {
// convert those values (such as Origin.FederationPrefix, Origin.StoragePrefix, etc.) into the OriginExports
// struct and return a list of one. Otherwise, we'll base things off the list of exports and ignore the single-prefix
// style of configuration.
func GetOriginExports() (*[]OriginExports, error) {
func GetOriginExports() (*[]OriginExport, error) {
if originExports != nil {
return originExports, nil
}

originExports = &[]OriginExports{}
originExports = &[]OriginExport{}

viper.SetDefault("Origin.StorageType", "posix")
StorageType := param.Origin_StorageType.GetString()
Expand Down Expand Up @@ -152,7 +155,7 @@ func GetOriginExports() (*[]OriginExports, error) {
}

reads := param.Origin_EnableReads.GetBool() || param.Origin_EnablePublicReads.GetBool()
originExport := OriginExports{
originExport := OriginExport{
FederationPrefix: federationPrefix,
StoragePrefix: storagePrefix,
Capabilities: server_structs.Capabilities{
Expand All @@ -175,7 +178,7 @@ func GetOriginExports() (*[]OriginExports, error) {

log.Warningln("Passing export volumes via -v at the command line causes Pelican to ignore exports configured via the yaml file")
log.Warningln("However, namespaces exported this way will inherit the Origin.Enable* settings from your configuration")
log.Warningln("For finer-grained control of each export, please configure them in your pelican.yaml file")
log.Warningln("For finer-grained control of each export, please configure them in your pelican.yaml file via Origin.Exports")
return originExports, nil
}

Expand Down Expand Up @@ -204,7 +207,7 @@ func GetOriginExports() (*[]OriginExports, error) {
log.Infoln("Configuring single-export origin")

reads := (param.Origin_EnableReads.GetBool() || param.Origin_EnablePublicReads.GetBool())
originExport := OriginExports{
originExport := OriginExport{
FederationPrefix: param.Origin_FederationPrefix.GetString(),
StoragePrefix: param.Origin_StoragePrefix.GetString(),
Capabilities: server_structs.Capabilities{
Expand All @@ -227,7 +230,7 @@ func GetOriginExports() (*[]OriginExports, error) {

federationPrefix := filepath.Join("/", param.Origin_S3ServiceName.GetString(),
param.Origin_S3Region.GetString(), param.Origin_S3Bucket.GetString())
originExport := OriginExports{
originExport := OriginExport{
FederationPrefix: federationPrefix,
StoragePrefix: "",
Capabilities: server_structs.Capabilities{
Expand All @@ -246,6 +249,23 @@ func GetOriginExports() (*[]OriginExports, error) {
return originExports, nil
}

func CheckSentinelLocation(exports *[]OriginExport) (ok bool, err error) {
for _, export := range *exports {
if export.SentinelLocation != "" {
sentinelPath := path.Clean(export.SentinelLocation)
if path.Base(sentinelPath) != sentinelPath {
return false, errors.Errorf("invalid SentinelLocation path for StoragePrefix %s, file must not contain a directory. Got %s", export.StoragePrefix, export.SentinelLocation)
}
fullPath := filepath.Join(export.StoragePrefix, sentinelPath)
_, err := os.Stat(fullPath)
if err != nil {
return false, errors.Wrapf(err, "fail to open SentinelLocation %s for StoragePrefix %s. Directory check failed", export.SentinelLocation, export.StoragePrefix)
}
}
}
return true, nil
}

func ResetOriginExports() {
originExports = nil
}
74 changes: 67 additions & 7 deletions server_utils/origin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ package server_utils

import (
_ "embed"
"os"
"path/filepath"
"strings"
"testing"

Expand All @@ -48,7 +50,7 @@ var (
exportSingleVolumeConfig string
)

func setup(t *testing.T, config string) *[]OriginExports {
func setup(t *testing.T, config string) *[]OriginExport {
viper.SetConfigType("yaml")
// Use viper to read in the embedded config
err := viper.ReadConfig(strings.NewReader(config))
Expand Down Expand Up @@ -93,7 +95,7 @@ func TestGetExports(t *testing.T) {
exports := setup(t, multiExportValidConfig)
assert.Len(t, *exports, 2, "expected 2 exports")

expectedExport1 := OriginExports{
expectedExport1 := OriginExport{
StoragePrefix: "/test1",
FederationPrefix: "/first/namespace",
Capabilities: server_structs.Capabilities{
Expand All @@ -106,7 +108,7 @@ func TestGetExports(t *testing.T) {
}
assert.Equal(t, expectedExport1, (*exports)[0])

expectedExport2 := OriginExports{
expectedExport2 := OriginExport{
StoragePrefix: "/test2",
FederationPrefix: "/second/namespace",
Capabilities: server_structs.Capabilities{
Expand All @@ -126,7 +128,7 @@ func TestGetExports(t *testing.T) {
exports := setup(t, exportVolumesValidConfig)
assert.Len(t, *exports, 2, "expected 2 exports")

expectedExport1 := OriginExports{
expectedExport1 := OriginExport{
StoragePrefix: "/test1",
FederationPrefix: "/first/namespace",
Capabilities: server_structs.Capabilities{
Expand All @@ -139,7 +141,7 @@ func TestGetExports(t *testing.T) {
}
assert.Equal(t, expectedExport1, (*exports)[0])

expectedExport2 := OriginExports{
expectedExport2 := OriginExport{
StoragePrefix: "/test2",
FederationPrefix: "/second/namespace",
Capabilities: server_structs.Capabilities{
Expand All @@ -161,7 +163,7 @@ func TestGetExports(t *testing.T) {
exports := setup(t, exportSingleVolumeConfig)
assert.Len(t, *exports, 1, "expected 1 export")

expectedExport := OriginExports{
expectedExport := OriginExport{
StoragePrefix: "/test1",
FederationPrefix: "/first/namespace",
Capabilities: server_structs.Capabilities{
Expand Down Expand Up @@ -190,7 +192,7 @@ func TestGetExports(t *testing.T) {
exports := setup(t, singleExportBlockConfig)
assert.Len(t, *exports, 1, "expected 1 export")

expectedExport := OriginExports{
expectedExport := OriginExport{
StoragePrefix: "/test1",
FederationPrefix: "/first/namespace",
Capabilities: server_structs.Capabilities{
Expand All @@ -213,3 +215,61 @@ func TestGetExports(t *testing.T) {
assert.True(t, viper.GetBool("Origin.EnableDirectReads"))
})
}

func TestCheckSentinelLocation(t *testing.T) {
tmpDir := t.TempDir()
tempStn := filepath.Join(tmpDir, "mock_sentinel")
file, err := os.Create(tempStn)
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)

mockExportNoStn := OriginExport{
StoragePrefix: "/foo/bar",
FederationPrefix: "/demo/foo/bar",
Capabilities: server_structs.Capabilities{Reads: true},
}
mockExportValidStn := OriginExport{
StoragePrefix: tmpDir,
FederationPrefix: "/demo/foo/bar",
Capabilities: server_structs.Capabilities{Reads: true},
SentinelLocation: "mock_sentinel",
}
mockExportInvalidStn := OriginExport{
StoragePrefix: tmpDir,
FederationPrefix: "/demo/foo/bar",
Capabilities: server_structs.Capabilities{Reads: true},
SentinelLocation: "sentinel_dne",
}

t.Run("empty-sentinel-return-ok", func(t *testing.T) {
exports := make([]OriginExport, 0)
exports = append(exports, mockExportNoStn)
exports = append(exports, mockExportNoStn)

ok, err := CheckSentinelLocation(&exports)
assert.NoError(t, err)
assert.True(t, ok)
})

t.Run("valid-sentinel-return-ok", func(t *testing.T) {
exports := make([]OriginExport, 0)
exports = append(exports, mockExportNoStn)
exports = append(exports, mockExportValidStn)

ok, err := CheckSentinelLocation(&exports)
assert.NoError(t, err)
assert.True(t, ok)
})

t.Run("invalid-sentinel-return-error", func(t *testing.T) {
exports := make([]OriginExport, 0)
exports = append(exports, mockExportNoStn)
exports = append(exports, mockExportValidStn)
exports = append(exports, mockExportInvalidStn)

ok, err := CheckSentinelLocation(&exports)
assert.Error(t, err)
assert.False(t, ok)
})
}
7 changes: 6 additions & 1 deletion web_ui/frontend/components/Config/ObjectField/ExportForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ const ExportForm = ({ onSubmit, value }: FormProps<Export>) => {
const [storagePrefix, setStoragePrefix] = React.useState<string>(value?.storageprefix || "")
const [federationPrefix, setFederationPrefix] = React.useState<string>(value?.federationprefix || "")
const [capabilities, setCapabilities] = React.useState<Capability[]>(value?.capabilities || [])
const [sentinelLocation, setSentinelLocation] = React.useState<string>(value?.sentinellocation || "")

const submitHandler = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

const exportValue = {
storageprefix: storagePrefix,
federationprefix: federationPrefix,
capabilities: capabilities
capabilities: capabilities,
sentinellocation: sentinelLocation
}


Expand All @@ -44,6 +46,9 @@ const ExportForm = ({ onSubmit, value }: FormProps<Export>) => {
<Box mb={2}>
<MultiSelectField<Capability> name={"Capabilities"} value={capabilities} onChange={setCapabilities} possibleValues={["PublicReads", "DirectReads", "Writes", "Listings", "Reads"]}/>
</Box>
<Box mb={2}>
<StringField name={"SentinelLocation"} value={sentinelLocation} onChange={setSentinelLocation} />
</Box>
<Button type={"submit"}>Submit</Button>
</form>
)
Expand Down
1 change: 1 addition & 0 deletions web_ui/frontend/components/Config/index.d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export interface Export {
storageprefix: string;
federationprefix: string;
capabilities: Capability[];
sentinellocation: string;
}

export interface Path {
Expand Down
2 changes: 1 addition & 1 deletion xrootd/xrootd_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type (
S3AccessKeyfile string
S3SecretKeyfile string
S3UrlStyle string
Exports []server_utils.OriginExports
Exports []server_utils.OriginExport
}

CacheConfig struct {
Expand Down

0 comments on commit dd9cbae

Please sign in to comment.