Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement remove command #89

Open
wants to merge 35 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
200ae79
Add implentation to remove files and dirs from a set
rk1274 Jan 15, 2025
2d73f4d
Add implementation to remove metadata and files from iRODS
rk1274 Jan 17, 2025
ec3ce10
Add test for removing hardlink files
rk1274 Jan 17, 2025
45ab258
Add implementation for removing hardlinks and dirs from iRODS
rk1274 Jan 20, 2025
f3c6283
Add validation to check if files/dirs are in the set
rk1274 Jan 21, 2025
b04ee4e
Refactor removeFilesFromIRODS and removeDirsFromIRODS functions
rk1274 Jan 21, 2025
e8bfdf2
Fix wrong implementation about deleting hardlink inodes file
rk1274 Jan 22, 2025
b879294
Add implementation to remove requester from iRODS metadata
rk1274 Jan 22, 2025
fc55102
Add extra validation for different user`s sets with the same name WIP
rk1274 Jan 22, 2025
3e418f2
Refactor handleSetsAndRequesters
rk1274 Jan 23, 2025
043d554
Fix bug when removing a failed file that was successful in a differen…
rk1274 Jan 24, 2025
26fc4be
Update set counts after removal WIP
rk1274 Jan 24, 2025
ace746b
Refactor SetNewCounts to recalculate file counts
y-popov Jan 31, 2025
73708fe
Combine removeFiles and removeDirs endpoints and add recently removed…
rk1274 Feb 3, 2025
48dc79d
Implement removeQueue WIP
rk1274 Feb 3, 2025
25131d3
Finish implentation for removeQueue WIP
rk1274 Feb 4, 2025
973a34d
Finish removeQueue implementation
rk1274 Feb 4, 2025
53aa3bb
Update status to have a `Removal status` line when removing
rk1274 Feb 4, 2025
cd1bae3
Handle removal retries
rk1274 Feb 5, 2025
18c0cb8
Refactor removeQueue implementation
rk1274 Feb 5, 2025
812e3a0
Add handling for when an item is removed from iRODS but fails to be r…
rk1274 Feb 6, 2025
6818231
Delint and fix tests
rk1274 Feb 7, 2025
2b954c6
Refactor classification of a path to use db instead of file system
rk1274 Feb 12, 2025
621627a
Modify code for pull request
rk1274 Feb 12, 2025
4b87f5b
Small PR fixes
rk1274 Feb 13, 2025
596919f
Change implementation to carry out iRODS commands through a handler o…
rk1274 Feb 14, 2025
540463d
Store removeReq in db incase server shutdown WIP
rk1274 Feb 14, 2025
345cfbb
Allow removal to recover if server goes down
rk1274 Feb 17, 2025
9ae8992
Small PR fixes, and optimisation of GetFilesInDir
rk1274 Feb 18, 2025
7d3b389
Move removal functionality from put pkg to remove pkg
rk1274 Feb 18, 2025
a4224f3
Add new baton pkg and make remove.handler use baton.GetBatonHandler()…
rk1274 Feb 19, 2025
891de16
Make remove.handler use baton.GetBatonHandler()
rk1274 Feb 19, 2025
12d625d
Refactor code to move baton tests into baton pkg and add new tests
rk1274 Feb 20, 2025
22ac24f
Add tests for mock (localHandler)
rk1274 Feb 20, 2025
78ed27e
Add tests for remove pkg and refactor remove functions
rk1274 Feb 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 176 additions & 67 deletions put/baton.go → baton/baton.go

Large diffs are not rendered by default.

243 changes: 243 additions & 0 deletions baton/baton_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*******************************************************************************
* Copyright (c) 2025 Genome Research Ltd.
*
* Author: Rosie Kern <[email protected]>
* Author: Iaroslav Popov <[email protected]>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************/

package baton

import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"

. "github.com/smartystreets/goconvey/convey"
"github.com/wtsi-hgi/ibackup/internal"
)

func TestBaton(t *testing.T) {
h, errgbh := GetBatonHandler()
if errgbh != nil {
t.Logf("GetBatonHandler error: %s", errgbh)
SkipConvey("Skipping baton tests since couldn't find baton", t, func() {})

return
}

remotePath := os.Getenv("IBACKUP_TEST_COLLECTION")
if remotePath == "" {
SkipConvey("Skipping baton tests since IBACKUP_TEST_COLLECTION is not defined", t, func() {})

return
}

resetIRODS(remotePath)

localPath := t.TempDir()

Convey("Given clients on a baton handler", t, func() {
err := h.InitClients()
So(err, ShouldBeNil)

So(h.removeClient.IsRunning(), ShouldBeTrue)
So(h.metaClient.IsRunning(), ShouldBeTrue)
So(h.putClient.IsRunning(), ShouldBeTrue)

meta := map[string]string{
"ibackup:test:a": "1",
"ibackup:test:b": "2",
}

Convey("And a local file", func() {
file1local := filepath.Join(localPath, "file1")
file1remote := filepath.Join(remotePath, "file1")

internal.CreateTestFileOfLength(t, file1local, 1)

Convey("You can stat a file not in iRODS", func() {
exists, _, errs := h.Stat(file1remote)
So(errs, ShouldBeNil)
So(exists, ShouldBeFalse)
})

Convey("You can put the file in iRODS", func() {
err = h.Put(file1local, file1remote)
So(err, ShouldBeNil)

So(isObjectInIRODS(remotePath, "file1"), ShouldBeTrue)

Convey("And putting a new file in the same location will overwrite existing object", func() {
file2local := filepath.Join(localPath, "file2")
sizeOfFile2 := 10

internal.CreateTestFileOfLength(t, file2local, sizeOfFile2)

err = h.Put(file2local, file1remote)
So(err, ShouldBeNil)

So(getSizeOfObject(file1remote), ShouldEqual, sizeOfFile2)
})

Convey("And then you can add metadata to it", func() {
errm := h.AddMeta(file1remote, meta)
So(errm, ShouldBeNil)

So(getRemoteMeta(file1remote), ShouldContainSubstring, "ibackup:test:a")
So(getRemoteMeta(file1remote), ShouldContainSubstring, "ibackup:test:b")
})

Convey("And given metadata on the file in iRODS", func() {
for k, v := range meta {
addRemoteMeta(file1remote, k, v)
}

Convey("You can get the metadata from a file in iRODS", func() {
fileMeta, errm := h.GetMeta(file1remote)
So(errm, ShouldBeNil)

So(fileMeta, ShouldResemble, meta)
})

Convey("You can stat a file and get its metadata", func() {
exists, fileMeta, errm := h.Stat(file1remote)
So(errm, ShouldBeNil)
So(exists, ShouldBeTrue)
So(fileMeta, ShouldResemble, meta)
})

Convey("You can query if files contain specific metadata", func() {
files, errq := h.QueryMeta(remotePath, map[string]string{"ibackup:test:a": "1"})
So(errq, ShouldBeNil)

So(len(files), ShouldEqual, 1)
So(files, ShouldContain, file1remote)

files, errq = h.QueryMeta(remotePath, map[string]string{"ibackup:test:a": "2"})
So(errq, ShouldBeNil)

So(len(files), ShouldEqual, 0)
})

Convey("You can remove specific metadata from a file in iRODS", func() {
errm := h.RemoveMeta(file1remote, map[string]string{"ibackup:test:a": "1"})
So(errm, ShouldBeNil)

fileMeta, errm := h.GetMeta(file1remote)
So(errm, ShouldBeNil)

So(fileMeta, ShouldResemble, map[string]string{"ibackup:test:b": "2"})
})
})

Convey("And then you can remove it from iRODS", func() {
err = h.RemoveFile(file1remote)
So(err, ShouldBeNil)

So(isObjectInIRODS(remotePath, "file1"), ShouldBeFalse)
})
})
})

Convey("You can open collection clients and put an empty dir in iRODS", func() {
dir1remote := filepath.Join(remotePath, "dir1")
err = h.EnsureCollection(dir1remote)
So(err, ShouldBeNil)

So(h.collPool.IsOpen(), ShouldBeTrue)

for _, client := range h.collClients {
So(client.IsRunning(), ShouldBeTrue)
}

So(isObjectInIRODS(remotePath, "dir1"), ShouldBeTrue)

Convey("Then you can close the collection clients", func() {
err = h.CollectionsDone()
So(err, ShouldBeNil)

So(h.collPool.IsOpen(), ShouldBeFalse)
So(len(h.collClients), ShouldEqual, 0)

Convey("And then you can remove the dir from iRODS", func() {
err = h.RemoveDir(dir1remote)
So(err, ShouldBeNil)

So(isObjectInIRODS(remotePath, "dir1"), ShouldBeFalse)
})
})
})

Convey("You can close those clients", func() {
h.CloseClients()

So(h.AllClientsStopped(), ShouldBeTrue)
})
})
}

func resetIRODS(remotePath string) {
if remotePath == "" {
return
}

exec.Command("irm", "-r", remotePath).Run() //nolint:errcheck

exec.Command("imkdir", remotePath).Run() //nolint:errcheck
}

func isObjectInIRODS(remotePath, name string) bool {
output, err := exec.Command("ils", remotePath).CombinedOutput()
So(err, ShouldBeNil)

return strings.Contains(string(output), name)
}

func getRemoteMeta(path string) string {
output, err := exec.Command("imeta", "ls", "-d", path).CombinedOutput()
So(err, ShouldBeNil)

return string(output)
}

func addRemoteMeta(path, key, val string) {
output, err := exec.Command("imeta", "add", "-d", path, key, val).CombinedOutput()
if strings.Contains(string(output), "CATALOG_ALREADY_HAS_ITEM_BY_THAT_NAME") {
return
}

So(err, ShouldBeNil)
}

func getSizeOfObject(path string) int {
output, err := exec.Command("ils", "-l", path).CombinedOutput()
So(err, ShouldBeNil)

cols := strings.Fields(string(output))
size, err := strconv.Atoi(cols[3])
So(err, ShouldBeNil)

return size
}
3 changes: 2 additions & 1 deletion cmd/filestatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/dustin/go-humanize" //nolint:misspell
"github.com/spf13/cobra"
"github.com/wtsi-hgi/ibackup/baton"
"github.com/wtsi-hgi/ibackup/put"
"github.com/wtsi-hgi/ibackup/set"
"github.com/wtsi-npg/extendo/v2"
Expand Down Expand Up @@ -107,7 +108,7 @@ func newFSG(db *set.DBRO, filePath string, useIRods bool) *fileStatusGetter {
return fsg
}

put.GetBatonHandler() //nolint:errcheck
baton.GetBatonHandler() //nolint:errcheck

if client, err := extendo.FindAndStart("--unbuffered", "--no-error"); err != nil {
warn("error occurred invoking baton; disabling irods mode: %s", err)
Expand Down
5 changes: 3 additions & 2 deletions cmd/put.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"time"

"github.com/spf13/cobra"
"github.com/wtsi-hgi/ibackup/baton"
"github.com/wtsi-hgi/ibackup/put"
"github.com/wtsi-hgi/ibackup/server"
)
Expand Down Expand Up @@ -247,7 +248,7 @@ func handlePut(client *server.Client, requests []*put.Request) (chan *put.Reques
}

func getPutter(requests []*put.Request) (*put.Putter, func()) {
handler, err := put.GetBatonHandler()
handler, err := baton.GetBatonHandler()
if err != nil {
die("%s", err)
}
Expand Down Expand Up @@ -301,7 +302,7 @@ func createCollection(p *put.Putter) {
func handleManualMode() {
requests, err := getRequestsFromFile(putFile, putMeta, putBase64)
if err != nil {
die(err.Error())
die("%s", err.Error())
}

_, uploadResults, skipResults, dfunc := handlePut(nil, requests)
Expand Down
Loading
Loading