diff --git a/pkg/multicloud/aliyun/host.go b/pkg/multicloud/aliyun/host.go index 669f46c3bb..2af5ceca54 100644 --- a/pkg/multicloud/aliyun/host.go +++ b/pkg/multicloud/aliyun/host.go @@ -179,7 +179,7 @@ func (self *SHost) GetInstanceById(instanceId string) (*SInstance, error) { } func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { - vmId, err := self._createVM(desc.Name, desc.ExternalImageId, desc.SysDisk, desc.Cpu, desc.MemoryMB, desc.InstanceType, desc.ExternalNetworkId, desc.IpAddr, desc.Description, desc.Password, desc.DataDisks, desc.PublicKey, desc.ExternalSecgroupId, desc.UserData, desc.BillingCycle, desc.ProjectId) + vmId, err := self._createVM(desc.Name, desc.ExternalImageId, desc.SysDisk, desc.Cpu, desc.MemoryMB, desc.InstanceType, desc.ExternalNetworkId, desc.IpAddr, desc.Description, desc.Password, desc.DataDisks, desc.PublicKey, desc.ExternalSecgroupId, desc.UserData, desc.BillingCycle, desc.ProjectId, desc.OsType) if err != nil { return nil, err } @@ -194,7 +194,7 @@ func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudpr func (self *SHost) _createVM(name string, imgId string, sysDisk cloudprovider.SDiskInfo, cpu int, memMB int, instanceType string, vswitchId string, ipAddr string, desc string, passwd string, dataDisks []cloudprovider.SDiskInfo, publicKey string, secgroupId string, - userData string, bc *billing.SBillingCycle, projectId string) (string, error) { + userData string, bc *billing.SBillingCycle, projectId, osType string) (string, error) { net := self.zone.getNetworkById(vswitchId) if net == nil { return "", fmt.Errorf("invalid switch ID %s", vswitchId) @@ -249,7 +249,7 @@ func (self *SHost) _createVM(name string, imgId string, sysDisk cloudprovider.SD if len(instanceType) > 0 { log.Debugf("Try instancetype : %s", instanceType) - vmId, err := self.zone.region.CreateInstance(name, imgId, instanceType, secgroupId, self.zone.ZoneId, desc, passwd, disks, vswitchId, ipAddr, keypair, userData, bc, projectId) + vmId, err := self.zone.region.CreateInstance(name, imgId, instanceType, secgroupId, self.zone.ZoneId, desc, passwd, disks, vswitchId, ipAddr, keypair, userData, bc, projectId, osType) if err != nil { log.Errorf("Failed for %s: %s", instanceType, err) return "", fmt.Errorf("Failed to create specification %s.%s", instanceType, err.Error()) @@ -269,7 +269,7 @@ func (self *SHost) _createVM(name string, imgId string, sysDisk cloudprovider.SD for _, instType := range instanceTypes { instanceTypeId := instType.InstanceTypeId log.Debugf("Try instancetype : %s", instanceTypeId) - vmId, err = self.zone.region.CreateInstance(name, imgId, instanceTypeId, secgroupId, self.zone.ZoneId, desc, passwd, disks, vswitchId, ipAddr, keypair, userData, bc, projectId) + vmId, err = self.zone.region.CreateInstance(name, imgId, instanceTypeId, secgroupId, self.zone.ZoneId, desc, passwd, disks, vswitchId, ipAddr, keypair, userData, bc, projectId, osType) if err != nil { log.Errorf("Failed for %s: %s", instanceTypeId, err) } else { diff --git a/pkg/multicloud/aliyun/instance.go b/pkg/multicloud/aliyun/instance.go index de0ea191a7..35687f193d 100644 --- a/pkg/multicloud/aliyun/instance.go +++ b/pkg/multicloud/aliyun/instance.go @@ -32,6 +32,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud" "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/onecloud/pkg/util/stringutils2" ) const ( @@ -480,7 +481,7 @@ func (self *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) { } func (self *SInstance) UpdateVM(ctx context.Context, name string) error { - return self.host.zone.region.UpdateVM(self.InstanceId, name) + return self.host.zone.region.UpdateVM(self.InstanceId, name, self.OSType) } func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { @@ -548,7 +549,7 @@ func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) { func (self *SRegion) CreateInstance(name string, imageId string, instanceType string, securityGroupId string, zoneId string, desc string, passwd string, disks []SDisk, vSwitchId string, ipAddr string, - keypair string, userData string, bc *billing.SBillingCycle, projectId string) (string, error) { + keypair string, userData string, bc *billing.SBillingCycle, projectId, osType string) (string, error) { params := make(map[string]string) params["RegionId"] = self.RegionId params["ImageId"] = imageId @@ -560,7 +561,7 @@ func (self *SRegion) CreateInstance(name string, imageId string, instanceType st params["InternetChargeType"] = "PayByTraffic" params["InternetMaxBandwidthIn"] = "200" params["InternetMaxBandwidthOut"] = "100" - params["HostName"] = name + params["HostName"] = stringutils2.GenerateHostName(name, osType) if len(passwd) > 0 { params["Password"] = passwd } else { @@ -776,7 +777,7 @@ func (self *SRegion) DeployVM(instanceId string, name string, password string, k if len(name) > 0 && instance.InstanceName != name { params["InstanceName"] = name - params["HostName"] = name + params["HostName"] = stringutils2.GenerateHostName(name, instance.OSType) } if len(description) > 0 && instance.Description != description { @@ -808,14 +809,14 @@ func (self *SInstance) DeleteVM(ctx context.Context) error { return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes } -func (self *SRegion) UpdateVM(instanceId string, hostname string) error { +func (self *SRegion) UpdateVM(instanceId string, name, osType string) error { /* api: ModifyInstanceAttribute https://help.aliyun.com/document_detail/25503.html?spm=a2c4g.11186623.4.1.DrgpjW */ params := make(map[string]string) - params["HostName"] = hostname - params["InstanceName"] = hostname + params["HostName"] = stringutils2.GenerateHostName(name, osType) + params["InstanceName"] = name return self.modifyInstanceAttribute(instanceId, params) } diff --git a/pkg/multicloud/qcloud/host.go b/pkg/multicloud/qcloud/host.go index f1edc7a317..0767ebf913 100644 --- a/pkg/multicloud/qcloud/host.go +++ b/pkg/multicloud/qcloud/host.go @@ -64,7 +64,7 @@ func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudpr desc.InstanceType, desc.ExternalNetworkId, desc.IpAddr, desc.Description, desc.Password, desc.DataDisks, desc.PublicKey, desc.ExternalSecgroupId, - desc.UserData, desc.BillingCycle, desc.ProjectId, desc.PublicIpBw, desc.PublicIpChargeType, desc.Tags) + desc.UserData, desc.BillingCycle, desc.ProjectId, desc.PublicIpBw, desc.PublicIpChargeType, desc.Tags, desc.OsType) if err != nil { return nil, err } @@ -79,7 +79,7 @@ func (self *SHost) _createVM(name string, imgId string, sysDisk cloudprovider.SD networkId string, ipAddr string, desc string, passwd string, diskSizes []cloudprovider.SDiskInfo, publicKey string, secgroupId string, userData string, bc *billing.SBillingCycle, projectId string, publicIpBw int, publicIpChargeType cloudprovider.TElasticipChargeType, - tags map[string]string, + tags map[string]string, osType string, ) (string, error) { net := self.zone.getNetworkById(networkId) if net == nil { @@ -130,7 +130,7 @@ func (self *SHost) _createVM(name string, imgId string, sysDisk cloudprovider.SD if len(instanceType) > 0 { log.Debugf("Try instancetype : %s", instanceType) - vmId, err := self.zone.region.CreateInstance(name, imgId, instanceType, secgroupId, self.zone.Zone, desc, passwd, disks, networkId, ipAddr, keypair, userData, bc, projectId, publicIpBw, publicIpChargeType, tags) + vmId, err := self.zone.region.CreateInstance(name, imgId, instanceType, secgroupId, self.zone.Zone, desc, passwd, disks, networkId, ipAddr, keypair, userData, bc, projectId, publicIpBw, publicIpChargeType, tags, osType) if err != nil { return "", errors.Wrapf(err, "Failed to create specification %s", instanceType) } @@ -149,7 +149,7 @@ func (self *SHost) _createVM(name string, imgId string, sysDisk cloudprovider.SD for _, instType := range instanceTypes { instanceTypeId := instType.InstanceType log.Debugf("Try instancetype : %s", instanceTypeId) - vmId, err = self.zone.region.CreateInstance(name, imgId, instanceTypeId, secgroupId, self.zone.Zone, desc, passwd, disks, networkId, ipAddr, keypair, userData, bc, projectId, publicIpBw, publicIpChargeType, tags) + vmId, err = self.zone.region.CreateInstance(name, imgId, instanceTypeId, secgroupId, self.zone.Zone, desc, passwd, disks, networkId, ipAddr, keypair, userData, bc, projectId, publicIpBw, publicIpChargeType, tags, osType) if err != nil { log.Errorf("Failed for %s: %s", instanceTypeId, err) } else { diff --git a/pkg/multicloud/qcloud/instance.go b/pkg/multicloud/qcloud/instance.go index 23983605b1..38317b075e 100644 --- a/pkg/multicloud/qcloud/instance.go +++ b/pkg/multicloud/qcloud/instance.go @@ -31,6 +31,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud" "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/onecloud/pkg/util/stringutils2" ) const ( @@ -419,7 +420,7 @@ func (self *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) { } func (self *SInstance) UpdateVM(ctx context.Context, name string) error { - return self.host.zone.region.UpdateVM(self.InstanceId, name) + return self.host.zone.region.UpdateVM(self.InstanceId, name, self.GetOSType()) } func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { @@ -496,7 +497,7 @@ func (self *SRegion) CreateInstance(name string, imageId string, instanceType st zoneId string, desc string, passwd string, disks []SDisk, networkId string, ipAddr string, keypair string, userData string, bc *billing.SBillingCycle, projectId string, publicIpBw int, publicIpChargeType cloudprovider.TElasticipChargeType, - tags map[string]string, + tags map[string]string, osType string, ) (string, error) { params := make(map[string]string) params["Region"] = self.Region @@ -508,7 +509,7 @@ func (self *SRegion) CreateInstance(name string, imageId string, instanceType st params["Placement.ProjectId"] = projectId } params["InstanceName"] = name - params["HostName"] = name + params["HostName"] = stringutils2.GenerateHostName(name, osType) bandwidth := publicIpBw if publicIpBw == 0 { @@ -717,9 +718,9 @@ func (self *SInstance) DeleteVM(ctx context.Context) error { return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes } -func (self *SRegion) UpdateVM(instanceId string, hostname string) error { +func (self *SRegion) UpdateVM(instanceId string, name, osType string) error { params := make(map[string]string) - params["HostName"] = hostname + params["HostName"] = stringutils2.GenerateHostName(name, osType) return self.modifyInstanceAttribute(instanceId, params) } diff --git a/pkg/util/stringutils2/stringutils.go b/pkg/util/stringutils2/stringutils.go index 52efc3412b..0ad1c5215f 100644 --- a/pkg/util/stringutils2/stringutils.go +++ b/pkg/util/stringutils2/stringutils.go @@ -18,7 +18,11 @@ import ( "crypto/md5" "encoding/hex" "fmt" + "strconv" "strings" + "unicode" + + "yunion.io/x/pkg/util/osprofile" ) func GetMD5Hash(text string) string { @@ -120,3 +124,64 @@ func SplitByQuotation(line string) ([]string, error) { } return segs, nil } + +func GenerateHostName(name string, osType string) string { + if len(name) < 2 { + name = fmt.Sprintf("hostname-for-%s-%s", name, osType) + } + // ()英文句号(.)和短横线(-)不能作为首尾字符,更不能连续使用。 + // 点号(.)和短横线(-)不能作为 HostName 的首尾字符,不能连续使用。 + var init = func(s string) string { + for { + if strings.Contains(s, "..") || strings.Contains(s, "--") { + s = strings.ReplaceAll(s, "..", ".") + s = strings.ReplaceAll(s, "--", "-") + continue + } + if strings.HasPrefix(s, ".") || strings.HasPrefix(s, "-") { + s = strings.TrimPrefix(s, ".") + s = strings.TrimPrefix(s, "-") + continue + } + if strings.HasSuffix(s, ".") || strings.HasSuffix(s, "-") { + s = strings.TrimSuffix(s, ".") + s = strings.TrimSuffix(s, "-") + continue + } + break + } + return s + } + name = init(name) + // (阿里云)Windows实例:字符长度为2~15,不支持英文句号(.),不能全是数字。允许大小写英文字母、数字和短横线(-)。 + // (腾讯云)Windows 实例:名字符长度为[2, 15],允许字母(不限制大小写)、数字和短横线(-)组成,不支持点号(.),不能全是数字 + var forWindows = func(s string) string { + s = strings.ReplaceAll(s, ".", "") + ret := "" + for _, c := range s { + if unicode.IsLetter(c) || unicode.IsNumber(c) || c == '-' { + ret += string(c) + } + } + _, err := strconv.Atoi(ret) + if err == nil { + ret = "host-" + ret + } + if len(ret) > 15 { + ret = init(ret[:15]) + } + return ret + } + // (阿里云)其他类型实例(Linux等):字符长度为2~64,支持多个英文句号(.),英文句号之间为一段,每段允许大小写英文字母、数字和短横线(-)。 + // (腾讯云)其他类型(Linux 等)实例:字符长度为[2, 60],允许支持多个点号,点之间为一段,每段允许字母(不限制大小写)、数字和短横线(-)组成。 + var forOther = func(s string) string { + if len(s) > 60 { + return init(s[:60]) + } + return s + } + if strings.ToLower(osType) == strings.ToLower(osprofile.OS_TYPE_WINDOWS) { + return forWindows(name) + } + return forOther(name) +} diff --git a/pkg/util/stringutils2/stringutils_test.go b/pkg/util/stringutils2/stringutils_test.go index 2a7f69cb58..f2ed89377e 100644 --- a/pkg/util/stringutils2/stringutils_test.go +++ b/pkg/util/stringutils2/stringutils_test.go @@ -17,6 +17,8 @@ package stringutils2 import ( "reflect" "testing" + + "yunion.io/x/pkg/util/osprofile" ) func TestEscapeString(t *testing.T) { @@ -120,3 +122,47 @@ func TestEscapeEchoString(t *testing.T) { }) } } + +func TestGenerateHostName(t *testing.T) { + type input struct { + name string + osType string + hostName string + } + for _, pair := range []input{ + input{ + name: "--test-host-name.......", + hostName: "test-host-name", + }, + input{ + name: "--test-host-1234567890-name.......", + osType: osprofile.OS_TYPE_WINDOWS, + hostName: "test-host-12345", + }, + input{ + name: "--test-host-1234-67890-name.......", + osType: osprofile.OS_TYPE_WINDOWS, + hostName: "test-host-1234", + }, + input{ + name: "1234567890123456", + osType: osprofile.OS_TYPE_WINDOWS, + hostName: "host-1234567890", + }, + input{ + name: "001234567890123456", + osType: osprofile.OS_TYPE_WINDOWS, + hostName: "host-0012345678", + }, + + input{ + name: "", + hostName: "hostname-for", + }, + } { + hostName := GenerateHostName(pair.name, pair.osType) + if hostName != pair.hostName { + t.Fatalf("%s hostName should be %s, current is %s", pair.name, pair.hostName, hostName) + } + } +}