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");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -18,183 +18,46 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"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() {
|
||||
// define options
|
||||
//var req AdminRequest
|
||||
flDebug := flag.Bool("debug", false, "Debug mode")
|
||||
flHelp := flag.Bool("help", false, "Help Message")
|
||||
flVersion := flag.Bool("version", false, "Version message")
|
||||
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()
|
||||
host := "tcp://127.0.0.1:23457"
|
||||
httpClient, err := newHTTPClient(host)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_ = flDebug
|
||||
_ = flLLD
|
||||
_ = flOperation
|
||||
_ = flMode
|
||||
_ = flTID
|
||||
_ = flSID
|
||||
_ = flCID
|
||||
_ = flLUN
|
||||
_ = flName
|
||||
_ = flValue
|
||||
_ = flBS
|
||||
_ = flTarget
|
||||
_ = flInitiatorName
|
||||
_ = flInitiatorAddress
|
||||
_ = flUser
|
||||
_ = flPassword
|
||||
_ = flHost
|
||||
_ = flForce
|
||||
_ = flDeviceType
|
||||
cli, err := client.NewClient(host, "0.1", httpClient, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := cmd.NewCommand(cli).Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func usage(status int) {
|
||||
if status != 0 {
|
||||
fmt.Fprintf(os.Stderr, "Try `%s --help' for more information.\n", os.Args[0])
|
||||
os.Exit(status)
|
||||
func newHTTPClient(host string) (*http.Client, error) {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: nil,
|
||||
}
|
||||
proto, addr, _, err := client.ParseHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var helpMessage = `Linux SCSI Target administration utility, version %s
|
||||
Usage: %s [OPTIONS]
|
||||
sockets.ConfigureTransport(tr, proto, addr)
|
||||
|
||||
Application Options:
|
||||
--lld <driver> --mode target --op new --tid <id> --targetname <name>
|
||||
add a new target with <id> and <name>. <id> must not be zero.
|
||||
--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)
|
||||
return &http.Client{
|
||||
Transport: tr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -18,70 +18,104 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"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/backingstore"
|
||||
)
|
||||
|
||||
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 {
|
||||
glog.Error(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer l.Close()
|
||||
t, err := scsi.NewTarget(0, "iscsi", "test-iscsi-target")
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
os.Exit(1)
|
||||
iscsit := reflect.ValueOf(t)
|
||||
// create a new target
|
||||
create := iscsit.MethodByName("NewTarget")
|
||||
create.Call([]reflect.Value{reflect.ValueOf("test-iscsi-target")})
|
||||
|
||||
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)
|
||||
|
||||
for {
|
||||
glog.Info("Listening ...")
|
||||
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)
|
||||
//hosts := []string{"unix:///var/run/gotgt.sock"}
|
||||
hosts := []string{}
|
||||
if *flHost != "" {
|
||||
hosts = append(hosts, *flHost)
|
||||
}
|
||||
}
|
||||
|
||||
func checkError(err error, info string) (res bool) {
|
||||
|
||||
if err != nil {
|
||||
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
|
||||
for _, protoAddr := range hosts {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
if len(protoAddrParts) != 2 {
|
||||
glog.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)
|
||||
return
|
||||
}
|
||||
if lenght > 0 {
|
||||
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)
|
||||
serverConfig.Addrs = append(serverConfig.Addrs, apiserver.Addr{Proto: protoAddrParts[0], Addr: protoAddrParts[1]})
|
||||
}
|
||||
|
||||
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");
|
||||
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.
|
||||
*/
|
||||
|
||||
// Package apiserver contains the code that provides a rest.ful api service.
|
||||
package apiserver
|
||||
package client
|
||||
|
||||
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");
|
||||
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.
|
||||
*/
|
||||
|
||||
// Package apiserver contains the code that provides a rest.ful api service.
|
||||
package apiserver
|
||||
package api
|
||||
|
||||
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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
type SCSICommandType byte
|
||||
@@ -147,7 +148,7 @@ type SCSICommand struct {
|
||||
TL uint32
|
||||
SCB *bytes.Buffer
|
||||
SCBLength int
|
||||
Lun []uint8
|
||||
Lun [8]uint8
|
||||
Attribute int
|
||||
Tag uint64
|
||||
Result byte
|
||||
@@ -156,13 +157,14 @@ type SCSICommand struct {
|
||||
ITNexus *ITNexus
|
||||
ITNexusLuInfo *ITNexusLuInfo
|
||||
}
|
||||
|
||||
type ITNexus struct {
|
||||
ID uint64
|
||||
Ctime uint64
|
||||
Commands []SCSICommand
|
||||
Target *SCSITarget
|
||||
Host int
|
||||
Info string
|
||||
ID uint64 `json:"id"`
|
||||
Ctime uint64 `json:"ctime"`
|
||||
Commands []SCSICommand `json:"-"`
|
||||
Target *SCSITarget `json:"-"`
|
||||
Host int `json:"host"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
type ITNexusLuInfo struct {
|
||||
@@ -172,14 +174,14 @@ type ITNexusLuInfo struct {
|
||||
}
|
||||
|
||||
type SCSITarget struct {
|
||||
Name string
|
||||
TID int
|
||||
LID int
|
||||
State SCSITargetState
|
||||
Devices []SCSILu
|
||||
ITNexus []ITNexus
|
||||
Name string `json:"name"`
|
||||
TID int `json:"tid"`
|
||||
LID int `json:"lid"`
|
||||
State SCSITargetState `json:"state"`
|
||||
Devices []*SCSILu `json:"-"`
|
||||
ITNexus []*ITNexus `json:"itnexus"`
|
||||
|
||||
SCSITargetDriver interface{}
|
||||
SCSITargetDriver interface{} `json:"-"`
|
||||
}
|
||||
|
||||
type SCSITargetDriverState int
|
||||
@@ -210,7 +212,7 @@ type SCSILuPhyAttribute struct {
|
||||
ProductRev string
|
||||
VersionDesction []uint16
|
||||
// Peripheral device type
|
||||
DeviceType uint
|
||||
DeviceType SCSIDeviceType
|
||||
// Peripheral Qualifier
|
||||
Qualifier bool
|
||||
// Removable media
|
||||
@@ -234,8 +236,8 @@ type SCSILuPhyAttribute struct {
|
||||
}
|
||||
|
||||
var (
|
||||
DefaultBlockShift int = 9
|
||||
DefaultSenseBufferSize int = 252
|
||||
DefaultBlockShift uint = 9
|
||||
DefaultSenseBufferSize int = 252
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -294,8 +296,32 @@ var (
|
||||
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 {
|
||||
FD int
|
||||
File *os.File
|
||||
Address uint64
|
||||
Size uint64
|
||||
Lun uint64
|
||||
@@ -304,4 +330,12 @@ type SCSILu struct {
|
||||
BlockShift uint
|
||||
ReserveID uint64
|
||||
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");
|
||||
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
|
||||
|
||||
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