Local package pool and local publishing rewritten with new constraints

Local package pool now implements more generic package pool API.
The base idea is to never expose full paths to files, so that other
kinds of package pools (e.g. package pool in S3) could be used to implement
the same interface.

Files get into the pool only using `Import` method. `Import` method is
now more smart, it supports moving files into the pool, it can detect if
files reside on the same filesystem and use hardlinking instead of copying.
This will make direct mirror downloads still as fast as they were with previous
version which was performing download directly to package pool.

New package pool doesn't have two things implemented yet:

1. New file placement according to SHA256 or other configured hash

2. Calculate at least SHA256/MD5 for each imported files.
MD5 would be required for S3/Swift publishing
This commit is contained in:
Andrey Smirnov
2017-04-06 20:06:15 +03:00
parent 94b49818a1
commit 7bad358408
4 changed files with 294 additions and 106 deletions
+126 -36
View File
@@ -7,6 +7,9 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
"syscall"
"github.com/smira/go-uuid/uuid"
"github.com/smira/aptly/aptly" "github.com/smira/aptly/aptly"
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
@@ -15,21 +18,33 @@ import (
// PackagePool is deduplicated storage of package files on filesystem // PackagePool is deduplicated storage of package files on filesystem
type PackagePool struct { type PackagePool struct {
sync.Mutex sync.Mutex
rootPath string
rootPath string
supportLegacyPaths bool
} }
// Check interface // Check interface
var ( var (
_ aptly.PackagePool = (*PackagePool)(nil) _ aptly.PackagePool = (*PackagePool)(nil)
_ aptly.LocalPackagePool = (*PackagePool)(nil)
) )
// NewPackagePool creates new instance of PackagePool which specified root // NewPackagePool creates new instance of PackagePool which specified root
func NewPackagePool(root string) *PackagePool { func NewPackagePool(root string) *PackagePool {
return &PackagePool{rootPath: filepath.Join(root, "pool")} rootPath := filepath.Join(root, "pool")
rootPath, err := filepath.Abs(rootPath)
if err != nil {
panic(err)
}
return &PackagePool{
rootPath: rootPath,
supportLegacyPaths: true,
}
} }
// RelativePath returns path relative to pool's root for package files given checksum info and original filename // LegacyPath returns path relative to pool's root for pre-1.1 aptly (based on MD5)
func (pool *PackagePool) RelativePath(filename string, checksums utils.ChecksumInfo) (string, error) { func (pool *PackagePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) {
filename = filepath.Base(filename) filename = filepath.Base(filename)
if filename == "." || filename == "/" { if filename == "." || filename == "/" {
return "", fmt.Errorf("filename %s is invalid", filename) return "", fmt.Errorf("filename %s is invalid", filename)
@@ -44,16 +59,6 @@ func (pool *PackagePool) RelativePath(filename string, checksums utils.ChecksumI
return filepath.Join(hashMD5[0:2], hashMD5[2:4], filename), nil return filepath.Join(hashMD5[0:2], hashMD5[2:4], filename), nil
} }
// Path returns full path to package file in pool given filename and hash of file contents
func (pool *PackagePool) Path(filename string, checksums utils.ChecksumInfo) (string, error) {
relative, err := pool.RelativePath(filename, checksums)
if err != nil {
return "", err
}
return filepath.Join(pool.rootPath, relative), nil
}
// FilepathList returns file paths of all the files in the pool // FilepathList returns file paths of all the files in the pool
func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) { func (pool *PackagePool) FilepathList(progress aptly.Progress) ([]string, error) {
pool.Lock() pool.Lock()
@@ -117,56 +122,141 @@ func (pool *PackagePool) Remove(path string) (size int64, err error) {
} }
// Import copies file into package pool // Import copies file into package pool
func (pool *PackagePool) Import(path string, checksums utils.ChecksumInfo) error { //
// - srcPath is full path to source file as it is now
// - basename is desired human-readable name (canonical filename)
// - checksums are used to calculate file placement
// - move indicates whether srcPath can be removed
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool) (string, error) {
pool.Lock() pool.Lock()
defer pool.Unlock() defer pool.Unlock()
source, err := os.Open(path) source, err := os.Open(srcPath)
if err != nil { if err != nil {
return err return "", err
} }
defer source.Close() defer source.Close()
sourceInfo, err := source.Stat() sourceInfo, err := source.Stat()
if err != nil { if err != nil {
return err return "", err
} }
poolPath, err := pool.Path(path, checksums) // build target path
// TODO: replace with new build scheme
poolPath, err := pool.LegacyPath(basename, checksums)
if err != nil { if err != nil {
return err return "", err
} }
targetInfo, err := os.Stat(poolPath) fullPoolPath := filepath.Join(pool.rootPath, poolPath)
targetInfo, err := os.Stat(fullPoolPath)
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
// unable to stat target location? // unable to stat target location?
return err return "", err
} }
} else { } else {
// target already exists // target already exists and same size
if targetInfo.Size() != sourceInfo.Size() { if targetInfo.Size() == sourceInfo.Size() {
// trying to overwrite file? return poolPath, nil
return fmt.Errorf("unable to import into pool: file %s already exists", poolPath)
} }
// assume that target is already there // trying to overwrite file?
return nil return "", fmt.Errorf("unable to import into pool: file %s already exists", poolPath)
}
if pool.supportLegacyPaths {
// file doesn't exist at new location, check legacy location
var (
legacyTargetInfo os.FileInfo
legacyPath, legacyFullPath string
)
legacyPath, err = pool.LegacyPath(basename, checksums)
if err != nil {
return "", err
}
legacyFullPath = filepath.Join(pool.rootPath, legacyPath)
legacyTargetInfo, err = os.Stat(legacyFullPath)
if err != nil {
if !os.IsNotExist(err) {
return "", err
}
} else {
// legacy file exists
if legacyTargetInfo.Size() == sourceInfo.Size() {
// file exists at legacy path and it's same size, consider it's already in the pool
return legacyPath, nil
}
// size is different, import at new path
}
} }
// create subdirs as necessary // create subdirs as necessary
err = os.MkdirAll(filepath.Dir(poolPath), 0777) poolDir := filepath.Dir(fullPoolPath)
err = os.MkdirAll(poolDir, 0777)
if err != nil { if err != nil {
return err return "", err
} }
target, err := os.Create(poolPath) // check if we can use hardlinks instead of copying/moving
poolDirInfo, err := os.Stat(poolDir)
if err != nil { if err != nil {
return err return "", err
} }
defer target.Close()
_, err = io.Copy(target, source) if poolDirInfo.Sys().(*syscall.Stat_t).Dev == sourceInfo.Sys().(*syscall.Stat_t).Dev {
// same filesystem, try to use hardlink
err = os.Link(srcPath, fullPoolPath)
} else {
err = os.ErrInvalid
}
return err if err != nil {
// different filesystems or failed hardlink, fallback to copy
var target *os.File
target, err = os.Create(fullPoolPath)
if err != nil {
return "", err
}
defer target.Close()
_, err = io.Copy(target, source)
if err == nil {
err = target.Close()
}
}
if err == nil && move {
err = os.Remove(srcPath)
}
return poolPath, err
}
// Open returns io.ReadCloser to access the file
func (pool *PackagePool) Open(path string) (io.ReadCloser, error) {
return os.Open(filepath.Join(pool.rootPath, path))
}
// Stat returns Unix stat(2) info
func (pool *PackagePool) Stat(path string) (os.FileInfo, error) {
return os.Stat(filepath.Join(pool.rootPath, path))
}
// Link generates hardlink to destination path
func (pool *PackagePool) Link(path, dstPath string) error {
return os.Link(filepath.Join(pool.rootPath, path), dstPath)
}
// GenerateTempPath generates temporary path for download (which is fast to import into package pool later on)
func (pool *PackagePool) GenerateTempPath(filename string) (string, error) {
random := uuid.NewRandom().String()
return filepath.Join(pool.rootPath, random[0:2], random[2:4], random[4:]+filename), nil
} }
+98 -26
View File
@@ -1,10 +1,12 @@
package files package files
import ( import (
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"syscall"
"github.com/smira/aptly/utils" "github.com/smira/aptly/utils"
@@ -14,6 +16,7 @@ import (
type PackagePoolSuite struct { type PackagePoolSuite struct {
pool *PackagePool pool *PackagePool
checksum utils.ChecksumInfo checksum utils.ChecksumInfo
debFile string
} }
var _ = Suite(&PackagePoolSuite{}) var _ = Suite(&PackagePoolSuite{})
@@ -23,30 +26,23 @@ func (s *PackagePoolSuite) SetUpTest(c *C) {
s.checksum = utils.ChecksumInfo{ s.checksum = utils.ChecksumInfo{
MD5: "91b1a1480b90b9e269ca44d897b12575", MD5: "91b1a1480b90b9e269ca44d897b12575",
} }
_, _File, _, _ := runtime.Caller(0)
s.debFile = filepath.Join(filepath.Dir(_File), "../system/files/libboost-program-options-dev_1.49.0.1_i386.deb")
} }
func (s *PackagePoolSuite) TestRelativePath(c *C) { func (s *PackagePoolSuite) TestLegacyPath(c *C) {
path, err := s.pool.RelativePath("a/b/package.deb", s.checksum) path, err := s.pool.LegacyPath("a/b/package.deb", &s.checksum)
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(path, Equals, "91/b1/package.deb") c.Assert(path, Equals, "91/b1/package.deb")
_, err = s.pool.RelativePath("/", s.checksum) _, err = s.pool.LegacyPath("/", &s.checksum)
c.Assert(err, ErrorMatches, ".*is invalid") c.Assert(err, ErrorMatches, ".*is invalid")
_, err = s.pool.RelativePath("", s.checksum) _, err = s.pool.LegacyPath("", &s.checksum)
c.Assert(err, ErrorMatches, ".*is invalid") c.Assert(err, ErrorMatches, ".*is invalid")
_, err = s.pool.RelativePath("a/b/package.deb", utils.ChecksumInfo{MD5: "9"}) _, err = s.pool.LegacyPath("a/b/package.deb", &utils.ChecksumInfo{MD5: "9"})
c.Assert(err, ErrorMatches, ".*MD5 is missing") c.Assert(err, ErrorMatches, ".*MD5 is missing")
} }
func (s *PackagePoolSuite) TestPath(c *C) {
path, err := s.pool.Path("a/b/package.deb", s.checksum)
c.Assert(err, IsNil)
c.Assert(path, Equals, filepath.Join(s.pool.rootPath, "91/b1/package.deb"))
_, err = s.pool.Path("/", s.checksum)
c.Assert(err, ErrorMatches, ".*is invalid")
}
func (s *PackagePoolSuite) TestFilepathList(c *C) { func (s *PackagePoolSuite) TestFilepathList(c *C) {
list, err := s.pool.FilepathList(nil) list, err := s.pool.FilepathList(nil)
c.Check(err, IsNil) c.Check(err, IsNil)
@@ -93,33 +89,109 @@ func (s *PackagePoolSuite) TestRemove(c *C) {
} }
func (s *PackagePoolSuite) TestImportOk(c *C) { func (s *PackagePoolSuite) TestImportOk(c *C) {
_, _File, _, _ := runtime.Caller(0) path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &s.checksum, false)
debFile := filepath.Join(filepath.Dir(_File), "../system/files/libboost-program-options-dev_1.49.0.1_i386.deb")
err := s.pool.Import(debFile, s.checksum)
c.Check(err, IsNil) c.Check(err, IsNil)
c.Check(path, Equals, "91/b1/libboost-program-options-dev_1.49.0.1_i386.deb")
info, err := os.Stat(filepath.Join(s.pool.rootPath, "91", "b1", "libboost-program-options-dev_1.49.0.1_i386.deb")) info, err := s.pool.Stat(path)
c.Check(err, IsNil) c.Assert(err, IsNil)
c.Check(info.Size(), Equals, int64(2738)) c.Check(info.Size(), Equals, int64(2738))
c.Check(info.Sys().(*syscall.Stat_t).Nlink > 1, Equals, true)
// import as different name
path, err = s.pool.Import(s.debFile, "some.deb", &s.checksum, false)
c.Check(err, IsNil)
c.Check(path, Equals, "91/b1/some.deb")
// double import, should be ok // double import, should be ok
err = s.pool.Import(debFile, s.checksum) path, err = s.pool.Import(s.debFile, filepath.Base(s.debFile), &s.checksum, false)
c.Check(err, IsNil) c.Check(err, IsNil)
c.Check(path, Equals, "91/b1/libboost-program-options-dev_1.49.0.1_i386.deb")
}
func (s *PackagePoolSuite) TestImportMove(c *C) {
tmpDir := c.MkDir()
tmpPath := filepath.Join(tmpDir, filepath.Base(s.debFile))
dst, err := os.Create(tmpPath)
c.Assert(err, IsNil)
src, err := os.Open(s.debFile)
c.Assert(err, IsNil)
_, err = io.Copy(dst, src)
c.Assert(err, IsNil)
c.Assert(dst.Close(), IsNil)
c.Assert(src.Close(), IsNil)
path, err := s.pool.Import(tmpPath, filepath.Base(tmpPath), &s.checksum, true)
c.Check(err, IsNil)
c.Check(path, Equals, "91/b1/libboost-program-options-dev_1.49.0.1_i386.deb")
info, err := s.pool.Stat(path)
c.Assert(err, IsNil)
c.Check(info.Size(), Equals, int64(2738))
c.Check(info.Sys().(*syscall.Stat_t).Nlink, Equals, uint16(1))
} }
func (s *PackagePoolSuite) TestImportNotExist(c *C) { func (s *PackagePoolSuite) TestImportNotExist(c *C) {
err := s.pool.Import("no-such-file", s.checksum) _, err := s.pool.Import("no-such-file", "a.deb", &s.checksum, false)
c.Check(err, ErrorMatches, ".*no such file or directory") c.Check(err, ErrorMatches, ".*no such file or directory")
} }
func (s *PackagePoolSuite) TestImportOverwrite(c *C) { func (s *PackagePoolSuite) TestImportOverwrite(c *C) {
_, _File, _, _ := runtime.Caller(0)
debFile := filepath.Join(filepath.Dir(_File), "../system/files/libboost-program-options-dev_1.49.0.1_i386.deb")
os.MkdirAll(filepath.Join(s.pool.rootPath, "91", "b1"), 0755) os.MkdirAll(filepath.Join(s.pool.rootPath, "91", "b1"), 0755)
ioutil.WriteFile(filepath.Join(s.pool.rootPath, "91", "b1", "libboost-program-options-dev_1.49.0.1_i386.deb"), []byte("1"), 0644) ioutil.WriteFile(filepath.Join(s.pool.rootPath, "91", "b1", "libboost-program-options-dev_1.49.0.1_i386.deb"), []byte("1"), 0644)
err := s.pool.Import(debFile, s.checksum) _, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &s.checksum, false)
c.Check(err, ErrorMatches, "unable to import into pool.*") c.Check(err, ErrorMatches, "unable to import into pool.*")
} }
func (s *PackagePoolSuite) TestStat(c *C) {
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &s.checksum, false)
c.Check(err, IsNil)
info, err := s.pool.Stat(path)
c.Assert(err, IsNil)
c.Check(info.Size(), Equals, int64(2738))
_, err = s.pool.Stat("do/es/ntexist")
c.Assert(os.IsNotExist(err), Equals, true)
}
func (s *PackagePoolSuite) TestOpen(c *C) {
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &s.checksum, false)
c.Check(err, IsNil)
f, err := s.pool.Open(path)
c.Assert(err, IsNil)
contents, err := ioutil.ReadAll(f)
c.Assert(err, IsNil)
c.Check(len(contents), Equals, 2738)
c.Check(f.Close(), IsNil)
_, err = s.pool.Open("do/es/ntexist")
c.Assert(os.IsNotExist(err), Equals, true)
}
func (s *PackagePoolSuite) TestLink(c *C) {
path, err := s.pool.Import(s.debFile, filepath.Base(s.debFile), &s.checksum, false)
c.Check(err, IsNil)
tmpDir := c.MkDir()
dstPath := filepath.Join(tmpDir, filepath.Base(s.debFile))
c.Check(s.pool.Link(path, dstPath), IsNil)
info, err := os.Stat(dstPath)
c.Assert(err, IsNil)
c.Check(info.Size(), Equals, int64(2738))
c.Check(info.Sys().(*syscall.Stat_t).Nlink > 2, Equals, true)
}
func (s *PackagePoolSuite) TestGenerateRandomPath(c *C) {
path, err := s.pool.GenerateTempPath("a.deb")
c.Check(err, IsNil)
c.Check(path, Matches, ".+/[0-9a-f][0-9a-f]/[0-9a-f][0-9a-f]/[0-9a-f-]+a\\.deb")
}
+32 -8
View File
@@ -114,13 +114,11 @@ func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress
// //
// publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/) // publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/)
// sourcePool is instance of aptly.PackagePool // sourcePool is instance of aptly.PackagePool
// sourcePath is filepath to package file in package pool // sourcePath is a relative path to package file in package pool
// //
// LinkFromPool returns relative path for the published file to be included in package index // LinkFromPool returns relative path for the published file to be included in package index
func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourcePool aptly.PackagePool, func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourcePool aptly.PackagePool,
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error { sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
// verify that package pool is local pool is filesystem pool
_ = sourcePool.(*PackagePool)
baseName := filepath.Base(sourcePath) baseName := filepath.Base(sourcePath)
poolPath := filepath.Join(storage.rootPath, publishedDirectory) poolPath := filepath.Join(storage.rootPath, publishedDirectory)
@@ -135,7 +133,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourceP
dstStat, err = os.Stat(filepath.Join(poolPath, baseName)) dstStat, err = os.Stat(filepath.Join(poolPath, baseName))
if err == nil { if err == nil {
// already exists, check source file // already exists, check source file
srcStat, err = os.Stat(sourcePath) srcStat, err = sourcePool.Stat(sourcePath)
if err != nil { if err != nil {
// source file doesn't exist? problem! // source file doesn't exist? problem!
return err return err
@@ -184,13 +182,39 @@ func (storage *PublishedStorage) LinkFromPool(publishedDirectory string, sourceP
} }
} }
// destination doesn't exist (or forced), create link // destination doesn't exist (or forced), create link or copy
if storage.linkMethod == LinkMethodCopy { if storage.linkMethod == LinkMethodCopy {
err = utils.CopyFile(sourcePath, filepath.Join(poolPath, baseName)) var r aptly.ReadSeekerCloser
r, err = sourcePool.Open(sourcePath)
if err != nil {
return err
}
var dst *os.File
dst, err = os.Create(filepath.Join(poolPath, baseName))
if err != nil {
r.Close()
return err
}
_, err = io.Copy(dst, r)
if err != nil {
r.Close()
dst.Close()
return err
}
err = r.Close()
if err != nil {
dst.Close()
return err
}
err = dst.Close()
} else if storage.linkMethod == LinkMethodSymLink { } else if storage.linkMethod == LinkMethodSymLink {
err = os.Symlink(sourcePath, filepath.Join(poolPath, baseName)) err = sourcePool.(aptly.LocalPackagePool).Symlink(sourcePath, filepath.Join(poolPath, baseName))
} else { } else {
err = os.Link(sourcePath, filepath.Join(poolPath, baseName)) err = sourcePool.(aptly.LocalPackagePool).Link(sourcePath, filepath.Join(poolPath, baseName))
} }
return err return err
+38 -36
View File
@@ -108,6 +108,7 @@ func (s *PublishedStorageSuite) TestRemoveDirs(c *C) {
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = s.storage.RemoveDirs("ppa/dists/", nil) err = s.storage.RemoveDirs("ppa/dists/", nil)
c.Assert(err, IsNil)
_, err = os.Stat(filepath.Join(s.storage.rootPath, "ppa/dists/squeeze/Release")) _, err = os.Stat(filepath.Join(s.storage.rootPath, "ppa/dists/squeeze/Release"))
c.Assert(err, NotNil) c.Assert(err, NotNil)
@@ -122,6 +123,7 @@ func (s *PublishedStorageSuite) TestRemove(c *C) {
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = s.storage.Remove("ppa/dists/squeeze/Release") err = s.storage.Remove("ppa/dists/squeeze/Release")
c.Assert(err, IsNil)
_, err = os.Stat(filepath.Join(s.storage.rootPath, "ppa/dists/squeeze/Release")) _, err = os.Stat(filepath.Join(s.storage.rootPath, "ppa/dists/squeeze/Release"))
c.Assert(err, NotNil) c.Assert(err, NotNil)
@@ -139,28 +141,28 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
{ // package name regular { // package name regular
prefix: "", prefix: "",
component: "main", component: "main",
sourcePath: "pool/01/ae/mars-invaders_1.03.deb", sourcePath: "mars-invaders_1.03.deb",
poolDirectory: "m/mars-invaders", poolDirectory: "m/mars-invaders",
expectedFilename: "pool/main/m/mars-invaders/mars-invaders_1.03.deb", expectedFilename: "pool/main/m/mars-invaders/mars-invaders_1.03.deb",
}, },
{ // lib-like filename { // lib-like filename
prefix: "", prefix: "",
component: "main", component: "main",
sourcePath: "pool/01/ae/libmars-invaders_1.03.deb", sourcePath: "libmars-invaders_1.03.deb",
poolDirectory: "libm/libmars-invaders", poolDirectory: "libm/libmars-invaders",
expectedFilename: "pool/main/libm/libmars-invaders/libmars-invaders_1.03.deb", expectedFilename: "pool/main/libm/libmars-invaders/libmars-invaders_1.03.deb",
}, },
{ // duplicate link, shouldn't panic { // duplicate link, shouldn't panic
prefix: "", prefix: "",
component: "main", component: "main",
sourcePath: "pool/01/ae/mars-invaders_1.03.deb", sourcePath: "mars-invaders_1.03.deb",
poolDirectory: "m/mars-invaders", poolDirectory: "m/mars-invaders",
expectedFilename: "pool/main/m/mars-invaders/mars-invaders_1.03.deb", expectedFilename: "pool/main/m/mars-invaders/mars-invaders_1.03.deb",
}, },
{ // prefix & component { // prefix & component
prefix: "ppa", prefix: "ppa",
component: "contrib", component: "contrib",
sourcePath: "pool/01/ae/libmars-invaders_1.04.deb", sourcePath: "libmars-invaders_1.04.deb",
poolDirectory: "libm/libmars-invaders", poolDirectory: "libm/libmars-invaders",
expectedFilename: "pool/contrib/libm/libmars-invaders/libmars-invaders_1.04.deb", expectedFilename: "pool/contrib/libm/libmars-invaders/libmars-invaders_1.04.deb",
}, },
@@ -169,38 +171,39 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
pool := NewPackagePool(s.root) pool := NewPackagePool(s.root)
for _, t := range tests { for _, t := range tests {
t.sourcePath = filepath.Join(s.root, t.sourcePath) tmpPath := filepath.Join(c.MkDir(), t.sourcePath)
err := ioutil.WriteFile(tmpPath, []byte("Contents"), 0644)
err := os.MkdirAll(filepath.Dir(t.sourcePath), 0755)
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = ioutil.WriteFile(t.sourcePath, []byte("Contents"), 0644) sourceChecksum, err := utils.ChecksumsForFile(tmpPath)
c.Assert(err, IsNil) c.Assert(err, IsNil)
sourceChecksum, err := utils.ChecksumsForFile(t.sourcePath) srcPoolPath, err := pool.Import(tmpPath, t.sourcePath, &utils.ChecksumInfo{MD5: "c1df1da7a1ce305a3b60af9d5733ac1d"}, false)
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = s.storage.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, sourceChecksum, false) // Test using hardlinks
err = s.storage.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, srcPoolPath, sourceChecksum, false)
c.Assert(err, IsNil) c.Assert(err, IsNil)
st, err := os.Stat(filepath.Join(s.storage.rootPath, t.prefix, t.expectedFilename)) st, err := os.Stat(filepath.Join(s.storage.rootPath, t.prefix, t.expectedFilename))
c.Assert(err, IsNil) c.Assert(err, IsNil)
info := st.Sys().(*syscall.Stat_t) info := st.Sys().(*syscall.Stat_t)
c.Check(int(info.Nlink), Equals, 2) c.Check(int(info.Nlink), Equals, 3)
// Test using symlinks // Test using symlinks
err = s.storageSymlink.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, sourceChecksum, false) err = s.storageSymlink.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, srcPoolPath, sourceChecksum, false)
c.Assert(err, IsNil) c.Assert(err, IsNil)
st, err = os.Stat(filepath.Join(s.storageSymlink.rootPath, t.prefix, t.expectedFilename)) st, err = os.Lstat(filepath.Join(s.storageSymlink.rootPath, t.prefix, t.expectedFilename))
c.Assert(err, IsNil) c.Assert(err, IsNil)
info = st.Sys().(*syscall.Stat_t) info = st.Sys().(*syscall.Stat_t)
c.Check(int(info.Nlink), Equals, 2) c.Check(int(info.Nlink), Equals, 1)
c.Check(int(info.Mode&syscall.S_IFMT), Equals, int(syscall.S_IFLNK))
// Test using copy with checksum verification // Test using copy with checksum verification
err = s.storageCopy.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, sourceChecksum, false) err = s.storageCopy.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, srcPoolPath, sourceChecksum, false)
c.Assert(err, IsNil) c.Assert(err, IsNil)
st, err = os.Stat(filepath.Join(s.storageCopy.rootPath, t.prefix, t.expectedFilename)) st, err = os.Stat(filepath.Join(s.storageCopy.rootPath, t.prefix, t.expectedFilename))
@@ -210,7 +213,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
c.Check(int(info.Nlink), Equals, 1) c.Check(int(info.Nlink), Equals, 1)
// Test using copy with size verification // Test using copy with size verification
err = s.storageCopySize.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, t.sourcePath, sourceChecksum, false) err = s.storageCopySize.LinkFromPool(filepath.Join(t.prefix, "pool", t.component, t.poolDirectory), pool, srcPoolPath, sourceChecksum, false)
c.Assert(err, IsNil) c.Assert(err, IsNil)
st, err = os.Stat(filepath.Join(s.storageCopySize.rootPath, t.prefix, t.expectedFilename)) st, err = os.Stat(filepath.Join(s.storageCopySize.rootPath, t.prefix, t.expectedFilename))
@@ -221,51 +224,50 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
} }
// test linking files to duplicate final name // test linking files to duplicate final name
sourcePath := filepath.Join(s.root, "pool/02/bc/mars-invaders_1.03.deb") tmpPath := filepath.Join(c.MkDir(), "mars-invaders_1.03.deb")
err := os.MkdirAll(filepath.Dir(sourcePath), 0755) err := ioutil.WriteFile(tmpPath, []byte("cONTENTS"), 0644)
c.Assert(err, IsNil) c.Assert(err, IsNil)
// use same size to ensure copy with size check will fail on this one sourceChecksum, err := utils.ChecksumsForFile(tmpPath)
err = ioutil.WriteFile(sourcePath, []byte("cONTENTS"), 0644)
c.Assert(err, IsNil) c.Assert(err, IsNil)
sourceChecksum, err := utils.ChecksumsForFile(sourcePath) srcPoolPath, err := pool.Import(tmpPath, "mars-invaders_1.03.deb", &utils.ChecksumInfo{MD5: "02bcda7a1ce305a3b60af9d5733ac1d"}, true)
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, false) st, err := pool.Stat(srcPoolPath)
c.Assert(err, IsNil)
nlinks := int(st.Sys().(*syscall.Stat_t).Nlink)
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, srcPoolPath, sourceChecksum, false)
c.Check(err, ErrorMatches, ".*file already exists and is different") c.Check(err, ErrorMatches, ".*file already exists and is different")
st, err := os.Stat(sourcePath) st, err = pool.Stat(srcPoolPath)
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Check(int(st.Sys().(*syscall.Stat_t).Nlink), Equals, nlinks)
info := st.Sys().(*syscall.Stat_t)
c.Check(int(info.Nlink), Equals, 1)
// linking with force // linking with force
err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, true) err = s.storage.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, srcPoolPath, sourceChecksum, true)
c.Check(err, IsNil) c.Check(err, IsNil)
st, err = os.Stat(sourcePath) st, err = pool.Stat(srcPoolPath)
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Check(int(st.Sys().(*syscall.Stat_t).Nlink), Equals, nlinks+1)
info = st.Sys().(*syscall.Stat_t)
c.Check(int(info.Nlink), Equals, 2)
// Test using symlinks // Test using symlinks
err = s.storageSymlink.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, false) err = s.storageSymlink.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, srcPoolPath, sourceChecksum, false)
c.Check(err, ErrorMatches, ".*file already exists and is different") 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) err = s.storageSymlink.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, srcPoolPath, sourceChecksum, true)
c.Check(err, IsNil) c.Check(err, IsNil)
// Test using copy with checksum verification // Test using copy with checksum verification
err = s.storageCopy.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, sourcePath, sourceChecksum, false) err = s.storageCopy.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, srcPoolPath, sourceChecksum, false)
c.Check(err, ErrorMatches, ".*file already exists and is different") 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) err = s.storageCopy.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, srcPoolPath, sourceChecksum, true)
c.Check(err, IsNil) c.Check(err, IsNil)
// Test using copy with size verification (this will NOT detect the difference) // 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) err = s.storageCopySize.LinkFromPool(filepath.Join("", "pool", "main", "m/mars-invaders"), pool, srcPoolPath, sourceChecksum, false)
c.Check(err, IsNil) c.Check(err, IsNil)
} }