diff --git a/pkg/apis/compute/api.go b/pkg/apis/compute/api.go index 5c121a02bc..a02eb6f920 100644 --- a/pkg/apis/compute/api.go +++ b/pkg/apis/compute/api.go @@ -308,6 +308,21 @@ type BaremetalDiskConfig struct { Direct *bool `json:"direct,omitempty"` } +type RootDiskMatcherSizeMBRange struct { + Start int64 `json:"start"` + End int64 `json:"end"` +} + +const ( + BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER = "baremetal_root_disk_matcher" +) + +type BaremetalRootDiskMatcher struct { + Device string `json:"device"` + SizeMB int64 `json:"size_mb"` + SizeMBRange *RootDiskMatcherSizeMBRange `json:"size_mb_range"` +} + type ServerConfigs struct { // 调度使用指定的云账号 PreferManager string `json:"prefer_manager_id"` @@ -400,6 +415,9 @@ type ServerConfigs struct { // 裸金属磁盘配置列表 BaremetalDiskConfigs []*BaremetalDiskConfig `json:"baremetal_disk_configs"` + // 裸金属系统盘匹配器 + BaremetalRootDiskMatcher *BaremetalRootDiskMatcher `json:"baremetal_root_disk_matcher"` + // 主机组列表, 参数可以是主机组名称或ID,建议使用ID InstanceGroupIds []string `json:"groups"` diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index a75cae204c..471e41fcb8 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -2546,6 +2546,34 @@ func (s *SBaremetalServer) GetRootTemplateId() string { return id } +func (s *SBaremetalServer) GetMetadata() (*jsonutils.JSONDict, error) { + metadata, err := s.desc.Get("metadata") + if err != nil { + return nil, errors.Wrap(err, "get desc.metadata") + } + return metadata.(*jsonutils.JSONDict), nil +} + +func (s *SBaremetalServer) GetRootDiskMatcher() (*api.BaremetalRootDiskMatcher, error) { + metadata, err := s.GetMetadata() + if err != nil { + return nil, errors.Wrap(err, "get metadata") + } + if !metadata.Contains(api.BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER) { + return nil, errors.Wrapf(errors.ErrNotFound, "not found %s in metadata", api.BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER) + } + jStr, _ := metadata.GetString(api.BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER) + jObj, err := jsonutils.ParseString(jStr) + if err != nil { + return nil, errors.Wrapf(err, "parse json string: %s", jStr) + } + matcher := new(api.BaremetalRootDiskMatcher) + if err := jObj.Unmarshal(matcher); err != nil { + return nil, errors.Wrapf(err, "unmarshal to matcher") + } + return matcher, nil +} + func (s *SBaremetalServer) GetDiskConfig() ([]*api.BaremetalDiskConfig, error) { layouts := make([]baremetal.Layout, 0) err := s.desc.Unmarshal(&layouts, "disk_config") @@ -2598,7 +2626,11 @@ func (s *SBaremetalServer) NewConfigedSSHPartitionTool(term *ssh.Client) (*diskt } } - tool, err := disktool.NewSSHPartitionTool(term, layouts) + matcher, err := s.GetRootDiskMatcher() + if errors.Cause(err) != errors.ErrNotFound { + log.Errorf("GetRootDiskMatcher: %v", err) + } + tool, err := disktool.NewSSHPartitionTool(term, layouts, matcher) if err != nil { return nil, errors.Wrap(err, "NewSSHPartitionTool") } @@ -2651,7 +2683,11 @@ func (s *SBaremetalServer) DoDiskConfig(term *ssh.Client) (*disktool.SSHPartitio } } - tool, err := disktool.NewSSHPartitionTool(term, layouts) + matcher, err := s.GetRootDiskMatcher() + if errors.Cause(err) != errors.ErrNotFound { + log.Errorf("GetRootDiskMatcher: %v", err) + } + tool, err := disktool.NewSSHPartitionTool(term, layouts, matcher) if err != nil { return nil, errors.Wrap(err, "NewSSHPartitionTool") } diff --git a/pkg/baremetal/utils/disktool/disktool.go b/pkg/baremetal/utils/disktool/disktool.go index 16ea322779..930a13839b 100644 --- a/pkg/baremetal/utils/disktool/disktool.go +++ b/pkg/baremetal/utils/disktool/disktool.go @@ -664,7 +664,7 @@ func (tool *PartitionTool) parseLsDisk(lines []string, driver string) { } } -func (tool *PartitionTool) FetchDiskConfs(diskConfs []baremetal.DiskConfiguration) *PartitionTool { +func (tool *PartitionTool) FetchDiskConfs(diskConfs []baremetal.DiskConfiguration, rootMatcher *api.BaremetalRootDiskMatcher) *PartitionTool { for _, d := range diskConfs { disk := newDiskPartitions(d.Driver, d.Adapter, d.RaidConfig, d.Size, d.Block, d.DiskType, tool) tool.disks = append(tool.disks, disk) @@ -681,9 +681,56 @@ func (tool *PartitionTool) FetchDiskConfs(diskConfs []baremetal.DiskConfiguratio } tool.diskTable[key] = append(tool.diskTable[key], disk) } + // reorder tool.disks + if rootMatcher != nil { + tool.reorderRootDisk(rootMatcher) + } return tool } +func (tool *PartitionTool) reorderRootDisk(matcher *api.BaremetalRootDiskMatcher) { + var rootDiskIdx = 0 + + isDiskMatch := func(disk *DiskPartitions, matcher *api.BaremetalRootDiskMatcher) bool { + if matcher.Device != "" { + if disk.dev == matcher.Device { + return true + } + if disk.devName == matcher.Device { + return true + } + } + if matcher.SizeMB > 0 { + if disk.sizeMB == matcher.SizeMB { + return true + } + } + if matcher.SizeMBRange != nil { + if disk.sizeMB >= matcher.SizeMBRange.Start && disk.sizeMB <= matcher.SizeMBRange.End { + return true + } + } + return false + } + + for idx, disk := range tool.disks { + if isDiskMatch(disk, matcher) { + rootDiskIdx = idx + break + } + } + log.Infof("Select %d as root disk", rootDiskIdx) + newDisks := make([]*DiskPartitions, 0) + newDisks = append(newDisks, tool.disks[rootDiskIdx]) + for idx := range tool.disks { + if idx == rootDiskIdx { + continue + } + newDisks = append(newDisks, tool.disks[idx]) + } + tool.disks = newDisks +} + func (tool *PartitionTool) IsAllDisksReady() bool { for idx, d := range tool.disks { if !d.IsReady() { @@ -788,9 +835,9 @@ func newSSHPartitionTool(term *ssh.Client) *SSHPartitionTool { return tool } -func NewSSHPartitionTool(term *ssh.Client, layouts []baremetal.Layout) (*SSHPartitionTool, error) { +func NewSSHPartitionTool(term *ssh.Client, layouts []baremetal.Layout, rootMatcher *api.BaremetalRootDiskMatcher) (*SSHPartitionTool, error) { tool := newSSHPartitionTool(term) - tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts)) + tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts), rootMatcher) if err := tool.RetrieveDiskInfo(); err != nil { return nil, errors.Wrapf(err, "RetrieveDiskInfo") } diff --git a/pkg/cloudcommon/cmdline/parser.go b/pkg/cloudcommon/cmdline/parser.go index 07cdb28a02..bb134c3681 100644 --- a/pkg/cloudcommon/cmdline/parser.go +++ b/pkg/cloudcommon/cmdline/parser.go @@ -369,6 +369,47 @@ func ParseIsolatedDevice(desc string, idx int) (*compute.IsolatedDeviceConfig, e return dev, nil } +func ParseBaremetalRootDiskMatcher(line string) (*compute.BaremetalRootDiskMatcher, error) { + ret := new(compute.BaremetalRootDiskMatcher) + for _, seg := range strings.Split(line, ",") { + info := strings.Split(seg, "=") + if len(info) != 2 { + return nil, errors.Errorf("invalid option %s", seg) + } + key := info[0] + val := info[1] + switch key { + case "size": + sizeMB, err := fileutils.GetSizeMb(val, 'M', 1024) + if err != nil { + return nil, errors.Wrapf(err, "parse size %s", val) + } + ret.SizeMB = int64(sizeMB) + case "device", "dev": + ret.Device = val + case "size_start": + sizeMB, err := fileutils.GetSizeMb(val, 'M', 1024) + if err != nil { + return nil, errors.Wrapf(err, "parse size_start %s", val) + } + if ret.SizeMBRange == nil { + ret.SizeMBRange = new(compute.RootDiskMatcherSizeMBRange) + } + ret.SizeMBRange.Start = int64(sizeMB) + case "size_end": + sizeMB, err := fileutils.GetSizeMb(val, 'M', 1024) + if err != nil { + return nil, errors.Wrapf(err, "parse size_end %s", val) + } + if ret.SizeMBRange == nil { + ret.SizeMBRange = new(compute.RootDiskMatcherSizeMBRange) + } + ret.SizeMBRange.End = int64(sizeMB) + } + } + return ret, nil +} + func ParseBaremetalDiskConfig(desc string) (*compute.BaremetalDiskConfig, error) { bdc := new(compute.BaremetalDiskConfig) bdc.Type = compute.DISK_TYPE_HYBRID diff --git a/pkg/cloudcommon/cmdline/parser_test.go b/pkg/cloudcommon/cmdline/parser_test.go index 8b7d2e8785..41c2b3e614 100644 --- a/pkg/cloudcommon/cmdline/parser_test.go +++ b/pkg/cloudcommon/cmdline/parser_test.go @@ -195,3 +195,44 @@ func TestFetchDiskConfigsByJSON(t *testing.T) { }) } } + +func TestParseBaremetalRootDiskMatcher(t *testing.T) { + tests := []struct { + args string + want *compute.BaremetalRootDiskMatcher + wantErr bool + }{ + { + args: "size=100G", + want: &compute.BaremetalRootDiskMatcher{SizeMB: 102400}, + }, + { + args: "device=/dev/sda", + want: &compute.BaremetalRootDiskMatcher{Device: "/dev/sda"}, + }, + { + args: "size_end=100G", + want: &compute.BaremetalRootDiskMatcher{SizeMBRange: &compute.RootDiskMatcherSizeMBRange{End: 102400}}, + }, + { + args: "size_end=100G,size_start=50G", + want: &compute.BaremetalRootDiskMatcher{ + SizeMBRange: &compute.RootDiskMatcherSizeMBRange{ + Start: 51200, + End: 102400, + }}, + }, + } + for _, tt := range tests { + t.Run(tt.args, func(t *testing.T) { + got, err := ParseBaremetalRootDiskMatcher(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("ParseBaremetalRootDiskMatcher() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseBaremetalRootDiskMatcher() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/compute/baremetal/diskconfig.go b/pkg/compute/baremetal/diskconfig.go index 83c4b41616..9897ae599b 100644 --- a/pkg/compute/baremetal/diskconfig.go +++ b/pkg/compute/baremetal/diskconfig.go @@ -23,6 +23,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/sets" "yunion.io/x/pkg/utils" @@ -683,3 +684,12 @@ func ValidateDiskConfigs(confs []*api.BaremetalDiskConfig) error { } return nil } + +func ValidateRootDiskMatcher(input *api.BaremetalRootDiskMatcher) error { + if input.SizeMBRange != nil { + if input.SizeMBRange.Start > input.SizeMBRange.End { + return errors.Errorf("size_mb_range.start %d is large than size_mb_range.end %d", input.SizeMBRange.Start, input.SizeMBRange.End) + } + } + return nil +} diff --git a/pkg/compute/guestdrivers/baremetals.go b/pkg/compute/guestdrivers/baremetals.go index 95bb8d173b..b777532232 100644 --- a/pkg/compute/guestdrivers/baremetals.go +++ b/pkg/compute/guestdrivers/baremetals.go @@ -412,6 +412,11 @@ func (self *SBaremetalGuestDriver) ValidateCreateData(ctx context.Context, userC return nil, httperrors.NewInputParameterError("Invalid raid config: %v", err) } } + if input.BaremetalRootDiskMatcher != nil { + if err := baremetal.ValidateRootDiskMatcher(input.BaremetalRootDiskMatcher); err != nil { + return nil, httperrors.NewInputParameterError("Invalid root disk matcher: %v", err) + } + } //if len(input.Disks) <= 0 { // return nil, httperrors.NewInputParameterError("Root disk must be present") //} diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 7b0f2d6352..834f68636a 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -2331,6 +2331,10 @@ func (guest *SGuest) PostCreate(ctx context.Context, userCred mcclient.TokenCred if jsonutils.QueryBoolean(data, imageapi.IMAGE_DISABLE_USB_KBD, false) { guest.SetMetadata(ctx, imageapi.IMAGE_DISABLE_USB_KBD, "true", userCred) } + matcherJson, _ := data.Get(api.BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER) + if matcherJson != nil { + guest.SetMetadata(ctx, api.BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER, matcherJson, userCred) + } userData, _ := data.GetString("user_data") if len(userData) > 0 { diff --git a/pkg/mcclient/options/compute/servers.go b/pkg/mcclient/options/compute/servers.go index 41beb1ed35..2bbbad5132 100644 --- a/pkg/mcclient/options/compute/servers.go +++ b/pkg/mcclient/options/compute/servers.go @@ -277,14 +277,15 @@ type ServerConfigs struct { --disk 'size=500M' --disk 'snpahost_id=1ceb8c6d-6571-451d-8957-4bd3a871af85' " nargs:"+"` - DiskSchedtag []string `help:"Disk schedtag description, e.g. '0::'"` - Net []string `help:"Network descriptions" metavar:"NETWORK"` - NetSchedtag []string `help:"Network schedtag description, e.g. '0::'"` - IsolatedDevice []string `help:"Isolated device model or ID" metavar:"ISOLATED_DEVICE"` - RaidConfig []string `help:"Baremetal raid config" json:"-"` - Project string `help:"'Owner project ID or Name" json:"tenant"` - User string `help:"Owner user ID or Name"` - Count int `help:"Create multiple simultaneously" default:"1"` + DiskSchedtag []string `help:"Disk schedtag description, e.g. '0::'"` + Net []string `help:"Network descriptions" metavar:"NETWORK"` + NetSchedtag []string `help:"Network schedtag description, e.g. '0::'"` + IsolatedDevice []string `help:"Isolated device model or ID" metavar:"ISOLATED_DEVICE"` + RaidConfig []string `help:"Baremetal raid config" json:"-"` + RootDiskMatcher string `help:"Baremetal root disk matcher, e.g. 'device=/dev/sdb' 'size=900G' 'size_start=800G,size_end=900G'" json:"-"` + Project string `help:"'Owner project ID or Name" json:"tenant"` + User string `help:"Owner user ID or Name"` + Count int `help:"Create multiple simultaneously" default:"1"` } func (o ServerConfigs) Data() (*computeapi.ServerConfigs, error) { @@ -356,6 +357,13 @@ func (o ServerConfigs) Data() (*computeapi.ServerConfigs, error) { data.BaremetalDiskConfigs = append(data.BaremetalDiskConfigs, raidConf) } } + if len(o.RootDiskMatcher) > 0 { + matcher, err := cmdline.ParseBaremetalRootDiskMatcher(o.RootDiskMatcher) + if err != nil { + return nil, err + } + data.BaremetalRootDiskMatcher = matcher + } for _, tag := range o.Schedtag { schedtag, err := cmdline.ParseSchedtagConfig(tag) if err != nil {