diff --git a/cmd/climc/shell/compute/servers.go b/cmd/climc/shell/compute/servers.go index cec69f1838..d7aa648bd7 100644 --- a/cmd/climc/shell/compute/servers.go +++ b/cmd/climc/shell/compute/servers.go @@ -112,6 +112,7 @@ func init() { cmd.Perform("qga-set-password", &options.ServerQgaSetPassword{}) cmd.Perform("qga-command", &options.ServerQgaCommand{}) cmd.Perform("set-password", &options.ServerSetPasswordOptions{}) + cmd.Perform("set-boot-index", &options.ServerSetBootIndexOptions{}) cmd.Get("vnc", new(options.ServerVncOptions)) cmd.Get("desc", new(options.ServerIdOptions)) diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index c451a9e426..880caaf96f 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -824,6 +824,13 @@ type GuestJsonDesc struct { IsDaemon bool `json:"is_daemon"` } +type ServerSetBootIndexInput struct { + // key index, value boot_index + Disks map[string]int8 `json:"disks"` + // key ordinal, value boot_index + Cdroms map[string]int8 `json:"cdroms"` +} + type ServerChangeDiskStorageInput struct { DiskId string `json:"disk_id"` TargetStorageId string `json:"target_storage_id"` diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 759bc4487a..a93c56c65c 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -62,6 +62,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/modules/notify" "yunion.io/x/onecloud/pkg/mcclient/modules/scheduler" "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/onecloud/pkg/util/bitmap" "yunion.io/x/onecloud/pkg/util/httputils" "yunion.io/x/onecloud/pkg/util/logclient" "yunion.io/x/onecloud/pkg/util/rand" @@ -5457,6 +5458,91 @@ func (self *SGuest) startSwitchToClonedDisk(ctx context.Context, userCred mcclie return nil } +func (self *SGuest) PerformSetBootIndex(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerSetBootIndexInput) (jsonutils.JSONObject, error) { + gds, err := self.GetGuestDisks() + if err != nil { + return nil, err + } + diskBootIndexes := map[int8]int8{} + for i := 0; i < len(gds); i++ { + diskBootIndexes[gds[i].Index] = gds[i].BootIndex + } + + gcs, err := self.getCdroms() + if err != nil { + return nil, err + } + cdromBootIndexes := map[int]int8{} + for i := 0; i < len(gcs); i++ { + cdromBootIndexes[gcs[i].Ordinal] = gcs[i].BootIndex + } + + for sDiskIndex, bootIndex := range input.Disks { + iDiskIndex, err := strconv.Atoi(sDiskIndex) + if err != nil { + return nil, httperrors.NewInputParameterError("failed parse disk index %s", sDiskIndex) + } else if iDiskIndex > 127 { + return nil, httperrors.NewInputParameterError("disk inex %s is exceed 127", sDiskIndex) + } + diskIndex := int8(iDiskIndex) + if _, ok := diskBootIndexes[diskIndex]; !ok { + return nil, httperrors.NewBadRequestError("disk has no index %d", diskIndex) + } + diskBootIndexes[diskIndex] = bootIndex + } + for sCdromOrdinal, bootIndex := range input.Cdroms { + cdromOrdinal, err := strconv.Atoi(sCdromOrdinal) + if err != nil { + return nil, httperrors.NewInputParameterError("failed parse cdrom ordinal %s", sCdromOrdinal) + } + if _, ok := cdromBootIndexes[cdromOrdinal]; !ok { + return nil, httperrors.NewBadRequestError("cdrom has no ordinal %d", cdromOrdinal) + } + cdromBootIndexes[cdromOrdinal] = bootIndex + } + bm := bitmap.NewBitMap(128) + for diskIndex, bootIndex := range diskBootIndexes { + if bootIndex < 0 { + continue + } + if bm.Has(int64(bootIndex)) { + return nil, httperrors.NewBadRequestError("disk index %d boot index %d is duplicated", diskIndex, bootIndex) + } else { + bm.Set(int64(bootIndex)) + } + } + for cdromOrdinal, bootIndex := range cdromBootIndexes { + if bootIndex < 0 { + continue + } + if bm.Has(int64(bootIndex)) { + return nil, httperrors.NewBadRequestError("cdrom ordianl %d boot index %d is duplicated", cdromOrdinal, bootIndex) + } else { + bm.Set(int64(bootIndex)) + } + } + + for i := 0; i < len(gds); i++ { + if gds[i].BootIndex != diskBootIndexes[gds[i].Index] { + if err := gds[i].SetBootIndex(diskBootIndexes[gds[i].Index]); err != nil { + log.Errorf("gds[i].SetBootIndex: %s", err) + return nil, err + } + } + } + + for i := 0; i < len(gcs); i++ { + if gcs[i].BootIndex != cdromBootIndexes[gcs[i].Ordinal] { + if err := gcs[i].SetBootIndex(cdromBootIndexes[gcs[i].Ordinal]); err != nil { + log.Errorf("gcs[i].SetBootIndex: %s", err) + return nil, err + } + } + } + + return nil, nil +} + func (self *SGuest) PerformProbeIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { host, err := self.GetHost() if err != nil { diff --git a/pkg/compute/models/guestcdrom.go b/pkg/compute/models/guestcdrom.go index 741bf50f75..e6e7387a3a 100644 --- a/pkg/compute/models/guestcdrom.go +++ b/pkg/compute/models/guestcdrom.go @@ -133,6 +133,14 @@ func (self *SGuestcdrom) GetDetails() string { } } +func (self *SGuestcdrom) SetBootIndex(bootIndex int8) error { + _, err := db.Update(self, func() error { + self.BootIndex = bootIndex + return nil + }) + return err +} + func (self *SGuestcdrom) getJsonDesc() *api.GuestcdromJsonDesc { if len(self.ImageId) > 0 && len(self.Path) > 0 { return &api.GuestcdromJsonDesc{ diff --git a/pkg/compute/models/guestdisks.go b/pkg/compute/models/guestdisks.go index 278555b323..95d8239b14 100644 --- a/pkg/compute/models/guestdisks.go +++ b/pkg/compute/models/guestdisks.go @@ -206,7 +206,8 @@ func (self *SGuestdisk) GetJsonDescAtHost(ctx context.Context, host *SHost) *api } desc.Format = disk.DiskFormat desc.Index = self.Index - desc.BootIndex = &self.BootIndex + bootIndex := self.BootIndex + desc.BootIndex = &bootIndex if len(disk.SnapshotId) > 0 { needMerge := disk.GetMetadata(ctx, "merge_snapshot", nil) @@ -263,6 +264,14 @@ func (self *SGuestdisk) ToDiskConfig() *api.DiskConfig { return conf } +func (self *SGuestdisk) SetBootIndex(bootIndex int8) error { + _, err := db.Update(self, func() error { + self.BootIndex = bootIndex + return nil + }) + return err +} + func (manager *SGuestdiskManager) ListItemFilter( ctx context.Context, q *sqlchemy.SQuery, diff --git a/pkg/mcclient/modules/compute/mod_serverdisks.go b/pkg/mcclient/modules/compute/mod_serverdisks.go index 91ca086cd8..400fae1c0c 100644 --- a/pkg/mcclient/modules/compute/mod_serverdisks.go +++ b/pkg/mcclient/modules/compute/mod_serverdisks.go @@ -29,7 +29,7 @@ func init() { "guestdisks", []string{"Guest_ID", "Guest", "Disk_ID", "Disk", "Disk_size", - "Driver", "Cache_mode", "Index", "BootIndex", "Status", "Disk_type", "Storage_type"}, + "Driver", "Cache_mode", "Index", "Boot_index", "Status", "Disk_type", "Storage_type"}, []string{}, &Servers, &Disks) diff --git a/pkg/mcclient/options/base.go b/pkg/mcclient/options/base.go index 43ebcd10d6..b61cdadc92 100644 --- a/pkg/mcclient/options/base.go +++ b/pkg/mcclient/options/base.go @@ -139,6 +139,8 @@ func optionsStructRvToParams(rv reflect.Value) (*jsonutils.JSONDict, error) { // TODO msg := fmt.Sprintf("do not know what to do with non-anonymous struct field: %s", ft.Name) panic(msg) + case reflect.Map: + p.Set(name, jsonutils.Marshal(f.Interface())) case reflect.Slice, reflect.Array: l := f.Len() for i := 0; i < l; i++ { diff --git a/pkg/mcclient/options/compute/servers.go b/pkg/mcclient/options/compute/servers.go index 473e6aa745..8ac470f4b8 100644 --- a/pkg/mcclient/options/compute/servers.go +++ b/pkg/mcclient/options/compute/servers.go @@ -835,6 +835,29 @@ func (o *ServerSetPasswordOptions) Params() (jsonutils.JSONObject, error) { return options.StructToParams(o) } +type ServerSetBootIndexOptions struct { + ServerIdOptions + Disks map[string]int8 `help:"Disk index and boot index" json:"disks"` + Cdroms map[string]int8 `help:"Cdrom ordinal and boot index" json:"cdroms"` +} + +func (o *ServerSetBootIndexOptions) Params() (jsonutils.JSONObject, error) { + for k, _ := range o.Disks { + if i, e := strconv.Atoi(k); e != nil { + return nil, e + } else if i > 127 { + return nil, fmt.Errorf("disk index grate than 127") + } + } + for k, _ := range o.Cdroms { + if _, e := strconv.Atoi(k); e != nil { + return nil, e + } + } + + return options.StructToParams(o) +} + type ServerSaveImageOptions struct { ServerIdOptions IMAGE string `help:"Image name" json:"name"`