2018-07-22 20:08:48 +00:00
|
|
|
package s3
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path"
|
|
|
|
|
|
|
|
"github.com/golang/glog"
|
|
|
|
"k8s.io/kubernetes/pkg/util/mount"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Implements Mounter
|
|
|
|
type s3backerMounter struct {
|
2018-07-27 10:56:28 +00:00
|
|
|
bucket *bucket
|
2018-07-22 20:08:48 +00:00
|
|
|
url string
|
|
|
|
region string
|
|
|
|
accessKeyID string
|
|
|
|
secretAccessKey string
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
2018-07-26 20:43:51 +00:00
|
|
|
s3backerCmd = "s3backer"
|
|
|
|
s3backerFsType = "xfs"
|
|
|
|
s3backerDevice = "file"
|
2018-07-22 20:08:48 +00:00
|
|
|
// blockSize to use in k
|
2018-07-27 10:56:28 +00:00
|
|
|
s3backerBlockSize = "128k"
|
|
|
|
s3backerDefaultSize = 1024 * 1024 * 1024 // 1GiB
|
2018-07-22 20:08:48 +00:00
|
|
|
)
|
|
|
|
|
2018-07-27 10:56:28 +00:00
|
|
|
func newS3backerMounter(bucket *bucket, cfg *Config) (Mounter, error) {
|
|
|
|
// s3backer cannot work with 0 size volumes
|
|
|
|
if bucket.CapacityBytes == 0 {
|
|
|
|
bucket.CapacityBytes = s3backerDefaultSize
|
|
|
|
}
|
2018-07-22 20:08:48 +00:00
|
|
|
s3backer := &s3backerMounter{
|
|
|
|
bucket: bucket,
|
|
|
|
url: cfg.Endpoint,
|
|
|
|
region: cfg.Region,
|
|
|
|
accessKeyID: cfg.AccessKeyID,
|
|
|
|
secretAccessKey: cfg.SecretAccessKey,
|
|
|
|
}
|
|
|
|
|
|
|
|
return s3backer, s3backer.writePasswd()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s3backer *s3backerMounter) String() string {
|
2018-07-27 10:56:28 +00:00
|
|
|
return s3backer.bucket.Name
|
2018-07-22 20:08:48 +00:00
|
|
|
}
|
|
|
|
|
2018-07-26 20:43:51 +00:00
|
|
|
func (s3backer *s3backerMounter) Stage(stageTarget string) error {
|
|
|
|
// s3backer requires two mounts
|
|
|
|
// first mount will fuse mount the bucket to a single 'file'
|
|
|
|
if err := s3backer.mountInit(stageTarget); err != nil {
|
2018-07-22 20:08:48 +00:00
|
|
|
return err
|
|
|
|
}
|
2018-07-26 20:43:51 +00:00
|
|
|
// ensure 'file' device is formatted
|
|
|
|
err := formatFs(s3backerFsType, path.Join(stageTarget, s3backerDevice))
|
|
|
|
if err != nil {
|
|
|
|
fuseUnmount(stageTarget, s3backerCmd)
|
2018-07-22 20:08:48 +00:00
|
|
|
}
|
2018-07-26 20:43:51 +00:00
|
|
|
return err
|
|
|
|
}
|
2018-07-22 20:08:48 +00:00
|
|
|
|
2018-07-26 20:43:51 +00:00
|
|
|
func (s3backer *s3backerMounter) Unstage(stageTarget string) error {
|
|
|
|
// Unmount the s3backer fuse mount
|
|
|
|
return fuseUnmount(stageTarget, s3backerCmd)
|
2018-07-22 20:08:48 +00:00
|
|
|
}
|
|
|
|
|
2018-07-26 20:43:51 +00:00
|
|
|
func (s3backer *s3backerMounter) Mount(source string, target string) error {
|
|
|
|
device := path.Join(source, s3backerDevice)
|
2018-07-22 20:08:48 +00:00
|
|
|
// second mount will mount the 'file' as a filesystem
|
2018-07-26 20:43:51 +00:00
|
|
|
err := mount.New("").Mount(device, target, s3backerFsType, []string{})
|
2018-07-22 20:08:48 +00:00
|
|
|
if err != nil {
|
|
|
|
// cleanup fuse mount
|
2018-07-26 20:43:51 +00:00
|
|
|
fuseUnmount(target, s3backerCmd)
|
2018-07-22 20:08:48 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s3backer *s3backerMounter) Unmount(targetPath string) error {
|
|
|
|
// Unmount the filesystem first
|
2018-07-26 20:43:51 +00:00
|
|
|
return mount.New("").Unmount(targetPath)
|
2018-07-22 20:08:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s3backer *s3backerMounter) mountInit(path string) error {
|
|
|
|
args := []string{
|
|
|
|
// baseURL must end with /
|
|
|
|
fmt.Sprintf("--baseURL=%s/", s3backer.url),
|
|
|
|
fmt.Sprintf("--blockSize=%v", s3backerBlockSize),
|
2018-07-27 10:56:28 +00:00
|
|
|
fmt.Sprintf("--size=%v", s3backer.bucket.CapacityBytes),
|
2018-07-22 20:08:48 +00:00
|
|
|
"--listBlocks",
|
2018-07-27 10:56:28 +00:00
|
|
|
s3backer.bucket.Name,
|
2018-07-22 20:08:48 +00:00
|
|
|
path,
|
|
|
|
}
|
|
|
|
if s3backer.region != "" {
|
|
|
|
args = append(args, fmt.Sprintf("--region=%s", s3backer.region))
|
|
|
|
}
|
|
|
|
|
|
|
|
return fuseMount(path, s3backerCmd, args)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s3backer *s3backerMounter) writePasswd() error {
|
|
|
|
pwFileName := fmt.Sprintf("%s/.s3backer_passwd", os.Getenv("HOME"))
|
|
|
|
pwFile, err := os.OpenFile(pwFileName, os.O_RDWR|os.O_CREATE, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = pwFile.WriteString(s3backer.accessKeyID + ":" + s3backer.secretAccessKey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pwFile.Close()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func formatFs(fsType string, device string) error {
|
|
|
|
diskMounter := &mount.SafeFormatAndMount{Interface: mount.New(""), Exec: mount.NewOsExec()}
|
|
|
|
format, err := diskMounter.GetDiskFormat(device)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if format != "" {
|
|
|
|
glog.Infof("Disk %s is already formatted with format %s", device, format)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
args := []string{
|
|
|
|
device,
|
|
|
|
}
|
|
|
|
cmd := exec.Command("mkfs."+fsType, args...)
|
|
|
|
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error formatting disk: %s", out)
|
|
|
|
}
|
2018-07-23 18:55:53 +00:00
|
|
|
glog.Infof("Formatting fs with type %s", fsType)
|
2018-07-22 20:08:48 +00:00
|
|
|
return nil
|
|
|
|
}
|