mirror of
https://github.com/minio/minio.git
synced 2025-11-07 12:52:58 -05:00
Merge remote-tracking branch 'origin/master' into HEAD
Conflicts: pkg/drivers/donut/donut.go pkg/storage/donut/bucket.go pkg/storage/donut/donut.go pkg/storage/donut/donut_test.go pkg/storage/donut/donutdriver.go
This commit is contained in:
@@ -6,11 +6,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type bucketDriver struct {
|
||||
nodes []string
|
||||
type donutBucket struct {
|
||||
nodes []string
|
||||
objects map[string][]byte
|
||||
}
|
||||
|
||||
func (b bucketDriver) GetNodes() ([]string, error) {
|
||||
// GetNodes - get list of associated nodes for a given bucket
|
||||
func (b donutBucket) GetNodes() ([]string, error) {
|
||||
var nodes []string
|
||||
for _, node := range b.nodes {
|
||||
nodes = append(nodes, node)
|
||||
@@ -18,7 +20,7 @@ func (b bucketDriver) GetNodes() ([]string, error) {
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (b bucketDriver) AddNode(nodeID, bucketID string) error {
|
||||
func (b donutBucket) AddNode(nodeID, bucketID string) error {
|
||||
tokens := strings.Split(bucketID, ":")
|
||||
if len(tokens) != 3 {
|
||||
return errors.New("Bucket ID malformed: " + bucketID)
|
||||
@@ -1,51 +1,181 @@
|
||||
package donut
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// INTERFACES
|
||||
|
||||
// Donut interface
|
||||
type Donut interface {
|
||||
CreateBucket(bucket string) error
|
||||
GetObject(bucket, object string) (io.ReadCloser, error)
|
||||
GetObjectMetadata(bucket, object string) (map[string]string, error)
|
||||
GetObjectWriter(bucket, object string) (ObjectWriter, error)
|
||||
ListBuckets() ([]string, error)
|
||||
ListObjects(bucket string) ([]string, error)
|
||||
type donut struct {
|
||||
buckets map[string]Bucket
|
||||
nodes map[string]Node
|
||||
}
|
||||
|
||||
// Bucket interface
|
||||
type Bucket interface {
|
||||
GetNodes() ([]string, error)
|
||||
AddNode(nodeID, bucketID string) error
|
||||
// NewDonut - instantiate new donut driver
|
||||
func NewDonut(root string) (Donut, error) {
|
||||
nodes := make(map[string]Node)
|
||||
nodes["localhost"] = &localDirectoryNode{root: root}
|
||||
driver := &donut{
|
||||
buckets: make(map[string]Bucket),
|
||||
nodes: nodes,
|
||||
}
|
||||
for nodeID, node := range nodes {
|
||||
bucketIDs, err := node.GetBuckets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, bucketID := range bucketIDs {
|
||||
tokens := strings.Split(bucketID, ":")
|
||||
if _, ok := driver.buckets[tokens[0]]; ok {
|
||||
// found bucket, skip creating
|
||||
} else {
|
||||
bucket := donutBucket{
|
||||
nodes: make([]string, 16),
|
||||
}
|
||||
// TODO catch errors
|
||||
driver.buckets[tokens[0]] = bucket
|
||||
}
|
||||
if err = driver.buckets[tokens[0]].AddNode(nodeID, bucketID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
// Node interface
|
||||
type Node interface {
|
||||
CreateBucket(bucket string) error
|
||||
GetBuckets() ([]string, error)
|
||||
GetDonutMetadata(bucket, object string) (map[string]string, error)
|
||||
GetMetadata(bucket, object string) (map[string]string, error)
|
||||
GetReader(bucket, object string) (io.ReadCloser, error)
|
||||
GetWriter(bucket, object string) (Writer, error)
|
||||
ListObjects(bucket string) ([]string, error)
|
||||
// CreateBucket - create a new bucket
|
||||
func (d donut) CreateBucket(bucketName string) error {
|
||||
if _, ok := d.buckets[bucketName]; ok == false {
|
||||
bucketName = strings.TrimSpace(bucketName)
|
||||
if bucketName == "" {
|
||||
return errors.New("Cannot create bucket with no name")
|
||||
}
|
||||
// assign nodes
|
||||
// TODO assign other nodes
|
||||
nodes := make([]string, 16)
|
||||
for i := 0; i < 16; i++ {
|
||||
nodes[i] = "localhost"
|
||||
if node, ok := d.nodes["localhost"]; ok {
|
||||
node.CreateBucket(bucketName + ":0:" + strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
bucket := donutBucket{
|
||||
nodes: nodes,
|
||||
}
|
||||
d.buckets[bucketName] = bucket
|
||||
return nil
|
||||
}
|
||||
return errors.New("Bucket exists")
|
||||
}
|
||||
|
||||
// ObjectWriter interface
|
||||
type ObjectWriter interface {
|
||||
Close() error
|
||||
CloseWithError(error) error
|
||||
GetMetadata() (map[string]string, error)
|
||||
SetMetadata(map[string]string) error
|
||||
Write([]byte) (int, error)
|
||||
// ListBuckets - list all buckets
|
||||
func (d donut) ListBuckets() ([]string, error) {
|
||||
var buckets []string
|
||||
for bucket := range d.buckets {
|
||||
buckets = append(buckets, bucket)
|
||||
}
|
||||
sort.Strings(buckets)
|
||||
return buckets, nil
|
||||
}
|
||||
|
||||
// Writer interface
|
||||
type Writer interface {
|
||||
ObjectWriter
|
||||
|
||||
GetDonutMetadata() (map[string]string, error)
|
||||
SetDonutMetadata(map[string]string) error
|
||||
// GetObjectWriter - get a new writer interface for a new object
|
||||
func (d donut) GetObjectWriter(bucketName, objectName string) (ObjectWriter, error) {
|
||||
if bucket, ok := d.buckets[bucketName]; ok == true {
|
||||
writers := make([]Writer, 16)
|
||||
nodes, err := bucket.GetNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, nodeID := range nodes {
|
||||
if node, ok := d.nodes[nodeID]; ok == true {
|
||||
writer, err := node.GetWriter(bucketName+":0:"+strconv.Itoa(i), objectName)
|
||||
if err != nil {
|
||||
for _, writerToClose := range writers {
|
||||
if writerToClose != nil {
|
||||
writerToClose.CloseWithError(err)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
writers[i] = writer
|
||||
}
|
||||
}
|
||||
return newErasureWriter(writers), nil
|
||||
}
|
||||
return nil, errors.New("Bucket not found")
|
||||
}
|
||||
|
||||
// GetObjectReader - get a new reader interface for a new object
|
||||
func (d donut) GetObjectReader(bucketName, objectName string) (io.ReadCloser, error) {
|
||||
r, w := io.Pipe()
|
||||
if bucket, ok := d.buckets[bucketName]; ok == true {
|
||||
readers := make([]io.ReadCloser, 16)
|
||||
nodes, err := bucket.GetNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var metadata map[string]string
|
||||
for i, nodeID := range nodes {
|
||||
if node, ok := d.nodes[nodeID]; ok == true {
|
||||
bucketID := bucketName + ":0:" + strconv.Itoa(i)
|
||||
reader, err := node.GetReader(bucketID, objectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readers[i] = reader
|
||||
if metadata == nil {
|
||||
metadata, err = node.GetDonutMetadata(bucketID, objectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
go erasureReader(readers, metadata, w)
|
||||
return r, nil
|
||||
}
|
||||
return nil, errors.New("Bucket not found")
|
||||
}
|
||||
|
||||
// GetObjectMetadata returns metadata for a given object in a bucket
|
||||
func (d donut) GetObjectMetadata(bucketName, object string) (map[string]string, error) {
|
||||
if bucket, ok := d.buckets[bucketName]; ok {
|
||||
nodes, err := bucket.GetNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node, ok := d.nodes[nodes[0]]; ok {
|
||||
bucketID := bucketName + ":0:0"
|
||||
metadata, err := node.GetMetadata(bucketID, object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
donutMetadata, err := node.GetDonutMetadata(bucketID, object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata["sys.created"] = donutMetadata["created"]
|
||||
metadata["sys.md5"] = donutMetadata["md5"]
|
||||
metadata["sys.size"] = donutMetadata["size"]
|
||||
return metadata, nil
|
||||
}
|
||||
return nil, errors.New("Cannot connect to node: " + nodes[0])
|
||||
}
|
||||
return nil, errors.New("Bucket not found")
|
||||
}
|
||||
|
||||
// ListObjects - list all the available objects in a bucket
|
||||
func (d donut) ListObjects(bucketName string) ([]string, error) {
|
||||
if bucket, ok := d.buckets[bucketName]; ok {
|
||||
nodes, err := bucket.GetNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node, ok := d.nodes[nodes[0]]; ok {
|
||||
return node.ListObjects(bucketName + ":0:0")
|
||||
}
|
||||
}
|
||||
return nil, errors.New("Bucket not found")
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package donut
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"bytes"
|
||||
. "gopkg.in/check.v1"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) { TestingT(t) }
|
||||
@@ -21,7 +21,7 @@ func (s *MySuite) TestEmptyBucket(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// check buckets are empty
|
||||
@@ -34,7 +34,7 @@ func (s *MySuite) TestBucketWithoutNameFails(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
// fail to create new bucket without a name
|
||||
err = donut.CreateBucket("")
|
||||
@@ -48,7 +48,7 @@ func (s *MySuite) TestCreateBucketAndList(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
// create bucket
|
||||
err = donut.CreateBucket("foo")
|
||||
@@ -64,7 +64,7 @@ func (s *MySuite) TestCreateBucketWithSameNameFails(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
err = donut.CreateBucket("foo")
|
||||
c.Assert(err, IsNil)
|
||||
@@ -77,7 +77,7 @@ func (s *MySuite) TestCreateMultipleBucketsAndList(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
// add a second bucket
|
||||
err = donut.CreateBucket("foo")
|
||||
@@ -102,7 +102,7 @@ func (s *MySuite) TestNewObjectFailsWithoutBucket(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
writer, err := donut.GetObjectWriter("foo", "obj")
|
||||
@@ -114,7 +114,7 @@ func (s *MySuite) TestNewObjectFailsWithEmptyName(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
writer, err := donut.GetObjectWriter("foo", "")
|
||||
@@ -130,7 +130,7 @@ func (s *MySuite) TestNewObjectCanBeWritten(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = donut.CreateBucket("foo")
|
||||
@@ -161,7 +161,7 @@ func (s *MySuite) TestNewObjectCanBeWritten(c *C) {
|
||||
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
reader, err := donut.GetObject("foo", "obj")
|
||||
reader, err := donut.GetObjectReader("foo", "obj")
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
var actualData bytes.Buffer
|
||||
@@ -183,7 +183,7 @@ func (s *MySuite) TestMultipleNewObjects(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Assert(donut.CreateBucket("foo"), IsNil)
|
||||
@@ -199,7 +199,7 @@ func (s *MySuite) TestMultipleNewObjects(c *C) {
|
||||
|
||||
// c.Skip("not complete")
|
||||
|
||||
reader, err := donut.GetObject("foo", "obj1")
|
||||
reader, err := donut.GetObjectReader("foo", "obj1")
|
||||
c.Assert(err, IsNil)
|
||||
var readerBuffer1 bytes.Buffer
|
||||
_, err = io.Copy(&readerBuffer1, reader)
|
||||
@@ -207,7 +207,7 @@ func (s *MySuite) TestMultipleNewObjects(c *C) {
|
||||
// c.Skip("Not Implemented")
|
||||
c.Assert(readerBuffer1.Bytes(), DeepEquals, []byte("one"))
|
||||
|
||||
reader, err = donut.GetObject("foo", "obj2")
|
||||
reader, err = donut.GetObjectReader("foo", "obj2")
|
||||
c.Assert(err, IsNil)
|
||||
var readerBuffer2 bytes.Buffer
|
||||
_, err = io.Copy(&readerBuffer2, reader)
|
||||
@@ -224,7 +224,7 @@ func (s *MySuite) TestSysPrefixShouldFail(c *C) {
|
||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||
c.Assert(err, IsNil)
|
||||
defer os.RemoveAll(root)
|
||||
donut, err := NewDonutDriver(root)
|
||||
donut, err := NewDonut(root)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Assert(donut.CreateBucket("foo"), IsNil)
|
||||
@@ -1,176 +0,0 @@
|
||||
package donut
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type donutDriver struct {
|
||||
buckets map[string]Bucket
|
||||
nodes map[string]Node
|
||||
}
|
||||
|
||||
// NewDonutDriver - instantiate new donut driver
|
||||
func NewDonutDriver(root string) (Donut, error) {
|
||||
nodes := make(map[string]Node)
|
||||
nodes["localhost"] = &localDirectoryNode{root: root}
|
||||
driver := &donutDriver{
|
||||
buckets: make(map[string]Bucket),
|
||||
nodes: nodes,
|
||||
}
|
||||
for nodeID, node := range nodes {
|
||||
bucketIDs, err := node.GetBuckets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, bucketID := range bucketIDs {
|
||||
tokens := strings.Split(bucketID, ":")
|
||||
if _, ok := driver.buckets[tokens[0]]; ok {
|
||||
// found bucket, skip creating
|
||||
} else {
|
||||
bucket := bucketDriver{
|
||||
nodes: make([]string, 16),
|
||||
}
|
||||
// TODO catch errors
|
||||
driver.buckets[tokens[0]] = bucket
|
||||
}
|
||||
if err = driver.buckets[tokens[0]].AddNode(nodeID, bucketID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
func (driver donutDriver) CreateBucket(bucketName string) error {
|
||||
if _, ok := driver.buckets[bucketName]; ok == false {
|
||||
bucketName = strings.TrimSpace(bucketName)
|
||||
if bucketName == "" {
|
||||
return errors.New("Cannot create bucket with no name")
|
||||
}
|
||||
// assign nodes
|
||||
// TODO assign other nodes
|
||||
nodes := make([]string, 16)
|
||||
for i := 0; i < 16; i++ {
|
||||
nodes[i] = "localhost"
|
||||
if node, ok := driver.nodes["localhost"]; ok {
|
||||
node.CreateBucket(bucketName + ":0:" + strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
bucket := bucketDriver{
|
||||
nodes: nodes,
|
||||
}
|
||||
driver.buckets[bucketName] = bucket
|
||||
return nil
|
||||
}
|
||||
return errors.New("Bucket exists")
|
||||
}
|
||||
|
||||
func (driver donutDriver) ListBuckets() ([]string, error) {
|
||||
var buckets []string
|
||||
for bucket := range driver.buckets {
|
||||
buckets = append(buckets, bucket)
|
||||
}
|
||||
sort.Strings(buckets)
|
||||
return buckets, nil
|
||||
}
|
||||
|
||||
func (driver donutDriver) GetObjectWriter(bucketName, objectName string) (ObjectWriter, error) {
|
||||
if bucket, ok := driver.buckets[bucketName]; ok == true {
|
||||
writers := make([]Writer, 16)
|
||||
nodes, err := bucket.GetNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, nodeID := range nodes {
|
||||
if node, ok := driver.nodes[nodeID]; ok == true {
|
||||
writer, err := node.GetWriter(bucketName+":0:"+strconv.Itoa(i), objectName)
|
||||
if err != nil {
|
||||
for _, writerToClose := range writers {
|
||||
if writerToClose != nil {
|
||||
writerToClose.CloseWithError(err)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
writers[i] = writer
|
||||
}
|
||||
}
|
||||
return newErasureWriter(writers), nil
|
||||
}
|
||||
return nil, errors.New("Bucket not found")
|
||||
}
|
||||
|
||||
func (driver donutDriver) GetObject(bucketName, objectName string) (io.ReadCloser, error) {
|
||||
r, w := io.Pipe()
|
||||
if bucket, ok := driver.buckets[bucketName]; ok == true {
|
||||
readers := make([]io.ReadCloser, 16)
|
||||
nodes, err := bucket.GetNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var metadata map[string]string
|
||||
for i, nodeID := range nodes {
|
||||
if node, ok := driver.nodes[nodeID]; ok == true {
|
||||
bucketID := bucketName + ":0:" + strconv.Itoa(i)
|
||||
reader, err := node.GetReader(bucketID, objectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readers[i] = reader
|
||||
if metadata == nil {
|
||||
metadata, err = node.GetDonutMetadata(bucketID, objectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
go erasureReader(readers, metadata, w)
|
||||
return r, nil
|
||||
}
|
||||
return nil, errors.New("Bucket not found")
|
||||
}
|
||||
|
||||
// GetObjectMetadata returns metadata for a given object in a bucket
|
||||
func (driver donutDriver) GetObjectMetadata(bucketName, object string) (map[string]string, error) {
|
||||
if bucket, ok := driver.buckets[bucketName]; ok {
|
||||
nodes, err := bucket.GetNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node, ok := driver.nodes[nodes[0]]; ok {
|
||||
bucketID := bucketName + ":0:0"
|
||||
metadata, err := node.GetMetadata(bucketID, object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
donutMetadata, err := node.GetDonutMetadata(bucketID, object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata["sys.created"] = donutMetadata["created"]
|
||||
metadata["sys.md5"] = donutMetadata["md5"]
|
||||
metadata["sys.size"] = donutMetadata["size"]
|
||||
return metadata, nil
|
||||
}
|
||||
return nil, errors.New("Cannot connect to node: " + nodes[0])
|
||||
}
|
||||
return nil, errors.New("Bucket not found")
|
||||
}
|
||||
|
||||
func (driver donutDriver) ListObjects(bucketName string) ([]string, error) {
|
||||
if bucket, ok := driver.buckets[bucketName]; ok {
|
||||
nodes, err := bucket.GetNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node, ok := driver.nodes[nodes[0]]; ok {
|
||||
return node.ListObjects(bucketName + ":0:0")
|
||||
}
|
||||
}
|
||||
return nil, errors.New("Bucket not found")
|
||||
}
|
||||
@@ -2,29 +2,70 @@ package donut
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
"github.com/minio-io/minio/pkg/encoding/erasure"
|
||||
"github.com/minio-io/minio/pkg/utils/split"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// getErasureTechnique - convert technique string into Technique type
|
||||
func getErasureTechnique(technique string) (erasure.Technique, error) {
|
||||
switch true {
|
||||
case technique == "Cauchy":
|
||||
return erasure.Cauchy, nil
|
||||
case technique == "Vandermonde":
|
||||
return erasure.Cauchy, nil
|
||||
default:
|
||||
return erasure.None, errors.New("Invalid erasure technique")
|
||||
}
|
||||
}
|
||||
|
||||
// erasureReader - returns aligned streaming reads over a PipeWriter
|
||||
func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, writer *io.PipeWriter) {
|
||||
// TODO handle errors
|
||||
totalChunks, _ := strconv.Atoi(donutMetadata["chunkCount"])
|
||||
totalLeft, _ := strconv.Atoi(donutMetadata["size"])
|
||||
blockSize, _ := strconv.Atoi(donutMetadata["blockSize"])
|
||||
k, _ := strconv.Atoi(donutMetadata["erasureK"])
|
||||
m, _ := strconv.Atoi(donutMetadata["erasureM"])
|
||||
expectedMd5sum, _ := hex.DecodeString(donutMetadata["md5"])
|
||||
totalChunks, err := strconv.Atoi(donutMetadata["chunkCount"])
|
||||
if err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
totalLeft, err := strconv.Atoi(donutMetadata["size"])
|
||||
if err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
blockSize, err := strconv.Atoi(donutMetadata["blockSize"])
|
||||
if err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
k, err := strconv.Atoi(donutMetadata["erasureK"])
|
||||
if err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
m, err := strconv.Atoi(donutMetadata["erasureM"])
|
||||
if err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
expectedMd5sum, err := hex.DecodeString(donutMetadata["md5"])
|
||||
if err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
technique, err := getErasureTechnique(donutMetadata["erasureTechnique"])
|
||||
if err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
summer := md5.New()
|
||||
// TODO select technique properly
|
||||
params, _ := erasure.ParseEncoderParams(uint8(k), uint8(m), erasure.Cauchy)
|
||||
params, _ := erasure.ParseEncoderParams(uint8(k), uint8(m), technique)
|
||||
encoder := erasure.NewEncoder(params)
|
||||
for _, reader := range readers {
|
||||
defer reader.Close()
|
||||
@@ -34,13 +75,17 @@ func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, wri
|
||||
if blockSize < totalLeft {
|
||||
curBlockSize = blockSize
|
||||
}
|
||||
curChunkSize := erasure.GetEncodedChunkLen(curBlockSize, uint8(k))
|
||||
|
||||
curChunkSize := erasure.GetEncodedBlockLen(curBlockSize, uint8(k))
|
||||
|
||||
encodedBytes := make([][]byte, 16)
|
||||
for i, reader := range readers {
|
||||
var bytesBuffer bytes.Buffer
|
||||
// TODO watch for errors
|
||||
io.CopyN(&bytesBuffer, reader, int64(curChunkSize))
|
||||
_, err := io.CopyN(&bytesBuffer, reader, int64(curChunkSize))
|
||||
if err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
encodedBytes[i] = bytesBuffer.Bytes()
|
||||
}
|
||||
decodedData, err := encoder.Decode(encodedBytes, curBlockSize)
|
||||
@@ -49,7 +94,11 @@ func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, wri
|
||||
return
|
||||
}
|
||||
summer.Write(decodedData)
|
||||
io.Copy(writer, bytes.NewBuffer(decodedData))
|
||||
_, err = io.Copy(writer, bytes.NewBuffer(decodedData))
|
||||
if err != nil {
|
||||
writer.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
totalLeft = totalLeft - blockSize
|
||||
}
|
||||
actualMd5sum := summer.Sum(nil)
|
||||
@@ -58,6 +107,7 @@ func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, wri
|
||||
return
|
||||
}
|
||||
writer.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// erasure writer
|
||||
@@ -70,6 +120,7 @@ type erasureWriter struct {
|
||||
isClosed <-chan bool
|
||||
}
|
||||
|
||||
// newErasureWriter - get a new writer
|
||||
func newErasureWriter(writers []Writer) ObjectWriter {
|
||||
r, w := io.Pipe()
|
||||
isClosed := make(chan bool)
|
||||
|
||||
51
pkg/storage/donut/interfaces.go
Normal file
51
pkg/storage/donut/interfaces.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package donut
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Collection of Donut specification interfaces
|
||||
|
||||
// Donut interface
|
||||
type Donut interface {
|
||||
CreateBucket(bucket string) error
|
||||
GetObjectReader(bucket, object string) (io.ReadCloser, error)
|
||||
GetObjectWriter(bucket, object string) (ObjectWriter, error)
|
||||
GetObjectMetadata(bucket, object string) (map[string]string, error)
|
||||
ListBuckets() ([]string, error)
|
||||
ListObjects(bucket string) ([]string, error)
|
||||
}
|
||||
|
||||
// Bucket interface
|
||||
type Bucket interface {
|
||||
GetNodes() ([]string, error)
|
||||
AddNode(nodeID, bucketID string) error
|
||||
}
|
||||
|
||||
// Node interface
|
||||
type Node interface {
|
||||
CreateBucket(bucket string) error
|
||||
GetBuckets() ([]string, error)
|
||||
GetDonutMetadata(bucket, object string) (map[string]string, error)
|
||||
GetMetadata(bucket, object string) (map[string]string, error)
|
||||
GetReader(bucket, object string) (io.ReadCloser, error)
|
||||
GetWriter(bucket, object string) (Writer, error)
|
||||
ListObjects(bucket string) ([]string, error)
|
||||
}
|
||||
|
||||
// ObjectWriter interface
|
||||
type ObjectWriter interface {
|
||||
Close() error
|
||||
CloseWithError(error) error
|
||||
GetMetadata() (map[string]string, error)
|
||||
SetMetadata(map[string]string) error
|
||||
Write([]byte) (int, error)
|
||||
}
|
||||
|
||||
// Writer interface
|
||||
type Writer interface {
|
||||
ObjectWriter
|
||||
|
||||
GetDonutMetadata() (map[string]string, error)
|
||||
SetDonutMetadata(map[string]string) error
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func (d localDirectoryNode) GetWriter(bucket, object string) (Writer, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newDonutFileWriter(objectPath)
|
||||
return newDonutObjectWriter(objectPath)
|
||||
}
|
||||
|
||||
func (d localDirectoryNode) GetReader(bucket, object string) (io.ReadCloser, error) {
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"path"
|
||||
)
|
||||
|
||||
func newDonutFileWriter(objectDir string) (Writer, error) {
|
||||
func newDonutObjectWriter(objectDir string) (Writer, error) {
|
||||
dataFile, err := os.OpenFile(path.Join(objectDir, "data"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return donutFileWriter{
|
||||
return donutObjectWriter{
|
||||
root: objectDir,
|
||||
file: dataFile,
|
||||
metadata: make(map[string]string),
|
||||
@@ -20,7 +20,7 @@ func newDonutFileWriter(objectDir string) (Writer, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
type donutFileWriter struct {
|
||||
type donutObjectWriter struct {
|
||||
root string
|
||||
file *os.File
|
||||
metadata map[string]string
|
||||
@@ -28,11 +28,11 @@ type donutFileWriter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (d donutFileWriter) Write(data []byte) (int, error) {
|
||||
func (d donutObjectWriter) Write(data []byte) (int, error) {
|
||||
return d.file.Write(data)
|
||||
}
|
||||
|
||||
func (d donutFileWriter) Close() error {
|
||||
func (d donutObjectWriter) Close() error {
|
||||
if d.err != nil {
|
||||
return d.err
|
||||
}
|
||||
@@ -44,14 +44,14 @@ func (d donutFileWriter) Close() error {
|
||||
return d.file.Close()
|
||||
}
|
||||
|
||||
func (d donutFileWriter) CloseWithError(err error) error {
|
||||
func (d donutObjectWriter) CloseWithError(err error) error {
|
||||
if d.err != nil {
|
||||
d.err = err
|
||||
}
|
||||
return d.Close()
|
||||
}
|
||||
|
||||
func (d donutFileWriter) SetMetadata(metadata map[string]string) error {
|
||||
func (d donutObjectWriter) SetMetadata(metadata map[string]string) error {
|
||||
for k := range d.metadata {
|
||||
delete(d.metadata, k)
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func (d donutFileWriter) SetMetadata(metadata map[string]string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d donutFileWriter) GetMetadata() (map[string]string, error) {
|
||||
func (d donutObjectWriter) GetMetadata() (map[string]string, error) {
|
||||
metadata := make(map[string]string)
|
||||
for k, v := range d.metadata {
|
||||
metadata[k] = v
|
||||
@@ -69,7 +69,7 @@ func (d donutFileWriter) GetMetadata() (map[string]string, error) {
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (d donutFileWriter) SetDonutMetadata(metadata map[string]string) error {
|
||||
func (d donutObjectWriter) SetDonutMetadata(metadata map[string]string) error {
|
||||
for k := range d.donutMetadata {
|
||||
delete(d.donutMetadata, k)
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func (d donutFileWriter) SetDonutMetadata(metadata map[string]string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d donutFileWriter) GetDonutMetadata() (map[string]string, error) {
|
||||
func (d donutObjectWriter) GetDonutMetadata() (map[string]string, error) {
|
||||
donutMetadata := make(map[string]string)
|
||||
for k, v := range d.donutMetadata {
|
||||
donutMetadata[k] = v
|
||||
@@ -1,39 +0,0 @@
|
||||
package donut
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type objectWriter struct {
|
||||
metadata map[string]string
|
||||
}
|
||||
|
||||
func (obj objectWriter) Write(data []byte) (length int, err error) {
|
||||
return 11, nil
|
||||
}
|
||||
|
||||
func (obj objectWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj objectWriter) CloseWithError(err error) error {
|
||||
return errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
func (obj objectWriter) SetMetadata(metadata map[string]string) error {
|
||||
for k := range obj.metadata {
|
||||
delete(obj.metadata, k)
|
||||
}
|
||||
for k, v := range metadata {
|
||||
obj.metadata[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj objectWriter) GetMetadata() (map[string]string, error) {
|
||||
ret := make(map[string]string)
|
||||
for k, v := range obj.metadata {
|
||||
ret[k] = v
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
Reference in New Issue
Block a user