diff --git a/cmd/climc/shell/servers.go b/cmd/climc/shell/servers.go index 8790256b7c..961525c62e 100644 --- a/cmd/climc/shell/servers.go +++ b/cmd/climc/shell/servers.go @@ -2,14 +2,17 @@ package shell import ( "fmt" - "io/ioutil" + "os" + "path/filepath" "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" - "yunion.io/x/pkg/util/regutils" ) func init() { @@ -660,4 +663,59 @@ func init() { return nil }) + type ServerImportOptions struct { + LOCATION string `help:"Server desc file location, should be desc file or workspace directory"` + HOST string `help:"Host id or name for this server"` + } + R(&ServerImportOptions{}, "server-import", "Import a server by desc file", func(s *mcclient.ClientSession, args *ServerImportOptions) error { + var descFiles []string + err := filepath.Walk(args.LOCATION, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + if info.Name() == "desc" { + descFiles = append(descFiles, path) + } + return nil + }) + if err != nil { + return fmt.Errorf("Find desc files: %v", err) + } + + importF := func(desc string) error { + ret, err := ioutil.ReadFile(desc) + if err != nil { + return fmt.Errorf("Read file %s: %v", desc, err) + } + jsonObj, err := jsonutils.Parse(ret) + if err != nil { + return fmt.Errorf("Parse %s to json: %v", string(ret), err) + } + params := jsonObj.(*jsonutils.JSONDict) + disks, err := params.GetArray("disks") + if err != nil || len(disks) == 0 { + return fmt.Errorf("Desc %s not have disks, skip it", desc) + } + params.Add(jsonutils.NewString(args.HOST), "host_id") + // project may not exists + params.Remove("tenant") + params.Remove("tenant_id") + _, err = modules.Servers.PerformClassAction(s, "import", params) + if err != nil { + return err + } + //printObject(result) + return nil + } + + for _, descFile := range descFiles { + if err := importF(descFile); err != nil { + log.Errorf("Import %s error: %v", descFile, err) + } + } + return nil + }) } diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 59e5b3882c..8099a22623 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -1105,6 +1105,8 @@ type SDiskConfig struct { Backend string // stroageType Medium string ImageProperties map[string]string + + DiskId string // import only } func parseDiskInfo(ctx context.Context, userCred mcclient.TokenCredential, info jsonutils.JSONObject) (*SDiskConfig, error) { @@ -1263,6 +1265,9 @@ func (self *SDisk) fetchDiskInfo(diskConfig *SDiskConfig) { } self.Nonpersistent = false } + if len(diskConfig.DiskId) > 0 && utils.IsMatchUUID(diskConfig.DiskId) { + self.Id = diskConfig.DiskId + } self.DiskFormat = diskConfig.Format self.DiskSize = diskConfig.SizeMb } diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 67ec596ad8..05abf42cb9 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -13,6 +13,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/fileutils" "yunion.io/x/pkg/util/regutils" "yunion.io/x/pkg/utils" @@ -2326,3 +2327,186 @@ func (self *SGuest) PerformStreamDisksComplete(ctx context.Context, userCred mcc } return nil, nil } + +func (man *SGuestManager) AllowPerformImport(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsAdminAllowClassPerform(userCred, man, "import") +} + +type SImportNic struct { + Index int `json:"index"` + Bridge string `json:"bridge"` + Domain string `json:"domain"` + Ip string `json:"ip"` + Vlan int `json:"vlan"` + Driver string `json:"driver"` + 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"` + Dns string `json:"dns"` + Net string `json:"net"` + Interface string `json:"interface"` + Gateway string `json:"gateway"` + Ifname string `json:"ifname"` +} + +func (n SImportNic) ToNetConfig(net *SNetwork) *SNetworkConfig { + return &SNetworkConfig{ + Network: net.Id, + Wire: net.WireId, + Address: n.Ip, + Mac: n.Mac, + Driver: n.Driver, + BwLimit: n.BandWidth, + } +} + +type SImportDisk struct { + Index int `json:"index"` + DiskId string `json:"disk_id"` + Driver string `json:"driver"` + CacheMode string `json:"cache_mode"` + AioMode string `json:"aio_mode"` + SizeMb int `json:"size"` + Format string `json:"format"` + Fs string `json:"fs"` + Mountpoint string `json:"mountpoint"` + Dev string `json:"dev"` + TemplateId string `json:"template_id"` +} + +func (d SImportDisk) ToDiskConfig() *SDiskConfig { + ret := &SDiskConfig{ + SizeMb: d.SizeMb, + ImageId: d.TemplateId, + Format: d.Format, + Driver: d.Driver, + DiskId: d.DiskId, + Cache: d.CacheMode, + } + if len(d.Mountpoint) > 0 { + ret.Mountpoint = d.Mountpoint + } + if len(d.Fs) > 0 { + ret.Fs = d.Fs + } + return ret +} + +type SImportGuestDesc struct { + Id string `json:"uuid"` + Name string `json:"name"` + Nics []SImportNic `json:"nics"` + Disks []SImportDisk `json:"disks"` + Metadata map[string]string `json:"metadata"` + MemSizeMb int `json:"mem"` + Cpu int `json:"cpu"` + TemplateId string `json:"template_id"` + ImagePath string `json:"image_path"` + Vdi string `json:"vdi"` + Hypervisor string `json:"hypervisor"` + HostId string `json:"host"` + BootOrder string `json:"boot_order"` + IsSystem bool `json:"is_system"` + Description string `json:"description"` +} + +func (man *SGuestManager) PerformImport(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + desc := SImportGuestDesc{} + if err := data.Unmarshal(&desc); err != nil { + return nil, httperrors.NewInputParameterError("Invalid desc: %s", data.String()) + } + if len(desc.Id) == 0 { + return nil, httperrors.NewInputParameterError("Server Id is empty") + } + if len(desc.Name) == 0 { + return nil, httperrors.NewInputParameterError("Server Name is empty") + } + if obj, _ := man.FetchByIdOrName(userCred, desc.Id); obj != nil { + return nil, httperrors.NewInputParameterError("Server %s already exists", desc.Id) + } + if err := db.NewNameValidator(man, userCred.GetProjectId(), desc.Name); err != nil { + return nil, err + } + if hostObj, _ := HostManager.FetchByIdOrName(userCred, desc.HostId); hostObj == nil { + return nil, httperrors.NewNotFoundError("Host %s not found", desc.HostId) + } else { + desc.HostId = hostObj.GetId() + } + // 1. create import guest on host + gst, err := man.createImportGuest(ctx, userCred, desc) + if err != nil { + return nil, err + } + // 2. import networks + if err := gst.importNics(ctx, userCred, desc.Nics); err != nil { + return nil, err + } + // 3. import disks + if err := gst.importDisks(ctx, userCred, desc.Disks); err != nil { + return nil, err + } + // 4. set metadata + for k, v := range desc.Metadata { + gst.SetMetadata(ctx, k, v, userCred) + } + return jsonutils.Marshal(gst), nil +} + +func (man *SGuestManager) createImportGuest(ctx context.Context, userCred mcclient.TokenCredential, desc SImportGuestDesc) (*SGuest, error) { + model, err := db.NewModelObject(man) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + gst, ok := model.(*SGuest) + if !ok { + return nil, httperrors.NewGeneralError(fmt.Errorf("Can't convert %#v to *SGuest model", model)) + } + gst.ProjectId = userCred.GetProjectId() + gst.IsSystem = desc.IsSystem + gst.Id = desc.Id + gst.Name = desc.Name + gst.HostId = desc.HostId + gst.Status = "import" + gst.Hypervisor = desc.Hypervisor + gst.VmemSize = desc.MemSizeMb + gst.VcpuCount = int8(desc.Cpu) + gst.BootOrder = desc.BootOrder + gst.Description = desc.Description + err = man.TableSpec().Insert(gst) + return gst, err +} + +func (self *SGuest) importNics(ctx context.Context, userCred mcclient.TokenCredential, nics []SImportNic) error { + if len(nics) == 0 { + return httperrors.NewInputParameterError("Empty import nics") + } + for _, nic := range nics { + net, err := NetworkManager.GetNetworkOfIP(nic.Ip, "", tristate.None) + if err != nil { + return httperrors.NewNotFoundError("Not found network by ip %s", nic.Ip) + } + err = self.attach2NetworkDesc(ctx, userCred, self.GetHost(), nic.ToNetConfig(net), nil) + if err != nil { + return err + } + } + return nil +} + +func (self *SGuest) importDisks(ctx context.Context, userCred mcclient.TokenCredential, disks []SImportDisk) error { + if len(disks) == 0 { + return httperrors.NewInputParameterError("Empty import disks") + } + for _, disk := range disks { + disk, err := self.createDiskOnHost(ctx, userCred, self.GetHost(), disk.ToDiskConfig(), nil, true) + if err != nil { + return err + } + disk.SetStatus(userCred, DISK_READY, "") + } + return nil +} diff --git a/pkg/compute/models/isolated_devices.go b/pkg/compute/models/isolated_devices.go index eb4778ffbc..0543a4f980 100644 --- a/pkg/compute/models/isolated_devices.go +++ b/pkg/compute/models/isolated_devices.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -95,6 +96,19 @@ func (manager *SIsolatedDeviceManager) AllowCreateItem(ctx context.Context, user return db.IsAdminAllowCreate(userCred, manager) } +func (manager *SIsolatedDeviceManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + hostId, _ := data.GetString("host_id") + host := HostManager.FetchHostById(hostId) + if host == nil { + return nil, httperrors.NewNotFoundError("Host %s not found", hostId) + } + if name, _ := data.GetString("name"); len(name) == 0 { + name = fmt.Sprintf("dev_%s_%d", host.GetName(), time.Now().UnixNano()) + data.Set("name", jsonutils.NewString(name)) + } + return manager.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) +} + func (self *SIsolatedDevice) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { return db.IsAdminAllowUpdate(userCred, self) } diff --git a/pkg/scheduler/algorithm/predicates/guest/network_predicate.go b/pkg/scheduler/algorithm/predicates/guest/network_predicate.go index ab3b888cc1..c90f700f4e 100644 --- a/pkg/scheduler/algorithm/predicates/guest/network_predicate.go +++ b/pkg/scheduler/algorithm/predicates/guest/network_predicate.go @@ -145,10 +145,10 @@ func (p *NetworkPredicate) Execute(u *core.Unit, c core.Candidater) (bool, []cor errMsgs := make([]string, 0) for _, net := range hc.Networks { - if !isMatchServerType(net) { + /*if !isMatchServerType(net) { errMsgs = append(errMsgs, fmt.Sprintf("%v(%v): server type not matched", net.Name, net.ID)) continue - } + }*/ if !(n.Idx == net.ID || n.Idx == net.Name) { errMsgs = append(errMsgs, fmt.Sprintf("%v(%v): id/name not matched", net.Name, net.ID)) } else if !(net.IsPublic || net.TenantID == d.OwnerTenantID) {