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

ocis storage driver #559

Closed
wants to merge 18 commits into from
Closed

ocis storage driver #559

wants to merge 18 commits into from

Conversation

butonic
Copy link
Contributor

@butonic butonic commented Mar 13, 2020

A traditional filesystem uses paths to organize files in a tree. But path based references are not persistent because they change when a file is renamed or moved.
OCIS needs a persistent fileid to let other services reference files. Shares and favorites for example should not break when a file is moved or renamed.

A naive approach would use the fileid as the key and the path as the value for an efficient lookup. But this leads to n updates in the lookup table when a folder is renamed, because the path of all children changes.

Depending on the underlying storage a fast lookup by inode (btrfs, gpfs) or a truly unique fileid (eos) might be possible. For these a dedicated storage driver should be used. But keep in mind that inodes are reused, which might cause a purely inode based reference to point to a different file. Gpfs has an option to disable reusing inodes.

Ocisfs deconstructs a filesystem to provide a persistent fileid, an efficient file by ID lookup at the cost of more inodes.

/var/tmp/ocis/root/
├── nodes
│   ├── 1dd84abf-9466-4e14-bb86-02fc4ea3abcf
│   ├── 5425aaf2-8fa4-497e-b0e6-ed3f0809e350
│   │   └── bif -> ../d1bba60c-87a9-41a3-88a2-abd221abc100
│   ├── 8f16ec6c-6314-4f44-b553-d97abd464d06
│   │   ├── [MS-SFU].pdf -> ../b3951b72-cce9-4ab0-9992-f13b164ebb5b
│   │   └── foo -> ../9822b551-3c9a-4291-8a3a-4fccdbe8bcaa
│   ├── 9822b551-3c9a-4291-8a3a-4fccdbe8bcaa
│   │   └── bar -> ../5425aaf2-8fa4-497e-b0e6-ed3f0809e350
│   ├── b3951b72-cce9-4ab0-9992-f13b164ebb5b
│   └── d1bba60c-87a9-41a3-88a2-abd221abc100
├── trash
│   └── 1dd84abf-9466-4e14-bb86-02fc4ea3abcf -> ../nodes/1dd84abf-9466-4e14-bb86-02fc4ea3abcf
├── uploads
│   ├── 162c3dc6-b117-434d-b6fb-f1a350017d68.info
│   └── 322fa701-12ad-4a77-a165-28199a96aecc.info
├── users
│   └── 4c510ada-c86b-4815-8820-42cdf82c3d51 -> ../nodes/8f16ec6c-6314-4f44-b553-d97abd464d06
└── versions

Every file and folder is represented by an entry in the nodes folder. Directories have a children subfolder containing symlinks to child nodes. Files have a content file. A versions subfolder contains timestamp content entries.

While this node based layout may look confusing at first sight not everything is lost. The symlink pattern used in the children folder allow navigating the filesystem using the traditional tree hierarchy. In fact it is used to implement the path based lookup.

Why the content and children nodes? Why not use a uuid file or folder? Because we need a parent link? We can store that as an extended attribute instead of the parentname symlink.
We need the filename (changes on rename) and parentid (changes on move). This duplication might cause inconsistencies. Which could be mitigated using a journal with file operations.

The etag is an extended attribute anyway. Additional properties could be stored either in the fs as extended attributes or as an additional file in a root/properties/uuid Json or ini file. Might be an option

Versions could be stored in additional root/versions/uuid/timestamp files.

Basic file management is implemented, but this is missing:

  • Rename a node (inside the same directory)
  • Move (a node into another directory)
  • etag propagation
  • Sharing, take from owncloud driver
  • Delete to trash
    • link to trashed node in a users trash folder
    • make From* methods walk the path to determine if the node has been deleted parents)
  • List Trash
  • Restore trash item
  • Versions
  • propagation for Move fails, see ocis storage driver #559 (comment)
  • upload
  • extended attributes
  • get rid of FillParentAndName, BecomeParent and
    • blob as content?
    • or using versions?
    • content addressed?

Feedback welcome.

@butonic
Copy link
Contributor Author

butonic commented Sep 1, 2020

Move fails with

