You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
7.2 KiB
236 lines
7.2 KiB
package shell
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol/blockapi"
|
|
)
|
|
|
|
func init() {
|
|
Commands = append(Commands, &commandBlockCreate{})
|
|
Commands = append(Commands, &commandBlockList{})
|
|
Commands = append(Commands, &commandBlockStatus{})
|
|
Commands = append(Commands, &commandBlockDelete{})
|
|
Commands = append(Commands, &commandBlockAssign{})
|
|
}
|
|
|
|
// mastersHTTP builds a comma-separated list of "http://host:port" URLs from
|
|
// the shell environment's master addresses.
|
|
func mastersHTTP(commandEnv *CommandEnv) string {
|
|
addrs := commandEnv.MasterClient.GetMasters(context.Background())
|
|
parts := make([]string, len(addrs))
|
|
for i, a := range addrs {
|
|
s := string(a)
|
|
if !strings.HasPrefix(s, "http://") && !strings.HasPrefix(s, "https://") {
|
|
s = "http://" + s
|
|
}
|
|
parts[i] = s
|
|
}
|
|
return strings.Join(parts, ",")
|
|
}
|
|
|
|
// ---- block.create ----
|
|
|
|
type commandBlockCreate struct{}
|
|
|
|
func (c *commandBlockCreate) Name() string { return "block.create" }
|
|
func (c *commandBlockCreate) Help() string {
|
|
return `create a block volume
|
|
|
|
block.create -name <name> -size <bytes> [-replicaPlacement <xyz>] [-disk <type>] [-durability <mode>] [-replicaFactor <n>]
|
|
|
|
durability modes: best_effort (default), sync_all, sync_quorum
|
|
replica factor: 1, 2 (default), or 3. sync_quorum requires 3.
|
|
`
|
|
}
|
|
func (c *commandBlockCreate) HasTag(CommandTag) bool { return false }
|
|
|
|
func (c *commandBlockCreate) Do(args []string, commandEnv *CommandEnv, writer io.Writer) error {
|
|
f := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
|
name := f.String("name", "", "volume name")
|
|
size := f.Uint64("size", 0, "volume size in bytes")
|
|
placement := f.String("replicaPlacement", "000", "placement string: 000, 001, 010, 100")
|
|
disk := f.String("disk", "", "disk type (e.g. ssd, hdd)")
|
|
durability := f.String("durability", "", "durability mode: best_effort (default), sync_all, sync_quorum")
|
|
rf := f.Int("replicaFactor", 0, "replica factor: 1, 2 (default), or 3")
|
|
if err := f.Parse(args); err != nil {
|
|
return nil
|
|
}
|
|
if *name == "" || *size == 0 {
|
|
return fmt.Errorf("both -name and -size are required")
|
|
}
|
|
|
|
client := blockapi.NewClient(mastersHTTP(commandEnv))
|
|
info, err := client.CreateVolume(context.Background(), blockapi.CreateVolumeRequest{
|
|
Name: *name,
|
|
SizeBytes: *size,
|
|
ReplicaPlacement: *placement,
|
|
DiskType: *disk,
|
|
DurabilityMode: *durability,
|
|
ReplicaFactor: *rf,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(writer, "created block volume %q on %s (iqn=%s, iscsi=%s)\n",
|
|
info.Name, info.VolumeServer, info.IQN, info.ISCSIAddr)
|
|
if info.ReplicaServer != "" {
|
|
fmt.Fprintf(writer, " replica on %s (iqn=%s, iscsi=%s)\n",
|
|
info.ReplicaServer, info.ReplicaIQN, info.ReplicaISCSIAddr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ---- block.list ----
|
|
|
|
type commandBlockList struct{}
|
|
|
|
func (c *commandBlockList) Name() string { return "block.list" }
|
|
func (c *commandBlockList) Help() string {
|
|
return `list all block volumes
|
|
|
|
block.list
|
|
`
|
|
}
|
|
func (c *commandBlockList) HasTag(CommandTag) bool { return false }
|
|
|
|
func (c *commandBlockList) Do(args []string, commandEnv *CommandEnv, writer io.Writer) error {
|
|
client := blockapi.NewClient(mastersHTTP(commandEnv))
|
|
vols, err := client.ListVolumes(context.Background())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(vols) == 0 {
|
|
fmt.Fprintln(writer, "no block volumes")
|
|
return nil
|
|
}
|
|
fmt.Fprintf(writer, "%-20s %-20s %-12s %-8s %-8s %-14s %-20s\n",
|
|
"NAME", "SERVER", "SIZE", "EPOCH", "ROLE", "DURABILITY", "STATUS")
|
|
for _, v := range vols {
|
|
durMode := v.DurabilityMode
|
|
if durMode == "" {
|
|
durMode = "best_effort"
|
|
}
|
|
fmt.Fprintf(writer, "%-20s %-20s %-12d %-8d %-8s %-14s %-20s\n",
|
|
v.Name, v.VolumeServer, v.SizeBytes, v.Epoch, v.Role, durMode, v.Status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ---- block.status ----
|
|
|
|
type commandBlockStatus struct{}
|
|
|
|
func (c *commandBlockStatus) Name() string { return "block.status" }
|
|
func (c *commandBlockStatus) Help() string {
|
|
return `show status of a block volume
|
|
|
|
block.status <name>
|
|
`
|
|
}
|
|
func (c *commandBlockStatus) HasTag(CommandTag) bool { return false }
|
|
|
|
func (c *commandBlockStatus) Do(args []string, commandEnv *CommandEnv, writer io.Writer) error {
|
|
if len(args) == 0 {
|
|
return fmt.Errorf("usage: block.status <name>")
|
|
}
|
|
name := args[0]
|
|
|
|
client := blockapi.NewClient(mastersHTTP(commandEnv))
|
|
info, err := client.LookupVolume(context.Background(), name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
durMode := info.DurabilityMode
|
|
if durMode == "" {
|
|
durMode = "best_effort"
|
|
}
|
|
fmt.Fprintf(writer, "Name: %s\n", info.Name)
|
|
fmt.Fprintf(writer, "VolumeServer: %s\n", info.VolumeServer)
|
|
fmt.Fprintf(writer, "SizeBytes: %d\n", info.SizeBytes)
|
|
fmt.Fprintf(writer, "Epoch: %d\n", info.Epoch)
|
|
fmt.Fprintf(writer, "Role: %s\n", info.Role)
|
|
fmt.Fprintf(writer, "Status: %s\n", info.Status)
|
|
fmt.Fprintf(writer, "Durability: %s\n", durMode)
|
|
fmt.Fprintf(writer, "ISCSIAddr: %s\n", info.ISCSIAddr)
|
|
fmt.Fprintf(writer, "IQN: %s\n", info.IQN)
|
|
if info.ReplicaServer != "" {
|
|
fmt.Fprintf(writer, "ReplicaServer: %s\n", info.ReplicaServer)
|
|
fmt.Fprintf(writer, "ReplicaISCSI: %s\n", info.ReplicaISCSIAddr)
|
|
fmt.Fprintf(writer, "ReplicaIQN: %s\n", info.ReplicaIQN)
|
|
fmt.Fprintf(writer, "ReplicaData: %s\n", info.ReplicaDataAddr)
|
|
fmt.Fprintf(writer, "ReplicaCtrl: %s\n", info.ReplicaCtrlAddr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ---- block.delete ----
|
|
|
|
type commandBlockDelete struct{}
|
|
|
|
func (c *commandBlockDelete) Name() string { return "block.delete" }
|
|
func (c *commandBlockDelete) Help() string {
|
|
return `delete a block volume
|
|
|
|
block.delete <name>
|
|
`
|
|
}
|
|
func (c *commandBlockDelete) HasTag(CommandTag) bool { return false }
|
|
|
|
func (c *commandBlockDelete) Do(args []string, commandEnv *CommandEnv, writer io.Writer) error {
|
|
if len(args) == 0 {
|
|
return fmt.Errorf("usage: block.delete <name>")
|
|
}
|
|
name := args[0]
|
|
|
|
client := blockapi.NewClient(mastersHTTP(commandEnv))
|
|
if err := client.DeleteVolume(context.Background(), name); err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(writer, "deleted block volume %q\n", name)
|
|
return nil
|
|
}
|
|
|
|
// ---- block.assign ----
|
|
|
|
type commandBlockAssign struct{}
|
|
|
|
func (c *commandBlockAssign) Name() string { return "block.assign" }
|
|
func (c *commandBlockAssign) Help() string {
|
|
return `assign a role to a block volume
|
|
|
|
block.assign -name <name> -epoch <n> -role <primary|replica> [-lease <ms>]
|
|
`
|
|
}
|
|
func (c *commandBlockAssign) HasTag(CommandTag) bool { return false }
|
|
|
|
func (c *commandBlockAssign) Do(args []string, commandEnv *CommandEnv, writer io.Writer) error {
|
|
f := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
|
name := f.String("name", "", "volume name")
|
|
epoch := f.Uint64("epoch", 0, "epoch number")
|
|
role := f.String("role", "", "role: primary or replica")
|
|
leaseTTLMs := f.Uint64("lease", 30000, "lease TTL in milliseconds")
|
|
if err := f.Parse(args); err != nil {
|
|
return nil
|
|
}
|
|
if *name == "" || *role == "" {
|
|
return fmt.Errorf("both -name and -role are required")
|
|
}
|
|
|
|
client := blockapi.NewClient(mastersHTTP(commandEnv))
|
|
if err := client.AssignRole(context.Background(), blockapi.AssignRequest{
|
|
Name: *name,
|
|
Epoch: *epoch,
|
|
Role: *role,
|
|
LeaseTTLMs: *leaseTTLMs,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(writer, "assignment queued: %s epoch=%d role=%s lease=%dms\n",
|
|
*name, *epoch, *role, *leaseTTLMs)
|
|
return nil
|
|
}
|