diff --git a/main.go b/main.go deleted file mode 100644 index 25aae52730..0000000000 --- a/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "net" - - "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/cloudcommon/dhcp" -) - -type Handler struct { -} - -func (h *Handler) ServeDHCP(pkt *dhcp.Packet, _ *net.Interface) (*dhcp.Packet, error) { - log.Errorln(pkt.DebugString()) - return nil, nil -} - -func main() { - s := dhcp.NewDHCPServer("0.0.0.0", 67) - - err := s.ListenAndServe(&Handler{}) - if err != nil { - log.Fatalf(err.Error()) - } -} diff --git a/pkg/baremetal/agent.go b/pkg/baremetal/agent.go index 0f18dab84a..bbcc21a11b 100644 --- a/pkg/baremetal/agent.go +++ b/pkg/baremetal/agent.go @@ -1,6 +1,7 @@ package baremetal import ( + "context" "fmt" "net" "time" @@ -55,7 +56,7 @@ func newBaremetalAgent() (*SBaremetalAgent, error) { } func GetAdminSession() *mcclient.ClientSession { - return auth.GetAdminSession(o.Options.Region, "v2") + return auth.GetAdminSession(context.TODO(), o.Options.Region, "v2") } func (agent *SBaremetalAgent) GetListenIPs() ([]net.IP, error) { @@ -104,6 +105,14 @@ func (agent *SBaremetalAgent) GetAccessIP() (net.IP, error) { return nil, fmt.Errorf("Not found AccessAddress %s on %s", o.Options.AccessAddress, o.Options.ListenInterface) } +func (agent *SBaremetalAgent) GetDHCPServerIP() (net.IP, error) { + listenIP := o.Options.ListenAddress + if len(listenIP) == 0 || listenIP == "0.0.0.0" { + return agent.GetAccessIP() + } + return agent.GetListenIP() +} + func getIfaceIPs(iface *net.Interface) ([]net.IP, error) { addrs, err := iface.Addrs() if err != nil { diff --git a/pkg/baremetal/handler/delay.go b/pkg/baremetal/handler/delay.go index 852aba82cb..b1f3b221f0 100644 --- a/pkg/baremetal/handler/delay.go +++ b/pkg/baremetal/handler/delay.go @@ -11,7 +11,7 @@ import ( var delayTaskWorkerMan *appsrv.SWorkerManager func init() { - delayTaskWorkerMan = appsrv.NewWorkerManager("DelayTaskWorkerManager", 8, 1024) + delayTaskWorkerMan = appsrv.NewWorkerManager("DelayTaskWorkerManager", 8, 1024, false) } type ProcessFunc func(data jsonutils.JSONObject) (jsonutils.JSONObject, error) diff --git a/pkg/baremetal/handler/handlers.go b/pkg/baremetal/handler/handlers.go index 71e42f3a7d..b7404f5f7f 100644 --- a/pkg/baremetal/handler/handlers.go +++ b/pkg/baremetal/handler/handlers.go @@ -5,6 +5,7 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/baremetal" + baremetaltypes "yunion.io/x/onecloud/pkg/baremetal/types" "yunion.io/x/onecloud/pkg/httperrors" ) @@ -13,15 +14,26 @@ func InitHandlers(app *appsrv.Application) { } 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("sync-config"), objectMiddleware(handleBaremetalSyncConfig)) - app.AddHandler("POST", getBaremetalPrefix("sync-ipmi"), objectMiddleware(handleBaremetalSyncIPMI)) - app.AddHandler("POST", getBaremetalPrefix("prepare"), objectMiddleware(handleBaremetalPrepare)) - app.AddHandler("POST", getBaremetalPrefix("reset-bmc"), objectMiddleware(handleBaremetalResetBMC)) + // baremetal actions handler + app.AddHandler("GET", bmActionPrefix("notify"), bmObjMiddleware(handleBaremetalNotify)) + app.AddHandler("POST", bmActionPrefix("maintenance"), bmObjMiddleware(handleBaremetalMaintenance)) + app.AddHandler("POST", bmActionPrefix("unmaintenance"), bmObjMiddleware(handleBaremetalUnmaintenance)) + app.AddHandler("POST", bmActionPrefix("delete"), bmObjMiddleware(handleBaremetalDelete)) + app.AddHandler("POST", bmActionPrefix("syncstatus"), bmObjMiddleware(handleBaremetalSyncStatus)) + app.AddHandler("POST", bmActionPrefix("sync-config"), bmObjMiddleware(handleBaremetalSyncConfig)) + app.AddHandler("POST", bmActionPrefix("sync-ipmi"), bmObjMiddleware(handleBaremetalSyncIPMI)) + app.AddHandler("POST", bmActionPrefix("prepare"), bmObjMiddleware(handleBaremetalPrepare)) + app.AddHandler("POST", bmActionPrefix("reset-bmc"), bmObjMiddleware(handleBaremetalResetBMC)) + + // server actions handler + app.AddHandler("POST", srvActionPrefix("create"), srvClassMiddleware(handleServerCreate)) + app.AddHandler("POST", srvActionPrefix("deploy"), srvObjMiddleware(handleServerDeploy)) + app.AddHandler("POST", srvActionPrefix("rebuild"), srvObjMiddleware(handleServerRebuild)) + app.AddHandler("POST", srvActionPrefix("start"), srvObjMiddleware(handleServerStart)) + app.AddHandler("POST", srvActionPrefix("stop"), srvObjMiddleware(handleServerStop)) + app.AddHandler("POST", srvActionPrefix("reset"), srvObjMiddleware(handleServerReset)) + app.AddHandler("POST", srvActionPrefix("status"), srvObjMiddleware(handleServerStatus)) + app.AddHandler("DELETE", srvIdPrefix(), srvObjMiddleware(handleServerDelete)) } func handleBaremetalNotify(ctx *Context, bm *baremetal.SBaremetalInstance) { @@ -84,3 +96,59 @@ func handleBaremetalResetBMC(ctx *Context, bm *baremetal.SBaremetalInstance) { bm.StartBaremetalResetBMCTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) ctx.ResponseOk() } + +func handleServerCreate(ctx *Context, bm *baremetal.SBaremetalInstance) { + err := bm.StartServerCreateTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()) + if err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerDelete(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + bm.StartServerDestroyTask(ctx.UserCred(), ctx.TaskId(), nil) + ctx.ResponseOk() +} + +func handleServerDeploy(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + if err := bm.StartServerDeployTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerRebuild(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + if err := bm.StartServerRebuildTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerStart(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + if err := bm.StartServerStartTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerStop(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + if err := bm.StartServerStopTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil { + ctx.ResponseError(httperrors.NewGeneralError(err)) + return + } + ctx.ResponseOk() +} + +func handleServerReset(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + ctx.DelayProcess(bm.DelayedServerReset, nil) + ctx.ResponseOk() +} + +func handleServerStatus(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) { + ctx.DelayProcess(bm.DelayedServerStatus, nil) + ctx.ResponseOk() +} diff --git a/pkg/baremetal/handler/middleware.go b/pkg/baremetal/handler/middleware.go index c93c4394d2..91e132a973 100644 --- a/pkg/baremetal/handler/middleware.go +++ b/pkg/baremetal/handler/middleware.go @@ -10,19 +10,38 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/baremetal" + baremetaltypes "yunion.io/x/onecloud/pkg/baremetal/types" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" ) const ( - PREFIX = "baremetals" + BM_PREFIX = "baremetals" + SERVER_PREFIX = "servers" - PARAMS_ID_KEY = "" + PARAMS_BMID_KEY = "" + PARAMS_SRVID_KEY = "" ) -func getBaremetalPrefix(action string) string { - return fmt.Sprintf("%s/%s/%s", PREFIX, PARAMS_ID_KEY, action) +func bmIdPrefix() string { + // baremetals/ + return fmt.Sprintf("%s/%s", BM_PREFIX, PARAMS_BMID_KEY) +} + +func bmActionPrefix(action string) string { + // baremetals//action + return fmt.Sprintf("%s/%s", bmIdPrefix(), action) +} + +func srvIdPrefix() string { + // baremetals//servers/ + return fmt.Sprintf("%s/%s/%s", bmIdPrefix(), SERVER_PREFIX, PARAMS_SRVID_KEY) +} + +func srvActionPrefix(action string) string { + // baremetals//servers//action + return fmt.Sprintf("%s/%s", srvIdPrefix(), action) } type handlerFunc func(ctx *Context) @@ -34,12 +53,12 @@ func authMiddleware(h handlerFunc) appsrv.FilterHandler { } } -type objectHandlerFunc func(ctx *Context, bm *baremetal.SBaremetalInstance) +type bmObjHandlerFunc func(ctx *Context, bm *baremetal.SBaremetalInstance) -func objectMiddleware(h objectHandlerFunc) appsrv.FilterHandler { +func bmObjMiddleware(h bmObjHandlerFunc) appsrv.FilterHandler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { newCtx := NewContext(ctx, w, r) - bmId := newCtx.Params()[PARAMS_ID_KEY] + bmId := newCtx.Params()[PARAMS_BMID_KEY] baremetal := newCtx.GetBaremetalManager().GetBaremetalById(bmId) if baremetal == nil { newCtx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId)) @@ -49,6 +68,45 @@ func objectMiddleware(h objectHandlerFunc) appsrv.FilterHandler { } } +type srvObjHandlerFunc func(ctx *Context, bm *baremetal.SBaremetalInstance, srv baremetaltypes.IBaremetalServer) + +func srvClassMiddleware(h bmObjHandlerFunc) appsrv.FilterHandler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + newCtx := NewContext(ctx, w, r) + bmId := newCtx.Params()[PARAMS_BMID_KEY] + //srvId := newCtx.Params()[PARAMS_SRVID_KEY] + baremetal := newCtx.GetBaremetalManager().GetBaremetalById(bmId) + if baremetal == nil { + newCtx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId)) + return + } + if baremetal.GetServerId() != "" { + newCtx.ResponseError(httperrors.NewNotAcceptableError("Baremetal %s occupied", bmId)) + return + } + h(newCtx, baremetal) + } +} + +func srvObjMiddleware(h srvObjHandlerFunc) appsrv.FilterHandler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + newCtx := NewContext(ctx, w, r) + bmId := newCtx.Params()[PARAMS_BMID_KEY] + srvId := newCtx.Params()[PARAMS_SRVID_KEY] + baremetal := newCtx.GetBaremetalManager().GetBaremetalById(bmId) + if baremetal == nil { + newCtx.ResponseError(httperrors.NewNotFoundError("Not found baremetal by id: %s", bmId)) + return + } + if baremetal.GetServerId() != srvId { + newCtx.ResponseError(httperrors.NewNotFoundError("Not found server by id: %s", srvId)) + return + } + srv := baremetal.GetServer() + h(newCtx, baremetal, srv) + } +} + type Context struct { context.Context userCred mcclient.TokenCredential diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index 48eb96ab2d..59dddbd465 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -9,11 +9,13 @@ import ( "reflect" "strings" "sync" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/util/errors" "yunion.io/x/pkg/util/regutils" + "yunion.io/x/pkg/util/seclib" "yunion.io/x/pkg/util/sets" "yunion.io/x/pkg/util/workqueue" "yunion.io/x/pkg/utils" @@ -23,9 +25,16 @@ import ( "yunion.io/x/onecloud/pkg/baremetal/pxe" baremetalstatus "yunion.io/x/onecloud/pkg/baremetal/status" "yunion.io/x/onecloud/pkg/baremetal/tasks" + baremetaltypes "yunion.io/x/onecloud/pkg/baremetal/types" + "yunion.io/x/onecloud/pkg/baremetal/utils/detect_storages" + "yunion.io/x/onecloud/pkg/baremetal/utils/disktool" "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" "yunion.io/x/onecloud/pkg/cloudcommon/dhcp" + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/hostman/guestfs" + "yunion.io/x/onecloud/pkg/hostman/guestfs/sshpart" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/mcclient/modules" @@ -222,18 +231,21 @@ func (m *sBaremetalMap) Pop(id string) *SBaremetalInstance { } type SBaremetalInstance struct { - manager *SBaremetalManager - desc *jsonutils.JSONDict - descLock *sync.Mutex - taskQueue *tasks.TaskQueue + manager *SBaremetalManager + desc *jsonutils.JSONDict + descLock *sync.Mutex + taskQueue *tasks.TaskQueue + server baremetaltypes.IBaremetalServer + serverLock *sync.Mutex } func newBaremetalInstance(man *SBaremetalManager, desc jsonutils.JSONObject) (*SBaremetalInstance, error) { bm := &SBaremetalInstance{ - manager: man, - desc: desc.(*jsonutils.JSONDict), - descLock: new(sync.Mutex), - taskQueue: tasks.NewTaskQueue(), + manager: man, + desc: desc.(*jsonutils.JSONDict), + descLock: new(sync.Mutex), + taskQueue: tasks.NewTaskQueue(), + serverLock: new(sync.Mutex), } err := os.MkdirAll(bm.GetDir(), 0755) if err != nil { @@ -243,7 +255,7 @@ func newBaremetalInstance(man *SBaremetalManager, desc jsonutils.JSONObject) (*S if err != nil { return nil, err } - // TODO: load server and init task queue + bm.loadServer() return bm, nil } @@ -308,6 +320,35 @@ func (b *SBaremetalInstance) SaveDesc(desc jsonutils.JSONObject) error { return ioutil.WriteFile(b.GetDescFilePath(), []byte(b.desc.String()), 0644) } +func (b *SBaremetalInstance) loadServer() { + b.serverLock.Lock() + defer b.serverLock.Unlock() + if !b.desc.Contains("server_id") { + return + } + descPath := b.GetServerDescFilePath() + desc, err := ioutil.ReadFile(descPath) + if err != nil { + log.Errorf("Failed to read server desc %s: %v", descPath, err) + return + } + descObj, err := jsonutils.Parse(desc) + if err != nil { + log.Errorf("Failed to parse server json string: %v", err) + return + } + srv, err := newBaremetalServer(b, descObj.(*jsonutils.JSONDict)) + if err != nil { + log.Errorf("New server error: %v", err) + return + } + if bmSrvId, _ := b.desc.GetString("server_id"); srv.GetId() != bmSrvId { + log.Errorf("Server id %q not equal baremetal %q server id %q", srv.GetId(), b.GetName(), bmSrvId) + return + } + b.server = srv +} + func (b *SBaremetalInstance) SaveSSHConfig(remoteAddr string, key string) error { var err error key, err = utils.EncryptAESBase64(b.GetId(), key) @@ -406,7 +447,9 @@ func (b *SBaremetalInstance) SyncSSHConfig(conf types.SSHConfig) error { } func (b *SBaremetalInstance) SyncStatusBackground() { - + go func() { + b.AutoSyncAllStatus() + }() } func PowerStatusToBaremetalStatus(status string) string { @@ -419,6 +462,20 @@ func PowerStatusToBaremetalStatus(status string) string { return baremetalstatus.UNKNOWN } +func PowerStatusToServerStatus(bm *SBaremetalInstance, status string) string { + switch status { + case types.POWER_STATUS_ON: + if conf, _ := bm.GetSSHConfig(); conf == nil { + return baremetalstatus.SERVER_RUNNING + } else { + return baremetalstatus.SERVER_ADMIN + } + case types.POWER_STATUS_OFF: + return baremetalstatus.SERVER_READY + } + return baremetalstatus.UNKNOWN +} + func (b *SBaremetalInstance) AutoSyncStatus() { b.SyncStatus("", "") } @@ -464,25 +521,46 @@ func (b *SBaremetalInstance) SyncAllStatus(status string) { } } b.SyncStatus(PowerStatusToBaremetalStatus(status), "") - // b.SyncServerStatus(PowerStatusToServerStatus(status)) + b.SyncServerStatus(PowerStatusToServerStatus(b, status)) } -func (b *SBaremetalInstance) getNicInfo() *types.NicInfo { - nicInfo := types.NicInfo{} - err := b.desc.Unmarshal(&nicInfo) +func (b *SBaremetalInstance) SyncServerStatus(status string) { + if b.GetServerId() == "" { + return + } + if status == "" { + powerStatus, err := b.GetPowerStatus() + if err != nil { + log.Errorf("Get power status error: %v", err) + } + status = PowerStatusToServerStatus(b, powerStatus) + } + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(status), "status") + _, err := modules.Servers.PerformAction(b.GetClientSession(), b.GetServerId(), "status", params) if err != nil { - log.Errorf("Unmarshal desc to get nic info error: %v", err) + log.Errorf("Update server %s status %s error: %v", b.GetServerName(), status, err) + return + } + log.Infof("Update server %s to status %s", b.GetServerName(), status) +} + +func (b *SBaremetalInstance) getNics() []types.Nic { + nics := []types.Nic{} + err := b.desc.Unmarshal(&nics, "nic_info") + if err != nil { + log.Errorf("Unmarshal desc to get nics error: %v", err) return nil } - return &nicInfo + return nics } func (b *SBaremetalInstance) getNicByType(nicType string) *types.Nic { - nicInfo := b.getNicInfo() - if nicInfo == nil { + nics := b.getNics() + if len(nics) == 0 { return nil } - for _, nic := range nicInfo.Nics { + for _, nic := range nics { tmp := nic if tmp.Type == nicType { return &tmp @@ -492,11 +570,11 @@ func (b *SBaremetalInstance) getNicByType(nicType string) *types.Nic { } func (b *SBaremetalInstance) GetNicByMac(mac net.HardwareAddr) *types.Nic { - nicInfo := b.getNicInfo() - if nicInfo == nil { + nics := b.getNics() + if len(nics) == 0 { return nil } - for _, nic := range nicInfo.Nics { + for _, nic := range nics { tmp := nic if tmp.Mac == mac.String() { return &tmp @@ -510,8 +588,22 @@ func (b *SBaremetalInstance) GetAdminNic() *types.Nic { } func (b *SBaremetalInstance) NeedPXEBoot() bool { - // TODO: - return true + task := b.GetTask() + taskName := "nil" + serverId := b.GetServerId() + if task != nil { + taskName = task.GetName() + } + taskNeedPXEBoot := false + if task != nil && task.NeedPXEBoot() { + taskNeedPXEBoot = true + } + ret := false + if taskNeedPXEBoot || (task == nil && len(serverId) == 0) { + ret = true + } + log.Infof("Check task %s, server %s NeedPXEBoot: %v", taskName, serverId, ret) + return ret } func (b *SBaremetalInstance) GetIPMINic(cliMac net.HardwareAddr) *types.Nic { @@ -534,19 +626,18 @@ func (b *SBaremetalInstance) GetIPMINicIPAddr() string { } 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__) - nic = self.get_server().get_nic_by_mac(mac) - hostname = self.get_server().get_name() - else: - nic = self.get_nic_by_mac(mac) - hostname = None - */ - nic := b.GetNicByMac(cliMac) + var nic *types.Nic + var hostname string + if b.GetServer() != nil && (b.GetTask() == nil || !b.GetTask().NeedPXEBoot()) { + nic = b.GetServer().GetNicByMac(cliMac) + hostname = b.GetServer().GetName() + } else { + nic = b.GetNicByMac(cliMac) + } if nic == nil { return nil, fmt.Errorf("GetNicDHCPConfig no nic found") } - return b.getDHCPConfig(nic, "", false, 0) + return b.getDHCPConfig(nic, hostname, false, 0) } func (b *SBaremetalInstance) GetPXEDHCPConfig(arch uint16) (*dhcp.ResponseConfig, error) { @@ -562,11 +653,11 @@ func (b *SBaremetalInstance) getDHCPConfig( if hostName == "" { hostName = b.GetName() } - accessIP, err := b.manager.Agent.GetAccessIP() + serverIP, err := b.manager.Agent.GetDHCPServerIP() if err != nil { return nil, err } - return GetNicDHCPConfig(nic, accessIP.String(), hostName, isPxe, arch) + return GetNicDHCPConfig(nic, serverIP.String(), hostName, isPxe, arch) } func (b *SBaremetalInstance) GetNotifyUrl() string { @@ -601,7 +692,6 @@ func (b *SBaremetalInstance) SetTask(task tasks.ITask) { } } -// TODO: impl func (b *SBaremetalInstance) InitAdminNetif( cliMac net.HardwareAddr, netConf *types.NetworkConfig, @@ -735,8 +825,44 @@ func (b *SBaremetalInstance) GetRawIPMIConfig() *types.IPMIInfo { return &ipmiInfo } -func (b *SBaremetalInstance) GetServer() interface{} { - return nil +func (b *SBaremetalInstance) GetServer() baremetaltypes.IBaremetalServer { + b.serverLock.Lock() + defer b.serverLock.Unlock() + if !b.desc.Contains("server_id") && b.server != nil { + log.Warningf("baremetal %s server_id not present, remove server %q", b.GetName(), b.server.GetName()) + b.RemoveServer() + return nil + } + return b.server +} + +func (b *SBaremetalInstance) GetServerId() string { + srv := b.GetServer() + if srv == nil { + return "" + } + return srv.GetId() +} + +func (b *SBaremetalInstance) GetServerName() string { + srv := b.GetServer() + if srv == nil { + return "" + } + return srv.GetName() +} + +func (b *SBaremetalInstance) RemoveServer() { + b.serverLock.Lock() + defer b.serverLock.Unlock() + b.removeServer() +} + +func (b *SBaremetalInstance) removeServer() { + if b.server != nil { + b.server.RemoveDesc() + b.server = nil + } } func (b *SBaremetalInstance) SetExistingIPMIIPAddr(ipAddr string) { @@ -853,6 +979,82 @@ func (b *SBaremetalInstance) StartBaremetalResetBMCTask(userCred mcclient.TokenC b.SetTask(task) } +func (b *SBaremetalInstance) DelayedServerReset(_ jsonutils.JSONObject) (jsonutils.JSONObject, error) { + err := b.DoDiskBoot() + return nil, err +} + +func (b *SBaremetalInstance) StartServerCreateTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + b.serverLock.Lock() + defer b.serverLock.Unlock() + if b.server != nil { + return fmt.Errorf("Baremetal %s already have server %s", b.GetName(), b.server.GetName()) + } + descData, err := data.Get("desc") + if err != nil { + return fmt.Errorf("Create data not found server desc: %v", err) + } + server, err := newBaremetalServer(b, descData.(*jsonutils.JSONDict)) + if err != nil { + return fmt.Errorf("New server error: %v", err) + } + b.server = server + b.desc.Set("server_id", jsonutils.NewString(b.server.GetId())) + b.AutoSaveDesc() + task := tasks.NewBaremetalServerCreateTask(b, taskId, data) + b.SetTask(task) + return nil +} + +func (b *SBaremetalInstance) StartServerDeployTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + desc, err := data.Get("desc") + if err != nil { + return fmt.Errorf("Not found desc in data") + } + if err := b.GetServer().SaveDesc(desc); err != nil { + return fmt.Errorf("Save server desc: %v", err) + } + task := tasks.NewBaremetalServerDeployTask(b, taskId, data) + b.SetTask(task) + return nil +} + +func (b *SBaremetalInstance) StartServerRebuildTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + desc, err := data.Get("desc") + if err != nil { + return fmt.Errorf("Not found desc in data") + } + if err := b.GetServer().SaveDesc(desc); err != nil { + return fmt.Errorf("Save server desc: %v", err) + } + task := tasks.NewBaremetalServerRebuildTask(b, taskId, data) + b.SetTask(task) + return nil +} + +func (b *SBaremetalInstance) StartServerStartTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + task, err := tasks.NewBaremetalServerStartTask(b, taskId, data) + if err != nil { + return err + } + b.SetTask(task) + return nil +} + +func (b *SBaremetalInstance) StartServerStopTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) error { + task, err := tasks.NewBaremetalServerStopTask(b, taskId, data) + if err != nil { + return err + } + b.SetTask(task) + return nil +} + +func (b *SBaremetalInstance) StartServerDestroyTask(userCred mcclient.TokenCredential, taskId string, data jsonutils.JSONObject) { + task := tasks.NewBaremetalServerDestroyTask(b, taskId, data) + b.SetTask(task) +} + func (b *SBaremetalInstance) DelayedSyncIPMIInfo(data jsonutils.JSONObject) (jsonutils.JSONObject, error) { ipmiCli := b.GetIPMITool() lanChannel := b.GetIPMILanChannel() @@ -887,21 +1089,33 @@ func (b *SBaremetalInstance) DelayedSyncDesc(data jsonutils.JSONObject) (jsonuti return nil, err } +func (b *SBaremetalInstance) DelayedServerStatus(data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + ps, err := b.GetPowerStatus() + if err != nil { + return nil, err + } + status := PowerStatusToServerStatus(b, ps) + resp := jsonutils.NewDict() + resp.Add(jsonutils.NewString(status), "status") + return resp, err +} + type SBaremetalServer struct { baremetal *SBaremetalInstance desc *jsonutils.JSONDict } -func newBaremetalServer(baremetal *SBaremetalInstance, desc *jsonutils.JSONDict) *SBaremetalServer { +func newBaremetalServer(baremetal *SBaremetalInstance, desc *jsonutils.JSONDict) (*SBaremetalServer, error) { server := &SBaremetalServer{ baremetal: baremetal, desc: desc, } - return server + err := server.SaveDesc(desc) + return server, err } func (server *SBaremetalServer) GetId() string { - id, err := server.desc.GetString("id") + id, err := server.desc.GetString("uuid") if err != nil { log.Fatalf("Get id from desc error: %v", err) } @@ -929,10 +1143,6 @@ func (s *SBaremetalServer) RemoveDesc() { s.baremetal = nil } -func (s *SBaremetalServer) GetDiskConfig() interface{} { - return nil -} - func (s *SBaremetalServer) GetRootTemplateId() string { rootDisk, err := s.desc.GetAt(0, "disks") if err != nil { @@ -943,8 +1153,307 @@ func (s *SBaremetalServer) GetRootTemplateId() string { return id } +func (s *SBaremetalServer) GetDiskConfig() ([]*baremetal.BaremetalDiskConfig, error) { + layouts := make([]baremetal.Layout, 0) + err := s.desc.Unmarshal(&layouts, "disk_config") + if err != nil { + return nil, err + } + return baremetal.GetLayoutRaidConfig(layouts), nil +} + +func (s *SBaremetalServer) DoDiskConfig(term *ssh.Client) error { + raid, nonRaid, pcie, err := detect_storages.DetectStorageInfo(term, true) + if err != nil { + return err + } + storages := make([]*baremetal.BaremetalStorage, 0) + storages = append(storages, raid...) + storages = append(storages, nonRaid...) + storages = append(storages, pcie...) + confs, err := s.GetDiskConfig() + if err != nil { + return err + } + layouts, err := baremetal.CalculateLayout(confs, storages) + if err != nil { + return err + } + diskConfs := baremetal.GroupLayoutResultsByDriverAdapter(layouts) + for _, dConf := range diskConfs { + driver := dConf.Driver + adapter := dConf.Adapter + //TODO: build raid here + log.Errorf("=== driver: %s, adapter: %d", driver, adapter) + time.Sleep(10 * time.Second) // wait 10 seconds for raid status OK + } + + tool := disktool.NewSSHPartitionTool(term) + tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts)) + err = tool.RetrieveDiskInfo() + if err != nil { + return err + } + maxTries := 60 + for tried := 0; !tool.IsAllDisksReady() && tried < maxTries; tried++ { + time.Sleep(5 * time.Second) + tool.RetrieveDiskInfo() + } + + if !tool.IsAllDisksReady() { + return fmt.Errorf("Raid disks are not ready???") + } + + return nil +} + +func (s *SBaremetalServer) DoDiskUnconfig(term *ssh.Client) error { + // tear down raid + //driver := s.baremetal.GetStorageDriver() + //util := raidutil.GetDriver(driver, term) + //if util != nil { + //} + // TODO: + return nil +} + func (s *SBaremetalServer) DoEraseDisk(term *ssh.Client) error { cmd := "/lib/mos/partdestroy.sh" _, err := term.Run(cmd) return err } + +func (s *SBaremetalServer) doCreateRoot(term *ssh.Client, devName string) error { + session := s.baremetal.GetClientSession() + token := session.GetToken().GetTokenString() + url, err := session.GetServiceURL("image", "internalURL") + if err != nil { + return err + } + imageId := s.GetRootTemplateId() + cmd := fmt.Sprintf("/lib/mos/rootcreate.sh %s %s %s %s", token, url, imageId, devName) + log.Errorf("====rootcreate cmd: %q", cmd) + if _, err := term.Run(cmd); err != nil { + return fmt.Errorf("Root create fail: %v", err) + } + return nil +} + +func (s *SBaremetalServer) DoPartitionDisk(term *ssh.Client) ([]*disktool.Partition, error) { + raid, nonRaid, pcie, err := detect_storages.DetectStorageInfo(term, true) + if err != nil { + return nil, err + } + storages := make([]*baremetal.BaremetalStorage, 0) + storages = append(storages, raid...) + storages = append(storages, nonRaid...) + storages = append(storages, pcie...) + confs, err := s.GetDiskConfig() + if err != nil { + return nil, err + } + layouts, err := baremetal.CalculateLayout(confs, storages) + if err != nil { + return nil, err + } + + tool := disktool.NewSSHPartitionTool(term) + tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts)) + err = tool.RetrieveDiskInfo() + if err != nil { + return nil, err + } + + disks, _ := s.desc.GetArray("disks") + if len(disks) == 0 { + return nil, fmt.Errorf("Empty disks in desc") + } + + rootDisk := disks[0] + rootSize, _ := rootDisk.Int("size") + err = s.doCreateRoot(term, tool.GetRootDisk().GetDevName()) + if err != nil { + return nil, fmt.Errorf("Failed to create root: %v", err) + } + + err = tool.RetrievePartitionInfo() + if err != nil { + return nil, fmt.Errorf("Failed to retrieve diskInfo after root created: %v", err) + } + parts := tool.GetPartitions() + if len(parts) == 0 { + return nil, fmt.Errorf("Root disk create failed, no partitions") + } + if err := tool.ResizePartition(0, int(rootSize)); err != nil { + return nil, fmt.Errorf("Fail to resize root to %d, err: %v", rootSize, err) + } + if len(disks) > 1 { + for _, disk := range disks[1:] { + sz, err := disk.Int("size") + if err != nil { + sz = -1 + } + fs, _ := disk.GetString("fs") + uuid, _ := disk.GetString("disk_id") + driver, _ := disk.GetString("driver") + log.Infof("Create partition %d %s", sz, fs) + if err := tool.CreatePartition(-1, int(sz), fs, true, driver, uuid); err != nil { + return nil, fmt.Errorf("Fail to create disk %s: %v", disk.String(), err) + } + } + } + log.Infof("Finish create partitions") + + return tool.GetPartitions(), nil +} + +func (s *SBaremetalServer) DoRebuildRootDisk(term *ssh.Client) ([]*disktool.Partition, error) { + raid, nonRaid, pcie, err := detect_storages.DetectStorageInfo(term, true) + if err != nil { + return nil, err + } + storages := make([]*baremetal.BaremetalStorage, 0) + storages = append(storages, raid...) + storages = append(storages, nonRaid...) + storages = append(storages, pcie...) + confs, err := s.GetDiskConfig() + if err != nil { + return nil, err + } + layouts, err := baremetal.CalculateLayout(confs, storages) + if err != nil { + return nil, err + } + + tool := disktool.NewSSHPartitionTool(term) + tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts)) + err = tool.RetrieveDiskInfo() + if err != nil { + return nil, err + } + + disks, _ := s.desc.GetArray("disks") + if len(disks) == 0 { + return nil, fmt.Errorf("Empty disks in desc") + } + + rootDisk := disks[0] + rootSize, _ := rootDisk.Int("size") + err = s.doCreateRoot(term, tool.GetRootDisk().GetDevName()) + if err != nil { + return nil, fmt.Errorf("Failed to create root: %v", err) + } + + err = tool.RetrievePartitionInfo() + if err != nil { + return nil, fmt.Errorf("Failed to retrieve diskInfo after root created: %v", err) + } + + log.Infof("Resize root to %d MB", rootSize) + if err := tool.ResizePartition(0, int(rootSize)); err != nil { + return nil, fmt.Errorf("Fail to resize root to %d, err: %v", rootSize, err) + } + if len(disks) > 1 { + for _, disk := range disks[1:] { + sz, err := disk.Int("size") + if err != nil { + sz = -1 + } + fs, _ := disk.GetString("fs") + uuid, _ := disk.GetString("disk_id") + driver, _ := disk.GetString("driver") + log.Infof("Create partition %d %s", sz, fs) + if err := tool.CreatePartition(0, int(sz), fs, false, driver, uuid); err != nil { + log.Errorf("Rebuild root create (%s, %d, %s, %s) partition error: %v", uuid, sz, fs, driver, err) + break + } + } + } + log.Infof("Finish create partitions") + + return tool.GetPartitions(), nil +} + +func (s *SBaremetalServer) SyncPartitionSize(term *ssh.Client, parts []*disktool.Partition) ([]jsonutils.JSONObject, error) { + disks, _ := s.desc.GetArray("disks") + rootPartsCnt := len(parts) - len(disks) + 1 + rootParts := parts[0:rootPartsCnt] + dataParts := parts[rootPartsCnt:] + idx := 0 + size := (rootParts[len(rootParts)-1].GetEnd() + 1) * 512 / 1024 / 1024 + disks[idx].(*jsonutils.JSONDict).Set("size", jsonutils.NewInt(int64(size))) + idx += 1 + for _, p := range dataParts { + sizeMB, err := p.GetSizeMB() + if err != nil { + return nil, err + } + disks[idx].(*jsonutils.JSONDict).Set("size", jsonutils.NewInt(int64(sizeMB))) + disks[idx].(*jsonutils.JSONDict).Set("dev", jsonutils.NewString(p.GetDev())) + idx++ + } + return disks, nil +} + +func (s *SBaremetalServer) DoDeploy(term *ssh.Client, data jsonutils.JSONObject, isInit bool) (jsonutils.JSONObject, error) { + publicKey := sshkeys.GetKeys(data) + deploys, _ := data.GetArray("deploys") + password, _ := data.GetString("password") + resetPassword := jsonutils.QueryBoolean(data, "reset_password", false) + if resetPassword && len(password) == 0 { + password = seclib.RandomPassword(12) + } + return s.deployFs(term, guestfs.NewDeployInfo(publicKey, deploys, password, isInit, false)) +} + +func (s *SBaremetalServer) deployFs(term *ssh.Client, deployInfo *guestfs.SDeployInfo) (jsonutils.JSONObject, error) { + raid, nonRaid, pcie, err := detect_storages.DetectStorageInfo(term, true) + if err != nil { + return nil, err + } + storages := make([]*baremetal.BaremetalStorage, 0) + storages = append(storages, raid...) + storages = append(storages, nonRaid...) + storages = append(storages, pcie...) + confs, err := s.GetDiskConfig() + if err != nil { + return nil, err + } + layouts, err := baremetal.CalculateLayout(confs, storages) + if err != nil { + return nil, err + } + rootDev, rootfs, err := sshpart.MountSSHRootfs(term, layouts) + if err != nil { + return nil, fmt.Errorf("Find rootfs error: %s", err) + } + defer func() { + if err := rootDev.Unmount(); err != nil { + log.Errorf("Unmount %s error: %v", rootDev.GetMountPath(), err) + } + }() + if strings.ToLower(rootfs.GetOs()) == "windows" { + return nil, fmt.Errorf("Unsupported OS: %s", rootfs.GetOs()) + } + return guestfs.DeployGuestFs(rootfs, s.desc, deployInfo) +} + +func (s *SBaremetalServer) GetNics() []types.ServerNic { + nics := []types.ServerNic{} + err := s.desc.Unmarshal(&nics, "nics") + if err != nil { + log.Errorf("Unmarshal desc to get server nics error: %v", err) + return nil + } + return nics +} + +func (s *SBaremetalServer) GetNicByMac(mac net.HardwareAddr) *types.Nic { + for _, n := range s.GetNics() { + if n.GetMac().String() == mac.String() { + nic := n.ToNic() + return &nic + } + } + return nil +} diff --git a/pkg/baremetal/nic.go b/pkg/baremetal/nic.go index 6788e69e93..ec04793ded 100644 --- a/pkg/baremetal/nic.go +++ b/pkg/baremetal/nic.go @@ -32,6 +32,11 @@ func GetNicDHCPConfig( subnetMask := net.ParseIP(netutils.Masklen2Mask(n.MaskLen).String()) + routes := make([][]string, 0) + for _, route := range n.Routes { + routes = append(routes, []string{route[0], route[1]}) + } + conf := &dhcp.ResponseConfig{ ServerIP: net.ParseIP(serverIP), ClientIP: net.ParseIP(ipAddr.String()), @@ -42,9 +47,9 @@ func GetNicDHCPConfig( Domain: n.Domain, OsName: "Linux", Hostname: hostName, - // TODO: routes opt - LeaseTime: time.Duration(o.Options.DhcpLeaseTime) * time.Second, - RenewalTime: time.Duration(o.Options.DhcpRenewalTime) * time.Second, + Routes: routes, + LeaseTime: time.Duration(o.Options.DhcpLeaseTime) * time.Second, + RenewalTime: time.Duration(o.Options.DhcpRenewalTime) * time.Second, } if isPxe { diff --git a/pkg/baremetal/options/options.go b/pkg/baremetal/options/options.go index bba3be34fc..b357b4a569 100644 --- a/pkg/baremetal/options/options.go +++ b/pkg/baremetal/options/options.go @@ -5,7 +5,7 @@ import ( ) type BaremetalOptions struct { - cloudcommon.Options + cloudcommon.CommonOptions ListenInterface string `help:"Master net interface of baremetal server" default:"br0"` AccessAddress string `help:"Management IP address of baremetal server, only need to use when multiple address bind to ListenInterface"` diff --git a/pkg/baremetal/pxe/dhcp.go b/pkg/baremetal/pxe/dhcp.go index cfead9797f..9b36a74745 100644 --- a/pkg/baremetal/pxe/dhcp.go +++ b/pkg/baremetal/pxe/dhcp.go @@ -58,6 +58,9 @@ func (h *DHCPHandler) ServeDHCP(pkt *dhcp.Packet, _ *net.Interface) (*dhcp.Packe if err != nil { return nil, err } + if conf == nil { + return nil, fmt.Errorf("Empty packet config") + } return dhcp.MakeReplyPacket(pkt, conf) } @@ -144,7 +147,7 @@ func (h *DHCPHandler) fetchConfig() (*dhcp.ResponseConfig, error) { return h.baremetalInstance.GetPXEDHCPConfig(h.ClientArch) } // ignore - log.Warningf("No need to pxeboot, ignore the request ...(mac:%s guid:%d)", h.ClientMac, h.ClientGuid) + log.Warningf("No need to pxeboot, ignore the request ...(mac:%s guid:%s)", h.ClientMac, h.ClientGuid) return nil, nil } else { // handle normal DHCP request diff --git a/pkg/baremetal/service/service.go b/pkg/baremetal/service/service.go index 52f0ee48e9..d18604cf82 100644 --- a/pkg/baremetal/service/service.go +++ b/pkg/baremetal/service/service.go @@ -15,12 +15,12 @@ import ( func StartService() { consts.SetServiceType("baremetal") - cloudcommon.ParseOptions(&o.Options, &o.Options.Options, os.Args, "baremetal.conf") - cloudcommon.InitAuth(&o.Options.Options, startAgent) + cloudcommon.ParseOptions(&o.Options, &o.Options.CommonOptions, os.Args, "baremetal.conf") + cloudcommon.InitAuth(&o.Options.CommonOptions, startAgent) - app := cloudcommon.InitApp(&o.Options.Options) + app := cloudcommon.InitApp(&o.Options.CommonOptions, false) handler.InitHandlers(app) - cloudcommon.ServeForever(app, &o.Options.Options) + cloudcommon.ServeForever(app, &o.Options.CommonOptions) } func startAgent() { diff --git a/pkg/baremetal/status/const.go b/pkg/baremetal/status/const.go index 71b91de1c2..056efc80ea 100644 --- a/pkg/baremetal/status/const.go +++ b/pkg/baremetal/status/const.go @@ -20,3 +20,10 @@ const ( START_FAIL = "start_fail" STOP_FAIL = "stop_fail" ) + +const ( + SERVER_RUNNING = "running" + SERVER_ADMIN = "admin" + SERVER_READY = "ready" + SERVER_UNKNOWN = "unknown" +) diff --git a/pkg/baremetal/tasks/base.go b/pkg/baremetal/tasks/base.go index d6d0d1c5df..ae9f1b7a80 100644 --- a/pkg/baremetal/tasks/base.go +++ b/pkg/baremetal/tasks/base.go @@ -130,7 +130,9 @@ type ITask interface { GetName() string Execute(ITask ITask, args interface{}) + SetSSHStageParams(task ITask, remoteIP string, passwd string) SSHExecute(task ITask, remoteIP string, passwd string, args interface{}) + NeedPXEBoot() bool } func NewTaskQueue() *TaskQueue { @@ -159,11 +161,6 @@ func (q *TaskQueue) AppendTask(task ITask) *TaskQueue { return q } -type IBaremetalTask interface { - ITask - NeedPXEBoot() bool -} - type SBaremetalTaskBase struct { Baremetal IBaremetal userCred mcclient.TokenCredential @@ -218,20 +215,25 @@ func (task *SBaremetalTaskBase) Execute(iTask ITask, args interface{}) { ExecuteTask(iTask, args) } +func (task *SBaremetalTaskBase) SetSSHStageParams(iTask ITask, remoteIP string, password string) { + iTask.SetStage(sshStageW(iTask.GetSSHStage(), remoteIP, password).Do) +} + func (task *SBaremetalTaskBase) SSHExecute( iTask ITask, remoteIP string, password string, args interface{}, ) { - iTask.SetStage(sshStageW(iTask.GetSSHStage(), remoteIP, password).Do) + //iTask.SetStage(sshStageW(iTask.GetSSHStage(), remoteIP, password).Do) + task.SetSSHStageParams(iTask, remoteIP, password) ExecuteTask(iTask, args) } -func (task *SBaremetalTaskBase) CallNextStage(iTask ITask, stage TaskStageFunc, args interface{}) { - iTask.SetStage(stage) - ExecuteTask(iTask, args) -} +//func (task *SBaremetalTaskBase) CallNextStage(iTask ITask, stage TaskStageFunc, args interface{}) { +//iTask.SetStage(stage) +//ExecuteTask(iTask, args) +//} func (task *SBaremetalTaskBase) GetClientSession() *mcclient.ClientSession { return task.Baremetal.GetClientSession() @@ -303,6 +305,10 @@ func (self *SBaremetalTaskBase) EnsurePowerUp(bootdev string) error { return nil } +func (self *SBaremetalTaskBase) NeedPXEBoot() bool { + return false +} + type IPXEBootTask interface { ITask OnPXEBoot(ctx context.Context, cli *ssh.Client, args interface{}) error @@ -333,14 +339,16 @@ func (self *SBaremetalPXEBootTaskBase) InitPXEBootTask(pxeBootTask IPXEBootTask, sshConf, _ := self.Baremetal.GetSSHConfig() if sshConf != nil && self.Baremetal.TestSSHConfig() { pxeBootTask.SetSSHStage(pxeBootTask.OnPXEBoot) - self.SSHExecute(pxeBootTask, sshConf.RemoteIP, sshConf.Password, nil) + pxeBootTask.SetSSHStageParams(pxeBootTask, sshConf.RemoteIP, sshConf.Password) return self } // Do soft reboot if data != nil && jsonutils.QueryBoolean(data, "soft_boot", false) { self.startTime = time.Now() self.Baremetal.DoPowerShutdown(true) - self.CallNextStage(self, self.WaitForShutdown, nil) + //self.CallNextStage(self, self.WaitForShutdown, nil) + self.SetStage(self.WaitForShutdown) + return self } // shutdown and power up to PXE mode @@ -351,6 +359,10 @@ func (self *SBaremetalPXEBootTaskBase) InitPXEBootTask(pxeBootTask IPXEBootTask, return self } +func (self *SBaremetalPXEBootTaskBase) NeedPXEBoot() bool { + return true +} + func (self *SBaremetalPXEBootTaskBase) WaitForShutdown(ctx context.Context, args interface{}) error { self.SetStage(self.OnStopComplete) status, err := self.Baremetal.GetPowerStatus() diff --git a/pkg/baremetal/tasks/basedeploy.go b/pkg/baremetal/tasks/basedeploy.go index 0405205b24..1e33d54383 100644 --- a/pkg/baremetal/tasks/basedeploy.go +++ b/pkg/baremetal/tasks/basedeploy.go @@ -63,7 +63,6 @@ func (self *SBaremetalServerBaseDeployTask) OnPXEBoot(ctx context.Context, term if utils.IsInStringArray(onFinishAction, []string{"restart", "shutdown"}) { err = self.EnsurePowerShutdown(false) if err != nil { - // TODO: maybe taskFail directly? return err } if onFinishAction == "restart" { diff --git a/pkg/baremetal/tasks/baseprepare.go b/pkg/baremetal/tasks/baseprepare.go index 720e34af97..1697322741 100644 --- a/pkg/baremetal/tasks/baseprepare.go +++ b/pkg/baremetal/tasks/baseprepare.go @@ -270,6 +270,7 @@ func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error { task.collectDiskInfo(updateInfo, diskInfo) updateData := jsonutils.Marshal(updateInfo) updateData.(*jsonutils.JSONDict).Update(ipmiInfo.ToPrepareParams()) + log.Infof("=== prepare update info: %s", updateData.PrettyString()) _, err = modules.Hosts.Update(task.getClientSession(), task.baremetal.GetId(), updateData) if err != nil { log.Errorf("Update baremetal info error: %v", err) @@ -405,7 +406,7 @@ func getDMICPUInfo(cli *ssh.Client) (*types.DMICPUInfo, error) { } func getDMIMemInfo(cli *ssh.Client) (*types.DMIMemInfo, error) { - ret, err := cli.Run("/usr/sbin/dmidecode -t 4") + ret, err := cli.Run("/usr/sbin/dmidecode -t 17") if err != nil { return nil, err } diff --git a/pkg/baremetal/tasks/create.go b/pkg/baremetal/tasks/create.go new file mode 100644 index 0000000000..b022e4df2f --- /dev/null +++ b/pkg/baremetal/tasks/create.go @@ -0,0 +1,68 @@ +package tasks + +import ( + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalServerCreateTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalServerCreateTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) *SBaremetalServerCreateTask { + task := new(SBaremetalServerCreateTask) + baseTask := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task +} + +func (self *SBaremetalServerCreateTask) GetName() string { + return "BaremetalServerCreateTask" +} + +func (self *SBaremetalServerCreateTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + // Build raid + err := self.Baremetal.GetServer().DoDiskConfig(term) + if err != nil { + return nil, self.onError(term, err) + } + time.Sleep(2 * time.Second) + if err := self.Baremetal.GetServer().DoEraseDisk(term); err != nil { + return nil, self.onError(term, err) + } + time.Sleep(2 * time.Second) + parts, err := self.Baremetal.GetServer().DoPartitionDisk(term) + if err != nil { + return nil, self.onError(term, err) + } + data := jsonutils.NewDict() + disks, err := self.Baremetal.GetServer().SyncPartitionSize(term, parts) + if err != nil { + return nil, self.onError(term, err) + } + data.Add(jsonutils.Marshal(disks), "disks") + deployInfo, err := self.Baremetal.GetServer().DoDeploy(term, data, true) + if err != nil { + return nil, self.onError(term, err) + } + if deployInfo != nil { + data.Update(deployInfo) + } + return data, nil +} + +func (self *SBaremetalServerCreateTask) onError(term *ssh.Client, err error) error { + if err1 := self.Baremetal.GetServer().DoEraseDisk(term); err1 != nil { + log.Warningf("EraseDisk error: %v", err1) + } + self.Baremetal.AutoSyncStatus() + return err +} diff --git a/pkg/baremetal/tasks/deploy.go b/pkg/baremetal/tasks/deploy.go new file mode 100644 index 0000000000..169bbca68c --- /dev/null +++ b/pkg/baremetal/tasks/deploy.go @@ -0,0 +1,30 @@ +package tasks + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalServerDeployTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalServerDeployTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) *SBaremetalServerDeployTask { + task := new(SBaremetalServerDeployTask) + baseTask := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task +} + +func (self *SBaremetalServerDeployTask) GetName() string { + return "BaremetalServerDeployTask" +} + +func (self *SBaremetalServerDeployTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + return self.Baremetal.GetServer().DoDeploy(term, self.data, false) +} diff --git a/pkg/baremetal/tasks/destroy.go b/pkg/baremetal/tasks/destroy.go new file mode 100644 index 0000000000..f3f4830f2e --- /dev/null +++ b/pkg/baremetal/tasks/destroy.go @@ -0,0 +1,38 @@ +package tasks + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalServerDestroyTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalServerDestroyTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) *SBaremetalServerDestroyTask { + task := new(SBaremetalServerDestroyTask) + baseTask := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task +} + +func (self *SBaremetalServerDestroyTask) GetName() string { + return "BaremetalServerDestroyTask" +} + +func (self *SBaremetalServerDestroyTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + if err := self.Baremetal.GetServer().DoEraseDisk(term); err != nil { + log.Errorf("Delete server do erase disk: %v", err) + } + if err := self.Baremetal.GetServer().DoDiskUnconfig(term); err != nil { + log.Errorf("Baremetal do disk unconfig: %v", err) + } + self.Baremetal.RemoveServer() + return nil, nil +} diff --git a/pkg/baremetal/tasks/interface.go b/pkg/baremetal/tasks/interface.go index 3f851536e4..3e084dff4f 100644 --- a/pkg/baremetal/tasks/interface.go +++ b/pkg/baremetal/tasks/interface.go @@ -5,6 +5,7 @@ import ( "yunion.io/x/jsonutils" + baremetaltypes "yunion.io/x/onecloud/pkg/baremetal/types" "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -23,6 +24,7 @@ type IBaremetal interface { GetRawIPMIConfig() *types.IPMIInfo GetIPMINic(mac net.HardwareAddr) *types.Nic SetExistingIPMIIPAddr(ipAddr string) + GetServer() baremetaltypes.IBaremetalServer SyncStatus(status, reason string) AutoSyncStatus() @@ -33,4 +35,6 @@ type IBaremetal interface { DoPowerShutdown(soft bool) error DoPXEBoot() error DoDiskBoot() error + + RemoveServer() } diff --git a/pkg/baremetal/tasks/prepare.go b/pkg/baremetal/tasks/prepare.go index b2499047e3..6fe83cbf15 100644 --- a/pkg/baremetal/tasks/prepare.go +++ b/pkg/baremetal/tasks/prepare.go @@ -24,6 +24,10 @@ func NewBaremetalServerPrepareTask( return task } +func (self *SBaremetalServerPrepareTask) NeedPXEBoot() bool { + return true +} + func (self *SBaremetalServerPrepareTask) GetName() string { return "BaremetalServerPrepareTask" } diff --git a/pkg/baremetal/tasks/rebuild.go b/pkg/baremetal/tasks/rebuild.go new file mode 100644 index 0000000000..9827ec1bcf --- /dev/null +++ b/pkg/baremetal/tasks/rebuild.go @@ -0,0 +1,47 @@ +package tasks + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type SBaremetalServerRebuildTask struct { + *SBaremetalServerBaseDeployTask +} + +func NewBaremetalServerRebuildTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) *SBaremetalServerRebuildTask { + task := new(SBaremetalServerRebuildTask) + baseTask := newBaremetalServerBaseDeployTask(baremetal, taskId, data, task) + task.SBaremetalServerBaseDeployTask = baseTask + return task +} + +func (self *SBaremetalServerRebuildTask) GetName() string { + return "BaremetalServerRebuildTask" +} + +func (self *SBaremetalServerRebuildTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { + parts, err := self.Baremetal.GetServer().DoRebuildRootDisk(term) + if err != nil { + return nil, fmt.Errorf("Rebuild root disk: %v", err) + } + disks, err := self.Baremetal.GetServer().SyncPartitionSize(term, parts) + if err != nil { + return nil, fmt.Errorf("SyncPartitionSize: %v", err) + } + data := jsonutils.NewDict() + data.Add(jsonutils.NewArray(disks...), "disks") + deployInfo, err := self.Baremetal.GetServer().DoDeploy(term, data, false) + if err != nil { + return nil, fmt.Errorf("DoDeploy: %v", err) + } + data.Update(deployInfo) + return data, nil +} diff --git a/pkg/baremetal/tasks/start.go b/pkg/baremetal/tasks/start.go new file mode 100644 index 0000000000..26bfb68cd2 --- /dev/null +++ b/pkg/baremetal/tasks/start.go @@ -0,0 +1,55 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" +) + +type SBaremetalServerStartTask struct { + *SBaremetalTaskBase +} + +func NewBaremetalServerStartTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (*SBaremetalServerStartTask, error) { + baseTask := newBaremetalTaskBase(baremetal, taskId, data) + self := &SBaremetalServerStartTask{ + SBaremetalTaskBase: baseTask, + } + if err := self.Baremetal.DoDiskBoot(); err != nil { + return nil, fmt.Errorf("DoDiskBoot: %v", err) + } + self.SetStage(self.WaitForStart) + ExecuteTask(self, nil) + return self, nil +} + +func (self *SBaremetalServerStartTask) GetName() string { + return "BaremetalServerStartTask" +} + +func (self *SBaremetalServerStartTask) WaitForStart(ctx context.Context, args interface{}) error { + self.SetStage(self.OnStartComplete) + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return fmt.Errorf("GetPowerStatus: %v", err) + } + log.Infof("%s WaitForStart status=%s", self.GetName(), status) + if status == types.POWER_STATUS_ON { + ExecuteTask(self, nil) + } + return nil +} + +func (self *SBaremetalServerStartTask) OnStartComplete(ctx context.Context, args interface{}) error { + self.Baremetal.SyncAllStatus(types.POWER_STATUS_ON) + SetTaskComplete(self, nil) + return nil +} diff --git a/pkg/baremetal/tasks/stop.go b/pkg/baremetal/tasks/stop.go new file mode 100644 index 0000000000..499cf3d8fc --- /dev/null +++ b/pkg/baremetal/tasks/stop.go @@ -0,0 +1,59 @@ +package tasks + +import ( + "context" + "fmt" + "time" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" +) + +type SBaremetalServerStopTask struct { + *SBaremetalTaskBase + startTime time.Time +} + +func NewBaremetalServerStopTask( + baremetal IBaremetal, + taskId string, + data jsonutils.JSONObject, +) (*SBaremetalServerStopTask, error) { + baseTask := newBaremetalTaskBase(baremetal, taskId, data) + self := &SBaremetalServerStopTask{ + SBaremetalTaskBase: baseTask, + } + if err := self.Baremetal.DoPowerShutdown(true); err != nil { + return nil, fmt.Errorf("Do power shutdown error: %v", err) + } + self.startTime = time.Now() + self.SetStage(self.WaitForStop) + ExecuteTask(self, nil) + return self, nil +} + +func (self *SBaremetalServerStopTask) GetName() string { + return "BaremetalServerStopTask" +} + +func (self *SBaremetalServerStopTask) WaitForStop(ctx context.Context, args interface{}) error { + self.SetStage(self.OnStopComplete) + status, err := self.Baremetal.GetPowerStatus() + if err != nil { + return fmt.Errorf("GetPowerStatus: %v", err) + } + if status == types.POWER_STATUS_OFF { + ExecuteTask(self, nil) + } else if time.Since(self.startTime) >= 90*time.Second { + if err := self.Baremetal.DoPowerShutdown(false); err != nil { + return err + } + } + return nil +} + +func (self *SBaremetalServerStopTask) OnStopComplete(ctx context.Context, args interface{}) error { + SetTaskComplete(self, nil) + return nil +} diff --git a/pkg/baremetal/tasks/worker.go b/pkg/baremetal/tasks/worker.go index 410feb4e77..05f607ca6d 100644 --- a/pkg/baremetal/tasks/worker.go +++ b/pkg/baremetal/tasks/worker.go @@ -13,7 +13,7 @@ import ( var baremetalTaskWorkerMan *appsrv.SWorkerManager func init() { - baremetalTaskWorkerMan = appsrv.NewWorkerManager("BaremetalTaskWorkerManager", 8, 1024) + baremetalTaskWorkerMan = appsrv.NewWorkerManager("BaremetalTaskWorkerManager", 8, 1024, false) } func ExecuteTask(task ITask, args interface{}) { diff --git a/pkg/baremetal/types/types.go b/pkg/baremetal/types/types.go new file mode 100644 index 0000000000..dddcb2af70 --- /dev/null +++ b/pkg/baremetal/types/types.go @@ -0,0 +1,26 @@ +package types + +import ( + "net" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/baremetal/utils/disktool" + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +type IBaremetalServer interface { + GetName() string + GetId() string + RemoveDesc() + DoDiskUnconfig(term *ssh.Client) error + DoDiskConfig(term *ssh.Client) error + DoEraseDisk(term *ssh.Client) error + DoPartitionDisk(term *ssh.Client) ([]*disktool.Partition, error) + DoRebuildRootDisk(term *ssh.Client) ([]*disktool.Partition, error) + SyncPartitionSize(term *ssh.Client, parts []*disktool.Partition) ([]jsonutils.JSONObject, error) + DoDeploy(term *ssh.Client, data jsonutils.JSONObject, isInit bool) (jsonutils.JSONObject, error) + SaveDesc(desc jsonutils.JSONObject) error + GetNicByMac(mac net.HardwareAddr) *types.Nic +} diff --git a/pkg/baremetal/utils/disktool/disktool.go b/pkg/baremetal/utils/disktool/disktool.go new file mode 100644 index 0000000000..d7f1fcb449 --- /dev/null +++ b/pkg/baremetal/utils/disktool/disktool.go @@ -0,0 +1,665 @@ +package disktool + +import ( + "fmt" + "math" + "strings" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/fileutils" + "yunion.io/x/onecloud/pkg/util/ssh" + "yunion.io/x/onecloud/pkg/util/sysutils" +) + +const ( + // MB_SECTORS = 2048 // 1MiB = 2014 sectors + GPT_SECTORS = 34 + + RAID_DRVIER = "raid" + NONRAID_DRIVER = "nonraid" + PCIE_DRIVER = "pcie" +) + +type Partition struct { + disk *DiskPartitions + index int + bootable bool + start int + end int + count int + diskType string + fs string + dev string +} + +func NewPartition( + disk *DiskPartitions, + index int, bootable bool, + start int, end int, count int, + diskType string, fs string, dev string, +) *Partition { + return &Partition{ + disk: disk, + index: index, + bootable: bootable, + start: start, + end: end, + count: count, + diskType: diskType, + fs: fs, + dev: dev, + } +} + +func (p *Partition) GetStart() int { + return p.start +} + +func (p *Partition) GetEnd() int { + return p.end +} + +func (p *Partition) GetDev() string { + return p.dev +} + +func (p *Partition) String() string { + bootStr := "" + if p.bootable { + bootStr = " boot" + } + return fmt.Sprintf("%s %d %d %s %s%s", p.dev, p.start, p.end, p.diskType, p.fs, bootStr) +} + +func (p *Partition) Format(fs string, uuid string) error { + cmd := []string{} + cmdUUID := []string{} + switch fs { + case "swap": + cmd = []string{"/sbin/mkswap", "-U", uuid} + case "ext2": + cmd = []string{"/usr/sbin/mkfs.ext2"} + cmdUUID = []string{"/usr/sbin/tune2fs", "-U", uuid} + case "ext3": + cmd = []string{"/usr/sbin/mkfs.ext3"} + cmdUUID = []string{"/usr/sbin/tune2fs", "-U", uuid} + case "ext4": + cmd = []string{"/usr/sbin/mkfs.ext4", "-E", "lazy_itable_init=1"} + cmdUUID = []string{"/usr/sbin/tune2fs", "-U", uuid} + case "ext4dev": + cmd = []string{"/usr/sbin/mkfs.ext4dev", "-E", "lazy_itable_init=1"} + cmdUUID = []string{"/usr/sbin/tune2fs", "-U", uuid} + case "xfs": + cmd = []string{"/sbin/mkfs.xfs", "-f"} + cmdUUID = []string{"PATH=/bin:/sbin:/usr/bin:/usr/sbin /usr/sbin/xfs_admin", "-U", uuid} + default: + return fmt.Errorf("Unsupported filesystem %s", fs) + } + cmd = append(cmd, p.dev) + cmds := []string{strings.Join(cmd, " ")} + if len(cmdUUID) != 0 { + cmdUUID = append(cmdUUID, p.dev) + cmds = append(cmds, strings.Join(cmdUUID, " ")) + } + _, err := p.Run(cmds...) + return err +} + +func (p *Partition) Fsck() error { + if p.fs == "" { + return fmt.Errorf("filesystem is empty") + } + cmd := []string{} + if strings.HasPrefix(p.fs, "ext") { + cmd = []string{fmt.Sprintf("/usr/sbin/fsck.%s", p.fs), "-f", "-p"} + } else if p.fs == "xfs" { + cmd = []string{"/sbin/fsck.xfs"} + } else { + return fmt.Errorf("Unsupported fsck filesystem: %s", p.fs) + } + cmd = append(cmd, p.dev) + _, err := p.Run(strings.Join(cmd, " ")) + return err +} + +func (p *Partition) Run(cmds ...string) ([]string, error) { + return p.disk.Run(cmds...) +} + +func (p *Partition) ResizeFs() error { + if p.fs == "" { + return nil + } + cmd := []string{} + if strings.HasPrefix(p.fs, "linux-swap") { + cmd = []string{"/sbin/mkswap", p.dev} + } else if strings.HasPrefix(p.fs, "ext") { + if err := p.Fsck(); err != nil { + log.Warningf("FSCK error: %v", err) + } + cmd = []string{"/usr/sbin/resize2fs", p.dev} + } else if p.fs == "xfs" { + return p.ResizeXfs() + } + if len(cmd) == 0 { + return nil + } + _, err := p.Run(strings.Join(cmd, " ")) + return err +} + +func (p *Partition) ResizeXfs() error { + mountPath := fmt.Sprintf("/tmp/%s", strings.Replace(p.dev, "/", "", -1)) + cmds := []string{ + fmt.Sprintf("mkdir -p %s", mountPath), + fmt.Sprintf("mount -t xfs %s %s", p.dev, mountPath), + fmt.Sprintf("/usr/sbin/xfs_growfs -d %s", mountPath), + fmt.Sprintf("umount %s", mountPath), + fmt.Sprintf("rm -fr %s", mountPath), + } + _, err := p.Run(cmds...) + if err != nil { + log.Errorf("Resize xfs error: %v", err) + cmds = []string{ + fmt.Sprintf("umount %s", mountPath), + fmt.Sprintf("rm -rf %s", mountPath), + } + _, err = p.Run(cmds...) + if err != nil { + log.Errorf("Umount error: %v", err) + return err + } + } + return nil +} + +func (p *Partition) GetSizeMB() (int, error) { + log.Infof("GetSizeMB: start %d, end: %d, count: %d", p.start, p.end, p.count) + if p.count != (p.end - p.start + 1) { + return 0, fmt.Errorf("Count(%d) != End(%d)-Start(%d)+1", p.count, p.end, p.start) + } + return p.count * 512 / 1024 / 1024, nil +} + +type DiskPartitions struct { + driver string + adapter int + sizeMB int64 // MB + tool *PartitionTool + dev string + devName string + sectors int + blockSize int + rotate bool + desc string + label string + partitions []*Partition +} + +func newDiskPartitions(driver string, adapter int, sizeMB int64, blockSize int, tool *PartitionTool) *DiskPartitions { + ps := new(DiskPartitions) + ps.driver = driver + ps.adapter = adapter + ps.sizeMB = sizeMB + ps.tool = tool + ps.blockSize = blockSize + ps.partitions = make([]*Partition, 0) + return ps +} + +func (p *DiskPartitions) SetInfo(info *types.DiskInfo) *DiskPartitions { + p.dev = fmt.Sprintf("/dev/%s", info.Dev) + p.devName = info.Dev + p.sectors = info.Sector + p.desc = info.ModuleInfo + p.blockSize = info.Block + if p.blockSize == 4096 { + p.sectors = (p.sectors >> 3) + } + return p +} + +func (ps *DiskPartitions) MBSectors() int { + return 1024 * 1024 / ps.blockSize +} + +func (ps *DiskPartitions) String() string { + return fmt.Sprintf("%s %d %s", ps.devName, ps.sizeMB, ps.driver) +} + +func (ps *DiskPartitions) DebugString() string { + partitionsStr := []string{} + for _, p := range ps.partitions { + partitionsStr = append(partitionsStr, fmt.Sprintf("%#v", *p)) + } + return fmt.Sprintf("driver: %s, dev: %s, sectors: %d, partitions: %#v", ps.driver, ps.dev, ps.sectors, partitionsStr) +} + +func (ps *DiskPartitions) IsReady() bool { + if ps.dev == "" { + return false + } + return true +} + +func (ps *DiskPartitions) GetDevName() string { + return ps.devName +} + +func (ps *DiskPartitions) RetrievePartitionInfo() error { + ps.partitions = make([]*Partition, 0) + cmd := []string{"/usr/sbin/parted", "-s", ps.dev, "--", "unit", "s", "print"} + ret, err := ps.Run(strings.Join(cmd, " ")) + if err != nil { + return err + } + parts, label := fileutils.ParseDiskPartitions(ps.dev, ret) + ps.label = label + for _, part := range parts { + ps.addPartition(part) + } + return nil +} + +func (ps *DiskPartitions) addPartition(p fileutils.Partition) { + part := NewPartition(ps, p.Index, p.Bootable, p.Start, p.End, p.Count, p.DiskType, p.Fs, p.DevName) + ps.partitions = append(ps.partitions, part) +} + +func (ps *DiskPartitions) GPTEndSector() int { + return ps.sectors - GPT_SECTORS +} + +func (ps *DiskPartitions) FsToTypeCode(fs string) string { + if strings.Contains(fs, "swap") { + return "8200" + } else if strings.HasPrefix(fs, "ntfs") || strings.HasPrefix(fs, "fat") { + return "0700" + } + return "8300" +} + +func (ps *DiskPartitions) doResize(dev string, cmd string) error { + cmds := []string{} + cmds = append(cmds, cmd) + cmds = append(cmds, fmt.Sprintf("/sbin/hdparm -f %s", dev)) + cmds = append(cmds, fmt.Sprintf("/sbin/hdparm -z %s", dev)) + _, err := ps.tool.Run(cmds...) + if err != nil { + return err + } + return ps.RetrievePartitionInfo() +} + +func (ps *DiskPartitions) ResizePartition(offsetMB int) error { + if len(ps.partitions) <= 0 { + return fmt.Errorf("ResizePartitions error: total %d partitions", len(ps.partitions)) + } + var cmd string + if ps.label == "msdos" { + part := ps.partitions[len(ps.partitions)-1] + if part.diskType == "extended" { + log.Infof("Find last partition an empty extended partition, removed it") + cmd := fmt.Sprintf("/usr/sbin/parted -a none -s %s -- rm %d", part.disk.dev, part.index) + if err := ps.doResize(part.disk.dev, cmd); err != nil { + log.Errorf("Fail to remove empty extended partition") + return err + } + } + } + part := ps.partitions[len(ps.partitions)-1] + var end int + if offsetMB <= 0 { + end = ps.GPTEndSector() + } else { + end = offsetMB*ps.MBSectors() - 1 + if end > ps.GPTEndSector() { + end = ps.GPTEndSector() + } + } + if end < part.end { + log.Warningf("Cannot reduce size %d %d, no need to resize", end, part.end) + end = part.end + } + if ps.label == "msdos" { + if part.diskType == "logical" { + extendIdx := -1 + for i := range ps.partitions { + if ps.partitions[i].diskType == "extended" { + log.Infof("Find extended at %d", i) + extendIdx = i + break + } + } + if extendIdx < 0 { + return fmt.Errorf("To resize logical parition, but fail to find extend partiton") + } + cmd = fmt.Sprintf("/usr/sbin/parted -a none -s %s -- unit s", part.disk.dev) + partsLen := len(ps.partitions) + for i := partsLen - 1; i > extendIdx-1; i-- { + cmd = fmt.Sprintf("%s rm %d", cmd, ps.partitions[i].index) + } + for i := extendIdx; i < partsLen; i++ { + cmdLet := fmt.Sprintf("mkpart %s %d", ps.partitions[i].diskType, ps.partitions[i].start) + if i == extendIdx { + cmdLet = fmt.Sprintf("%s %d", cmdLet, ps.GPTEndSector()) + } else if i == (len(ps.partitions) - 1) { + cmdLet = fmt.Sprintf("%s %d", cmdLet, end) + } else { + cmdLet = fmt.Sprintf("%s %d", cmdLet, ps.partitions[i].end) + } + cmd = fmt.Sprintf("%s %s", cmd, cmdLet) + } + } else { + cmd = fmt.Sprintf("/usr/sbin/parted -a none -s %s -- unit s rm %d mkpart %s", part.disk.dev, part.index, part.diskType) + cmd = fmt.Sprintf("%s %d %d", cmd, part.start, part.end) + if part.bootable { + cmd = fmt.Sprintf("%s set %d boot on", cmd, part.index) + } + } + } else { + // gpt + cmd = fmt.Sprintf("/usr/sbin/sgdisk --set-alignment=1 --delete=%d", part.index) + cmd = fmt.Sprintf("%s --new=%d:%d:%d", cmd, part.index, part.start, part.end) + if len(part.diskType) != 0 { + cmd = fmt.Sprintf("%s --change-name=%d:\"%s\"", cmd, part.index, part.diskType) + } + if len(part.fs) != 0 { + cmd = fmt.Sprintf("%s --typecode=%d:%s", cmd, part.index, ps.FsToTypeCode(part.fs)) + } + cmd = fmt.Sprintf("%s %s", cmd, ps.dev) + } + log.Infof("Resize cmd: %s", cmd) + if err := ps.doResize(part.disk.dev, cmd); err != nil { + return err + } + return ps.partitions[len(ps.partitions)-1].ResizeFs() +} + +func (ps *DiskPartitions) IsSpaceAvailable(sizeMB int) bool { + start := ps.getNextPartStart() + freeSect := ps.GPTEndSector() - start + if sizeMB <= 0 { + sizeMB = 1 + } + reqSect := sizeMB * ps.MBSectors() + if reqSect > freeSect { + log.Warningf("No space require %d(%d) left %d", reqSect, sizeMB, freeSect) + return false + } + return true +} + +func (ps *DiskPartitions) MakeLabel() error { + label := "gpt" + if ps.sizeMB <= 1024*1024*2 { + label = "msdos" + } + return ps.makeLabel(label) +} + +func (ps *DiskPartitions) makeLabel(label string) error { + ps.label = label + cmd := fmt.Sprintf("/usr/sbin/parted -s %s -- mklabel %s", ps.dev, ps.label) + // cmd = ['/usr/sbin/sgdisk', '-og', self.dev] + _, err := ps.Run(cmd) + return err +} + +func (ps *DiskPartitions) getNextPartIndex() int { + max := 0 + for _, part := range ps.partitions { + if max < part.index { + max = part.index + } + } + return max + 1 +} + +func (ps *DiskPartitions) getNextPartStart() int { + var start int + if len(ps.partitions) == 0 { + start = ps.MBSectors() // 1MB + } else { + gap := 2 + lastPart := ps.partitions[len(ps.partitions)-1] + start = ((lastPart.end + gap) / ps.MBSectors()) * ps.MBSectors() + if start < lastPart.end+gap { + start += ps.MBSectors() + } + } + return start +} + +func (ps *DiskPartitions) Run(cmd ...string) ([]string, error) { + return ps.tool.Run(cmd...) +} + +func (ps *DiskPartitions) CreatePartition(sizeMB int, fs string, doformat bool, uuid string) error { + if len(ps.partitions) == 0 { + if err := ps.MakeLabel(); err != nil { + return err + } + } + start := ps.getNextPartStart() + var end int + if sizeMB <= 0 { + end = start + int((ps.GPTEndSector()-start)/ps.MBSectors())*ps.MBSectors() - 1 + } else { + end = start + sizeMB*ps.MBSectors() - 1 + } + partIdx := ps.getNextPartIndex() + var cmd string + var diskType string + if ps.label == "msdos" { + if partIdx < 5 { + diskType = "primary" + } else if partIdx < 9 { + diskType = "logical" + } else { + return fmt.Errorf("Too many partitions on a MSDOS disk") + } + cmd = fmt.Sprintf("/usr/sbin/parted -a none -s %s -- unit s mkpart %s", ps.dev, diskType) + if len(fs) != 0 { + cmd = fmt.Sprintf("%s %s", cmd, fileutils.FsFormatToDiskType(fs)) + } + cmd = fmt.Sprintf("%s %d %d", cmd, start, end) + } else { + cmd = fmt.Sprintf("/usr/sbin/sgdisk --set-alignment=1 --new=%d:%d:%d", partIdx, start, end) + if len(fs) != 0 { + cmd = fmt.Sprintf("%s --typecode=%d:%s", cmd, partIdx, fileutils.FsFormatToDiskType(fs)) + } + cmd = fmt.Sprintf("%s %s", cmd, ps.dev) + } + _, err := ps.Run(cmd) + if err != nil { + return err + } + if err := ps.RetrievePartitionInfo(); err != nil { + return fmt.Errorf("Fail to RetrievePartitionInfo: %v", err) + } + if fs != "" && doformat { + err = ps.partitions[len(ps.partitions)-1].Format(fs, uuid) + if err != nil { + return fmt.Errorf("Fail to format partition: %v", err) + } + if err := ps.RetrievePartitionInfo(); err != nil { + return fmt.Errorf("Fail to RetrievePartitionInfo: %v", err) + } + } + return nil +} + +type IPartitionRunner interface { + Run(cmds ...string) ([]string, error) +} + +type PartitionTool struct { + disks []*DiskPartitions + diskTable map[string][]*DiskPartitions + runner IPartitionRunner +} + +func NewPartitionTool(runner IPartitionRunner) *PartitionTool { + return &PartitionTool{ + disks: make([]*DiskPartitions, 0), + diskTable: make(map[string][]*DiskPartitions), + runner: runner, + } +} + +func (tool *PartitionTool) DebugString() string { + ret := []string{} + disksString := func(disks []*DiskPartitions) []string { + for _, disk := range disks { + ret = append(ret, disk.DebugString()) + } + return ret + } + for driver, disks := range tool.diskTable { + s := fmt.Sprintf("%s: %v", driver, disksString(disks)) + ret = append(ret, s) + } + return strings.Join(ret, "\n") +} + +func (tool *PartitionTool) parseLsDisk(lines []string, driver string) { + disks := sysutils.ParseDiskInfo(lines, driver) + if len(disks) == 0 { + return + } + minCnt := int(math.Min(float64(len(disks)), float64(len(tool.diskTable[driver])))) + for i := 0; i < minCnt; i++ { + tool.diskTable[driver][i].SetInfo(disks[i]) + } +} + +func (tool *PartitionTool) FetchDiskConfs(diskConfs []baremetal.DiskConfiguration) *PartitionTool { + for _, d := range diskConfs { + disk := newDiskPartitions(d.Driver, d.Adapter, d.Size, d.Block, tool) + tool.disks = append(tool.disks, disk) + var key string + if d.Driver == baremetal.DISK_DRIVER_LINUX { + key = NONRAID_DRIVER + } else if d.Driver == baremetal.DISK_DRIVER_PCIE { + key = PCIE_DRIVER + } else { + key = RAID_DRVIER + } + if _, ok := tool.diskTable[key]; !ok { + tool.diskTable[key] = make([]*DiskPartitions, 0) + } + tool.diskTable[key] = append(tool.diskTable[key], disk) + } + return tool +} + +func (tool *PartitionTool) IsAllDisksReady() bool { + for _, d := range tool.disks { + if !d.IsReady() { + return false + } + } + return true +} + +func (tool *PartitionTool) RetrieveDiskInfo() error { + for _, driver := range []string{RAID_DRVIER, NONRAID_DRIVER, PCIE_DRIVER} { + cmd := fmt.Sprintf("/lib/mos/lsdisk --%s", driver) + ret, err := tool.Run(cmd) + if err != nil { + return err + } + tool.parseLsDisk(ret, driver) + } + return nil +} + +func (tool *PartitionTool) RetrievePartitionInfo() error { + for _, disk := range tool.disks { + if err := disk.RetrievePartitionInfo(); err != nil { + return err + } + } + return nil +} + +func (tool *PartitionTool) ResizePartition(diskIdx int, sizeMB int) error { + if diskIdx >= 0 && diskIdx < len(tool.disks) { + return tool.disks[diskIdx].ResizePartition(sizeMB) + } + return fmt.Errorf("Invalid disk index: %d", diskIdx) +} + +func (tool *PartitionTool) GetDisks() []*DiskPartitions { + return tool.disks +} + +func (tool *PartitionTool) GetRootDisk() *DiskPartitions { + if len(tool.disks) == 0 { + return nil + } + return tool.disks[0] +} + +func (tool *PartitionTool) GetPCIEDisks() []*DiskPartitions { + disks := make([]*DiskPartitions, 0) + for _, disk := range tool.disks { + if disk.driver == PCIE_DRIVER { + disks = append(disks, disk) + } + } + return disks +} + +func (tool *PartitionTool) CreatePartition(diskIdx int, sizeMB int, fs string, doformat bool, driver string, uuid string) error { + disks := tool.disks + if driver == PCIE_DRIVER { + disks = tool.GetPCIEDisks() + } + if diskIdx < 0 || diskIdx >= len(disks) { + for _, disk := range disks { + if disk.IsSpaceAvailable(sizeMB) { + return disk.CreatePartition(sizeMB, fs, doformat, uuid) + } + } + } else { + disk := disks[diskIdx] + if disk.IsSpaceAvailable(sizeMB) { + return disk.CreatePartition(sizeMB, fs, doformat, uuid) + } + } + return nil +} + +func (tool *PartitionTool) GetPartitions() []*Partition { + parts := make([]*Partition, 0) + for _, d := range tool.disks { + parts = append(parts, d.partitions...) + } + return parts +} + +func (tool *PartitionTool) Run(cmds ...string) ([]string, error) { + return tool.runner.Run(cmds...) +} + +type SSHPartitionTool struct { + *PartitionTool + term *ssh.Client +} + +func NewSSHPartitionTool(term *ssh.Client) *SSHPartitionTool { + tool := &SSHPartitionTool{ + term: term, + } + tool.PartitionTool = NewPartitionTool(tool) + return tool +} + +func (tool *SSHPartitionTool) Run(cmds ...string) ([]string, error) { + return tool.term.Run(cmds...) +} diff --git a/pkg/baremetal/utils/disktool/disktool_test.go b/pkg/baremetal/utils/disktool/disktool_test.go new file mode 100644 index 0000000000..3d7a2fcc76 --- /dev/null +++ b/pkg/baremetal/utils/disktool/disktool_test.go @@ -0,0 +1,50 @@ +package disktool + +import ( + "testing" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/util/ssh" + "yunion.io/x/pkg/util/stringutils" +) + +var ( + term *ssh.Client +) + +func init() { + var err error + term, err = ssh.NewClient("192.168.0.254", 22, "root", "rMw2qrm6Lb5NVpe0", "") + if err != nil { + log.Fatalf("Failed to init ssh client: %v", err) + } +} + +func TestSSHCreate(t *testing.T) { + tool := NewSSHPartitionTool(term) + err := tool.FetchDiskConfs([]baremetal.DiskConfiguration{ + { + Adapter: 0, + Driver: baremetal.DISK_DRIVER_LINUX, + }, + }).RetrieveDiskInfo() + if err != nil { + t.Errorf("Failed to RetrieveDiskInfo: %v", err) + } + err = tool.RetrievePartitionInfo() + if err != nil { + t.Errorf("Failed to RetrievePartitionInfo: %v", err) + } + log.Infof("%s", tool.DebugString()) + + uuid := stringutils.UUID4 + tool.CreatePartition(-1, 32, "swap", true, baremetal.DISK_DRIVER_LINUX, uuid()) + tool.CreatePartition(-1, 1024, "ext4", true, baremetal.DISK_DRIVER_LINUX, uuid()) + err = tool.CreatePartition(-1, -1, "xfs", true, baremetal.DISK_DRIVER_LINUX, uuid()) + //err = tool.ResizePartition(0, 110*1024) + if err != nil { + t.Errorf("Failed to resize fs: %v", err) + } +} diff --git a/pkg/baremetal/utils/ipmitool/ipmitool.go b/pkg/baremetal/utils/ipmitool/ipmitool.go index 1b74c4c33f..615de5c280 100644 --- a/pkg/baremetal/utils/ipmitool/ipmitool.go +++ b/pkg/baremetal/utils/ipmitool/ipmitool.go @@ -16,6 +16,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/util/procutils" "yunion.io/x/onecloud/pkg/util/ssh" + stage_stringutils "yunion.io/x/onecloud/pkg/util/stringutils" "yunion.io/x/onecloud/pkg/util/sysutils" ) @@ -294,10 +295,13 @@ func SetLanUserPasswd(exector IPMIExecutor, channel int, user string, password s if err != nil { return err } + password, err = stage_stringutils.EscapeEchoString(password) + if err != nil { + return fmt.Errorf("EscapeEchoString for password: %s, error: %v", password, 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), } diff --git a/pkg/cloudcommon/dhcp/conn.go b/pkg/cloudcommon/dhcp/conn.go index 01d5070e2f..e0ad5c37ee 100644 --- a/pkg/cloudcommon/dhcp/conn.go +++ b/pkg/cloudcommon/dhcp/conn.go @@ -142,10 +142,10 @@ func (c *Conn) RecvDHCP() (*Packet, *net.Interface, error) { if err != nil { return nil, nil, err } - if c.ifIndex != 0 && ifidx != c.ifIndex { + /*if c.ifIndex != 0 && ifidx != c.ifIndex { log.Errorf("======= ifIndex continue, c.ifIndex: %d, ifidx: %d", c.ifIndex, ifidx) continue - } + }*/ pkt, err := Unmarshal(b) if err != nil { log.Errorf("======= pkt Unmarshal error: %v", err) diff --git a/pkg/cloudcommon/dhcp/helpers.go b/pkg/cloudcommon/dhcp/helpers.go index d11a85cb43..d0eef838eb 100644 --- a/pkg/cloudcommon/dhcp/helpers.go +++ b/pkg/cloudcommon/dhcp/helpers.go @@ -70,8 +70,8 @@ func GetClasslessRoutePack(route []string) []byte { gwaddr := net.ParseIP(gw) res := []byte{byte(masklen)} - res = append(res, netaddr...) - return append(res, gwaddr...) + res = append(res, []byte(netaddr.To4())...) + return append(res, []byte(gwaddr.To4())...) } func MakeReplyPacket(pkt *Packet, conf *ResponseConfig) (*Packet, error) { @@ -153,9 +153,11 @@ func makeDHCPReplyPacket(pkt *Packet, conf *ResponseConfig, msgType MessageType) if strings.HasPrefix(strings.ToLower(conf.OsName), "win") { optCode = OptClasslessRouteWin } + routeBytes := []byte{} for _, route := range conf.Routes { - resp.Options.AddOption(optCode, GetClasslessRoutePack(route)) + routeBytes = append(routeBytes, GetClasslessRoutePack(route)...) } + resp.Options.AddOption(optCode, routeBytes) } return resp } diff --git a/pkg/cloudcommon/dhcp/server.go b/pkg/cloudcommon/dhcp/server.go index 1ccf00e5ca..8b1b429798 100644 --- a/pkg/cloudcommon/dhcp/server.go +++ b/pkg/cloudcommon/dhcp/server.go @@ -3,6 +3,7 @@ package dhcp import ( "fmt" "net" + "runtime/debug" "yunion.io/x/log" ) @@ -52,6 +53,13 @@ func (s *DHCPServer) serveDHCP(handler DHCPHandler) error { } go func() { + defer func() { + if r := recover(); r != nil { + log.Errorf("Serve panic error: %v", r) + debug.PrintStack() + } + }() + resp, err := handler.ServeDHCP(pkt, intf) if err != nil { log.Warningf("[DHCP] handler serve error: %v", err) diff --git a/pkg/cloudcommon/types/nic.go b/pkg/cloudcommon/types/nic.go index 28b222817c..2818269e7e 100644 --- a/pkg/cloudcommon/types/nic.go +++ b/pkg/cloudcommon/types/nic.go @@ -15,25 +15,22 @@ var ( NIC_TYPES = []string{NIC_TYPE_IPMI, NIC_TYPE_ADMIN} ) -type NicInfo struct { - Nics []Nic `json:"nic_info"` -} - type Nic struct { - Type string `json:"nic_type"` - Domain string `json:"domain"` - Wire string `json:"wire"` - IpAddr string `json:"ip_addr"` - WireId string `json:"wire_id"` - NetId string `json:"net_id"` - Rate int64 `json:"rate"` - Mtu int64 `json:"mtu"` - Mac string `json:"mac"` - Dns string `json:"dns"` - MaskLen int8 `json:"masklen"` - Net string `json:"net"` - Gateway string `json:"gateway"` - LinkUp bool `json:"link_up"` + Type string `json:"nic_type"` + Domain string `json:"domain"` + Wire string `json:"wire"` + IpAddr string `json:"ip_addr"` + WireId string `json:"wire_id"` + NetId string `json:"net_id"` + Rate int64 `json:"rate"` + Mtu int64 `json:"mtu"` + Mac string `json:"mac"` + Dns string `json:"dns"` + MaskLen int8 `json:"masklen"` + Net string `json:"net"` + Gateway string `json:"gateway"` + LinkUp bool `json:"link_up"` + Routes []Route `json:"routes,omitempty"` } func (n Nic) GetNetMask() string { @@ -56,6 +53,7 @@ type ServerNic struct { Masklen int `json:"masklen"` Virtual bool `json:"virtual"` Manual bool `json:"manual"` + WireId string `json:"wire_id"` NetId string `json:"net_id"` Mac string `json:"mac"` BandWidth int `json:"bw"` @@ -65,4 +63,33 @@ type ServerNic struct { Gateway string `json:"gateway"` Ifname string `json:"ifname"` Routes []Route `json:"routes,omitempty"` + NicType string `json:"nic_type,omitempty"` + LinkUp bool `json:"link_up,omitempty"` + Mtu int64 `json:"mtu,omitempty"` +} + +func (n ServerNic) GetNetMask() string { + return netutils.Masklen2Mask(int8(n.Masklen)).String() +} + +func (n ServerNic) GetMac() net.HardwareAddr { + return getMac(n.Mac) +} + +func (n ServerNic) ToNic() Nic { + return Nic{ + Type: n.NicType, + Domain: n.Domain, + IpAddr: n.Ip, + WireId: n.WireId, + NetId: n.NetId, + Mac: n.Mac, + Dns: n.Dns, + MaskLen: int8(n.Masklen), + Net: n.Net, + Gateway: n.Gateway, + Routes: n.Routes, + LinkUp: n.LinkUp, + Mtu: n.Mtu, + } } diff --git a/pkg/compute/baremetal/diskconfig.go b/pkg/compute/baremetal/diskconfig.go index 6215b6ef37..125250d7e9 100644 --- a/pkg/compute/baremetal/diskconfig.go +++ b/pkg/compute/baremetal/diskconfig.go @@ -3,6 +3,7 @@ package baremetal import ( "encoding/json" "fmt" + "reflect" "regexp" "strconv" "strings" @@ -416,6 +417,33 @@ func ExpandNoneConf(layouts []Layout) (ret []Layout) { return ret } +func GetLayoutRaidConfig(layouts []Layout) []*BaremetalDiskConfig { + var disk []*BaremetalStorage + ret := make([]*BaremetalDiskConfig, 0) + for _, layout := range layouts { + if layout.Conf.Conf == DISK_CONF_NONE && + sets.NewString(DISK_DRIVER_LINUX, DISK_DRIVER_PCIE).Has(layout.Disks[0].Driver) { + continue + } + if !reflect.DeepEqual(disk, layout.Disks) { + ret = append(ret, layout.Conf) + disk = layout.Disks + lastConf := ret[len(ret)-1] + /* + if 'size' in ret[-1]: + ret[-1]['size'] = [ret[-1]['size']] + */ + if lastConf.Size == nil { + lastConf.Size = make([]int64, 0) + } + } else { + lastConf := ret[len(ret)-1] + lastConf.Size = append(lastConf.Size, layout.Conf.Size...) + } + } + return ret +} + func CalculateLayout(confs []*BaremetalDiskConfig, storages []*BaremetalStorage) (layouts []Layout, err error) { var confIdx = 0 for len(storages) > 0 { @@ -608,3 +636,62 @@ func GetDiskSpecV2(storages []*BaremetalStorage) DiskDriverSpecs { } return spec } + +type DiskConfiguration struct { + Driver string + Adapter int + Block int + Size int64 +} + +func GetDiskConfigurations(layouts []Layout) []DiskConfiguration { + disks := make([]DiskConfiguration, 0) + for _, rr := range layouts { + driver := rr.Disks[0].Driver + adapter := rr.Disks[0].Adapter + block := rr.Disks[0].GetBlock() + raidConf := rr.Conf.Conf + if raidConf == DISK_CONF_NONE { + for _, d := range rr.Disks { + disks = append(disks, DiskConfiguration{Driver: driver, Adapter: adapter, Block: block, Size: d.Size}) + } + } else { + if len(rr.Conf.Size) != 0 { + for _, sz := range rr.Conf.Size { + disks = append(disks, DiskConfiguration{Driver: driver, Adapter: adapter, Block: block, Size: sz}) + } + } else { + disks = append(disks, DiskConfiguration{Driver: driver, Adapter: adapter, Block: block, Size: rr.Size}) + } + } + } + return disks +} + +type DriverAdapterDiskConfig struct { + Driver string + Adapter int + Configs []*BaremetalDiskConfig +} + +func GroupLayoutResultsByDriverAdapter(layouts []Layout) []*DriverAdapterDiskConfig { + ret := make([]*DriverAdapterDiskConfig, 0) + tbl := make(map[string]*DriverAdapterDiskConfig) + for _, layout := range layouts { + driver := layout.Disks[0].Driver + adapter := layout.Disks[0].Adapter + key := fmt.Sprintf("%s.%d", driver, adapter) + if item, ok := tbl[key]; ok { + item.Configs = append(item.Configs, layout.Conf) + } else { + item := &DriverAdapterDiskConfig{ + Driver: driver, + Adapter: adapter, + Configs: []*BaremetalDiskConfig{layout.Conf}, + } + ret = append(ret, item) + tbl[key] = item + } + } + return ret +} diff --git a/pkg/compute/baremetal/types.go b/pkg/compute/baremetal/types.go index 1fd1126e00..9e3eff83fd 100644 --- a/pkg/compute/baremetal/types.go +++ b/pkg/compute/baremetal/types.go @@ -91,6 +91,13 @@ type BaremetalStorage struct { Index int64 `json:"index,omitempty"` } +func (s BaremetalStorage) GetBlock() int { + if s.Block <= 0 { + return 512 + } + return s.Block +} + type BaremetalDiskConfig struct { // disk type Type string `json:"type"` diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index b009681319..7404cfee54 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -3268,6 +3268,7 @@ func (self *SGuest) saveOsType(osType string) error { } func (self *SGuest) SaveDeployInfo(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject) { + log.Errorf("------SaveDeployInfo: %s", data.PrettyString()) info := make(map[string]interface{}) if data.Contains("os") { osName, _ := data.GetString("os") diff --git a/pkg/compute/tasks/baremetal_server_rebuild_root_task.go b/pkg/compute/tasks/baremetal_server_rebuild_root_task.go new file mode 100644 index 0000000000..16ed55ddb8 --- /dev/null +++ b/pkg/compute/tasks/baremetal_server_rebuild_root_task.go @@ -0,0 +1,83 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type BaremetalServerRebuildRootTask struct { + SGuestBaseTask +} + +func init() { + taskman.RegisterTask(BaremetalServerRebuildRootTask{}) +} + +func (self *BaremetalServerRebuildRootTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + if jsonutils.QueryBoolean(self.Params, "need_stop", false) { + self.SetStage("OnStopServerComplete", nil) + guest.StartGuestStopTask(ctx, self.UserCred, false, self.GetTaskId()) + return + } + self.StartRebuildRootDisk(ctx, guest) +} + +func (self *BaremetalServerRebuildRootTask) OnStopServerComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.StartRebuildRootDisk(ctx, guest) +} + +func (self *BaremetalServerRebuildRootTask) StartRebuildRootDisk(ctx context.Context, guest *models.SGuest) { + if guest.Status != models.VM_ADMIN { + guest.SetStatus(self.UserCred, models.VM_REBUILD_ROOT, "") + } + imageId, _ := self.Params.GetString("image_id") + db.OpsLog.LogEvent(guest, db.ACT_REBUILDING_ROOT, imageId, self.UserCred) + gds := guest.CategorizeDisks() + oldStatus := gds.Root.Status + _, err := gds.Root.GetModelManager().TableSpec().Update(gds.Root, func() error { + gds.Root.TemplateId = imageId + gds.Root.Status = models.DISK_REBUILD + return nil + }) + if err != nil { + self.SetStageFailed(ctx, err.Error()) + logclient.AddActionLog(guest, logclient.ACT_VM_REBUILD, err, self.UserCred, false) + return + } else { + db.OpsLog.LogEvent(gds.Root, db.ACT_UPDATE_STATUS, + fmt.Sprintf("%s=>%s", oldStatus, models.DISK_REBUILD), self.UserCred) + } + self.SetStage("OnRebuildRootDiskComplete", nil) + + // clear logininfo + loginParams := make(map[string]interface{}) + loginParams["login_account"] = "none" + loginParams["login_key"] = "none" + loginParams["login_key_timestamp"] = "none" + guest.SetAllMetadata(ctx, loginParams, self.UserCred) + guest.StartGuestDeployTask(ctx, self.UserCred, self.Params, "rebuild", self.GetTaskId()) +} + +func (self *BaremetalServerRebuildRootTask) OnRebuildRootDiskComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(guest, db.ACT_REBUILD_ROOT, "", self.UserCred) + self.SetStage("OnSyncStatusComplete", nil) + guest.StartSyncstatus(ctx, self.UserCred, self.GetTaskId()) +} + +func (self *BaremetalServerRebuildRootTask) OnRebuildRootDiskCompleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(guest, db.ACT_REBUILD_ROOT_FAIL, data, self.UserCred) + if guest.Status != models.VM_ADMIN { + guest.SetStatus(self.UserCred, models.VM_REBUILD_ROOT_FAIL, "") + } +} + +func (self *BaremetalServerRebuildRootTask) OnSyncStatusComplete(ctx context.Context, _ *models.SGuest, _ jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/hostman/guestfs/android.go b/pkg/hostman/guestfs/android.go index 5e4e06ba56..bb3f830f66 100644 --- a/pkg/hostman/guestfs/android.go +++ b/pkg/hostman/guestfs/android.go @@ -1,13 +1,9 @@ package guestfs type SAndroidRootFs struct { - *SGuestRootFsDriver + *sGuestRootFsDriver } -func NewAndroidRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SAndroidRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} -} - -func init() { - rootfsDrivers = append(rootfsDrivers, NewAndroidRootFs) -} +//func NewAndroidRootFs(part IDiskPartition) IRootFsDriver { +//return &SAndroidRootFs{sGuestRootFsDriver: newGuestRootFsDriver(part)} +//} diff --git a/pkg/hostman/guestfs/core.go b/pkg/hostman/guestfs/core.go index 7b6538f9a8..9649f2d7a4 100644 --- a/pkg/hostman/guestfs/core.go +++ b/pkg/hostman/guestfs/core.go @@ -2,13 +2,17 @@ package guestfs import ( "fmt" + "math/rand" + "path" "path/filepath" + "strings" "syscall" "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" "yunion.io/x/pkg/util/netutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" ) type SDeployInfo struct { @@ -19,11 +23,23 @@ type SDeployInfo struct { enableTty bool } -type newRootFsDriverFunc func(part *SKVMGuestDiskPartition) IRootFsDriver +func NewDeployInfo( + publicKey *sshkeys.SSHKeys, + deploys []jsonutils.JSONObject, + password string, + isInit bool, + enableTty bool, +) *SDeployInfo { + return &SDeployInfo{ + publicKey: publicKey, + deploys: deploys, + password: password, + isInit: isInit, + enableTty: enableTty, + } +} -var rootfsDrivers = make([]newRootFsDriverFunc, 0) - -func DetectRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { +func DetectRootFs(part IDiskPartition) IRootFsDriver { for _, newDriverFunc := range rootfsDrivers { d := newDriverFunc(part) if testRootfs(d) { @@ -50,73 +66,73 @@ func testRootfs(d IRootFsDriver) bool { return true } -type IRootFsDriver interface { - GetPartition() *SKVMGuestDiskPartition - String() string - - IsFsCaseInsensitive() bool - RootSignatures() []string - RootExcludeSignatures() []string - GetReleaseInfo() []string - GetOs() string - DeployFiles([]jsonutils.JSONObject) error - DeployHostname(hn, domain string) error - DeployHost(hn, domain string, ips []string) error - DeployNetworkingScripts([]jsonutils.JSONObject) error - DeployStandbyNetworkingScripts(nics, nicsStandby []jsonutils.JSONObject) error - DeployUdevSubsystemScripts() error - DeployFstabScripts([]jsonutils.JSONObject) error - GetLoginAccount() string - DeployPublicKey(string, *sshkeys.SSHKeys) error - ChangeUserPasswd(account, gid, publicKey, password string) string - DeployYunionroot(*sshkeys.SSHKeys) error - EnableSerialConsole(*jsonutils.JSONDict) error - DisableSerialConsole() error - CommitChanges() error - - DeployGuestFs(IRootFsDriver, *jsonutils.JSONDict, *SDeployInfo) (jsonutils.JSONObject, error) - - PrepareFsForTemplate() error +type sGuestRootFsDriver struct { + rootFs IDiskPartition } -type SGuestRootFsDriver struct { - rootFs *SKVMGuestDiskPartition +func newGuestRootFsDriver(rootFs IDiskPartition) *sGuestRootFsDriver { + return &sGuestRootFsDriver{ + rootFs: rootFs, + } } -func NewGuestRootFsDriver(rootFs *SKVMGuestDiskPartition) IRootFsDriver { - return &SGuestRootFsDriver{rootFs} -} - -func (d *SGuestRootFsDriver) GetPartition() *SKVMGuestDiskPartition { +func (d *sGuestRootFsDriver) GetPartition() IDiskPartition { return d.rootFs } -func (d *SGuestRootFsDriver) String() string { - return "" +func (d *sGuestRootFsDriver) RootExcludeSignatures() []string { + return []string{} } -func (d *SGuestRootFsDriver) IsFsCaseInsensitive() bool { +func (d *sGuestRootFsDriver) IsFsCaseInsensitive() bool { return false } -func (d *SGuestRootFsDriver) RootSignatures() []string { +func (d *sGuestRootFsDriver) DeployYunionroot(rootfs IDiskPartition, pubkeys *sshkeys.SSHKeys) error { return nil } -func (d *SGuestRootFsDriver) RootExcludeSignatures() []string { +func (d *sGuestRootFsDriver) DeployUdevSubsystemScripts(rootfs IDiskPartition) error { return nil } -func (d *SGuestRootFsDriver) GetReleaseInfo() []string { +func (d *sGuestRootFsDriver) DeployStandbyNetworkingScripts(part IDiskPartition, nics, nicsStandby []jsonutils.JSONObject) error { return nil } -func (d *SGuestRootFsDriver) GetOs() string { - return "" +func (d *sGuestRootFsDriver) DeployFstabScripts(_ IDiskPartition, _ []jsonutils.JSONObject) error { + return nil } -func (d *SGuestRootFsDriver) DeployFiles(deploys []jsonutils.JSONObject) error { - caseInsensitive := d.IsFsCaseInsensitive() +func (d *sGuestRootFsDriver) EnableSerialConsole(rootfs IDiskPartition, sysInfo *jsonutils.JSONDict) error { + return nil +} + +func (d *sGuestRootFsDriver) DisableSerialConsole(rootfs IDiskPartition) error { + return nil +} + +func (d *sGuestRootFsDriver) CommitChanges(rootfs IDiskPartition) error { + return nil +} + +type SReleaseInfo struct { + Distro string + Version string + Arch string + Language string +} + +func newReleaseInfo(distro, version, arch string) *SReleaseInfo { + return &SReleaseInfo{ + Distro: distro, + Version: version, + Arch: arch, + } +} + +func DeployFiles(rootfs IRootFsDriver, deploys []jsonutils.JSONObject) error { + caseInsensitive := rootfs.IsFsCaseInsensitive() for _, deploy := range deploys { var modAppend = false if action, _ := deploy.GetString("action"); action == "append" { @@ -127,15 +143,15 @@ func (d *SGuestRootFsDriver) DeployFiles(deploys []jsonutils.JSONObject) error { return err } dirname := filepath.Dir(sPath) - if !d.rootFs.Exists(sPath, caseInsensitive) { + if !rootfs.GetPartition().Exists(sPath, caseInsensitive) { modeRWXOwner := syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR - err := d.rootFs.Mkdir(dirname, modeRWXOwner, caseInsensitive) + err := rootfs.GetPartition().Mkdir(dirname, modeRWXOwner, caseInsensitive) if err != nil { return err } } if content, err := deploy.GetString("content"); err != nil { - err := d.rootFs.FilePutContents(sPath, content, modAppend, caseInsensitive) + err := rootfs.GetPartition().FilePutContents(sPath, content, modAppend, caseInsensitive) if err != nil { return err } @@ -144,162 +160,197 @@ func (d *SGuestRootFsDriver) DeployFiles(deploys []jsonutils.JSONObject) error { return nil } -func (d *SGuestRootFsDriver) PrepareFsForTemplate() error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) DeployHostname(hn, domain string) error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) DeployHost(hn, domain string, ips []string) error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) DeployNetworkingScripts([]jsonutils.JSONObject) error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) DeployStandbyNetworkingScripts(nics, nicsStandby []jsonutils.JSONObject) error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) DeployUdevSubsystemScripts() error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) DeployFstabScripts([]jsonutils.JSONObject) error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) GetLoginAccount() string { - return "" -} - -func (d *SGuestRootFsDriver) DeployPublicKey(string, *sshkeys.SSHKeys) error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) ChangeUserPasswd(account, gid, publicKey, password string) string { - return "" -} - -func (d *SGuestRootFsDriver) DeployYunionroot(*sshkeys.SSHKeys) error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) EnableSerialConsole(*jsonutils.JSONDict) error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) DisableSerialConsole() error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) CommitChanges() error { - return fmt.Errorf("Not Implemented") -} - -func (d *SGuestRootFsDriver) DeployGuestFs(rootfs IRootFsDriver, guestDesc *jsonutils.JSONDict, - deployInfo *SDeployInfo) (jsonutils.JSONObject, error) { +func DeployGuestFs( + rootfs IRootFsDriver, + guestDesc *jsonutils.JSONDict, + deployInfo *SDeployInfo, +) (jsonutils.JSONObject, error) { var ret = jsonutils.NewDict() var ips = make([]string, 0) - var releaseInfo = rootfs.GetReleaseInfo() + var err error + hn, _ := guestDesc.GetString("name") domain, _ := guestDesc.GetString("domain") gid, _ := guestDesc.GetString("uuid") - nics, _ := guestDesc.GetArray("nisc") + nics, _ := guestDesc.GetArray("nics") + + partition := rootfs.GetPartition() + releaseInfo := rootfs.GetReleaseInfo(partition) - var err error for _, n := range nics { ip, _ := n.GetString("ip") var addr netutils.IPV4Addr if addr, err = netutils.NewIPV4Addr(ip); err != nil { - log.Errorln(err) - return nil, err + return nil, fmt.Errorf("Fail to get ip addr from %s: %v", n.String(), err) } if netutils.IsPrivate(addr) { ips = append(ips, ip) } } if releaseInfo != nil { - ret.Set("distro", jsonutils.NewString(releaseInfo[0])) - if len(releaseInfo) > 1 && len(releaseInfo[1]) > 0 { - ret.Set("version", jsonutils.NewString(releaseInfo[1])) + ret.Set("distro", jsonutils.NewString(releaseInfo.Distro)) + if len(releaseInfo.Version) > 0 { + ret.Set("version", jsonutils.NewString(releaseInfo.Version)) } - if len(releaseInfo) > 2 && len(releaseInfo[2]) > 0 { - ret.Set("arch", jsonutils.NewString(releaseInfo[2])) + if len(releaseInfo.Arch) > 0 { + ret.Set("arch", jsonutils.NewString(releaseInfo.Arch)) } - if len(releaseInfo) > 3 && len(releaseInfo[3]) > 0 { - ret.Set("language", jsonutils.NewString(releaseInfo[3])) + if len(releaseInfo.Language) > 0 { + ret.Set("language", jsonutils.NewString(releaseInfo.Language)) } } ret.Set("os", jsonutils.NewString(rootfs.GetOs())) - if d.rootFs.GetReadonly() { + if !IsPartitionReadonly(partition) { if len(deployInfo.deploys) > 0 { - if err = rootfs.DeployFiles(deployInfo.deploys); err != nil { - log.Errorln(err) - return nil, err + if err = DeployFiles(rootfs, deployInfo.deploys); err != nil { + return nil, fmt.Errorf("DeployFiles: %v", err) } } - if err = rootfs.DeployHostname(hn, domain); err != nil { - log.Errorln(err) - return nil, err + if err = rootfs.DeployHostname(partition, hn, domain); err != nil { + return nil, fmt.Errorf("DeployHostname: %v", err) } - if err = rootfs.DeployHost(hn, domain, ips); err != nil { - log.Errorln(err) - return nil, err + if err = rootfs.DeployHosts(partition, hn, domain, ips); err != nil { + return nil, fmt.Errorf("DeployHosts: %v", err) } - if err = rootfs.DeployNetworkingScripts(nics); err != nil { - log.Errorln(err) - return nil, err + if err = rootfs.DeployNetworkingScripts(partition, nics); err != nil { + return nil, fmt.Errorf("DeployNetworkingScripts: %v", err) } if nicsStandby, e := guestDesc.GetArray("nics_standby"); e == nil { - rootfs.DeployStandbyNetworkingScripts(nics, nicsStandby) + if err = rootfs.DeployStandbyNetworkingScripts(partition, nics, nicsStandby); err != nil { + return nil, fmt.Errorf("DeployStandbyNetworkingScripts: %v", err) + } } - if err = rootfs.DeployUdevSubsystemScripts(); err != nil { - log.Errorln(err) - return nil, err + if err = rootfs.DeployUdevSubsystemScripts(partition); err != nil { + return nil, fmt.Errorf("DeployUdevSubsystemScripts: %v", err) } if deployInfo.isInit { disks, _ := guestDesc.GetArray("disks") - if err = rootfs.DeployFstabScripts(disks); err != nil { - log.Errorln(err) - return nil, err + if err = rootfs.DeployFstabScripts(partition, disks); err != nil { + return nil, fmt.Errorf("DeployFstabScripts: %v", err) } } if len(deployInfo.password) > 0 { - if account := rootfs.GetLoginAccount(); len(account) > 0 { - if err = rootfs.DeployPublicKey(account, deployInfo.publicKey); err != nil { - log.Errorln(err) - return nil, err + if account := rootfs.GetLoginAccount(partition); len(account) > 0 { + ret.Set("account", jsonutils.NewString(account)) + if err = rootfs.DeployPublicKey(partition, account, deployInfo.publicKey); err != nil { + return nil, fmt.Errorf("DeployPublicKey: %v", err) + } + var secret string + if secret, err = rootfs.ChangeUserPasswd(partition, account, gid, + deployInfo.publicKey.PublicKey, deployInfo.password); err != nil { + return nil, fmt.Errorf("ChangeUserPasswd: %v", err) } - secret := rootfs.ChangeUserPasswd(account, gid, - deployInfo.publicKey.PublicKey, deployInfo.password) if len(secret) > 0 { ret.Set("key", jsonutils.NewString(secret)) } } } - if err = rootfs.DeployYunionroot(deployInfo.publicKey); err != nil { - log.Errorln(err) - return nil, err + if err = rootfs.DeployYunionroot(partition, deployInfo.publicKey); err != nil { + return nil, fmt.Errorf("DeployYunionroot: %v", err) } - if deployInfo.enableTty { - if err = rootfs.EnableSerialConsole(ret); err != nil { - log.Errorln(err) - return nil, err + if partition.SupportSerialPorts() { + if deployInfo.enableTty { + if err = rootfs.EnableSerialConsole(partition, ret); err != nil { + return nil, fmt.Errorf("EnableSerialConsole: %v", err) + } + } else { + if err = rootfs.DisableSerialConsole(partition); err != nil { + return nil, fmt.Errorf("DisableSerialConsole: %v", err) + } } - } else { - if err = rootfs.DisableSerialConsole(); err != nil { - log.Errorln(err) - return nil, err + if err = rootfs.CommitChanges(partition); err != nil { + return nil, fmt.Errorf("CommitChanges: %v", err) } } - if err = rootfs.CommitChanges(); err != nil { - log.Errorln(err) - return nil, err - } } return ret, nil } + +func IsPartitionReadonly(rootfs IDiskPartition) bool { + log.Infof("Test if read-only fs ...") + var filename = fmt.Sprint("./%f", rand.Float32()) + if err := rootfs.FilePutContents(filename, fmt.Sprint("%f", rand.Float32()), false, false); err == nil { + rootfs.Remove(filename, false) + return false + } else { + log.Errorf("File system is readonly: %s", err) + return true + } +} + +func DeployAuthorizedKeys(rootFs IDiskPartition, usrDir string, pubkeys *sshkeys.SSHKeys, replace bool) error { + usrStat := rootFs.Stat(usrDir, false) + if usrStat != nil { + sshDir := path.Join(usrDir, ".ssh") + authFile := path.Join(sshDir, "authorized_keys") + modeRwxOwner := syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR + modeRwOwner := syscall.S_IRUSR | syscall.S_IWUSR + fStat := usrStat.Sys().(*syscall.Stat_t) + if !rootFs.Exists(sshDir, false) { + err := rootFs.Mkdir(sshDir, modeRwxOwner, false) + if err != nil { + return err + } + err = rootFs.Chown(sshDir, int(fStat.Uid), int(fStat.Gid), false) + if err != nil { + return err + } + } + var oldKeys = "" + if !replace { + bOldKeys, _ := rootFs.FileGetContents(authFile, false) + oldKeys = string(bOldKeys) + } + newKeys := MergeAuthorizedKeys(oldKeys, pubkeys) + if err := rootFs.FilePutContents(authFile, newKeys, false, false); err != nil { + return fmt.Errorf("Put keys to %s: %v", authFile, err) + } + log.Infof("after merge keys=====%s, put to: %s", newKeys, authFile) + if err := rootFs.Chown(authFile, int(fStat.Uid), int(fStat.Gid), false); err != nil { + return fmt.Errorf("Chown %s to uid: %d, gid: %d: %v", authFile, fStat.Uid, fStat.Gid, err) + } + if err := rootFs.Chmod(authFile, uint32(modeRwOwner), false); err != nil { + return fmt.Errorf("Chmod %s to %d error: %v", authFile, uint32(modeRwOwner), err) + } + } + return nil +} + +func MergeAuthorizedKeys(oldKeys string, pubkeys *sshkeys.SSHKeys) string { + var allkeys = make(map[string]string) + if len(oldKeys) > 0 { + for _, line := range strings.Split(oldKeys, "\n") { + line = strings.TrimSpace(line) + dat := strings.Split(line, " ") + if len(dat) > 1 { + if _, ok := allkeys[dat[1]]; !ok { + allkeys[dat[1]] = line + } + } + } + } + if len(pubkeys.DeletePublicKey) > 0 { + dat := strings.Split(pubkeys.DeletePublicKey, " ") + if len(dat) > 1 { + if _, ok := allkeys[dat[1]]; ok { + delete(allkeys, dat[1]) + } + } + } + for _, k := range []string{pubkeys.PublicKey, pubkeys.AdminPublicKey, pubkeys.ProjectPublicKey} { + if len(k) > 0 { + k = strings.TrimSpace(k) + dat := strings.Split(k, " ") + if len(dat) > 1 { + if _, ok := allkeys[dat[1]]; !ok { + allkeys[dat[1]] = k + } + } + } + } + var keys = make([]string, len(allkeys)) + for key := range allkeys { + keys = append(keys, key) + } + return strings.Join(keys, "\n") +} diff --git a/pkg/hostman/guestfs/drivers.go b/pkg/hostman/guestfs/drivers.go new file mode 100644 index 0000000000..c86034a535 --- /dev/null +++ b/pkg/hostman/guestfs/drivers.go @@ -0,0 +1,18 @@ +package guestfs + +type newRootFsDriverFunc func(part IDiskPartition) IRootFsDriver + +var rootfsDrivers = make([]newRootFsDriverFunc, 0) + +func init() { + linuxFsDrivers := []newRootFsDriverFunc{ + NewCentosRootFs, NewFedoraRootFs, NewRhelRootFs, + NewDebianRootFs, NewCirrosRootFs, NewCirrosNewRootFs, NewUbuntuRootFs, + //NewGentooRootFs, NewArchLinuxRootFs, NewOpenWrtRootFs, NewCoreOsRootFs, + } + rootfsDrivers = append(rootfsDrivers, linuxFsDrivers...) + //rootfsDrivers = append(rootfsDrivers, NewMacOSRootFs) + //rootfsDrivers = append(rootfsDrivers, NewEsxiRootFs) + //rootfsDrivers = append(rootfsDrivers, NewWindowsRootFs) + //rootfsDrivers = append(rootfsDrivers, NewAndroidRootFs) +} diff --git a/pkg/hostman/guestfs/esxi.go b/pkg/hostman/guestfs/esxi.go index 6bda01754c..eb156321b1 100644 --- a/pkg/hostman/guestfs/esxi.go +++ b/pkg/hostman/guestfs/esxi.go @@ -1,13 +1,9 @@ package guestfs type SEsxiRootFs struct { - *SGuestRootFsDriver + *sGuestRootFsDriver } -func NewEsxiRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SEsxiRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} -} - -func init() { - rootfsDrivers = append(rootfsDrivers, NewEsxiRootFs) -} +//func NewEsxiRootFs(part IDiskPartition) IRootFsDriver { +//return &SEsxiRootFs{sGuestRootFsDriver: newGuestRootFsDriver(part)} +//} diff --git a/pkg/hostman/guestfs/interface.go b/pkg/hostman/guestfs/interface.go new file mode 100644 index 0000000000..250096b1b6 --- /dev/null +++ b/pkg/hostman/guestfs/interface.go @@ -0,0 +1,60 @@ +package guestfs + +import ( + "os" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" +) + +type IDiskPartition interface { + FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) + FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error + Exists(sPath string, caseInsensitive bool) bool + Chown(sPath string, uid, gid int, caseInsensitive bool) error + Chmod(sPath string, mode uint32, caseInsensitive bool) error + UserAdd(user string, caseInsensitive bool) error + Stat(sPath string, caseInsensitive bool) os.FileInfo + + Passwd(account, password string, caseInsensitive bool) error + Mkdir(sPath string, mode int, caseInsensitive bool) error + ListDir(sPath string, caseInsensitive bool) []string + Remove(path string, caseInsensitive bool) + Cleandir(dir string, keepdir, caseInsensitive bool) error + Zerofiles(dir string, caseInsensitive bool) error + SupportSerialPorts() bool +} + +type IRootFsDriver interface { + GetPartition() IDiskPartition + GetName() string + + IsFsCaseInsensitive() bool + RootSignatures() []string + RootExcludeSignatures() []string + GetReleaseInfo(IDiskPartition) *SReleaseInfo + GetOs() string + DeployHostname(part IDiskPartition, hn, domain string) error + DeployHosts(part IDiskPartition, hn, domain string, ips []string) error + DeployNetworkingScripts(IDiskPartition, []jsonutils.JSONObject) error + DeployStandbyNetworkingScripts(part IDiskPartition, nics, nicsStandby []jsonutils.JSONObject) error + DeployUdevSubsystemScripts(IDiskPartition) error + DeployFstabScripts(IDiskPartition, []jsonutils.JSONObject) error + GetLoginAccount(IDiskPartition) string + DeployPublicKey(IDiskPartition, string, *sshkeys.SSHKeys) error + ChangeUserPasswd(part IDiskPartition, account, gid, publicKey, password string) (string, error) + DeployYunionroot(IDiskPartition, *sshkeys.SSHKeys) error + EnableSerialConsole(IDiskPartition, *jsonutils.JSONDict) error + DisableSerialConsole(IDiskPartition) error + CommitChanges(IDiskPartition) error + + PrepareFsForTemplate(IDiskPartition) error +} + +type IDebianRootFsDriver interface { + IRootFsDriver + + DistroName() string + VersionFilePath() string +} diff --git a/pkg/hostman/guestfs/linux.go b/pkg/hostman/guestfs/linux.go index a03e23f01c..d168213f27 100644 --- a/pkg/hostman/guestfs/linux.go +++ b/pkg/hostman/guestfs/linux.go @@ -19,28 +19,28 @@ import ( "yunion.io/x/onecloud/pkg/util/fstabutils" "yunion.io/x/onecloud/pkg/util/netutils2" "yunion.io/x/onecloud/pkg/util/seclib2" + "yunion.io/x/onecloud/pkg/util/sysutils" ) -type SLinuxRootFs struct { - *SGuestRootFsDriver +type sLinuxRootFs struct { + *sGuestRootFsDriver } -func NewLinuxRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SLinuxRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} +func newLinuxRootFs(part IDiskPartition) *sLinuxRootFs { + return &sLinuxRootFs{ + sGuestRootFsDriver: newGuestRootFsDriver(part), + } } -func (l *SLinuxRootFs) String() string { - return "LinuxRootFs" -} - -func (l *SLinuxRootFs) RootSignatures() []string { +func (l *sLinuxRootFs) RootSignatures() []string { return []string{"/bin", "/etc", "/boot", "/lib", "/usr"} } -func (l *SLinuxRootFs) DeployHost(hn, domain string, ips []string) error { +func (l *sLinuxRootFs) DeployHosts(rootFs IDiskPartition, hostname, domain string, ips []string) error { + var etcHosts = "/etc/hosts" var oldHostFile string - if l.rootFs.Exists("/etc/hosts", false) { - oldhf, err := l.rootFs.FileGetContents("/etc/hosts", false) + if rootFs.Exists(etcHosts, false) { + oldhf, err := rootFs.FileGetContents(etcHosts, false) if err != nil { return err } @@ -50,83 +50,87 @@ func (l *SLinuxRootFs) DeployHost(hn, domain string, ips []string) error { hf.Parse(oldHostFile) hf.Add("127.0.0.1", "localhost") for _, ip := range ips { - hf.Add(ip, fmt.Sprintf("%s.%s", hn+domain), hn) + hf.Add(ip, fmt.Sprintf("%s.%s", hostname, domain), hostname) } - return nil + return rootFs.FilePutContents(etcHosts, hf.String(), false, false) } -func (l *SLinuxRootFs) GetLoginAccount() string { +func (l *sLinuxRootFs) GetLoginAccount(rootFs IDiskPartition) string { var selUsr string - if options.HostOptions.LinuxDefaultRootUser && l.rootFs.Exists("/root", false) { + if options.HostOptions.LinuxDefaultRootUser && rootFs.Exists("/root", false) { selUsr = "root" } else { - usrs := l.rootFs.Listdir("/home", false) + usrs := rootFs.ListDir("/home", false) for _, usr := range usrs { if len(selUsr) == 0 || len(selUsr) > len(usr) { selUsr = usr } } - if len(selUsr) > 0 && l.rootFs.Exists("/root", false) { + if len(selUsr) > 0 && rootFs.Exists("/root", false) { selUsr = "root" } } return selUsr } -func (l *SLinuxRootFs) ChangeUserPassswd(account, gid, publicKey, password string) string { +func (l *sLinuxRootFs) ChangeUserPasswd(rootFs IDiskPartition, account, gid, publicKey, password string) (string, error) { var secret string - if err := l.rootFs.Passwd(account, password, false); err != nil { + var err error + err = rootFs.Passwd(account, password, false) + if err == nil { if len(publicKey) > 0 { - secret, _ = seclib2.EncryptBase64(publicKey, password) + secret, err = seclib2.EncryptBase64(publicKey, password) } else { - secret, _ = utils.EncryptAESBase64(gid, password) + secret, err = utils.EncryptAESBase64(gid, password) } } else { - log.Errorf("Change uer passwd error: %s", err) + return "", fmt.Errorf("ChangeUserPasswd error: %v", err) } - return secret + return secret, err } -func (l *SLinuxRootFs) DeployPublicKey(selUsr string, pubkeys *sshkeys.SSHKeys) error { +func (l *sLinuxRootFs) DeployPublicKey(rootFs IDiskPartition, selUsr string, pubkeys *sshkeys.SSHKeys) error { var usrDir string if selUsr == "root" { usrDir = "/root" } else { usrDir = path.Join("/home", selUsr) } - return l.rootFs.DeployAuthorizedKeys(usrDir, pubkeys, false) + return DeployAuthorizedKeys(rootFs, usrDir, pubkeys, false) } -func (l *SLinuxRootFs) DeployYunionroot(pubkeys *sshkeys.SSHKeys) error { - l.DisableSelinux() - l.DisableCloudinit() +func (l *sLinuxRootFs) DeployYunionroot(rootFs IDiskPartition, pubkeys *sshkeys.SSHKeys) error { + l.DisableSelinux(rootFs) + l.DisableCloudinit(rootFs) var yunionroot = "cloudroot" - if err := l.rootFs.UserAdd(yunionroot, false); err != nil { - return err + if err := rootFs.UserAdd(yunionroot, false); err != nil && !strings.Contains(err.Error(), "already exists") { + return fmt.Errorf("UserAdd %s: %v", yunionroot, err) } - err := l.rootFs.DeployAuthorizedKeys(path.Join("/home", yunionroot), pubkeys, false) + err := DeployAuthorizedKeys(rootFs, path.Join("/home", yunionroot), pubkeys, true) if err != nil { - return err + return fmt.Errorf("DeployAuthorizedKeys: %v", err) } - return l.EnableUserSudo(yunionroot) -} - -func (l *SLinuxRootFs) EnableUserSudo(user string) error { - var sudoDir = "/etc/sudoers.d" - var content = fmt.Sprintf("%s ALL=(ALL) NOPASSWD:ALL\n", user) - if l.rootFs.Exists(sudoDir, false) { - filepath := path.Join(sudoDir, fmt.Sprintf("90-%s-users", user)) - err := l.rootFs.FilePutContents(filepath, content, false, false) - if err != nil { - log.Errorln(err) - return err - } - return l.rootFs.Chmod(filepath, syscall.S_IRUSR|syscall.S_IRGRP, false) + if err := l.EnableUserSudo(rootFs, yunionroot); err != nil { + return fmt.Errorf("EnableUserSudo: %v", err) } return nil } -func (l *SLinuxRootFs) DisableSelinux() { +func (l *sLinuxRootFs) EnableUserSudo(rootFs IDiskPartition, user string) error { + var sudoDir = "/etc/sudoers.d" + var content = fmt.Sprintf("%s ALL=(ALL) NOPASSWD:ALL\n", user) + if rootFs.Exists(sudoDir, false) { + filepath := path.Join(sudoDir, fmt.Sprintf("90-%s-users", user)) + err := rootFs.FilePutContents(filepath, content, false, false) + if err != nil { + return fmt.Errorf("Write contents to %s: %v", filepath, err) + } + return rootFs.Chmod(filepath, syscall.S_IRUSR|syscall.S_IRGRP, false) + } + return nil +} + +func (l *sLinuxRootFs) DisableSelinux(rootFs IDiskPartition) { selinuxConfig := "/etc/selinux/config" content := `# This file controls the state of SELinux on the system. # SELINUX= can take one of these three values: @@ -140,21 +144,25 @@ SELINUX=disabled # mls - Multi Level Security protection. SELINUXTYPE=targeted ` - if l.rootFs.Exists(selinuxConfig, false) { - l.rootFs.FilePutContents(selinuxConfig, content, false, false) + if rootFs.Exists(selinuxConfig, false) { + if err := rootFs.FilePutContents(selinuxConfig, content, false, false); err != nil { + log.Errorf("DisableSelinux error: %v", err) + } } } -func (l *SLinuxRootFs) DisableCloudinit() { +func (l *sLinuxRootFs) DisableCloudinit(rootFs IDiskPartition) { cloudDir := "/etc/cloud" cloudDisableFile := "/etc/cloud/cloud-init.disabled" - if l.rootFs.Exists(cloudDir, false) { - l.rootFs.FilePutContents(cloudDisableFile, "", false, false) + if rootFs.Exists(cloudDir, false) { + if err := rootFs.FilePutContents(cloudDisableFile, "", false, false); err != nil { + log.Errorf("DisableCloudinit error: %v", err) + } } } -func (l *SLinuxRootFs) DeployFstabScripts(disks []jsonutils.JSONObject) error { - fstabcont, err := l.rootFs.FileGetContents("/etc/fstab", false) +func (l *sLinuxRootFs) DeployFstabScripts(rootFs IDiskPartition, disks []jsonutils.JSONObject) error { + fstabcont, err := rootFs.FileGetContents("/etc/fstab", false) if err != nil { return err } @@ -193,18 +201,18 @@ func (l *SLinuxRootFs) DeployFstabScripts(disks []jsonutils.JSONObject) error { } } cf := fstab.ToConf() - return l.rootFs.FilePutContents("/etc/fstab", cf, false, false) + return rootFs.FilePutContents("/etc/fstab", cf, false, false) } -func (l *SLinuxRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) error { +func (l *sLinuxRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject) error { udevPath := "/etc/udev/rules.d/" - if l.rootFs.Exists(udevPath, false) { - rules := l.rootFs.Listdir(udevPath, false) + if rootFs.Exists(udevPath, false) { + rules := rootFs.ListDir(udevPath, false) for _, rule := range rules { if strings.Index(rule, "persistent-net.rules") > 0 { - l.rootFs.Remove(path.Join(udevPath, rule), false) + rootFs.Remove(path.Join(udevPath, rule), false) } else if strings.Index(rule, "persistent-cd.rules") > 0 { - if err := l.rootFs.FilePutContents(path.Join(udevPath, rule), "", false, false); err != nil { + if err := rootFs.FilePutContents(path.Join(udevPath, rule), "", false, false); err != nil { return err } } @@ -218,14 +226,14 @@ func (l *SLinuxRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) erro idx, _ := nic.Int("index") nicRules += fmt.Sprintf(`NAME="eth%d"\n`, idx) } - if err := l.rootFs.FilePutContents(path.Join(udevPath, "70-persistent-net.rules"), nicRules, false, false); err != nil { + if err := rootFs.FilePutContents(path.Join(udevPath, "70-persistent-net.rules"), nicRules, false, false); err != nil { return err } var usbRules string usbRules = `SUBSYSTEM=="usb", ATTRS{idVendor}=="1d6b", ATTRS{idProduct}=="0001", ` usbRules += `RUN+="/bin/sh -c \'echo enabled > /sys$env{DEVPATH}/../power/wakeup\'"\n` - if err := l.rootFs.FilePutContents(path.Join(udevPath, + if err := rootFs.FilePutContents(path.Join(udevPath, "90-usb-tablet-remote-wakeup.rules"), usbRules, false, false); err != nil { return err } @@ -233,7 +241,7 @@ func (l *SLinuxRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) erro return nil } -func (l *SLinuxRootFs) DeployStandbyNetworkingScripts(nics, nicsStandby []jsonutils.JSONObject) error { +func (l *sLinuxRootFs) DeployStandbyNetworkingScripts(rootFs IDiskPartition, nics, nicsStandby []jsonutils.JSONObject) error { var udevPath = "/etc/udev/rules.d/" var nicRules string for _, nic := range nicsStandby { @@ -247,71 +255,71 @@ func (l *SLinuxRootFs) DeployStandbyNetworkingScripts(nics, nicsStandby []jsonut nicRules += fmt.Sprintf(`NAME="eth%d"\n`, idx) } } - if err := l.rootFs.FilePutContents(path.Join(udevPath, "70-persistent-net.rules"), nicRules, false, false); err != nil { + if err := rootFs.FilePutContents(path.Join(udevPath, "70-persistent-net.rules"), nicRules, false, false); err != nil { return err } return nil } -func (l *SLinuxRootFs) GetOs() string { +func (l *sLinuxRootFs) GetOs() string { return "Linux" } -func (l *SLinuxRootFs) GetArch() string { - if l.rootFs.Exists("/lib64", false) && l.rootFs.Exists("/usr/lib64", false) { +func (l *sLinuxRootFs) GetArch(rootFs IDiskPartition) string { + if rootFs.Exists("/lib64", false) && rootFs.Exists("/usr/lib64", false) { return "x86_64" } else { return "x86" } } -func (l *SLinuxRootFs) PrepareFsForTemplate() error { +func (l *sLinuxRootFs) PrepareFsForTemplate(rootFs IDiskPartition) error { // clean /etc/fstab - if l.rootFs.Exists("/etc/fstab", false) { - fstabcont, _ := l.rootFs.FileGetContents("/etc/fstab", false) + if rootFs.Exists("/etc/fstab", false) { + fstabcont, _ := rootFs.FileGetContents("/etc/fstab", false) fstab := fstabutils.FSTabFile(string(fstabcont)) fstab.RemoveDevices(1) cf := fstab.ToConf() - if err := l.rootFs.FilePutContents("/etc/fstab", cf, false, false); err != nil { + if err := rootFs.FilePutContents("/etc/fstab", cf, false, false); err != nil { return err } } // rm /etc/ssh/*_key.* - if l.rootFs.Exists("/etc/ssh", false) { - for _, f := range l.rootFs.Listdir("/etc/ssh", false) { + if rootFs.Exists("/etc/ssh", false) { + for _, f := range l.rootFs.ListDir("/etc/ssh", false) { if strings.HasSuffix(f, "_key") || strings.HasSuffix(f, "_key.pub") { - l.rootFs.Remove("/etc/ssh/"+f, false) + rootFs.Remove("/etc/ssh/"+f, false) } } } // clean cloud-init - if l.rootFs.Exists("/var/lib/cloud", false) { - if err := l.rootFs.Cleandir("/var/lib/cloud", false, false); err != nil { + if rootFs.Exists("/var/lib/cloud", false) { + if err := rootFs.Cleandir("/var/lib/cloud", false, false); err != nil { return err } } cloudDisableFile := "/etc/cloud/cloud-init.disabled" - if l.rootFs.Exists(cloudDisableFile, false) { - l.rootFs.Remove(cloudDisableFile, false) + if rootFs.Exists(cloudDisableFile, false) { + rootFs.Remove(cloudDisableFile, false) } // clean /tmp /var/log /var/cache /var/spool /var/run for _, dir := range []string{"/tmp", "/var/tmp"} { - if l.rootFs.Exists(dir, false) { - if err := l.rootFs.Cleandir(dir, false, false); err != nil { + if rootFs.Exists(dir, false) { + if err := rootFs.Cleandir(dir, false, false); err != nil { return err } } } for _, dir := range []string{"/var/log", "/var/cache", "/usr/local/var/log", "/usr/local/var/cache"} { - if l.rootFs.Exists(dir, false) { + if rootFs.Exists(dir, false) { if err := l.rootFs.Zerofiles(dir, false); err != nil { return err } } } for _, dir := range []string{"/var/spool", "/var/run", "/run", "/usr/local/var/spool", "/usr/local/var/run"} { - if l.rootFs.Exists(dir, false) { - if err := l.rootFs.Cleandir(dir, true, true); err != nil { + if rootFs.Exists(dir, false) { + if err := rootFs.Cleandir(dir, true, true); err != nil { return err } } @@ -319,38 +327,113 @@ func (l *SLinuxRootFs) PrepareFsForTemplate() error { return nil } -func (l *SLinuxRootFs) GetSerialPorts() []string { - // var confpath = "/proc/tty/driver/serial" - if l.rootFs.SupportOsPathExists() { - // TODO: with sshpart utils +func (l *sLinuxRootFs) getSerialPorts(rootFs IDiskPartition) []string { + if !rootFs.SupportSerialPorts() { return nil - } else { + } + // XXX HACK, only sshpart.SSHPartition support this + var confpath = "/proc/tty/driver/serial" + content, err := rootFs.FileGetContents(confpath, false) + if err != nil { + log.Errorf("Get %s error: %v", confpath, err) return nil } + return sysutils.GetSerialPorts(strings.Split(string(content), "\n")) +} + +func (l *sLinuxRootFs) enableSerialConsoleInitCentos(rootFs IDiskPartition) error { + // http://www.jonno.org/drupal/node/10 + var err error + for _, tty := range l.getSerialPorts(rootFs) { + content := fmt.Sprintf( + `stop on runlevel [016] +start on runlevel [345] +instance %s +respawn +pre-start exec /sbin/securetty %s +exec /sbin/agetty /dev/%s 115200 vt100`, tty, tty, tty) + err = rootFs.FilePutContents(fmt.Sprintf("/etc/init/%s.conf", tty), content, false, false) + } + return err +} + +func (l *sLinuxRootFs) enableSerialConsoleInit(rootFs IDiskPartition) error { + // https://help.ubuntu.com/community/SerialConsoleHowto + var err error + for _, tty := range l.getSerialPorts(rootFs) { + content := fmt.Sprintf( + `start on stopped rc or RUNLEVEL=[12345] +stop on runlevel [!12345] +respawn +exec /sbin/getty -L 115200 %s vt102`, tty) + err = rootFs.FilePutContents(fmt.Sprintf("/etc/init/%s.conf", tty), content, false, false) + } + return err +} + +func (l *sLinuxRootFs) disableSerialConsoleInit(rootFs IDiskPartition) { + for _, tty := range l.getSerialPorts(rootFs) { + path := fmt.Sprintf("/etc/init/%s.conf", tty) + if rootFs.Exists(path, false) { + rootFs.Remove(path, false) + } + } } -// TODO: _enable_serial_console_init_centos, _enable_serial_console_init, -// _disable_serial_console_init, _enable_serial_console_systemd, _disable_serial_console_systemd - -type SDebianLikeRootFs struct { - *SLinuxRootFs +func (l *sLinuxRootFs) enableSerialConsoleSystemd(rootFs IDiskPartition) error { + for _, tty := range l.getSerialPorts(rootFs) { + sPath := fmt.Sprintf("/etc/systemd/system/getty.target.wants/getty@%s.service", tty) + if rootFs.Exists(sPath, false) { + //rootFs.Symlink("/usr/lib/systemd/system/getty@.service", sPath) + } + } + return nil } -func NewDebianLikeRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SDebianLikeRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +func (l *sLinuxRootFs) disableSerialConsoleSystemd(rootFs IDiskPartition) { + for _, tty := range l.getSerialPorts(rootFs) { + sPath := fmt.Sprintf("/etc/systemd/system/getty.target.wants/getty@%s.service", tty) + if rootFs.Exists(sPath, false) { + rootFs.Remove(sPath, false) + } + } } -func (d *SDebianLikeRootFs) RootSignatures() []string { - sig := d.SLinuxRootFs.RootSignatures() +type sDebianLikeRootFs struct { + *sLinuxRootFs +} + +func newDebianLikeRootFs(part IDiskPartition) *sDebianLikeRootFs { + return &sDebianLikeRootFs{ + sLinuxRootFs: newLinuxRootFs(part), + } +} + +func (d *sDebianLikeRootFs) GetReleaseInfo(rootFs IDiskPartition, driver IDebianRootFsDriver) *SReleaseInfo { + version, err := rootFs.FileGetContents(driver.VersionFilePath(), false) + if err != nil { + log.Errorf("Get %s error: %v", driver.VersionFilePath(), err) + return nil + } + versionStr := strings.TrimSpace(string(version)) + return &SReleaseInfo{ + Distro: driver.DistroName(), + Version: versionStr, + Arch: d.GetArch(rootFs), + } +} + +func (d *sDebianLikeRootFs) RootSignatures() []string { + sig := d.sLinuxRootFs.RootSignatures() return append([]string{"/etc/hostname"}, sig...) } -func (d *SDebianLikeRootFs) DeployHostname(hn, domain string) error { - return d.rootFs.FilePutContents("/etc/hostname", hn, false, false) +func (d *sDebianLikeRootFs) DeployHostname(rootFs IDiskPartition, hn, domain string) error { + return rootFs.FilePutContents("/etc/hostname", hn, false, false) } -func (d *SDebianLikeRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) error { - if err := d.SLinuxRootFs.DeployNetworkingScripts(nics); err != nil { +func (d *sDebianLikeRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject) error { + if err := d.sLinuxRootFs.DeployNetworkingScripts(rootFs, nics); err != nil { return err } fn := "/etc/network/interfaces" @@ -409,64 +492,191 @@ func (d *SDebianLikeRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) cmds += fmt.Sprintf("iface eth%d inet dhcp\n\n", nicIdx) } } - return d.rootFs.FilePutContents(fn, cmds, false, false) + return rootFs.FilePutContents(fn, cmds, false, false) } type SDebianRootFs struct { - *SDebianLikeRootFs + *sDebianLikeRootFs } -func NewDebianRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SDebianRootFs{SDebianLikeRootFs: NewDebianLikeRootFs(part).(*SDebianLikeRootFs)} +func NewDebianRootFs(part IDiskPartition) IRootFsDriver { + driver := new(SDebianRootFs) + driver.sDebianLikeRootFs = newDebianLikeRootFs(part) + return driver +} + +func (d *SDebianRootFs) GetName() string { + return "Debian" +} + +func (d *SDebianRootFs) DistroName() string { + return d.GetName() +} + +func (d *SDebianRootFs) VersionFilePath() string { + return "/etc/debian_version" +} + +func (d *SDebianRootFs) rootSignatures(driver IDebianRootFsDriver) []string { + sig := d.sDebianLikeRootFs.RootSignatures() + return append([]string{driver.VersionFilePath()}, sig...) +} + +func (d *SDebianRootFs) RootSignatures() []string { + return d.rootSignatures(d) +} + +func (d *SDebianRootFs) RootExcludeSignatures() []string { + return []string{"/etc/lsb-release"} +} + +func (d *SDebianRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + return d.sDebianLikeRootFs.GetReleaseInfo(rootFs, d) } type SCirrosRootFs struct { *SDebianRootFs } -func NewCirrosRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SCirrosRootFs{SDebianRootFs: NewDebianRootFs(part).(*SDebianRootFs)} +func NewCirrosRootFs(part IDiskPartition) IRootFsDriver { + driver := new(SCirrosRootFs) + driver.SDebianRootFs = NewDebianRootFs(part).(*SDebianRootFs) + return driver +} + +func (d *SCirrosRootFs) GetName() string { + return "Cirros" +} + +func (d *SCirrosRootFs) DistroName() string { + return d.GetName() +} + +func (d *SCirrosRootFs) VersionFilePath() string { + return "/etc/br-version" +} + +func (d *SCirrosRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + return d.SDebianRootFs.sDebianLikeRootFs.GetReleaseInfo(rootFs, d) +} + +func (d *SCirrosRootFs) RootSignatures() []string { + return d.rootSignatures(d) } type SCirrosNewRootFs struct { *SDebianRootFs } -func NewCirrosNewRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SCirrosNewRootFs{SDebianRootFs: NewDebianRootFs(part).(*SDebianRootFs)} +func NewCirrosNewRootFs(part IDiskPartition) IRootFsDriver { + driver := new(SCirrosNewRootFs) + driver.SDebianRootFs = NewDebianRootFs(part).(*SDebianRootFs) + return driver +} + +func (d *SCirrosNewRootFs) GetName() string { + return "Cirros" +} + +func (d *SCirrosNewRootFs) DistroName() string { + return d.GetName() +} + +func (d *SCirrosNewRootFs) VersionFilePath() string { + return "/etc/cirros/version" +} + +func (d *SCirrosNewRootFs) RootSignatures() []string { + return d.rootSignatures(d) +} + +func (d *SCirrosNewRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + return d.SDebianRootFs.sDebianLikeRootFs.GetReleaseInfo(rootFs, d) } type SUbuntuRootFs struct { - *SDebianRootFs + *sDebianLikeRootFs } -func NewUbuntuRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SUbuntuRootFs{SDebianRootFs: NewDebianRootFs(part).(*SDebianRootFs)} +func NewUbuntuRootFs(part IDiskPartition) IRootFsDriver { + driver := new(SUbuntuRootFs) + driver.sDebianLikeRootFs = newDebianLikeRootFs(part) + return driver } -type SRedhatLikeRootFs struct { - *SLinuxRootFs +func (d *SUbuntuRootFs) RootSignatures() []string { + sig := d.sDebianLikeRootFs.RootSignatures() + return append([]string{"/etc/lsb-release"}, sig...) } -func NewRedhatLikeRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SRedhatLikeRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +func (d *SUbuntuRootFs) GetName() string { + return "Ubuntu" } -func (r *SRedhatLikeRootFs) RootSignatures() []string { - sig := r.SLinuxRootFs.RootSignatures() +func (d *SUbuntuRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + distroKey := "DISTRIB_RELEASE=" + rel, err := rootFs.FileGetContents("/etc/lsb-release", false) + if err != nil { + log.Errorf("Get ubuntu release info error: %v", err) + return nil + } + var version string + lines := strings.Split(string(rel), "\n") + for _, l := range lines { + if strings.HasPrefix(l, distroKey) { + version = strings.TrimSpace(l[len(distroKey) : len(l)-1]) + } + } + return newReleaseInfo(d.GetName(), version, d.GetArch(rootFs)) +} + +func (d *SUbuntuRootFs) EnableSerialConsole(rootFs IDiskPartition, sysInfo *jsonutils.JSONDict) error { + relInfo := d.GetReleaseInfo(rootFs) + ver := strings.Split(relInfo.Version, ".") + verInt, _ := strconv.Atoi(ver[0]) + if verInt < 16 { + return d.enableSerialConsoleInit(rootFs) + } + return d.enableSerialConsoleSystemd(rootFs) +} + +func (d *SUbuntuRootFs) DisableSerialConcole(rootFs IDiskPartition) error { + relInfo := d.GetReleaseInfo(rootFs) + ver := strings.Split(relInfo.Version, ".") + verInt, _ := strconv.Atoi(ver[0]) + if verInt < 16 { + d.disableSerialConsoleInit(rootFs) + return nil + } + d.disableSerialConsoleSystemd(rootFs) + return nil +} + +type sRedhatLikeRootFs struct { + *sLinuxRootFs +} + +func newRedhatLikeRootFs(part IDiskPartition) *sRedhatLikeRootFs { + return &sRedhatLikeRootFs{ + sLinuxRootFs: newLinuxRootFs(part), + } +} + +func (r *sRedhatLikeRootFs) RootSignatures() []string { + sig := r.sLinuxRootFs.RootSignatures() return append([]string{"/etc/sysconfig/network", "/etc/redhat-release"}, sig...) } -func (r *SRedhatLikeRootFs) DeployHostname(hn, domain string) error { +func (r *sRedhatLikeRootFs) DeployHostname(rootFs IDiskPartition, hn, domain string) error { var sPath = "/etc/sysconfig/network" centosHn := "" centosHn += "NETWORKING=yes\n" centosHn += fmt.Sprintf("HOSTNAME=%s.%s\n", hn, domain) - if err := r.rootFs.FilePutContents(sPath, centosHn, false, false); err != nil { + if err := rootFs.FilePutContents(sPath, centosHn, false, false); err != nil { return err } - if r.rootFs.Exists("/etc/hostname", false) { - return r.rootFs.FilePutContents("/etc/hostname", hn, false, false) + if rootFs.Exists("/etc/hostname", false) { + return rootFs.FilePutContents("/etc/hostname", hn, false, false) } return nil } @@ -483,9 +693,9 @@ func (r *SRedhatLikeRootFs) DeployHostname(hn, domain string) error { self.root_fs.file_put_contents(os.path.join(udev_path, '60-net.rules'), nic_rules) */ -func (r *SRedhatLikeRootFs) Centos5DeployNetworkingScripts(nics []jsonutils.JSONObject) error { +func (r *sRedhatLikeRootFs) Centos5DeployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject) error { var udevPath = "/etc/udev/rules.d/" - if r.rootFs.Exists(udevPath, false) { + if rootFs.Exists(udevPath, false) { var nicRules = "" for _, nic := range nics { var nicdesc = new(types.ServerNic) @@ -496,22 +706,21 @@ func (r *SRedhatLikeRootFs) Centos5DeployNetworkingScripts(nics []jsonutils.JSON nicRules += fmt.Sprintf(`SYSFS{address}=="%s", `, strings.ToLower(nicdesc.Mac)) nicRules += fmt.Sprintf(`NAME="eth%d"\`, nicdesc.Index) } - return r.rootFs.FilePutContents(udevPath, nicRules, false, false) + return rootFs.FilePutContents(udevPath, nicRules, false, false) } return nil } -func (r *SRedhatLikeRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) error { - relinfo := r.GetReleaseInfo() - ver := strings.Split(relinfo[0], ".") +func (r *sRedhatLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject, relInfo *SReleaseInfo) error { + ver := strings.Split(relInfo.Version, ".") iv, err := strconv.ParseInt(ver[0], 10, 0) if err != nil { - return err + return fmt.Errorf("Failed to get release version: %v", err) } if iv < 6 { - err = r.Centos5DeployNetworkingScripts(nics) + err = r.Centos5DeployNetworkingScripts(rootFs, nics) } else { - err = r.SLinuxRootFs.DeployNetworkingScripts(nics) + err = r.sLinuxRootFs.DeployNetworkingScripts(rootFs, nics) } if err != nil { return err @@ -556,7 +765,7 @@ func (r *SRedhatLikeRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) } if len(rtbl) > 0 { var fn = fmt.Sprintf("/etc/sysconfig/network-scripts/route-eth%d", nicdesc.Index) - if err := r.rootFs.FilePutContents(fn, rtbl, false, false); err != nil { + if err := rootFs.FilePutContents(fn, rtbl, false, false); err != nil { return err } } @@ -572,15 +781,15 @@ func (r *SRedhatLikeRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) cmds += "BOOTPROTO=dhcp\n" } var fn = fmt.Sprintf("/etc/sysconfig/network-scripts/ifcfg-eth%d", nicdesc.Index) - if err := r.rootFs.FilePutContents(fn, cmds, false, false); err != nil { + if err := rootFs.FilePutContents(fn, cmds, false, false); err != nil { return err } } return nil } -func (r *SRedhatLikeRootFs) DeployStandbyNetworkingScripts(nics, nicsStandby []jsonutils.JSONObject) error { - if err := r.SLinuxRootFs.DeployStandbyNetworkingScripts(nics, nicsStandby); err != nil { +func (r *sRedhatLikeRootFs) DeployStandbyNetworkingScripts(rootFs IDiskPartition, nics, nicsStandby []jsonutils.JSONObject) error { + if err := r.sLinuxRootFs.DeployStandbyNetworkingScripts(rootFs, nics, nicsStandby); err != nil { return err } for _, nic := range nicsStandby { @@ -596,7 +805,7 @@ func (r *SRedhatLikeRootFs) DeployStandbyNetworkingScripts(nics, nicsStandby []j cmds += fmt.Sprintf("MACADDR=%s\n", nicdesc.Mac) cmds += "ONBOOT=no\n" var fn = fmt.Sprintf("/etc/sysconfig/network-scripts/ifcfg-eth%d", nicdesc.Index) - if err := r.rootFs.FilePutContents(fn, cmds, false, false); err != nil { + if err := rootFs.FilePutContents(fn, cmds, false, false); err != nil { return err } } @@ -608,20 +817,24 @@ func (r *SRedhatLikeRootFs) DeployStandbyNetworkingScripts(nics, nicsStandby []j //TODO disable_serial_console type SCentosRootFs struct { - *SRedhatLikeRootFs + *sRedhatLikeRootFs } -func NewCentosRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SCentosRootFs{SRedhatLikeRootFs: NewRedhatLikeRootFs(part).(*SRedhatLikeRootFs)} +func NewCentosRootFs(part IDiskPartition) IRootFsDriver { + return &SCentosRootFs{sRedhatLikeRootFs: newRedhatLikeRootFs(part)} +} + +func (c *SCentosRootFs) GetName() string { + return "CentOS" } func (c *SCentosRootFs) RootSignatures() []string { - sig := c.SRedhatLikeRootFs.RootSignatures() + sig := c.sRedhatLikeRootFs.RootSignatures() return append([]string{"/etc/centos-release"}, sig...) } -func (c *SCentosRootFs) GetReleaseInfo() []string { - rel, _ := c.rootFs.FileGetContents("/etc/centos-release", false) +func (c *SCentosRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + rel, _ := rootFs.FileGetContents("/etc/centos-release", false) var version string if len(rel) > 0 { re := regexp.MustCompile(`^\d+\.\d+`) @@ -633,19 +846,20 @@ func (c *SCentosRootFs) GetReleaseInfo() []string { } } } - return []string{"CentOS", version, c.GetArch()} + return newReleaseInfo(c.GetName(), version, c.GetArch(rootFs)) } -func (c *SCentosRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) error { - if err := c.SRedhatLikeRootFs.DeployNetworkingScripts(nics); err != nil { +func (c *SCentosRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics []jsonutils.JSONObject) error { + relInfo := c.GetReleaseInfo(rootFs) + if err := c.sRedhatLikeRootFs.deployNetworkingScripts(rootFs, nics, relInfo); err != nil { return err } var udevPath = "/etc/udev/rules.d/" var files = []string{"60-net.rules", "75-persistent-net-generator.rules"} for _, f := range files { sPath := path.Join(udevPath, f) - if !c.rootFs.Exists(sPath, false) { - if err := c.rootFs.FilePutContents(sPath, "", false, false); err != nil { + if !rootFs.Exists(sPath, false) { + if err := rootFs.FilePutContents(sPath, "", false, false); err != nil { return err } } @@ -654,58 +868,90 @@ func (c *SCentosRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) err } type SFedoraRootFs struct { - *SRedhatLikeRootFs + *sRedhatLikeRootFs } -func NewFedoraRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SFedoraRootFs{SRedhatLikeRootFs: NewRedhatLikeRootFs(part).(*SRedhatLikeRootFs)} +func NewFedoraRootFs(part IDiskPartition) IRootFsDriver { + return &SFedoraRootFs{sRedhatLikeRootFs: newRedhatLikeRootFs(part)} +} + +func (c *SFedoraRootFs) GetName() string { + return "Fedora" +} + +func (c *SFedoraRootFs) RootSignatures() []string { + sig := c.sRedhatLikeRootFs.RootSignatures() + return append([]string{"/etc/fedora-release"}, sig...) +} + +func (c *SFedoraRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + rel, _ := rootFs.FileGetContents("/etc/fedora-release", false) + var version string + if len(rel) > 0 { + re := regexp.MustCompile(`^\d+`) + dat := strings.Split(string(rel), " ") + for _, v := range dat { + if re.Match([]byte(v)) { + version = v + break + } + } + } + return newReleaseInfo(c.GetName(), version, c.GetArch(rootFs)) } type SRhelRootFs struct { - *SRedhatLikeRootFs + *sRedhatLikeRootFs } -func NewRhelRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SRhelRootFs{SRedhatLikeRootFs: NewRedhatLikeRootFs(part).(*SRedhatLikeRootFs)} +func NewRhelRootFs(part IDiskPartition) IRootFsDriver { + return &SRhelRootFs{sRedhatLikeRootFs: newRedhatLikeRootFs(part)} } -type SGentooRootFs struct { - *SLinuxRootFs +func (d *SRhelRootFs) GetName() string { + return "RHEL" } -func NewGentooRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SGentooRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +func (d *SRhelRootFs) GetReleaseInfo(rootFs IDiskPartition) *SReleaseInfo { + rel, _ := rootFs.FileGetContents("/etc/redhat-release", false) + var version string + if len(rel) > 0 { + dat := strings.Split(string(rel), " ") + if len(dat) > 6 { + version = dat[6] + } + } + return newReleaseInfo(d.GetName(), version, d.GetArch(rootFs)) +} + +/*type SGentooRootFs struct { + *sLinuxRootFs +} + +func NewGentooRootFs(part IDiskPartition) IRootFsDriver { + return &SGentooRootFs{sLinuxRootFs: newLinuxRootFs(part)} } type SArchLinuxRootFs struct { - *SLinuxRootFs + *sLinuxRootFs } -func NewArchLinuxRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SArchLinuxRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +func NewArchLinuxRootFs(part IDiskPartition) IRootFsDriver { + return &SArchLinuxRootFs{sLinuxRootFs: newLinuxRootFs(part)} } type SOpenWrtRootFs struct { - *SLinuxRootFs + *sLinuxRootFs } -func NewOpenWrtRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SOpenWrtRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +func NewOpenWrtRootFs(part IDiskPartition) IRootFsDriver { + return &SOpenWrtRootFs{sLinuxRootFs: newLinuxRootFs(part)} } type SCoreOsRootFs struct { *SGuestRootFsDriver } -func NewCoreOsRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SCoreOsRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} -} - -func init() { - linuxFsDrivers := []newRootFsDriverFunc{ - NewLinuxRootFs, NewDebianLikeRootFs, NewDebianRootFs, NewCirrosRootFs, NewCirrosNewRootFs, - NewUbuntuRootFs, NewRedhatLikeRootFs, NewCentosRootFs, NewFedoraRootFs, NewRhelRootFs, - NewGentooRootFs, NewArchLinuxRootFs, NewOpenWrtRootFs, NewCoreOsRootFs, - } - rootfsDrivers = append(rootfsDrivers, linuxFsDrivers...) -} +func NewCoreOsRootFs(part IDiskPartition) IRootFsDriver { + return &SCoreOsRootFs{SGuestRootFsDriver: newGuestRootFsDriver(part)} +}*/ diff --git a/pkg/hostman/guestfs/localfs.go b/pkg/hostman/guestfs/localfs.go index 0a5439d228..9ca09aea40 100644 --- a/pkg/hostman/guestfs/localfs.go +++ b/pkg/hostman/guestfs/localfs.go @@ -4,43 +4,26 @@ import ( "fmt" "io" "io/ioutil" - "math/rand" "os" "os/exec" "path" "strings" - "syscall" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" "yunion.io/x/onecloud/pkg/util/fileutils2" ) type SLocalGuestFS struct { mountPath string - readOnly bool -} - -func (f *SLocalGuestFS) SupportOsPathExists() bool { - return false } func (f *SLocalGuestFS) IsReadonly() bool { - log.Infof("Test if read-only fs ...") - var filename = fmt.Sprint("./%f", rand.Float32()) - if err := f.FilePutContents(filename, fmt.Sprint("%f", rand.Float32()), false, false); err == nil { - f.Remove(filename, false) - return false - } else { - log.Errorf("File system is readonly: %s", err) - f.readOnly = true - return true - } + return IsPartitionReadonly(f) } -func (f *SLocalGuestFS) GetReadonly() bool { - return f.readOnly +func (f *SLocalGuestFS) SupportSerialPorts() bool { + return false } func (f *SLocalGuestFS) getLocalPath(sPath string, caseInsensitive bool) string { @@ -98,7 +81,7 @@ func (f *SLocalGuestFS) Mkdir(sPath string, mode int, caseInsensitive bool) erro return nil } -func (f *SLocalGuestFS) Listdir(sPath string, caseInsensitive bool) []string { +func (f *SLocalGuestFS) ListDir(sPath string, caseInsensitive bool) []string { sPath = f.getLocalPath(sPath, caseInsensitive) if len(sPath) > 0 { files, err := ioutil.ReadDir(sPath) @@ -219,82 +202,6 @@ func (f *SLocalGuestFS) UserAdd(user string, caseInsensitive bool) error { return nil } -func (f *SLocalGuestFS) MergeAuthorizedKeys(oldKeys string, pubkeys *sshkeys.SSHKeys) string { - var allkeys = make(map[string]string, 0) - if len(oldKeys) > 0 { - for _, line := range strings.Split(oldKeys, "\n") { - line = strings.TrimSpace(line) - dat := strings.Split(line, " ") - if len(dat) > 1 { - if _, ok := allkeys[dat[1]]; !ok { - allkeys[dat[1]] = line - } - } - } - } - if len(pubkeys.DeletePublicKey) > 0 { - dat := strings.Split(pubkeys.DeletePublicKey, " ") - if len(dat) > 1 { - if _, ok := allkeys[dat[1]]; ok { - delete(allkeys, dat[1]) - } - } - } - for _, k := range []string{pubkeys.PublicKey, pubkeys.AdminPublicKey, pubkeys.ProjectPublicKey} { - if len(k) > 0 { - k = strings.TrimSpace(k) - dat := strings.Split(k, " ") - if len(dat) > 1 { - if _, ok := allkeys[dat[1]]; !ok { - allkeys[dat[1]] = k - } - } - } - } - var keys = make([]string, len(allkeys)) - for key := range allkeys { - keys = append(keys, key) - } - return strings.Join(keys, "\n") -} - -func (f *SLocalGuestFS) DeployAuthorizedKeys(usrDir string, pubkeys *sshkeys.SSHKeys, replace bool) error { - usrStat := f.Stat(usrDir, false) - if usrStat != nil { - sshDir := path.Join(usrDir, ".ssh") - authFile := path.Join(sshDir, "authorized_keys") - modeRwxOwner := syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR - modeRwOwner := syscall.S_IRUSR | syscall.S_IWUSR - fStat := usrStat.Sys().(*syscall.Stat_t) - if !f.Exists(sshDir, false) { - err := f.Mkdir(sshDir, modeRwxOwner, false) - if err != nil { - return err - } - err = f.Chown(sshDir, int(fStat.Uid), int(fStat.Gid), false) - if err != nil { - return err - } - } - var oldKeys = "" - if !replace { - bOldKeys, _ := f.FileGetContents(authFile, false) - oldKeys = string(bOldKeys) - } - newKeys := f.MergeAuthorizedKeys(oldKeys, pubkeys) - err := f.FilePutContents(authFile, newKeys, false, false) - if err != nil { - return err - } - err = f.Chown(authFile, int(fStat.Uid), int(fStat.Gid), false) - if err != nil { - return err - } - return f.Chmod(authFile, uint32(modeRwOwner), false) - } - return nil -} - func (f *SLocalGuestFS) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) { sPath = f.getLocalPath(sPath, caseInsensitive) if len(sPath) > 0 { diff --git a/pkg/hostman/guestfs/macos.go b/pkg/hostman/guestfs/macos.go index 2fa37242b7..c5190ff788 100644 --- a/pkg/hostman/guestfs/macos.go +++ b/pkg/hostman/guestfs/macos.go @@ -1,13 +1,9 @@ package guestfs type SMacOSRootFs struct { - *SGuestRootFsDriver + *sGuestRootFsDriver } -func NewMacOSRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SMacOSRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} -} - -func init() { - rootfsDrivers = append(rootfsDrivers, NewMacOSRootFs) -} +//func NewMacOSRootFs(part IDiskPartition) IRootFsDriver { +//return &SMacOSRootFs{sGuestRootFsDriver: newGuestRootFsDriver(part)} +//} diff --git a/pkg/hostman/guestfs/sshpart/sshpart.go b/pkg/hostman/guestfs/sshpart/sshpart.go new file mode 100644 index 0000000000..1258d926b0 --- /dev/null +++ b/pkg/hostman/guestfs/sshpart/sshpart.go @@ -0,0 +1,497 @@ +package sshpart + +import ( + "fmt" + "os" + "path" + "regexp" + "strconv" + "strings" + "syscall" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/baremetal/utils/disktool" + "yunion.io/x/onecloud/pkg/compute/baremetal" + "yunion.io/x/onecloud/pkg/hostman/guestfs" + "yunion.io/x/onecloud/pkg/util/ssh" + "yunion.io/x/onecloud/pkg/util/stringutils" +) + +type SSHPartition struct { + term *ssh.Client + partDev string + mountPath string +} + +func NewSSHPartition(term *ssh.Client, dev string) *SSHPartition { + p := new(SSHPartition) + p.term = term + p.partDev = dev + p.mountPath = fmt.Sprintf("/tmp/%s", strings.Replace(p.partDev, "/", "_", -1)) + return p +} + +func (p *SSHPartition) GetMountPath() string { + return p.mountPath +} + +func (p *SSHPartition) GetFsFormat() (string, error) { + cmd := fmt.Sprintf("/lib/mos/partfs.sh %s", p.partDev) + ret, err := p.term.Run(cmd) + if err != nil { + return "", err + } + return strings.TrimSpace(ret[0]), nil +} + +func (p *SSHPartition) osChmod(path string, mode uint32) error { + cmd := fmt.Sprintf("chmod %o %s", (mode & 0777), path) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) osMkdirP(dir string, mode uint32) error { + cmd := fmt.Sprintf("mkdir -p %s", dir) + _, err := p.term.Run(cmd) + if err != nil { + return err + } + if mode != 0 { + return p.osChmod(dir, mode) + } + return nil +} + +func (p *SSHPartition) Mkdir(sPath string, mode int, caseInsensitive bool) error { + segs := strings.Split(sPath, "/") + sp := "" + pPath := p.getLocalPath("/", caseInsensitive) + var err error + for _, s := range segs { + if len(s) > 0 { + sp = path.Join(sp, s) + vPath := p.getLocalPath(sp, caseInsensitive) + if len(vPath) == 0 { + err = p.osMkdirP(path.Join(pPath, s), uint32(mode)) + pPath = p.getLocalPath(sp, caseInsensitive) + } else { + pPath = vPath + } + } + } + return err +} + +func (p *SSHPartition) osRmDir(path string) error { + _, err := p.term.Run(fmt.Sprintf("rm -fr %s", path)) + return err +} + +func (p *SSHPartition) osPathExists(path string) bool { + _, err := p.term.Run(fmt.Sprintf("test -e %s", path)) + if err != nil { + return false + } + return true +} + +func (p *SSHPartition) Mount() error { + if err := p.osMkdirP(p.mountPath, 0); err != nil { + return err + } + fs, err := p.GetFsFormat() + if err != nil { + return err + } + fstr := "" + if fs == "ntfs" { + fstr = "-t ntfs-3g" + } + cmd := fmt.Sprintf("mount %s -o sync %s %s", fstr, p.partDev, p.mountPath) + log.Infof("Do mount %s", cmd) + _, err = p.term.Run(cmd) + if err != nil { + p.osRmDir(p.mountPath) + return err + } + return nil +} + +func (p *SSHPartition) Unmount() error { + if !p.IsMounted() { + return fmt.Errorf("%s is not mounted", p.mountPath) + } + var err error + for tries := 0; tries < 10; tries++ { + cmds := []string{ + "sync", + "/sbin/sysctl -w vm.drop_caches=3", + fmt.Sprintf("/bin/umount %s", p.mountPath), + fmt.Sprintf("/sbin/hdparm -f %s", p.partDev), + } + _, err = p.term.Run(cmds...) + if err != nil { + log.Errorf("umount %s error: %v", p.mountPath, err) + time.Sleep(1 * time.Second) + } else { + return nil + } + } + return err +} + +func (p *SSHPartition) IsMounted() bool { + if !p.osPathExists(p.mountPath) { + return false + } + _, err := p.term.Run(fmt.Sprintf("mountpoint %s", p.mountPath)) + if err != nil { + return false + } + return true +} + +func (p *SSHPartition) Chmod(sPath string, mode uint32, caseI bool) error { + sPath = p.getLocalPath(sPath, caseI) + if sPath != "" { + return p.osChmod(sPath, mode) + } + return nil +} + +func (p *SSHPartition) osIsDir(path string) bool { + _, err := p.term.Run(fmt.Sprintf("test -d %s", path)) + if err != nil { + return false + } + return true +} + +func (p *SSHPartition) osListDir(path string) ([]string, error) { + if !p.osIsDir(path) { + return nil, fmt.Errorf("Path %s is not dir", path) + } + ret, err := p.term.Run(fmt.Sprintf("ls -a %s", path)) + if err != nil { + return nil, err + } + files := []string{} + for _, f := range ret { + f = strings.TrimSpace(f) + if !utils.IsInStringArray(f, []string{"", ".", ".."}) { + files = append(files, f) + } + } + return files, nil +} + +func (p *SSHPartition) getLocalPath(sPath string, caseI bool) string { + var fullPath = p.mountPath + pathSegs := strings.Split(sPath, "/") + for _, seg := range pathSegs { + if len(seg) > 0 { + var realSeg string + files, err := p.osListDir(fullPath) + if err != nil { + log.Errorf("List dir %s error: %v", sPath, err) + return "" + } + for _, f := range files { + if f == seg || (caseI && (strings.ToLower(f)) == strings.ToLower(seg)) { + realSeg = f + break + } + } + if len(realSeg) > 0 { + fullPath = path.Join(fullPath, realSeg) + } else { + return "" + } + } + } + return fullPath +} + +func (p *SSHPartition) Exists(sPath string, caseInsensitive bool) bool { + sPath = p.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return p.osPathExists(sPath) + } + return false +} + +func (p *SSHPartition) sshFileGetContents(path string) ([]byte, error) { + cmd := fmt.Sprintf("cat %s", path) + ret, err := p.term.Run(cmd) + if err != nil { + return nil, err + } + if len(ret) > 0 && ret[len(ret)-1] == "" { + ret = ret[0 : len(ret)-1] + } + retBytes := []byte(strings.Join(ret, "\n")) + return retBytes, nil +} + +func (p *SSHPartition) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) { + sPath = p.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return p.sshFileGetContents(sPath) + } + return nil, fmt.Errorf("Cann't find path: %s", sPath) +} + +func (p *SSHPartition) sshFilePutContents(sPath, content string, modAppend bool) error { + op := ">" + if modAppend { + op = ">>" + } + + if len(content) == 0 { + _, err := p.term.Run(fmt.Sprintf("echo '' %s %s", op, sPath)) + return err + } + + cmds := []string{} + var chunkSize int = 8192 + for offset := 0; offset < len(content); offset += chunkSize { + end := offset + chunkSize + if end > len(content) { + end = len(content) + } + ll, err := stringutils.EscapeEchoString(content[offset:end]) + if err != nil { + return fmt.Errorf("EscapeEchoString %q error: %v", content[offset:end], err) + } + cmd := fmt.Sprintf("echo -n -e \"%s\" %s %s", ll, op, sPath) + cmds = append(cmds, cmd) + if op == ">" { + op = ">>" + } + } + _, err := p.term.Run(cmds...) + return err +} + +func (p *SSHPartition) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error { + sFilePath := p.getLocalPath(sPath, caseInsensitive) + if len(sFilePath) > 0 { + sPath = sFilePath + } else { + dirPath := p.getLocalPath(path.Dir(sPath), caseInsensitive) + if len(dirPath) > 0 { + sPath = path.Join(dirPath, path.Base(sPath)) + } + } + if len(sPath) > 0 { + return p.sshFilePutContents(sPath, content, modAppend) + } + return fmt.Errorf("Can't put content to %s", sPath) +} + +func (p *SSHPartition) ListDir(sPath string, caseInsensitive bool) []string { + sPath = p.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + ret, err := p.osListDir(sPath) + if err != nil { + log.Errorf("list dir for %s: %v", sPath, err) + return nil + } + return ret + } + return nil +} + +func (p *SSHPartition) osChown(sPath string, uid, gid int) error { + cmd := fmt.Sprintf("chown %d.%d %s", uid, gid, sPath) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) Chown(sPath string, uid, gid int, caseInsensitive bool) error { + sPath = p.getLocalPath(sPath, caseInsensitive) + if len(sPath) == 0 { + return fmt.Errorf("Can't get local path: %s", sPath) + } + return p.osChown(sPath, uid, gid) +} + +func (p *SSHPartition) osRemove(sPath string) error { + cmd := fmt.Sprintf("rm %s", sPath) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) Remove(sPath string, caseInsensitive bool) { + sPath = p.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + p.osRemove(sPath) + } +} + +func (p *SSHPartition) UserAdd(user string, caseInsensitive bool) error { + cmd := fmt.Sprintf("/usr/sbin/chroot %s /usr/sbin/useradd -m -s /bin/bash %s", p.mountPath, user) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) Passwd(user, password string, caseInsensitive bool) error { + newpass := "/tmp/newpass" + p.sshFilePutContents(newpass, fmt.Sprintf("%s\n%s\n", password, password), false) + cmd := fmt.Sprintf("/usr/sbin/chroot %s /usr/bin/passwd %s < %s", p.mountPath, user, newpass) + _, err := p.term.Run(cmd) + return err +} + +func (p *SSHPartition) osStat(sPath string) (os.FileInfo, error) { + cmd := fmt.Sprintf("ls -a -l -n -i -s -d %s", sPath) + ret, err := p.term.Run(cmd) + if err != nil { + return nil, err + } + for _, line := range ret { + dat := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(line), -1) + if len(dat) > 7 && ((dat[2][0] != 'l' && dat[len(dat)-1] == sPath) || + (dat[2][0] == 'l' && dat[len(dat)-3] == sPath)) { + stMode, err := modeStr2Bin(dat[2]) + if err != nil { + return nil, err + } + stIno, _ := strconv.Atoi(dat[0]) + stUid, _ := strconv.Atoi(dat[4]) + stGid, _ := strconv.Atoi(dat[5]) + stSize, _ := strconv.Atoi(dat[6]) + info := &sFileInfo{ + name: sPath, + size: int64(stSize), + mode: os.FileMode(stMode), + isDir: dat[2][0] == 'd', + stat: &syscall.Stat_t{ + Ino: uint64(stIno), + Uid: uint32(stUid), + Gid: uint32(stGid), + Size: int64(stSize), + }, + } + return info, nil + } + } + return nil, fmt.Errorf("Can't stat for path %s", sPath) +} + +func modeStr2Bin(mode string) (uint32, error) { + table := []map[byte]uint32{ + {'-': syscall.S_IRUSR, 'd': syscall.S_IFDIR, 'l': syscall.S_IFLNK}, + {'r': syscall.S_IRUSR}, + {'w': syscall.S_IWUSR}, + {'x': syscall.S_IXUSR, 's': syscall.S_ISUID}, + {'r': syscall.S_IRGRP}, + {'w': syscall.S_IWGRP}, + {'x': syscall.S_IXGRP, 's': syscall.S_ISGID}, + {'r': syscall.S_IROTH}, + {'w': syscall.S_IWOTH}, + {'x': syscall.S_IXOTH}, + } + if len(mode) != len(table) { + return 0, fmt.Errorf("Invalid mod %q", mode) + } + var ret uint32 = 0 + for i := 0; i < len(table); i++ { + ret |= table[i][mode[i]] + } + return ret, nil +} + +// sFileInfo implements os.FileInfo interface +type sFileInfo struct { + name string + size int64 + mode os.FileMode + isDir bool + stat *syscall.Stat_t +} + +func (info sFileInfo) Name() string { + return info.name +} + +func (info sFileInfo) Size() int64 { + return info.size +} + +func (info sFileInfo) Mode() os.FileMode { + return info.mode +} + +func (info sFileInfo) IsDir() bool { + return info.isDir +} + +func (info sFileInfo) ModTime() time.Time { + // TODO: impl + return time.Now() +} + +func (info sFileInfo) Sys() interface{} { + return info.stat +} + +func (p *SSHPartition) Stat(sPath string, caseInsensitive bool) os.FileInfo { + sPath = p.getLocalPath(sPath, caseInsensitive) + if len(sPath) == 0 { + return nil + } + info, err := p.osStat(sPath) + if err != nil { + log.Errorf("stat %s error: %v", sPath, err) + return nil + } + return info +} + +func (p *SSHPartition) Zerofiles(dir string, caseI bool) error { + return nil +} + +func (p *SSHPartition) GetReadonly() bool { + return false +} + +func (p *SSHPartition) SupportSerialPorts() bool { + return true +} + +func (p *SSHPartition) Cleandir(dir string, keepdir, caseInsensitive bool) error { + return nil +} + +func MountSSHRootfs(term *ssh.Client, layouts []baremetal.Layout) (*SSHPartition, guestfs.IRootFsDriver, error) { + tool := disktool.NewSSHPartitionTool(term) + tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts)) + if err := tool.RetrieveDiskInfo(); err != nil { + return nil, nil, err + } + if err := tool.RetrievePartitionInfo(); err != nil { + return nil, nil, err + } + parts := tool.GetPartitions() + if len(parts) == 0 { + return nil, nil, fmt.Errorf("Not found partitions") + } + for _, part := range parts { + dev := NewSSHPartition(term, part.GetDev()) + if err := dev.Mount(); err != nil { + continue + } + if rootFs := guestfs.DetectRootFs(dev); rootFs != nil { + log.Infof("Use class %#v", rootFs) + return dev, rootFs, nil + } else { + dev.Unmount() + } + } + return nil, nil, fmt.Errorf("Fail to find rootfs") +} diff --git a/pkg/hostman/guestfs/sshpart/sshpart_test.go b/pkg/hostman/guestfs/sshpart/sshpart_test.go new file mode 100644 index 0000000000..3d4dd68949 --- /dev/null +++ b/pkg/hostman/guestfs/sshpart/sshpart_test.go @@ -0,0 +1,43 @@ +package sshpart + +import ( + //"syscall" + "testing" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" + "yunion.io/x/onecloud/pkg/hostman/guestfs" + "yunion.io/x/onecloud/pkg/util/ssh" +) + +var defaultSSHClient *ssh.Client + +func init() { + var err error + defaultSSHClient, err = ssh.NewClient("192.168.0.254", 22, "root", "oYA79tAcs4A1X4uT", "") + if err != nil { + log.Fatalf("Connect to ssh client error: %v", err) + } +} + +func TestNewSSHPartition(t *testing.T) { + dev := NewSSHPartition(defaultSSHClient, "/dev/sda2") + //err := dev.Mount() + //if err != nil { + //t.Errorf("Mount error: %v", err) + //return + //} + //defer func() { dev.Unmount() }() + log.Infof("%v", dev.Exists("/etc/", false)) + log.Infof("%v", dev.ListDir("/", false)) + dev.Mkdir("/tmp/test123", 0777, false) + dev.FilePutContents("/tmp/test123/content", "test1234\nhhhh", true, false) + pubkeys := &sshkeys.SSHKeys{ + PublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz0BLJD+xXYd3AP26uFs42mQSoznPew6gC84P9eUEAJHdkT/8WqTJV0z9M8ZU+8UbuR3iTSbblatrZepPkU2KkvE9ZkFftCIGCWCgvRWFfrDdMF1jwGYtKDg1xVxCmxzTgR+NCuE7HIyDsNL/IKbIVH6QMCxwAIdxHrAT4WdVvkDrD5ihSmIMgnmbCSidok8N7l9zECN54EccV3LGaABumtO5Y7Um7HRm+gdc6esg3HTkIXW402w92zaeHaqm4EGek/FB24WhIcwSErMhXnnHPoAATNzWD+3RQZo2po+95FE/oZw7QO7hG9lWmCDYpJNim+Ix35ftYs1j1S4hray3z lzx@lzx-t470p", + } + err := guestfs.DeployAuthorizedKeys(dev, "/home/cloudroot", pubkeys, true) + if err != nil { + log.Errorf("Deploy keys error: %v", err) + } +} diff --git a/pkg/hostman/guestfs/windows.go b/pkg/hostman/guestfs/windows.go index 3dacebf6a2..13b9d6670f 100644 --- a/pkg/hostman/guestfs/windows.go +++ b/pkg/hostman/guestfs/windows.go @@ -1,13 +1,9 @@ package guestfs type SWindowsRootFs struct { - *SGuestRootFsDriver + *sGuestRootFsDriver } -func NewWindowsRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - return &SWindowsRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} -} - -func init() { - rootfsDrivers = append(rootfsDrivers, NewWindowsRootFs) -} +//func NewWindowsRootFs(part IDiskPartition) IRootFsDriver { +//return &SWindowsRootFs{sGuestRootFsDriver: newGuestRootFsDriver(part)} +//} diff --git a/pkg/util/fileutils/fileutils.go b/pkg/util/fileutils/fileutils.go new file mode 100644 index 0000000000..8bb0f28fb0 --- /dev/null +++ b/pkg/util/fileutils/fileutils.go @@ -0,0 +1,126 @@ +package fileutils + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/util/regutils" +) + +type Partition struct { + Index int + Bootable bool + Start int + End int + Count int + DiskType string + Fs string + DevName string +} + +func NewPartition(idx int, bootable bool, start int, end int, count int, diskType string, fs string, devName string) Partition { + return Partition{ + Index: idx, + Bootable: bootable, + Start: start, + End: end, + Count: count, + DiskType: diskType, + Fs: fs, + DevName: devName, + } +} + +// ParseDiskPartitions parse command `parted -s /dev/sda -- unit s print` result +func ParseDiskPartitions(dev string, lines []string) ([]Partition, string) { + parts := make([]Partition, 0) + labelPattern := `Partition Table:\s+(?P