2020-09-01T16:17:44+02:00 INF unary code=OK end="01/Sep/2020:16:17:44 +0200" from=tcp://[::1]:37302 pkg=rgrpc service=reva start="01/Sep/2020:16:17:44 +0200" time_ns=4167182 traceid=eeac0c5e1646257d1cf24f3d4aaeab55 uri=/cs3.gateway.v1beta1.GatewayAPI/Stat user-agent=grpc-go/1.26.0
2020-09-01T16:17:44+02:00 DBG skipping auth method=/cs3.gateway.v1beta1.GatewayAPI/Move pkg=rgrpc service=reva traceid=eeac0c5e1646257d1cf24f3d4aaeab55
2020-09-01T16:17:44+02:00 INF unary code=OK end="01/Sep/2020:16:17:44 +0200" from=tcp://[::1]:37260 pkg=rgrpc service=reva start="01/Sep/2020:16:17:44 +0200" time_ns=362160 traceid=eeac0c5e1646257d1cf24f3d4aaeab55 uri=/cs3.storage.registry.v1beta1.RegistryAPI/GetStorageProvider user-agent=grpc-go/1.26.0
2020-09-01T16:17:44+02:00 INF unary code=OK end="01/Sep/2020:16:17:44 +0200" from=tcp://[::1]:37260 pkg=rgrpc service=reva start="01/Sep/2020:16:17:44 +0200" time_ns=285833 traceid=eeac0c5e1646257d1cf24f3d4aaeab55 uri=/cs3.storage.registry.v1beta1.RegistryAPI/GetStorageProvider user-agent=grpc-go/1.26.0
2020-09-01T16:17:44+02:00 ERR home/jfd/Repositories/reva/internal/grpc/services/storageprovider/storageprovider.go:439 > error moving file error="ocisfs: Move: could not propagate old node: ocisfs: Propagate: readlink error: ocisfs: invalid node info '&{ParentID:8ce51b56-4382-4838-8c36-0e340045a7a7 ID: Name:bam Exists:false}'" pkg=rgrpc service=reva traceid=eeac0c5e1646257d1cf24f3d4aaeab55
2020-09-01T16:17:44+02:00 INF unary code=OK end="01/Sep/2020:16:17:44 +0200" from=tcp://[::1]:33198 pkg=rgrpc service=reva start="01/Sep/2020:16:17:44 +0200" time_ns=1361495 traceid=eeac0c5e1646257d1cf24f3d4aaeab55 uri=/cs3.storage.provider.v1beta1.ProviderAPI/Move user-agent=grpc-go/1.26.0
2020-09-01T16:17:44+02:00 INF unary code=OK end="01/Sep/2020:16:17:44 +0200" from=tcp://[::1]:37302 pkg=rgrpc service=reva start="01/Sep/2020:16:17:44 +0200" time_ns=5021947 traceid=eeac0c5e1646257d1cf24f3d4aaeab55 uri=/cs3.gateway.v1beta1.GatewayAPI/Move user-agent=grpc-go/1.26.0
2020-09-01T16:17:44+02:00 ERR error handling move request move_request=CODE_INTERNAL pkg=rhttp service=reva traceid=eeac0c5e1646257d1cf24f3d4aaeab55
2020-09-01T16:17:44+02:00 ERR http end="01/Sep/2020:16:17:44 +0200" host=::1 method=MOVE pkg=rhttp proto=HTTP/1.1 service=reva size=0 start="01/Sep/2020:16:17:44 +0200" status=500 time_ns=23075986 traceid=eeac0c5e1646257d1cf24f3d4aaeab55 uri=/remote.php/webdav/bam url=/remote.php/webdav/bam

Copy link
Contributor

@PVince81 PVince81 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments, many already discussed partly

pkg/storage/fs/ocis/node.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/node.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/ocis.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/ocis.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/ocis.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/tree.go Outdated Show resolved Hide resolved
func (fs *Tree) Propagate(ctx context.Context, node *NodeInfo) (err error) {
// generate an etag
bytes := make([]byte, 16)
if _, err := rand.Read(bytes); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I seem to remember you saying that we generate the etag based on mtime ? or was it just an idea ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an idea ... for migrations / backup we need to be able to store an etag. the owncloud driver already has some logic for that.


node.BecomeParent()
}
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems we don't have mtime propagation here


var defaultFilePerm = os.FileMode(0664)

// TODO deprecated ... use tus
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate ticket or checkbox ?

pkg/storage/fs/ocis/upload.go Outdated Show resolved Hide resolved
@butonic butonic changed the title [WIP] local ocis driver [WIP] ocis storage driver Sep 7, 2020
Copy link
Contributor

@PVince81 PVince81 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments for latest additions

pkg/storage/fs/ocis/grants.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/grants.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/grants.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/grants.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/grants.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/tree.go Outdated Show resolved Hide resolved
pkg/storage/fs/ocis/tree.go Show resolved Hide resolved

_, err = io.Copy(tmp, r)
if err != nil {
return errors.Wrap(err, "ocisfs: error writing to tmp file "+tmp.Name())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete temp file on error or does this happen automatically ? how often ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm ... the put is deprecated ... but yeah we should delete the file in this case

// versions are stored alongside the actual file, so a rename can be efficient and does not cross storage / partition boundaries
versionsPath := filepath.Join(upload.fs.conf.Root, "nodes", n.ID+".REV."+fi.ModTime().UTC().Format(time.RFC3339Nano))

if err := os.Rename(targetPath, versionsPath); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a copy might be better in case the below rename fails, in which case the file would disappear completely from view

return err
}

if err := xattr.Set(targetPath, "user.ocis.parentid", []byte(n.ParentID)); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if we're going too low-level here and whether the upload code should talk to a higher level API, one that simply configures the node correctly, with xattrs and children.

@PVince81
Copy link
Contributor

PVince81 commented Sep 8, 2020

let's get this merged, since the main file operations work

follow up for the remaining tasks:

PVince81
PVince81 previously approved these changes Sep 8, 2020
Copy link
Contributor

@PVince81 PVince81 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, as discussed 👍

@butonic butonic changed the title [WIP] ocis storage driver ocis storage driver Sep 8, 2020
@PVince81
Copy link
Contributor

PVince81 commented Sep 8, 2020

weird results:

runsh: Total unexpected failed scenarios throughout the test run:
apiTrashbin/trashbinFilesFolders.feature:185
apiTrashbin/trashbinFilesFolders.feature:186
apiTrashbin/trashbinFilesFolders.feature:203
apiTrashbin/trashbinFilesFolders.feature:204
apiTrashbin/trashbinFilesFolders.feature:227
apiTrashbin/trashbinFilesFolders.feature:228
apiTrashbin/trashbinFilesFolders.feature:241
apiTrashbin/trashbinFilesFolders.feature:242
apiTrashbin/trashbinFilesFolders.feature:255
apiTrashbin/trashbinFilesFolders.feature:256
runsh: Total unexpected passed scenarios throughout the test run:
apiTrashbin/trashbinFilesFolders.feature:217
apiTrashbin/trashbinFilesFolders.feature:218
apiTrashbin/trashbinFilesFolders.feature:235
apiTrashbin/trashbinFilesFolders.feature:236
apiTrashbin/trashbinFilesFolders.feature:259
apiTrashbin/trashbinFilesFolders.feature:260
apiTrashbin/trashbinFilesFolders.feature:273
apiTrashbin/trashbinFilesFolders.feature:274
apiTrashbin/trashbinFilesFolders.feature:287
apiTrashbin/trashbinFilesFolders.feature:288

so this broke something related to trashbin on OC storage but also fixed other things ?!

@PVince81
Copy link
Contributor

PVince81 commented Sep 8, 2020

@butonic can you add instructions in the top POST about how to test it ? it is not clear to me what env variables to switch to get the proper working setup

@PVince81
Copy link
Contributor

PVince81 commented Sep 8, 2020

we might want for #1148 to be merged first

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
@phil-davis
Copy link
Contributor

so this broke something related to trashbin on OC storage but also fixed other things ?!

I guess that it needs a rebase. The core commit id to use for tests is embedded in .drone.star - so it will be using whatever is the core commit id in .drone.star in this branch, but the expected-failures list will be as it is in master.

@PVince81
Copy link
Contributor

PVince81 commented Sep 8, 2020

the failing tests are related to accessing another user's trashbin.
instead of getting a 401 we receive an empty XML response.
this is reproducible locally

  Scenario: Listing other user's trashbin is prohibited                                    # /srv/www/htdocs/owncloud/tests/acceptance/features/apiTrashbin/trashbinFilesFolders.feature:173
    Given using old DAV path                                                               # FeatureContext::usingOldOrNewDavPath()
    And user "testtrashbin100" has been created with default attributes and skeleton files # FeatureContext::userHasBeenCreatedWithDefaultAttributes()
    And user "Brian" has been created with default attributes and without skeleton files   # FeatureContext::userHasBeenCreatedWithDefaultAttributesAndWithoutSkeletonFiles()
    And user "testtrashbin100" has deleted file "/textfile1.txt"                           # FeatureContext::userHasDeletedFile()
    When user "Brian" tries to list the trashbin content for user "testtrashbin100"        # TrashbinContext::userTriesToListTheTrashbinContentForUser()
      Received empty response where XML was expected (Exception)
    Then the HTTP status code should be "401"                                              # FeatureContext::thenTheHTTPStatusCodeShouldBe()
    And the last webdav response should not contain the following elements                 # TrashbinContext::theLastWebdavResponseShouldNotContainFollowingElements()
      | path          | user            |
      | textfile1.txt | testtrashbin100 |

there is a slight chance that the permission refactoring that happened affects this

@PVince81
Copy link
Contributor

PVince81 commented Sep 8, 2020

more bad luck striking:

0. init.................. FAIL (connection refused by `revad-services' port 20080: Connection refused)

@PVince81
Copy link
Contributor

PVince81 commented Sep 8, 2020

okay, so now all tests passed (except litmus hiccup)... so the rebase worked...

@phil-davis
Copy link
Contributor

It would be nice to have #1148 merged - that will get the core API test suite having some more accept share scenarios, which are running in the ocis and ocis-reva repos today. I am not sure who is able to review-approve-merge that. Then a rebase here and it should be good.

@butonic
Copy link
Contributor Author

butonic commented Sep 11, 2020

we should close this in favor of #1155

@butonic butonic closed this Sep 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants