add basic command framework and apiserver
This commit is contained in:
197
citadm.go
197
citadm.go
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2015 The GoStor Authors All rights reserved.
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -18,183 +18,46 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gostor/gotgt/pkg/version"
|
"github.com/docker/go-connections/sockets"
|
||||||
|
"github.com/gostor/gotgt/cmd"
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AdminMode int
|
|
||||||
type AdminOperation int
|
|
||||||
|
|
||||||
const (
|
|
||||||
OP_NEW = iota
|
|
||||||
OP_DELETE
|
|
||||||
OP_SHOW
|
|
||||||
OP_BIND
|
|
||||||
OP_UNBIND
|
|
||||||
OP_UPDATE
|
|
||||||
OP_STATS
|
|
||||||
OP_START
|
|
||||||
OP_STOP
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
MODE_SYSTEM = iota
|
|
||||||
MODE_TARGET
|
|
||||||
MODE_DEVICE
|
|
||||||
MODE_PORTAL
|
|
||||||
MODE_LLD
|
|
||||||
|
|
||||||
MODE_SESSION
|
|
||||||
MODE_CONNECTION
|
|
||||||
MODE_ACCOUNT
|
|
||||||
)
|
|
||||||
|
|
||||||
type AdminRequest struct {
|
|
||||||
Mode AdminMode
|
|
||||||
Operation AdminOperation
|
|
||||||
LLD string
|
|
||||||
Length uint32
|
|
||||||
TID int32
|
|
||||||
SID uint64
|
|
||||||
Lun uint64
|
|
||||||
Cid uint64
|
|
||||||
host_no uint32
|
|
||||||
device_type uint32
|
|
||||||
ac_dir uint32
|
|
||||||
pack uint32
|
|
||||||
force uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// define options
|
host := "tcp://127.0.0.1:23457"
|
||||||
//var req AdminRequest
|
httpClient, err := newHTTPClient(host)
|
||||||
flDebug := flag.Bool("debug", false, "Debug mode")
|
if err != nil {
|
||||||
flHelp := flag.Bool("help", false, "Help Message")
|
fmt.Fprintf(os.Stderr, "%v", err)
|
||||||
flVersion := flag.Bool("version", false, "Version message")
|
os.Exit(1)
|
||||||
flLLD := flag.String("lld", "", "Low level device")
|
|
||||||
flOperation := flag.String("op", "", "Operation")
|
|
||||||
flMode := flag.String("mode", "", "")
|
|
||||||
flTID := flag.String("tid", "", "")
|
|
||||||
flSID := flag.String("sid", "", "")
|
|
||||||
flCID := flag.String("cid", "", "")
|
|
||||||
flLUN := flag.String("lun", "", "")
|
|
||||||
flName := flag.String("name", "", "")
|
|
||||||
flValue := flag.String("value", "", "")
|
|
||||||
flBS := flag.String("backing-store", "", "")
|
|
||||||
flTarget := flag.String("target", "", "")
|
|
||||||
flInitiatorName := flag.String("initiator-name", "", "")
|
|
||||||
flInitiatorAddress := flag.String("initiator-address", "", "")
|
|
||||||
flUser := flag.String("user", "", "")
|
|
||||||
flPassword := flag.String("password", "", "")
|
|
||||||
flHost := flag.String("host", "", "")
|
|
||||||
flForce := flag.Bool("force", false, "")
|
|
||||||
flDeviceType := flag.String("devicetype", "", "")
|
|
||||||
|
|
||||||
flag.Usage = func() { usage(0) }
|
|
||||||
flag.Parse()
|
|
||||||
if *flHelp {
|
|
||||||
usage(0)
|
|
||||||
}
|
|
||||||
if *flVersion {
|
|
||||||
showVersion()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = flDebug
|
cli, err := client.NewClient(host, "0.1", httpClient, nil)
|
||||||
_ = flLLD
|
if err != nil {
|
||||||
_ = flOperation
|
fmt.Fprintf(os.Stderr, "%v", err)
|
||||||
_ = flMode
|
os.Exit(1)
|
||||||
_ = flTID
|
}
|
||||||
_ = flSID
|
if err := cmd.NewCommand(cli).Execute(); err != nil {
|
||||||
_ = flCID
|
fmt.Println(err)
|
||||||
_ = flLUN
|
os.Exit(-1)
|
||||||
_ = flName
|
}
|
||||||
_ = flValue
|
|
||||||
_ = flBS
|
|
||||||
_ = flTarget
|
|
||||||
_ = flInitiatorName
|
|
||||||
_ = flInitiatorAddress
|
|
||||||
_ = flUser
|
|
||||||
_ = flPassword
|
|
||||||
_ = flHost
|
|
||||||
_ = flForce
|
|
||||||
_ = flDeviceType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func usage(status int) {
|
func newHTTPClient(host string) (*http.Client, error) {
|
||||||
if status != 0 {
|
tr := &http.Transport{
|
||||||
fmt.Fprintf(os.Stderr, "Try `%s --help' for more information.\n", os.Args[0])
|
TLSClientConfig: nil,
|
||||||
os.Exit(status)
|
}
|
||||||
|
proto, addr, _, err := client.ParseHost(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var helpMessage = `Linux SCSI Target administration utility, version %s
|
sockets.ConfigureTransport(tr, proto, addr)
|
||||||
Usage: %s [OPTIONS]
|
|
||||||
|
|
||||||
Application Options:
|
return &http.Client{
|
||||||
--lld <driver> --mode target --op new --tid <id> --targetname <name>
|
Transport: tr,
|
||||||
add a new target with <id> and <name>. <id> must not be zero.
|
}, nil
|
||||||
--lld <driver> --mode target --op delete [--force] --tid <id>
|
|
||||||
delete the specific target with <id>.
|
|
||||||
With force option, the specific target is deleted
|
|
||||||
even if there is an activity.
|
|
||||||
--lld <driver> --mode target --op show
|
|
||||||
show all the targets.
|
|
||||||
--lld <driver> --mode target --op show --tid <id>
|
|
||||||
show the specific target's parameters.
|
|
||||||
--lld <driver> --mode target --op update --tid <id> --name <param> --value <value>
|
|
||||||
change the target parameters of the target with <id>.
|
|
||||||
--lld <driver> --mode target --op bind --tid <id> --initiator-address <address>
|
|
||||||
--lld <driver> --mode target --op bind --tid <id> --initiator-name <name>
|
|
||||||
enable the target to accept the specific initiators.
|
|
||||||
--lld <driver> --mode target --op unbind --tid <id> --initiator-address <address>
|
|
||||||
--lld <driver> --mode target --op unbind --tid <id> --initiator-name <name>
|
|
||||||
disable the specific permitted initiators.
|
|
||||||
--lld <driver> --mode logicalunit --op new --tid <id> --lun <lun>
|
|
||||||
--backing-store <path> --bstype <type> --bsopts <bs options> --bsoflags <options>
|
|
||||||
add a new logical unit with <lun> to the specific
|
|
||||||
target with <id>. The logical unit is offered
|
|
||||||
to the initiators. <path> must be block device files
|
|
||||||
(including LVM and RAID devices) or regular files.
|
|
||||||
bstype option is optional.
|
|
||||||
bsopts are specific to the bstype.
|
|
||||||
bsoflags supported options are sync and direct
|
|
||||||
(sync:direct for both).
|
|
||||||
--lld <driver> --mode logicalunit --op delete --tid <id> --lun <lun>
|
|
||||||
delete the specific logical unit with <lun> that
|
|
||||||
the target with <id> has.
|
|
||||||
--lld <driver> --mode account --op new --user <name> --password <pass>
|
|
||||||
add a new account with <name> and <pass>.
|
|
||||||
--lld <driver> --mode account --op delete --user <name>
|
|
||||||
delete the specific account having <name>.
|
|
||||||
--lld <driver> --mode account --op bind --tid <id> --user <name> [--outgoing]
|
|
||||||
add the specific account having <name> to
|
|
||||||
the specific target with <id>.
|
|
||||||
<user> could be <IncomingUser> or <OutgoingUser>.
|
|
||||||
If you use --outgoing option, the account will
|
|
||||||
be added as an outgoing account.
|
|
||||||
--lld <driver> --mode account --op unbind --tid <id> --user <name> [--outgoing]
|
|
||||||
delete the specific account having <name> from specific
|
|
||||||
target. The --outgoing option must be added if you
|
|
||||||
delete an outgoing account.
|
|
||||||
--lld <driver> --mode lld --op start
|
|
||||||
Start the specified lld without restarting the tgtd process.
|
|
||||||
--control-port <port> use control port <port>
|
|
||||||
|
|
||||||
Help Options:
|
|
||||||
--help
|
|
||||||
display this help and exit
|
|
||||||
|
|
||||||
Report bugs via <https://github.com/gostor/gotgt/issues>.
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
fmt.Printf(helpMessage, version.VERSION, os.Args[0])
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showVersion() {
|
|
||||||
fmt.Printf("%s\n", version.VERSION)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|||||||
136
citd.go
136
citd.go
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2015 The GoStor Authors All rights reserved.
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -18,70 +18,104 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/gostor/gotgt/pkg/api"
|
"github.com/gostor/gotgt/pkg/apiserver"
|
||||||
|
"github.com/gostor/gotgt/pkg/port"
|
||||||
|
_ "github.com/gostor/gotgt/pkg/port/iscsit"
|
||||||
"github.com/gostor/gotgt/pkg/scsi"
|
"github.com/gostor/gotgt/pkg/scsi"
|
||||||
|
_ "github.com/gostor/gotgt/pkg/scsi/backingstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
l, err := net.Listen("tcp", ":3260")
|
|
||||||
|
flHelp := flag.Bool("help", false, "Print help message for Hyperd daemon")
|
||||||
|
flHost := flag.String("host", "tcp://127.0.0.1:23457", "Host for SCSI target daemon")
|
||||||
|
flDriver := flag.String("driver", "iscsi", "SCSI low level driver")
|
||||||
|
flag.Usage = func() { *flHelp = true }
|
||||||
|
flag.Parse()
|
||||||
|
flag.Set("logtostderr", "true")
|
||||||
|
if *flHelp == true {
|
||||||
|
fmt.Println(`Usage:
|
||||||
|
xxxd [OPTIONS]
|
||||||
|
|
||||||
|
Application Options:
|
||||||
|
--host="" Host for SCSI target daemon
|
||||||
|
--driver=iscsi SCSI low level driver
|
||||||
|
|
||||||
|
Help Options:
|
||||||
|
-h, --help Show this help message
|
||||||
|
`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scsi := scsi.NewSCSITargetService()
|
||||||
|
t, err := port.NewTargetService(*flDriver, scsi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error(err)
|
glog.Error(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer l.Close()
|
iscsit := reflect.ValueOf(t)
|
||||||
t, err := scsi.NewTarget(0, "iscsi", "test-iscsi-target")
|
// create a new target
|
||||||
if err != nil {
|
create := iscsit.MethodByName("NewTarget")
|
||||||
glog.Error(err)
|
create.Call([]reflect.Value{reflect.ValueOf("test-iscsi-target")})
|
||||||
os.Exit(1)
|
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
// run a service
|
||||||
|
run := iscsit.MethodByName("Run")
|
||||||
|
go run.Call([]reflect.Value{})
|
||||||
|
|
||||||
|
serverConfig := &apiserver.Config{
|
||||||
|
Addrs: []apiserver.Addr{},
|
||||||
}
|
}
|
||||||
conns := make(map[string]net.Conn)
|
//hosts := []string{"unix:///var/run/gotgt.sock"}
|
||||||
|
hosts := []string{}
|
||||||
for {
|
if *flHost != "" {
|
||||||
glog.Info("Listening ...")
|
hosts = append(hosts, *flHost)
|
||||||
conn, err := l.Accept()
|
|
||||||
checkError(err, "Accept")
|
|
||||||
glog.Info("Accepting ...")
|
|
||||||
conns[conn.RemoteAddr().String()] = conn
|
|
||||||
// start a new thread to do with this command
|
|
||||||
go Handler(conn, t)
|
|
||||||
}
|
}
|
||||||
}
|
for _, protoAddr := range hosts {
|
||||||
|
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||||
func checkError(err error, info string) (res bool) {
|
if len(protoAddrParts) != 2 {
|
||||||
|
glog.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)
|
||||||
if err != nil {
|
return
|
||||||
glog.Error(info + " " + err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func Handler(conn net.Conn, tgt *api.SCSITarget) {
|
|
||||||
|
|
||||||
glog.Infof("connection is connected from %s...\n", conn.RemoteAddr().String())
|
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
|
||||||
for {
|
|
||||||
lenght, err := conn.Read(buf)
|
|
||||||
if checkError(err, "Connection") == false {
|
|
||||||
conn.Close()
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if lenght > 0 {
|
serverConfig.Addrs = append(serverConfig.Addrs, apiserver.Addr{Proto: protoAddrParts[0], Addr: protoAddrParts[1]})
|
||||||
buf[lenght] = 0
|
|
||||||
}
|
|
||||||
v := reflect.ValueOf(tgt.SCSITargetDriver)
|
|
||||||
iscsit := v.MethodByName("ProcessCommand")
|
|
||||||
in := make([]reflect.Value, 1)
|
|
||||||
in[0] = reflect.ValueOf(buf[0:lenght])
|
|
||||||
res := iscsit.Call(in)[0]
|
|
||||||
b := res.Bytes()
|
|
||||||
glog.Infof("%s\n", string(b))
|
|
||||||
conn.Write(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s, err := apiserver.New(serverConfig)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.InitRouters()
|
||||||
|
// The serve API routine never exits unless an error occurs
|
||||||
|
// We need to start it as a goroutine and wait on it so
|
||||||
|
// daemon doesn't exit
|
||||||
|
serveAPIWait := make(chan error)
|
||||||
|
go s.Wait(serveAPIWait)
|
||||||
|
|
||||||
|
stopAll := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(stopAll, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
// Daemon is fully initialized and handling API traffic
|
||||||
|
// Wait for serve API job to complete
|
||||||
|
select {
|
||||||
|
case errAPI := <-serveAPIWait:
|
||||||
|
// If we have an error here it is unique to API (as daemonErr would have
|
||||||
|
// exited the daemon process above)
|
||||||
|
if errAPI != nil {
|
||||||
|
glog.Warningf("Shutting down due to ServeAPI error: %v", errAPI)
|
||||||
|
}
|
||||||
|
case <-stopAll:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
43
cmd/cmd.go
Normal file
43
cmd/cmd.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCommand(cli *client.Client) *cobra.Command {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "citadm",
|
||||||
|
Short: "Gotgt is a very fast and stable SCSI target framework",
|
||||||
|
Long: ``,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// Do Stuff Here
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.AddCommand(
|
||||||
|
newCreateCommand(cli),
|
||||||
|
newRemoveCommand(cli),
|
||||||
|
newListCommand(cli),
|
||||||
|
newVersionCommand(cli),
|
||||||
|
)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoArgs validate args and returns an error if there are any args
|
||||||
|
func NoArgs(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.HasSubCommands() {
|
||||||
|
return fmt.Errorf("\n" + strings.TrimRight(cmd.UsageString(), "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf(
|
||||||
|
"\"%s\" accepts no argument(s).\n",
|
||||||
|
cmd.CommandPath(),
|
||||||
|
)
|
||||||
|
}
|
||||||
95
cmd/create.go
Normal file
95
cmd/create.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api"
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCreateCommand(cli *client.Client) *cobra.Command {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Short: "Create a new object",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println(cmd.UsageString())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.AddCommand(
|
||||||
|
newCreateTargetCmd(cli),
|
||||||
|
newCreateLuCmd(cli),
|
||||||
|
)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateTargetCmd(cli *client.Client) *cobra.Command {
|
||||||
|
opts := api.TargetCreateRequest{}
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "target",
|
||||||
|
Short: "Create a new target into gotgt",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"\"%s\" accepts no argument(s).\n",
|
||||||
|
cmd.CommandPath(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return createTarget(cli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&opts.Name, "name", "", "Specify target name")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateLuCmd(cli *client.Client) *cobra.Command {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "lu",
|
||||||
|
Short: "Create a new Lu into gotgt",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := NoArgs(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return createLu(cli)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags := cmd.Flags()
|
||||||
|
_ = flags
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTarget(cli *client.Client, opts api.TargetCreateRequest) error {
|
||||||
|
tgt, err := cli.TargetCreate(context.Background(), opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("Target %s successfully created\n", tgt.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createLu(cli *client.Client) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
108
cmd/list.go
Normal file
108
cmd/list.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api"
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newListCommand(cli *client.Client) *cobra.Command {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List object(s)",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println(cmd.UsageString())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.AddCommand(
|
||||||
|
newListTargetCmd(cli),
|
||||||
|
newListLuCmd(cli),
|
||||||
|
)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListTargetCmd(cli *client.Client) *cobra.Command {
|
||||||
|
opts := api.TargetListOptions{}
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "target",
|
||||||
|
Short: "List target(s) of gotgt",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) > 0 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"\"%s\" accepts no argument(s).\n",
|
||||||
|
cmd.CommandPath(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return listTarget(cli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&opts.Name, "name", "", "Specify target name")
|
||||||
|
flags.BoolVar(&opts.Verbose, "verbose", false, "Show more details")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListLuCmd(cli *client.Client) *cobra.Command {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "lu",
|
||||||
|
Short: "List Lu(s) of gotgt",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := NoArgs(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return listLu(cli)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags := cmd.Flags()
|
||||||
|
_ = flags
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func listTarget(cli *client.Client, opts api.TargetListOptions) error {
|
||||||
|
results, err := cli.TargetList(context.Background(), opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
||||||
|
fmt.Fprintln(w, "TARGET NAME\tSTATE\tSESSIONS")
|
||||||
|
for _, tgt := range results {
|
||||||
|
status := "online"
|
||||||
|
if tgt.State == api.TargetReady {
|
||||||
|
status = "ready"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s\t%s\t%d\n", tgt.Name, status, len(tgt.ITNexus))
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listLu(cli *client.Client) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
87
cmd/remove.go
Normal file
87
cmd/remove.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api"
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRemoveCommand(cli *client.Client) *cobra.Command {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "rm",
|
||||||
|
Short: "remove a new object",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println(cmd.UsageString())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.AddCommand(
|
||||||
|
newRemoveTargetCmd(cli),
|
||||||
|
newRemoveLuCmd(cli),
|
||||||
|
)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoveTargetCmd(cli *client.Client) *cobra.Command {
|
||||||
|
opts := api.TargetRemoveOptions{}
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "target",
|
||||||
|
Short: "Remove a new target into gotgt",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return removeTarget(cli, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&opts.Name, "name", "", "Specify target name")
|
||||||
|
flags.BoolVar(&opts.Force, "force", false, "Specify target name")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoveLuCmd(cli *client.Client) *cobra.Command {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "lu",
|
||||||
|
Short: "Remove a new Lu into gotgt",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return removeLu(cli)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags := cmd.Flags()
|
||||||
|
_ = flags
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTarget(cli *client.Client, opts api.TargetRemoveOptions) error {
|
||||||
|
err := cli.TargetRemove(context.Background(), opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("Target %s successfully removed\n", opts.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeLu(cli *client.Client) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
21
cmd/version.go
Normal file
21
cmd/version.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client"
|
||||||
|
"github.com/gostor/gotgt/pkg/version"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newVersionCommand(cli *client.Client) *cobra.Command {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Print the version number of gotgt",
|
||||||
|
Long: `All software has versions. This is Gotgt 's`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Printf("Gotgt %s -- HEAD\n", version.VERSION)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
106
pkg/api/client/client.go
Normal file
106
pkg/api/client/client.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
// proto holds the client protocol i.e. unix.
|
||||||
|
proto string
|
||||||
|
// addr holds the client address.
|
||||||
|
addr string
|
||||||
|
// basePath holds the path to prepend to the requests.
|
||||||
|
basePath string
|
||||||
|
// transport is the interface to send request with, it implements transport.Client.
|
||||||
|
transport transport.Client
|
||||||
|
// version of the server to talk to.
|
||||||
|
version string
|
||||||
|
// custom http headers configured by users.
|
||||||
|
customHTTPHeaders map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient initializes a new API client for the given host and API version.
|
||||||
|
// It uses the given http client as transport.
|
||||||
|
// It also initializes the custom http headers to add to each request.
|
||||||
|
//
|
||||||
|
// It won't send any version information if the version number is empty. It is
|
||||||
|
// highly recommended that you set a version or your client may break if the
|
||||||
|
// server is upgraded.
|
||||||
|
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
|
||||||
|
proto, addr, basePath, err := ParseHost(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transport, err := transport.NewTransportWithHTTP(proto, addr, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
proto: proto,
|
||||||
|
addr: addr,
|
||||||
|
basePath: basePath,
|
||||||
|
transport: transport,
|
||||||
|
version: version,
|
||||||
|
customHTTPHeaders: httpHeaders,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAPIPath returns the versioned request path to call the api.
|
||||||
|
// It appends the query parameters to the path if they are not empty.
|
||||||
|
func (cli *Client) getAPIPath(p string, query url.Values) string {
|
||||||
|
var apiPath string
|
||||||
|
if cli.version != "" {
|
||||||
|
v := strings.TrimPrefix(cli.version, "v")
|
||||||
|
apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, p)
|
||||||
|
} else {
|
||||||
|
apiPath = fmt.Sprintf("%s%s", cli.basePath, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
u := &url.URL{
|
||||||
|
Path: apiPath,
|
||||||
|
}
|
||||||
|
if len(query) > 0 {
|
||||||
|
u.RawQuery = query.Encode()
|
||||||
|
}
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientVersion returns the version string associated with this
|
||||||
|
// instance of the Client. Note that this value can be changed
|
||||||
|
// via the DOCKER_API_VERSION env var.
|
||||||
|
func (cli *Client) ClientVersion() string {
|
||||||
|
return cli.version
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateClientVersion updates the version string associated with this
|
||||||
|
// instance of the Client.
|
||||||
|
func (cli *Client) UpdateClientVersion(v string) {
|
||||||
|
cli.version = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHost verifies that the given host strings is valid.
|
||||||
|
func ParseHost(host string) (string, string, string, error) {
|
||||||
|
protoAddrParts := strings.SplitN(host, "://", 2)
|
||||||
|
if len(protoAddrParts) == 1 {
|
||||||
|
return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
var basePath string
|
||||||
|
proto, addr := protoAddrParts[0], protoAddrParts[1]
|
||||||
|
if proto == "tcp" {
|
||||||
|
parsed, err := url.Parse("tcp://" + addr)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
addr = parsed.Host
|
||||||
|
basePath = parsed.Path
|
||||||
|
}
|
||||||
|
return proto, addr, basePath, nil
|
||||||
|
}
|
||||||
4
pkg/api/client/client_darwin.go
Normal file
4
pkg/api/client/client_darwin.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
|
||||||
|
const DefaultDockerHost = "tcp://127.0.0.1:2375"
|
||||||
72
pkg/api/client/client_test.go
Normal file
72
pkg/api/client/client_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetAPIPath(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
v string
|
||||||
|
p string
|
||||||
|
q url.Values
|
||||||
|
e string
|
||||||
|
}{
|
||||||
|
{"", "/containers/json", nil, "/containers/json"},
|
||||||
|
{"", "/containers/json", url.Values{}, "/containers/json"},
|
||||||
|
{"", "/containers/json", url.Values{"s": []string{"c"}}, "/containers/json?s=c"},
|
||||||
|
{"1.22", "/containers/json", nil, "/v1.22/containers/json"},
|
||||||
|
{"1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
|
||||||
|
{"1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
|
||||||
|
{"v1.22", "/containers/json", nil, "/v1.22/containers/json"},
|
||||||
|
{"v1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
|
||||||
|
{"v1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
|
||||||
|
{"v1.22", "/networks/kiwl$%^", nil, "/v1.22/networks/kiwl$%25%5E"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cs := range cases {
|
||||||
|
c, err := NewClient("unix:///var/run/docker.sock", cs.v, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
g := c.getAPIPath(cs.p, cs.q)
|
||||||
|
if g != cs.e {
|
||||||
|
t.Fatalf("Expected %s, got %s", cs.e, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseHost(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
host string
|
||||||
|
proto string
|
||||||
|
addr string
|
||||||
|
base string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"", "", "", "", true},
|
||||||
|
{"foobar", "", "", "", true},
|
||||||
|
{"foo://bar", "foo", "bar", "", false},
|
||||||
|
{"tcp://localhost:2476", "tcp", "localhost:2476", "", false},
|
||||||
|
{"tcp://localhost:2476/path", "tcp", "localhost:2476", "/path", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cs := range cases {
|
||||||
|
p, a, b, e := ParseHost(cs.host)
|
||||||
|
if cs.err && e == nil {
|
||||||
|
t.Fatalf("expected error, got nil")
|
||||||
|
}
|
||||||
|
if !cs.err && e != nil {
|
||||||
|
t.Fatal(e)
|
||||||
|
}
|
||||||
|
if cs.proto != p {
|
||||||
|
t.Fatalf("expected proto %s, got %s", cs.proto, p)
|
||||||
|
}
|
||||||
|
if cs.addr != a {
|
||||||
|
t.Fatalf("expected addr %s, got %s", cs.addr, a)
|
||||||
|
}
|
||||||
|
if cs.base != b {
|
||||||
|
t.Fatalf("expected base %s, got %s", cs.base, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
pkg/api/client/client_unix.go
Normal file
6
pkg/api/client/client_unix.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// +build linux freebsd solaris openbsd
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
|
||||||
|
const DefaultDockerHost = "unix:///var/run/docker.sock"
|
||||||
200
pkg/api/client/request.go
Normal file
200
pkg/api/client/request.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client/transport/cancellable"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrConnectionFailed is an error raised when the connection between the client and the server failed.
|
||||||
|
var ErrConnectionFailed = errors.New("Cannot connect to the SCSI Target daemon. Is the SCSI target daemon running on this host?")
|
||||||
|
|
||||||
|
// serverResponse is a wrapper for http API responses.
|
||||||
|
type serverResponse struct {
|
||||||
|
body io.ReadCloser
|
||||||
|
header http.Header
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
// head sends an http request to the docker API using the method HEAD.
|
||||||
|
func (cli *Client) head(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
return cli.sendRequest(ctx, "HEAD", path, query, nil, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getWithContext sends an http request to the docker API using the method GET with a specific go context.
|
||||||
|
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
return cli.sendRequest(ctx, "GET", path, query, nil, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// postWithContext sends an http request to the docker API using the method POST with a specific go context.
|
||||||
|
func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
return cli.sendRequest(ctx, "POST", path, query, obj, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
return cli.sendClientRequest(ctx, "POST", path, query, body, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// put sends an http request to the docker API using the method PUT.
|
||||||
|
func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
return cli.sendRequest(ctx, "PUT", path, query, obj, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// put sends an http request to the docker API using the method PUT.
|
||||||
|
func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
return cli.sendClientRequest(ctx, "PUT", path, query, body, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete sends an http request to the docker API using the method DELETE.
|
||||||
|
func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
return cli.sendRequest(ctx, "DELETE", path, query, nil, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
var body io.Reader
|
||||||
|
|
||||||
|
if obj != nil {
|
||||||
|
var err error
|
||||||
|
body, err = encodeData(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if headers == nil {
|
||||||
|
headers = make(map[string][]string)
|
||||||
|
}
|
||||||
|
headers["Content-Type"] = []string{"application/json"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cli.sendClientRequest(ctx, method, path, query, body, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Client) sendClientRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
|
||||||
|
serverResp := &serverResponse{
|
||||||
|
body: nil,
|
||||||
|
statusCode: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPayload := (method == "POST" || method == "PUT")
|
||||||
|
if expectedPayload && body == nil {
|
||||||
|
body = bytes.NewReader([]byte{})
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := cli.newRequest(method, path, query, body, headers)
|
||||||
|
if err != nil {
|
||||||
|
return serverResp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cli.proto == "unix" || cli.proto == "npipe" {
|
||||||
|
// For local communications, it doesn't matter what the host is. We just
|
||||||
|
// need a valid and meaningful host name.
|
||||||
|
req.Host = "gotgt"
|
||||||
|
}
|
||||||
|
req.URL.Host = cli.addr
|
||||||
|
req.URL.Scheme = cli.transport.Scheme()
|
||||||
|
|
||||||
|
if expectedPayload && req.Header.Get("Content-Type") == "" {
|
||||||
|
req.Header.Set("Content-Type", "text/plain")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := cancellable.Do(ctx, cli.transport, req)
|
||||||
|
if err != nil {
|
||||||
|
if isTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
|
||||||
|
return serverResp, ErrConnectionFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cli.transport.Secure() && strings.Contains(err.Error(), "malformed HTTP response") {
|
||||||
|
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cli.transport.Secure() && strings.Contains(err.Error(), "bad certificate") {
|
||||||
|
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != nil {
|
||||||
|
serverResp.statusCode = resp.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return serverResp, err
|
||||||
|
}
|
||||||
|
if len(body) == 0 {
|
||||||
|
return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorMessage string
|
||||||
|
errorMessage = string(body)
|
||||||
|
|
||||||
|
return serverResp, fmt.Errorf("Error response from daemon: %s", strings.TrimSpace(errorMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
serverResp.body = resp.Body
|
||||||
|
serverResp.header = resp.Header
|
||||||
|
return serverResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *Client) newRequest(method, path string, query url.Values, body io.Reader, headers map[string][]string) (*http.Request, error) {
|
||||||
|
apiPath := cli.getAPIPath(path, query)
|
||||||
|
req, err := http.NewRequest(method, apiPath, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
||||||
|
// then the user can't change OUR headers
|
||||||
|
for k, v := range cli.customHTTPHeaders {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if headers != nil {
|
||||||
|
for k, v := range headers {
|
||||||
|
req.Header[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||||
|
params := bytes.NewBuffer(nil)
|
||||||
|
if data != nil {
|
||||||
|
if err := json.NewEncoder(params).Encode(data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureReaderClosed(response *serverResponse) {
|
||||||
|
if response != nil && response.body != nil {
|
||||||
|
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
|
||||||
|
io.CopyN(ioutil.Discard, response.body, 512)
|
||||||
|
response.body.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTimeout(err error) bool {
|
||||||
|
type timeout interface {
|
||||||
|
Timeout() bool
|
||||||
|
}
|
||||||
|
e := err
|
||||||
|
switch urlErr := err.(type) {
|
||||||
|
case *url.Error:
|
||||||
|
e = urlErr.Err
|
||||||
|
}
|
||||||
|
t, ok := e.(timeout)
|
||||||
|
return ok && t.Timeout()
|
||||||
|
}
|
||||||
35
pkg/api/client/target_create.go
Normal file
35
pkg/api/client/target_create.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TargetCreate creates a target in the SCSI Target.
|
||||||
|
func (cli *Client) TargetCreate(ctx context.Context, options api.TargetCreateRequest) (api.SCSITarget, error) {
|
||||||
|
var target api.SCSITarget
|
||||||
|
resp, err := cli.post(ctx, "/target/create", nil, options, nil)
|
||||||
|
if err != nil {
|
||||||
|
return target, err
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(resp.body).Decode(&target)
|
||||||
|
ensureReaderClosed(resp)
|
||||||
|
return target, err
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2015 The GoStor Authors All rights reserved.
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -14,5 +14,22 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package apiserver contains the code that provides a rest.ful api service.
|
package client
|
||||||
package apiserver
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TargetCreate creates a target in the SCSI Target.
|
||||||
|
func (cli *Client) TargetRemove(ctx context.Context, options api.TargetRemoveOptions) error {
|
||||||
|
query := url.Values{}
|
||||||
|
if options.Force {
|
||||||
|
query.Set("force", "1")
|
||||||
|
}
|
||||||
|
resp, err := cli.delete(ctx, "/targets/"+options.Name, query, nil)
|
||||||
|
ensureReaderClosed(resp)
|
||||||
|
return err
|
||||||
|
}
|
||||||
40
pkg/api/client/target_list.go
Normal file
40
pkg/api/client/target_list.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TargetCreate creates a target in the SCSI Target.
|
||||||
|
func (cli *Client) TargetList(ctx context.Context, options api.TargetListOptions) ([]api.SCSITarget, error) {
|
||||||
|
var targets []api.SCSITarget
|
||||||
|
var query = url.Values{}
|
||||||
|
if options.Name != "" {
|
||||||
|
query.Set("name", options.Name)
|
||||||
|
}
|
||||||
|
resp, err := cli.get(ctx, "/target/list", query, nil)
|
||||||
|
if err != nil {
|
||||||
|
return targets, err
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(resp.body).Decode(&targets)
|
||||||
|
ensureReaderClosed(resp)
|
||||||
|
return targets, err
|
||||||
|
}
|
||||||
191
pkg/api/client/transport/LICENSE
Normal file
191
pkg/api/client/transport/LICENSE
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2015-2016 Docker, 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
|
||||||
|
|
||||||
|
https://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.
|
||||||
27
pkg/api/client/transport/cancellable/LICENSE
Normal file
27
pkg/api/client/transport/cancellable/LICENSE
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
23
pkg/api/client/transport/cancellable/canceler.go
Normal file
23
pkg/api/client/transport/cancellable/canceler.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.5
|
||||||
|
|
||||||
|
package cancellable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
func canceler(client transport.Sender, req *http.Request) func() {
|
||||||
|
// TODO(djd): Respect any existing value of req.Cancel.
|
||||||
|
ch := make(chan struct{})
|
||||||
|
req.Cancel = ch
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
pkg/api/client/transport/cancellable/canceler_go14.go
Normal file
27
pkg/api/client/transport/cancellable/canceler_go14.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !go1.5
|
||||||
|
|
||||||
|
package cancellable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
type requestCanceler interface {
|
||||||
|
CancelRequest(*http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func canceler(client transport.Sender, req *http.Request) func() {
|
||||||
|
rc, ok := client.(requestCanceler)
|
||||||
|
if !ok {
|
||||||
|
return func() {}
|
||||||
|
}
|
||||||
|
return func() {
|
||||||
|
rc.CancelRequest(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
112
pkg/api/client/transport/cancellable/cancellable.go
Normal file
112
pkg/api/client/transport/cancellable/cancellable.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package cancellable provides helper function to cancel http requests.
|
||||||
|
package cancellable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/api/client/transport"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func nop() {}
|
||||||
|
|
||||||
|
var (
|
||||||
|
testHookContextDoneBeforeHeaders = nop
|
||||||
|
testHookDoReturned = nop
|
||||||
|
testHookDidBodyClose = nop
|
||||||
|
)
|
||||||
|
|
||||||
|
// Do sends an HTTP request with the provided transport.Sender and returns an HTTP response.
|
||||||
|
// If the client is nil, http.DefaultClient is used.
|
||||||
|
// If the context is canceled or times out, ctx.Err() will be returned.
|
||||||
|
//
|
||||||
|
// FORK INFORMATION:
|
||||||
|
//
|
||||||
|
// This function deviates from the upstream version in golang.org/x/net/context/ctxhttp by
|
||||||
|
// taking a Sender interface rather than a *http.Client directly. That allow us to use
|
||||||
|
// this funcion with mocked clients and hijacked connections.
|
||||||
|
func Do(ctx context.Context, client transport.Sender, req *http.Request) (*http.Response, error) {
|
||||||
|
if client == nil {
|
||||||
|
client = http.DefaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request cancelation changed in Go 1.5, see canceler.go and canceler_go14.go.
|
||||||
|
cancel := canceler(client, req)
|
||||||
|
|
||||||
|
type responseAndError struct {
|
||||||
|
resp *http.Response
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
result := make(chan responseAndError, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
testHookDoReturned()
|
||||||
|
result <- responseAndError{resp, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
testHookContextDoneBeforeHeaders()
|
||||||
|
cancel()
|
||||||
|
// Clean up after the goroutine calling client.Do:
|
||||||
|
go func() {
|
||||||
|
if r := <-result; r.resp != nil && r.resp.Body != nil {
|
||||||
|
testHookDidBodyClose()
|
||||||
|
r.resp.Body.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case r := <-result:
|
||||||
|
var err error
|
||||||
|
resp, err = r.resp, r.err
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
cancel()
|
||||||
|
case <-c:
|
||||||
|
// The response's Body is closed.
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resp.Body = ¬ifyingReader{resp.Body, c}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// notifyingReader is an io.ReadCloser that closes the notify channel after
|
||||||
|
// Close is called or a Read fails on the underlying ReadCloser.
|
||||||
|
type notifyingReader struct {
|
||||||
|
io.ReadCloser
|
||||||
|
notify chan<- struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *notifyingReader) Read(p []byte) (int, error) {
|
||||||
|
n, err := r.ReadCloser.Read(p)
|
||||||
|
if err != nil && r.notify != nil {
|
||||||
|
close(r.notify)
|
||||||
|
r.notify = nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *notifyingReader) Close() error {
|
||||||
|
err := r.ReadCloser.Close()
|
||||||
|
if r.notify != nil {
|
||||||
|
close(r.notify)
|
||||||
|
r.notify = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
47
pkg/api/client/transport/client.go
Normal file
47
pkg/api/client/transport/client.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sender is an interface that clients must implement
|
||||||
|
// to be able to send requests to a remote connection.
|
||||||
|
type Sender interface {
|
||||||
|
// Do sends request to a remote endpoint.
|
||||||
|
Do(*http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client is an interface that abstracts all remote connections.
|
||||||
|
type Client interface {
|
||||||
|
Sender
|
||||||
|
// Secure tells whether the connection is secure or not.
|
||||||
|
Secure() bool
|
||||||
|
// Scheme returns the connection protocol the client uses.
|
||||||
|
Scheme() string
|
||||||
|
// TLSConfig returns any TLS configuration the client uses.
|
||||||
|
TLSConfig() *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlsInfo returns information about the TLS configuration.
|
||||||
|
type tlsInfo struct {
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig returns the TLS configuration.
|
||||||
|
func (t *tlsInfo) TLSConfig() *tls.Config {
|
||||||
|
return t.tlsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scheme returns protocol scheme to use.
|
||||||
|
func (t *tlsInfo) Scheme() string {
|
||||||
|
if t.tlsConfig != nil {
|
||||||
|
return "https"
|
||||||
|
}
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secure returns true if there is a TLS configuration.
|
||||||
|
func (t *tlsInfo) Secure() bool {
|
||||||
|
return t.tlsConfig != nil
|
||||||
|
}
|
||||||
57
pkg/api/client/transport/transport.go
Normal file
57
pkg/api/client/transport/transport.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Package transport provides function to send request to remote endpoints.
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/go-connections/sockets"
|
||||||
|
)
|
||||||
|
|
||||||
|
// apiTransport holds information about the http transport to connect with the API.
|
||||||
|
type apiTransport struct {
|
||||||
|
*http.Client
|
||||||
|
*tlsInfo
|
||||||
|
transport *http.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTransportWithHTTP creates a new transport based on the provided proto, address and http client.
|
||||||
|
// It uses Docker's default http transport configuration if the client is nil.
|
||||||
|
// It does not modify the client's transport if it's not nil.
|
||||||
|
func NewTransportWithHTTP(proto, addr string, client *http.Client) (Client, error) {
|
||||||
|
var transport *http.Transport
|
||||||
|
|
||||||
|
if client != nil {
|
||||||
|
tr, ok := client.Transport.(*http.Transport)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport)
|
||||||
|
}
|
||||||
|
transport = tr
|
||||||
|
} else {
|
||||||
|
transport = defaultTransport(proto, addr)
|
||||||
|
client = &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiTransport{
|
||||||
|
Client: client,
|
||||||
|
tlsInfo: &tlsInfo{transport.TLSClientConfig},
|
||||||
|
transport: transport,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelRequest stops a request execution.
|
||||||
|
func (a *apiTransport) CancelRequest(req *http.Request) {
|
||||||
|
a.transport.CancelRequest(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultTransport creates a new http.Transport with Docker's
|
||||||
|
// default transport configuration.
|
||||||
|
func defaultTransport(proto, addr string) *http.Transport {
|
||||||
|
tr := new(http.Transport)
|
||||||
|
sockets.ConfigureTransport(tr, proto, addr)
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Client = &apiTransport{}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2015 The GoStor Authors All rights reserved.
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -14,5 +14,18 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package apiserver contains the code that provides a rest.ful api service.
|
package api
|
||||||
package apiserver
|
|
||||||
|
type TargetCreateRequest struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TargetRemoveOptions struct {
|
||||||
|
Name string
|
||||||
|
Force bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type TargetListOptions struct {
|
||||||
|
Name string
|
||||||
|
Verbose bool
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SCSICommandType byte
|
type SCSICommandType byte
|
||||||
@@ -147,7 +148,7 @@ type SCSICommand struct {
|
|||||||
TL uint32
|
TL uint32
|
||||||
SCB *bytes.Buffer
|
SCB *bytes.Buffer
|
||||||
SCBLength int
|
SCBLength int
|
||||||
Lun []uint8
|
Lun [8]uint8
|
||||||
Attribute int
|
Attribute int
|
||||||
Tag uint64
|
Tag uint64
|
||||||
Result byte
|
Result byte
|
||||||
@@ -156,13 +157,14 @@ type SCSICommand struct {
|
|||||||
ITNexus *ITNexus
|
ITNexus *ITNexus
|
||||||
ITNexusLuInfo *ITNexusLuInfo
|
ITNexusLuInfo *ITNexusLuInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type ITNexus struct {
|
type ITNexus struct {
|
||||||
ID uint64
|
ID uint64 `json:"id"`
|
||||||
Ctime uint64
|
Ctime uint64 `json:"ctime"`
|
||||||
Commands []SCSICommand
|
Commands []SCSICommand `json:"-"`
|
||||||
Target *SCSITarget
|
Target *SCSITarget `json:"-"`
|
||||||
Host int
|
Host int `json:"host"`
|
||||||
Info string
|
Info string `json:"info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ITNexusLuInfo struct {
|
type ITNexusLuInfo struct {
|
||||||
@@ -172,14 +174,14 @@ type ITNexusLuInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SCSITarget struct {
|
type SCSITarget struct {
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
TID int
|
TID int `json:"tid"`
|
||||||
LID int
|
LID int `json:"lid"`
|
||||||
State SCSITargetState
|
State SCSITargetState `json:"state"`
|
||||||
Devices []SCSILu
|
Devices []*SCSILu `json:"-"`
|
||||||
ITNexus []ITNexus
|
ITNexus []*ITNexus `json:"itnexus"`
|
||||||
|
|
||||||
SCSITargetDriver interface{}
|
SCSITargetDriver interface{} `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SCSITargetDriverState int
|
type SCSITargetDriverState int
|
||||||
@@ -210,7 +212,7 @@ type SCSILuPhyAttribute struct {
|
|||||||
ProductRev string
|
ProductRev string
|
||||||
VersionDesction []uint16
|
VersionDesction []uint16
|
||||||
// Peripheral device type
|
// Peripheral device type
|
||||||
DeviceType uint
|
DeviceType SCSIDeviceType
|
||||||
// Peripheral Qualifier
|
// Peripheral Qualifier
|
||||||
Qualifier bool
|
Qualifier bool
|
||||||
// Removable media
|
// Removable media
|
||||||
@@ -234,8 +236,8 @@ type SCSILuPhyAttribute struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultBlockShift int = 9
|
DefaultBlockShift uint = 9
|
||||||
DefaultSenseBufferSize int = 252
|
DefaultSenseBufferSize int = 252
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -294,8 +296,32 @@ var (
|
|||||||
TYPE_PT SCSIDeviceType = 0xff
|
TYPE_PT SCSIDeviceType = 0xff
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CommandFunc func(host int, cmd *SCSICommand) SAMStat
|
||||||
|
|
||||||
|
type BackingStore interface {
|
||||||
|
Open(dev *SCSILu, path string) (*os.File, error)
|
||||||
|
Close(dev *SCSILu) error
|
||||||
|
Init(dev *SCSILu, Opts string) error
|
||||||
|
Exit(dev *SCSILu) error
|
||||||
|
CommandSubmit(cmd *SCSICommand) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SCSIDeviceProtocol interface {
|
||||||
|
PerformCommand(opcode int) interface{}
|
||||||
|
InitLu(lu *SCSILu) error
|
||||||
|
ConfigLu(lu *SCSILu) error
|
||||||
|
OnlineLu(lu *SCSILu) error
|
||||||
|
OfflineLu(lu *SCSILu) error
|
||||||
|
ExitLu(lu *SCSILu) error
|
||||||
|
}
|
||||||
|
type ModePage struct {
|
||||||
|
Pcode uint8 // Page code
|
||||||
|
SubPcode uint8 // Sub page code
|
||||||
|
Data []byte // Rest of mode page info
|
||||||
|
}
|
||||||
|
|
||||||
type SCSILu struct {
|
type SCSILu struct {
|
||||||
FD int
|
File *os.File
|
||||||
Address uint64
|
Address uint64
|
||||||
Size uint64
|
Size uint64
|
||||||
Lun uint64
|
Lun uint64
|
||||||
@@ -304,4 +330,12 @@ type SCSILu struct {
|
|||||||
BlockShift uint
|
BlockShift uint
|
||||||
ReserveID uint64
|
ReserveID uint64
|
||||||
Attrs SCSILuPhyAttribute
|
Attrs SCSILuPhyAttribute
|
||||||
|
ModePages []ModePage
|
||||||
|
|
||||||
|
Target *SCSITarget
|
||||||
|
Storage BackingStore
|
||||||
|
DeviceProtocol SCSIDeviceProtocol
|
||||||
|
|
||||||
|
PerformCommand CommandFunc
|
||||||
|
FinishCommand func(*SCSITarget, *SCSICommand)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2015 The GoStor Authors All rights reserved.
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -16,3 +16,306 @@ limitations under the License.
|
|||||||
|
|
||||||
// Package apiserver contains the code that provides a rest.ful api service.
|
// Package apiserver contains the code that provides a rest.ful api service.
|
||||||
package apiserver
|
package apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
systemdActivation "github.com/coreos/go-systemd/activation"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
|
"github.com/docker/go-connections/sockets"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/gostor/gotgt/pkg/apiserver/httputils"
|
||||||
|
"github.com/gostor/gotgt/pkg/apiserver/router"
|
||||||
|
"github.com/gostor/gotgt/pkg/apiserver/router/discovery"
|
||||||
|
"github.com/gostor/gotgt/pkg/apiserver/router/lu"
|
||||||
|
"github.com/gostor/gotgt/pkg/apiserver/router/target"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// versionMatcher defines a variable matcher to be parsed by the router
|
||||||
|
// when a request is about to be served.
|
||||||
|
const versionMatcher = "/v{version:[0-9.]+}"
|
||||||
|
|
||||||
|
// Config provides the configuration for the API server
|
||||||
|
type Config struct {
|
||||||
|
Logging bool
|
||||||
|
EnableCors bool
|
||||||
|
CorsHeaders string
|
||||||
|
AuthorizationPluginNames []string
|
||||||
|
Version string
|
||||||
|
SocketGroup string
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
Addrs []Addr
|
||||||
|
APIrouter string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr contains string representation of address and its protocol (tcp, unix...).
|
||||||
|
type Addr struct {
|
||||||
|
Proto string
|
||||||
|
Addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server contains instance details for the server
|
||||||
|
type Server struct {
|
||||||
|
cfg *Config
|
||||||
|
servers []*HTTPServer
|
||||||
|
routers []router.Router
|
||||||
|
routerSwapper *routerSwapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new instance of the server based on the specified configuration.
|
||||||
|
// It allocates resources which will be needed for ServeAPI(ports, unix-sockets).
|
||||||
|
func New(cfg *Config) (*Server, error) {
|
||||||
|
s := &Server{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
for _, addr := range cfg.Addrs {
|
||||||
|
srv, err := s.newServer(addr.Proto, addr.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
glog.V(3).Infof("Server created for HTTP on %s (%s)", addr.Proto, addr.Addr)
|
||||||
|
s.servers = append(s.servers, srv...)
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes servers and thus stop receiving requests
|
||||||
|
func (s *Server) Close() {
|
||||||
|
for _, srv := range s.servers {
|
||||||
|
if err := srv.Close(); err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveAPI loops through all initialized servers and spawns goroutine
|
||||||
|
// with Server method for each. It sets createMux() as Handler also.
|
||||||
|
func (s *Server) serveAPI() error {
|
||||||
|
s.initRouterSwapper()
|
||||||
|
|
||||||
|
var chErrors = make(chan error, len(s.servers))
|
||||||
|
for _, srv := range s.servers {
|
||||||
|
srv.srv.Handler = s.routerSwapper
|
||||||
|
go func(srv *HTTPServer) {
|
||||||
|
var err error
|
||||||
|
glog.V(3).Infof("API listen on %s", srv.l.Addr())
|
||||||
|
if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
chErrors <- err
|
||||||
|
}(srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(s.servers); i++ {
|
||||||
|
err := <-chErrors
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPServer contains an instance of http server and the listener.
|
||||||
|
// srv *http.Server, contains configuration to create a http server and a mux router with all api end points.
|
||||||
|
// l net.Listener, is a TCP or Socket listener that dispatches incoming request to the router.
|
||||||
|
type HTTPServer struct {
|
||||||
|
srv *http.Server
|
||||||
|
l net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve starts listening for inbound requests.
|
||||||
|
func (s *HTTPServer) Serve() error {
|
||||||
|
return s.srv.Serve(s.l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the HTTPServer from listening for the inbound requests.
|
||||||
|
func (s *HTTPServer) Close() error {
|
||||||
|
return s.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) {
|
||||||
|
if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert {
|
||||||
|
glog.Warning("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||||
|
}
|
||||||
|
if l, err = sockets.NewTCPSocket(addr, s.cfg.TLSConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// log the handler call
|
||||||
|
glog.V(3).Infof("Calling %s %s", r.Method, r.URL.Path)
|
||||||
|
|
||||||
|
// Define the context that we'll pass around to share info
|
||||||
|
// like the docker-request-id.
|
||||||
|
//
|
||||||
|
// The 'context' will be used for global data that should
|
||||||
|
// apply to all requests. Data that is specific to the
|
||||||
|
// immediate function being called should still be passed
|
||||||
|
// as 'args' on the function call.
|
||||||
|
ctx := context.Background()
|
||||||
|
handlerFunc := s.handleWithGlobalMiddlewares(handler)
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
if vars == nil {
|
||||||
|
vars = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := handlerFunc(ctx, w, r, vars); err != nil {
|
||||||
|
glog.Errorf("Handler for %s %s returned error: %s", r.Method, r.URL.Path, utils.GetErrorMessage(err))
|
||||||
|
httputils.WriteError(w, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitRouters initializes a list of routers for the server.
|
||||||
|
func (s *Server) InitRouters() {
|
||||||
|
s.addRouter(target.NewRouter())
|
||||||
|
s.addRouter(lu.NewRouter())
|
||||||
|
s.addRouter(discovery.NewRouter())
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRouter adds a new router to the server.
|
||||||
|
func (s *Server) addRouter(r router.Router) {
|
||||||
|
s.routers = append(s.routers, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createMux initializes the main router the server uses.
|
||||||
|
// we keep enableCors just for legacy usage, need to be removed in the future
|
||||||
|
func (s *Server) createMux() *mux.Router {
|
||||||
|
m := mux.NewRouter()
|
||||||
|
|
||||||
|
glog.V(3).Infof("Registering routers")
|
||||||
|
for _, apiRouter := range s.routers {
|
||||||
|
for _, r := range apiRouter.Routes() {
|
||||||
|
f := s.makeHTTPHandler(r.Handler())
|
||||||
|
|
||||||
|
glog.V(3).Infof("Registering %s, %s", r.Method(), r.Path())
|
||||||
|
m.Path(versionMatcher + r.Path()).Methods(r.Method()).Handler(f)
|
||||||
|
m.Path(r.Path()).Methods(r.Method()).Handler(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait blocks the server goroutine until it exits.
|
||||||
|
// It sends an error message if there is any error during
|
||||||
|
// the API execution.
|
||||||
|
func (s *Server) Wait(waitChan chan error) {
|
||||||
|
if err := s.serveAPI(); err != nil {
|
||||||
|
glog.Errorf("ServeAPI error: %v", err)
|
||||||
|
waitChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
waitChan <- nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) initRouterSwapper() {
|
||||||
|
s.routerSwapper = &routerSwapper{
|
||||||
|
router: s.createMux(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// newServer sets up the required HTTPServers and does protocol specific checking.
|
||||||
|
// newServer does not set any muxers, you should set it later to Handler field
|
||||||
|
func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
ls []net.Listener
|
||||||
|
)
|
||||||
|
switch proto {
|
||||||
|
case "fd":
|
||||||
|
ls, err = listenFD(addr, s.cfg.TLSConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "tcp":
|
||||||
|
l, err := s.initTCPSocket(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ls = append(ls, l)
|
||||||
|
case "unix":
|
||||||
|
l, err := sockets.NewUnixSocket(addr, s.cfg.SocketGroup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't create unix socket %s: %v", addr, err)
|
||||||
|
}
|
||||||
|
ls = append(ls, l)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid protocol format: %q", proto)
|
||||||
|
}
|
||||||
|
var res []*HTTPServer
|
||||||
|
for _, l := range ls {
|
||||||
|
res = append(res, &HTTPServer{
|
||||||
|
&http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
},
|
||||||
|
l,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listenFD returns the specified socket activated files as a slice of
|
||||||
|
// net.Listeners or all of the activated files if "*" is given.
|
||||||
|
func listenFD(addr string, tlsConfig *tls.Config) ([]net.Listener, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
listeners []net.Listener
|
||||||
|
)
|
||||||
|
// socket activation
|
||||||
|
if tlsConfig != nil {
|
||||||
|
listeners, err = systemdActivation.TLSListeners(false, tlsConfig)
|
||||||
|
} else {
|
||||||
|
listeners, err = systemdActivation.Listeners(false)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(listeners) == 0 {
|
||||||
|
return nil, fmt.Errorf("No sockets found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to all fds just like unix:// and tcp://
|
||||||
|
if addr == "" || addr == "*" {
|
||||||
|
return listeners, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fdNum, err := strconv.Atoi(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse systemd address, should be number: %v", err)
|
||||||
|
}
|
||||||
|
fdOffset := fdNum - 3
|
||||||
|
if len(listeners) < int(fdOffset)+1 {
|
||||||
|
return nil, fmt.Errorf("Too few socket activated files passed in")
|
||||||
|
}
|
||||||
|
if listeners[fdOffset] == nil {
|
||||||
|
return nil, fmt.Errorf("failed to listen on systemd activated file at fd %d", fdOffset+3)
|
||||||
|
}
|
||||||
|
for i, ls := range listeners {
|
||||||
|
if i == fdOffset || ls == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := ls.Close(); err != nil {
|
||||||
|
glog.Errorf("Failed to close systemd activated file at fd %d: %v", fdOffset+3, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []net.Listener{listeners[fdOffset]}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 The GoStor Authors All rights reserved.
|
|
||||||
|
|
||||||
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 apiserver
|
|
||||||
88
pkg/apiserver/httputils/form.go
Normal file
88
pkg/apiserver/httputils/form.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 httputils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BoolValue transforms a form value in different formats into a boolean type.
|
||||||
|
func BoolValue(r *http.Request, k string) bool {
|
||||||
|
s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
|
||||||
|
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolValueOrDefault returns the default bool passed if the query param is
|
||||||
|
// missing, otherwise it's just a proxy to boolValue above
|
||||||
|
func BoolValueOrDefault(r *http.Request, k string, d bool) bool {
|
||||||
|
if _, ok := r.Form[k]; !ok {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
return BoolValue(r, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64ValueOrZero parses a form value into an int64 type.
|
||||||
|
// It returns 0 if the parsing fails.
|
||||||
|
func Int64ValueOrZero(r *http.Request, k string) int64 {
|
||||||
|
val, err := Int64ValueOrDefault(r, k, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64ValueOrDefault parses a form value into an int64 type. If there is an
|
||||||
|
// error, returns the error. If there is no value returns the default value.
|
||||||
|
func Int64ValueOrDefault(r *http.Request, field string, def int64) (int64, error) {
|
||||||
|
if r.Form.Get(field) != "" {
|
||||||
|
value, err := strconv.ParseInt(r.Form.Get(field), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return def, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveOptions stores archive information for different operations.
|
||||||
|
type ArchiveOptions struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveFormValues parses form values and turns them into ArchiveOptions.
|
||||||
|
// It fails if the archive name and path are not in the request.
|
||||||
|
func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) {
|
||||||
|
if err := ParseForm(r); err != nil {
|
||||||
|
return ArchiveOptions{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := vars["name"]
|
||||||
|
path := filepath.FromSlash(r.Form.Get("path"))
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case name == "":
|
||||||
|
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
|
||||||
|
case path == "":
|
||||||
|
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ArchiveOptions{name, path}, nil
|
||||||
|
}
|
||||||
105
pkg/apiserver/httputils/form_test.go
Normal file
105
pkg/apiserver/httputils/form_test.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package httputils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBoolValue(t *testing.T) {
|
||||||
|
cases := map[string]bool{
|
||||||
|
"": false,
|
||||||
|
"0": false,
|
||||||
|
"no": false,
|
||||||
|
"false": false,
|
||||||
|
"none": false,
|
||||||
|
"1": true,
|
||||||
|
"yes": true,
|
||||||
|
"true": true,
|
||||||
|
"one": true,
|
||||||
|
"100": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for c, e := range cases {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("test", c)
|
||||||
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
|
r.Form = v
|
||||||
|
|
||||||
|
a := BoolValue(r, "test")
|
||||||
|
if a != e {
|
||||||
|
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolValueOrDefault(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("GET", "", nil)
|
||||||
|
if !BoolValueOrDefault(r, "queryparam", true) {
|
||||||
|
t.Fatal("Expected to get true default value, got false")
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("param", "")
|
||||||
|
r, _ = http.NewRequest("GET", "", nil)
|
||||||
|
r.Form = v
|
||||||
|
if BoolValueOrDefault(r, "param", true) {
|
||||||
|
t.Fatal("Expected not to get true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64ValueOrZero(t *testing.T) {
|
||||||
|
cases := map[string]int64{
|
||||||
|
"": 0,
|
||||||
|
"asdf": 0,
|
||||||
|
"0": 0,
|
||||||
|
"1": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
for c, e := range cases {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("test", c)
|
||||||
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
|
r.Form = v
|
||||||
|
|
||||||
|
a := Int64ValueOrZero(r, "test")
|
||||||
|
if a != e {
|
||||||
|
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64ValueOrDefault(t *testing.T) {
|
||||||
|
cases := map[string]int64{
|
||||||
|
"": -1,
|
||||||
|
"-1": -1,
|
||||||
|
"42": 42,
|
||||||
|
}
|
||||||
|
|
||||||
|
for c, e := range cases {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("test", c)
|
||||||
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
|
r.Form = v
|
||||||
|
|
||||||
|
a, err := Int64ValueOrDefault(r, "test", -1)
|
||||||
|
if a != e {
|
||||||
|
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error should be nil, but received: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt64ValueOrDefaultWithError(t *testing.T) {
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("test", "invalid")
|
||||||
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
|
r.Form = v
|
||||||
|
|
||||||
|
_, err := Int64ValueOrDefault(r, "test", -1)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected an error.")
|
||||||
|
}
|
||||||
|
}
|
||||||
167
pkg/apiserver/httputils/httputils.go
Normal file
167
pkg/apiserver/httputils/httputils.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 httputils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/gostor/gotgt/pkg/version"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIVersionKey is the client's requested API version.
|
||||||
|
const APIVersionKey = "api-version"
|
||||||
|
|
||||||
|
// APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
|
||||||
|
// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
|
||||||
|
type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
||||||
|
|
||||||
|
// HijackConnection interrupts the http response writer to get the
|
||||||
|
// underlying connection and operate with it.
|
||||||
|
func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||||
|
conn, _, err := w.(http.Hijacker).Hijack()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// Flush the options to make sure the client sets the raw mode
|
||||||
|
conn.Write([]byte{})
|
||||||
|
return conn, conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseStreams ensures that a list for http streams are properly closed.
|
||||||
|
func CloseStreams(streams ...interface{}) {
|
||||||
|
for _, stream := range streams {
|
||||||
|
if tcpc, ok := stream.(interface {
|
||||||
|
CloseWrite() error
|
||||||
|
}); ok {
|
||||||
|
tcpc.CloseWrite()
|
||||||
|
} else if closer, ok := stream.(io.Closer); ok {
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchesContentType validates the content type against the expected one
|
||||||
|
func MatchesContentType(contentType, expectedType string) bool {
|
||||||
|
mimetype, _, err := mime.ParseMediaType(contentType)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
|
||||||
|
}
|
||||||
|
return err == nil && mimetype == expectedType
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckForJSON makes sure that the request's Content-Type is application/json.
|
||||||
|
func CheckForJSON(r *http.Request) error {
|
||||||
|
ct := r.Header.Get("Content-Type")
|
||||||
|
|
||||||
|
// No Content-Type header is ok as long as there's no Body
|
||||||
|
if ct == "" {
|
||||||
|
if r.Body == nil || r.ContentLength == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise it better be json
|
||||||
|
if MatchesContentType(ct, "application/json") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseForm ensures the request form is parsed even with invalid content types.
|
||||||
|
// If we don't do this, POST method without Content-type (even with empty body) will fail.
|
||||||
|
func ParseForm(r *http.Request) error {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMultipartForm ensure the request form is parsed, even with invalid content types.
|
||||||
|
func ParseMultipartForm(r *http.Request) error {
|
||||||
|
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteError decodes a specific docker error and sends it in the response.
|
||||||
|
func WriteError(w http.ResponseWriter, err error) {
|
||||||
|
if err == nil || w == nil {
|
||||||
|
logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode := http.StatusInternalServerError
|
||||||
|
errMsg := err.Error()
|
||||||
|
|
||||||
|
// This part of will be removed once we've
|
||||||
|
// converted everything over to use the errcode package
|
||||||
|
|
||||||
|
// FIXME: this is brittle and should not be necessary.
|
||||||
|
// If we need to differentiate between different possible error types,
|
||||||
|
// we should create appropriate error types with clearly defined meaning
|
||||||
|
errStr := strings.ToLower(err.Error())
|
||||||
|
for keyword, status := range map[string]int{
|
||||||
|
"not found": http.StatusNotFound,
|
||||||
|
"no such": http.StatusNotFound,
|
||||||
|
"bad parameter": http.StatusBadRequest,
|
||||||
|
"conflict": http.StatusConflict,
|
||||||
|
"impossible": http.StatusNotAcceptable,
|
||||||
|
"wrong login/password": http.StatusUnauthorized,
|
||||||
|
"hasn't been activated": http.StatusForbidden,
|
||||||
|
} {
|
||||||
|
if strings.Contains(errStr, keyword) {
|
||||||
|
statusCode = status
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode == 0 {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Error(w, errMsg, statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteJSON writes the value v to the http response stream as json with standard json encoding.
|
||||||
|
func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(code)
|
||||||
|
return json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionFromContext returns an API version from the context using APIVersionKey.
|
||||||
|
// It panics if the context value does not have version.Version type.
|
||||||
|
func VersionFromContext(ctx context.Context) string {
|
||||||
|
if ctx == nil {
|
||||||
|
return version.VERSION
|
||||||
|
}
|
||||||
|
val := ctx.Value(APIVersionKey)
|
||||||
|
if val == nil {
|
||||||
|
return version.VERSION
|
||||||
|
}
|
||||||
|
return val.(string)
|
||||||
|
}
|
||||||
60
pkg/apiserver/router/discovery/discovery.go
Normal file
60
pkg/apiserver/router/discovery/discovery.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/apiserver/router"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// containerRouter is a router to talk with the container controller
|
||||||
|
type discoveryRouter struct {
|
||||||
|
routes []router.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRouter initializes a new container router
|
||||||
|
func NewRouter() router.Router {
|
||||||
|
r := &discoveryRouter{}
|
||||||
|
r.initRoutes()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns the available routers to the container controller
|
||||||
|
func (r *discoveryRouter) Routes() []router.Route {
|
||||||
|
return r.routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// initRoutes initializes the routes in discovery router
|
||||||
|
func (r *discoveryRouter) initRoutes() {
|
||||||
|
r.routes = []router.Route{
|
||||||
|
// GET
|
||||||
|
router.NewGetRoute("/discovery/{name:.*}", r.getDiscovery),
|
||||||
|
// POST
|
||||||
|
router.NewPostRoute("/discovery/create", r.postDiscoveryCreate),
|
||||||
|
// PUT
|
||||||
|
// DELETE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *discoveryRouter) getDiscovery(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *discoveryRouter) postDiscoveryCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
65
pkg/apiserver/router/lu/lu.go
Normal file
65
pkg/apiserver/router/lu/lu.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 lu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/apiserver/router"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// containerRouter is a router to talk with the container controller
|
||||||
|
type luRouter struct {
|
||||||
|
routes []router.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRouter initializes a new container router
|
||||||
|
func NewRouter() router.Router {
|
||||||
|
r := &luRouter{}
|
||||||
|
r.initRoutes()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns the available routers to the container controller
|
||||||
|
func (r *luRouter) Routes() []router.Route {
|
||||||
|
return r.routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// initRoutes initializes the routes in lu router
|
||||||
|
func (r *luRouter) initRoutes() {
|
||||||
|
r.routes = []router.Route{
|
||||||
|
// GET
|
||||||
|
router.NewGetRoute("/lu/{id:.*}", r.getLu),
|
||||||
|
// POST
|
||||||
|
router.NewPostRoute("/lu/create", r.postLuCreate),
|
||||||
|
// PUT
|
||||||
|
// DELETE
|
||||||
|
router.NewDeleteRoute("/lu/{id:.*}", r.deleteLu),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *luRouter) getLu(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *luRouter) postLuCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *luRouter) deleteLu(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
76
pkg/apiserver/router/router.go
Normal file
76
pkg/apiserver/router/router.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 router
|
||||||
|
|
||||||
|
import "github.com/gostor/gotgt/pkg/apiserver/httputils"
|
||||||
|
|
||||||
|
// Router defines an interface to specify a group of routes to add the the docker server.
|
||||||
|
type Router interface {
|
||||||
|
Routes() []Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route defines an individual API route in the docker server.
|
||||||
|
type Route interface {
|
||||||
|
// Handler returns the raw function to create the http handler.
|
||||||
|
Handler() httputils.APIFunc
|
||||||
|
// Method returns the http method that the route responds to.
|
||||||
|
Method() string
|
||||||
|
// Path returns the subpath where the route responds to.
|
||||||
|
Path() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// localRoute defines an individual API route to connect with the docker daemon.
|
||||||
|
// It implements router.Route.
|
||||||
|
type localRoute struct {
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
handler httputils.APIFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler returns the APIFunc to let the server wrap it in middlewares
|
||||||
|
func (l localRoute) Handler() httputils.APIFunc {
|
||||||
|
return l.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method returns the http method that the route responds to.
|
||||||
|
func (l localRoute) Method() string {
|
||||||
|
return l.method
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the subpath where the route responds to.
|
||||||
|
func (l localRoute) Path() string {
|
||||||
|
return l.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoute initializes a new local router for the reouter
|
||||||
|
func NewRoute(method, path string, handler httputils.APIFunc) Route {
|
||||||
|
return localRoute{method, path, handler}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetRoute initializes a new route with the http method GET.
|
||||||
|
func NewGetRoute(path string, handler httputils.APIFunc) Route {
|
||||||
|
return NewRoute("GET", path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPostRoute initializes a new route with the http method POST.
|
||||||
|
func NewPostRoute(path string, handler httputils.APIFunc) Route {
|
||||||
|
return NewRoute("POST", path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPostRoute initializes a new route with the http method POST.
|
||||||
|
func NewDeleteRoute(path string, handler httputils.APIFunc) Route {
|
||||||
|
return NewRoute("DELETE", path, handler)
|
||||||
|
}
|
||||||
77
pkg/apiserver/router/target/target.go
Normal file
77
pkg/apiserver/router/target/target.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 target
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gostor/gotgt/pkg/apiserver/httputils"
|
||||||
|
"github.com/gostor/gotgt/pkg/apiserver/router"
|
||||||
|
"github.com/gostor/gotgt/pkg/scsi"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// containerRouter is a router to talk with the container controller
|
||||||
|
type targetRouter struct {
|
||||||
|
routes []router.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRouter initializes a new container router
|
||||||
|
func NewRouter() router.Router {
|
||||||
|
r := &targetRouter{}
|
||||||
|
r.initRoutes()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns the available routers to the container controller
|
||||||
|
func (r *targetRouter) Routes() []router.Route {
|
||||||
|
return r.routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// initRoutes initializes the routes in target router
|
||||||
|
func (r *targetRouter) initRoutes() {
|
||||||
|
r.routes = []router.Route{
|
||||||
|
// GET
|
||||||
|
router.NewGetRoute("/target/list", r.getTargetList),
|
||||||
|
// POST
|
||||||
|
router.NewPostRoute("/target/create", r.postTargetCreate),
|
||||||
|
router.NewPostRoute("/target/up", r.postTargetUp),
|
||||||
|
// PUT
|
||||||
|
// DELETE
|
||||||
|
router.NewDeleteRoute("/target/{name:.*}", r.deleteTarget),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *targetRouter) getTargetList(ctx context.Context, w http.ResponseWriter, req *http.Request, vars map[string]string) error {
|
||||||
|
service := scsi.NewSCSITargetService()
|
||||||
|
tgts, err := service.GetTargetList()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return httputils.WriteJSON(w, http.StatusOK, tgts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *targetRouter) postTargetCreate(ctx context.Context, w http.ResponseWriter, req *http.Request, vars map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *targetRouter) postTargetUp(ctx context.Context, w http.ResponseWriter, req *http.Request, vars map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *targetRouter) deleteTarget(ctx context.Context, w http.ResponseWriter, req *http.Request, vars map[string]string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
47
pkg/apiserver/router_swapper.go
Normal file
47
pkg/apiserver/router_swapper.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The GoStor Authors All rights reserved.
|
||||||
|
|
||||||
|
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 apiserver contains the code that provides a rest.ful api service.
|
||||||
|
package apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// routerSwapper is an http.Handler that allow you to swap
|
||||||
|
// mux routers.
|
||||||
|
type routerSwapper struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
router *mux.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap changes the old router with the new one.
|
||||||
|
func (rs *routerSwapper) Swap(newRouter *mux.Router) {
|
||||||
|
rs.mu.Lock()
|
||||||
|
rs.router = newRouter
|
||||||
|
rs.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP makes the routerSwapper to implement the http.Handler interface.
|
||||||
|
func (rs *routerSwapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
rs.mu.Lock()
|
||||||
|
router := rs.router
|
||||||
|
rs.mu.Unlock()
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user