2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
|||||||
weed
|
go/weed/.goxc*
|
||||||
tags
|
tags
|
||||||
*.swp
|
*.swp
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ Introduction
|
|||||||
############
|
############
|
||||||
This file contains list of recent changes, important features, usage changes, data format changes, etc. Do read this if you upgrade.
|
This file contains list of recent changes, important features, usage changes, data format changes, etc. Do read this if you upgrade.
|
||||||
|
|
||||||
|
v0.68
|
||||||
|
#####
|
||||||
|
1. Filer supports storing file~file_id mapping to remote key-value storage Redis, Cassandra. So multiple filers are supported.
|
||||||
|
|
||||||
v0.67
|
v0.67
|
||||||
#####
|
#####
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func NewDirectoryManagerInMap(dirLogFile string) (dm *DirectoryManagerInMap, err
|
|||||||
//dm.Root do not use NewDirectoryEntryInMap, since dm.max will be changed
|
//dm.Root do not use NewDirectoryEntryInMap, since dm.max will be changed
|
||||||
dm.Root = &DirectoryEntryInMap{SubDirectories: make(map[string]*DirectoryEntryInMap)}
|
dm.Root = &DirectoryEntryInMap{SubDirectories: make(map[string]*DirectoryEntryInMap)}
|
||||||
if dm.logFile, err = os.OpenFile(dirLogFile, os.O_RDWR|os.O_CREATE, 0644); err != nil {
|
if dm.logFile, err = os.OpenFile(dirLogFile, os.O_RDWR|os.O_CREATE, 0644); err != nil {
|
||||||
return nil, fmt.Errorf("cannot write directory log file %s.idx: %s", dirLogFile, err.Error())
|
return nil, fmt.Errorf("cannot write directory log file %s.idx: %v", dirLogFile, err)
|
||||||
}
|
}
|
||||||
return dm, dm.load()
|
return dm, dm.load()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ func (lr *LookupResult) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
vc VidCache
|
vc VidCache // caching of volume locations, re-check if after 10 minutes
|
||||||
)
|
)
|
||||||
|
|
||||||
func Lookup(server string, vid string) (ret *LookupResult, err error) {
|
func Lookup(server string, vid string) (ret *LookupResult, err error) {
|
||||||
locations, cache_err := vc.Get(vid)
|
locations, cache_err := vc.Get(vid)
|
||||||
if cache_err != nil {
|
if cache_err != nil {
|
||||||
if ret, err = do_lookup(server, vid); err == nil {
|
if ret, err = do_lookup(server, vid); err == nil {
|
||||||
vc.Set(vid, ret.Locations, 1*time.Minute)
|
vc.Set(vid, ret.Locations, 10*time.Minute)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ret = &LookupResult{VolumeId: vid, Locations: locations}
|
ret = &LookupResult{VolumeId: vid, Locations: locations}
|
||||||
@@ -75,19 +75,44 @@ func LookupFileId(server string, fileId string) (fullUrl string, err error) {
|
|||||||
return "http://" + lookup.Locations[rand.Intn(len(lookup.Locations))].PublicUrl + "/" + fileId, nil
|
return "http://" + lookup.Locations[rand.Intn(len(lookup.Locations))].PublicUrl + "/" + fileId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupVolumeIds find volume locations by cache and actual lookup
|
||||||
func LookupVolumeIds(server string, vids []string) (map[string]LookupResult, error) {
|
func LookupVolumeIds(server string, vids []string) (map[string]LookupResult, error) {
|
||||||
values := make(url.Values)
|
ret := make(map[string]LookupResult)
|
||||||
|
var unknown_vids []string
|
||||||
|
|
||||||
|
//check vid cache first
|
||||||
for _, vid := range vids {
|
for _, vid := range vids {
|
||||||
|
locations, cache_err := vc.Get(vid)
|
||||||
|
if cache_err == nil {
|
||||||
|
ret[vid] = LookupResult{VolumeId: vid, Locations: locations}
|
||||||
|
} else {
|
||||||
|
unknown_vids = append(unknown_vids, vid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//return success if all volume ids are known
|
||||||
|
if len(unknown_vids) == 0 {
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//only query unknown_vids
|
||||||
|
values := make(url.Values)
|
||||||
|
for _, vid := range unknown_vids {
|
||||||
values.Add("volumeId", vid)
|
values.Add("volumeId", vid)
|
||||||
}
|
}
|
||||||
jsonBlob, err := util.Post("http://"+server+"/vol/lookup", values)
|
jsonBlob, err := util.Post("http://"+server+"/vol/lookup", values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ret := make(map[string]LookupResult)
|
|
||||||
err = json.Unmarshal(jsonBlob, &ret)
|
err = json.Unmarshal(jsonBlob, &ret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(err.Error() + " " + string(jsonBlob))
|
return nil, errors.New(err.Error() + " " + string(jsonBlob))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//set newly checked vids to cache
|
||||||
|
for _, vid := range unknown_vids {
|
||||||
|
locations := ret[vid].Locations
|
||||||
|
vc.Set(vid, locations, 10*time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,12 +214,12 @@ func DumpNeedleMapToCdb(cdbName string, nm *NeedleMap) error {
|
|||||||
func openTempCdb(fileName string) (cdb.AdderFunc, cdb.CloserFunc, error) {
|
func openTempCdb(fileName string) (cdb.AdderFunc, cdb.CloserFunc, error) {
|
||||||
fh, err := os.Create(fileName)
|
fh, err := os.Create(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("cannot create cdb file %s: %s", fileName, err.Error())
|
return nil, nil, fmt.Errorf("cannot create cdb file %s: %v", fileName, err)
|
||||||
}
|
}
|
||||||
adder, closer, err := cdb.MakeFactory(fh)
|
adder, closer, err := cdb.MakeFactory(fh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fh.Close()
|
fh.Close()
|
||||||
return nil, nil, fmt.Errorf("error creating factory: %s", err.Error())
|
return nil, nil, fmt.Errorf("error creating factory: %v", err)
|
||||||
}
|
}
|
||||||
return adder, func() error {
|
return adder, func() error {
|
||||||
if e := closer(); e != nil {
|
if e := closer(); e != nil {
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func (nm *NeedleMap) Put(key uint64, offset uint32, size uint32) (int, error) {
|
|||||||
nm.DeletionByteCounter = nm.DeletionByteCounter + uint64(oldSize)
|
nm.DeletionByteCounter = nm.DeletionByteCounter + uint64(oldSize)
|
||||||
}
|
}
|
||||||
if _, err := nm.indexFile.Seek(0, 2); err != nil {
|
if _, err := nm.indexFile.Seek(0, 2); err != nil {
|
||||||
return 0, fmt.Errorf("cannot go to the end of indexfile %s: %s", nm.indexFile.Name(), err.Error())
|
return 0, fmt.Errorf("cannot go to the end of indexfile %s: %v", nm.indexFile.Name(), err)
|
||||||
}
|
}
|
||||||
return nm.indexFile.Write(bytes)
|
return nm.indexFile.Write(bytes)
|
||||||
}
|
}
|
||||||
@@ -141,10 +141,10 @@ func (nm *NeedleMap) Delete(key uint64) error {
|
|||||||
util.Uint32toBytes(bytes[8:12], 0)
|
util.Uint32toBytes(bytes[8:12], 0)
|
||||||
util.Uint32toBytes(bytes[12:16], 0)
|
util.Uint32toBytes(bytes[12:16], 0)
|
||||||
if _, err := nm.indexFile.Seek(0, 2); err != nil {
|
if _, err := nm.indexFile.Seek(0, 2); err != nil {
|
||||||
return fmt.Errorf("cannot go to the end of indexfile %s: %s", nm.indexFile.Name(), err.Error())
|
return fmt.Errorf("cannot go to the end of indexfile %s: %v", nm.indexFile.Name(), err)
|
||||||
}
|
}
|
||||||
if _, err := nm.indexFile.Write(bytes); err != nil {
|
if _, err := nm.indexFile.Write(bytes); err != nil {
|
||||||
return fmt.Errorf("error writing to indexfile %s: %s", nm.indexFile.Name(), err.Error())
|
return fmt.Errorf("error writing to indexfile %s: %v", nm.indexFile.Name(), err)
|
||||||
}
|
}
|
||||||
nm.DeletionCounter++
|
nm.DeletionCounter++
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ func (n *Needle) Append(w io.Writer, version Version) (size uint32, err error) {
|
|||||||
defer func(s io.Seeker, off int64) {
|
defer func(s io.Seeker, off int64) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, e = s.Seek(off, 0); e != nil {
|
if _, e = s.Seek(off, 0); e != nil {
|
||||||
glog.V(0).Infof("Failed to seek %s back to %d with error: %s", w, off, e.Error())
|
glog.V(0).Infof("Failed to seek %s back to %d with error: %v", w, off, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(s, end)
|
}(s, end)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("Cnnot Read Current Volume Position: %s", e.Error())
|
err = fmt.Errorf("Cannot Read Current Volume Position: %v", e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func (mn *MasterNodes) findMaster() (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if mn.lastNode < 0 {
|
if mn.lastNode < 0 {
|
||||||
return "", errors.New("No master node avalable!")
|
return "", errors.New("No master node available!")
|
||||||
}
|
}
|
||||||
return mn.nodes[mn.lastNode], nil
|
return mn.nodes[mn.lastNode], nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ func (v *Volume) write(n *Needle) (size uint32, err error) {
|
|||||||
}
|
}
|
||||||
var offset int64
|
var offset int64
|
||||||
if offset, err = v.dataFile.Seek(0, 2); err != nil {
|
if offset, err = v.dataFile.Seek(0, 2); err != nil {
|
||||||
glog.V(0).Infof("faile to seek the end of file: %v", err)
|
glog.V(0).Infof("failed to seek the end of file: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,10 +287,10 @@ func ScanVolumeFile(dirname string, collection string, id VolumeId,
|
|||||||
visitNeedle func(n *Needle, offset int64) error) (err error) {
|
visitNeedle func(n *Needle, offset int64) error) (err error) {
|
||||||
var v *Volume
|
var v *Volume
|
||||||
if v, err = loadVolumeWithoutIndex(dirname, collection, id); err != nil {
|
if v, err = loadVolumeWithoutIndex(dirname, collection, id); err != nil {
|
||||||
return errors.New("Failed to load volume:" + err.Error())
|
return fmt.Errorf("Failed to load volume %d: %v", id, err)
|
||||||
}
|
}
|
||||||
if err = visitSuperBlock(v.SuperBlock); err != nil {
|
if err = visitSuperBlock(v.SuperBlock); err != nil {
|
||||||
return errors.New("Failed to read super block:" + err.Error())
|
return fmt.Errorf("Failed to read volume %d super block: %v", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
version := v.Version()
|
version := v.Version()
|
||||||
|
|||||||
@@ -39,5 +39,6 @@ func NewVolumeInfo(m *operation.VolumeInformationMessage) (vi VolumeInfo, err er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (vi VolumeInfo) String() string {
|
func (vi VolumeInfo) String() string {
|
||||||
return fmt.Sprintf("Id:%s, Size:%d, ReplicaPlacement:%s, Collection:%s, Version:%v, FileCount:%d, DeleteCount:%d, DeletedByteCount:%d, ReadOnly:%v", vi.Id, vi.Size, vi.ReplicaPlacement, vi.Collection, vi.Version, vi.FileCount, vi.DeleteCount, vi.DeletedByteCount, vi.ReadOnly)
|
return fmt.Sprintf("Id:%d, Size:%d, ReplicaPlacement:%s, Collection:%s, Version:%v, FileCount:%d, DeleteCount:%d, DeletedByteCount:%d, ReadOnly:%v",
|
||||||
|
vi.Id, vi.Size, vi.ReplicaPlacement, vi.Collection, vi.Version, vi.FileCount, vi.DeleteCount, vi.DeletedByteCount, vi.ReadOnly)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func (s *SuperBlock) Bytes() []byte {
|
|||||||
func (v *Volume) maybeWriteSuperBlock() error {
|
func (v *Volume) maybeWriteSuperBlock() error {
|
||||||
stat, e := v.dataFile.Stat()
|
stat, e := v.dataFile.Stat()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
glog.V(0).Infof("failed to stat datafile %s: %s", v.dataFile, e.Error())
|
glog.V(0).Infof("failed to stat datafile %s: %v", v.dataFile, e)
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
if stat.Size() == 0 {
|
if stat.Size() == 0 {
|
||||||
@@ -57,11 +57,11 @@ func (v *Volume) maybeWriteSuperBlock() error {
|
|||||||
}
|
}
|
||||||
func (v *Volume) readSuperBlock() (err error) {
|
func (v *Volume) readSuperBlock() (err error) {
|
||||||
if _, err = v.dataFile.Seek(0, 0); err != nil {
|
if _, err = v.dataFile.Seek(0, 0); err != nil {
|
||||||
return fmt.Errorf("cannot seek to the beginning of %s: %s", v.dataFile.Name(), err.Error())
|
return fmt.Errorf("cannot seek to the beginning of %s: %v", v.dataFile.Name(), err)
|
||||||
}
|
}
|
||||||
header := make([]byte, SuperBlockSize)
|
header := make([]byte, SuperBlockSize)
|
||||||
if _, e := v.dataFile.Read(header); e != nil {
|
if _, e := v.dataFile.Read(header); e != nil {
|
||||||
return fmt.Errorf("cannot read superblock: %s", e.Error())
|
return fmt.Errorf("cannot read volume %d super block: %v", v.Id, e)
|
||||||
}
|
}
|
||||||
v.SuperBlock, err = ParseSuperBlock(header)
|
v.SuperBlock, err = ParseSuperBlock(header)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func TestLoadConfiguration(t *testing.T) {
|
|||||||
|
|
||||||
fmt.Printf("%s\n", c)
|
fmt.Printf("%s\n", c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unmarshal error:%s", err.Error())
|
t.Fatalf("unmarshal error:%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Topo.DataCenters) <= 0 || c.Topo.DataCenters[0].Name != "dc1" {
|
if len(c.Topo.DataCenters) <= 0 || c.Topo.DataCenters[0].Name != "dc1" {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ func (t *Topology) HasWritableVolume(option *VolumeGrowOption) bool {
|
|||||||
func (t *Topology) PickForWrite(count int, option *VolumeGrowOption) (string, int, *DataNode, error) {
|
func (t *Topology) PickForWrite(count int, option *VolumeGrowOption) (string, int, *DataNode, error) {
|
||||||
vid, count, datanodes, err := t.GetVolumeLayout(option.Collection, option.ReplicaPlacement, option.Ttl).PickForWrite(count, option)
|
vid, count, datanodes, err := t.GetVolumeLayout(option.Collection, option.ReplicaPlacement, option.Ttl).PickForWrite(count, option)
|
||||||
if err != nil || datanodes.Length() == 0 {
|
if err != nil || datanodes.Length() == 0 {
|
||||||
return "", 0, nil, errors.New("No writable volumes avalable!")
|
return "", 0, nil, errors.New("No writable volumes available!")
|
||||||
}
|
}
|
||||||
fileId, count := t.Sequence.NextFileId(count)
|
fileId, count := t.Sequence.NextFileId(count)
|
||||||
return storage.NewFileId(*vid, fileId, rand.Uint32()).String(), count, datanodes.Head(), nil
|
return storage.NewFileId(*vid, fileId, rand.Uint32()).String(), count, datanodes.Head(), nil
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ func (vg *VolumeGrowth) grow(topo *Topology, vid storage.VolumeId, option *Volum
|
|||||||
glog.V(0).Infoln("Created Volume", vid, "on", server)
|
glog.V(0).Infoln("Created Volume", vid, "on", server)
|
||||||
} else {
|
} else {
|
||||||
glog.V(0).Infoln("Failed to assign", vid, "to", servers, "error", err)
|
glog.V(0).Infoln("Failed to assign", vid, "to", servers, "error", err)
|
||||||
return fmt.Errorf("Failed to assign %s: %s", vid.String(), err.Error())
|
return fmt.Errorf("Failed to assign %d: %v", vid, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VERSION = "0.67"
|
VERSION = "0.68"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func runFiler(cmd *Command, args []string) bool {
|
|||||||
*f.redis_server, *f.redis_database,
|
*f.redis_server, *f.redis_database,
|
||||||
)
|
)
|
||||||
if nfs_err != nil {
|
if nfs_err != nil {
|
||||||
glog.Fatalf(nfs_err.Error())
|
glog.Fatalf("Filer startup error: %v", nfs_err)
|
||||||
}
|
}
|
||||||
glog.V(0).Infoln("Start Seaweed Filer", util.VERSION, "at port", strconv.Itoa(*f.port))
|
glog.V(0).Infoln("Start Seaweed Filer", util.VERSION, "at port", strconv.Itoa(*f.port))
|
||||||
filerListener, e := util.NewListener(
|
filerListener, e := util.NewListener(
|
||||||
@@ -85,10 +85,10 @@ func runFiler(cmd *Command, args []string) bool {
|
|||||||
time.Duration(10)*time.Second,
|
time.Duration(10)*time.Second,
|
||||||
)
|
)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
glog.Fatalf(e.Error())
|
glog.Fatalf("Filer listener error: %v", e)
|
||||||
}
|
}
|
||||||
if e := http.Serve(filerListener, r); e != nil {
|
if e := http.Serve(filerListener, r); e != nil {
|
||||||
glog.Fatalf("Filer Fail to serve:%s", e.Error())
|
glog.Fatalf("Filer Fail to serve: %v", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func runMaster(cmd *Command, args []string) bool {
|
|||||||
|
|
||||||
listener, e := util.NewListener(listeningAddress, time.Duration(*mTimeout)*time.Second)
|
listener, e := util.NewListener(listeningAddress, time.Duration(*mTimeout)*time.Second)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
glog.Fatalf(e.Error())
|
glog.Fatalf("Master startup error: %v", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -93,7 +93,7 @@ func runMaster(cmd *Command, args []string) bool {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if e := http.Serve(listener, r); e != nil {
|
if e := http.Serve(listener, r); e != nil {
|
||||||
glog.Fatalf("Fail to serve:%s", e.Error())
|
glog.Fatalf("Fail to serve: %v", e)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ func init() {
|
|||||||
filerOptions.dir = cmdServer.Flag.String("filer.dir", "", "directory to store meta data, default to a 'filer' sub directory of what -mdir is specified")
|
filerOptions.dir = cmdServer.Flag.String("filer.dir", "", "directory to store meta data, default to a 'filer' sub directory of what -mdir is specified")
|
||||||
filerOptions.defaultReplicaPlacement = cmdServer.Flag.String("filer.defaultReplicaPlacement", "", "Default replication type if not specified during runtime.")
|
filerOptions.defaultReplicaPlacement = cmdServer.Flag.String("filer.defaultReplicaPlacement", "", "Default replication type if not specified during runtime.")
|
||||||
filerOptions.redirectOnRead = cmdServer.Flag.Bool("filer.redirectOnRead", false, "whether proxy or redirect to volume server during file GET request")
|
filerOptions.redirectOnRead = cmdServer.Flag.Bool("filer.redirectOnRead", false, "whether proxy or redirect to volume server during file GET request")
|
||||||
filerOptions.cassandra_server = cmdFiler.Flag.String("filer.cassandra.server", "", "host[:port] of the cassandra server")
|
filerOptions.cassandra_server = cmdServer.Flag.String("filer.cassandra.server", "", "host[:port] of the cassandra server")
|
||||||
filerOptions.cassandra_keyspace = cmdFiler.Flag.String("filer.cassandra.keyspace", "seaweed", "keyspace of the cassandra server")
|
filerOptions.cassandra_keyspace = cmdServer.Flag.String("filer.cassandra.keyspace", "seaweed", "keyspace of the cassandra server")
|
||||||
filerOptions.redis_server = cmdServer.Flag.String("filer.redis.server", "", "host:port of the redis server, e.g., 127.0.0.1:6379")
|
filerOptions.redis_server = cmdServer.Flag.String("filer.redis.server", "", "host:port of the redis server, e.g., 127.0.0.1:6379")
|
||||||
filerOptions.redis_database = cmdFiler.Flag.Int("filer.redis.database", 0, "the database on the redis server")
|
filerOptions.redis_database = cmdServer.Flag.Int("filer.redis.database", 0, "the database on the redis server")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ func runServer(cmd *Command, args []string) bool {
|
|||||||
"", 0,
|
"", 0,
|
||||||
)
|
)
|
||||||
if nfs_err != nil {
|
if nfs_err != nil {
|
||||||
glog.Fatalf(nfs_err.Error())
|
glog.Fatalf("Filer startup error: %v", nfs_err)
|
||||||
}
|
}
|
||||||
glog.V(0).Infoln("Start Seaweed Filer", util.VERSION, "at port", strconv.Itoa(*filerOptions.port))
|
glog.V(0).Infoln("Start Seaweed Filer", util.VERSION, "at port", strconv.Itoa(*filerOptions.port))
|
||||||
filerListener, e := util.NewListener(
|
filerListener, e := util.NewListener(
|
||||||
@@ -175,10 +175,11 @@ func runServer(cmd *Command, args []string) bool {
|
|||||||
time.Duration(10)*time.Second,
|
time.Duration(10)*time.Second,
|
||||||
)
|
)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
|
glog.Fatalf("Filer listener error: %v", e)
|
||||||
glog.Fatalf(e.Error())
|
glog.Fatalf(e.Error())
|
||||||
}
|
}
|
||||||
if e := http.Serve(filerListener, r); e != nil {
|
if e := http.Serve(filerListener, r); e != nil {
|
||||||
glog.Fatalf("Filer Fail to serve:%s", e.Error())
|
glog.Fatalf("Filer Fail to serve: %v", e)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -199,7 +200,7 @@ func runServer(cmd *Command, args []string) bool {
|
|||||||
glog.V(0).Infoln("Start Seaweed Master", util.VERSION, "at", *serverIp+":"+strconv.Itoa(*masterPort))
|
glog.V(0).Infoln("Start Seaweed Master", util.VERSION, "at", *serverIp+":"+strconv.Itoa(*masterPort))
|
||||||
masterListener, e := util.NewListener(*serverBindIp+":"+strconv.Itoa(*masterPort), time.Duration(*serverTimeout)*time.Second)
|
masterListener, e := util.NewListener(*serverBindIp+":"+strconv.Itoa(*masterPort), time.Duration(*serverTimeout)*time.Second)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
glog.Fatalf(e.Error())
|
glog.Fatalf("Master startup error: %v", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -224,7 +225,7 @@ func runServer(cmd *Command, args []string) bool {
|
|||||||
volumeWait.Wait()
|
volumeWait.Wait()
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
r := http.NewServeMux()
|
r := http.NewServeMux()
|
||||||
volumeServer := weed_server.NewVolumeServer(r, *serverIp, *volumePort, *serverPublicIp, folders, maxCounts,
|
volumeServer := weed_server.NewVolumeServer(r, r, *serverIp, *volumePort, *serverPublicIp, folders, maxCounts,
|
||||||
*serverIp+":"+strconv.Itoa(*masterPort), *volumePulse, *serverDataCenter, *serverRack,
|
*serverIp+":"+strconv.Itoa(*masterPort), *volumePulse, *serverDataCenter, *serverRack,
|
||||||
serverWhiteList, *volumeFixJpgOrientation,
|
serverWhiteList, *volumeFixJpgOrientation,
|
||||||
)
|
)
|
||||||
@@ -235,7 +236,7 @@ func runServer(cmd *Command, args []string) bool {
|
|||||||
time.Duration(*serverTimeout)*time.Second,
|
time.Duration(*serverTimeout)*time.Second,
|
||||||
)
|
)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
glog.Fatalf(e.Error())
|
glog.Fatalf("Volume server listener error: %v", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
OnInterrupt(func() {
|
OnInterrupt(func() {
|
||||||
@@ -244,7 +245,7 @@ func runServer(cmd *Command, args []string) bool {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if e := http.Serve(volumeListener, r); e != nil {
|
if e := http.Serve(volumeListener, r); e != nil {
|
||||||
glog.Fatalf("Fail to serve:%s", e.Error())
|
glog.Fatalf("Volume server fail to serve:%v", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -13,8 +13,42 @@ import (
|
|||||||
"github.com/chrislusf/weed-fs/go/weed/weed_server"
|
"github.com/chrislusf/weed-fs/go/weed/weed_server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
v VolumeServerOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
type VolumeServerOptions struct {
|
||||||
|
port *int
|
||||||
|
adminPort *int
|
||||||
|
folders []string
|
||||||
|
folderMaxLimits []int
|
||||||
|
ip *string
|
||||||
|
publicIp *string
|
||||||
|
bindIp *string
|
||||||
|
master *string
|
||||||
|
pulseSeconds *int
|
||||||
|
idleConnectionTimeout *int
|
||||||
|
maxCpu *int
|
||||||
|
dataCenter *string
|
||||||
|
rack *string
|
||||||
|
whiteList []string
|
||||||
|
fixJpgOrientation *bool
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmdVolume.Run = runVolume // break init cycle
|
cmdVolume.Run = runVolume // break init cycle
|
||||||
|
v.port = cmdVolume.Flag.Int("port", 8080, "http listen port")
|
||||||
|
v.adminPort = cmdVolume.Flag.Int("port.admin", 8443, "https admin port, active when SSL certs are specified. Not ready yet.")
|
||||||
|
v.ip = cmdVolume.Flag.String("ip", "", "ip or server name")
|
||||||
|
v.publicIp = cmdVolume.Flag.String("publicIp", "", "Publicly accessible <ip|server_name>")
|
||||||
|
v.bindIp = cmdVolume.Flag.String("ip.bind", "0.0.0.0", "ip address to bind to")
|
||||||
|
v.master = cmdVolume.Flag.String("mserver", "localhost:9333", "master server location")
|
||||||
|
v.pulseSeconds = cmdVolume.Flag.Int("pulseSeconds", 5, "number of seconds between heartbeats, must be smaller than or equal to the master's setting")
|
||||||
|
v.idleConnectionTimeout = cmdVolume.Flag.Int("idleTimeout", 10, "connection idle seconds")
|
||||||
|
v.maxCpu = cmdVolume.Flag.Int("maxCpu", 0, "maximum number of CPUs. 0 means all available CPUs")
|
||||||
|
v.dataCenter = cmdVolume.Flag.String("dataCenter", "", "current volume server's data center name")
|
||||||
|
v.rack = cmdVolume.Flag.String("rack", "", "current volume server's rack name")
|
||||||
|
v.fixJpgOrientation = cmdVolume.Flag.Bool("images.fix.orientation", true, "Adjust jpg orientation when uploading.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdVolume = &Command{
|
var cmdVolume = &Command{
|
||||||
@@ -26,76 +60,66 @@ var cmdVolume = &Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
vport = cmdVolume.Flag.Int("port", 8080, "http listen port")
|
|
||||||
volumeSecurePort = cmdVolume.Flag.Int("port.secure", 8443, "https listen port, active when SSL certs are specified. Not ready yet.")
|
|
||||||
volumeFolders = cmdVolume.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...")
|
volumeFolders = cmdVolume.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...")
|
||||||
maxVolumeCounts = cmdVolume.Flag.String("max", "7", "maximum numbers of volumes, count[,count]...")
|
maxVolumeCounts = cmdVolume.Flag.String("max", "7", "maximum numbers of volumes, count[,count]...")
|
||||||
ip = cmdVolume.Flag.String("ip", "", "ip or server name")
|
|
||||||
publicIp = cmdVolume.Flag.String("publicIp", "", "Publicly accessible <ip|server_name>")
|
|
||||||
volumeBindIp = cmdVolume.Flag.String("ip.bind", "0.0.0.0", "ip address to bind to")
|
|
||||||
masterNode = cmdVolume.Flag.String("mserver", "localhost:9333", "master server location")
|
|
||||||
vpulse = cmdVolume.Flag.Int("pulseSeconds", 5, "number of seconds between heartbeats, must be smaller than or equal to the master's setting")
|
|
||||||
vTimeout = cmdVolume.Flag.Int("idleTimeout", 10, "connection idle seconds")
|
|
||||||
vMaxCpu = cmdVolume.Flag.Int("maxCpu", 0, "maximum number of CPUs. 0 means all available CPUs")
|
|
||||||
dataCenter = cmdVolume.Flag.String("dataCenter", "", "current volume server's data center name")
|
|
||||||
rack = cmdVolume.Flag.String("rack", "", "current volume server's rack name")
|
|
||||||
volumeWhiteListOption = cmdVolume.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.")
|
volumeWhiteListOption = cmdVolume.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.")
|
||||||
fixJpgOrientation = cmdVolume.Flag.Bool("images.fix.orientation", true, "Adjust jpg orientation when uploading.")
|
|
||||||
|
|
||||||
volumeWhiteList []string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func runVolume(cmd *Command, args []string) bool {
|
func runVolume(cmd *Command, args []string) bool {
|
||||||
if *vMaxCpu < 1 {
|
if *v.maxCpu < 1 {
|
||||||
*vMaxCpu = runtime.NumCPU()
|
*v.maxCpu = runtime.NumCPU()
|
||||||
}
|
}
|
||||||
runtime.GOMAXPROCS(*vMaxCpu)
|
runtime.GOMAXPROCS(*v.maxCpu)
|
||||||
folders := strings.Split(*volumeFolders, ",")
|
|
||||||
|
//Set multiple folders and each folder's max volume count limit'
|
||||||
|
v.folders = strings.Split(*volumeFolders, ",")
|
||||||
maxCountStrings := strings.Split(*maxVolumeCounts, ",")
|
maxCountStrings := strings.Split(*maxVolumeCounts, ",")
|
||||||
maxCounts := make([]int, 0)
|
|
||||||
for _, maxString := range maxCountStrings {
|
for _, maxString := range maxCountStrings {
|
||||||
if max, e := strconv.Atoi(maxString); e == nil {
|
if max, e := strconv.Atoi(maxString); e == nil {
|
||||||
maxCounts = append(maxCounts, max)
|
v.folderMaxLimits = append(v.folderMaxLimits, max)
|
||||||
} else {
|
} else {
|
||||||
glog.Fatalf("The max specified in -max not a valid number %s", maxString)
|
glog.Fatalf("The max specified in -max not a valid number %s", maxString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(folders) != len(maxCounts) {
|
if len(v.folders) != len(v.folderMaxLimits) {
|
||||||
glog.Fatalf("%d directories by -dir, but only %d max is set by -max", len(folders), len(maxCounts))
|
glog.Fatalf("%d directories by -dir, but only %d max is set by -max", len(v.folders), len(v.folderMaxLimits))
|
||||||
}
|
}
|
||||||
for _, folder := range folders {
|
for _, folder := range v.folders {
|
||||||
if err := util.TestFolderWritable(folder); err != nil {
|
if err := util.TestFolderWritable(folder); err != nil {
|
||||||
glog.Fatalf("Check Data Folder(-dir) Writable %s : %s", folder, err)
|
glog.Fatalf("Check Data Folder(-dir) Writable %s : %s", folder, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *publicIp == "" {
|
//security related white list configuration
|
||||||
if *ip == "" {
|
|
||||||
*ip = "127.0.0.1"
|
|
||||||
*publicIp = "localhost"
|
|
||||||
} else {
|
|
||||||
*publicIp = *ip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if *volumeWhiteListOption != "" {
|
if *volumeWhiteListOption != "" {
|
||||||
volumeWhiteList = strings.Split(*volumeWhiteListOption, ",")
|
v.whiteList = strings.Split(*volumeWhiteListOption, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
//derive default public ip address
|
||||||
|
if *v.publicIp == "" {
|
||||||
|
if *v.ip == "" {
|
||||||
|
*v.ip = "127.0.0.1"
|
||||||
|
*v.publicIp = "localhost"
|
||||||
|
} else {
|
||||||
|
*v.publicIp = *v.ip
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r := http.NewServeMux()
|
r := http.NewServeMux()
|
||||||
|
|
||||||
volumeServer := weed_server.NewVolumeServer(r, *ip, *vport, *publicIp, folders, maxCounts,
|
volumeServer := weed_server.NewVolumeServer(r, r, *v.ip, *v.port, *v.publicIp, v.folders, v.folderMaxLimits,
|
||||||
*masterNode, *vpulse, *dataCenter, *rack,
|
*v.master, *v.pulseSeconds, *v.dataCenter, *v.rack,
|
||||||
volumeWhiteList,
|
v.whiteList,
|
||||||
*fixJpgOrientation,
|
*v.fixJpgOrientation,
|
||||||
)
|
)
|
||||||
|
|
||||||
listeningAddress := *volumeBindIp + ":" + strconv.Itoa(*vport)
|
listeningAddress := *v.bindIp + ":" + strconv.Itoa(*v.port)
|
||||||
|
|
||||||
glog.V(0).Infoln("Start Seaweed volume server", util.VERSION, "at", listeningAddress)
|
glog.V(0).Infoln("Start Seaweed volume server", util.VERSION, "at", listeningAddress)
|
||||||
|
|
||||||
listener, e := util.NewListener(listeningAddress, time.Duration(*vTimeout)*time.Second)
|
listener, e := util.NewListener(listeningAddress, time.Duration(*v.idleConnectionTimeout)*time.Second)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
glog.Fatalf(e.Error())
|
glog.Fatalf("Volume server listener error:%v", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
OnInterrupt(func() {
|
OnInterrupt(func() {
|
||||||
@@ -103,7 +127,7 @@ func runVolume(cmd *Command, args []string) bool {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if e := http.Serve(listener, r); e != nil {
|
if e := http.Serve(listener, r); e != nil {
|
||||||
glog.Fatalf("Fail to serve:%s", e.Error())
|
glog.Fatalf("Volume server fail to serve: %v", e)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func writeJson(w http.ResponseWriter, r *http.Request, httpStatus int, obj inter
|
|||||||
// wrapper for writeJson - just logs errors
|
// wrapper for writeJson - just logs errors
|
||||||
func writeJsonQuiet(w http.ResponseWriter, r *http.Request, httpStatus int, obj interface{}) {
|
func writeJsonQuiet(w http.ResponseWriter, r *http.Request, httpStatus int, obj interface{}) {
|
||||||
if err := writeJson(w, r, httpStatus, obj); err != nil {
|
if err := writeJson(w, r, httpStatus, obj); err != nil {
|
||||||
glog.V(0).Infof("error writing JSON %s: %s", obj, err.Error())
|
glog.V(0).Infof("error writing JSON %s: %v", obj, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func writeJsonError(w http.ResponseWriter, r *http.Request, httpStatus int, err error) {
|
func writeJsonError(w http.ResponseWriter, r *http.Request, httpStatus int, err error) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func NewFilerServer(r *http.ServeMux, port int, master string, dir string, colle
|
|||||||
fs.filer = flat_namespace.NewFlatNamesapceFiler(master, redis_store)
|
fs.filer = flat_namespace.NewFlatNamesapceFiler(master, redis_store)
|
||||||
} else {
|
} else {
|
||||||
if fs.filer, err = embedded_filer.NewFilerEmbedded(master, dir); err != nil {
|
if fs.filer, err = embedded_filer.NewFilerEmbedded(master, dir); err != nil {
|
||||||
glog.Fatalf("Can not start filer in dir %s : %v", err)
|
glog.Fatalf("Can not start filer in dir %s : %v", dir, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func (ms *MasterServer) redirectHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
if machines != nil && len(machines) > 0 {
|
if machines != nil && len(machines) > 0 {
|
||||||
http.Redirect(w, r, "http://"+machines[0].PublicUrl+r.URL.Path, http.StatusMovedPermanently)
|
http.Redirect(w, r, "http://"+machines[0].PublicUrl+r.URL.Path, http.StatusMovedPermanently)
|
||||||
} else {
|
} else {
|
||||||
writeJsonError(w, r, http.StatusNotFound, fmt.Errorf("volume id %s not found.", volumeId))
|
writeJsonError(w, r, http.StatusNotFound, fmt.Errorf("volume id %d not found.", volumeId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type VolumeServer struct {
|
|||||||
FixJpgOrientation bool
|
FixJpgOrientation bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVolumeServer(r *http.ServeMux, ip string, port int, publicIp string, folders []string, maxCounts []int,
|
func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string, port int, publicIp string, folders []string, maxCounts []int,
|
||||||
masterNode string, pulseSeconds int,
|
masterNode string, pulseSeconds int,
|
||||||
dataCenter string, rack string,
|
dataCenter string, rack string,
|
||||||
whiteList []string,
|
whiteList []string,
|
||||||
@@ -39,18 +39,18 @@ func NewVolumeServer(r *http.ServeMux, ip string, port int, publicIp string, fol
|
|||||||
|
|
||||||
vs.guard = security.NewGuard(whiteList, "")
|
vs.guard = security.NewGuard(whiteList, "")
|
||||||
|
|
||||||
r.HandleFunc("/status", vs.guard.Secure(vs.statusHandler))
|
adminMux.HandleFunc("/status", vs.guard.Secure(vs.statusHandler))
|
||||||
r.HandleFunc("/admin/assign_volume", vs.guard.Secure(vs.assignVolumeHandler))
|
adminMux.HandleFunc("/admin/assign_volume", vs.guard.Secure(vs.assignVolumeHandler))
|
||||||
r.HandleFunc("/admin/vacuum_volume_check", vs.guard.Secure(vs.vacuumVolumeCheckHandler))
|
adminMux.HandleFunc("/admin/vacuum_volume_check", vs.guard.Secure(vs.vacuumVolumeCheckHandler))
|
||||||
r.HandleFunc("/admin/vacuum_volume_compact", vs.guard.Secure(vs.vacuumVolumeCompactHandler))
|
adminMux.HandleFunc("/admin/vacuum_volume_compact", vs.guard.Secure(vs.vacuumVolumeCompactHandler))
|
||||||
r.HandleFunc("/admin/vacuum_volume_commit", vs.guard.Secure(vs.vacuumVolumeCommitHandler))
|
adminMux.HandleFunc("/admin/vacuum_volume_commit", vs.guard.Secure(vs.vacuumVolumeCommitHandler))
|
||||||
r.HandleFunc("/admin/freeze_volume", vs.guard.Secure(vs.freezeVolumeHandler))
|
adminMux.HandleFunc("/admin/freeze_volume", vs.guard.Secure(vs.freezeVolumeHandler))
|
||||||
r.HandleFunc("/admin/delete_collection", vs.guard.Secure(vs.deleteCollectionHandler))
|
adminMux.HandleFunc("/admin/delete_collection", vs.guard.Secure(vs.deleteCollectionHandler))
|
||||||
r.HandleFunc("/stats/counter", vs.guard.Secure(statsCounterHandler))
|
adminMux.HandleFunc("/stats/counter", vs.guard.Secure(statsCounterHandler))
|
||||||
r.HandleFunc("/stats/memory", vs.guard.Secure(statsMemoryHandler))
|
adminMux.HandleFunc("/stats/memory", vs.guard.Secure(statsMemoryHandler))
|
||||||
r.HandleFunc("/stats/disk", vs.guard.Secure(vs.statsDiskHandler))
|
adminMux.HandleFunc("/stats/disk", vs.guard.Secure(vs.statsDiskHandler))
|
||||||
r.HandleFunc("/delete", vs.guard.Secure(vs.batchDeleteHandler))
|
publicMux.HandleFunc("/delete", vs.guard.Secure(vs.batchDeleteHandler))
|
||||||
r.HandleFunc("/", vs.storeHandler)
|
publicMux.HandleFunc("/", vs.storeHandler)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
connected := true
|
connected := true
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func (w *countingWriter) Write(p []byte) (n int, err error) {
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// rangesMIMESize returns the nunber of bytes it takes to encode the
|
// rangesMIMESize returns the number of bytes it takes to encode the
|
||||||
// provided ranges as a multipart response.
|
// provided ranges as a multipart response.
|
||||||
func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
|
func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
|
||||||
var w countingWriter
|
var w countingWriter
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Design for Seaweed-FS security
|
|||||||
Design Objectives
|
Design Objectives
|
||||||
Security can mean many different things. The original vision is that: if you have one machine lying around
|
Security can mean many different things. The original vision is that: if you have one machine lying around
|
||||||
somewhere with some disk space, it should be able to join your file system to contribute some disk space and
|
somewhere with some disk space, it should be able to join your file system to contribute some disk space and
|
||||||
network bandwidth.
|
network bandwidth, securely!
|
||||||
|
|
||||||
To achieve this purpose, the security should be able to:
|
To achieve this purpose, the security should be able to:
|
||||||
1. Secure the inter-server communication. Only real cluster servers can join and communicate.
|
1. Secure the inter-server communication. Only real cluster servers can join and communicate.
|
||||||
@@ -14,7 +14,7 @@ Non Objective
|
|||||||
User specific access control.
|
User specific access control.
|
||||||
|
|
||||||
Design Architect
|
Design Architect
|
||||||
master, and volume servers all talk securely via 2-way SSL for admin.
|
master, and volume servers all talk securely via 2-way SSL for admin operations.
|
||||||
upon joining, master gives its secret key to volume servers.
|
upon joining, master gives its secret key to volume servers.
|
||||||
filer or clients talk to master to get secret key, and use the key to generate JWT to write on volume server.
|
filer or clients talk to master to get secret key, and use the key to generate JWT to write on volume server.
|
||||||
A side benefit:
|
A side benefit:
|
||||||
@@ -34,3 +34,18 @@ file uploading:
|
|||||||
when filer/clients wants to upload, master generate a JWT
|
when filer/clients wants to upload, master generate a JWT
|
||||||
filer~>volume(public port)
|
filer~>volume(public port)
|
||||||
master~>volume(public port)
|
master~>volume(public port)
|
||||||
|
|
||||||
|
Currently, volume server has 2 ip addresses: ip and publicUrl.
|
||||||
|
The ip is for admin purpose, and master talk to volume server this way.
|
||||||
|
The publicUrl is for clients to access the server, via http GET/POST/DELETE etc.
|
||||||
|
The write operations are secured by JWT.
|
||||||
|
clients talk to master also via https? possible. Decide on this later.
|
||||||
|
|
||||||
|
Dev plan:
|
||||||
|
1. volume server separate admin from public GET/POST/DELETE handlers
|
||||||
|
The step 1 may be good enough for most use cases.
|
||||||
|
|
||||||
|
If 2-way ssl are still needed
|
||||||
|
2. volume server add ssl support
|
||||||
|
3. https connections to operate on volume servers
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user