Refactoring cli to take commands

This commit is contained in:
Frederick F. Kautz IV 2015-04-22 21:31:29 -07:00
parent 4ed03881ff
commit 0f8626ab1a
2 changed files with 190 additions and 221 deletions

231
main.go
View File

@ -25,14 +25,99 @@ import (
"strings" "strings"
"time" "time"
"errors"
"github.com/minio-io/cli" "github.com/minio-io/cli"
"github.com/minio-io/minio/pkg/api"
"github.com/minio-io/minio/pkg/api/web"
"github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/iodine"
"github.com/minio-io/minio/pkg/server" "github.com/minio-io/minio/pkg/server"
"github.com/minio-io/minio/pkg/server/httpserver"
"github.com/minio-io/minio/pkg/storage/drivers/memory"
"github.com/minio-io/minio/pkg/utils/log" "github.com/minio-io/minio/pkg/utils/log"
"reflect"
) )
var globalDebugFlag = false var globalDebugFlag = false
var commands = []cli.Command{
modeCmd,
}
var modeCommands = []cli.Command{
memoryCmd,
donutCmd,
}
var modeCmd = cli.Command{
Name: "mode",
Subcommands: modeCommands,
}
var memoryCmd = cli.Command{
Name: "memory",
Action: runMemory,
Flags: []cli.Flag{
cli.StringFlag{
Name: "max-memory",
Value: "100M",
Usage: "",
},
},
}
var donutCmd = cli.Command{
Name: "donut",
Action: runDonut,
Flags: []cli.Flag{},
}
type memoryFactory struct {
server.Config
maxMemory int64
}
func (f memoryFactory) getStartServerFunc() startServerFunc {
return func() (chan<- string, <-chan error) {
httpConfig := httpserver.Config{}
httpConfig.Address = f.Address
httpConfig.TLS = f.TLS
httpConfig.CertFile = f.CertFile
httpConfig.KeyFile = f.KeyFile
httpConfig.Websocket = false
_, _, driver := memory.Start(1024 * 1024 * 1024)
ctrl, status, _ := httpserver.Start(api.HTTPHandler(f.Domain, driver), httpConfig)
return ctrl, status
}
}
type webFactory struct {
server.Config
}
func (f webFactory) getStartServerFunc() startServerFunc {
return func() (chan<- string, <-chan error) {
httpConfig := httpserver.Config{}
httpConfig.Address = f.Address
httpConfig.TLS = f.TLS
httpConfig.CertFile = f.CertFile
httpConfig.KeyFile = f.KeyFile
httpConfig.Websocket = false
ctrl, status, _ := httpserver.Start(web.HTTPHandler(), httpConfig)
return ctrl, status
}
}
type donutFactory struct {
server.Config
}
func (f donutFactory) getStartServerFunc() startServerFunc {
return func() (chan<- string, <-chan error) {
return nil, nil
}
}
var flags = []cli.Flag{ var flags = []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "domain,d", Name: "domain,d",
@ -61,11 +146,6 @@ var flags = []cli.Flag{
Value: "", Value: "",
Usage: "key.pem", Usage: "key.pem",
}, },
cli.StringFlag{
Name: "driver-type,t",
Value: "donut",
Usage: "valid entries: file,inmemory,donut",
},
cli.BoolFlag{ cli.BoolFlag{
Name: "debug", Name: "debug",
Usage: "print debug information", Usage: "print debug information",
@ -80,58 +160,126 @@ func init() {
} }
} }
func getDriverType(input string) server.DriverType { type startServerFunc func() (chan<- string, <-chan error)
switch {
case input == "memory":
return server.Memory
case input == "donut":
return server.Donut
case input == "":
return server.Donut
default:
{
log.Fatal("Unknown driver type: '", input, "', Please specify a valid driver.")
return -1 // should never reach here
}
}
}
func runCmd(c *cli.Context) { func runCmd(c *cli.Context) {
driverTypeStr := c.String("driver-type") // default to memory driver, 1GB
domain := c.String("domain") apiServerConfig := getAPIServerConfig(c)
apiaddress := c.String("api-address") memoryDriver := memoryFactory{
webaddress := c.String("web-address") Config: apiServerConfig,
maxMemory: 1024 * 1024 * 1024,
}
apiServer := memoryDriver.getStartServerFunc()
webServer := getWebServerConfigFunc(c)
servers := []startServerFunc{apiServer, webServer}
startMinio(servers)
}
func runMemory(c *cli.Context) {
apiServerConfig := getAPIServerConfig(c)
// TODO max-memory should take human readable values
maxMemory, err := strconv.ParseInt(c.String("max-memory"), 10, 64)
if err != nil {
log.Println("max-memory not a numeric value")
}
memoryDriver := memoryFactory{
Config: apiServerConfig,
maxMemory: maxMemory,
}
apiServer := memoryDriver.getStartServerFunc()
webServer := getWebServerConfigFunc(c)
servers := []startServerFunc{apiServer, webServer}
startMinio(servers)
}
func runDonut(c *cli.Context) {
apiServerConfig := getAPIServerConfig(c)
donutDriver := donutFactory{
Config: apiServerConfig,
}
apiServer := donutDriver.getStartServerFunc()
webServer := getWebServerConfigFunc(c)
servers := []startServerFunc{apiServer, webServer}
startMinio(servers)
}
func getAPIServerConfig(c *cli.Context) server.Config {
certFile := c.String("cert") certFile := c.String("cert")
keyFile := c.String("key") keyFile := c.String("key")
if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") { if (certFile != "" && keyFile == "") || (certFile == "" && keyFile != "") {
log.Fatal("Both certificate and key must be provided to enable https") log.Fatal("Both certificate and key must be provided to enable https")
} }
tls := (certFile != "" && keyFile != "") tls := (certFile != "" && keyFile != "")
driverType := getDriverType(driverTypeStr) return server.Config{
var serverConfigs []server.Config Domain: c.String("domain"),
apiServerConfig := server.Config{ Address: c.String("api-address"),
Domain: domain,
Address: apiaddress,
TLS: tls, TLS: tls,
CertFile: certFile, CertFile: certFile,
KeyFile: keyFile, KeyFile: keyFile,
APIType: server.MinioAPI{
DriverType: driverType,
},
} }
webUIServerConfig := server.Config{ }
Domain: domain,
Address: webaddress, func getWebServerConfigFunc(c *cli.Context) startServerFunc {
config := server.Config{
Domain: c.String("domain"),
Address: c.String("web-address"),
TLS: false, TLS: false,
CertFile: "", CertFile: "",
KeyFile: "", KeyFile: "",
APIType: server.Web{
Websocket: false,
},
} }
serverConfigs = append(serverConfigs, apiServerConfig) webDrivers := webFactory{
serverConfigs = append(serverConfigs, webUIServerConfig) Config: config,
server.Start(serverConfigs) }
return webDrivers.getStartServerFunc()
}
func startMinio(servers []startServerFunc) {
var ctrlChannels []chan<- string
var errChannels []<-chan error
for _, server := range servers {
ctrlChannel, errChannel := server()
ctrlChannels = append(ctrlChannels, ctrlChannel)
errChannels = append(errChannels, errChannel)
}
cases := createSelectCases(errChannels)
for len(cases) > 0 {
chosen, value, recvOk := reflect.Select(cases)
switch recvOk {
case true:
// Status Message Received
switch true {
case value.Interface() != nil:
// For any error received cleanup all existing channels and fail
for _, ch := range ctrlChannels {
close(ch)
}
msg := fmt.Sprintf("%q", value.Interface())
log.Fatal(iodine.New(errors.New(msg), nil))
}
case false:
// Channel closed, remove from list
var aliveStatusChans []<-chan error
for i, ch := range errChannels {
if i != chosen {
aliveStatusChans = append(aliveStatusChans, ch)
}
}
// create new select cases without defunct channel
errChannels = aliveStatusChans
cases = createSelectCases(errChannels)
}
}
}
func createSelectCases(channels []<-chan error) []reflect.SelectCase {
cases := make([]reflect.SelectCase, len(channels))
for i, ch := range channels {
cases[i] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(ch),
}
}
return cases
} }
// Convert bytes to human readable string. Like a 2 MB, 64.2 KB, 52 B // Convert bytes to human readable string. Like a 2 MB, 64.2 KB, 52 B
@ -191,6 +339,7 @@ func main() {
app.Usage = "Minimalist Object Storage" app.Usage = "Minimalist Object Storage"
app.EnableBashCompletion = true app.EnableBashCompletion = true
app.Flags = flags app.Flags = flags
app.Commands = commands
app.Action = runCmd app.Action = runCmd
app.Before = func(c *cli.Context) error { app.Before = func(c *cli.Context) error {
globalDebugFlag = c.GlobalBool("debug") globalDebugFlag = c.GlobalBool("debug")

View File

@ -16,24 +16,6 @@
package server package server
import (
"fmt"
"os/user"
"path"
"reflect"
"errors"
"github.com/minio-io/minio/pkg/api"
"github.com/minio-io/minio/pkg/api/web"
"github.com/minio-io/minio/pkg/iodine"
"github.com/minio-io/minio/pkg/server/httpserver"
"github.com/minio-io/minio/pkg/storage/drivers"
"github.com/minio-io/minio/pkg/storage/drivers/donut"
"github.com/minio-io/minio/pkg/storage/drivers/memory"
"github.com/minio-io/minio/pkg/utils/log"
)
// Config - http server parameters // Config - http server parameters
type Config struct { type Config struct {
Domain string Domain string
@ -43,165 +25,3 @@ type Config struct {
KeyFile string KeyFile string
APIType interface{} APIType interface{}
} }
// MinioAPI - driver type donut, file, memory
type MinioAPI struct {
DriverType DriverType
}
// Web - web related
type Web struct {
Websocket bool // TODO
}
// DriverType - different driver types supported by minio
type DriverType int
// Driver types
const (
Memory DriverType = iota
Donut
)
func getHTTPChannels(configs []Config) (ctrlChans []chan<- string, statusChans []<-chan error) {
// a pair of control channels, we use these primarily to add to the lists above
var ctrlChan chan<- string
var statusChan <-chan error
for _, config := range configs {
switch k := config.APIType.(type) {
case MinioAPI:
{
// configure web server
var driver drivers.Driver
var httpConfig = httpserver.Config{}
httpConfig.Address = config.Address
httpConfig.Websocket = false
httpConfig.TLS = config.TLS
if config.CertFile != "" {
httpConfig.CertFile = config.CertFile
}
if config.KeyFile != "" {
httpConfig.KeyFile = config.KeyFile
}
ctrlChans, statusChans, driver = getDriverChannels(k.DriverType)
// start minio api in a web server, pass driver driver into it
ctrlChan, statusChan, _ = httpserver.Start(api.HTTPHandler(config.Domain, driver), httpConfig)
ctrlChans = append(ctrlChans, ctrlChan)
statusChans = append(statusChans, statusChan)
}
case Web:
{
var httpConfig = httpserver.Config{}
httpConfig.Address = config.Address
httpConfig.TLS = config.TLS
httpConfig.CertFile = config.CertFile
httpConfig.KeyFile = config.KeyFile
httpConfig.Websocket = k.Websocket
ctrlChan, statusChan, _ = httpserver.Start(web.HTTPHandler(), httpConfig)
ctrlChans = append(ctrlChans, ctrlChan)
statusChans = append(statusChans, statusChan)
}
default:
{
err := iodine.New(errors.New("Invalid API type"), nil)
log.Fatal(err)
}
}
}
return
}
func getDriverChannels(driverType DriverType) (ctrlChans []chan<- string, statusChans []<-chan error, driver drivers.Driver) {
// a pair of control channels, we use these primarily to add to the lists above
var ctrlChan chan<- string
var statusChan <-chan error
// instantiate driver
// preconditions:
// - driver type specified
// - any configuration for driver is populated
// postconditions:
// - driver driver is initialized
// - ctrlChans has channel to communicate to driver
// - statusChans has channel for messages coming from driver
switch {
case driverType == Memory:
{
ctrlChan, statusChan, driver = memory.Start(1024 * 1024 * 1024)
ctrlChans = append(ctrlChans, ctrlChan)
statusChans = append(statusChans, statusChan)
}
case driverType == Donut:
{
u, err := user.Current()
if err != nil {
log.Error.Println(iodine.New(err, nil))
return nil, nil, nil
}
root := path.Join(u.HomeDir, "minio-storage", "donut")
ctrlChan, statusChan, driver = donut.Start(root)
ctrlChans = append(ctrlChans, ctrlChan)
statusChans = append(statusChans, statusChan)
}
default: // should never happen
{
log.Fatal(iodine.New(errors.New("No driver found"), nil))
}
}
return
}
// Start - create channels
func Start(configs []Config) {
// reflected looping is necessary to remove dead channels from loop and not flood switch
ctrlChans, statusChans := getHTTPChannels(configs)
cases := createSelectCases(statusChans)
for len(cases) > 0 {
chosen, value, recvOk := reflect.Select(cases)
switch recvOk {
case true:
// Status Message Received
switch true {
case value.Interface() != nil:
// For any error received cleanup all existing channels and fail
for _, ch := range ctrlChans {
close(ch)
}
msg := fmt.Sprintf("%q", value.Interface())
log.Fatal(iodine.New(errors.New(msg), nil))
}
case false:
// Channel closed, remove from list
var aliveStatusChans []<-chan error
for i, ch := range statusChans {
if i != chosen {
aliveStatusChans = append(aliveStatusChans, ch)
}
}
// create new select cases without defunct channel
statusChans = aliveStatusChans
cases = createSelectCases(statusChans)
}
}
}
// creates select cases for reflect to switch over dynamically
// this is necessary in order to remove dead channels and not flood
// the loop with closed channel errors
func createSelectCases(channels []<-chan error) []reflect.SelectCase {
cases := make([]reflect.SelectCase, len(channels))
for i, ch := range channels {
cases[i] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(ch),
}
}
return cases
}