Implemented filesystem endpoint with support for hardlinks, symlinks and copy.

This commit is contained in:
Clemens Rabe
2017-03-24 16:34:13 +01:00
parent ac475c0a10
commit 25f9c29f00
57 changed files with 1178 additions and 50 deletions
+78 -11
View File
@@ -5,6 +5,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/smira/aptly/aptly"
@@ -13,18 +14,53 @@ import (
// PublishedStorage abstract file system with public dirs (published repos)
type PublishedStorage struct {
rootPath string
rootPath string
linkMethod uint
verifyMethod uint
}
// Check interfaces
var (
_ aptly.PublishedStorage = (*PublishedStorage)(nil)
_ aptly.LocalPublishedStorage = (*PublishedStorage)(nil)
_ aptly.PublishedStorage = (*PublishedStorage)(nil)
_ aptly.FileSystemPublishedStorage = (*PublishedStorage)(nil)
)
// Constants defining the type of creating links
const (
LinkMethodHardLink uint = iota
LinkMethodSymLink
LinkMethodCopy
)
// Constants defining the type of file verification for LinkMethodCopy
const (
VerificationMethodChecksum uint = iota
VerificationMethodFileSize
)
// NewPublishedStorage creates new instance of PublishedStorage which specified root
func NewPublishedStorage(root string) *PublishedStorage {
return &PublishedStorage{rootPath: filepath.Join(root, "public")}
func NewPublishedStorage(root string, linkMethod string, verifyMethod string) *PublishedStorage {
// Ensure linkMethod is one of 'hardlink', 'symlink', 'copy'
var verifiedLinkMethod uint
if strings.EqualFold(linkMethod, "copy") {
verifiedLinkMethod = LinkMethodCopy
} else if strings.EqualFold(linkMethod, "symlink") {
verifiedLinkMethod = LinkMethodSymLink
} else {
verifiedLinkMethod = LinkMethodHardLink
}
var verifiedVerifyMethod uint
if strings.EqualFold(verifyMethod, "size") {
verifiedVerifyMethod = VerificationMethodFileSize
} else {
verifiedVerifyMethod = VerificationMethodChecksum
}
return &PublishedStorage{rootPath: root, linkMethod: verifiedLinkMethod,
verifyMethod: verifiedVerifyMethod}
}
// PublicPath returns root of public part
@@ -105,12 +141,35 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourceP
return err
}
srcSys := srcStat.Sys().(*syscall.Stat_t)
dstSys := dstStat.Sys().(*syscall.Stat_t)
if storage.linkMethod == LinkMethodCopy {
if storage.verifyMethod == VerificationMethodFileSize {
// if source and destination have the same size, no need to copy
if srcStat.Size() == dstStat.Size() {
return nil
}
} else {
// if source and destination have the same checksums, no need to copy
dstMD5, err := utils.MD5ChecksumForFile(filepath.Join(poolPath, baseName))
// source and destination inodes match, no need to link
if srcSys.Ino == dstSys.Ino {
return nil
if err != nil {
return err
}
if dstMD5 == sourceChecksums.MD5 {
return nil
}
}
} else {
srcSys := srcStat.Sys().(*syscall.Stat_t)
dstSys := dstStat.Sys().(*syscall.Stat_t)
// if source and destination inodes match, no need to link
// Symlink can point to different filesystem with identical inodes
// so we have to check the device as well.
if srcSys.Ino == dstSys.Ino && srcSys.Dev == dstSys.Dev {
return nil
}
}
// source and destination have different inodes, if !forced, this is fatal error
@@ -126,7 +185,15 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourceP
}
// destination doesn't exist (or forced), create link
return os.Link(sourcePath, filepath.Join(poolPath, baseName))
if storage.linkMethod == LinkMethodCopy {
err = utils.CopyFile(sourcePath, filepath.Join(poolPath, baseName))
} else if storage.linkMethod == LinkMethodSymLink {
err = os.Symlink(sourcePath, filepath.Join(poolPath, baseName))
} else {
err = os.Link(sourcePath, filepath.Join(poolPath, baseName))
}
return err
}
// Filelist returns list of files under prefix
+84 -8
View File
@@ -12,19 +12,40 @@ import (
)
type PublishedStorageSuite struct {
root string
storage *PublishedStorage
root string
storage *PublishedStorage
storageSymlink *PublishedStorage
storageCopy *PublishedStorage
storageCopySize *PublishedStorage
}
var _ = Suite(&PublishedStorageSuite{})
func (s *PublishedStorageSuite) SetUpTest(c *C) {
s.root = c.MkDir()
s.storage = NewPublishedStorage(s.root)
s.storage = NewPublishedStorage(filepath.Join(s.root, "public"), "", "")
s.storageSymlink = NewPublishedStorage(filepath.Join(s.root, "public_symlink"), "symlink", "")
s.storageCopy = NewPublishedStorage(filepath.Join(s.root, "public_copy"), "copy", "")
s.storageCopySize = NewPublishedStorage(filepath.Join(s.root, "public_copysize"), "copy", "size")
}
func (s *PublishedStorageSuite) TestLinkMethodField(c *C) {
c.Assert(s.storage.linkMethod, Equals, LinkMethodHardLink)
c.Assert(s.storageSymlink.linkMethod, Equals, LinkMethodSymLink)
c.Assert(s.storageCopy.linkMethod, Equals, LinkMethodCopy)
c.Assert(s.storageCopySize.linkMethod, Equals, LinkMethodCopy)
}
func (s *PublishedStorageSuite) TestVerifyMethodField(c *C) {
c.Assert(s.storageCopy.verifyMethod, Equals, VerificationMethodChecksum)
c.Assert(s.storageCopySize.verifyMethod, Equals, VerificationMethodFileSize)
}
func (s *PublishedStorageSuite) TestPublicPath(c *C) {
c.Assert(s.storage.PublicPath(), Equals, filepath.Join(s.root, "public"))
c.Assert(s.storageSymlink.PublicPath(), Equals, filepath.Join(s.root, "public_symlink"))
c.Assert(s.storageCopy.PublicPath(), Equals, filepath.Join(s.root, "public_copy"))
c.Assert(s.storageCopySize.PublicPath(), Equals, filepath.Join(s.root, "public_copysize"))
}
func (s *PublishedStorageSuite) TestMkDir(c *C) {
@@ -35,7 +56,7 @@ func (s *PublishedStorageSuite) TestMkDir(c *C) {
c.Assert(err, IsNil)
}
func (s *PublishedStorageSuite) TesPutFile(c *C) {
func (s *PublishedStorageSuite) TestPutFile(c *C) {
err := s.storage.MkDir("ppa/dists/squeeze/")
c.Assert(err, IsNil)
@@ -156,7 +177,10 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
err = ioutil.WriteFile(t.sourcePath, []byte("Contents"), 0644)
c.Assert(err, IsNil)
err = s.storage.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, utils.ChecksumInfo{}, false)
sourceChecksum, err := utils.ChecksumsForFile(t.sourcePath)
c.Assert(err, IsNil)
err = s.storage.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, sourceChecksum, false)
c.Assert(err, IsNil)
st, err := os.Stat(filepath.Join(s.storage.rootPath, t.prefix, t.expectedFilename))
@@ -164,6 +188,36 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
info := st.Sys().(*syscall.Stat_t)
c.Check(int(info.Nlink), Equals, 2)
// Test using symlinks
err = s.storageSymlink.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, sourceChecksum, false)
c.Assert(err, IsNil)
st, err = os.Stat(filepath.Join(s.storageSymlink.rootPath, t.prefix, t.expectedFilename))
c.Assert(err, IsNil)
info = st.Sys().(*syscall.Stat_t)
c.Check(int(info.Nlink), Equals, 2)
// Test using copy with checksum verification
err = s.storageCopy.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, sourceChecksum, false)
c.Assert(err, IsNil)
st, err = os.Stat(filepath.Join(s.storageCopy.rootPath, t.prefix, t.expectedFilename))
c.Assert(err, IsNil)
info = st.Sys().(*syscall.Stat_t)
c.Check(int(info.Nlink), Equals, 1)
// Test using copy with size verification
err = s.storageCopySize.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, sourceChecksum, false)
c.Assert(err, IsNil)
st, err = os.Stat(filepath.Join(s.storageCopySize.rootPath, t.prefix, t.expectedFilename))
c.Assert(err, IsNil)
info = st.Sys().(*syscall.Stat_t)
c.Check(int(info.Nlink), Equals, 1)
}
// test linking files to duplicate final name
@@ -171,10 +225,14 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
err := os.MkdirAll(filepath.Dir(sourcePath), 0755)
c.Assert(err, IsNil)
err = ioutil.WriteFile(sourcePath, []byte("Contents"), 0644)
// use same size to ensure copy with size check will fail on this one
err = ioutil.WriteFile(sourcePath, []byte("cONTENTS"), 0644)
c.Assert(err, IsNil)
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, utils.ChecksumInfo{}, false)
sourceChecksum, err := utils.ChecksumsForFile(sourcePath)
c.Assert(err, IsNil)
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, false)
c.Check(err, ErrorMatches, ".*file already exists and is different")
st, err := os.Stat(sourcePath)
@@ -184,7 +242,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
c.Check(int(info.Nlink), Equals, 1)
// linking with force
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, utils.ChecksumInfo{}, true)
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, true)
c.Check(err, IsNil)
st, err = os.Stat(sourcePath)
@@ -192,4 +250,22 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
info = st.Sys().(*syscall.Stat_t)
c.Check(int(info.Nlink), Equals, 2)
// Test using symlinks
err = s.storageSymlink.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, false)
c.Check(err, ErrorMatches, ".*file already exists and is different")
err = s.storageSymlink.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, true)
c.Check(err, IsNil)
// Test using copy with checksum verification
err = s.storageCopy.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, false)
c.Check(err, ErrorMatches, ".*file already exists and is different")
err = s.storageCopy.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, true)
c.Check(err, IsNil)
// Test using copy with size verification (this will NOT detect the difference)
err = s.storageCopySize.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, false)
c.Check(err, IsNil)
}