diff --git a/controller-main.go b/controller-main.go index bd458bacc..98794b6f6 100644 --- a/controller-main.go +++ b/controller-main.go @@ -16,7 +16,18 @@ package main -import "github.com/minio/cli" +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "os" + "strings" + + "github.com/minio/cli" + "github.com/minio/minio/pkg/minhttp" + "github.com/minio/minio/pkg/probe" +) var controllerCmd = cli.Command{ Name: "controller", @@ -35,11 +46,92 @@ EXAMPLES: `, } +// configureControllerRPC instance +func configureControllerRPC(conf minioConfig, rpcHandler http.Handler) (*http.Server, *probe.Error) { + // Minio server config + rpcServer := &http.Server{ + Addr: conf.ControllerAddress, + Handler: rpcHandler, + MaxHeaderBytes: 1 << 20, + } + if conf.TLS { + var err error + rpcServer.TLSConfig = &tls.Config{} + rpcServer.TLSConfig.Certificates = make([]tls.Certificate, 1) + rpcServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(conf.CertFile, conf.KeyFile) + if err != nil { + return nil, probe.NewError(err) + } + } + + host, port, err := net.SplitHostPort(conf.ControllerAddress) + if err != nil { + return nil, probe.NewError(err) + } + + var hosts []string + switch { + case host != "": + hosts = append(hosts, host) + default: + addrs, err := net.InterfaceAddrs() + if err != nil { + return nil, probe.NewError(err) + } + for _, addr := range addrs { + if addr.Network() == "ip+net" { + host := strings.Split(addr.String(), "/")[0] + if ip := net.ParseIP(host); ip.To4() != nil { + hosts = append(hosts, host) + } + } + } + } + + for _, host := range hosts { + if conf.TLS { + fmt.Printf("Starting minio controller on: https://%s:%s, PID: %d\n", host, port, os.Getpid()) + } else { + fmt.Printf("Starting minio controller on: http://%s:%s, PID: %d\n", host, port, os.Getpid()) + } + } + return rpcServer, nil +} + +// startController starts a minio controller +func startController(conf minioConfig) *probe.Error { + rpcServer, err := configureControllerRPC(conf, getControllerRPCHandler()) + if err != nil { + return err.Trace() + } + // Setting rate limit to 'zero' no ratelimiting implemented + if err := minhttp.ListenAndServeLimited(0, rpcServer); err != nil { + return err.Trace() + } + return nil +} + +func getControllerConfig(c *cli.Context) minioConfig { + certFile := c.GlobalString("cert") + keyFile := c.GlobalString("key") + if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") { + Fatalln("Both certificate and key are required to enable https.") + } + tls := (certFile != "" && keyFile != "") + return minioConfig{ + ControllerAddress: c.GlobalString("address-controller"), + TLS: tls, + CertFile: certFile, + KeyFile: keyFile, + RateLimit: c.GlobalInt("ratelimit"), + } +} + func controllerMain(c *cli.Context) { if c.Args().Present() { cli.ShowCommandHelpAndExit(c, "controller", 1) } - err := StartController() + err := startController(getControllerConfig(c)) errorIf(err.Trace(), "Failed to start minio controller.", nil) } diff --git a/controller-router.go b/controller-router.go index d891ec27b..a408b3480 100644 --- a/controller-router.go +++ b/controller-router.go @@ -24,14 +24,13 @@ import ( "github.com/gorilla/rpc/v2/json" ) -// getRPCHandler rpc handler -func getRPCCtrlHandler() http.Handler { +// getControllerRPCHandler rpc handler for controller +func getControllerRPCHandler() http.Handler { s := jsonrpc.NewServer() s.RegisterCodec(json.NewCodec(), "application/json") - s.RegisterService(new(VersionService), "Version") s.RegisterService(new(DonutService), "Donut") s.RegisterService(new(AuthService), "Auth") - s.RegisterService(new(controllerServerRPCService), "Server") + s.RegisterService(new(controllerRPCService), "Server") // Add new RPC services here return registerRPC(router.NewRouter(), s) } diff --git a/controller-rpc-server.go b/controller-rpc-server.go index 93dc4ec6c..64851f43d 100644 --- a/controller-rpc-server.go +++ b/controller-rpc-server.go @@ -23,17 +23,18 @@ import ( "github.com/minio/minio/pkg/probe" ) -type controllerServerRPCService struct { +type controllerRPCService struct { serverList []ServerArg } -func proxyRequest(method string, ip string, arg interface{}, res interface{}) error { +func proxyRequest(method string, url string, arg interface{}, res interface{}) error { + // can be configured to something else in future + namespace := "Server" op := rpcOperation{ - Method: "Server." + method, + Method: namespace + "." + method, Request: arg, } - - request, _ := newRPCRequest("http://"+ip+":9002/rpc", op, nil) + request, _ := newRPCRequest(url, op, nil) resp, err := request.Do() if err != nil { return probe.WrapError(err) @@ -42,27 +43,31 @@ func proxyRequest(method string, ip string, arg interface{}, res interface{}) er return decodeerr } -func (s *controllerServerRPCService) Add(r *http.Request, arg *ServerArg, res *DefaultRep) error { - err := proxyRequest("Add", arg.IP, arg, res) +func (s *controllerRPCService) Add(r *http.Request, arg *ServerArg, res *DefaultRep) error { + err := proxyRequest("Add", arg.URL, arg, res) if err == nil { s.serverList = append(s.serverList, *arg) } return err } -func (s *controllerServerRPCService) MemStats(r *http.Request, arg *ServerArg, res *MemStatsRep) error { - return proxyRequest("MemStats", arg.IP, arg, res) +func (s *controllerRPCService) MemStats(r *http.Request, arg *ServerArg, res *MemStatsRep) error { + return proxyRequest("MemStats", arg.URL, arg, res) } -func (s *controllerServerRPCService) DiskStats(r *http.Request, arg *ServerArg, res *DiskStatsRep) error { - return proxyRequest("DiskStats", arg.IP, arg, res) +func (s *controllerRPCService) DiskStats(r *http.Request, arg *ServerArg, res *DiskStatsRep) error { + return proxyRequest("DiskStats", arg.URL, arg, res) } -func (s *controllerServerRPCService) SysInfo(r *http.Request, arg *ServerArg, res *SysInfoRep) error { - return proxyRequest("SysInfo", arg.IP, arg, res) +func (s *controllerRPCService) SysInfo(r *http.Request, arg *ServerArg, res *SysInfoRep) error { + return proxyRequest("SysInfo", arg.URL, arg, res) } -func (s *controllerServerRPCService) List(r *http.Request, arg *ServerArg, res *ListRep) error { +func (s *controllerRPCService) List(r *http.Request, arg *ServerArg, res *ListRep) error { res.List = s.serverList return nil } + +func (s *controllerRPCService) Version(r *http.Request, arg *ServerArg, res *VersionRep) error { + return proxyRequest("Version", arg.URL, arg, res) +} diff --git a/controller-rpc-version.go b/controller-rpc-version.go deleted file mode 100644 index 0a070ff00..000000000 --- a/controller-rpc-version.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "net/http" - "runtime" -) - -// VersionArgs basic json RPC params -type VersionArgs struct{} - -// VersionService get version service -type VersionService struct{} - -// VersionReply version reply -type VersionReply struct { - Version string `json:"version"` - BuildDate string `json:"buildDate"` - Architecture string `json:"arch"` - OperatingSystem string `json:"os"` -} - -// Get version -func (v *VersionService) Get(r *http.Request, args *VersionArgs, reply *VersionReply) error { - reply.Version = "0.0.1" - reply.BuildDate = minioVersion - reply.Architecture = runtime.GOARCH - reply.OperatingSystem = runtime.GOOS - return nil -} diff --git a/controller.go b/controller.go deleted file mode 100644 index 2f3082c36..000000000 --- a/controller.go +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "fmt" - "net" - "net/http" - "os" - "strings" - - "github.com/minio/minio/pkg/minhttp" - "github.com/minio/minio/pkg/probe" -) - -// getRPCServer instance -func getControllerRPCServer(rpcHandler http.Handler) (*http.Server, *probe.Error) { - // Minio server config - httpServer := &http.Server{ - Addr: ":9001", // TODO make this configurable - Handler: rpcHandler, - MaxHeaderBytes: 1 << 20, - } - var hosts []string - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil, probe.NewError(err) - } - for _, addr := range addrs { - if addr.Network() == "ip+net" { - host := strings.Split(addr.String(), "/")[0] - if ip := net.ParseIP(host); ip.To4() != nil { - hosts = append(hosts, host) - } - } - } - for _, host := range hosts { - fmt.Printf("Starting minio server on: http://%s:9001/rpc, PID: %d\n", host, os.Getpid()) - } - return httpServer, nil -} - -// StartController starts a minio controller -func StartController() *probe.Error { - rpcServer, err := getControllerRPCServer(getRPCCtrlHandler()) - if err != nil { - return err.Trace() - } - // Setting rate limit to 'zero' no ratelimiting implemented - if err := minhttp.ListenAndServeLimited(0, rpcServer); err != nil { - return err.Trace() - } - return nil -} diff --git a/controller_rpc_test.go b/controller_rpc_test.go index dbc1ae1d2..5087af6d0 100644 --- a/controller_rpc_test.go +++ b/controller_rpc_test.go @@ -1,5 +1,3 @@ -// +build ignore - /* * Minio Cloud Storage, (C) 2014 Minio, Inc. * @@ -33,26 +31,33 @@ type ControllerRPCSuite struct{} var _ = Suite(&ControllerRPCSuite{}) -var testRPCServer *httptest.Server +var ( + testControllerRPC *httptest.Server + testServerRPC *httptest.Server +) func (s *ControllerRPCSuite) SetUpSuite(c *C) { root, err := ioutil.TempDir(os.TempDir(), "api-") c.Assert(err, IsNil) auth.SetAuthConfigPath(root) - testRPCServer = httptest.NewServer(getRPCCtrlHandler()) + testControllerRPC = httptest.NewServer(getControllerRPCHandler()) + testServerRPC = httptest.NewUnstartedServer(getServerRPCHandler()) + testServerRPC.Config.Addr = ":9002" + testServerRPC.Start() } func (s *ControllerRPCSuite) TearDownSuite(c *C) { - testRPCServer.Close() + testServerRPC.Close() + testControllerRPC.Close() } func (s *ControllerRPCSuite) TestMemStats(c *C) { op := rpcOperation{ Method: "Server.MemStats", - Request: ServerArg{}, + Request: ServerArg{URL: testServerRPC.URL + "/rpc"}, } - req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + req, err := newRPCRequest(testControllerRPC.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) c.Assert(req.Get("Content-Type"), Equals, "application/json") resp, err := req.Do() @@ -68,9 +73,9 @@ func (s *ControllerRPCSuite) TestMemStats(c *C) { func (s *ControllerRPCSuite) TestSysInfo(c *C) { op := rpcOperation{ Method: "Server.SysInfo", - Request: ServerArg{}, + Request: ServerArg{URL: testServerRPC.URL + "/rpc"}, } - req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + req, err := newRPCRequest(testControllerRPC.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) c.Assert(req.Get("Content-Type"), Equals, "application/json") resp, err := req.Do() @@ -86,9 +91,9 @@ func (s *ControllerRPCSuite) TestSysInfo(c *C) { func (s *ControllerRPCSuite) TestServerList(c *C) { op := rpcOperation{ Method: "Server.List", - Request: ServerArg{}, + Request: ServerArg{URL: testServerRPC.URL + "/rpc"}, } - req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + req, err := newRPCRequest(testControllerRPC.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) c.Assert(req.Get("Content-Type"), Equals, "application/json") resp, err := req.Do() @@ -104,9 +109,9 @@ func (s *ControllerRPCSuite) TestServerList(c *C) { func (s *ControllerRPCSuite) TestServerAdd(c *C) { op := rpcOperation{ Method: "Server.Add", - Request: ServerArg{}, + Request: ServerArg{URL: testServerRPC.URL + "/rpc"}, } - req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + req, err := newRPCRequest(testControllerRPC.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) c.Assert(req.Get("Content-Type"), Equals, "application/json") resp, err := req.Do() @@ -124,7 +129,7 @@ func (s *ControllerRPCSuite) TestAuth(c *C) { Method: "Auth.Generate", Request: AuthArgs{User: "newuser"}, } - req, err := newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + req, err := newRPCRequest(testControllerRPC.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) c.Assert(req.Get("Content-Type"), Equals, "application/json") resp, err := req.Do() @@ -143,7 +148,7 @@ func (s *ControllerRPCSuite) TestAuth(c *C) { Method: "Auth.Fetch", Request: AuthArgs{User: "newuser"}, } - req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + req, err = newRPCRequest(testControllerRPC.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) c.Assert(req.Get("Content-Type"), Equals, "application/json") resp, err = req.Do() @@ -162,7 +167,7 @@ func (s *ControllerRPCSuite) TestAuth(c *C) { Method: "Auth.Reset", Request: AuthArgs{User: "newuser"}, } - req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + req, err = newRPCRequest(testControllerRPC.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) c.Assert(req.Get("Content-Type"), Equals, "application/json") resp, err = req.Do() @@ -184,7 +189,7 @@ func (s *ControllerRPCSuite) TestAuth(c *C) { Method: "Auth.Generate", Request: AuthArgs{User: "newuser"}, } - req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + req, err = newRPCRequest(testControllerRPC.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) c.Assert(req.Get("Content-Type"), Equals, "application/json") resp, err = req.Do() @@ -196,7 +201,7 @@ func (s *ControllerRPCSuite) TestAuth(c *C) { Method: "Auth.Generate", Request: AuthArgs{User: ""}, } - req, err = newRPCRequest(testRPCServer.URL+"/rpc", op, http.DefaultTransport) + req, err = newRPCRequest(testControllerRPC.URL+"/rpc", op, http.DefaultTransport) c.Assert(err, IsNil) c.Assert(req.Get("Content-Type"), Equals, "application/json") resp, err = req.Do() diff --git a/disks.go b/donut-disks.go similarity index 100% rename from disks.go rename to donut-disks.go diff --git a/flags.go b/flags.go index 1dbb7efbc..8977ed63b 100644 --- a/flags.go +++ b/flags.go @@ -28,15 +28,15 @@ var ( Usage: "ADDRESS:PORT for cloud storage access", } - addressMgmtFlag = cli.StringFlag{ - Name: "address-mgmt", + addressControllerFlag = cli.StringFlag{ + Name: "address-controller", Hide: true, Value: ":9001", Usage: "ADDRESS:PORT for management console access", } - addressRPCServerFlag = cli.StringFlag{ - Name: "address-rpcserver", + addressServerRPCFlag = cli.StringFlag{ + Name: "address-server-rpc", Hide: true, Value: ":9002", Usage: "ADDRESS:PORT for management console access", diff --git a/logger.go b/logger.go index 266001fbd..3cef3e5ea 100644 --- a/logger.go +++ b/logger.go @@ -54,7 +54,21 @@ func errorIf(err *probe.Error, msg string, fields map[string]interface{}) { fields["probe"] = string(jsonErr) } log.WithFields(fields).Error(msg) +} + +func fatalIf(err *probe.Error, msg string, fields map[string]interface{}) { + if err == nil { + return + } + if fields == nil { + fields = make(map[string]interface{}) + } + fields["error"] = err.ToGoError() + if jsonErr, e := json.Marshal(err); e == nil { + fields["probe"] = string(jsonErr) + } + log.WithFields(fields).Fatal(msg) } func audit(msg string, fields logrus.Fields) { diff --git a/main.go b/main.go index 20a629c8c..39c14d04f 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,17 @@ import ( "github.com/minio/cli" ) +// minioConfig - http server config +type minioConfig struct { + Address string + ControllerAddress string + RPCAddress string + TLS bool + CertFile string + KeyFile string + RateLimit int +} + func init() { // Check for the environment early on and gracefuly report. @@ -86,12 +97,12 @@ func registerApp() *cli.App { // register all flags registerFlag(addressFlag) - registerFlag(addressMgmtFlag) + registerFlag(addressControllerFlag) registerFlag(ratelimitFlag) registerFlag(certFlag) registerFlag(keyFlag) registerFlag(debugFlag) - registerFlag(addressRPCServerFlag) + registerFlag(addressServerRPCFlag) // set up app app := cli.NewApp() diff --git a/rpc-definitions.go b/rpc-definitions.go index 0d610ca68..32bdb061e 100644 --- a/rpc-definitions.go +++ b/rpc-definitions.go @@ -16,65 +16,73 @@ package main -// Ethernet interface -type Ifc struct { - IP string `json:"ip"` - Mask string `json:"mask"` - Eth string `json:"ifc"` +// Network properties of a server +type Network struct { + IP string `json:"address"` + Mask string `json:"netmask"` + Ethernet string `json:"networkInterface"` } -// Identify a server +// ServerArg server metadata to identify a server type ServerArg struct { Name string `json:"name"` - IP string `json:"ip"` + URL string `json:"url"` ID string `json:"id"` } -// Needed for Reply for Server.List +// ServerRep server reply container for Server.List type ServerRep struct { - Name string `json:"name"` - IP string `json:"ip"` - ID string `json:"id"` + Name string `json:"name"` + Address string `json:"address"` + ID string `json:"id"` } -// Default reply +// DefaultRep default reply type DefaultRep struct { Error int64 `json:"error"` Message string `json:"message"` } -// Needed for Reply List +// ServerListRep collection of server replies type ServerListRep struct { List []ServerRep } -// Reply DiskStats +// DiskStatsRep collection of disks type DiskStatsRep struct { Disks []string } -// Reply MemStats +// MemStatsRep memory statistics of a server type MemStatsRep struct { Total uint64 `json:"total"` Free uint64 `json:"free"` } -// Reply NetStats +// NetStatsRep network statistics of a server type NetStatsRep struct { - Interfaces []Ifc + Interfaces []Network } -// Reply SysInfo +// SysInfoRep system information of a server type SysInfoRep struct { Hostname string `json:"hostname"` - SysARCH string `json:"sys.arch"` - SysOS string `json:"sys.os"` - SysCPUS int `json:"sys.ncpus"` - Routines int `json:"goroutines"` - GOVersion string `json:"goversion"` + SysARCH string `json:"sysArch"` + SysOS string `json:"sysOS"` + SysCPUS int `json:"sysNcpus"` + Routines int `json:"goRoutines"` + GOVersion string `json:"goVersion"` } -// Reply List +// ListRep all servers list type ListRep struct { List []ServerArg `json:"list"` } + +// VersionRep version reply +type VersionRep struct { + Version string `json:"version"` + BuildDate string `json:"buildDate"` + Architecture string `json:"arch"` + OperatingSystem string `json:"os"` +} diff --git a/server-api-bucket-handlers.go b/server-api-bucket-handlers.go index 4843f70a9..e317ef1e7 100644 --- a/server-api-bucket-handlers.go +++ b/server-api-bucket-handlers.go @@ -30,6 +30,7 @@ func (api MinioAPI) isValidOp(w http.ResponseWriter, req *http.Request, acceptsC bucketMetadata, err := api.Donut.GetBucketMetadata(bucket, nil) if err != nil { + errorIf(err.Trace(), "GetBucketMetadata failed.", nil) switch err.ToGoError().(type) { case donut.BucketNotFound: writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) @@ -38,7 +39,6 @@ func (api MinioAPI) isValidOp(w http.ResponseWriter, req *http.Request, acceptsC writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) return false default: - // log.Error.Println(err.Trace()) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return false } @@ -100,31 +100,32 @@ func (api MinioAPI) ListMultipartUploadsHandler(w http.ResponseWriter, req *http var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } resources, err := api.Donut.ListMultipartUploads(bucket, resources, signature) - if err == nil { - // generate response - response := generateListMultipartUploadsResponse(bucket, resources) - encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) - // write headers - setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) - // write body - w.Write(encodedSuccessResponse) + if err != nil { + errorIf(err.Trace(), "ListMultipartUploads failed.", nil) + switch err.ToGoError().(type) { + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.BucketNotFound: + writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.BucketNotFound: - writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + // generate response + response := generateListMultipartUploadsResponse(bucket, resources) + encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) + // write headers + setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) + // write body + w.Write(encodedSuccessResponse) } // ListObjectsHandler - GET Bucket (List Objects) @@ -171,6 +172,7 @@ func (api MinioAPI) ListObjectsHandler(w http.ResponseWriter, req *http.Request) var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } @@ -199,7 +201,7 @@ func (api MinioAPI) ListObjectsHandler(w http.ResponseWriter, req *http.Request) case donut.ObjectNameInvalid: writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) default: - // log.Error.Println(err.Trace()) + errorIf(err.Trace(), "ListObjects failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } } @@ -232,6 +234,7 @@ func (api MinioAPI) ListBucketsHandler(w http.ResponseWriter, req *http.Request) var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } @@ -252,7 +255,7 @@ func (api MinioAPI) ListBucketsHandler(w http.ResponseWriter, req *http.Request) case donut.SignatureDoesNotMatch: writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) default: - // log.Error.Println(err.Trace()) + errorIf(err.Trace(), "ListBuckets failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } } @@ -298,6 +301,7 @@ func (api MinioAPI) PutBucketHandler(w http.ResponseWriter, req *http.Request) { var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } @@ -314,25 +318,25 @@ func (api MinioAPI) PutBucketHandler(w http.ResponseWriter, req *http.Request) { } err := api.Donut.MakeBucket(bucket, getACLTypeString(aclType), req.Body, signature) - if err == nil { - // Make sure to add Location information here only for bucket - w.Header().Set("Location", "/"+bucket) - writeSuccessResponse(w, acceptsContentType) + if err != nil { + errorIf(err.Trace(), "MakeBucket failed.", nil) + switch err.ToGoError().(type) { + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.TooManyBuckets: + writeErrorResponse(w, req, TooManyBuckets, acceptsContentType, req.URL.Path) + case donut.BucketNameInvalid: + writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) + case donut.BucketExists: + writeErrorResponse(w, req, BucketAlreadyExists, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.TooManyBuckets: - writeErrorResponse(w, req, TooManyBuckets, acceptsContentType, req.URL.Path) - case donut.BucketNameInvalid: - writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) - case donut.BucketExists: - writeErrorResponse(w, req, BucketAlreadyExists, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + // Make sure to add Location information here only for bucket + w.Header().Set("Location", "/"+bucket) + writeSuccessResponse(w, acceptsContentType) } // PutBucketACLHandler - PUT Bucket ACL @@ -366,27 +370,28 @@ func (api MinioAPI) PutBucketACLHandler(w http.ResponseWriter, req *http.Request var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } err := api.Donut.SetBucketMetadata(bucket, map[string]string{"acl": getACLTypeString(aclType)}, signature) - if err == nil { - writeSuccessResponse(w, acceptsContentType) + if err != nil { + errorIf(err.Trace(), "PutBucketACL failed.", nil) + switch err.ToGoError().(type) { + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.BucketNameInvalid: + writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) + case donut.BucketNotFound: + writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.BucketNameInvalid: - writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) - case donut.BucketNotFound: - writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + writeSuccessResponse(w, acceptsContentType) } // HeadBucketHandler - HEAD Bucket @@ -416,25 +421,26 @@ func (api MinioAPI) HeadBucketHandler(w http.ResponseWriter, req *http.Request) var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } _, err := api.Donut.GetBucketMetadata(bucket, signature) - if err == nil { - writeSuccessResponse(w, acceptsContentType) + if err != nil { + errorIf(err.Trace(), "GetBucketMetadata failed.", nil) + switch err.ToGoError().(type) { + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.BucketNotFound: + writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) + case donut.BucketNameInvalid: + writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.BucketNotFound: - writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) - case donut.BucketNameInvalid: - writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + writeSuccessResponse(w, acceptsContentType) } diff --git a/server-api-definitions.go b/server-api-definitions.go index 44ada72c7..c05f5fc3d 100644 --- a/server-api-definitions.go +++ b/server-api-definitions.go @@ -18,16 +18,6 @@ package main import "encoding/xml" -// APIConfig - http server config -type APIConfig struct { - Address string - AddressRPC string - TLS bool - CertFile string - KeyFile string - RateLimit int -} - // Limit number of objects in a given response const ( maxObjectList = 1000 diff --git a/server-api-object-handlers.go b/server-api-object-handlers.go index ee4270b2b..071477b73 100644 --- a/server-api-object-handlers.go +++ b/server-api-object-handlers.go @@ -59,41 +59,41 @@ func (api MinioAPI) GetObjectHandler(w http.ResponseWriter, req *http.Request) { var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } metadata, err := api.Donut.GetObjectMetadata(bucket, object, signature) - if err == nil { - var hrange *httpRange - hrange, err = getRequestedRange(req.Header.Get("Range"), metadata.Size) - if err != nil { - writeErrorResponse(w, req, InvalidRange, acceptsContentType, req.URL.Path) - return - } - setObjectHeaders(w, metadata, hrange) - if _, err = api.Donut.GetObject(w, bucket, object, hrange.start, hrange.length); err != nil { - // unable to write headers, we've already printed data. Just close the connection. - // log.Error.Println(err.Trace()) - return + if err != nil { + errorIf(err.Trace(), "GetObject failed.", nil) + switch err.ToGoError().(type) { + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.BucketNameInvalid: + writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) + case donut.BucketNotFound: + writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) + case donut.ObjectNotFound: + writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) + case donut.ObjectNameInvalid: + writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) } return } - switch err.ToGoError().(type) { - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.BucketNameInvalid: - writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) - case donut.BucketNotFound: - writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) - case donut.ObjectNotFound: - writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) - case donut.ObjectNameInvalid: - writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + var hrange *httpRange + hrange, err = getRequestedRange(req.Header.Get("Range"), metadata.Size) + if err != nil { + writeErrorResponse(w, req, InvalidRange, acceptsContentType, req.URL.Path) + return + } + setObjectHeaders(w, metadata, hrange) + if _, err = api.Donut.GetObject(w, bucket, object, hrange.start, hrange.length); err != nil { + errorIf(err.Trace(), "GetObject failed.", nil) + return } } @@ -126,32 +126,33 @@ func (api MinioAPI) HeadObjectHandler(w http.ResponseWriter, req *http.Request) var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } metadata, err := api.Donut.GetObjectMetadata(bucket, object, signature) - if err == nil { - setObjectHeaders(w, metadata, nil) - w.WriteHeader(http.StatusOK) + if err != nil { + errorIf(err.Trace(), "GetObjectMetadata failed.", nil) + switch err.ToGoError().(type) { + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.BucketNameInvalid: + writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) + case donut.BucketNotFound: + writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) + case donut.ObjectNotFound: + writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) + case donut.ObjectNameInvalid: + writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.BucketNameInvalid: - writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) - case donut.BucketNotFound: - writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) - case donut.ObjectNotFound: - writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) - case donut.ObjectNameInvalid: - writeErrorResponse(w, req, NoSuchKey, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + setObjectHeaders(w, metadata, nil) + w.WriteHeader(http.StatusOK) } // PutObjectHandler - PUT Object @@ -220,40 +221,41 @@ func (api MinioAPI) PutObjectHandler(w http.ResponseWriter, req *http.Request) { var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } metadata, err := api.Donut.CreateObject(bucket, object, md5, sizeInt64, req.Body, nil, signature) - if err == nil { - w.Header().Set("ETag", metadata.MD5Sum) - writeSuccessResponse(w, acceptsContentType) + if err != nil { + errorIf(err.Trace(), "CreateObject failed.", nil) + switch err.ToGoError().(type) { + case donut.BucketNotFound: + writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) + case donut.BucketNameInvalid: + writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) + case donut.ObjectExists: + writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) + case donut.BadDigest: + writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path) + case donut.MissingDateHeader: + writeErrorResponse(w, req, RequestTimeTooSkewed, acceptsContentType, req.URL.Path) + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.IncompleteBody: + writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path) + case donut.EntityTooLarge: + writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path) + case donut.InvalidDigest: + writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.BucketNotFound: - writeErrorResponse(w, req, NoSuchBucket, acceptsContentType, req.URL.Path) - case donut.BucketNameInvalid: - writeErrorResponse(w, req, InvalidBucketName, acceptsContentType, req.URL.Path) - case donut.ObjectExists: - writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) - case donut.BadDigest: - writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path) - case donut.MissingDateHeader: - writeErrorResponse(w, req, RequestTimeTooSkewed, acceptsContentType, req.URL.Path) - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.IncompleteBody: - writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path) - case donut.EntityTooLarge: - writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path) - case donut.InvalidDigest: - writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + w.Header().Set("ETag", metadata.MD5Sum) + writeSuccessResponse(w, acceptsContentType) } /// Multipart API @@ -290,30 +292,32 @@ func (api MinioAPI) NewMultipartUploadHandler(w http.ResponseWriter, req *http.R var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } uploadID, err := api.Donut.NewMultipartUpload(bucket, object, req.Header.Get("Content-Type"), signature) - if err == nil { - response := generateInitiateMultipartUploadResponse(bucket, object, uploadID) - encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) - // write headers - setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) - // write body - w.Write(encodedSuccessResponse) + if err != nil { + errorIf(err.Trace(), "NewMultipartUpload failed.", nil) + switch err.ToGoError().(type) { + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.ObjectExists: + writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.ObjectExists: - writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + + response := generateInitiateMultipartUploadResponse(bucket, object, uploadID) + encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) + // write headers + setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) + // write body + w.Write(encodedSuccessResponse) } // PutObjectPartHandler - Upload part @@ -375,6 +379,7 @@ func (api MinioAPI) PutObjectPartHandler(w http.ResponseWriter, req *http.Reques partID, err = strconv.Atoi(partIDString) if err != nil { writeErrorResponse(w, req, InvalidPart, acceptsContentType, req.URL.Path) + return } } @@ -384,36 +389,37 @@ func (api MinioAPI) PutObjectPartHandler(w http.ResponseWriter, req *http.Reques var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } calculatedMD5, err := api.Donut.CreateObjectPart(bucket, object, uploadID, partID, "", md5, sizeInt64, req.Body, signature) - if err == nil { - w.Header().Set("ETag", calculatedMD5) - writeSuccessResponse(w, acceptsContentType) + if err != nil { + errorIf(err.Trace(), "CreateObjectPart failed.", nil) + switch err.ToGoError().(type) { + case donut.InvalidUploadID: + writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) + case donut.ObjectExists: + writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) + case donut.BadDigest: + writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path) + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.IncompleteBody: + writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path) + case donut.EntityTooLarge: + writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path) + case donut.InvalidDigest: + writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.InvalidUploadID: - writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) - case donut.ObjectExists: - writeErrorResponse(w, req, MethodNotAllowed, acceptsContentType, req.URL.Path) - case donut.BadDigest: - writeErrorResponse(w, req, BadDigest, acceptsContentType, req.URL.Path) - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.IncompleteBody: - writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path) - case donut.EntityTooLarge: - writeErrorResponse(w, req, EntityTooLarge, acceptsContentType, req.URL.Path) - case donut.InvalidDigest: - writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + w.Header().Set("ETag", calculatedMD5) + writeSuccessResponse(w, acceptsContentType) } // AbortMultipartUploadHandler - Abort multipart upload @@ -444,26 +450,27 @@ func (api MinioAPI) AbortMultipartUploadHandler(w http.ResponseWriter, req *http var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } err := api.Donut.AbortMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, signature) - if err == nil { - setCommonHeaders(w, getContentTypeString(acceptsContentType), 0) - w.WriteHeader(http.StatusNoContent) + if err != nil { + errorIf(err.Trace(), "AbortMutlipartUpload failed.", nil) + switch err.ToGoError().(type) { + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.InvalidUploadID: + writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.InvalidUploadID: - writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + setCommonHeaders(w, getContentTypeString(acceptsContentType), 0) + w.WriteHeader(http.StatusNoContent) } // ListObjectPartsHandler - List object parts @@ -505,30 +512,31 @@ func (api MinioAPI) ListObjectPartsHandler(w http.ResponseWriter, req *http.Requ var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } objectResourcesMetadata, err := api.Donut.ListObjectParts(bucket, object, objectResourcesMetadata, signature) - if err == nil { - response := generateListPartsResponse(objectResourcesMetadata) - encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) - // write headers - setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) - // write body - w.Write(encodedSuccessResponse) + if err != nil { + errorIf(err.Trace(), "ListObjectParts failed.", nil) + switch err.ToGoError().(type) { + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.InvalidUploadID: + writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.InvalidUploadID: - writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + response := generateListPartsResponse(objectResourcesMetadata) + encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) + // write headers + setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) + // write body + w.Write(encodedSuccessResponse) } // CompleteMultipartUploadHandler - Complete multipart upload @@ -559,39 +567,40 @@ func (api MinioAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, req *h var err *probe.Error signature, err = initSignatureV4(req) if err != nil { + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) return } } metadata, err := api.Donut.CompleteMultipartUpload(bucket, object, objectResourcesMetadata.UploadID, req.Body, signature) - if err == nil { - response := generateCompleteMultpartUploadResponse(bucket, object, "", metadata.MD5Sum) - encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) - // write headers - setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) - // write body - w.Write(encodedSuccessResponse) + if err != nil { + errorIf(err.Trace(), "CompleteMultipartUpload failed.", nil) + switch err.ToGoError().(type) { + case donut.InvalidUploadID: + writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) + case donut.InvalidPart: + writeErrorResponse(w, req, InvalidPart, acceptsContentType, req.URL.Path) + case donut.InvalidPartOrder: + writeErrorResponse(w, req, InvalidPartOrder, acceptsContentType, req.URL.Path) + case donut.MissingDateHeader: + writeErrorResponse(w, req, RequestTimeTooSkewed, acceptsContentType, req.URL.Path) + case donut.SignatureDoesNotMatch: + writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) + case donut.IncompleteBody: + writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path) + case donut.MalformedXML: + writeErrorResponse(w, req, MalformedXML, acceptsContentType, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) + } return } - switch err.ToGoError().(type) { - case donut.InvalidUploadID: - writeErrorResponse(w, req, NoSuchUpload, acceptsContentType, req.URL.Path) - case donut.InvalidPart: - writeErrorResponse(w, req, InvalidPart, acceptsContentType, req.URL.Path) - case donut.InvalidPartOrder: - writeErrorResponse(w, req, InvalidPartOrder, acceptsContentType, req.URL.Path) - case donut.MissingDateHeader: - writeErrorResponse(w, req, RequestTimeTooSkewed, acceptsContentType, req.URL.Path) - case donut.SignatureDoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, acceptsContentType, req.URL.Path) - case donut.IncompleteBody: - writeErrorResponse(w, req, IncompleteBody, acceptsContentType, req.URL.Path) - case donut.MalformedXML: - writeErrorResponse(w, req, MalformedXML, acceptsContentType, req.URL.Path) - default: - // log.Error.Println(err.Trace()) - writeErrorResponse(w, req, InternalError, acceptsContentType, req.URL.Path) - } + response := generateCompleteMultpartUploadResponse(bucket, object, "", metadata.MD5Sum) + encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType) + // write headers + setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedSuccessResponse)) + // write body + w.Write(encodedSuccessResponse) } /// Delete API diff --git a/server-api-response.go b/server-api-response.go index afc826623..54ab10c35 100644 --- a/server-api-response.go +++ b/server-api-response.go @@ -18,7 +18,6 @@ package main import ( "net/http" - "sort" "github.com/minio/minio/pkg/donut" ) @@ -55,13 +54,6 @@ func generateListBucketsResponse(buckets []donut.BucketMetadata) ListBucketsResp return data } -// itemKey -type itemKey []*Object - -func (b itemKey) Len() int { return len(b) } -func (b itemKey) Swap(i, j int) { b[i], b[j] = b[j], b[i] } -func (b itemKey) Less(i, j int) bool { return b[i].Key < b[j].Key } - // takes a set of objects and prepares the objects for serialization // input: // bucket name @@ -92,7 +84,6 @@ func generateListObjectsResponse(bucket string, objects []donut.ObjectMetadata, content.Owner = owner contents = append(contents, content) } - sort.Sort(itemKey(contents)) // TODO - support EncodingType in xml decoding data.Name = bucket data.Contents = contents @@ -201,7 +192,7 @@ func writeErrorResponse(w http.ResponseWriter, req *http.Request, errorType int, setCommonHeaders(w, getContentTypeString(acceptsContentType), len(encodedErrorResponse)) // write Header w.WriteHeader(error.HTTPStatusCode) - // HEAD should have no body + // HEAD should have no body, do not attempt to write to it if req.Method != "HEAD" { // write error body w.Write(encodedErrorResponse) diff --git a/server-api.go b/server-api.go deleted file mode 100644 index ad50c6a7f..000000000 --- a/server-api.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2015 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import "github.com/minio/minio/pkg/donut" - -// APIOperation container for individual operations read by Ticket Master -type APIOperation struct { - ProceedCh chan struct{} -} - -// MinioAPI container for API and also carries OP (operation) channel -type MinioAPI struct { - OP chan APIOperation - Donut donut.Interface -} - -// NewAPI instantiate a new minio API -func NewAPI() MinioAPI { - // ignore errors for now - d, err := donut.New() - if err != nil { - panic(err) - } - return MinioAPI{ - OP: make(chan APIOperation), - Donut: d, - } -} diff --git a/server-main.go b/server-main.go index 2f2b5b46f..904fc3cc0 100644 --- a/server-main.go +++ b/server-main.go @@ -16,7 +16,18 @@ package main -import "github.com/minio/cli" +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "os" + "strings" + + "github.com/minio/cli" + "github.com/minio/minio/pkg/minhttp" + "github.com/minio/minio/pkg/probe" +) var serverCmd = cli.Command{ Name: "server", @@ -35,17 +46,117 @@ EXAMPLES: `, } -func getServerConfig(c *cli.Context) APIConfig { +// configureAPIServer configure a new server instance +func configureAPIServer(conf minioConfig, apiHandler http.Handler) (*http.Server, *probe.Error) { + // Minio server config + apiServer := &http.Server{ + Addr: conf.Address, + Handler: apiHandler, + MaxHeaderBytes: 1 << 20, + } + + if conf.TLS { + var err error + apiServer.TLSConfig = &tls.Config{} + apiServer.TLSConfig.Certificates = make([]tls.Certificate, 1) + apiServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(conf.CertFile, conf.KeyFile) + if err != nil { + return nil, probe.NewError(err) + } + } + + host, port, err := net.SplitHostPort(conf.Address) + if err != nil { + return nil, probe.NewError(err) + } + + var hosts []string + switch { + case host != "": + hosts = append(hosts, host) + default: + addrs, err := net.InterfaceAddrs() + if err != nil { + return nil, probe.NewError(err) + } + for _, addr := range addrs { + if addr.Network() == "ip+net" { + host := strings.Split(addr.String(), "/")[0] + if ip := net.ParseIP(host); ip.To4() != nil { + hosts = append(hosts, host) + } + } + } + } + + for _, host := range hosts { + if conf.TLS { + fmt.Printf("Starting minio server on: https://%s:%s, PID: %d\n", host, port, os.Getpid()) + } else { + fmt.Printf("Starting minio server on: http://%s:%s, PID: %d\n", host, port, os.Getpid()) + } + } + return apiServer, nil +} + +// configureServerRPC configure server rpc port +func configureServerRPC(conf minioConfig, rpcHandler http.Handler) (*http.Server, *probe.Error) { + // Minio server config + rpcServer := &http.Server{ + Addr: conf.RPCAddress, + Handler: rpcHandler, + MaxHeaderBytes: 1 << 20, + } + + if conf.TLS { + var err error + rpcServer.TLSConfig = &tls.Config{} + rpcServer.TLSConfig.Certificates = make([]tls.Certificate, 1) + rpcServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(conf.CertFile, conf.KeyFile) + if err != nil { + return nil, probe.NewError(err) + } + } + return rpcServer, nil +} + +// Start ticket master +func startTM(a MinioAPI) { + for { + for op := range a.OP { + op.ProceedCh <- struct{}{} + } + } +} + +// startServer starts an s3 compatible cloud storage server +func startServer(conf minioConfig) *probe.Error { + minioAPI := getNewAPI() + apiHandler := getAPIHandler(minioAPI) + apiServer, err := configureAPIServer(conf, apiHandler) + if err != nil { + return err.Trace() + } + rpcServer, err := configureServerRPC(conf, getServerRPCHandler()) + + // start ticket master + go startTM(minioAPI) + if err := minhttp.ListenAndServe(apiServer, rpcServer); err != nil { + return err.Trace() + } + return nil +} + +func getServerConfig(c *cli.Context) minioConfig { certFile := c.GlobalString("cert") keyFile := c.GlobalString("key") if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") { Fatalln("Both certificate and key are required to enable https.") } tls := (certFile != "" && keyFile != "") - - return APIConfig{ + return minioConfig{ Address: c.GlobalString("address"), - AddressRPC: c.GlobalString("address-rpcserver"), + RPCAddress: c.GlobalString("address-server-rpc"), TLS: tls, CertFile: certFile, KeyFile: keyFile, @@ -59,6 +170,6 @@ func serverMain(c *cli.Context) { } apiServerConfig := getServerConfig(c) - err := StartServer(apiServerConfig) + err := startServer(apiServerConfig) errorIf(err.Trace(), "Failed to start the minio server.", nil) } diff --git a/server-router.go b/server-router.go index 2ebf5102c..8d752187a 100644 --- a/server-router.go +++ b/server-router.go @@ -22,6 +22,7 @@ import ( router "github.com/gorilla/mux" jsonrpc "github.com/gorilla/rpc/v2" "github.com/gorilla/rpc/v2/json" + "github.com/minio/minio/pkg/donut" ) // registerAPI - register all the object API handlers to their respective paths @@ -55,8 +56,31 @@ func registerCustomMiddleware(mux *router.Router, mwHandlers ...MiddlewareHandle return f } +// APIOperation container for individual operations read by Ticket Master +type APIOperation struct { + ProceedCh chan struct{} +} + +// MinioAPI container for API and also carries OP (operation) channel +type MinioAPI struct { + OP chan APIOperation + Donut donut.Interface +} + +// getNewAPI instantiate a new minio API +func getNewAPI() MinioAPI { + // ignore errors for now + d, err := donut.New() + fatalIf(err.Trace(), "Instantiating donut failed.", nil) + + return MinioAPI{ + OP: make(chan APIOperation), + Donut: d, + } +} + // getAPIHandler api handler -func getAPIHandler(conf APIConfig) (http.Handler, MinioAPI) { +func getAPIHandler(minioAPI MinioAPI) http.Handler { var mwHandlers = []MiddlewareHandler{ ValidContentTypeHandler, TimeValidityHandler, @@ -65,18 +89,16 @@ func getAPIHandler(conf APIConfig) (http.Handler, MinioAPI) { // api.LoggingHandler, // Disabled logging until we bring in external logging support CorsHandler, } - mux := router.NewRouter() - minioAPI := NewAPI() registerAPI(mux, minioAPI) apiHandler := registerCustomMiddleware(mux, mwHandlers...) - return apiHandler, minioAPI + return apiHandler } -func getRPCServerHandler() http.Handler { +func getServerRPCHandler() http.Handler { s := jsonrpc.NewServer() s.RegisterCodec(json.NewCodec(), "application/json") - s.RegisterService(new(serverServerService), "Server") + s.RegisterService(new(serverRPCService), "Server") mux := router.NewRouter() mux.Handle("/rpc", s) return mux diff --git a/server-rpc-server.go b/server-rpc-server.go index 94e493608..e5cdf0728 100644 --- a/server-rpc-server.go +++ b/server-rpc-server.go @@ -24,26 +24,26 @@ import ( "github.com/minio/minio/pkg/probe" ) -type serverServerService struct{} +type serverRPCService struct{} -func (s *serverServerService) Add(r *http.Request, arg *ServerArg, rep *DefaultRep) error { +func (s *serverRPCService) Add(r *http.Request, arg *ServerArg, rep *DefaultRep) error { rep.Error = 0 rep.Message = "Added successfully" return nil } -func (s *serverServerService) MemStats(r *http.Request, arg *ServerArg, rep *MemStatsRep) error { +func (s *serverRPCService) MemStats(r *http.Request, arg *ServerArg, rep *MemStatsRep) error { rep.Total = 64 * 1024 * 1024 * 1024 rep.Free = 9 * 1024 * 1024 * 1024 return nil } -func (s *serverServerService) DiskStats(r *http.Request, arg *ServerArg, rep *DiskStatsRep) error { +func (s *serverRPCService) DiskStats(r *http.Request, arg *ServerArg, rep *DiskStatsRep) error { rep.Disks = []string{"/mnt/disk1", "/mnt/disk2", "/mnt/disk3", "/mnt/disk4", "/mnt/disk5", "/mnt/disk6"} return nil } -func (s *serverServerService) SysInfo(r *http.Request, arg *ServerArg, rep *SysInfoRep) error { +func (s *serverRPCService) SysInfo(r *http.Request, arg *ServerArg, rep *SysInfoRep) error { rep.SysARCH = runtime.GOARCH rep.SysOS = runtime.GOOS rep.SysCPUS = runtime.NumCPU() @@ -57,7 +57,15 @@ func (s *serverServerService) SysInfo(r *http.Request, arg *ServerArg, rep *SysI return nil } -func (s *serverServerService) NetStats(r *http.Request, arg *ServerArg, rep *NetStatsRep) error { - rep.Interfaces = []Ifc{{"192.168.1.1", "255.255.255.0", "eth0"}} +func (s *serverRPCService) NetStats(r *http.Request, arg *ServerArg, rep *NetStatsRep) error { + rep.Interfaces = []Network{{"192.168.1.1", "255.255.255.0", "eth0"}} + return nil +} + +func (s *serverRPCService) Version(r *http.Request, arg *ServerArg, rep *VersionRep) error { + rep.Version = "0.0.1" + rep.BuildDate = minioVersion + rep.Architecture = runtime.GOARCH + rep.OperatingSystem = runtime.GOOS return nil } diff --git a/server.go b/server.go deleted file mode 100644 index 6ee18a9a4..000000000 --- a/server.go +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2014 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "crypto/tls" - "fmt" - "net" - "net/http" - "os" - "strings" - - "github.com/minio/minio/pkg/minhttp" - "github.com/minio/minio/pkg/probe" -) - -func configureServer(conf APIConfig, httpServer *http.Server) *probe.Error { - if conf.TLS { - var err error - httpServer.TLSConfig = &tls.Config{} - httpServer.TLSConfig.Certificates = make([]tls.Certificate, 1) - httpServer.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(conf.CertFile, conf.KeyFile) - if err != nil { - return probe.NewError(err) - } - } - - host, port, err := net.SplitHostPort(conf.Address) - if err != nil { - return probe.NewError(err) - } - - var hosts []string - switch { - case host != "": - hosts = append(hosts, host) - default: - addrs, err := net.InterfaceAddrs() - if err != nil { - return probe.NewError(err) - } - for _, addr := range addrs { - if addr.Network() == "ip+net" { - host := strings.Split(addr.String(), "/")[0] - if ip := net.ParseIP(host); ip.To4() != nil { - hosts = append(hosts, host) - } - } - } - } - - for _, host := range hosts { - if conf.TLS { - fmt.Printf("Starting minio server on: https://%s:%s, PID: %d\n", host, port, os.Getpid()) - } else { - fmt.Printf("Starting minio server on: http://%s:%s, PID: %d\n", host, port, os.Getpid()) - } - } - return nil -} - -// getAPI server instance -func getAPIServer(conf APIConfig, apiHandler http.Handler) (*http.Server, *probe.Error) { - // Minio server config - httpServer := &http.Server{ - Addr: conf.Address, - Handler: apiHandler, - MaxHeaderBytes: 1 << 20, - } - if err := configureServer(conf, httpServer); err != nil { - return nil, err - } - return httpServer, nil -} - -// Start ticket master -func startTM(a MinioAPI) { - for { - for op := range a.OP { - op.ProceedCh <- struct{}{} - } - } -} - -func getServerRPCServer(conf APIConfig, handler http.Handler) (*http.Server, *probe.Error) { - httpServer := &http.Server{ - Addr: conf.AddressRPC, - Handler: handler, - MaxHeaderBytes: 1 << 20, - } - if err := configureServer(conf, httpServer); err != nil { - return nil, err - } - return httpServer, nil -} - -// Start starts a s3 compatible cloud storage server -func StartServer(conf APIConfig) *probe.Error { - apiHandler, minioAPI := getAPIHandler(conf) - apiServer, err := getAPIServer(conf, apiHandler) - if err != nil { - return err.Trace() - } - // start ticket master - go startTM(minioAPI) - rpcHandler := getRPCServerHandler() - rpcServer, err := getServerRPCServer(conf, rpcHandler) - if err != nil { - return err.Trace() - } - if err := minhttp.ListenAndServeLimited(conf.RateLimit, apiServer, rpcServer); err != nil { - return err.Trace() - } - return nil -} diff --git a/server_donut_cache_test.go b/server_donut_cache_test.go index cb2354dc2..29b2697c6 100644 --- a/server_donut_cache_test.go +++ b/server_donut_cache_test.go @@ -51,9 +51,10 @@ func (s *MyAPIDonutCacheSuite) SetUpSuite(c *C) { perr := donut.SaveConfig(conf) c.Assert(perr, IsNil) - httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16}) + minioAPI := getNewAPI() + apiHandler := getAPIHandler(minioAPI) go startTM(minioAPI) - testAPIDonutCacheServer = httptest.NewServer(httpHandler) + testAPIDonutCacheServer = httptest.NewServer(apiHandler) } func (s *MyAPIDonutCacheSuite) TearDownSuite(c *C) { diff --git a/server_donut_test.go b/server_donut_test.go index bd9cd2fa1..700461e8b 100644 --- a/server_donut_test.go +++ b/server_donut_test.go @@ -70,7 +70,8 @@ func (s *MyAPIDonutSuite) SetUpSuite(c *C) { perr := donut.SaveConfig(conf) c.Assert(perr, IsNil) - httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16}) + minioAPI := getNewAPI() + httpHandler := getAPIHandler(minioAPI) go startTM(minioAPI) testAPIDonutServer = httptest.NewServer(httpHandler) } diff --git a/server_signature_v4_test.go b/server_signature_v4_test.go index 2f39ed7d2..4355aa28b 100644 --- a/server_signature_v4_test.go +++ b/server_signature_v4_test.go @@ -78,7 +78,8 @@ func (s *MyAPISignatureV4Suite) SetUpSuite(c *C) { perr = auth.SaveConfig(authConf) c.Assert(perr, IsNil) - httpHandler, minioAPI := getAPIHandler(APIConfig{RateLimit: 16}) + minioAPI := getNewAPI() + httpHandler := getAPIHandler(minioAPI) go startTM(minioAPI) testSignatureV4Server = httptest.NewServer(httpHandler) }