From 78fb5a9cdc6613b3ebc70a53d71f2dc293d38f03 Mon Sep 17 00:00:00 2001 From: Zexi Li Date: Tue, 11 Dec 2018 00:59:10 +0800 Subject: [PATCH] baremetal prepare --- Gopkg.lock | 1 - cmd/ipmicli/main.go | 130 ++++ pkg/baremetal/doc.go | 1 + pkg/baremetal/handler/delay.go | 46 ++ pkg/baremetal/handler/doc.go | 1 + pkg/baremetal/handler/handlers.go | 69 ++ .../{handlers.go => handler/middleware.go} | 83 ++- pkg/baremetal/manager.go | 249 ++++++- pkg/baremetal/options/doc.go | 1 + pkg/baremetal/options/options.go | 3 + pkg/baremetal/profiles/doc.go | 1 + pkg/baremetal/profiles/profiles.go | 108 +++ pkg/baremetal/pxe/dhcp.go | 12 +- pkg/baremetal/pxe/doc.go | 1 + pkg/baremetal/pxe/pxe.go | 16 +- pkg/baremetal/pxe/tftp.go | 80 +-- pkg/baremetal/service/doc.go | 1 + pkg/baremetal/service/service.go | 3 +- pkg/baremetal/status/doc.go | 1 + pkg/baremetal/sysutils/doc.go | 1 + pkg/baremetal/sysutils/sysutils.go | 11 +- pkg/baremetal/sysutils/sysutils_test.go | 4 +- pkg/baremetal/tasks/base.go | 126 +++- pkg/baremetal/tasks/basedeploy.go | 52 +- pkg/baremetal/tasks/baseprepare.go | 360 +++++++++- pkg/baremetal/tasks/doc.go | 1 + pkg/baremetal/tasks/interface.go | 24 +- pkg/baremetal/tasks/maintenance.go | 40 ++ pkg/baremetal/tasks/prepare.go | 5 + pkg/baremetal/tasks/reprepare.go | 32 + pkg/baremetal/tasks/unmaintenance.go | 70 ++ pkg/baremetal/tasks/worker.go | 10 +- pkg/baremetal/types/dmi.go | 1 - pkg/baremetal/types/doc.go | 1 + pkg/baremetal/types/ipmi.go | 23 + pkg/baremetal/types/nic.go | 14 + pkg/baremetal/types/types.go | 49 +- pkg/baremetal/utils/detect_storages/doc.go | 1 + pkg/baremetal/utils/ipmitool/doc.go | 1 + pkg/baremetal/utils/ipmitool/ipmitool.go | 634 ++++++++++++++++++ pkg/baremetal/utils/ipmitool/ipmitool_test.go | 32 + pkg/cloudcommon/dhcp/helpers.go | 8 +- pkg/cloudcommon/tftp/doc.go | 1 + pkg/cloudcommon/tftp/tftp.go | 395 +++++++++++ pkg/compute/models/hosts.go | 2 + pkg/mcclient/modules/mod_tasks.go | 30 +- pkg/util/ipmitool/shell/boot.go | 52 ++ pkg/util/ipmitool/shell/doc.go | 1 + pkg/util/ipmitool/shell/lan.go | 35 + pkg/util/ipmitool/shell/shell.go | 28 + pkg/util/ipmitool/shell/status.go | 19 + pkg/util/procutils/doc.go | 1 + pkg/util/ssh/doc.go | 1 + pkg/util/ssh/ssh.go | 17 +- vendor/github.com/pin/tftp/.gitignore | 24 - vendor/github.com/pin/tftp/.travis.yml | 8 - vendor/github.com/pin/tftp/CONTRIBUTORS | 4 - vendor/github.com/pin/tftp/LICENSE | 21 - vendor/github.com/pin/tftp/README.md | 171 ----- vendor/github.com/pin/tftp/backoff.go | 35 - vendor/github.com/pin/tftp/client.go | 125 ---- .../github.com/pin/tftp/netascii/netascii.go | 108 --- vendor/github.com/pin/tftp/packet.go | 190 ------ vendor/github.com/pin/tftp/receiver.go | 234 ------- vendor/github.com/pin/tftp/sender.go | 243 ------- vendor/github.com/pin/tftp/server.go | 199 ------ 66 files changed, 2712 insertions(+), 1539 deletions(-) create mode 100644 cmd/ipmicli/main.go create mode 100644 pkg/baremetal/doc.go create mode 100644 pkg/baremetal/handler/delay.go create mode 100644 pkg/baremetal/handler/doc.go create mode 100644 pkg/baremetal/handler/handlers.go rename pkg/baremetal/{handlers.go => handler/middleware.go} (61%) create mode 100644 pkg/baremetal/options/doc.go create mode 100644 pkg/baremetal/profiles/doc.go create mode 100644 pkg/baremetal/profiles/profiles.go create mode 100644 pkg/baremetal/pxe/doc.go create mode 100644 pkg/baremetal/service/doc.go create mode 100644 pkg/baremetal/status/doc.go create mode 100644 pkg/baremetal/sysutils/doc.go create mode 100644 pkg/baremetal/tasks/doc.go create mode 100644 pkg/baremetal/tasks/maintenance.go create mode 100644 pkg/baremetal/tasks/reprepare.go create mode 100644 pkg/baremetal/tasks/unmaintenance.go delete mode 100644 pkg/baremetal/types/dmi.go create mode 100644 pkg/baremetal/types/doc.go create mode 100644 pkg/baremetal/utils/detect_storages/doc.go create mode 100644 pkg/baremetal/utils/ipmitool/doc.go create mode 100644 pkg/baremetal/utils/ipmitool/ipmitool.go create mode 100644 pkg/baremetal/utils/ipmitool/ipmitool_test.go create mode 100644 pkg/cloudcommon/tftp/doc.go create mode 100644 pkg/cloudcommon/tftp/tftp.go create mode 100644 pkg/util/ipmitool/shell/boot.go create mode 100644 pkg/util/ipmitool/shell/doc.go create mode 100644 pkg/util/ipmitool/shell/lan.go create mode 100644 pkg/util/ipmitool/shell/shell.go create mode 100644 pkg/util/ipmitool/shell/status.go create mode 100644 pkg/util/procutils/doc.go create mode 100644 pkg/util/ssh/doc.go delete mode 100644 vendor/github.com/pin/tftp/.gitignore delete mode 100644 vendor/github.com/pin/tftp/.travis.yml delete mode 100644 vendor/github.com/pin/tftp/CONTRIBUTORS delete mode 100644 vendor/github.com/pin/tftp/LICENSE delete mode 100644 vendor/github.com/pin/tftp/README.md delete mode 100644 vendor/github.com/pin/tftp/backoff.go delete mode 100644 vendor/github.com/pin/tftp/client.go delete mode 100644 vendor/github.com/pin/tftp/netascii/netascii.go delete mode 100644 vendor/github.com/pin/tftp/packet.go delete mode 100644 vendor/github.com/pin/tftp/receiver.go delete mode 100644 vendor/github.com/pin/tftp/sender.go delete mode 100644 vendor/github.com/pin/tftp/server.go diff --git a/Gopkg.lock b/Gopkg.lock index 2c44543146..7159ea0c25 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1767,7 +1767,6 @@ "github.com/moul/http2curl", "github.com/nelsonken/cos-go-sdk-v5/cos", "github.com/pierrec/lz4", - "github.com/pin/tftp", "github.com/serialx/hashring", "github.com/shirou/gopsutil", "github.com/stretchr/testify/assert", diff --git a/cmd/ipmicli/main.go b/cmd/ipmicli/main.go new file mode 100644 index 0000000000..84dbe1701f --- /dev/null +++ b/cmd/ipmicli/main.go @@ -0,0 +1,130 @@ +package main + +import ( + "fmt" + "os" + + "yunion.io/x/log" + "yunion.io/x/structarg" + + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + "yunion.io/x/onecloud/pkg/util/shellutils" + "yunion.io/x/onecloud/pkg/util/ssh" + + _ "yunion.io/x/onecloud/pkg/util/ipmitool/shell" +) + +type BaseOptions struct { + Help bool `help:"Show help" short-token:"h"` + MODE string `help:"Execute command mode" choices:"ssh|rmcp"` + HOST string `help:"IP address of remote host"` + PASSWD string `help:"Password"` + User string `help:"Username" short-token:"u" default:"root"` + Port int `help:"Remote service port"` + SUBCOMMAND string `help:"ipmicli subcommand" subcommand:"true"` +} + +func showErrorAndExit(err error) { + log.Errorf("%s", err) + os.Exit(1) +} + +func getSubcommandParser() (*structarg.ArgumentParser, error) { + parser, err := structarg.NewArgumentParser( + &BaseOptions{}, + "ipmicli", + "Command-line interface to ipmitool", + `See "ipmicli help COMMAND" for help on a specific command.`, + ) + if err != nil { + return nil, err + } + subcmd := parser.GetSubcommand() + if subcmd == nil { + return nil, fmt.Errorf("No subcommand argument.") + } + type HelpOptions struct { + SUBCOMMAND string `help:"sub-command name"` + } + shellutils.R(&HelpOptions{}, "help", "Show help of a subcommand", func(args *HelpOptions) error { + helpstr, e := subcmd.SubHelpString(args.SUBCOMMAND) + if e != nil { + return e + } else { + fmt.Print(helpstr) + return nil + } + }) + for _, v := range shellutils.CommandTable { + _, e := subcmd.AddSubParser(v.Options, v.Command, v.Desc, v.Callback) + if e != nil { + return nil, e + } + } + return parser, nil +} + +func newExecutor(options *BaseOptions) (ipmitool.IPMIExecutor, error) { + if options.MODE == "ssh" { + port := 22 + if options.Port > 0 { + port = options.Port + } + sshCli, err := ssh.NewClient(options.HOST, port, options.User, options.PASSWD, "") + if err != nil { + return nil, err + } + return ipmitool.NewSSHIPMI(sshCli), nil + } + if options.MODE == "rmcp" { + port := 623 + if options.Port > 0 { + port = options.Port + } + return ipmitool.NewLanPlusIPMIWithPort(options.HOST, options.User, options.PASSWD, port), nil + } + return nil, fmt.Errorf("Unsupported mode: %s", options.MODE) +} + +func main() { + parser, err := getSubcommandParser() + if err != nil { + showErrorAndExit(err) + } + + err = parser.ParseArgs(os.Args[1:], false) + options := parser.Options().(*BaseOptions) + + if options.Help { + fmt.Print(parser.HelpString()) + return + } + + subcmd := parser.GetSubcommand() + subparser := subcmd.GetSubParser() + if err != nil { + if subparser != nil { + fmt.Print(subparser.Usage()) + } else { + fmt.Print(parser.Usage()) + } + showErrorAndExit(err) + return + } + + suboptions := subparser.Options() + var args []interface{} + if options.SUBCOMMAND == "help" { + args = append(args, suboptions) + } else { + executor, err := newExecutor(options) + if err != nil { + showErrorAndExit(err) + } + args = append(args, executor, suboptions) + } + err = subcmd.Invoke(args...) + if err != nil { + showErrorAndExit(err) + } +} diff --git a/pkg/baremetal/doc.go b/pkg/baremetal/doc.go new file mode 100644 index 0000000000..c42c883361 --- /dev/null +++ b/pkg/baremetal/doc.go @@ -0,0 +1 @@ +package baremetal // import "yunion.io/x/onecloud/pkg/baremetal" diff --git a/pkg/baremetal/handler/delay.go b/pkg/baremetal/handler/delay.go new file mode 100644 index 0000000000..bc11a11753 --- /dev/null +++ b/pkg/baremetal/handler/delay.go @@ -0,0 +1,46 @@ +package handler + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var delayTaskWorkerMan *appsrv.SWorkerManager + +func init() { + delayTaskWorkerMan = appsrv.NewWorkerManager("DelayTaskWorkerManager", 8, 1024) +} + +type ProcessFunc func() (jsonutils.JSONObject, error) + +type delayTask struct { + process ProcessFunc + taskId string + session *mcclient.ClientSession +} + +func newDelayTask(process ProcessFunc, session *mcclient.ClientSession, taskId string) *delayTask { + return &delayTask{ + process: process, + taskId: taskId, + session: session, + } +} + +func DelayProcess(process ProcessFunc, session *mcclient.ClientSession, taskId string) { + delayTaskWorkerMan.Run(func() { + executeDelayProcess(newDelayTask(process, session, taskId)) + }, nil, nil) +} + +func executeDelayProcess(task *delayTask) { + ret, err := task.process() + if err != nil { + modules.ComputeTasks.TaskFailed(task.session, task.taskId, err) + return + } + modules.ComputeTasks.TaskComplete(task.session, task.taskId, ret) +} diff --git a/pkg/baremetal/handler/doc.go b/pkg/baremetal/handler/doc.go new file mode 100644 index 0000000000..3a787f6375 --- /dev/null +++ b/pkg/baremetal/handler/doc.go @@ -0,0 +1 @@ +package handler // import "yunion.io/x/onecloud/pkg/baremetal/handler" diff --git a/pkg/baremetal/handler/handlers.go b/pkg/baremetal/handler/handlers.go new file mode 100644 index 0000000000..bb386eb4cd --- /dev/null +++ b/pkg/baremetal/handler/handlers.go @@ -0,0 +1,69 @@ +package handler + +import ( + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/baremetal" + "yunion.io/x/onecloud/pkg/baremetal/tasks" + "yunion.io/x/onecloud/pkg/httperrors" +) + +func InitHandlers(app *appsrv.Application) { + initBaremetalsHandler(app) +} + +func initBaremetalsHandler(app *appsrv.Application) { + app.AddHandler("GET", getBaremetalPrefix("notify"), objectMiddleware(handleBaremetalNotify)) + app.AddHandler("POST", getBaremetalPrefix("maintenance"), objectMiddleware(handleBaremetalMaintenance)) + app.AddHandler("POST", getBaremetalPrefix("unmaintenance"), objectMiddleware(handleBaremetalUnmaintenance)) + app.AddHandler("POST", getBaremetalPrefix("delete"), objectMiddleware(handleBaremetalDelete)) + app.AddHandler("POST", getBaremetalPrefix("syncstatus"), objectMiddleware(handleBaremetalSyncStatus)) + app.AddHandler("POST", getBaremetalPrefix("prepare"), objectMiddleware(handleBaremetalPrepare)) +} + +func handleBaremetalNotify(ctx *Context, bm *baremetal.SBaremetalInstance) { + key, err := ctx.Query().GetString("key") + if err != nil { + ctx.ResponseError(httperrors.NewInputParameterError("Not found key in query")) + return + } + remoteAddr := ctx.RequestRemoteIP() + err = bm.SaveSSHConfig(remoteAddr, key) + if err != nil { + log.Errorf("Save baremetal %s ssh config: %v", bm.GetId(), err) + } + + // execute BaremetalServerPrepareTask + task := bm.GetTask() + log.Errorf("====== get task %#v", task) + if task != nil { + task.(*tasks.SBaremetalServerPrepareTask).SSHExecute(task, remoteAddr, key, nil) + } + ctx.ResponseOk() +} + +func handleBaremetalMaintenance(ctx *Context, bm *baremetal.SBaremetalInstance) { + bm.StartBaremetalMaintenanceTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) + ctx.ResponseOk() +} + +func handleBaremetalUnmaintenance(ctx *Context, bm *baremetal.SBaremetalInstance) { + bm.StartBaremetalUnmaintenanceTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) + ctx.ResponseOk() +} + +func handleBaremetalDelete(ctx *Context, bm *baremetal.SBaremetalInstance) { + ctx.DelayProcess(bm.DelayedRemove) + ctx.ResponseOk() +} + +func handleBaremetalSyncStatus(ctx *Context, bm *baremetal.SBaremetalInstance) { + ctx.DelayProcess(bm.DelayedSyncStatus) + ctx.ResponseOk() +} + +func handleBaremetalPrepare(ctx *Context, bm *baremetal.SBaremetalInstance) { + bm.StartBaremetalReprepareTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) + ctx.ResponseOk() +} diff --git a/pkg/baremetal/handlers.go b/pkg/baremetal/handler/middleware.go similarity index 61% rename from pkg/baremetal/handlers.go rename to pkg/baremetal/handler/middleware.go index b4814b9770..91ed7d8a05 100644 --- a/pkg/baremetal/handlers.go +++ b/pkg/baremetal/handler/middleware.go @@ -1,4 +1,4 @@ -package baremetal +package handler import ( "context" @@ -7,22 +7,46 @@ import ( "strings" "yunion.io/x/jsonutils" - "yunion.io/x/log" "yunion.io/x/onecloud/pkg/appsrv" - "yunion.io/x/onecloud/pkg/baremetal/tasks" + "yunion.io/x/onecloud/pkg/baremetal" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" ) -func InitHandlers(app *appsrv.Application) { - initBaremetalsHandler(app) +const ( + PREFIX = "baremetals" + + PARAMS_ID_KEY = "" +) + +func getBaremetalPrefix(action string) string { + return fmt.Sprintf("%s/%s/%s", PREFIX, PARAMS_ID_KEY, action) } -func initBaremetalsHandler(app *appsrv.Application) { - prefix := "baremetals" - app.AddHandler("GET", fmt.Sprintf("%s//notify", prefix), authMiddleware(handleBaremetalNotify)) +type handlerFunc func(ctx *Context) + +func authMiddleware(h handlerFunc) appsrv.FilterHandler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + newCtx := NewContext(ctx, w, r) + h(newCtx) + } +} + +type objectHandlerFunc func(ctx *Context, bm *baremetal.SBaremetalInstance) + +func objectMiddleware(h objectHandlerFunc) appsrv.FilterHandler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + newCtx := NewContext(ctx, w, r) + bmId := newCtx.Params()[PARAMS_ID_KEY] + baremetal := newCtx.GetBaremetalManager().GetBaremetalById(bmId) + if baremetal == nil { + newCtx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId)) + return + } + h(newCtx, baremetal) + } } type Context struct { @@ -64,6 +88,10 @@ func (ctx *Context) UserCred() mcclient.TokenCredential { return ctx.userCred } +func (ctx *Context) TaskId() string { + return ctx.Request().Header.Get(mcclient.TASK_ID) +} + func (ctx *Context) ResponseStruct(obj interface{}) { appsrv.SendStruct(ctx.writer, obj) } @@ -91,41 +119,10 @@ func (ctx *Context) ResponseOk() { appsrv.SendJSON(ctx.writer, obj) } -func (ctx *Context) GetBaremetalManager() *SBaremetalManager { - return GetBaremetalManager() +func (ctx *Context) GetBaremetalManager() *baremetal.SBaremetalManager { + return baremetal.GetBaremetalManager() } -type handlerFunc func(ctx *Context) - -func authMiddleware(h handlerFunc) appsrv.FilterHandler { - return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { - newCtx := NewContext(ctx, w, r) - h(newCtx) - } -} - -func handleBaremetalNotify(ctx *Context) { - bmId := ctx.Params()[""] - key, err := ctx.Query().GetString("key") - if err != nil { - ctx.ResponseError(httperrors.NewInputParameterError("Not found key in query")) - return - } - remoteAddr := ctx.RequestRemoteIP() - baremetal := ctx.GetBaremetalManager().GetBaremetalById(bmId) - if baremetal == nil { - ctx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId)) - return - } - err = baremetal.SaveSSHConfig(remoteAddr, key) - if err != nil { - log.Errorf("Save baremetal %s ssh config: %v", bmId, err) - } - - // execute BaremetalServerPrepareTask - task := baremetal.GetTask() - if task != nil { - task.(*tasks.SBaremetalServerPrepareTask).SSHExecute(task, remoteAddr, key, nil) - } - ctx.ResponseOk() +func (ctx *Context) DelayProcess(process ProcessFunc) { + DelayProcess(process, ctx.GetBaremetalManager().GetClientSession(), ctx.TaskId()) } diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index 90367ad339..90159c8a9d 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -19,10 +19,12 @@ import ( "yunion.io/x/pkg/utils" o "yunion.io/x/onecloud/pkg/baremetal/options" + "yunion.io/x/onecloud/pkg/baremetal/profiles" "yunion.io/x/onecloud/pkg/baremetal/pxe" - "yunion.io/x/onecloud/pkg/baremetal/status" + baremetalstatus "yunion.io/x/onecloud/pkg/baremetal/status" "yunion.io/x/onecloud/pkg/baremetal/tasks" "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" "yunion.io/x/onecloud/pkg/cloudcommon/dhcp" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" @@ -167,7 +169,7 @@ func (m *SBaremetalManager) GetBaremetalByMac(mac net.HardwareAddr) pxe.IBaremet var obj *SBaremetalInstance getter := func(key, val interface{}) bool { instance := val.(*SBaremetalInstance) - if instance.getNicByMac(mac) != nil { + if instance.GetNicByMac(mac) != nil { obj = instance // stop the iteration return false @@ -221,7 +223,7 @@ func (m *sBaremetalMap) Pop(id string) *SBaremetalInstance { type SBaremetalInstance struct { manager *SBaremetalManager - desc jsonutils.JSONObject + desc *jsonutils.JSONDict descLock *sync.Mutex taskQueue *tasks.TaskQueue } @@ -229,7 +231,7 @@ type SBaremetalInstance struct { func newBaremetalInstance(man *SBaremetalManager, desc jsonutils.JSONObject) (*SBaremetalInstance, error) { bm := &SBaremetalInstance{ manager: man, - desc: desc, + desc: desc.(*jsonutils.JSONDict), descLock: new(sync.Mutex), taskQueue: tasks.NewTaskQueue(), } @@ -245,6 +247,10 @@ func newBaremetalInstance(man *SBaremetalManager, desc jsonutils.JSONObject) (*S return bm, nil } +func (b *SBaremetalInstance) GetClientSession() *mcclient.ClientSession { + return b.manager.GetClientSession() +} + func (b *SBaremetalInstance) GetId() string { id, err := b.desc.GetString("id") if err != nil { @@ -289,11 +295,17 @@ func (b *SBaremetalInstance) GetStatus() string { return status } +func (b *SBaremetalInstance) AutoSaveDesc() error { + return b.SaveDesc(nil) +} + func (b *SBaremetalInstance) SaveDesc(desc jsonutils.JSONObject) error { b.descLock.Lock() defer b.descLock.Unlock() - b.desc = desc - return ioutil.WriteFile(b.GetDescFilePath(), []byte(desc.String()), 0644) + if desc != nil { + b.desc = desc.(*jsonutils.JSONDict) + } + return ioutil.WriteFile(b.GetDescFilePath(), []byte(b.desc.String()), 0644) } func (b *SBaremetalInstance) SaveSSHConfig(remoteAddr string, key string) error { @@ -374,7 +386,10 @@ func (b *SBaremetalInstance) ClearSSHConfig() { Password: "None", RemoteIP: "None", } - b.SyncSSHConfig(emptyConfig) + err = b.SyncSSHConfig(emptyConfig) + if err != nil { + log.Errorf("Sync emtpy SSH config error: %v", err) + } } func (b *SBaremetalInstance) SyncSSHConfig(conf types.SSHConfig) error { @@ -394,9 +409,62 @@ func (b *SBaremetalInstance) SyncStatusBackground() { } -func (b *SBaremetalInstance) SyncStatus(status string) { - log.Infof("sync baremetal %s status %s", b.GetName(), status) - // TODO +func PowerStatusToBaremetalStatus(status string) string { + switch status { + case types.POWER_STATUS_ON: + return baremetalstatus.RUNNING + case types.POWER_STATUS_OFF: + return baremetalstatus.READY + } + return baremetalstatus.UNKNOWN +} + +func (b *SBaremetalInstance) AutoSyncStatus() { + b.SyncStatus("", "") +} + +func (b *SBaremetalInstance) SyncStatus(status string, reason string) { + if status == "" { + powerStatus, err := b.GetPowerStatus() + if err != nil { + log.Errorf("Get power status error: %v", err) + } + status = PowerStatusToBaremetalStatus(powerStatus) + } + b.desc.Set("status", jsonutils.NewString(status)) + b.AutoSaveDesc() + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(status), "status") + if reason != "" { + params.Add(jsonutils.NewString(reason), "reason") + } + _, err := modules.Hosts.PerformAction(b.GetClientSession(), b.GetId(), "status", params) + if err != nil { + log.Errorf("Update baremetal %s status %s error: %v", b.GetId(), status, err) + return + } + log.Infof("Update baremetal %s to status %s", b.GetId(), status) +} + +func (b *SBaremetalInstance) AutoSyncAllStatus() { + b.SyncAllStatus("") +} + +func (b *SBaremetalInstance) DelayedSyncStatus() (jsonutils.JSONObject, error) { + b.AutoSyncAllStatus() + return nil, nil +} + +func (b *SBaremetalInstance) SyncAllStatus(status string) { + var err error + if status == "" { + status, err = b.GetPowerStatus() + if err != nil { + log.Errorf("Get power status error: %v", err) + } + } + b.SyncStatus(PowerStatusToBaremetalStatus(status), "") + // b.SyncServerStatus(PowerStatusToServerStatus(status)) } func (b *SBaremetalInstance) getNicInfo() *types.NicInfo { @@ -423,7 +491,7 @@ func (b *SBaremetalInstance) getNicByType(nicType string) *types.Nic { return nil } -func (b *SBaremetalInstance) getNicByMac(mac net.HardwareAddr) *types.Nic { +func (b *SBaremetalInstance) GetNicByMac(mac net.HardwareAddr) *types.Nic { nicInfo := b.getNicInfo() if nicInfo == nil { return nil @@ -457,6 +525,14 @@ func (b *SBaremetalInstance) GetIPMINic(cliMac net.HardwareAddr) *types.Nic { return nil } +func (b *SBaremetalInstance) GetIPMINicIPAddr() string { + nic := b.getNicByType(types.NIC_TYPE_IPMI) + if nic == nil { + return "" + } + return nic.IpAddr +} + func (b *SBaremetalInstance) GetDHCPConfig(cliMac net.HardwareAddr) (*dhcp.ResponseConfig, error) { /* if self.get_server() is not None and (self.get_task() is None or not self.get_task().__pxe_boot__) @@ -466,7 +542,7 @@ func (b *SBaremetalInstance) GetDHCPConfig(cliMac net.HardwareAddr) (*dhcp.Respo nic = self.get_nic_by_mac(mac) hostname = None */ - nic := b.getNicByMac(cliMac) + nic := b.GetNicByMac(cliMac) if nic == nil { return nil, fmt.Errorf("GetNicDHCPConfig no nic found") } @@ -520,8 +596,8 @@ func (b *SBaremetalInstance) GetTask() tasks.ITask { func (b *SBaremetalInstance) SetTask(task tasks.ITask) { b.taskQueue.AppendTask(task) if reflect.DeepEqual(task, b.taskQueue.GetTask()) { - //tasks.ExecuteTask(task, nil) log.Infof("Set task equal") + tasks.ExecuteTask(task, nil) } } @@ -535,13 +611,16 @@ func (b *SBaremetalInstance) InitAdminNetif( // sync status to PREPARE if nicType == types.NIC_TYPE_ADMIN && utils.IsInStringArray(b.GetStatus(), - []string{status.INIT, status.PREPARE, status.PREPARE_FAIL, status.UNKNOWN}) && + []string{baremetalstatus.INIT, + baremetalstatus.PREPARE, + baremetalstatus.PREPARE_FAIL, + baremetalstatus.UNKNOWN}) && b.GetTask() == nil && b.GetServer() == nil { b.SetTask(tasks.NewBaremetalServerPrepareTask(b)) - b.SyncStatus(status.PREPARE) + b.SyncStatus(baremetalstatus.PREPARE, "") } - nic := b.getNicByMac(cliMac) + nic := b.GetNicByMac(cliMac) if nic == nil || nic.WireId == "" { _, err := b.attachWire(cliMac, netConf.WireId, nicType) if err != nil { @@ -558,7 +637,7 @@ func (b *SBaremetalInstance) RegisterNetif( cliMac net.HardwareAddr, netConf *types.NetworkConfig, ) error { - nic := b.getNicByMac(cliMac) + nic := b.GetNicByMac(cliMac) if nic == nil || nic.WireId == "" || nic.WireId != netConf.WireId { desc, err := b.attachWire(cliMac, netConf.WireId, nic.Type) if err != nil { @@ -609,9 +688,36 @@ func (b *SBaremetalInstance) enableWire(mac net.HardwareAddr, ipAddr string, nic if nicType == types.NIC_TYPE_IPMI { params.Add(jsonutils.NewString("stepup"), "alloc_dir") // alloc bottom up } + log.Errorf("enable net if params: %s", params.String()) return modules.Hosts.PerformAction(session, b.GetId(), "enable-netif", params) } +func (b *SBaremetalInstance) GetIPMIConfig() *types.IPMIInfo { + conf := b.GetRawIPMIConfig() + if conf == nil || conf.Password == "" { + return nil + } + if conf.Username == "" { + sysInfo := types.IPMISystemInfo{} + err := b.desc.Unmarshal(&sysInfo, "sys_info") + if err != nil { + log.Errorf("Unmarshal get sys_info error: %v", err) + } + conf.Username = profiles.GetRootName(&sysInfo) + } + if conf.IpAddr == "" { + nicIPAddr := b.GetIPMINicIPAddr() + if nicIPAddr != "" { + conf.IpAddr = nicIPAddr + } + } + conf.Password = utils.Unquote(conf.Password) // XXX: remove quotes!!! + if conf.IpAddr == "" { + return nil + } + return conf +} + func (b *SBaremetalInstance) GetRawIPMIConfig() *types.IPMIInfo { ipmiInfo := types.IPMIInfo{} err := b.desc.Unmarshal(&ipmiInfo, "ipmi_info") @@ -633,6 +739,111 @@ func (b *SBaremetalInstance) GetServer() interface{} { return nil } -func (b *SBaremetalInstance) DoPowerShutdown(soft bool) { - log.Infof("DoPowerShutdown") +func (b *SBaremetalInstance) SetExistingIPMIIPAddr(ipAddr string) { + info, _ := b.desc.Get("ipmi_info") + if info == nil { + info = jsonutils.NewDict() + } + oIPAddr, _ := info.GetString("ip_addr") + if oIPAddr == "" { + info.(*jsonutils.JSONDict).Add(jsonutils.NewString(ipAddr), "ip_addr") + } + b.desc.Set("ipmi_info", info) +} + +func (b *SBaremetalInstance) GetIPMITool() *ipmitool.LanPlusIPMI { + conf := b.GetIPMIConfig() + if conf == nil { + return nil + } + return ipmitool.NewLanPlusIPMI(conf.IpAddr, conf.Username, conf.Password) +} + +func (b *SBaremetalInstance) GetIPMILanChannel() int { + conf := b.GetIPMIConfig() + if conf == nil { + return 0 + } + return conf.LanChannel +} + +func (b *SBaremetalInstance) DoPXEBoot() error { + log.Infof("Do PXE Boot ........., wait") + b.ClearSSHConfig() + ipmiCli := b.GetIPMITool() + if ipmiCli != nil { + return ipmitool.DoRebootToPXE(ipmiCli) + } + return fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId()) +} + +func (b *SBaremetalInstance) DoDiskBoot() error { + log.Infof("Do DISK Boot ........., wait") + b.ClearSSHConfig() + ipmiCli := b.GetIPMITool() + if ipmiCli != nil { + return ipmitool.DoRebootToDisk(ipmiCli) + } + return fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId()) +} + +func (b *SBaremetalInstance) GetPowerStatus() (string, error) { + ipmiCli := b.GetIPMITool() + if ipmiCli == nil { + return "", fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId()) + } + return ipmitool.GetChassisPowerStatus(ipmiCli) +} + +func (b *SBaremetalInstance) DoPowerShutdown(soft bool) error { + b.ClearSSHConfig() + ipmiCli := b.GetIPMITool() + if ipmiCli != nil { + if soft { + return ipmitool.DoSoftShutdown(ipmiCli) + } + return ipmitool.DoHardShutdown(ipmiCli) + } + return fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId()) +} + +func (b *SBaremetalInstance) GetStorageDriver() string { + driver, _ := b.desc.GetString("storage_driver") + return driver +} + +func (b *SBaremetalInstance) GetZoneId() string { + return b.manager.GetZoneId() +} + +func (b *SBaremetalInstance) DelayedRemove() (jsonutils.JSONObject, error) { + b.remove() + return nil, nil +} + +func (b *SBaremetalInstance) remove() { + b.manager.CleanBaremetal(b.GetId()) + b.manager = nil + b.desc = nil +} + +func (b *SBaremetalInstance) StartBaremetalMaintenanceTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) { + if jsonutils.QueryBoolean(data, "force_reboot", false) { + b.ClearSSHConfig() + } + if jsonutils.QueryBoolean(data, "guest_running", false) { + data.(*jsonutils.JSONDict).Set("soft_reboot", jsonutils.JSONTrue) + } + task := tasks.NewBaremetalMaintenanceTask(b, taskId, data) + b.SetTask(task) +} + +func (b *SBaremetalInstance) StartBaremetalUnmaintenanceTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) { + task := tasks.NewBaremetalUnmaintenanceTask(b, taskId, data) + b.SetTask(task) +} + +func (b *SBaremetalInstance) StartBaremetalReprepareTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) { + task := tasks.NewBaremetalReprepareTask(b, taskId, data) + b.SetTask(task) } diff --git a/pkg/baremetal/options/doc.go b/pkg/baremetal/options/doc.go new file mode 100644 index 0000000000..3db6801a41 --- /dev/null +++ b/pkg/baremetal/options/doc.go @@ -0,0 +1 @@ +package options // import "yunion.io/x/onecloud/pkg/baremetal/options" diff --git a/pkg/baremetal/options/options.go b/pkg/baremetal/options/options.go index 6bd5c6cd9b..bba3be34fc 100644 --- a/pkg/baremetal/options/options.go +++ b/pkg/baremetal/options/options.go @@ -23,6 +23,9 @@ type BaremetalOptions struct { TftpMaxTimeoutRetries int `default:"20" help:"Maximal tftp timeout retries, default is 20"` LengthyWorkerCount int `default:"8" help:"Parallel worker count for lengthy tasks"` ShortWorkerCount int `default:"8" help:"Parallel worker count for short-lived tasks"` + + DefaultIpmiPassword string `help:"Default IPMI passowrd"` + DefaultStrongIpmiPassword string `help:"Default strong IPMI passowrd"` } var ( diff --git a/pkg/baremetal/profiles/doc.go b/pkg/baremetal/profiles/doc.go new file mode 100644 index 0000000000..0751ba7b63 --- /dev/null +++ b/pkg/baremetal/profiles/doc.go @@ -0,0 +1 @@ +package profiles // import "yunion.io/x/onecloud/pkg/baremetal/profiles" diff --git a/pkg/baremetal/profiles/profiles.go b/pkg/baremetal/profiles/profiles.go new file mode 100644 index 0000000000..da309d0aa9 --- /dev/null +++ b/pkg/baremetal/profiles/profiles.go @@ -0,0 +1,108 @@ +package profiles + +import ( + "strings" + + "yunion.io/x/onecloud/pkg/baremetal/types" +) + +type IPMIProfile struct { + LanChannel []int + RootName string + RootId int + StrongPass bool +} + +func DefaultProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{1}, + RootName: "root", + RootId: 2, + } +} + +func InspurProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{8, 1}, + RootName: "admin", + RootId: 2, + } +} + +func LenovoProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{8}, + RootName: "root", + RootId: 2, + } +} + +func HpProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{2}, + RootName: "root", + RootId: 1, + } +} + +func HuaweiProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{1}, + RootName: "root", + RootId: 2, + StrongPass: true, + } +} + +func FoxconnProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{1}, + RootName: "root", + RootId: 2, + StrongPass: true, + } +} + +func QemuProfile() IPMIProfile { + return IPMIProfile{ + LanChannel: []int{8, 1}, + RootName: "root", + RootId: 2, + StrongPass: true, + } +} + +var ( + PROFILES map[string]IPMIProfile = map[string]IPMIProfile{ + "inspur": InspurProfile(), + "lenovo": LenovoProfile(), + "hp": HpProfile(), + "huawei": HuaweiProfile(), + "foxconn": FoxconnProfile(), + "qemu": QemuProfile(), + } +) + +func GetProfile(sysinfo *types.IPMISystemInfo) IPMIProfile { + profile, ok := PROFILES[strings.ToLower(sysinfo.Manufacture)] + if ok { + return profile + } + return DefaultProfile() +} + +func GetLanChannel(sysinfo *types.IPMISystemInfo) []int { + return GetProfile(sysinfo).LanChannel +} + +func GetRootId(sysinfo *types.IPMISystemInfo) int { + return GetProfile(sysinfo).RootId +} + +func GetRootName(sysinfo *types.IPMISystemInfo) string { + return GetProfile(sysinfo).RootName +} + +func IsStrongPass(sysinfo *types.IPMISystemInfo) bool { + return GetProfile(sysinfo).StrongPass +} diff --git a/pkg/baremetal/pxe/dhcp.go b/pkg/baremetal/pxe/dhcp.go index 7546a639ec..d6f247f031 100644 --- a/pkg/baremetal/pxe/dhcp.go +++ b/pkg/baremetal/pxe/dhcp.go @@ -44,12 +44,12 @@ type DHCPHandler struct { } func (h *DHCPHandler) ServeDHCP(pkt *dhcp.Packet) (*dhcp.Packet, error) { - log.Debugf("[DHCP] request: %s", pkt.DebugString()) + log.V(4).Debugf("[DHCP] request: %s", pkt.DebugString()) err := h.parsePacket(pkt) if err != nil { log.Errorf("[DHCP] parse packet error: %v", err) } - log.Infof("======Parse packet end: %#v", h) + log.V(4).Debugf("[DHCP] parse packet end: %#v", h) if h.RelayAddr.String() == "0.0.0.0" { return nil, fmt.Errorf("Request not from a DHCP relay, ignore mac: %s", h.ClientMac) @@ -160,12 +160,16 @@ func (h *DHCPHandler) fetchConfig() (*dhcp.ResponseConfig, error) { h.baremetalInstance = bmInstance ipmiNic := h.baremetalInstance.GetIPMINic(h.ClientMac) if ipmiNic != nil && ipmiNic.Mac == h.ClientMac.String() { - err = h.baremetalInstance.InitAdminNetif(h.ClientMac, h.netConfig, types.NIC_TYPE_ADMIN) + err = h.baremetalInstance.InitAdminNetif(h.ClientMac, h.netConfig, types.NIC_TYPE_IPMI) if err != nil { return nil, err } } else { - h.baremetalInstance.RegisterNetif(h.ClientMac, h.netConfig) + err = h.baremetalInstance.RegisterNetif(h.ClientMac, h.netConfig) + if err != nil { + log.Errorf("RegisterNetif error: %v", err) + return nil, err + } } return h.baremetalInstance.GetDHCPConfig(h.ClientMac) } diff --git a/pkg/baremetal/pxe/doc.go b/pkg/baremetal/pxe/doc.go new file mode 100644 index 0000000000..dbbbc21e32 --- /dev/null +++ b/pkg/baremetal/pxe/doc.go @@ -0,0 +1 @@ +package pxe // import "yunion.io/x/onecloud/pkg/baremetal/pxe" diff --git a/pkg/baremetal/pxe/pxe.go b/pkg/baremetal/pxe/pxe.go index 1e2f64c657..c3a4072b78 100644 --- a/pkg/baremetal/pxe/pxe.go +++ b/pkg/baremetal/pxe/pxe.go @@ -1,10 +1,8 @@ package pxe import ( + "fmt" "net" - "time" - - "github.com/pin/tftp" "yunion.io/x/jsonutils" @@ -105,12 +103,17 @@ func (s *Server) Serve() error { if s.TFTPPort == 0 { s.TFTPPort = portTFTP } + + tftpConn, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", s.Address, s.TFTPPort)) + if err != nil { + return err + } tftpHandler, err := NewTFTPHandler(s.TFTPRootDir, s.BaremetalManager) if err != nil { return err } - tftpSrv := tftp.NewServer(tftpHandler.ReadHandler, nil) - tftpSrv.SetTimeout(5 * time.Second) + //tftpSrv := tftp.NewServer(tftpHandler.ReadHandler, nil) + //tftpSrv.SetTimeout(5 * time.Second) dhcpSrv := dhcp.NewDHCPServer(s.Address, s.DHCPPort) @@ -119,9 +122,8 @@ func (s *Server) Serve() error { dhcpHandler := &DHCPHandler{baremetalManager: s.BaremetalManager} go func() { s.errs <- s.serveDHCP(dhcpSrv, dhcpHandler) }() - go func() { s.errs <- s.serveTFTP(tftpSrv) }() + go func() { s.errs <- s.serveTFTP(tftpConn, tftpHandler) }() err = <-s.errs - tftpSrv.Shutdown() return err } diff --git a/pkg/baremetal/pxe/tftp.go b/pkg/baremetal/pxe/tftp.go index e2762a277a..c92c19aab0 100644 --- a/pkg/baremetal/pxe/tftp.go +++ b/pkg/baremetal/pxe/tftp.go @@ -4,14 +4,15 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "net" "os" "path/filepath" "regexp" - "github.com/pin/tftp" - "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/tftp" ) var ( @@ -33,9 +34,8 @@ func NewTFTPHandler(rootDir string, baremetalManager IBaremetalManager) (*TFTPHa }, nil } -// ReadHandler is called when client starts file download from server -func (h *TFTPHandler) ReadHandler(filename string, rf io.ReaderFrom) error { - log.Debugf("TFTP request file: %s", filename) +// Handle is called when client starts file download from server +func (h *TFTPHandler) Handle(filename string, clientAddr net.Addr) (io.ReadCloser, int64, error) { regEx := regexp.MustCompile(PxeLinuxCfgPattern) matches := regEx.FindStringSubmatch(filename) @@ -49,71 +49,71 @@ func (h *TFTPHandler) ReadHandler(filename string, rf io.ReaderFrom) error { } mac, ok := paramsMap["mac"] if !ok { - return fmt.Errorf("request filename %q not found mac pattern", filename) + return nil, 0, fmt.Errorf("request filename %q not found mac pattern", filename) } macAddr, err := net.ParseMAC(mac) if err != nil { - return fmt.Errorf("Parse mac string %q error: %v", mac, err) + return nil, 0, fmt.Errorf("Parse mac string %q error: %v", mac, err) } - return h.sendPxeLinuxCfgResponse(macAddr, rf) + return h.sendPxeLinuxCfgResponse(macAddr, clientAddr) } - return h.sendFile(filename, rf) + return h.sendFile(filename, clientAddr) } -func (h *TFTPHandler) sendPxeLinuxCfgResponse(mac net.HardwareAddr, rf io.ReaderFrom) error { +func (h *TFTPHandler) sendPxeLinuxCfgResponse(mac net.HardwareAddr, _ net.Addr) (io.ReadCloser, int64, error) { log.Debugf("[TFTP] client mac: %s", mac) bmInstance := h.BaremetalManager.GetBaremetalByMac(mac) if bmInstance == nil { err := fmt.Errorf("Not found baremetal instance by mac: %s", mac) log.Errorf("Get baremetal error: %v", err) - return err + return nil, 0, err } respStr := bmInstance.GetTFTPResponse() log.Debugf("[TFTP] get tftp response config: %s", respStr) - size := len(respStr) + bs := []byte(respStr) + size := int64(len(bs)) buffer := bytes.NewBufferString(respStr) - rf.(tftp.OutgoingTransfer).SetSize(int64(size)) - - n, err := rf.ReadFrom(buffer) - if err != nil { - return err - } - - log.Debugf("[TFTP] %d bytes sent", n) - return nil + return ioutil.NopCloser(buffer), size, nil } -func (h *TFTPHandler) sendFile(filename string, rf io.ReaderFrom) error { +func (h *TFTPHandler) sendFile(filename string, _ net.Addr) (io.ReadCloser, int64, error) { filename = h.getFilePath(filename) + + st, err := os.Stat(filename) + if err != nil { + log.Errorf("TFTP stat file %q error: %v", filename, err) + return nil, 0, err + } + if !st.Mode().IsRegular() { + return nil, 0, fmt.Errorf("requested path %q is not a file", filename) + } + file, err := os.Open(filename) if err != nil { log.Errorf("TFTP open file %q error: %v", filename, err) - return err + return nil, 0, err } - n, err := rf.ReadFrom(file) - if err != nil { - return err - } - log.Debugf("[TFTP] %d bytes sent", n) - return nil + return file, st.Size(), err } func (h *TFTPHandler) getFilePath(fileName string) string { return filepath.Join(h.RootDir, fileName) } -func (s *Server) serveTFTP(srv *tftp.Server) error { - addr := fmt.Sprintf("%s:%d", s.Address, s.TFTPPort) - udpAddr, err := net.ResolveUDPAddr("udp4", addr) - if err != nil { - return err - } - conn, err := net.ListenUDP("udp4", udpAddr) - if err != nil { - return err - } +func (h *TFTPHandler) transferLog(clientAddr net.Addr, path string, err error) { + log.Debugf("TFTP transfer log clientAddr: %s, path: %s, error: %v", clientAddr, path, err) +} - srv.Serve(conn) +func (s *Server) serveTFTP(l net.PacketConn, handler *TFTPHandler) error { + ts := tftp.Server{ + Handler: handler.Handle, + InfoLog: func(msg string) { log.Debugf("TFTP msg: %s", msg) }, + TransferLog: handler.transferLog, + } + err := ts.Serve(l) + if err != nil { + return fmt.Errorf("TFTP server shut down: %v", err) + } return nil } diff --git a/pkg/baremetal/service/doc.go b/pkg/baremetal/service/doc.go new file mode 100644 index 0000000000..7dea0c8409 --- /dev/null +++ b/pkg/baremetal/service/doc.go @@ -0,0 +1 @@ +package service // import "yunion.io/x/onecloud/pkg/baremetal/service" diff --git a/pkg/baremetal/service/service.go b/pkg/baremetal/service/service.go index f8ff8a300c..52f0ee48e9 100644 --- a/pkg/baremetal/service/service.go +++ b/pkg/baremetal/service/service.go @@ -6,6 +6,7 @@ import ( "yunion.io/x/log" "yunion.io/x/onecloud/pkg/baremetal" + "yunion.io/x/onecloud/pkg/baremetal/handler" o "yunion.io/x/onecloud/pkg/baremetal/options" "yunion.io/x/onecloud/pkg/cloudcommon" "yunion.io/x/onecloud/pkg/cloudcommon/consts" @@ -18,7 +19,7 @@ func StartService() { cloudcommon.InitAuth(&o.Options.Options, startAgent) app := cloudcommon.InitApp(&o.Options.Options) - baremetal.InitHandlers(app) + handler.InitHandlers(app) cloudcommon.ServeForever(app, &o.Options.Options) } diff --git a/pkg/baremetal/status/doc.go b/pkg/baremetal/status/doc.go new file mode 100644 index 0000000000..b9a200b3fe --- /dev/null +++ b/pkg/baremetal/status/doc.go @@ -0,0 +1 @@ +package status // import "yunion.io/x/onecloud/pkg/baremetal/status" diff --git a/pkg/baremetal/sysutils/doc.go b/pkg/baremetal/sysutils/doc.go new file mode 100644 index 0000000000..140baac504 --- /dev/null +++ b/pkg/baremetal/sysutils/doc.go @@ -0,0 +1 @@ +package sysutils // import "yunion.io/x/onecloud/pkg/baremetal/sysutils" diff --git a/pkg/baremetal/sysutils/sysutils.go b/pkg/baremetal/sysutils/sysutils.go index db01389476..aeeb0a5351 100644 --- a/pkg/baremetal/sysutils/sysutils.go +++ b/pkg/baremetal/sysutils/sysutils.go @@ -2,6 +2,7 @@ package sysutils import ( "fmt" + "net" "strconv" "strings" @@ -23,11 +24,11 @@ func valueOfKeyword(line string, key string) *string { return nil } -func dumpMapToObject(data map[string]string, obj interface{}) error { +func DumpMapToObject(data map[string]string, obj interface{}) error { return jsonutils.Marshal(data).Unmarshal(obj) } -func ParseDMISysinfo(lines []string) (*types.DMIInfo, error) { +func ParseDMISysinfo(lines []string) (*types.DMISystemInfo, error) { if len(lines) == 0 { return nil, fmt.Errorf("Empty input") } @@ -46,8 +47,8 @@ func ParseDMISysinfo(lines []string) (*types.DMIInfo, error) { } } } - info := types.DMIInfo{} - err := dumpMapToObject(ret, &info) + info := types.DMISystemInfo{} + err := DumpMapToObject(ret, &info) if err != nil { return nil, err } @@ -156,7 +157,7 @@ func ParseNicInfo(lines []string) []*types.NicDevInfo { dat := strings.Split(line, " ") if len(dat) > 4 { dev := dat[0] - mac := dat[1] + mac, _ := net.ParseMAC(dat[1]) speed, _ := strconv.Atoi(dat[2]) up := false if dat[3] == "1" { diff --git a/pkg/baremetal/sysutils/sysutils_test.go b/pkg/baremetal/sysutils/sysutils_test.go index 75db4a5953..2ee5652425 100644 --- a/pkg/baremetal/sysutils/sysutils_test.go +++ b/pkg/baremetal/sysutils/sysutils_test.go @@ -235,8 +235,8 @@ func TestParseNicInfo(t *testing.T) { }, }, want: []*types.NicDevInfo{ - &types.NicDevInfo{Dev: "eth0", Mac: "00:22:25:0b:ab:49", Speed: 0, Up: true, Mtu: 1500}, - &types.NicDevInfo{Dev: "eth1", Mac: "00:22:25:0b:ab:50", Speed: 0, Up: false, Mtu: 1500}, + {Dev: "eth0", Mac: "00:22:25:0b:ab:49", Speed: 0, Up: true, Mtu: 1500}, + {Dev: "eth1", Mac: "00:22:25:0b:ab:50", Speed: 0, Up: false, Mtu: 1500}, }, }, } diff --git a/pkg/baremetal/tasks/base.go b/pkg/baremetal/tasks/base.go index 336fe81304..299d3913a2 100644 --- a/pkg/baremetal/tasks/base.go +++ b/pkg/baremetal/tasks/base.go @@ -5,10 +5,13 @@ import ( "context" "fmt" "sync" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/ssh" ) @@ -119,6 +122,7 @@ type ITask interface { // GetTaskId return remote service task id GetTaskId() string + GetClientSession() *mcclient.ClientSession GetTaskQueue() *TaskQueue // GetData return TaskData from region @@ -162,6 +166,7 @@ type IBaremetalTask interface { type SBaremetalTaskBase struct { Baremetal IBaremetal + userCred mcclient.TokenCredential stageFunc TaskStageFunc sshStageFunc SSHTaskStageFunc taskId string @@ -228,35 +233,112 @@ func (task *SBaremetalTaskBase) CallNextStage(iTask ITask, stage TaskStageFunc, ExecuteTask(iTask, args) } +func (task *SBaremetalTaskBase) GetClientSession() *mcclient.ClientSession { + return task.Baremetal.GetClientSession() +} + +func (self *SBaremetalTaskBase) EnsurePowerShutdown(soft bool) error { + log.Infof("EnsurePowerShutdown: soft=%v", soft) + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + startTime := time.Now() + maxWait := 60 * time.Second + for status == "" || status == types.POWER_STATUS_ON { + if time.Since(startTime).Seconds() >= maxWait.Seconds() && soft { + soft = false + } + self.Baremetal.DoPowerShutdown(soft) + time.Sleep(20 * time.Second) + status, err = self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + } + if status != types.POWER_STATUS_OFF { + return fmt.Errorf("Baremetal invalid status %s for shutdown", status) + } + return nil +} + +func (self *SBaremetalTaskBase) EnsurePowerUp(bootdev string) error { + log.Infof("EnsurePowerUp: bootdev=%s", bootdev) + var bootFunc func() error = nil + switch bootdev { + case "pxe": + bootFunc = self.Baremetal.DoPXEBoot + case "disk": + bootFunc = self.Baremetal.DoDiskBoot + } + if bootFunc == nil { + return fmt.Errorf("No boot func %s found", bootdev) + } + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + for status == "" || status == types.POWER_STATUS_OFF { + if status == types.POWER_STATUS_OFF { + err = bootFunc() + if err != nil { + return err + } + } + status, err = self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + if status == "" || status == types.POWER_STATUS_OFF { + time.Sleep(40 * time.Second) + status, err = self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + } + } + if status != types.POWER_STATUS_ON { + return fmt.Errorf("Baremetal invalid restart status: %s", status) + } + return nil +} + type IPXEBootTask interface { ITask - OnPXEBoot(ctx context.Context, args interface{}) error + OnPXEBoot(ctx context.Context, cli *ssh.Client, args interface{}) error } type SBaremetalPXEBootTaskBase struct { *SBaremetalTaskBase + pxeBootTask IPXEBootTask + startTime time.Time } func newBaremetalPXEBootTaskBase( baremetal IBaremetal, taskId string, data jsonutils.JSONObject, - pxeBootTask IPXEBootTask, ) *SBaremetalPXEBootTaskBase { baseTask := newBaremetalTaskBase(baremetal, taskId, data) self := &SBaremetalPXEBootTaskBase{ SBaremetalTaskBase: baseTask, } + return self + +} + +func (self *SBaremetalPXEBootTaskBase) InitPXEBootTask(pxeBootTask IPXEBootTask, data jsonutils.JSONObject) *SBaremetalPXEBootTaskBase { + self.pxeBootTask = pxeBootTask //OnInitStage(pxeBootTask) sshConf, _ := self.Baremetal.GetSSHConfig() if sshConf != nil && self.Baremetal.TestSSHConfig() { - pxeBootTask.SetStage(pxeBootTask.OnPXEBoot) + pxeBootTask.SetSSHStage(pxeBootTask.OnPXEBoot) + self.SSHExecute(pxeBootTask, sshConf.RemoteIP, sshConf.Password, nil) return self } // Do soft reboot if data != nil && jsonutils.QueryBoolean(data, "soft_boot", false) { - //self.SetStage(self.WaitForShutdown) - // self.start_time = time.time() + self.startTime = time.Now() self.Baremetal.DoPowerShutdown(true) self.CallNextStage(self, self.WaitForShutdown, nil) return self @@ -264,23 +346,37 @@ func newBaremetalPXEBootTaskBase( // shutdown and power up to PXE mode self.EnsurePowerShutdown(false) self.EnsurePowerUp("pxe") - // this stage will be called by baremetalInstance when pxe start - self.SetStage(pxeBootTask.OnPXEBoot) + // this stage will be called by baremetalInstance when pxe start notify + self.SetSSHStage(pxeBootTask.OnPXEBoot) return self } func (self *SBaremetalPXEBootTaskBase) WaitForShutdown(ctx context.Context, args interface{}) error { + self.SetStage(self.OnStopComplete) + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + if status == types.POWER_STATUS_OFF { + self.Execute(self, nil) + } else if time.Since(self.startTime) >= 90*time.Second { + err = self.Baremetal.DoPowerShutdown(false) + if err != nil { + return err + } + } + return nil +} + +func (self *SBaremetalPXEBootTaskBase) OnStopComplete(ctx context.Context, args interface{}) error { + err := self.EnsurePowerUp("pxe") + if err != nil { + return err + } + self.SetSSHStage(self.pxeBootTask.OnPXEBoot) return nil } func (self *SBaremetalPXEBootTaskBase) GetName() string { return "BaremetalPXEBootTaskBase" } - -func (self *SBaremetalPXEBootTaskBase) EnsurePowerShutdown(soft bool) { - -} - -func (self *SBaremetalPXEBootTaskBase) EnsurePowerUp(bootdev string) { - log.Infof("[EnsurePowerUp] bootdev: %s", bootdev) -} diff --git a/pkg/baremetal/tasks/basedeploy.go b/pkg/baremetal/tasks/basedeploy.go index 0d5d5327ab..0405205b24 100644 --- a/pkg/baremetal/tasks/basedeploy.go +++ b/pkg/baremetal/tasks/basedeploy.go @@ -5,21 +5,32 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/util/ssh" ) +type IServerBaseDeployTask interface { + IPXEBootTask + DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) +} + type SBaremetalServerBaseDeployTask struct { *SBaremetalPXEBootTaskBase + serverDeployTask IServerBaseDeployTask } func newBaremetalServerBaseDeployTask( baremetal IBaremetal, taskId string, data jsonutils.JSONObject, - queue *TaskQueue, + deployTask IServerBaseDeployTask, ) *SBaremetalServerBaseDeployTask { task := new(SBaremetalServerBaseDeployTask) - baseTask := newBaremetalPXEBootTaskBase(baremetal, taskId, data, task) + baseTask := newBaremetalPXEBootTaskBase(baremetal, taskId, data) task.SBaremetalPXEBootTaskBase = baseTask + baseTask.InitPXEBootTask(task, data) + task.serverDeployTask = deployTask return task } @@ -27,7 +38,42 @@ func (self *SBaremetalServerBaseDeployTask) GetName() string { return "BaremetalServerBaseDeployTask" } -func (self *SBaremetalServerBaseDeployTask) OnPXEBoot(ctx context.Context, args interface{}) error { +func (self *SBaremetalServerBaseDeployTask) GetFinishAction() string { + if self.data != nil { + action, _ := self.data.GetString("on_finish") + return action + } + return "" +} + +func (self *SBaremetalServerBaseDeployTask) OnPXEBoot(ctx context.Context, term *ssh.Client, args interface{}) error { log.Infof("%s called on stage pxeboot, args: %v", self.GetName(), args) + result, err := self.serverDeployTask.DoDeploys(term) + if err != nil { + return err + } + _, err = term.Run( + "/bin/sync", + "/sbin/sysctl -w vm.drop_caches=3", + ) + if err != nil { + return err + } + onFinishAction := self.GetFinishAction() + if utils.IsInStringArray(onFinishAction, []string{"restart", "shutdown"}) { + err = self.EnsurePowerShutdown(false) + if err != nil { + // TODO: maybe taskFail directly? + return err + } + if onFinishAction == "restart" { + err = self.EnsurePowerUp("disk") + if err != nil { + return err + } + } + } + self.Baremetal.AutoSyncAllStatus() + SetTaskComplete(self, result) return nil } diff --git a/pkg/baremetal/tasks/baseprepare.go b/pkg/baremetal/tasks/baseprepare.go index e6af3fe9af..6405e65801 100644 --- a/pkg/baremetal/tasks/baseprepare.go +++ b/pkg/baremetal/tasks/baseprepare.go @@ -3,12 +3,20 @@ package tasks import ( "fmt" "strings" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" + "yunion.io/x/pkg/util/seclib" + o "yunion.io/x/onecloud/pkg/baremetal/options" + "yunion.io/x/onecloud/pkg/baremetal/profiles" "yunion.io/x/onecloud/pkg/baremetal/sysutils" "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/util/ssh" ) @@ -55,8 +63,169 @@ func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error { return err } + ipmiInfo := &types.IPMIInfo{ + Present: ipmiEnable, + } + // set ipmi nic DHCP if ipmiEnable { - log.Errorf("TODO: ipmi enable") + sshIPMI := ipmitool.NewSSHIPMI(cli) + // ipmitool.SetSysInfo + ipmiSysInfo := sysInfo.ToIPMISystemInfo() + SetIPMILanPortShared(sshIPMI, ipmiSysInfo) + ipmiUser, ipmiPasswd, ipmiIpAddr := task.getIPMIUserPasswd(ipmiSysInfo) + ipmiInfo.Username = ipmiUser + ipmiInfo.Password = ipmiPasswd + + var ipmiLanChannel int = -1 + for _, lanChannel := range ipmitool.GetLanChannels(ipmiSysInfo) { + log.Infof("Try lan channel %d ...", lanChannel) + conf, err := ipmitool.GetLanConfig(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Get lan channel %d config error: %v", lanChannel, err) + continue + } + if conf.Mac == nil { + log.Errorf("Lan channel %d MAC address is empty", lanChannel) + continue + } + ipmiNic := &types.NicDevInfo{ + Mac: conf.Mac, + Up: false, + Speed: 100, + Mtu: 1500, + } + task.sendNicInfo(ipmiNic, -1, types.NIC_TYPE_IPMI, true, "") + err = ipmitool.SetLanUserPasswd(sshIPMI, lanChannel, ipmiUser, ipmiPasswd) + if err != nil { + log.Errorf("Lan channel %d set user password error: %v", lanChannel, err) + } + err = ipmitool.EnableLanAccess(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Lan channel %d enable lan access error: %v", lanChannel, err) + } + + tryAddrs := make([]string, 0) + if ipmiIpAddr != "" { + tryAddrs = append(tryAddrs, ipmiIpAddr) + } + if conf.IPAddr != "" && conf.IPAddr != ipmiIpAddr { + tryAddrs = append(tryAddrs, conf.IPAddr) + } + if len(tryAddrs) > 0 && !o.Options.ForceDhcpProbeIpmi { + for _, tryAddr := range tryAddrs { + log.Infof("IP addr found in IPMI config, try use %s as IPMI address", tryAddr) + ipConf, err := task.getIPMIIPConfig(tryAddr) + if err != nil { + log.Errorf("Failed to get IPMI ipconfig for %s", tryAddr) + continue + } + err = ipmitool.SetLanStatic(sshIPMI, lanChannel, ipConf.IPAddr, ipConf.Netmask, ipConf.Gateway) + if err != nil { + log.Errorf("Failed to set IPMI static net config %#v for %s", *ipConf, tryAddr) + continue + } + time.Sleep(1 * time.Second) + conf, err = ipmitool.GetLanConfig(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Failed to get lan config after set static network: %v", err) + continue + } + log.Infof("Get lan config %#v", *conf) + if conf.IPAddr == "" || conf.IPAddr != tryAddr { + log.Errorf("Failed to set ipmi lan channel %d static ipaddr", lanChannel) + continue + } + rmcpIPMI := ipmitool.NewLanPlusIPMI(tryAddr, ipmiUser, ipmiPasswd) + conf2, err := ipmitool.GetLanConfig(rmcpIPMI, lanChannel) + if err != nil { + log.Errorf("Failed to get lan channel %d config use RMCP mode: %v", lanChannel, err) + continue + } + if len(conf2.Mac) != 0 && + conf2.Mac.String() == conf.Mac.String() && + conf2.IPAddr != "" && conf2.IPAddr == tryAddr { + // make sure the ipaddr is a IPMI address + // enable the netif + if err := task.sendNicInfo(ipmiNic, -1, types.NIC_TYPE_IPMI, false, tryAddr); err != nil { + log.Errorf("Fail to set existing BMC IP address to %s", tryAddr) + } else { + ipmiInfo.IpAddr = tryAddr + ipmiLanChannel = lanChannel + break + } + } else { + log.Errorf("Use RMCP mode get invalid lan config: %#v", conf2) + } + if ipmiLanChannel >= 0 { + // found and set config on lanChannel + break + } + } + } + if len(tryAddrs) > 0 { + task.baremetal.SetExistingIPMIIPAddr(tryAddrs[0]) + } + + err = ipmitool.SetLanDHCP(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Set lan channel %d dhcp error: %v", lanChannel, err) + } + time.Sleep(1 * time.Second) + nic := task.baremetal.GetIPMINic(conf.Mac) + maxTries := 180 // wait 3 minutes + for tried := 0; nic != nil && nic.IpAddr == "" && tried < maxTries; tried++ { + nic = task.baremetal.GetIPMINic(conf.Mac) + } + if len(nic.IpAddr) == 0 { + err = ipmitool.DoBMCReset(sshIPMI) // do BMC reset to force DHCP request + if err != nil { + log.Errorf("Do BMC reset error: %v", err) + } + } + for tried := 0; nic != nil && nic.IpAddr == "" && tried < maxTries; tried++ { + nic = task.baremetal.GetIPMINic(conf.Mac) + } + if nic != nil && len(nic.IpAddr) == 0 { + log.Errorf("DHCP wait IPMI address fail, retry ...") + continue + } + log.Infof("DHCP get IPMI address succ, wait 2 seconds ...") + var tried int = 0 + for tried < maxTries { + time.Sleep(2 * time.Second) + lanConf, err := ipmitool.GetLanConfig(sshIPMI, lanChannel) + if err != nil { + log.Errorf("Get lan config at channel %d error: %v", lanChannel, err) + tried += 2 + continue + } + if lanConf.IPAddr == nic.IpAddr { + break + } + log.Infof("waiting IPMI DHCP address %s %s", lanConf.IPAddr, nic.IpAddr) + tried += 2 + } + if tried >= maxTries { + continue + } + err = ipmitool.SetLanStatic( + sshIPMI, + lanChannel, + nic.IpAddr, + nic.GetNetMask(), + nic.Gateway, + ) + if err != nil { + log.Errorf("Set lanChannel %d static net %#v error: %v", lanChannel, nic, err) + continue + } + ipmiInfo.IpAddr = nic.IpAddr + ipmiLanChannel = lanChannel + } + if ipmiLanChannel == -1 { + return fmt.Errorf("Fail to get IPMI address from DHCP") + } + ipmiInfo.LanChannel = ipmiLanChannel } adminNic := task.baremetal.GetAdminNic() @@ -65,7 +234,7 @@ func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error { updateInfo := make(map[string]interface{}) oname := fmt.Sprintf("BM%s", strings.Replace(adminNic.Mac, ":", "", -1)) if task.baremetal.GetName() == oname { - //updateInfo["name"] = fmt.Sprintf("BM-%s", strings.Replace(ipmiInfo.IPAddr, ".", "-", -1)) + updateInfo["name"] = fmt.Sprintf("BM-%s", strings.Replace(ipmiInfo.IpAddr, ".", "-", -1)) } updateInfo["access_ip"] = adminNic.IpAddr updateInfo["cpu_count"] = cpuInfo.Count @@ -73,14 +242,124 @@ func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error { updateInfo["cpu_desc"] = cpuInfo.Model updateInfo["cpu_mhz"] = cpuInfo.Freq updateInfo["cpu_cache"] = cpuInfo.Cache + updateInfo["mem_size"] = memInfo.Total updateInfo["sys_info"] = sysInfo updateInfo["sn"] = sysInfo.SN + // TODO: collect disk info + // updateInfo.update(task.collect_diskinfo(diskinfo)) + updateData := jsonutils.Marshal(updateInfo) + updateData.(*jsonutils.JSONDict).Update(ipmiInfo.ToPrepareParams()) + _, err = modules.Hosts.Update(task.getClientSession(), task.baremetal.GetId(), updateData) + if err != nil { + log.Errorf("Update baremetal info error: %v", err) + } + // task.sendStorageInfo(size) + for i := range nicsInfo { + if nicsInfo[i].Mac.String() == adminNic.GetMac().String() { + if i != 0 { + nicsInfo = append(nicsInfo[i:], nicsInfo[0:i]...) + } + break + } + } + err = task.removeAllNics() + if err != nil { + return err + } + for i := range nicsInfo { + err = task.sendNicInfo(nicsInfo[i], i, "", false, "") + if err != nil { + log.Errorf("Send nicinfo idx: %d, %#v error: %v", i, nicsInfo[i], err) + } + } + for _, nicInfo := range nicsInfo { + if nicInfo.Mac.String() != adminNic.GetMac().String() && nicInfo.Up { + err = task.doNicWireProbe(cli, nicInfo) + if err != nil { + log.Errorf("doNicWireProbe nic %#v error: %v", nicInfo, err) + } + } + } - log.Infof("Parse DMI info: %#v, \ncpuInfo: %#v", sysInfo, cpuInfo) + log.Infof("Prepare complete") return nil } -func getDMISysinfo(cli *ssh.Client) (*types.DMIInfo, error) { +func (task *sBaremetalPrepareTask) getIPMIUserPasswd(sysInfo *types.IPMISystemInfo) (string, string, string) { + var ( + ipmiUser string + ipmiPasswd string + ipmiIpAddr string + ) + ipmiUser = profiles.GetRootName(sysInfo) + isStrongPass := profiles.IsStrongPass(sysInfo) + if !isStrongPass && o.Options.DefaultIpmiPassword != "" { + ipmiPasswd = o.Options.DefaultIpmiPassword + } else if isStrongPass && o.Options.DefaultStrongIpmiPassword != "" { + ipmiPasswd = o.Options.DefaultStrongIpmiPassword + } else if isStrongPass && o.Options.DefaultIpmiPassword != "" { + ipmiPasswd = o.Options.DefaultIpmiPassword + } else { + ipmiPasswd = seclib.RandomPassword(20) + } + oldIPMIConf := task.baremetal.GetRawIPMIConfig() + if oldIPMIConf != nil { + if oldIPMIConf.Username != "" { + ipmiUser = oldIPMIConf.Username + } + if oldIPMIConf.Password != "" { + ipmiPasswd = oldIPMIConf.Password + } + if oldIPMIConf.IpAddr != "" { + ipmiIpAddr = oldIPMIConf.IpAddr + } + } + return ipmiUser, ipmiPasswd, ipmiIpAddr +} + +type ipmiIPConfig struct { + IPAddr string + Netmask string + Gateway string +} + +func (task *sBaremetalPrepareTask) getIPMIIPConfig(ipAddr string) (*ipmiIPConfig, error) { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(ipAddr), "ip") + listRet, err := modules.Networks.List(task.getClientSession(), params) + if err != nil { + return nil, err + } + if len(listRet.Data) != 1 { + return nil, fmt.Errorf("Invalid network list count: %d", len(listRet.Data)) + } + netObj := listRet.Data[0] + config := &ipmiIPConfig{} + config.IPAddr, _ = netObj.GetString("ipaddr") + maskLen, _ := netObj.Int("guest_ip_mask") + config.Netmask = netutils.Masklen2Mask(int8(maskLen)).String() + config.Gateway, _ = netObj.GetString("guest_gateway") + return config, nil +} + +func (task *sBaremetalPrepareTask) getClientSession() *mcclient.ClientSession { + return task.baremetal.GetClientSession() +} + +func (task *sBaremetalPrepareTask) removeAllNics() error { + resp, err := modules.Hosts.PerformAction( + task.getClientSession(), + task.baremetal.GetId(), + "remove-all-netifs", + nil, + ) + if err != nil { + return nil + } + return task.baremetal.SaveDesc(resp) +} + +func getDMISysinfo(cli *ssh.Client) (*types.DMISystemInfo, error) { ret, err := cli.Run("/usr/sbin/dmidecode -t 1") if err != nil { return nil, err @@ -127,3 +406,76 @@ func isIPMIEnable(cli *ssh.Client) (bool, error) { } return sysutils.ParseDMIIPMIInfo(ret), nil } + +func (task *sBaremetalPrepareTask) sendNicInfo(nic *types.NicDevInfo, idx int, nicType string, reset bool, ipAddr string) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(nic.Mac.String()), "mac") + params.Add(jsonutils.NewInt(int64(nic.Speed)), "rate") + if idx >= 0 { + params.Add(jsonutils.NewInt(int64(idx)), "index") + } + if nicType != "" { + params.Add(jsonutils.NewString(nicType), "nic_type") + } + params.Add(jsonutils.NewInt(int64(nic.Mtu)), "mtu") + params.Add(jsonutils.NewBool(nic.Up), "link_up") + if reset { + params.Add(jsonutils.JSONTrue, "reset") + } + if ipAddr != "" { + params.Add(jsonutils.NewString(ipAddr), "ip_addr") + params.Add(jsonutils.JSONTrue, "require_designated_ip") + } + resp, err := modules.Hosts.PerformAction( + task.getClientSession(), + task.baremetal.GetId(), + "add-netif", + params, + ) + if err != nil { + return err + } + task.baremetal.SaveDesc(resp) + return nil +} + +func (task *sBaremetalPrepareTask) sendStorageInfo(size int64) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewInt(size), "capacity") + params.Add(jsonutils.NewString(task.baremetal.GetZoneId()), "zone_id") + _, err := modules.Hosts.PerformAction(task.getClientSession(), task.baremetal.GetId(), "update-storage", params) + return err +} + +func (task *sBaremetalPrepareTask) doNicWireProbe(cli *ssh.Client, nic *types.NicDevInfo) error { + maxTries := 6 + for tried := 0; tried < maxTries; tried++ { + log.Infof("doNicWireProbe %v", nic) + _, err := cli.Run(fmt.Sprintf("/sbin/udhcpc -t 1 -T 3 -n -i %s", nic.Dev)) + if err != nil { + log.Errorf("/sbin/udhcpc error: %v", err) + } + nicInfo := task.baremetal.GetNicByMac(nic.Mac) + if nicInfo != nil && nicInfo.WireId != "" { + log.Infof("doNicWireProbe success, get result %#v", nicInfo) + break + } + } + return nil +} + +func SetIPMILanPortShared(cli ipmitool.IPMIExecutor, sysInfo *types.IPMISystemInfo) { + if !o.Options.IpmiLanPortShared { + return + } + oemName := strings.ToLower(sysInfo.Manufacture) + var err error + if strings.Contains(oemName, "huawei") { + err = ipmitool.SetHuaweiIPMILanPortShared(cli) + } else if strings.Contains(oemName, "dell") { + err = ipmitool.SetDellIPMILanPortShared(cli) + } + if err != nil { + log.Errorf("Set %s ipmi lan port shared failed: %v", oemName, err) + } +} diff --git a/pkg/baremetal/tasks/doc.go b/pkg/baremetal/tasks/doc.go new file mode 100644 index 0000000000..957ed5ea30 --- /dev/null +++ b/pkg/baremetal/tasks/doc.go @@ -0,0 +1 @@ +package tasks // import "yunion.io/x/onecloud/pkg/baremetal/tasks" diff --git a/pkg/baremetal/tasks/interface.go b/pkg/baremetal/tasks/interface.go index d0e8e4f079..9a2c884aea 100644 --- a/pkg/baremetal/tasks/interface.go +++ b/pkg/baremetal/tasks/interface.go @@ -1,14 +1,36 @@ package tasks import ( + "net" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/mcclient" ) type IBaremetal interface { + GetId() string + GetZoneId() string GetTaskQueue() *TaskQueue GetSSHConfig() (*types.SSHConfig, error) TestSSHConfig() bool - DoPowerShutdown(soft bool) GetAdminNic() *types.Nic GetName() string + GetClientSession() *mcclient.ClientSession + SaveDesc(desc jsonutils.JSONObject) error + GetNicByMac(net.HardwareAddr) *types.Nic + GetRawIPMIConfig() *types.IPMIInfo + GetIPMINic(mac net.HardwareAddr) *types.Nic + SetExistingIPMIIPAddr(ipAddr string) + + SyncStatus(status, reason string) + AutoSyncStatus() + SyncAllStatus(status string) + AutoSyncAllStatus() + + GetPowerStatus() (string, error) + DoPowerShutdown(soft bool) error + DoPXEBoot() error + DoDiskBoot() error } diff --git a/pkg/baremetal/tasks/maintenance.go b/pkg/baremetal/tasks/maintenance.go new file mode 100644 index 0000000000..5e65c1b9cb --- /dev/null +++ b/pkg/baremetal/tasks/maintenance.go @@ -0,0 +1,40 @@ +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalMaintenanceTask struct { + *SBaremetalPXEBootTaskBase +} + +func NewBaremetalMaintenanceTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) *SBaremetalMaintenanceTask { + task := new(SBaremetalMaintenanceTask) + baseTask := newBaremetalPXEBootTaskBase(baremetal, taskId, data) + task.SBaremetalPXEBootTaskBase = baseTask + baseTask.InitPXEBootTask(task, data) + return task +} + +func (self *SBaremetalMaintenanceTask) OnPXEBoot(ctx context.Context, term *ssh.Client, args interface{}) error { + sshConfig := term.GetConfig() + dataObj := map[string]interface{}{ + "username": sshConfig.Username, + "password": sshConfig.Password, + "ip": sshConfig.Host, + } + if jsonutils.QueryBoolean(self.data, "guest_running", false) { + dataObj["guest_running"] = true + } + self.Baremetal.AutoSyncStatus() + SetTaskComplete(self, jsonutils.Marshal(dataObj)) + return nil +} diff --git a/pkg/baremetal/tasks/prepare.go b/pkg/baremetal/tasks/prepare.go index 3638e230b2..b2499047e3 100644 --- a/pkg/baremetal/tasks/prepare.go +++ b/pkg/baremetal/tasks/prepare.go @@ -5,6 +5,7 @@ import ( "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/baremetal/status" "yunion.io/x/onecloud/pkg/util/ssh" ) @@ -32,6 +33,10 @@ func (self *SBaremetalServerPrepareTask) OnPXEBootRequest(ctx context.Context, c err := newBaremetalPrepareTask(self.Baremetal).DoPrepare(cli) if err != nil { log.Errorf("Prepare failed: %v", err) + self.Baremetal.SyncStatus(status.PREPARE_FAIL, err.Error()) + return err } + self.Baremetal.AutoSyncStatus() + SetTaskComplete(self, nil) return nil } diff --git a/pkg/baremetal/tasks/reprepare.go b/pkg/baremetal/tasks/reprepare.go new file mode 100644 index 0000000000..84bb848481 --- /dev/null +++ b/pkg/baremetal/tasks/reprepare.go @@ -0,0 +1,32 @@ +package tasks + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalReprepareTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalReprepareTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) *SBaremetalReprepareTask { + task := new(SBaremetalReprepareTask) + baseTask := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task +} + +func (self *SBaremetalReprepareTask) GetName() string { + return "BaremetalReprepareTask" +} + +func (self *SBaremetalReprepareTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + task := newBaremetalPrepareTask(self.Baremetal) + err := task.DoPrepare(term) + return nil, err +} diff --git a/pkg/baremetal/tasks/unmaintenance.go b/pkg/baremetal/tasks/unmaintenance.go new file mode 100644 index 0000000000..0655d94451 --- /dev/null +++ b/pkg/baremetal/tasks/unmaintenance.go @@ -0,0 +1,70 @@ +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + + baremetalstatus "yunion.io/x/onecloud/pkg/baremetal/status" + "yunion.io/x/onecloud/pkg/baremetal/types" +) + +type SBaremetalUnmaintenanceTask struct { + *SBaremetalTaskBase +} + +func NewBaremetalUnmaintenanceTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) *SBaremetalUnmaintenanceTask { + task := new(SBaremetalUnmaintenanceTask) + baseTask := newBaremetalTaskBase(baremetal, taskId, data) + task.SBaremetalTaskBase = baseTask + var err error + if jsonutils.QueryBoolean(task.data, "guest_running", false) { + err = task.EnsurePowerShutdown(false) + if err != nil { + SetTaskFail(task, err) + return task + } + err = task.EnsurePowerUp("disk") + if err != nil { + SetTaskFail(task, err) + return task + } + task.Baremetal.SyncStatus(baremetalstatus.RUNNING, "") + SetTaskComplete(task, nil) + return task + } + task.SetStage(task.WaitForStop) + err = task.EnsurePowerShutdown(true) + if err != nil { + SetTaskFail(task, err) + return task + } + ExecuteTask(task, nil) + return task +} + +func (self *SBaremetalUnmaintenanceTask) WaitForStop(ctx context.Context, args interface{}) error { + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return err + } + self.SetStage(self.OnStopComplete) + if status == types.POWER_STATUS_OFF { + ExecuteTask(self, nil) + } + return nil +} + +func (self *SBaremetalUnmaintenanceTask) OnStopComplete(ctx context.Context, args interface{}) error { + self.Baremetal.SyncStatus(baremetalstatus.READY, "") + SetTaskComplete(self, nil) + return nil +} + +func (self *SBaremetalUnmaintenanceTask) GetName() string { + return "BaremetalUnmaintenanceTask" +} diff --git a/pkg/baremetal/tasks/worker.go b/pkg/baremetal/tasks/worker.go index a8b5725b5e..410feb4e77 100644 --- a/pkg/baremetal/tasks/worker.go +++ b/pkg/baremetal/tasks/worker.go @@ -3,9 +3,11 @@ package tasks import ( "context" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/mcclient/modules" ) var baremetalTaskWorkerMan *appsrv.SWorkerManager @@ -35,10 +37,11 @@ func executeTask(task ITask, args interface{}) { } } -func SetTaskComplete(task ITask) { +func SetTaskComplete(task ITask, data jsonutils.JSONObject) { taskId := task.GetTaskId() if taskId != "" { - // TODO: notify region complete + session := task.GetClientSession() + modules.ComputeTasks.TaskComplete(session, taskId, data) } onTaskEnd(task) } @@ -46,7 +49,8 @@ func SetTaskComplete(task ITask) { func SetTaskFail(task ITask, err error) { taskId := task.GetTaskId() if taskId != "" { - // TODO: notify region task fail + session := task.GetClientSession() + modules.ComputeTasks.TaskFailed(session, taskId, err) } onTaskEnd(task) } diff --git a/pkg/baremetal/types/dmi.go b/pkg/baremetal/types/dmi.go deleted file mode 100644 index ab1254f4c2..0000000000 --- a/pkg/baremetal/types/dmi.go +++ /dev/null @@ -1 +0,0 @@ -package types diff --git a/pkg/baremetal/types/doc.go b/pkg/baremetal/types/doc.go new file mode 100644 index 0000000000..cf70c34158 --- /dev/null +++ b/pkg/baremetal/types/doc.go @@ -0,0 +1 @@ +package types // import "yunion.io/x/onecloud/pkg/baremetal/types" diff --git a/pkg/baremetal/types/ipmi.go b/pkg/baremetal/types/ipmi.go index b10b6c6b14..0f1a422425 100644 --- a/pkg/baremetal/types/ipmi.go +++ b/pkg/baremetal/types/ipmi.go @@ -1,5 +1,12 @@ package types +import "yunion.io/x/jsonutils" + +const ( + POWER_STATUS_ON = "on" + POWER_STATUS_OFF = "off" +) + type IPMIInfo struct { Username string `json:"username"` Password string `json:"password"` @@ -7,3 +14,19 @@ type IPMIInfo struct { Present bool `json:"present"` LanChannel int `json:"lan_channel"` } + +func (info IPMIInfo) ToPrepareParams() jsonutils.JSONObject { + data := jsonutils.NewDict() + if info.Username != "" { + data.Add(jsonutils.NewString(info.Username), "ipmi_username") + } + if info.Password != "" { + data.Add(jsonutils.NewString(info.Password), "ipmi_password") + } + if info.IpAddr != "" { + data.Add(jsonutils.NewString(info.IpAddr), "ipmi_ip_addr") + } + data.Add(jsonutils.NewBool(info.Present), "ipmi_present") + data.Add(jsonutils.NewInt(int64(info.LanChannel)), "ipmi_lan_channel") + return data +} diff --git a/pkg/baremetal/types/nic.go b/pkg/baremetal/types/nic.go index 6b100196a6..96d2257ebf 100644 --- a/pkg/baremetal/types/nic.go +++ b/pkg/baremetal/types/nic.go @@ -1,5 +1,11 @@ package types +import ( + "net" + + "yunion.io/x/pkg/util/netutils" +) + const ( NIC_TYPE_IPMI = "ipmi" NIC_TYPE_ADMIN = "admin" @@ -29,3 +35,11 @@ type Nic struct { Gateway string `json:"gateway"` LinkUp bool `json:"link_up"` } + +func (n Nic) GetNetMask() string { + return netutils.Masklen2Mask(n.MaskLen).String() +} + +func (n Nic) GetMac() net.HardwareAddr { + return getMac(n.Mac) +} diff --git a/pkg/baremetal/types/types.go b/pkg/baremetal/types/types.go index b0d64a182e..831bb82464 100644 --- a/pkg/baremetal/types/types.go +++ b/pkg/baremetal/types/types.go @@ -1,18 +1,29 @@ package types +import "net" + type SSHConfig struct { Username string `json:"username,omitempty"` RemoteIP string `json:"ip"` Password string `json:"password"` } -type DMIInfo struct { +type DMISystemInfo struct { Manufacture string `json:"manufacture"` Model string `json:"model"` Version string `json:"version,omitempty"` SN string `json:"sn"` } +func (info *DMISystemInfo) ToIPMISystemInfo() *IPMISystemInfo { + return &IPMISystemInfo{ + Manufacture: info.Manufacture, + Model: info.Model, + Version: info.Version, + SN: info.SN, + } +} + type CPUInfo struct { Count int `json:"count"` Model string `json:"desc"` @@ -29,11 +40,16 @@ type DMIMemInfo struct { } type NicDevInfo struct { - Dev string `json:"dev"` - Mac string `json:"mac"` - Speed int `json:"speed"` - Up bool `json:"up"` - Mtu int `json:"mtu"` + Dev string `json:"dev"` + Mac net.HardwareAddr `json:"mac"` + Speed int `json:"speed"` + Up bool `json:"up"` + Mtu int `json:"mtu"` +} + +func getMac(macStr string) net.HardwareAddr { + mac, _ := net.ParseMAC(macStr) + return mac } type DiskInfo struct { @@ -47,3 +63,24 @@ type DiskInfo struct { PCIClass string `json:"pci_class"` Driver string `json:"driver"` } + +type IPMISystemInfo struct { + Manufacture string `json:"manufacture"` + Model string `json:"model"` + SN string `json:"sn"` + Version string `json:"version"` + BSN string `json:"bsn"` +} + +type IPMILanConfig struct { + IPSrc string `json:"ipsrc"` + IPAddr string `json:"ipaddr"` + Netmask string `json:"netmask"` + Mac net.HardwareAddr `json:"mac"` + Gateway string `json:"gateway"` +} + +type IPMIBootFlags struct { + Dev string `json:"dev"` + Sol *bool `json:"sol"` +} diff --git a/pkg/baremetal/utils/detect_storages/doc.go b/pkg/baremetal/utils/detect_storages/doc.go new file mode 100644 index 0000000000..5b3f00e35a --- /dev/null +++ b/pkg/baremetal/utils/detect_storages/doc.go @@ -0,0 +1 @@ +package detect_storages // import "yunion.io/x/onecloud/pkg/baremetal/utils/detect_storages" diff --git a/pkg/baremetal/utils/ipmitool/doc.go b/pkg/baremetal/utils/ipmitool/doc.go new file mode 100644 index 0000000000..6cc2666658 --- /dev/null +++ b/pkg/baremetal/utils/ipmitool/doc.go @@ -0,0 +1 @@ +package ipmitool // import "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" diff --git a/pkg/baremetal/utils/ipmitool/ipmitool.go b/pkg/baremetal/utils/ipmitool/ipmitool.go new file mode 100644 index 0000000000..a67f469881 --- /dev/null +++ b/pkg/baremetal/utils/ipmitool/ipmitool.go @@ -0,0 +1,634 @@ +package ipmitool + +import ( + "fmt" + "net" + "strconv" + "strings" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/baremetal/profiles" + "yunion.io/x/onecloud/pkg/baremetal/sysutils" + "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/util/procutils" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type IPMIParser struct{} + +func (parser *IPMIParser) GetDefaultTimeout() time.Duration { + return 20 * time.Second +} + +var ( + BOOTDEVS = []string{"pxe", "disk", "safe", "diag", "cdrom", "bios"} + SOLOPTS = []string{"default", "skip", "enable"} +) + +type Args []string + +func newArgs(args ...interface{}) Args { + ret := make([]string, len(args)) + for i, arg := range args { + ret[i] = fmt.Sprintf("%v", arg) + } + return ret +} + +type IPMIExecutor interface { + GetMode() string + ExecuteCommand(args ...string) ([]string, error) +} + +type SSHIPMI struct { + IPMIParser + sshClient *ssh.Client +} + +func NewSSHIPMI(cli *ssh.Client) *SSHIPMI { + return &SSHIPMI{ + sshClient: cli, + } +} + +func (ipmi *SSHIPMI) GetMode() string { + return "ssh" +} + +func (ipmi *SSHIPMI) GetCommand(args ...string) *procutils.Command { + nArgs := []string{"-I", "open"} + nArgs = append(nArgs, args...) + return procutils.NewCommand("/usr/bin/ipmitool", nArgs...) +} + +func (ipmi *SSHIPMI) ExecuteCommand(args ...string) ([]string, error) { + cmd := ipmi.GetCommand(args...) + log.Debugf("[SSHIPMI] execute command: %s", cmd) + return ipmi.sshClient.Run(cmd.String()) +} + +type LanPlusIPMI struct { + IPMIParser + host string + user string + password string + port int +} + +func NewLanPlusIPMI(host, user, password string) *LanPlusIPMI { + return NewLanPlusIPMIWithPort(host, user, password, 623) +} + +func NewLanPlusIPMIWithPort(host, user, password string, port int) *LanPlusIPMI { + return &LanPlusIPMI{ + host: host, + user: user, + password: password, + port: port, + } +} + +func (ipmi *LanPlusIPMI) GetMode() string { + return "rmcp" +} + +func (ipmi *LanPlusIPMI) GetCommand(args ...string) *procutils.Command { + nArgs := []string{ + "--signal=KILL", + fmt.Sprintf("%s", ipmi.GetDefaultTimeout()), + "ipmitool", "-I", "lanplus", "-H", ipmi.host, + "-p", fmt.Sprintf("%d", ipmi.port), + "-U", ipmi.user, + "-P", ipmi.password, + } + nArgs = append(nArgs, args...) + return procutils.NewCommand("timeout", nArgs...) +} + +func (ipmi *LanPlusIPMI) ExecuteCommand(args ...string) ([]string, error) { + cmd := ipmi.GetCommand(args...) + log.Debugf("[LanPlusIPMI] execute command: %s", cmd.String()) + out, err := cmd.Run() + if err != nil { + return nil, err + } + return ssh.ParseOutput(out), nil +} + +func splitKeyValue(line string) (*string, *string) { + pos := strings.Index(line, ":") + if pos > 0 { + key := strings.TrimSpace(line[:pos]) + val := strings.TrimSpace(line[pos+1:]) + return &key, &val + } + return nil, nil +} + +func GetSysInfo(exector IPMIExecutor) (*types.IPMISystemInfo, error) { + // TODO: do cache + args := []string{"fru", "print", "0"} + lines, err := exector.ExecuteCommand(args...) + if err != nil { + return nil, err + } + ret := make(map[string]string) + + keys := map[string]string{ + "manufacture": "Product Manufacturer", + "model": "Product Name", + "bmodel": "Board Product", + "version": "Product Version", + "sn": "Product Serial", + "bsn": "Board Serial", + } + + for _, line := range lines { + key, val := splitKeyValue(line) + if key != nil { + for n, v := range keys { + if _, ok := ret[n]; v == *key && !ok { + ret[n] = *val + } + } + } + } + _, snOk := ret["sn"] + bsn, bsnOk := ret["bsn"] + if !snOk && bsnOk { + // no product serial + ret["sn"] = bsn + } + info := types.IPMISystemInfo{} + err = sysutils.DumpMapToObject(ret, &info) + return &info, err +} + +func GetLanChannels(sysinfo *types.IPMISystemInfo) []int { + return profiles.GetLanChannel(sysinfo) +} + +func GetDefaultLanChannel(sysinfo *types.IPMISystemInfo) int { + return GetLanChannels(sysinfo)[0] +} + +func GetRootId(sysinfo *types.IPMISystemInfo) int { + return profiles.GetRootId(sysinfo) +} + +func GetLanConfig(exector IPMIExecutor, channel int) (*types.IPMILanConfig, error) { + args := newArgs("lan", "print", channel) + lines, err := ExecuteCommands(exector, args) + if err != nil { + return nil, err + } + ret := new(types.IPMILanConfig) + for _, line := range lines { + key, val := splitKeyValue(line) + if key == nil { + continue + } + switch *key { + case "IP Address Source": + if *val == "Static Address" { + ret.IPSrc = "static" + } + case "IP Address": + ret.IPAddr = *val + case "Subnet Mask": + ret.Netmask = *val + case "MAC Address": + ret.Mac, _ = net.ParseMAC(*val) + case "Default Gateway IP": + ret.Gateway = *val + } + } + return ret, nil +} + +func tryExecuteCommand(exector IPMIExecutor, args ...string) ([]string, error) { + var err error + var ret []string + maxTries := 3 + for tried := 0; tried < maxTries; tried++ { + ret, err = exector.ExecuteCommand(args...) + if err == nil { + return ret, nil + } + sleepTime := time.Second * (1 << uint(tried)) + log.Errorf("Execute args %v error: %v, sleep %s then try again", args, err, sleepTime) + time.Sleep(sleepTime) + } + return ret, err +} + +func ExecuteCommands(exector IPMIExecutor, args ...Args) ([]string, error) { + results := make([]string, 0) + for _, arg := range args { + ret, err := tryExecuteCommand(exector, arg...) + if err != nil { + return nil, err + } + results = append(results, ret...) + } + return results, nil +} + +func doActions(exector IPMIExecutor, actionName string, args ...Args) error { + _, err := ExecuteCommands(exector, args...) + if err != nil { + return fmt.Errorf("Do %s action error: %v", actionName, err) + } + return nil +} + +func SetLanDHCP(exector IPMIExecutor, lanChannel int) error { + args := newArgs("lan", "set", lanChannel, "ipsrc", "dhcp") + return doActions(exector, "set_lan_dhcp", args) +} + +func SetLanStatic( + exector IPMIExecutor, + channel int, + ip string, + mask string, + gateway string, +) error { + config, err := GetLanConfig(exector, channel) + if err != nil { + return err + } + var argss []Args + if config.IPAddr == ip && config.Netmask == mask && config.Gateway == gateway { + argss = []Args{ + newArgs("lan", "set", channel, "ipsrc", "static"), + newArgs("lan", "set", channel, "ipaddr", ip), + newArgs("lan", "set", channel, "netmask", mask), + newArgs("lan", "set", channel, "defgw", "ipaddr", gateway), + } + } else { + argss = []Args{ + newArgs("lan", "set", channel, "ipaddr", ip), + newArgs("lan", "set", channel, "defgw", "ipaddr", gateway), + newArgs("lan", "set", channel, "netmask", mask), + newArgs("lan", "set", channel, "ipsrc", "static"), + } + } + return doActions(exector, "set_lan_static", argss...) +} + +func SetLanStaticIP(exector IPMIExecutor, channel int, ip string) error { + args := newArgs("lan", "set", channel, "ipaddr", ip) + return doActions(exector, "set_lan_static_ip", args) +} + +func setLanAccess(exector IPMIExecutor, channel int, access string) error { + args := []Args{ + newArgs("lan", "set", channel, "access", access), + // newArgs("lan", "set", channel, "auth", "ADMIN", "MD5"), + } + return doActions(exector, "set_lan_access", args...) +} + +func EnableLanAccess(exector IPMIExecutor, channel int) error { + return setLanAccess(exector, channel, "on") +} + +func SetLanUserPasswd(exector IPMIExecutor, channel int, user string, password string) error { + sysInfo, err := GetSysInfo(exector) + if err != nil { + return err + } + rootId := GetRootId(sysInfo) + args := []Args{ + newArgs("user", "enable", rootId), + // TODO: escape json string for password + newArgs("user", "set", "name", rootId, user, fmt.Sprintf("\"%s\"", password)), + newArgs("user", "priv", rootId, 4, channel), + } + err = doActions(exector, "set_lan_user_password", args...) + if err != nil { + return err + } + args = []Args{newArgs( + "raw", "0x06", "0x43", + fmt.Sprintf("0x%02x", 0xb0+channel), + fmt.Sprintf("0x%02x", rootId), "0x04", "0x00")} + err = doActions(exector, "set_lan_user_password2", args...) + if err == nil { + return nil + } + args = []Args{newArgs( + "channel", "setaccess", channel, + rootId, "link=on", "ipmi=on", + "callin=on", "privilege=4", + )} + return doActions(exector, "set_lan_user_password3", args...) +} + +func SetLanPasswd(exector IPMIExecutor, rootId string, password string) error { + // TODO: escape password + args := newArgs("user", "set", "password", rootId, fmt.Sprint("\"%s\"", password)) + return doActions(exector, "set_lan_passwd", args) +} + +func GetChassisPowerStatus(exector IPMIExecutor) (string, error) { + args := newArgs("chassis", "power", "status") + ret, err := ExecuteCommands(exector, args) + if err != nil { + return "", err + } + for _, line := range ret { + if strings.Contains(line, "Chassis Power is") { + data := strings.Split(line, " ") + status := strings.ToLower(strings.TrimSpace(data[len(data)-1])) + return status, nil + } + } + return "", fmt.Errorf("Unknown chassis status") +} + +func GetBootFlags(exector IPMIExecutor) (*types.IPMIBootFlags, error) { + args := newArgs("raw", "0x00", "0x09", "0x05", "0x00", "0x00") + ret, err := ExecuteCommands(exector, args) + if err != nil { + return nil, err + } + bytes, err := HexStr2Bytes(ret[0]) + if err != nil { + return nil, err + } + bootdevIdx := ((bytes[3] >> 2) & 0x0f) - 1 + bootdev := "" + if bootdevIdx >= 0 && int(bootdevIdx) < len(BOOTDEVS) { + bootdev = BOOTDEVS[bootdevIdx] + } + flags := &types.IPMIBootFlags{ + Dev: bootdev, + } + solIdx := (bytes[4] & 0x03) + if solIdx == 1 { + sol := false + flags.Sol = &sol + } else if solIdx == 2 { + sol := true + flags.Sol = &sol + } + log.Errorf("====bytes: %v, flags: %#v", bytes, flags) + return flags, nil +} + +func HexStr2Bytes(hs string) ([]int64, error) { + log.Errorf("======HexStr2Bytes: %#v", hs) + b := []int64{} + for _, x := range strings.Split(hs, " ") { + intV, err := strconv.ParseInt(x, 16, 64) + if err != nil { + return nil, err + } + b = append(b, intV) + } + return b, nil +} + +func GetACPIPowerStatus(exector IPMIExecutor) ([]int64, error) { + args := newArgs("raw", "0x06", "0x07") + ret, err := ExecuteCommands(exector, args) + if err != nil { + return nil, err + } + return HexStr2Bytes(ret[0]) +} + +func DoSoftShutdown(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "soft") + return doActions(exector, "do_soft_shutdown", args) +} + +func DoHardShutdown(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "off") + return doActions(exector, "do_hard_shutdown", args) +} + +func DoPowerOn(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "on") + return doActions(exector, "do_power_on", args) +} + +func DoPowerReset(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "reset") + return doActions(exector, "do_power_reset", args) +} + +func DoPowerCycle(exector IPMIExecutor) error { + args := newArgs("chassis", "power", "cycle") + return doActions(exector, "do_power_cycle", args) +} + +func DoReboot(exector IPMIExecutor) error { + maxTries := 10 + + var status string + var err error + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + + isValidStatus := func(s string) bool { + return utils.IsInStringArray(s, []string{types.POWER_STATUS_ON, types.POWER_STATUS_OFF}) + } + + for tried := 0; !isValidStatus(status) && tried <= maxTries; tried++ { + time.Sleep(1 * time.Second) + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + } + + if !isValidStatus(status) { + return fmt.Errorf("Unexpected status: %s", status) + } + + // do shutdown + if status == types.POWER_STATUS_ON { + err = DoHardShutdown(exector) + if err != nil { + return err + } + time.Sleep(1 * time.Second) + for tried := 0; tried < maxTries; tried++ { + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + if status == types.POWER_STATUS_OFF { + break + } + time.Sleep(1 * time.Second) + } + } + + // do power on + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + for tried := 0; status != types.POWER_STATUS_ON && tried < maxTries; tried++ { + err = DoPowerOn(exector) + if err != nil { + return err + } + time.Sleep(1 * time.Second) + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + } + + status, err = GetChassisPowerStatus(exector) + if err != nil { + return err + } + if status != types.POWER_STATUS_ON { + return fmt.Errorf("do reboot fail to poweron, current status: %s", status) + } + return nil +} + +func doRebootToFlag(exector IPMIExecutor, setFunc func(IPMIExecutor) error) error { + err := setFunc(exector) + if err != nil { + return err + } + return DoReboot(exector) +} + +func SetRebootToDisk(exector IPMIExecutor) error { + return SetBootFlags(exector, "disk", tristate.True, true) +} + +func DoRebootToDisk(exector IPMIExecutor) error { + return doRebootToFlag(exector, SetRebootToDisk) +} + +func SetRebootToPXE(exector IPMIExecutor) error { + return SetBootFlagPXE(exector) +} + +func DoRebootToPXE(exector IPMIExecutor) error { + return doRebootToFlag(exector, SetRebootToPXE) +} + +func SetRebootToBIOS(exector IPMIExecutor) error { + return SetBootFlags(exector, "bios", tristate.True, false) +} + +func DoRebootToBIOS(exector IPMIExecutor) error { + return doRebootToFlag(exector, SetRebootToBIOS) +} + +func SetBootFlagPXE(exector IPMIExecutor) error { + return setBootFlagsV2(exector, "pxe") +} + +func SetBootFlags( + exector IPMIExecutor, + bootdev string, + sol tristate.TriState, + enablePersistent bool, +) error { + err := setBootFlagsV1(exector, bootdev, sol, enablePersistent) + if err == nil { + return nil + } + return setBootFlagsV2(exector, bootdev) +} + +func setBootFlagsV1( + exector IPMIExecutor, + bootdev string, + sol tristate.TriState, + enablePersistent bool, +) error { + cmd := []interface{}{"raw", "0x00", "0x08", "0x05"} + bootdevIdx := 0 + if ok, idx := utils.InStringArray(bootdev, BOOTDEVS); ok { + bootdevIdx = idx + 1 + } else { + return fmt.Errorf("Illegal bootdev %s", bootdev) + } + valid := 0x80 + if enablePersistent { + valid = valid + 0x40 + } + solIdx := 0 + if !sol.IsNone() { + if sol.IsTrue() { + solIdx = 2 + } else { + solIdx = 1 + } + } + for _, x := range []int{valid, bootdevIdx << 2, solIdx, 0, 0} { + cmd = append(cmd, fmt.Sprintf("0x%02x", x)) + } + return doActions(exector, "set_boot_flags_v1", newArgs(cmd...)) +} + +func setBootFlagsV2(exector IPMIExecutor, bootdev string) error { + return doActions( + exector, + fmt.Sprintf("set_boot_flag_%s", bootdev), + newArgs("chassis", "bootdev", bootdev), + ) +} + +func GetIPMILanPort(exector IPMIExecutor) (string, error) { + ret, err := ExecuteCommands(exector, newArgs("delloem", "lan", "get")) + if err != nil { + return "", err + } + return ret[1], nil +} + +func SetDellIPMILanPortShared(exector IPMIExecutor) error { + args1 := newArgs("delloem", "lan", "set", "shared") + args2 := newArgs("delloem", "lan", "set", "shared", "with", "lom1") + err2 := doActions(exector, "_dell_set_ipmi_lan_port_shared_02", args2) + if err2 != nil { + return doActions(exector, "_dell_set_ipmi_lan_port_shared_01", args1) + } + return nil +} + +func SetHuaweiIPMILanPortShared(exector IPMIExecutor) error { + args := []Args{ + newArgs( + "raw", "0xc", "0x1", "0x1", "0xd7", "0xdb", + "0x07", "0x00", "0x2", + ), + newArgs( + "raw", "0x30", "0x93", "0xdb", "0x07", "0x00", + "0x05", "0x0d", "0x0", "0x0", "0x1", "0x0", + ), + } + return doActions(exector, "_huawei_set_ipmi_lan_port_shared", args...) +} + +func SetIPMILanPortDedicated(exector IPMIExecutor) error { + return doActions( + exector, + "set_ipmi_lan_port_dedicated", + newArgs("delloem", "lan", "set", "dedicated"), + ) +} + +func DoBMCReset(exector IPMIExecutor) error { + return doActions(exector, "do_bmc_reset", newArgs("mc", "reset", "cold")) +} diff --git a/pkg/baremetal/utils/ipmitool/ipmitool_test.go b/pkg/baremetal/utils/ipmitool/ipmitool_test.go new file mode 100644 index 0000000000..2a05ffefa4 --- /dev/null +++ b/pkg/baremetal/utils/ipmitool/ipmitool_test.go @@ -0,0 +1,32 @@ +package ipmitool + +import ( + "reflect" + "testing" +) + +func TestGetSysInfo(t *testing.T) { + type args struct { + exector IPMIExecutor + } + tests := []struct { + name string + args args + want *SystemInfo + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetSysInfo(tt.args.exector) + if (err != nil) != tt.wantErr { + t.Errorf("GetSysInfo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetSysInfo() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/cloudcommon/dhcp/helpers.go b/pkg/cloudcommon/dhcp/helpers.go index e5f91fc8e4..cadd5c7552 100644 --- a/pkg/cloudcommon/dhcp/helpers.go +++ b/pkg/cloudcommon/dhcp/helpers.go @@ -131,10 +131,10 @@ func makeDHCPReplyPacket(pkt *Packet, conf *ResponseConfig, msgType MessageType) } func IsPXERequest(pkt *Packet) bool { - if pkt.Type != MsgDiscover { - log.Warningf("packet is %s, not %s", pkt.Type, MsgDiscover) - return false - } + //if pkt.Type != MsgDiscover { + //log.Warningf("packet is %s, not %s", pkt.Type, MsgDiscover) + //return false + //} if pkt.Options[OptClientArchitecture] == nil { log.Warningf("not a PXE boot request (missing option 93)") diff --git a/pkg/cloudcommon/tftp/doc.go b/pkg/cloudcommon/tftp/doc.go new file mode 100644 index 0000000000..e7d6a24323 --- /dev/null +++ b/pkg/cloudcommon/tftp/doc.go @@ -0,0 +1 @@ +package tftp // import "yunion.io/x/onecloud/pkg/cloudcommon/tftp" diff --git a/pkg/cloudcommon/tftp/tftp.go b/pkg/cloudcommon/tftp/tftp.go new file mode 100644 index 0000000000..77cb20fe77 --- /dev/null +++ b/pkg/cloudcommon/tftp/tftp.go @@ -0,0 +1,395 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tftp implements a read-only TFTP server. +package tftp + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "strconv" + "time" +) + +const ( + // DefaultWriteTimeout is the duration a client has to acknowledge + // a data packet from the server. This can be overridden by + // setting Server.WriteTimeout. + DefaultWriteTimeout = 2 * time.Second + // DefaultWriteAttempts is the maximum number of times a single + // packet will be (re)sent before timing out a client. This can be + // overridden by setting Server.WriteAttempts. + DefaultWriteAttempts = 5 + // DefaultBlockSize is the maximum block size used to send data to + // clients. The server will respect a request for a smaller block + // size, but requests for larger block sizes will be clamped to + // DefaultBlockSize. This can be overridden by setting + // Server.MaxBlockSize. + DefaultBlockSize = 1450 + + // maxErrorSize is the largest error message string that will be + // sent to the client without truncation. + maxErrorSize = 500 +) + +// A Handler provides bytes for a file. +// +// If size is non-zero, it must be equal to the number of bytes in +// file. The server will offer the "tsize" extension to clients that +// request it. +// +// Note that some clients (particularly firmware TFTP clients) can be +// very capricious about servers not supporting all the options that +// they request, so passing a size of 0 may cause TFTP transfers to +// fail for some clients. +type Handler func(path string, clientAddr net.Addr) (file io.ReadCloser, size int64, err error) + +// A Server defines parameters for running a TFTP server. +type Server struct { + Handler Handler // handler to invoke for requests + + // WriteTimeout sets the duration to wait for the client to + // acknowledge a data packet. Defaults to DefaultWriteTimeout. + WriteTimeout time.Duration + // WriteAttempts sets how many times a packet will be (re)sent + // before timing out the client and aborting the transfer. If 0, + // uses DefaultWriteAttempts. + WriteAttempts int + // MaxBlockSize sets the maximum block size used for file + // transfers. If 0, uses DefaultBlockSize. + MaxBlockSize int64 + + // InfoLog specifies an optional logger for informational + // messages. If nil, informational messages are suppressed. + InfoLog func(msg string) + // TransferLog specifies an optional logger for completed + // transfers. A successful transfer is logged with err == nil. If + // nil, transfer logs are suppressed. + TransferLog func(clientAddr net.Addr, path string, err error) + + // Dial specifies a function to use when setting up a "connected" + // UDP socket to a TFTP client. While this is mostly here for + // testing, it can also be used to implement advanced relay + // functionality (e.g. serving TFTP through SOCKS). If nil, + // net.Dial is used. + Dial func(network, addr string) (net.Conn, error) +} + +// ListenAndServe listens on the UDP network address addr and then +// calls Serve to handle TFTP requests. If addr is blank, ":69" is +// used. +func (s *Server) ListenAndServe(addr string) error { + if addr == "" { + addr = ":69" + } + l, err := net.ListenPacket("udp", addr) + if err != nil { + return err + } + defer l.Close() + s.infoLog("TFTP listening on %s", l.LocalAddr()) + return s.Serve(l) +} + +// Serve accepts requests on listener l, creating a new transfer +// goroutine for each. The transfer goroutines use s.Handler to get +// bytes, and transfers them to the client. +func (s *Server) Serve(l net.PacketConn) error { + if s.Handler == nil { + return errors.New("can't serve, Handler is nil") + } + if err := l.SetDeadline(time.Time{}); err != nil { + return err + } + buf := make([]byte, 512) + for { + n, addr, err := l.ReadFrom(buf) + if err != nil { + return err + } + + req, err := parseRRQ(buf[:n]) + if err != nil { + s.infoLog("bad request from %q: %s", addr, err) + continue + } + + go s.transferAndLog(addr, req) + } + +} + +func (s *Server) infoLog(msg string, args ...interface{}) { + if s.InfoLog != nil { + s.InfoLog(fmt.Sprintf(msg, args...)) + } +} + +func (s *Server) transferLog(addr net.Addr, path string, err error) { + if s.TransferLog != nil { + s.TransferLog(addr, path, err) + } +} + +func (s *Server) transferAndLog(addr net.Addr, req *rrq) { + err := s.transfer(addr, req) + if err != nil { + err = fmt.Errorf("%q: %s", addr, err) + } + s.transferLog(addr, req.Filename, err) +} + +func (s *Server) transfer(addr net.Addr, req *rrq) error { + d := s.Dial + if d == nil { + d = net.Dial + } + conn, err := d("udp", addr.String()) + if err != nil { + return fmt.Errorf("creating socket: %s", err) + } + defer conn.Close() + + file, size, err := s.Handler(req.Filename, addr) + if err != nil { + conn.Write(tftpError("failed to get file")) + return fmt.Errorf("getting file bytes: %s", err) + } + defer file.Close() + + var b bytes.Buffer + if req.BlockSize != 0 || (req.WantSize && size != 0) { + // Client requested options, need to OACK them before sending + // data. + b.WriteByte(0) + b.WriteByte(6) + + if req.BlockSize != 0 { + maxBlockSize := s.MaxBlockSize + if maxBlockSize <= 0 { + maxBlockSize = DefaultBlockSize + } + if req.BlockSize > maxBlockSize { + s.infoLog("clamping blocksize to %q: %d -> %d", addr, req.BlockSize, maxBlockSize) + req.BlockSize = maxBlockSize + } + + b.WriteString("blksize") + b.WriteByte(0) + b.WriteString(strconv.FormatInt(req.BlockSize, 10)) + b.WriteByte(0) + } + + if req.WantSize && size != 0 { + b.WriteString("tsize") + b.WriteByte(0) + b.WriteString(strconv.FormatInt(size, 10)) + b.WriteByte(0) + } + + if err := s.send(conn, b.Bytes(), 0); err != nil { + return fmt.Errorf("sending OACK: %s", err) + } + b.Reset() + } + if req.BlockSize == 0 { + // Client didn't negotiate, use classic blocksize from RFC. + req.BlockSize = 512 + } + + seq := uint16(1) + b.Grow(int(req.BlockSize + 4)) + b.WriteByte(0) + b.WriteByte(3) + for { + b.Truncate(2) + if err = binary.Write(&b, binary.BigEndian, seq); err != nil { + conn.Write(tftpError("internal server error")) + return fmt.Errorf("writing seqnum: %s", err) + } + n, err := io.CopyN(&b, file, req.BlockSize) + if err != nil && err != io.EOF { + conn.Write(tftpError("internal server error")) + return fmt.Errorf("reading bytes for block %d: %s", seq, err) + } + if err = s.send(conn, b.Bytes(), seq); err != nil { + conn.Write(tftpError("timeout")) + return fmt.Errorf("sending data packet %d: %s", seq, err) + } + seq++ + if n < req.BlockSize { + // Transfer complete + return nil + } + } +} + +func (s *Server) send(conn net.Conn, b []byte, seq uint16) error { + timeout := s.WriteTimeout + if timeout <= 0 { + timeout = DefaultWriteTimeout + } + attempts := s.WriteAttempts + if attempts <= 0 { + attempts = DefaultWriteAttempts + } + +Attempt: + for attempt := 0; attempt < attempts; attempt++ { + if _, err := conn.Write(b); err != nil { + return err + } + + conn.SetReadDeadline(time.Now().Add(timeout)) + + var recv [256]byte + for { + n, err := conn.Read(recv[:]) + if err != nil { + if t, ok := err.(net.Error); ok && t.Timeout() { + continue Attempt + } + return err + } + + if n < 4 { // packet too small + continue + } + switch binary.BigEndian.Uint16(recv[:2]) { + case 4: + if binary.BigEndian.Uint16(recv[2:4]) == seq { + return nil + } + case 5: + msg, _, _ := tftpStr(recv[4:]) + return fmt.Errorf("client aborted transfer: %s", msg) + } + } + } + + return errors.New("timeout waiting for ACK") +} + +type rrq struct { + Filename string + BlockSize int64 + WantSize bool +} + +func parseRRQ(bs []byte) (*rrq, error) { + // Smallest a useful TFTP packet can be is 6 bytes: 2b opcode, 1b + // filename, 1b null, 1b mode, 1b null. + if len(bs) < 6 || binary.BigEndian.Uint16(bs[:2]) != 1 { + return nil, errors.New("not an RRQ packet") + } + + fname, bs, err := tftpStr(bs[2:]) + if err != nil { + return nil, fmt.Errorf("reading filename: %s", err) + } + + mode, bs, err := tftpStr(bs) + if err != nil { + return nil, fmt.Errorf("reading mode: %s", err) + } + if mode != "octet" { + // Only support octet mode, because in practice that's the + // only remaining sensible use of TFTP (i.e. PXE booting) + return nil, fmt.Errorf("unsupported transfer mode %q", mode) + } + + req := &rrq{ + Filename: fname, + } + + for len(bs) > 0 { + opt, rest, err := tftpStr(bs) + if err != nil { + return nil, fmt.Errorf("reading option name: %s", err) + } + bs = rest + val, rest, err := tftpStr(bs) + if err != nil { + return nil, fmt.Errorf("reading option %q value: %s", opt, err) + } + bs = rest + if opt != "blksize" { + if opt == "tsize" { + req.WantSize = true + } + continue + } + size, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return nil, fmt.Errorf("non-integer block size value %q", val) + } + if size < 8 || size > 65464 { + return nil, fmt.Errorf("unsupported block size %q", size) + } + req.BlockSize = size + } + + return req, nil +} + +// tftpError constructs an ERROR packet. +// +// The error is coerced to the sensible subset of "netascii", namely +// the printable ASCII characters plus newline. +func tftpError(msg string) []byte { + if len(msg) > maxErrorSize { + msg = msg[:maxErrorSize] + } + var ret bytes.Buffer + ret.Grow(len(msg) + 5) + ret.Write([]byte{0, 5, 0, 0}) // generic "see message" error packet + for _, b := range msg { + switch { + case b >= 0x20 && b <= 0x7E: + ret.WriteRune(b) + case b == '\r': + // Assume this is the start of a CRLF sequence and just + // swallow the CR. The LF will output CRLF, see + // below. Also, please stop using CRLF line termination in + // Go. + case b == '\n': + ret.WriteString("\r\n") + default: + ret.WriteByte('?') + } + } + ret.WriteByte(0) + return ret.Bytes() +} + +// tftpStr extracts a null-terminated string from the given bytes, and +// returns any remaining bytes. +// +// String content is checked to be a "read-useful" subset of +// "netascii", itself a subset of ASCII. Specifically, all byte values +// must fall in the range 0x20 to 0x7E inclusive. +func tftpStr(bs []byte) (str string, remaining []byte, err error) { + for i, b := range bs { + if b == 0 { + return string(bs[:i]), bs[i+1:], nil + } else if b < 0x20 || b > 0x7E { + return "", nil, fmt.Errorf("invalid netascii byte %q at offset %d", b, i) + } + } + return "", nil, errors.New("no null terminated string found") +} diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 414fdfebb5..1141042ebc 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -2749,6 +2749,7 @@ func (self *SHost) StartPrepareTask(ctx context.Context, userCred mcclient.Token if len(onfinish) > 0 { data.Set("on_finish", jsonutils.NewString(onfinish)) } + self.SetStatus(userCred, BAREMETAL_PREPARE, "") if task, err := taskman.TaskManager.NewTask(ctx, "BaremetalPrepareTask", self, userCred, data, parentTaskId, "", nil); err != nil { log.Errorf(err.Error()) return err @@ -2957,6 +2958,7 @@ func (self *SHost) EnableNetif(ctx context.Context, userCred mcclient.TokenCrede if bn != nil { return nil } + log.Errorf("==========EnableNetif %#v, net: %s, ipAddr: %s, allocDir: %s, reserve: %v, requireDesignatedIp: %v", netif, network, ipAddr, allocDir, reserve, requireDesignatedIp) var net *SNetwork var err error if len(ipAddr) > 0 { diff --git a/pkg/mcclient/modules/mod_tasks.go b/pkg/mcclient/modules/mod_tasks.go index 49040264b2..e9a21ff7d0 100644 --- a/pkg/mcclient/modules/mod_tasks.go +++ b/pkg/mcclient/modules/mod_tasks.go @@ -19,6 +19,21 @@ type ComputeTasksManager struct { ResourceManager } +func init() { + Tasks = NewITSMManager("task", "taskman", + []string{"id", "status", "create_by", "update_by", "delete_by", "gmt_create", "gmt_modified", "gmt_delete", "is_deleted", "project_id", "remark", "instance_id", "business_id", "task_id", "task_type", "task_name", "task_status", "current_approver", "approver_name", "receive_time", "finish_time", "result", "content", "common_start_string"}, + []string{"id", "status", "create_by", "update_by", "delete_by", "gmt_create", "gmt_modified", "gmt_delete", "is_deleted", "project_id", "remark", "instance_id", "business_id", "task_id", "task_type", "task_name", "task_status", "current_approver", "approver_name", "receive_time", "finish_time", "result", "content", "common_start_string"}, + ) + register(&Tasks) + + ComputeTasks = ComputeTasksManager{ + ResourceManager: NewComputeManager("task", "tasks", + []string{}, + []string{"Id", "Obj_name", "Obj_Id", "Task_name", "Stage", "Created_at"}), + } + registerCompute(&ComputeTasks) +} + func (man ComputeTasksManager) TaskComplete(session *mcclient.ClientSession, taskId string, params jsonutils.JSONObject) { for i := 0; i < 3; i++ { _, err := man.PerformClassAction(session, taskId, params) @@ -37,18 +52,3 @@ func (man ComputeTasksManager) TaskFailed(session *mcclient.ClientSession, taskI params.Add(jsonutils.NewString(err.Error()), "__reason__") man.TaskComplete(session, taskId, params) } - -func init() { - Tasks = NewITSMManager("task", "taskman", - []string{"id", "status", "create_by", "update_by", "delete_by", "gmt_create", "gmt_modified", "gmt_delete", "is_deleted", "project_id", "remark", "instance_id", "business_id", "task_id", "task_type", "task_name", "task_status", "current_approver", "approver_name", "receive_time", "finish_time", "result", "content", "common_start_string"}, - []string{"id", "status", "create_by", "update_by", "delete_by", "gmt_create", "gmt_modified", "gmt_delete", "is_deleted", "project_id", "remark", "instance_id", "business_id", "task_id", "task_type", "task_name", "task_status", "current_approver", "approver_name", "receive_time", "finish_time", "result", "content", "common_start_string"}, - ) - register(&Tasks) - - ComputeTasks = ComputeTasksManager{ - ResourceManager: NewComputeManager("task", "tasks", - []string{}, - []string{"Id", "Obj_name", "Obj_Id", "Task_name", "Stage", "Created_at"}), - } - registerCompute(&ComputeTasks) -} diff --git a/pkg/util/ipmitool/shell/boot.go b/pkg/util/ipmitool/shell/boot.go new file mode 100644 index 0000000000..571c8b603b --- /dev/null +++ b/pkg/util/ipmitool/shell/boot.go @@ -0,0 +1,52 @@ +package shell + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + "yunion.io/x/onecloud/pkg/util/printutils" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + shellutils.R(&EmptyOptions{}, "get-boot-flags", "Get boot flags info", func(client ipmitool.IPMIExecutor, _ *EmptyOptions) error { + info, err := ipmitool.GetBootFlags(client) + if err != nil { + return err + } + printutils.PrintInterfaceObject(info) + return nil + }) + + shellutils.R(&EmptyOptions{}, "do-reboot", "Do reboot", func(client ipmitool.IPMIExecutor, _ *EmptyOptions) error { + return ipmitool.DoReboot(client) + }) + + shellutils.R(&BootFlagOptions{}, "set-boot-flag", "Set bootflag, do reboot to make it work", func(cli ipmitool.IPMIExecutor, args *BootFlagOptions) error { + switch args.FLAG { + case "pxe": + return ipmitool.SetRebootToPXE(cli) + case "disk": + return ipmitool.SetRebootToDisk(cli) + case "bios": + return ipmitool.SetRebootToBIOS(cli) + default: + return fmt.Errorf("Invalid boot flag: %s", args.FLAG) + } + }) + + shellutils.R(&ShutdownOptions{}, "do-shutdown", "Do shutdown", func(client ipmitool.IPMIExecutor, args *ShutdownOptions) error { + if args.Soft { + return ipmitool.DoSoftShutdown(client) + } + return ipmitool.DoHardShutdown(client) + }) + + shellutils.R(&EmptyOptions{}, "do-power-on", "Do power on", func(client ipmitool.IPMIExecutor, _ *EmptyOptions) error { + return ipmitool.DoPowerOn(client) + }) + + shellutils.R(&EmptyOptions{}, "do-power-reset", "Do power on", func(client ipmitool.IPMIExecutor, _ *EmptyOptions) error { + return ipmitool.DoPowerReset(client) + }) +} diff --git a/pkg/util/ipmitool/shell/doc.go b/pkg/util/ipmitool/shell/doc.go new file mode 100644 index 0000000000..26e11b6fed --- /dev/null +++ b/pkg/util/ipmitool/shell/doc.go @@ -0,0 +1 @@ +package shell // import "yunion.io/x/onecloud/pkg/util/ipmitool/shell" diff --git a/pkg/util/ipmitool/shell/lan.go b/pkg/util/ipmitool/shell/lan.go new file mode 100644 index 0000000000..7e23fe7bb2 --- /dev/null +++ b/pkg/util/ipmitool/shell/lan.go @@ -0,0 +1,35 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + "yunion.io/x/onecloud/pkg/util/printutils" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type LanOptions struct { + CHANNEL int `help:"lan channel"` + } + shellutils.R(&LanOptions{}, "set-lan-dhcp", "Set lan channel DHCP", func(client ipmitool.IPMIExecutor, args *LanOptions) error { + return ipmitool.SetLanDHCP(client, args.CHANNEL) + }) + + shellutils.R(&LanOptions{}, "get-lan-config", "Get lan channel config", func(cli ipmitool.IPMIExecutor, args *LanOptions) error { + config, err := ipmitool.GetLanConfig(cli, args.CHANNEL) + if err != nil { + return err + } + printutils.PrintInterfaceObject(config) + return nil + }) + + type SetLanStaticIpOptions struct { + LanOptions + IP string + MASK string + GATEWAY string + } + shellutils.R(&SetLanStaticIpOptions{}, "set-lan-static", "Set lan static network", func(cli ipmitool.IPMIExecutor, args *SetLanStaticIpOptions) error { + return ipmitool.SetLanStatic(cli, args.CHANNEL, args.IP, args.MASK, args.GATEWAY) + }) +} diff --git a/pkg/util/ipmitool/shell/shell.go b/pkg/util/ipmitool/shell/shell.go new file mode 100644 index 0000000000..d2df320e19 --- /dev/null +++ b/pkg/util/ipmitool/shell/shell.go @@ -0,0 +1,28 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + "yunion.io/x/onecloud/pkg/util/printutils" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +type EmptyOptions struct{} + +type BootFlagOptions struct { + FLAG string `help:"Boot flag" choices:"pxe|disk|bios"` +} + +type ShutdownOptions struct { + Soft bool `help:"Do soft shutdown"` +} + +func init() { + shellutils.R(&EmptyOptions{}, "get-sysinfo", "Get system info", func(client ipmitool.IPMIExecutor, _ *EmptyOptions) error { + info, err := ipmitool.GetSysInfo(client) + if err != nil { + return err + } + printutils.PrintInterfaceObject(info) + return nil + }) +} diff --git a/pkg/util/ipmitool/shell/status.go b/pkg/util/ipmitool/shell/status.go new file mode 100644 index 0000000000..ee53f7498e --- /dev/null +++ b/pkg/util/ipmitool/shell/status.go @@ -0,0 +1,19 @@ +package shell + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + shellutils.R(&EmptyOptions{}, "get-chassis-power-status", "Get chassis power status", func(cli ipmitool.IPMIExecutor, _ *EmptyOptions) error { + status, err := ipmitool.GetChassisPowerStatus(cli) + if err != nil { + return err + } + fmt.Println(status) + return nil + }) +} diff --git a/pkg/util/procutils/doc.go b/pkg/util/procutils/doc.go new file mode 100644 index 0000000000..b11680af58 --- /dev/null +++ b/pkg/util/procutils/doc.go @@ -0,0 +1 @@ +package procutils // import "yunion.io/x/onecloud/pkg/util/procutils" diff --git a/pkg/util/ssh/doc.go b/pkg/util/ssh/doc.go new file mode 100644 index 0000000000..9370b7b4c8 --- /dev/null +++ b/pkg/util/ssh/doc.go @@ -0,0 +1 @@ +package ssh // import "yunion.io/x/onecloud/pkg/util/ssh" diff --git a/pkg/util/ssh/ssh.go b/pkg/util/ssh/ssh.go index c16db35942..a70cc83a7a 100644 --- a/pkg/util/ssh/ssh.go +++ b/pkg/util/ssh/ssh.go @@ -3,6 +3,7 @@ package ssh import ( "fmt" "strings" + "time" "golang.org/x/crypto/ssh" @@ -25,6 +26,7 @@ func (conf ClientConfig) ToSshConfig() (*ssh.ClientConfig, error) { cliConfig := &ssh.ClientConfig{ User: conf.Username, HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: 15 * time.Second, } auths := make([]ssh.AuthMethod, 0) if conf.Password != "" { @@ -55,6 +57,7 @@ func (conf ClientConfig) Connect() (*ssh.Client, error) { } type Client struct { + config ClientConfig client *ssh.Client } @@ -64,6 +67,7 @@ func (conf ClientConfig) NewClient() (*Client, error) { return nil, err } return &Client{ + config: conf, client: cli, }, nil } @@ -85,6 +89,10 @@ func NewClient( return config.NewClient() } +func (s *Client) GetConfig() ClientConfig { + return s.config +} + func (s *Client) Run(cmds ...string) ([]string, error) { ret := []string{} for _, cmd := range cmds { @@ -98,14 +106,17 @@ func (s *Client) Run(cmds ...string) ([]string, error) { log.Errorf("Error output: %s", string(out)) return nil, err } - ret = append(ret, parseOutput(out)...) + ret = append(ret, ParseOutput(out)...) } return ret, nil } -func parseOutput(output []byte) []string { - lines := strings.Split(string(output), "\n") +func ParseOutput(output []byte) []string { + lines := make([]string, 0) + for _, line := range strings.Split(string(output), "\n") { + lines = append(lines, strings.TrimSpace(line)) + } return lines } diff --git a/vendor/github.com/pin/tftp/.gitignore b/vendor/github.com/pin/tftp/.gitignore deleted file mode 100644 index daf913b1b3..0000000000 --- a/vendor/github.com/pin/tftp/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/vendor/github.com/pin/tftp/.travis.yml b/vendor/github.com/pin/tftp/.travis.yml deleted file mode 100644 index edbec9a6a2..0000000000 --- a/vendor/github.com/pin/tftp/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: go - -os: - - linux - - osx - -before_install: - - ulimit -n 4096 diff --git a/vendor/github.com/pin/tftp/CONTRIBUTORS b/vendor/github.com/pin/tftp/CONTRIBUTORS deleted file mode 100644 index c8c331dee6..0000000000 --- a/vendor/github.com/pin/tftp/CONTRIBUTORS +++ /dev/null @@ -1,4 +0,0 @@ -Dmitri Popov -Mojo Talantikite -Giovanni Bajo -Andrew Danforth diff --git a/vendor/github.com/pin/tftp/LICENSE b/vendor/github.com/pin/tftp/LICENSE deleted file mode 100644 index dada3d0955..0000000000 --- a/vendor/github.com/pin/tftp/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) -Copyright (c) 2016 Dmitri Popov - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/pin/tftp/README.md b/vendor/github.com/pin/tftp/README.md deleted file mode 100644 index e360cc8821..0000000000 --- a/vendor/github.com/pin/tftp/README.md +++ /dev/null @@ -1,171 +0,0 @@ -TFTP server and client library for Golang -========================================= - -[![GoDoc](https://godoc.org/github.com/pin/tftp?status.svg)](https://godoc.org/github.com/pin/tftp) -[![Build Status](https://travis-ci.org/pin/tftp.svg?branch=master)](https://travis-ci.org/pin/tftp) - -Implements: - * [RFC 1350](https://tools.ietf.org/html/rfc1350) - The TFTP Protocol (Revision 2) - * [RFC 2347](https://tools.ietf.org/html/rfc2347) - TFTP Option Extension - * [RFC 2348](https://tools.ietf.org/html/rfc2348) - TFTP Blocksize Option - -Partially implements (tsize server side only): - * [RFC 2349](https://tools.ietf.org/html/rfc2349) - TFTP Timeout Interval and Transfer Size Options - -Set of features is sufficient for PXE boot support. - -``` go -import "github.com/pin/tftp" -``` - -The package is cohesive to Golang `io`. Particularly it implements -`io.ReaderFrom` and `io.WriterTo` interfaces. That allows efficient data -transmission without unnecessary memory copying and allocations. - - -TFTP Server ------------ - -```go - -// readHandler is called when client starts file download from server -func readHandler(filename string, rf io.ReaderFrom) error { - file, err := os.Open(filename) - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - return err - } - n, err := rf.ReadFrom(file) - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - return err - } - fmt.Printf("%d bytes sent\n", n) - return nil -} - -// writeHandler is called when client starts file upload to server -func writeHandler(filename string, wt io.WriterTo) error { - file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - return err - } - n, err := wt.WriteTo(file) - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - return err - } - fmt.Printf("%d bytes received\n", n) - return nil -} - -func main() { - // use nil in place of handler to disable read or write operations - s := tftp.NewServer(readHandler, writeHandler) - s.SetTimeout(5 * time.Second) // optional - err := s.ListenAndServe(":69") // blocks until s.Shutdown() is called - if err != nil { - fmt.Fprintf(os.Stdout, "server: %v\n", err) - os.Exit(1) - } -} -``` - -TFTP Client ------------ -Upload file to server: - -```go -c, err := tftp.NewClient("172.16.4.21:69") -file, err := os.Open(path) -c.SetTimeout(5 * time.Second) // optional -rf, err := c.Send("foobar.txt", "octet") -n, err := rf.ReadFrom(file) -fmt.Printf("%d bytes sent\n", n) -``` - -Download file from server: - -```go -c, err := tftp.NewClient("172.16.4.21:69") -wt, err := c.Receive("foobar.txt", "octet") -file, err := os.Create(path) -// Optionally obtain transfer size before actual data. -if n, ok := wt.(IncomingTransfer).Size(); ok { - fmt.Printf("Transfer size: %d\n", n) -} -n, err := wt.WriteTo(file) -fmt.Printf("%d bytes received\n", n) -``` - -Note: please handle errors better :) - -TSize option ------------- - -PXE boot ROM often expects tsize option support from a server: client -(e.g. computer that boots over the network) wants to know size of a -download before the actual data comes. Server has to obtain stream -size and send it to a client. - -Often it will happen automatically because TFTP library tries to check -if `io.Reader` provided to `ReadFrom` method also satisfies -`io.Seeker` interface (`os.File` for instance) and uses `Seek` to -determine file size. - -In case `io.Reader` you provide to `ReadFrom` in read handler does not -satisfy `io.Seeker` interface or you do not want TFTP library to call -`Seek` on your reader but still want to respond with tsize option -during outgoing request you can use an `OutgoingTransfer` interface: - -```go - -func readHandler(filename string, rf io.ReaderFrom) error { - ... - // Set transfer size before calling ReadFrom. - rf.(tftp.OutgoingTransfer).SetSize(myFileSize) - ... - // ReadFrom ... - -``` - -Similarly, it is possible to obtain size of a file that is about to be -received using `IncomingTransfer` interface (see `Size` method). - -Remote Address --------------- - -The `OutgoingTransfer` and `IncomingTransfer` interfaces also provide the -`RemoteAddr` method which returns the peer IP address and port as a -`net.UDPAddr`. This can be used for detailed logging in a server handler. - -```go - -func readHandler(filename string, rf io.ReaderFrom) error { - ... - raddr := rf.(tftp.OutgoingTransfer).RemoteAddr() - log.Println("RRQ from", raddr.String()) - ... - // ReadFrom ... -``` - -Backoff -------- - -The default backoff before retransmitting an unacknowledged packet is a -random duration between 0 and 1 second. This behavior can be overridden -in clients and servers by providing a custom backoff calculation function. - -```go - s := tftp.NewServer(readHandler, writeHandler) - s.SetBackoff(func (attempts int) time.Duration { - return time.Duration(attempts) * time.Second - }) -``` - -or, for no backoff - -```go - s.SetBackoff(func (int) time.Duration { return 0 }) -``` diff --git a/vendor/github.com/pin/tftp/backoff.go b/vendor/github.com/pin/tftp/backoff.go deleted file mode 100644 index 5c5326c2ee..0000000000 --- a/vendor/github.com/pin/tftp/backoff.go +++ /dev/null @@ -1,35 +0,0 @@ -package tftp - -import ( - "math/rand" - "time" -) - -const ( - defaultTimeout = 5 * time.Second - defaultRetries = 5 -) - -type backoffFunc func(int) time.Duration - -type backoff struct { - attempt int - handler backoffFunc -} - -func (b *backoff) reset() { - b.attempt = 0 -} - -func (b *backoff) count() int { - return b.attempt -} - -func (b *backoff) backoff() { - if b.handler == nil { - time.Sleep(time.Duration(rand.Int63n(int64(time.Second)))) - } else { - time.Sleep(b.handler(b.attempt)) - } - b.attempt++ -} diff --git a/vendor/github.com/pin/tftp/client.go b/vendor/github.com/pin/tftp/client.go deleted file mode 100644 index 9f1802de42..0000000000 --- a/vendor/github.com/pin/tftp/client.go +++ /dev/null @@ -1,125 +0,0 @@ -package tftp - -import ( - "fmt" - "io" - "net" - "strconv" - "time" -) - -// NewClient creates TFTP client for server on address provided. -func NewClient(addr string) (*Client, error) { - a, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, fmt.Errorf("resolving address %s: %v", addr, err) - } - return &Client{ - addr: a, - timeout: defaultTimeout, - retries: defaultRetries, - }, nil -} - -// SetTimeout sets maximum time client waits for single network round-trip to succeed. -// Default is 5 seconds. -func (c *Client) SetTimeout(t time.Duration) { - if t <= 0 { - c.timeout = defaultTimeout - } - c.timeout = t -} - -// SetRetries sets maximum number of attempts client made to transmit a packet. -// Default is 5 attempts. -func (c *Client) SetRetries(count int) { - if count < 1 { - c.retries = defaultRetries - } - c.retries = count -} - -// SetBackoff sets a user provided function that is called to provide a -// backoff duration prior to retransmitting an unacknowledged packet. -func (c *Client) SetBackoff(h backoffFunc) { - c.backoff = h -} - -type Client struct { - addr *net.UDPAddr - timeout time.Duration - retries int - backoff backoffFunc - blksize int - tsize bool -} - -// Send starts outgoing file transmission. It returns io.ReaderFrom or error. -func (c Client) Send(filename string, mode string) (io.ReaderFrom, error) { - conn, err := net.ListenUDP("udp", &net.UDPAddr{}) - if err != nil { - return nil, err - } - s := &sender{ - send: make([]byte, datagramLength), - receive: make([]byte, datagramLength), - conn: conn, - retry: &backoff{handler: c.backoff}, - timeout: c.timeout, - retries: c.retries, - addr: c.addr, - mode: mode, - } - if c.blksize != 0 { - s.opts = make(options) - s.opts["blksize"] = strconv.Itoa(c.blksize) - } - n := packRQ(s.send, opWRQ, filename, mode, s.opts) - addr, err := s.sendWithRetry(n) - if err != nil { - return nil, err - } - s.addr = addr - s.opts = nil - return s, nil -} - -// Receive starts incoming file transmission. It returns io.WriterTo or error. -func (c Client) Receive(filename string, mode string) (io.WriterTo, error) { - conn, err := net.ListenUDP("udp", &net.UDPAddr{}) - if err != nil { - return nil, err - } - if c.timeout == 0 { - c.timeout = defaultTimeout - } - r := &receiver{ - send: make([]byte, datagramLength), - receive: make([]byte, datagramLength), - conn: conn, - retry: &backoff{handler: c.backoff}, - timeout: c.timeout, - retries: c.retries, - addr: c.addr, - autoTerm: true, - block: 1, - mode: mode, - } - if c.blksize != 0 || c.tsize { - r.opts = make(options) - } - if c.blksize != 0 { - r.opts["blksize"] = strconv.Itoa(c.blksize) - } - if c.tsize { - r.opts["tsize"] = "0" - } - n := packRQ(r.send, opRRQ, filename, mode, r.opts) - l, addr, err := r.receiveWithRetry(n) - if err != nil { - return nil, err - } - r.l = l - r.addr = addr - return r, nil -} diff --git a/vendor/github.com/pin/tftp/netascii/netascii.go b/vendor/github.com/pin/tftp/netascii/netascii.go deleted file mode 100644 index 92cee03bbc..0000000000 --- a/vendor/github.com/pin/tftp/netascii/netascii.go +++ /dev/null @@ -1,108 +0,0 @@ -package netascii - -// TODO: make it work not only on linux - -import "io" - -const ( - CR = '\x0d' - LF = '\x0a' - NUL = '\x00' -) - -func ToReader(r io.Reader) io.Reader { - return &toReader{ - r: r, - buf: make([]byte, 256), - } -} - -type toReader struct { - r io.Reader - buf []byte - n int - i int - err error - lf bool - nul bool -} - -func (r *toReader) Read(p []byte) (int, error) { - var n int - for n < len(p) { - if r.lf { - p[n] = LF - n++ - r.lf = false - continue - } - if r.nul { - p[n] = NUL - n++ - r.nul = false - continue - } - if r.i < r.n { - if r.buf[r.i] == LF { - p[n] = CR - r.lf = true - } else if r.buf[r.i] == CR { - p[n] = CR - r.nul = true - - } else { - p[n] = r.buf[r.i] - } - r.i++ - n++ - continue - } - if r.err == nil { - r.n, r.err = r.r.Read(r.buf) - r.i = 0 - } else { - return n, r.err - } - } - return n, r.err -} - -type fromWriter struct { - w io.Writer - buf []byte - i int - cr bool -} - -func FromWriter(w io.Writer) io.Writer { - return &fromWriter{ - w: w, - buf: make([]byte, 256), - } -} - -func (w *fromWriter) Write(p []byte) (n int, err error) { - for n < len(p) { - if w.cr { - if p[n] == LF { - w.buf[w.i] = LF - } - if p[n] == NUL { - w.buf[w.i] = CR - } - w.cr = false - w.i++ - } else if p[n] == CR { - w.cr = true - } else { - w.buf[w.i] = p[n] - w.i++ - } - n++ - if w.i == len(w.buf) || n == len(p) { - _, err = w.w.Write(w.buf[:w.i]) - w.i = 0 - } - } - return n, err -} diff --git a/vendor/github.com/pin/tftp/packet.go b/vendor/github.com/pin/tftp/packet.go deleted file mode 100644 index 1ac77428fd..0000000000 --- a/vendor/github.com/pin/tftp/packet.go +++ /dev/null @@ -1,190 +0,0 @@ -package tftp - -import ( - "bytes" - "encoding/binary" - "fmt" -) - -const ( - opRRQ = uint16(1) // Read request (RRQ) - opWRQ = uint16(2) // Write request (WRQ) - opDATA = uint16(3) // Data - opACK = uint16(4) // Acknowledgement - opERROR = uint16(5) // Error - opOACK = uint16(6) // Options Acknowledgment -) - -const ( - blockLength = 512 - datagramLength = 516 -) - -type options map[string]string - -// RRQ/WRQ packet -// -// 2 bytes string 1 byte string 1 byte -// -------------------------------------------------- -// | Opcode | Filename | 0 | Mode | 0 | -// -------------------------------------------------- -type pRRQ []byte -type pWRQ []byte - -// packRQ returns length of the packet in b -func packRQ(p []byte, op uint16, filename, mode string, opts options) int { - binary.BigEndian.PutUint16(p, op) - n := 2 - n += copy(p[2:len(p)-10], filename) - p[n] = 0 - n++ - n += copy(p[n:], mode) - p[n] = 0 - n++ - for name, value := range opts { - n += copy(p[n:], name) - p[n] = 0 - n++ - n += copy(p[n:], value) - p[n] = 0 - n++ - } - return n -} - -func unpackRQ(p []byte) (filename, mode string, opts options, err error) { - bs := bytes.Split(p[2:], []byte{0}) - if len(bs) < 2 { - return "", "", nil, fmt.Errorf("missing filename or mode") - } - filename = string(bs[0]) - mode = string(bs[1]) - if len(bs) < 4 { - return filename, mode, nil, nil - } - opts = make(options) - for i := 2; i+1 < len(bs); i += 2 { - opts[string(bs[i])] = string(bs[i+1]) - } - return filename, mode, opts, nil -} - -// OACK packet -// -// +----------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ -// | Opcode | opt1 | 0 | value1 | 0 | optN | 0 | valueN | 0 | -// +----------+---~~---+---+---~~---+---+---~~---+---+---~~---+---+ -type pOACK []byte - -func packOACK(p []byte, opts options) int { - binary.BigEndian.PutUint16(p, opOACK) - n := 2 - for name, value := range opts { - n += copy(p[n:], name) - p[n] = 0 - n++ - n += copy(p[n:], value) - p[n] = 0 - n++ - } - return n -} - -func unpackOACK(p []byte) (opts options, err error) { - bs := bytes.Split(p[2:], []byte{0}) - opts = make(options) - for i := 0; i+1 < len(bs); i += 2 { - opts[string(bs[i])] = string(bs[i+1]) - } - return opts, nil -} - -// ERROR packet -// -// 2 bytes 2 bytes string 1 byte -// ------------------------------------------ -// | Opcode | ErrorCode | ErrMsg | 0 | -// ------------------------------------------ -type pERROR []byte - -func packERROR(p []byte, code uint16, message string) int { - binary.BigEndian.PutUint16(p, opERROR) - binary.BigEndian.PutUint16(p[2:], code) - n := copy(p[4:len(p)-2], message) - p[4+n] = 0 - return n + 5 -} - -func (p pERROR) code() uint16 { - return binary.BigEndian.Uint16(p[2:]) -} - -func (p pERROR) message() string { - return string(p[4:]) -} - -// DATA packet -// -// 2 bytes 2 bytes n bytes -// ---------------------------------- -// | Opcode | Block # | Data | -// ---------------------------------- -type pDATA []byte - -func (p pDATA) block() uint16 { - return binary.BigEndian.Uint16(p[2:]) -} - -// ACK packet -// -// 2 bytes 2 bytes -// ----------------------- -// | Opcode | Block # | -// ----------------------- -type pACK []byte - -func (p pACK) block() uint16 { - return binary.BigEndian.Uint16(p[2:]) -} - -func parsePacket(p []byte) (interface{}, error) { - l := len(p) - if l < 2 { - return nil, fmt.Errorf("short packet") - } - opcode := binary.BigEndian.Uint16(p) - switch opcode { - case opRRQ: - if l < 4 { - return nil, fmt.Errorf("short RRQ packet: %d", l) - } - return pRRQ(p), nil - case opWRQ: - if l < 4 { - return nil, fmt.Errorf("short WRQ packet: %d", l) - } - return pWRQ(p), nil - case opDATA: - if l < 4 { - return nil, fmt.Errorf("short DATA packet: %d", l) - } - return pDATA(p), nil - case opACK: - if l < 4 { - return nil, fmt.Errorf("short ACK packet: %d", l) - } - return pACK(p), nil - case opERROR: - if l < 5 { - return nil, fmt.Errorf("short ERROR packet: %d", l) - } - return pERROR(p), nil - case opOACK: - if l < 6 { - return nil, fmt.Errorf("short OACK packet: %d", l) - } - return pOACK(p), nil - default: - return nil, fmt.Errorf("unknown opcode: %d", opcode) - } -} diff --git a/vendor/github.com/pin/tftp/receiver.go b/vendor/github.com/pin/tftp/receiver.go deleted file mode 100644 index 6e0153d264..0000000000 --- a/vendor/github.com/pin/tftp/receiver.go +++ /dev/null @@ -1,234 +0,0 @@ -package tftp - -import ( - "encoding/binary" - "fmt" - "io" - "net" - "strconv" - "time" - - "github.com/pin/tftp/netascii" -) - -// IncomingTransfer provides methods that expose information associated with -// an incoming transfer. -type IncomingTransfer interface { - // Size returns the size of an incoming file if the request included the - // tsize option (see RFC2349). To differentiate a zero-sized file transfer - // from a request without tsize use the second boolean "ok" return value. - Size() (n int64, ok bool) - - // RemoteAddr returns the remote peer's IP address and port. - RemoteAddr() net.UDPAddr -} - -func (r *receiver) RemoteAddr() net.UDPAddr { return *r.addr } - -func (r *receiver) Size() (n int64, ok bool) { - if r.opts != nil { - if s, ok := r.opts["tsize"]; ok { - n, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return 0, false - } - return n, true - } - } - return 0, false -} - -type receiver struct { - send []byte - receive []byte - addr *net.UDPAddr - tid int - conn *net.UDPConn - block uint16 - retry *backoff - timeout time.Duration - retries int - l int - autoTerm bool - dally bool - mode string - opts options -} - -func (r *receiver) WriteTo(w io.Writer) (n int64, err error) { - if r.mode == "netascii" { - w = netascii.FromWriter(w) - } - if r.opts != nil { - err := r.sendOptions() - if err != nil { - r.abort(err) - return 0, err - } - } - binary.BigEndian.PutUint16(r.send[0:2], opACK) - for { - if r.l > 0 { - l, err := w.Write(r.receive[4:r.l]) - n += int64(l) - if err != nil { - r.abort(err) - return n, err - } - if r.l < len(r.receive) { - if r.autoTerm { - r.terminate() - r.conn.Close() - } - return n, nil - } - } - binary.BigEndian.PutUint16(r.send[2:4], r.block) - r.block++ // send ACK for current block and expect next one - ll, _, err := r.receiveWithRetry(4) - if err != nil { - r.abort(err) - return n, err - } - r.l = ll - } -} - -func (r *receiver) sendOptions() error { - for name, value := range r.opts { - if name == "blksize" { - err := r.setBlockSize(value) - if err != nil { - delete(r.opts, name) - continue - } - } else { - delete(r.opts, name) - } - } - if len(r.opts) > 0 { - m := packOACK(r.send, r.opts) - r.block = 1 // expect data block number 1 - ll, _, err := r.receiveWithRetry(m) - if err != nil { - r.abort(err) - return err - } - r.l = ll - } - return nil -} - -func (r *receiver) setBlockSize(blksize string) error { - n, err := strconv.Atoi(blksize) - if err != nil { - return err - } - if n < 512 { - return fmt.Errorf("blkzise too small: %d", n) - } - if n > 65464 { - return fmt.Errorf("blksize too large: %d", n) - } - r.receive = make([]byte, n+4) - return nil -} - -func (r *receiver) receiveWithRetry(l int) (int, *net.UDPAddr, error) { - r.retry.reset() - for { - n, addr, err := r.receiveDatagram(l) - if _, ok := err.(net.Error); ok && r.retry.count() < r.retries { - r.retry.backoff() - continue - } - return n, addr, err - } -} - -func (r *receiver) receiveDatagram(l int) (int, *net.UDPAddr, error) { - err := r.conn.SetReadDeadline(time.Now().Add(r.timeout)) - if err != nil { - return 0, nil, err - } - _, err = r.conn.WriteToUDP(r.send[:l], r.addr) - if err != nil { - return 0, nil, err - } - for { - c, addr, err := r.conn.ReadFromUDP(r.receive) - if err != nil { - return 0, nil, err - } - if !addr.IP.Equal(r.addr.IP) || (r.tid != 0 && addr.Port != r.tid) { - continue - } - p, err := parsePacket(r.receive[:c]) - if err != nil { - return 0, addr, err - } - r.tid = addr.Port - switch p := p.(type) { - case pDATA: - if p.block() == r.block { - return c, addr, nil - } - case pOACK: - opts, err := unpackOACK(p) - if r.block != 1 { - continue - } - if err != nil { - r.abort(err) - return 0, addr, err - } - for name, value := range opts { - if name == "blksize" { - err := r.setBlockSize(value) - if err != nil { - continue - } - } - } - r.block = 0 // ACK with block number 0 - r.opts = opts - return 0, addr, nil - case pERROR: - return 0, addr, fmt.Errorf("code: %d, message: %s", - p.code(), p.message()) - } - } -} - -func (r *receiver) terminate() error { - binary.BigEndian.PutUint16(r.send[2:4], r.block) - if r.dally { - for i := 0; i < 3; i++ { - _, _, err := r.receiveDatagram(4) - if err != nil { - return nil - } - } - return fmt.Errorf("dallying termination failed") - } else { - _, err := r.conn.WriteToUDP(r.send[:4], r.addr) - if err != nil { - return err - } - } - return nil -} - -func (r *receiver) abort(err error) error { - if r.conn == nil { - return nil - } - n := packERROR(r.send, 1, err.Error()) - _, err = r.conn.WriteToUDP(r.send[:n], r.addr) - if err != nil { - return err - } - r.conn.Close() - r.conn = nil - return nil -} diff --git a/vendor/github.com/pin/tftp/sender.go b/vendor/github.com/pin/tftp/sender.go deleted file mode 100644 index d018c4f075..0000000000 --- a/vendor/github.com/pin/tftp/sender.go +++ /dev/null @@ -1,243 +0,0 @@ -package tftp - -import ( - "encoding/binary" - "fmt" - "io" - "net" - "strconv" - "time" - - "github.com/pin/tftp/netascii" -) - -// OutgoingTransfer provides methods to set the outgoing transfer size and -// retrieve the remote address of the peer. -type OutgoingTransfer interface { - // SetSize is used to set the outgoing transfer size (tsize option: RFC2349) - // manually in a server write transfer handler. - // - // It is not necessary in most cases; when the io.Reader provided to - // ReadFrom also satisfies io.Seeker (e.g. os.File) the transfer size will - // be determined automatically. Seek will not be attempted when the - // transfer size option is set with SetSize. - // - // The value provided will be used only if SetSize is called before ReadFrom - // and only on in a server read handler. - SetSize(n int64) - - // RemoteAddr returns the remote peer's IP address and port. - RemoteAddr() net.UDPAddr -} - -type sender struct { - conn *net.UDPConn - addr *net.UDPAddr - tid int - send []byte - receive []byte - retry *backoff - timeout time.Duration - retries int - block uint16 - mode string - opts options -} - -func (s *sender) RemoteAddr() net.UDPAddr { return *s.addr } - -func (s *sender) SetSize(n int64) { - if s.opts != nil { - if _, ok := s.opts["tsize"]; ok { - s.opts["tsize"] = strconv.FormatInt(n, 10) - } - } -} - -func (s *sender) ReadFrom(r io.Reader) (n int64, err error) { - if s.mode == "netascii" { - r = netascii.ToReader(r) - } - if s.opts != nil { - // check that tsize is set - if ts, ok := s.opts["tsize"]; ok { - // check that tsize is not set with SetSize already - i, err := strconv.ParseInt(ts, 10, 64) - if err == nil && i == 0 { - if rs, ok := r.(io.Seeker); ok { - pos, err := rs.Seek(0, 1) - if err != nil { - return 0, err - } - size, err := rs.Seek(0, 2) - if err != nil { - return 0, err - } - s.opts["tsize"] = strconv.FormatInt(size, 10) - _, err = rs.Seek(pos, 0) - if err != nil { - return 0, err - } - } - } - } - err = s.sendOptions() - if err != nil { - s.abort(err) - return 0, err - } - } - s.block = 1 // start data transmission with block 1 - binary.BigEndian.PutUint16(s.send[0:2], opDATA) - for { - l, err := io.ReadFull(r, s.send[4:]) - n += int64(l) - if err != nil && err != io.ErrUnexpectedEOF { - if err == io.EOF { - binary.BigEndian.PutUint16(s.send[2:4], s.block) - _, err = s.sendWithRetry(4) - if err != nil { - s.abort(err) - return n, err - } - s.conn.Close() - return n, nil - } - s.abort(err) - return n, err - } - binary.BigEndian.PutUint16(s.send[2:4], s.block) - _, err = s.sendWithRetry(4 + l) - if err != nil { - s.abort(err) - return n, err - } - if l < len(s.send)-4 { - s.conn.Close() - return n, nil - } - s.block++ - } -} - -func (s *sender) sendOptions() error { - for name, value := range s.opts { - if name == "blksize" { - err := s.setBlockSize(value) - if err != nil { - delete(s.opts, name) - continue - } - } else if name == "tsize" { - if value != "0" { - s.opts["tsize"] = value - } else { - delete(s.opts, name) - continue - } - } else { - delete(s.opts, name) - } - } - if len(s.opts) > 0 { - m := packOACK(s.send, s.opts) - _, err := s.sendWithRetry(m) - if err != nil { - return err - } - } - return nil -} - -func (s *sender) setBlockSize(blksize string) error { - n, err := strconv.Atoi(blksize) - if err != nil { - return err - } - if n < 512 { - return fmt.Errorf("blkzise too small: %d", n) - } - if n > 65464 { - return fmt.Errorf("blksize too large: %d", n) - } - s.send = make([]byte, n+4) - return nil -} - -func (s *sender) sendWithRetry(l int) (*net.UDPAddr, error) { - s.retry.reset() - for { - addr, err := s.sendDatagram(l) - if _, ok := err.(net.Error); ok && s.retry.count() < s.retries { - s.retry.backoff() - continue - } - return addr, err - } -} - -func (s *sender) sendDatagram(l int) (*net.UDPAddr, error) { - err := s.conn.SetReadDeadline(time.Now().Add(s.timeout)) - if err != nil { - return nil, err - } - _, err = s.conn.WriteToUDP(s.send[:l], s.addr) - if err != nil { - return nil, err - } - for { - n, addr, err := s.conn.ReadFromUDP(s.receive) - if err != nil { - return nil, err - } - if !addr.IP.Equal(s.addr.IP) || (s.tid != 0 && addr.Port != s.tid) { - continue - } - p, err := parsePacket(s.receive[:n]) - if err != nil { - continue - } - s.tid = addr.Port - switch p := p.(type) { - case pACK: - if p.block() == s.block { - return addr, nil - } - case pOACK: - opts, err := unpackOACK(p) - if s.block != 0 { - continue - } - if err != nil { - s.abort(err) - return addr, err - } - for name, value := range opts { - if name == "blksize" { - err := s.setBlockSize(value) - if err != nil { - continue - } - } - } - return addr, nil - case pERROR: - return nil, fmt.Errorf("sending block %d: code=%d, error: %s", - s.block, p.code(), p.message()) - } - } -} - -func (s *sender) abort(err error) error { - if s.conn == nil { - return nil - } - n := packERROR(s.send, 1, err.Error()) - _, err = s.conn.WriteToUDP(s.send[:n], s.addr) - if err != nil { - return err - } - s.conn.Close() - s.conn = nil - return nil -} diff --git a/vendor/github.com/pin/tftp/server.go b/vendor/github.com/pin/tftp/server.go deleted file mode 100644 index 755119bf37..0000000000 --- a/vendor/github.com/pin/tftp/server.go +++ /dev/null @@ -1,199 +0,0 @@ -package tftp - -import ( - "fmt" - "io" - "net" - "sync" - "time" -) - -// NewServer creates TFTP server. It requires two functions to handle -// read and write requests. -// In case nil is provided for read or write handler the respective -// operation is disabled. -func NewServer(readHandler func(filename string, rf io.ReaderFrom) error, - writeHandler func(filename string, wt io.WriterTo) error) *Server { - return &Server{ - readHandler: readHandler, - writeHandler: writeHandler, - timeout: defaultTimeout, - retries: defaultRetries, - } -} - -type Server struct { - readHandler func(filename string, rf io.ReaderFrom) error - writeHandler func(filename string, wt io.WriterTo) error - backoff backoffFunc - conn *net.UDPConn - quit chan chan struct{} - wg sync.WaitGroup - timeout time.Duration - retries int -} - -// SetTimeout sets maximum time server waits for single network -// round-trip to succeed. -// Default is 5 seconds. -func (s *Server) SetTimeout(t time.Duration) { - if t <= 0 { - s.timeout = defaultTimeout - } else { - s.timeout = t - } -} - -// SetRetries sets maximum number of attempts server made to transmit a -// packet. -// Default is 5 attempts. -func (s *Server) SetRetries(count int) { - if count < 1 { - s.retries = defaultRetries - } else { - s.retries = count - } -} - -// SetBackoff sets a user provided function that is called to provide a -// backoff duration prior to retransmitting an unacknowledged packet. -func (s *Server) SetBackoff(h backoffFunc) { - s.backoff = h -} - -// ListenAndServe binds to address provided and start the server. -// ListenAndServe returns when Shutdown is called. -func (s *Server) ListenAndServe(addr string) error { - a, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return err - } - conn, err := net.ListenUDP("udp", a) - if err != nil { - return err - } - s.Serve(conn) - return nil -} - -// Serve starts server provided already opened UDP connecton. It is -// useful for the case when you want to run server in separate goroutine -// but still want to be able to handle any errors opening connection. -// Serve returns when Shutdown is called or connection is closed. -func (s *Server) Serve(conn *net.UDPConn) { - s.conn = conn - s.quit = make(chan chan struct{}) - for { - select { - case q := <-s.quit: - q <- struct{}{} - return - default: - err := s.processRequest(s.conn) - if err != nil { - // TODO: add logging handler - } - } - } -} - -// Shutdown make server stop listening for new requests, allows -// server to finish outstanding transfers and stops server. -func (s *Server) Shutdown() { - s.conn.Close() - q := make(chan struct{}) - s.quit <- q - <-q - s.wg.Wait() -} - -func (s *Server) processRequest(conn *net.UDPConn) error { - var buffer []byte - buffer = make([]byte, datagramLength) - n, remoteAddr, err := conn.ReadFromUDP(buffer) - if err != nil { - return fmt.Errorf("reading UDP: %v", err) - } - p, err := parsePacket(buffer[:n]) - if err != nil { - return err - } - switch p := p.(type) { - case pWRQ: - filename, mode, opts, err := unpackRQ(p) - if err != nil { - return fmt.Errorf("unpack WRQ: %v", err) - } - //fmt.Printf("got WRQ (filename=%s, mode=%s, opts=%v)\n", filename, mode, opts) - conn, err := net.ListenUDP("udp", &net.UDPAddr{}) - if err != nil { - return err - } - if err != nil { - return fmt.Errorf("open transmission: %v", err) - } - wt := &receiver{ - send: make([]byte, datagramLength), - receive: make([]byte, datagramLength), - conn: conn, - retry: &backoff{handler: s.backoff}, - timeout: s.timeout, - retries: s.retries, - addr: remoteAddr, - mode: mode, - opts: opts, - } - s.wg.Add(1) - go func() { - if s.writeHandler != nil { - err := s.writeHandler(filename, wt) - if err != nil { - wt.abort(err) - } else { - wt.terminate() - wt.conn.Close() - } - } else { - wt.abort(fmt.Errorf("server does not support write requests")) - } - s.wg.Done() - }() - case pRRQ: - filename, mode, opts, err := unpackRQ(p) - if err != nil { - return fmt.Errorf("unpack RRQ: %v", err) - } - //fmt.Printf("got RRQ (filename=%s, mode=%s, opts=%v)\n", filename, mode, opts) - conn, err := net.ListenUDP("udp", &net.UDPAddr{}) - if err != nil { - return err - } - rf := &sender{ - send: make([]byte, datagramLength), - receive: make([]byte, datagramLength), - tid: remoteAddr.Port, - conn: conn, - retry: &backoff{handler: s.backoff}, - timeout: s.timeout, - retries: s.retries, - addr: remoteAddr, - mode: mode, - opts: opts, - } - s.wg.Add(1) - go func() { - if s.readHandler != nil { - err := s.readHandler(filename, rf) - if err != nil { - rf.abort(err) - } - } else { - rf.abort(fmt.Errorf("server does not support read requests")) - } - s.wg.Done() - }() - default: - return fmt.Errorf("unexpected %T", p) - } - return nil -}