diff --git a/README.md b/README.md index fb873a7fa..2046f7b00 100644 --- a/README.md +++ b/README.md @@ -98,20 +98,22 @@ Start minio server. ~~~ $ minio server ~/Photos -AccessKey: WLGDGYAQYIGI833EV05A SecretKey: BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF Region: us-east-1 - -Minio Object Storage: - http://127.0.0.1:9000 - http://10.1.10.177:9000 - -Minio Browser: - http://127.0.0.1:9000 - http://10.1.10.177:9000 - -To configure Minio Client: - $ wget https://dl.minio.io/client/mc/release/darwin-amd64/mc - $ chmod 755 mc - $ ./mc config host add myminio http://localhost:9000 WLGDGYAQYIGI833EV05A BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF +Endpoint: http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000 +AccessKey: USWUXHGYZQYFYFFIT3RE +SecretKey: MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03 +Region: us-east-1 + +Browser Access: + http://10.0.0.10:9000 http://127.0.0.1:9000 http://172.17.0.1:9000 + +Command-line Access: https://docs.minio.io/docs/minio-client-quick-start-guide + $ ./mc config host add myminio http://10.0.0.10:9000 USWUXHGYZQYFYFFIT3RE MOJRH0mkL1IPauahWITSVvyDrQbEEIwljvmxdq03 + +Object API (Amazon S3 compatible): + Go: https://docs.minio.io/docs/golang-client-quickstart-guide + Java: https://docs.minio.io/docs/java-client-quickstart-guide + Python: https://docs.minio.io/docs/python-client-quickstart-guide + JavaScript: https://docs.minio.io/docs/javascript-client-quickstart-guide ~~~ #### How to use AWS CLI with Minio? @@ -206,7 +208,7 @@ Bucket 's3://mybucket/' created To copy an object to bucket. ``` $ s3cmd put newfile s3://testbucket -upload: 'newfile' -> 's3://testbucket/newfile' +upload: 'newfile' -> 's3://testbucket/newfile' ``` To copy an object to local system. diff --git a/checkport.go b/checkport.go new file mode 100644 index 000000000..5b8cc8090 --- /dev/null +++ b/checkport.go @@ -0,0 +1,93 @@ +/* + * Minio Cloud Storage, (C) 2016 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 ( + "errors" + "net" + "os" + "syscall" +) + +// Make sure that none of the other processes are listening on the +// specified port on any of the interfaces. +// +// On linux if a process is listening on 127.0.0.1:9000 then Listen() +// on ":9000" fails with the error "port already in use". +// However on Mac OSX Listen() on ":9000" falls back to the IPv6 address. +// This causes confusion on Mac OSX that minio server is not reachable +// on 127.0.0.1 even though minio server is running. So before we start +// the minio server we make sure that the port is free on all the IPs. +func checkPortAvailability(port int) { + isAddrInUse := func(err error) bool { + // Check if the syscall error is EADDRINUSE. + // EADDRINUSE is the system call error if another process is + // already listening at the specified port. + neterr, ok := err.(*net.OpError) + if !ok { + return false + } + osErr, ok := neterr.Err.(*os.SyscallError) + if !ok { + return false + } + sysErr, ok := osErr.Err.(syscall.Errno) + if !ok { + return false + } + if sysErr != syscall.EADDRINUSE { + return false + } + return true + } + ifcs, err := net.Interfaces() + if err != nil { + fatalIf(err, "Unable to list interfaces.") + } + for _, ifc := range ifcs { + addrs, err := ifc.Addrs() + if err != nil { + fatalIf(err, "Unable to list addresses on interface %s.", ifc.Name) + } + for _, addr := range addrs { + ipnet, ok := addr.(*net.IPNet) + if !ok { + errorIf(errors.New(""), "Failed to assert type on (*net.IPNet) interface.") + continue + } + ip := ipnet.IP + network := "tcp4" + if ip.To4() == nil { + network = "tcp6" + } + tcpAddr := net.TCPAddr{IP: ip, Port: port, Zone: ifc.Name} + l, err := net.ListenTCP(network, &tcpAddr) + if err != nil { + if isAddrInUse(err) { + // Fail if port is already in use. + fatalIf(err, "Unable to listen on %s:%.d.", tcpAddr.IP, tcpAddr.Port) + } else { + // Ignore other errors. + continue + } + } + if err = l.Close(); err != nil { + fatalIf(err, "Unable to close listener on %s:%.d.", tcpAddr.IP, tcpAddr.Port) + } + } + } +} diff --git a/server-main.go b/server-main.go index 9c00ecea0..ceb17c8f5 100644 --- a/server-main.go +++ b/server-main.go @@ -17,19 +17,16 @@ package main import ( - "errors" "fmt" "net" "net/http" "os" - "runtime" + "sort" "strconv" "strings" - "syscall" "time" "github.com/minio/cli" - "github.com/minio/mc/pkg/console" ) var serverCmd = cli.Command{ @@ -113,35 +110,44 @@ func configureServer(srvCmdConfig serverCmdConfig) *http.Server { // getListenIPs - gets all the ips to listen on. func getListenIPs(httpServerConf *http.Server) (hosts []string, port string) { host, port, err := net.SplitHostPort(httpServerConf.Addr) - fatalIf(err, "Unable to parse host port.") + fatalIf(err, "Unable to parse host address.", httpServerConf.Addr) - switch { - case host != "": + if host != "" { hosts = append(hosts, host) - default: - addrs, err := net.InterfaceAddrs() - fatalIf(err, "Unable to determine network interface address.") - 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) - } + return hosts, port + } + addrs, err := net.InterfaceAddrs() + fatalIf(err, "Unable to determine network interface address.") + 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) } } } return hosts, port } -// Print listen ips. -func printListenIPs(tls bool, hosts []string, port string) { +// Finalizes the endpoints based on the host list and port. +func finalizeEndpoints(tls bool, apiServer *http.Server) (endPoints []string) { + // Get list of listen ips and port. + hosts, port := getListenIPs(apiServer) + + // Verify current scheme. + scheme := "http" + if tls { + scheme = "https" + } + + // Construct proper endpoints. for _, host := range hosts { - if tls { - console.Printf(" https://%s:%s\n", host, port) - } else { - console.Printf(" http://%s:%s\n", host, port) - } + endPoints = append(endPoints, fmt.Sprintf("%s://%s:%s", scheme, host, port)) } + + // Success. + sort.Strings(endPoints) + return endPoints } // initServerConfig initialize server config. @@ -207,84 +213,25 @@ func checkServerSyntax(c *cli.Context) { // Extract port number from address address should be of the form host:port. func getPort(address string) int { - _, portStr, err := net.SplitHostPort(address) - fatalIf(err, "Unable to parse host port.") + _, portStr, _ := net.SplitHostPort(address) + // If port empty, default to port '80' + if portStr == "" { + portStr = "80" + // if SSL is enabled, choose port as "443" instead. + if isSSL() { + portStr = "443" + } + } + + // Return converted port number. portInt, err := strconv.Atoi(portStr) fatalIf(err, "Invalid port number.") return portInt } -// Make sure that none of the other processes are listening on the -// specified port on any of the interfaces. -// -// On linux if a process is listening on 127.0.0.1:9000 then Listen() -// on ":9000" fails with the error "port already in use". -// However on Mac OSX Listen() on ":9000" falls back to the IPv6 address. -// This causes confusion on Mac OSX that minio server is not reachable -// on 127.0.0.1 even though minio server is running. So before we start -// the minio server we make sure that the port is free on all the IPs. -func checkPortAvailability(port int) { - isAddrInUse := func(err error) bool { - // Check if the syscall error is EADDRINUSE. - // EADDRINUSE is the system call error if another process is - // already listening at the specified port. - neterr, ok := err.(*net.OpError) - if !ok { - return false - } - osErr, ok := neterr.Err.(*os.SyscallError) - if !ok { - return false - } - sysErr, ok := osErr.Err.(syscall.Errno) - if !ok { - return false - } - if sysErr != syscall.EADDRINUSE { - return false - } - return true - } - ifcs, err := net.Interfaces() - if err != nil { - fatalIf(err, "Unable to list interfaces.") - } - for _, ifc := range ifcs { - addrs, err := ifc.Addrs() - if err != nil { - fatalIf(err, "Unable to list addresses on interface %s.", ifc.Name) - } - for _, addr := range addrs { - ipnet, ok := addr.(*net.IPNet) - if !ok { - errorIf(errors.New(""), "Failed to assert type on (*net.IPNet) interface.") - continue - } - ip := ipnet.IP - network := "tcp4" - if ip.To4() == nil { - network = "tcp6" - } - tcpAddr := net.TCPAddr{IP: ip, Port: port, Zone: ifc.Name} - l, err := net.ListenTCP(network, &tcpAddr) - if err != nil { - if isAddrInUse(err) { - // Fail if port is already in use. - fatalIf(err, "Unable to listen on %s:%.d.", tcpAddr.IP, tcpAddr.Port) - } else { - // Ignore other errors. - continue - } - } - if err = l.Close(); err != nil { - fatalIf(err, "Unable to close listener on %s:%.d.", tcpAddr.IP, tcpAddr.Port) - } - } - } -} - +// serverMain handler called for 'minio server' command. func serverMain(c *cli.Context) { - // check 'server' cli arguments. + // Check 'server' cli arguments. checkServerSyntax(c) // Initialize server config. @@ -296,18 +243,8 @@ func serverMain(c *cli.Context) { // Server address. serverAddress := c.String("address") - host, port, _ := net.SplitHostPort(serverAddress) - // If port empty, default to port '80' - if port == "" { - port = "80" - // if SSL is enabled, choose port as "443" instead. - if tls { - port = "443" - } - } - // Check if requested port is available. - checkPortAvailability(getPort(net.JoinHostPort(host, port))) + checkPortAvailability(getPort(serverAddress)) // Disks to be ignored in server init, to skip format healing. ignoredDisks := strings.Split(c.String("ignore-disks"), ",") @@ -322,47 +259,16 @@ func serverMain(c *cli.Context) { ignoredDisks: ignoredDisks, }) - // Credential. - cred := serverConfig.GetCredential() - - // Region. - region := serverConfig.GetRegion() + // Fetch endpoints which we are going to serve from. + endPoints := finalizeEndpoints(tls, apiServer) - // Print credentials and region. - console.Println("\n" + cred.String() + " " + colorMagenta("Region: ") + colorWhite(region)) - - hosts, port := getListenIPs(apiServer) // get listen ips and port. - - console.Println("\nMinio Object Storage:") - // Print api listen ips. - printListenIPs(tls, hosts, port) - - console.Println("\nMinio Browser:") - // Print browser listen ips. - printListenIPs(tls, hosts, port) - - console.Println("\nTo configure Minio Client:") - - // Figure out right endpoint for 'mc'. - endpoint := fmt.Sprintf("http://%s:%s", hosts[0], port) - if tls { - endpoint = fmt.Sprintf("https://%s:%s", hosts[0], port) - } - - // Download 'mc' info. - if runtime.GOOS == "windows" { - console.Printf(" Download 'mc' from https://dl.minio.io/client/mc/release/%s-%s/mc.exe\n", runtime.GOOS, runtime.GOARCH) - console.Printf(" $ mc.exe config host add myminio %s %s %s\n", endpoint, cred.AccessKeyID, cred.SecretAccessKey) - } else { - console.Printf(" $ wget https://dl.minio.io/client/mc/release/%s-%s/mc\n", runtime.GOOS, runtime.GOARCH) - console.Printf(" $ chmod 755 mc\n") - console.Printf(" $ ./mc config host add myminio %s %s %s\n", endpoint, cred.AccessKeyID, cred.SecretAccessKey) - } + // Prints the formatted startup message. + printStartupMessage(endPoints) // Start server. var err error // Configure TLS if certs are available. - if isSSL() { + if tls { err = apiServer.ListenAndServeTLS(mustGetCertFile(), mustGetKeyFile()) } else { // Fallback to http. diff --git a/server-startup-msg.go b/server-startup-msg.go new file mode 100644 index 000000000..54c549b17 --- /dev/null +++ b/server-startup-msg.go @@ -0,0 +1,92 @@ +/* + * Minio Cloud Storage, (C) 2016 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" + "runtime" + "strings" + + "github.com/minio/mc/pkg/console" +) + +// Documentation links, these are part of message printing code. +const ( + mcQuickStartGuide = "https://docs.minio.io/docs/minio-client-quick-start-guide" + goQuickStartGuide = "https://docs.minio.io/docs/golang-client-quickstart-guide" + jsQuickStartGuide = "https://docs.minio.io/docs/javascript-client-quickstart-guide" + javaQuickStartGuide = "https://docs.minio.io/docs/java-client-quickstart-guide" + pyQuickStartGuide = "https://docs.minio.io/docs/python-client-quickstart-guide" +) + +// generates format string depending on the string length and padding. +func getFormatStr(strLen int, padding int) string { + formatStr := fmt.Sprintf("%ds", strLen+padding) + return "%" + formatStr +} + +// Prints the formatted startup message. +func printStartupMessage(endPoints []string) { + printServerCommonMsg(endPoints) + printCLIAccessMsg(endPoints[0]) + printObjectAPIMsg() +} + +// Prints common server startup message. Prints credential, region and browser access. +func printServerCommonMsg(endPoints []string) { + // Get saved credentials. + cred := serverConfig.GetCredential() + + // Get saved region. + region := serverConfig.GetRegion() + + endPointStr := strings.Join(endPoints, " ") + // Colorize the message and print. + console.Println(colorMagenta("\nEndpoint: ") + colorGreen(fmt.Sprintf(getFormatStr(len(endPointStr), 1), endPointStr))) + console.Println(colorMagenta("AccessKey: ") + colorWhite(fmt.Sprintf("%s ", cred.AccessKeyID))) + console.Println(colorMagenta("SecretKey: ") + colorWhite(fmt.Sprintf("%s ", cred.SecretAccessKey))) + console.Println(colorMagenta("Region: ") + colorWhite(fmt.Sprintf(getFormatStr(len(region), 3), region))) + + console.Println(colorMagenta("\nBrowser Access:")) + console.Println(colorGreen(fmt.Sprintf(getFormatStr(len(endPointStr), 3), endPointStr))) +} + +// Prints startup message for command line access. Prints link to our documentation +// and custom platform specific message. +func printCLIAccessMsg(endPoint string) { + // Get saved credentials. + cred := serverConfig.GetCredential() + + // Configure 'mc', following block prints platform specific information for minio client. + console.Println(colorMagenta("\nCommand-line Access: ") + colorWhite(mcQuickStartGuide)) + if runtime.GOOS == "windows" { + mcMessage := fmt.Sprintf("$ mc.exe config host add myminio %s %s %s", endPoint, cred.AccessKeyID, cred.SecretAccessKey) + console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage)) + } else { + mcMessage := fmt.Sprintf("$ ./mc config host add myminio %s %s %s", endPoint, cred.AccessKeyID, cred.SecretAccessKey) + console.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage)) + } +} + +// Prints startup message for Object API acces, prints link to our SDK documentation. +func printObjectAPIMsg() { + console.Println("\nObject API (Amazon S3 compatible):") + console.Println(" Go: " + fmt.Sprintf(getFormatStr(len(goQuickStartGuide), 8), goQuickStartGuide)) + console.Println(" Java: " + fmt.Sprintf(getFormatStr(len(javaQuickStartGuide), 6), javaQuickStartGuide)) + console.Println(" Python: " + fmt.Sprintf(getFormatStr(len(pyQuickStartGuide), 4), pyQuickStartGuide)) + console.Println(" JavaScript: " + jsQuickStartGuide) +} diff --git a/xl-v1-healing.go b/xl-v1-healing.go index d95ac9263..f0deda2c0 100644 --- a/xl-v1-healing.go +++ b/xl-v1-healing.go @@ -44,12 +44,13 @@ func commonTime(modTimes []time.Time) (modTime time.Time) { return modTime } +// Beginning of unix time is treated as sentinel value here. var timeSentinel = time.Unix(0, 0).UTC() -// Boot uuids upto disk count, setting the value to UUID sentinel. +// Boot modTimes up to disk count, setting the value to time sentinel. func bootModtimes(diskCount int) []time.Time { modTimes := make([]time.Time, diskCount) - // Boots up all the uuids. + // Boots up all the modtimes. for i := range modTimes { modTimes[i] = timeSentinel }