control: Implement service command 'stop,restart,status'. (#2883)
- stop - stops all the servers. - restart - restart all the servers. - status - prints status of storage info about the cluster.master
parent
57f75b1d9b
commit
3cfb23750a
@ -0,0 +1,96 @@ |
||||
/* |
||||
* 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 cmd |
||||
|
||||
import ( |
||||
"net/url" |
||||
"path" |
||||
|
||||
"github.com/minio/cli" |
||||
"github.com/minio/mc/pkg/console" |
||||
) |
||||
|
||||
var serviceCmd = cli.Command{ |
||||
Name: "service", |
||||
Usage: "Service command line to manage Minio server.", |
||||
Action: serviceControl, |
||||
Flags: globalFlags, |
||||
CustomHelpTemplate: `NAME: |
||||
minio control {{.Name}} - {{.Usage}} |
||||
|
||||
USAGE: |
||||
minio control {{.Name}} [status|restart|stop] URL |
||||
|
||||
FLAGS: |
||||
{{range .Flags}}{{.}} |
||||
{{end}} |
||||
EXAMPLES: |
||||
1. Prints current status information of the cluster. |
||||
$ minio control service status http://10.1.10.92:9000/
|
||||
|
||||
2. Restarts the url and all the servers in the cluster. |
||||
$ minio control service restart http://localhost:9000/
|
||||
|
||||
3. Shuts down the url and all the servers in the cluster. |
||||
$ minio control service stop http://localhost:9000/
|
||||
`, |
||||
} |
||||
|
||||
// "minio control service" entry point.
|
||||
func serviceControl(c *cli.Context) { |
||||
if !c.Args().Present() && len(c.Args()) != 2 { |
||||
cli.ShowCommandHelpAndExit(c, "service", 1) |
||||
} |
||||
|
||||
var signal serviceSignal |
||||
switch c.Args().Get(0) { |
||||
case "status": |
||||
signal = serviceStatus |
||||
case "restart": |
||||
signal = serviceRestart |
||||
case "stop": |
||||
signal = serviceStop |
||||
default: |
||||
fatalIf(errInvalidArgument, "Unsupported signalling requested %s", c.Args().Get(0)) |
||||
} |
||||
|
||||
parsedURL, err := url.Parse(c.Args().Get(1)) |
||||
fatalIf(err, "Unable to parse URL %s", c.Args().Get(1)) |
||||
|
||||
authCfg := &authConfig{ |
||||
accessKey: serverConfig.GetCredential().AccessKeyID, |
||||
secretKey: serverConfig.GetCredential().SecretAccessKey, |
||||
secureConn: parsedURL.Scheme == "https", |
||||
address: parsedURL.Host, |
||||
path: path.Join(reservedBucket, controlPath), |
||||
loginMethod: "Controller.LoginHandler", |
||||
} |
||||
client := newAuthClient(authCfg) |
||||
|
||||
args := &ServiceArgs{ |
||||
Signal: signal, |
||||
// This is necessary so that the remotes,
|
||||
// don't end up sending requests back and forth.
|
||||
Remote: true, |
||||
} |
||||
reply := &ServiceReply{} |
||||
err = client.Call("Controller.ServiceHandler", args, reply) |
||||
fatalIf(err, "Service command %s failed for %s", c.Args().Get(0), parsedURL.Host) |
||||
if signal == serviceStatus { |
||||
console.Println(getStorageInfoMsg(reply.StorageInfo)) |
||||
} |
||||
} |
@ -1,79 +0,0 @@ |
||||
/* |
||||
* 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 cmd |
||||
|
||||
import ( |
||||
"net/url" |
||||
"path" |
||||
|
||||
"github.com/minio/cli" |
||||
) |
||||
|
||||
var shutdownFlags = []cli.Flag{ |
||||
cli.BoolFlag{ |
||||
Name: "restart", |
||||
Usage: "Restart the server.", |
||||
}, |
||||
} |
||||
|
||||
var shutdownCmd = cli.Command{ |
||||
Name: "shutdown", |
||||
Usage: "Shutdown or restart the server.", |
||||
Action: shutdownControl, |
||||
Flags: append(shutdownFlags, globalFlags...), |
||||
CustomHelpTemplate: `NAME: |
||||
minio control {{.Name}} - {{.Usage}} |
||||
|
||||
USAGE: |
||||
minio control {{.Name}} http://localhost:9000/
|
||||
|
||||
FLAGS: |
||||
{{range .Flags}}{{.}} |
||||
{{end}} |
||||
|
||||
EXAMPLES: |
||||
1. Shutdown the server: |
||||
$ minio control shutdown http://localhost:9000/
|
||||
|
||||
2. Reboot the server: |
||||
$ minio control shutdown --restart http://localhost:9000/
|
||||
`, |
||||
} |
||||
|
||||
// "minio control shutdown" entry point.
|
||||
func shutdownControl(c *cli.Context) { |
||||
if len(c.Args()) != 1 { |
||||
cli.ShowCommandHelpAndExit(c, "shutdown", 1) |
||||
} |
||||
|
||||
parsedURL, err := url.Parse(c.Args()[0]) |
||||
fatalIf(err, "Unable to parse URL.") |
||||
|
||||
authCfg := &authConfig{ |
||||
accessKey: serverConfig.GetCredential().AccessKeyID, |
||||
secretKey: serverConfig.GetCredential().SecretAccessKey, |
||||
secureConn: parsedURL.Scheme == "https", |
||||
address: parsedURL.Host, |
||||
path: path.Join(reservedBucket, controlPath), |
||||
loginMethod: "Controller.LoginHandler", |
||||
} |
||||
client := newAuthClient(authCfg) |
||||
|
||||
args := &ShutdownArgs{Restart: c.Bool("restart")} |
||||
err = client.Call("Controller.ShutdownHandler", args, &GenericReply{}) |
||||
errorIf(err, "Shutting down Minio server at %s failed.", parsedURL.Host) |
||||
} |
@ -0,0 +1,91 @@ |
||||
/* |
||||
* 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 cmd |
||||
|
||||
import "testing" |
||||
|
||||
// Tests initialization of remote controller clients.
|
||||
func TestInitRemoteControllerClients(t *testing.T) { |
||||
rootPath, err := newTestConfig("us-east-1") |
||||
if err != nil { |
||||
t.Fatal("Unable to initialize config", err) |
||||
} |
||||
defer removeAll(rootPath) |
||||
|
||||
testCases := []struct { |
||||
srvCmdConfig serverCmdConfig |
||||
totalClients int |
||||
}{ |
||||
// Test - 1 no allocation if server config is not distributed XL.
|
||||
{ |
||||
srvCmdConfig: serverCmdConfig{ |
||||
isDistXL: false, |
||||
}, |
||||
totalClients: 0, |
||||
}, |
||||
// Test - 2 two clients allocated with 4 disks with 2 disks on same node each.
|
||||
{ |
||||
srvCmdConfig: serverCmdConfig{ |
||||
isDistXL: true, |
||||
disks: []string{ |
||||
"10.1.10.1:/mnt/disk1", |
||||
"10.1.10.1:/mnt/disk2", |
||||
"10.1.10.2:/mnt/disk3", |
||||
"10.1.10.2:/mnt/disk4", |
||||
}, |
||||
}, |
||||
totalClients: 2, |
||||
}, |
||||
// Test - 3 4 clients allocated with 4 disks with 1 disk on each node.
|
||||
{ |
||||
srvCmdConfig: serverCmdConfig{ |
||||
isDistXL: true, |
||||
disks: []string{ |
||||
"10.1.10.1:/mnt/disk1", |
||||
"10.1.10.2:/mnt/disk2", |
||||
"10.1.10.3:/mnt/disk3", |
||||
"10.1.10.4:/mnt/disk4", |
||||
}, |
||||
}, |
||||
totalClients: 4, |
||||
}, |
||||
// Test - 4 2 clients allocated with 4 disks with 1 disk ignored.
|
||||
{ |
||||
srvCmdConfig: serverCmdConfig{ |
||||
isDistXL: true, |
||||
disks: []string{ |
||||
"10.1.10.1:/mnt/disk1", |
||||
"10.1.10.2:/mnt/disk2", |
||||
"10.1.10.3:/mnt/disk3", |
||||
"10.1.10.4:/mnt/disk4", |
||||
}, |
||||
ignoredDisks: []string{ |
||||
"10.1.10.1:/mnt/disk1", |
||||
}, |
||||
}, |
||||
totalClients: 3, |
||||
}, |
||||
} |
||||
|
||||
// Evaluate and validate all test cases.
|
||||
for i, testCase := range testCases { |
||||
rclients := initRemoteControllerClients(testCase.srvCmdConfig) |
||||
if len(rclients) != testCase.totalClients { |
||||
t.Errorf("Test %d, Expected %d, got %d RPC clients.", i+1, testCase.totalClients, len(rclients)) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,121 @@ |
||||
/* |
||||
* 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 cmd |
||||
|
||||
import ( |
||||
"os" |
||||
"os/exec" |
||||
"syscall" |
||||
) |
||||
|
||||
// Represents a type of an exit func which will be invoked upon service signal.
|
||||
type onExitFunc func(err error) |
||||
|
||||
// Represents a type for all the the callback functions invoked upon service signal.
|
||||
type cleanupOnExitFunc func() error |
||||
|
||||
// Type of service signals currently supported.
|
||||
type serviceSignal int |
||||
|
||||
const ( |
||||
serviceStatus = iota // Gets status about the service.
|
||||
serviceRestart // Restarts the service.
|
||||
serviceStop // Stops the server.
|
||||
// Add new service requests here.
|
||||
) |
||||
|
||||
// Global service signal channel.
|
||||
var globalServiceSignalCh chan serviceSignal |
||||
|
||||
// Global service done channel.
|
||||
var globalServiceDoneCh chan struct{} |
||||
|
||||
// Initialize service mutex once.
|
||||
func init() { |
||||
globalServiceDoneCh = make(chan struct{}, 1) |
||||
globalServiceSignalCh = make(chan serviceSignal, 1) |
||||
} |
||||
|
||||
// restartProcess starts a new process passing it the active fd's. It
|
||||
// doesn't fork, but starts a new process using the same environment and
|
||||
// arguments as when it was originally started. This allows for a newly
|
||||
// deployed binary to be started. It returns the pid of the newly started
|
||||
// process when successful.
|
||||
func restartProcess() error { |
||||
// Use the original binary location. This works with symlinks such that if
|
||||
// the file it points to has been changed we will use the updated symlink.
|
||||
argv0, err := exec.LookPath(os.Args[0]) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Pass on the environment and replace the old count key with the new one.
|
||||
cmd := exec.Command(argv0, os.Args[1:]...) |
||||
cmd.Stdout = os.Stdout |
||||
cmd.Stderr = os.Stderr |
||||
return cmd.Start() |
||||
} |
||||
|
||||
// Handles all serviceSignal and execute service functions.
|
||||
func (m *ServerMux) handleServiceSignals() error { |
||||
// Custom exit function
|
||||
runExitFn := func(err error) { |
||||
// If global profiler is set stop before we exit.
|
||||
if globalProfiler != nil { |
||||
globalProfiler.Stop() |
||||
} |
||||
|
||||
// Call user supplied user exit function
|
||||
fatalIf(err, "Unable to gracefully complete service operation.") |
||||
|
||||
// We are usually done here, close global service done channel.
|
||||
globalServiceDoneCh <- struct{}{} |
||||
} |
||||
|
||||
// Start listening on service signal. Monitor signals.
|
||||
trapCh := signalTrap(os.Interrupt, syscall.SIGTERM) |
||||
for { |
||||
select { |
||||
case <-trapCh: |
||||
// Initiate graceful stop.
|
||||
globalServiceSignalCh <- serviceStop |
||||
case signal := <-globalServiceSignalCh: |
||||
switch signal { |
||||
case serviceStatus: |
||||
/// We don't do anything for this.
|
||||
case serviceRestart: |
||||
if err := m.Close(); err != nil { |
||||
errorIf(err, "Unable to close server gracefully") |
||||
} |
||||
if err := restartProcess(); err != nil { |
||||
errorIf(err, "Unable to restart the server.") |
||||
} |
||||
runExitFn(nil) |
||||
case serviceStop: |
||||
if err := m.Close(); err != nil { |
||||
errorIf(err, "Unable to close server gracefully") |
||||
} |
||||
objAPI := newObjectLayerFn() |
||||
if objAPI == nil { |
||||
// Server not initialized yet, exit happily.
|
||||
runExitFn(nil) |
||||
} |
||||
runExitFn(objAPI.Shutdown()) |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue