Merge pull request #949 in YUNIONIO/onecloud from ~TANGBIN/onecloud:feature/tb-huawei-stage-2 to release/2.6.0

* commit '9007c966cf69947f95343ea1b1c33d1d5ff32df3':
  fix comment
  add instance price key
  add more comment
  huaweicli support unsubscirbe instance
  huawei support stage 2
This commit is contained in:
邱剑
2019-01-24 11:57:21 +08:00
90 changed files with 10236 additions and 333 deletions
+131
View File
@@ -0,0 +1,131 @@
package main
import (
"fmt"
"os"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
"yunion.io/x/structarg"
_ "yunion.io/x/onecloud/pkg/util/huawei/shell"
)
type BaseOptions struct {
Help bool `help:"Show help"`
AccessUrl string `help:"Access key" default:"$HUAWEI_ACCESS_URL" choices:"ChinaCloud|InternationalCloud"`
AccessKey string `help:"Access key" default:"$HUAWEI_ACCESS_KEY"`
Secret string `help:"Secret" default:"$HUAWEI_SECRET"`
RegionId string `help:"RegionId" default:"$HUAWEI_REGION"`
ProjectId string `help:"RegionId" default:"$HUAWEI_PROJECT"`
SUBCOMMAND string `help:"huaweicli subcommand" subcommand:"true"`
}
func getSubcommandParser() (*structarg.ArgumentParser, error) {
parse, e := structarg.NewArgumentParser(&BaseOptions{},
"huaweicli",
"Command-line interface to huawei API.",
`See "huaweicli help COMMAND" for help on a specific command.`)
if e != nil {
return nil, e
}
subcmd := parse.GetSubcommand()
if subcmd == nil {
return nil, fmt.Errorf("No subcommand argument.")
}
type HelpOptions struct {
SUBCOMMAND string `help:"sub-command name"`
}
shellutils.R(&HelpOptions{}, "help", "Show help of a subcommand", func(args *HelpOptions) error {
helpstr, e := subcmd.SubHelpString(args.SUBCOMMAND)
if e != nil {
return e
} else {
fmt.Print(helpstr)
return nil
}
})
for _, v := range shellutils.CommandTable {
_, e := subcmd.AddSubParser(v.Options, v.Command, v.Desc, v.Callback)
if e != nil {
return nil, e
}
}
return parse, nil
}
func showErrorAndExit(e error) {
log.Errorf("%s", e)
os.Exit(1)
}
func newClient(options *BaseOptions) (*huawei.SRegion, error) {
if len(options.AccessKey) == 0 {
return nil, fmt.Errorf("Missing accessKey")
}
if len(options.Secret) == 0 {
return nil, fmt.Errorf("Missing secret")
}
account := ""
if len(options.ProjectId) > 0 {
account = options.AccessKey + "/" + options.ProjectId
} else {
account = options.AccessKey
}
cli, err := huawei.NewHuaweiClient("", "", options.AccessUrl, account, options.Secret)
if err != nil {
return nil, err
}
region := cli.GetRegion(options.RegionId)
if region == nil {
return nil, fmt.Errorf("No such region %s", options.RegionId)
}
return region, nil
}
func main() {
parser, e := getSubcommandParser()
if e != nil {
showErrorAndExit(e)
}
e = parser.ParseArgs(os.Args[1:], false)
options := parser.Options().(*BaseOptions)
if options.Help {
fmt.Print(parser.HelpString())
} else {
subcmd := parser.GetSubcommand()
subparser := subcmd.GetSubParser()
if e != nil {
if subparser != nil {
fmt.Print(subparser.Usage())
} else {
fmt.Print(parser.Usage())
}
showErrorAndExit(e)
} else {
suboptions := subparser.Options()
if options.SUBCOMMAND == "help" {
e = subcmd.Invoke(suboptions)
} else {
var region *huawei.SRegion
region, e = newClient(options)
if e != nil {
showErrorAndExit(e)
}
e = subcmd.Invoke(region, suboptions)
}
if e != nil {
showErrorAndExit(e)
}
}
}
}
+1 -1
View File
@@ -68,7 +68,7 @@ func (region *SFakeOnPremiseRegion) CreateIVpc(name string, desc string, cidr st
return nil, ErrNotSupported
}
func (region *SFakeOnPremiseRegion) CreateEIP(name string, bwMbps int, chargeType string) (ICloudEIP, error) {
func (region *SFakeOnPremiseRegion) CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (ICloudEIP, error) {
return nil, ErrNotSupported
}
+1 -1
View File
@@ -48,7 +48,7 @@ type ICloudRegion interface {
SyncSecurityGroup(secgroupId string, vpcId string, name string, desc string, rules []secrules.SecurityRule) (string, error)
CreateIVpc(name string, desc string, cidr string) (ICloudVpc, error)
CreateEIP(name string, bwMbps int, chargeType string) (ICloudEIP, error)
CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (ICloudEIP, error)
GetISnapshots() ([]ICloudSnapshot, error)
GetISnapshotById(snapshotId string) (ICloudSnapshot, error)
+12
View File
@@ -37,3 +37,15 @@ func WaitDeleted(res ICloudResource, interval time.Duration, timeout time.Durati
}
return ErrTimeout
}
func WaitCreated(interval time.Duration, timeout time.Duration, callback func() bool) error {
startTime := time.Now()
for time.Now().Sub(startTime) < timeout {
ok := callback()
if ok {
return nil
}
time.Sleep(interval)
}
return ErrTimeout
}
+202 -2
View File
@@ -3,12 +3,16 @@ package guestdrivers
import (
"context"
"fmt"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/billing"
"yunion.io/x/onecloud/pkg/util/seclib2"
"yunion.io/x/pkg/utils"
)
@@ -83,11 +87,207 @@ func (self *SHuaweiGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *m
}
func (self *SHuaweiGuestDriver) RequestDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error {
// todo: implement me
config := guest.GetDeployConfigOnHost(ctx, host, task.GetParams())
log.Debugf("RequestDeployGuestOnHost: %s", config)
action, err := config.GetString("action")
if err != nil {
return err
}
publicKey, _ := config.GetString("public_key")
adminPublicKey, _ := config.GetString("admin_public_key")
projectPublicKey, _ := config.GetString("project_public_key")
oUserData, _ := config.GetString("user_data")
userData := generateUserData(adminPublicKey, projectPublicKey, oUserData)
resetPassword := jsonutils.QueryBoolean(config, "reset_password", false)
passwd, _ := config.GetString("password")
if resetPassword && len(passwd) == 0 {
passwd = seclib2.RandomPassword2(12)
}
ihost, err := host.GetIHost()
if err != nil {
return err
}
desc := SManagedVMCreateConfig{}
err = config.Unmarshal(&desc, "desc")
if err != nil {
return err
}
if action == "create" {
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
nets := guest.GetNetworks()
net := nets[0].GetNetwork()
vpc := net.GetVpc()
iregion, err := host.GetIRegion()
if err != nil {
return nil, err
}
secgroupCache := models.SecurityGroupCacheManager.Register(ctx, task.GetUserCred(), desc.SecGroupId, vpc.Id, vpc.CloudregionId, vpc.ManagerId)
if secgroupCache == nil {
return nil, fmt.Errorf("failed to registor secgroupCache for secgroup: %s, vpc: %s", desc.SecGroupId, vpc.Name)
}
secgroupExtId, err := iregion.SyncSecurityGroup(secgroupCache.ExternalId, vpc.ExternalId, desc.SecGroupName, "", desc.SecRules)
if err != nil {
log.Errorf("SyncSecurityGroup fail %s", err)
return nil, err
}
if err := secgroupCache.SetExternalId(secgroupExtId); err != nil {
return nil, fmt.Errorf("failed to set externalId for secgroup %s externalId %s: error: %v", desc.SecGroupId, secgroupExtId, err)
}
var createErr error
var iVM cloudprovider.ICloudVM
var bc *billing.SBillingCycle
if desc.BillingCycle.IsValid() {
bc = &desc.BillingCycle
}
if len(desc.InstanceType) > 0 {
iVM, createErr = ihost.CreateVM2(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.InstanceType, desc.ExternalNetworkId,
desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, bc)
} else {
iVM, createErr = ihost.CreateVM(desc.Name, desc.ExternalImageId, desc.SysDiskSize, desc.Cpu, desc.Memory, desc.ExternalNetworkId,
desc.IpAddr, desc.Description, passwd, desc.StorageType, desc.DataDisks, publicKey, secgroupExtId, userData, bc)
}
if createErr != nil {
return nil, createErr
}
log.Debugf("VMcreated %s, wait status ready ...", iVM.GetGlobalId())
err = cloudprovider.WaitStatus(iVM, models.VM_RUNNING, time.Second*5, time.Second*1800)
if err != nil {
return nil, err
}
log.Debugf("VMcreated %s, and status is ready", iVM.GetGlobalId())
iVM, err = ihost.GetIVMById(iVM.GetGlobalId())
if err != nil {
log.Errorf("cannot find vm %s", err)
return nil, err
}
data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action)
return data, nil
})
} else if action == "deploy" {
iVM, err := ihost.GetIVMById(guest.GetExternalId())
if err != nil || iVM == nil {
log.Errorf("cannot find vm %s", err)
return fmt.Errorf("cannot find vm")
}
params := task.GetParams()
log.Debugf("Deploy VM params %s", params.String())
name, _ := params.GetString("name")
description, _ := params.GetString("description")
publicKey, _ := config.GetString("public_key")
deleteKeypair := jsonutils.QueryBoolean(params, "__delete_keypair__", false)
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
if len(userData) > 0 {
err := iVM.UpdateUserData(userData)
if err != nil {
log.Errorf("update userdata fail %s", err)
}
}
err := iVM.DeployVM(ctx, name, passwd, publicKey, deleteKeypair, description)
if err != nil {
return nil, err
}
data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action)
return data, nil
})
} else if action == "rebuild" {
iVM, err := ihost.GetIVMById(guest.GetExternalId())
if err != nil || iVM == nil {
log.Errorf("cannot find vm %s", err)
return fmt.Errorf("cannot find vm")
}
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
if len(userData) > 0 {
err := iVM.UpdateUserData(userData)
if err != nil {
log.Errorf("update userdata fail %s", err)
}
}
diskId, err := iVM.RebuildRoot(ctx, desc.ExternalImageId, passwd, publicKey, desc.SysDiskSize)
if err != nil {
return nil, err
}
log.Debugf("VMrebuildRoot %s new diskID %s, wait status ready ...", iVM.GetGlobalId(), diskId)
err = cloudprovider.WaitStatus(iVM, models.VM_READY, time.Second*5, time.Second*1800)
if err != nil {
return nil, err
}
log.Debugf("VMrebuildRoot %s, and status is ready", iVM.GetGlobalId())
maxWaitSecs := 300
waited := 0
for {
// hack, wait disk number consistent
idisks, err := iVM.GetIDisks()
if err != nil {
log.Errorf("fail to find VM idisks %s", err)
return nil, err
}
if len(idisks) < len(desc.DataDisks)+1 || idisks[0].GetGlobalId() != diskId {
if waited > maxWaitSecs {
log.Errorf("inconsistent disk number, wait timeout, must be something wrong on remote")
return nil, cloudprovider.ErrTimeout
}
if len(idisks) < len(desc.DataDisks)+1 {
log.Debugf("inconsistent disk number???? %d != %d", len(idisks), len(desc.DataDisks)+1)
}
if len(idisks) > 0 && idisks[0].GetGlobalId() != diskId {
log.Errorf("system disk id inconsistent %s != %s", idisks[0].GetGlobalId(), diskId)
}
time.Sleep(time.Second * 5)
waited += 5
} else {
break
}
}
data := fetchIVMinfo(desc, iVM, guest.Id, "root", passwd, action)
return data, nil
})
} else {
log.Errorf("RequestDeployGuestOnHost: Action %s not supported", action)
return fmt.Errorf("Action %s not supported", action)
}
return nil
}
func (self *SHuaweiGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool {
// todo: implement me
months := bc.GetMonths()
if (months >= 1 && months <= 9) || (months == 12) || (months == 24) || (months == 36) {
return true
}
return false
}
+7 -1
View File
@@ -30,7 +30,13 @@ func (self *SManagedVirtualizationHostDriver) CheckAndSetCacheImage(ctx context.
osArch, _ := params.GetString("os_arch")
osType, _ := params.GetString("os_type")
osDist, _ := params.GetString("os_distribution")
osVersion, _ := params.GetString("os_version")
var osVersion string
providerName := storageCache.GetProviderName()
if providerName == models.CLOUD_PROVIDER_HUAWEI {
osVersion, _ = params.GetString("os_full_version")
} else {
osVersion, _ = params.GetString("os_version")
}
isForce := jsonutils.QueryBoolean(params, "is_force", false)
userCred := task.GetUserCred()
+1
View File
@@ -77,6 +77,7 @@ type SElasticip struct {
Bandwidth int `list:"user" create:"required"`
ChargeType string `list:"user" create:"required"`
BgpType string `list:"user" create:"optional"` // 目前只有华为云此字段是必需填写的。
AutoDellocate tristate.TriState `default:"false" get:"user" create:"optional"`
+13 -1
View File
@@ -1046,7 +1046,19 @@ func (manager *SNetworkManager) ValidateCreateData(ctx context.Context, userCred
}
vpc := vpcObj.(*SVpc)
zone := zoneObj.(*SZone)
wires, err := WireManager.getWiresByVpcAndZone(vpc, zone)
region := zone.GetRegion()
if region == nil {
return nil, httperrors.NewInternalServerError("zone %s related region not found", zone.Id)
}
// 华为云wire zone_id 为空
var wires []SWire
if region.Provider == CLOUD_PROVIDER_HUAWEI {
wires, err = WireManager.getWiresByVpcAndZone(vpc, nil)
} else {
wires, err = WireManager.getWiresByVpcAndZone(vpc, zone)
}
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewNotFoundError("wire not found for zone %s and vpc %s", zoneStr, vpcStr)
+1
View File
@@ -283,6 +283,7 @@ func (self *SStoragecache) StartImageCacheTask(ctx context.Context, userCred mcc
data.Add(jsonutils.NewString(imgInfo.OsArch), "os_arch")
data.Add(jsonutils.NewString(imgInfo.OsDistro), "os_distribution")
data.Add(jsonutils.NewString(imgInfo.OsVersion), "os_version")
data.Add(jsonutils.NewString(imgInfo.OsFullVersion), "os_full_version")
}
if isForce {
+1
View File
@@ -90,6 +90,7 @@ var (
STORAGE_STANDARD_LRS, STORAGE_STANDARDSSD_LRS, STORAGE_PREMIUM_LRS,
STORAGE_GP2_SSD, STORAGE_IO1_SSD, STORAGE_ST1_HDD, STORAGE_SC1_HDD, STORAGE_STANDARD_HDD,
STORAGE_LOCAL_BASIC, STORAGE_LOCAL_SSD, STORAGE_CLOUD_BASIC, STORAGE_CLOUD_PREMIUM,
STORAGE_HUAWEI_SSD, STORAGE_HUAWEI_SAS, STORAGE_HUAWEI_SATA,
}
STORAGE_LIMITED_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS, STORAGE_RBD, STORAGE_NFS}
+13 -9
View File
@@ -40,9 +40,9 @@ func init() {
type SWire struct {
db.SStandaloneResourceBase
Bandwidth int `list:"admin" update:"admin" nullable:"false" create:"admin_required"` // = Column(Integer, nullable=False) # bandwidth of network in Mbps
ScheduleRank int `list:"admin" update:"admin"` // = Column(Integer, default=0, nullable=True)
ZoneId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` // = Column(VARCHAR(36, charset='ascii'), nullable=False)
Bandwidth int `list:"admin" update:"admin" nullable:"false" create:"admin_required"` // = Column(Integer, nullable=False) # bandwidth of network in Mbps
ScheduleRank int `list:"admin" update:"admin"` // = Column(Integer, default=0, nullable=True)
ZoneId string `width:"36" charset:"ascii" nullable:"true" list:"admin" create:"admin_required"` // = Column(VARCHAR(36, charset='ascii'), nullable=False)
VpcId string `wdith:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"`
}
@@ -259,16 +259,20 @@ func (manager *SWireManager) newFromCloudWire(extWire cloudprovider.ICloudWire,
wire.ExternalId = extWire.GetGlobalId()
wire.Bandwidth = extWire.GetBandwidth()
wire.VpcId = vpc.Id
zoneObj, err := ZoneManager.FetchByExternalId(extWire.GetIZone().GetGlobalId())
if err != nil {
log.Errorf("cannot find zone for wire %s", err)
return nil, err
izone := extWire.GetIZone()
if izone != nil {
zoneObj, err := ZoneManager.FetchByExternalId(izone.GetGlobalId())
if err != nil {
log.Errorf("cannot find zone for wire %s", err)
return nil, err
}
wire.ZoneId = zoneObj.(*SZone).Id
}
wire.ZoneId = zoneObj.(*SZone).Id
wire.IsEmulated = extWire.IsEmulated()
err = manager.TableSpec().Insert(&wire)
err := manager.TableSpec().Insert(&wire)
if err != nil {
log.Errorf("newFromCloudWire fail %s", err)
return nil, err
+122 -32
View File
@@ -480,6 +480,116 @@ func (manager *SZoneManager) InitializeData() error {
return nil
}
/*
Query 1:
vpc.manager_id is not empty && wire.zone_id is not empty
*/
func (manager *SZoneManager) usableZoneQ1(providers, vpcs, wires, networks *sqlchemy.SSubQuery, usableNet, usableVpc bool) *sqlchemy.SSubQuery {
// join tables
sq := wires.Query(sqlchemy.DISTINCT("zone_id", wires.Field("zone_id")))
if usableNet {
sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id")))
}
sq = sq.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id")))
sq = sq.Join(providers, sqlchemy.Equals(vpcs.Field("manager_id"), providers.Field("id")))
// add filters
if usableNet {
sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE))
}
sq = sq.Filter(sqlchemy.IsNotEmpty(wires.Field("zone_id")))
sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled")))
sq = sq.Filter(sqlchemy.In(providers.Field("status"), CLOUD_PROVIDER_VALID_STATUS))
if usableVpc {
sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE))
}
return sq.SubQuery()
}
/*
Query 2:
vpc.manager_id is empty && wire.zone_id is not empty
*/
func (manager *SZoneManager) usableZoneQ2(vpcs, wires, networks *sqlchemy.SSubQuery, usableNet, usableVpc bool) *sqlchemy.SSubQuery {
// join tables
sq := wires.Query(sqlchemy.DISTINCT("zone_id", wires.Field("zone_id")))
if usableNet {
sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id")))
}
sq = sq.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id")))
// add filters
if usableNet {
sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE))
}
sq = sq.Filter(sqlchemy.IsNotEmpty(wires.Field("zone_id")))
sq = sq.Filter(sqlchemy.IsNullOrEmpty(vpcs.Field("manager_id")))
if usableVpc {
sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE))
}
return sq.SubQuery()
}
/*
Query 3:
vpc.manager_id is not empty && wire.zone_id is empty
2019.01.17 目前华为云子网在整个region 可用。wire中zone_id留空。
*/
func (manager *SZoneManager) usableZoneQ3(providers, vpcs, wires, networks, zones *sqlchemy.SSubQuery, usableNet, usableVpc bool) *sqlchemy.SSubQuery {
// join tables
sq := zones.Query(sqlchemy.DISTINCT("zone_id", zones.Field("id")))
sq = sq.Join(vpcs, sqlchemy.Equals(zones.Field("cloudregion_id"), vpcs.Field("cloudregion_id")))
sq = sq.Join(wires, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id")))
if usableNet {
sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id")))
}
sq = sq.Join(providers, sqlchemy.Equals(vpcs.Field("manager_id"), providers.Field("id")))
// add filters
if usableNet {
sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE))
}
sq = sq.Filter(sqlchemy.IsNullOrEmpty(wires.Field("zone_id")))
sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled")))
sq = sq.Filter(sqlchemy.In(providers.Field("status"), CLOUD_PROVIDER_VALID_STATUS))
if usableVpc {
sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE))
}
return sq.SubQuery()
}
/*
Query 4:
vpc.manager_id is empty && wire.zone_id is empty
2019.01.17 目前华为云子网在整个region 可用。wire中zone_id留空。
*/
func (manager *SZoneManager) usableZoneQ4(vpcs, wires, networks, zones *sqlchemy.SSubQuery, usableNet, usableVpc bool) *sqlchemy.SSubQuery {
// join tables
sq := zones.Query(sqlchemy.DISTINCT("zone_id", zones.Field("id")))
sq = sq.Join(vpcs, sqlchemy.Equals(zones.Field("cloudregion_id"), vpcs.Field("cloudregion_id")))
sq = sq.Join(wires, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id")))
if usableNet {
sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id")))
}
// add filters
if usableNet {
sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE))
}
sq = sq.Filter(sqlchemy.IsNullOrEmpty(wires.Field("zone_id")))
sq = sq.Filter(sqlchemy.IsNullOrEmpty(vpcs.Field("manager_id")))
if usableVpc {
sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE))
}
return sq.SubQuery()
}
func (manager *SZoneManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
q, err := manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query)
if err != nil {
@@ -496,45 +606,25 @@ func (manager *SZoneManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQu
}
if jsonutils.QueryBoolean(query, "usable", false) || jsonutils.QueryBoolean(query, "usable_vpc", false) {
usableNet := jsonutils.QueryBoolean(query, "usable", false)
usableVpc := jsonutils.QueryBoolean(query, "usable_vpc", false)
networks := NetworkManager.Query().SubQuery()
wires := WireManager.Query().SubQuery()
vpcs := VpcManager.Query().SubQuery()
providers := CloudproviderManager.Query().SubQuery()
zones := ZoneManager.Query().SubQuery()
usableNet := jsonutils.QueryBoolean(query, "usable", false)
usableVpc := jsonutils.QueryBoolean(query, "usable_vpc", false)
sq := wires.Query(sqlchemy.DISTINCT("zone_id", wires.Field("zone_id")))
if usableNet {
sq = sq.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id")))
}
sq = sq.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id")))
sq = sq.Join(providers, sqlchemy.Equals(vpcs.Field("manager_id"), providers.Field("id")))
if usableNet {
sq = sq.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE))
}
sq = sq.Filter(sqlchemy.IsTrue(providers.Field("enabled")))
sq = sq.Filter(sqlchemy.In(providers.Field("status"), CLOUD_PROVIDER_VALID_STATUS))
if usableVpc {
sq = sq.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE))
}
sq2 := wires.Query(sqlchemy.DISTINCT("zone_id", wires.Field("zone_id")))
if usableNet {
sq2 = sq2.Join(networks, sqlchemy.Equals(wires.Field("id"), networks.Field("wire_id")))
}
sq2 = sq2.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id")))
if usableNet {
sq2 = sq2.Filter(sqlchemy.Equals(networks.Field("status"), NETWORK_STATUS_AVAILABLE))
}
sq2 = sq2.Filter(sqlchemy.IsNullOrEmpty(vpcs.Field("manager_id")))
if usableVpc {
sq2 = sq2.Filter(sqlchemy.Equals(vpcs.Field("status"), VPC_STATUS_AVAILABLE))
}
sq1 := manager.usableZoneQ1(providers, vpcs, wires, networks, usableNet, usableVpc)
sq2 := manager.usableZoneQ2(vpcs, wires, networks, usableNet, usableVpc)
sq3 := manager.usableZoneQ3(providers, vpcs, wires, networks, zones, usableNet, usableVpc)
sq4 := manager.usableZoneQ4(vpcs, wires, networks, zones, usableNet, usableVpc)
q = q.Filter(sqlchemy.OR(
sqlchemy.In(q.Field("id"), sq.SubQuery()),
sqlchemy.In(q.Field("id"), sq2.SubQuery()),
sqlchemy.In(q.Field("id"), sq1),
sqlchemy.In(q.Field("id"), sq2),
sqlchemy.In(q.Field("id"), sq3),
sqlchemy.In(q.Field("id"), sq4),
))
q = q.Equals("status", ZONE_ENABLE)
}
+1 -1
View File
@@ -45,7 +45,7 @@ func (self *EipAllocateTask) OnInit(ctx context.Context, obj db.IStandaloneModel
return
}
extEip, err := iregion.CreateEIP(eip.Name, eip.Bandwidth, eip.ChargeType)
extEip, err := iregion.CreateEIP(eip.Name, eip.Bandwidth, eip.ChargeType, eip.BgpType)
if err != nil {
msg := fmt.Sprintf("create eip fail %s", err)
eip.SetStatus(self.UserCred, models.EIP_STATUS_ALLOCATE_FAIL, msg)
+1 -1
View File
@@ -18,7 +18,7 @@ type ServerListOptions struct {
Gpu *bool `help:"Show gpu servers"`
Secgroup string `help:"Secgroup ID or Name"`
AdminSecgroup string `help:"AdminSecgroup ID or Name"`
Hypervisor string `help:"Show server of hypervisor" choices:"kvm|esxi|container|baremetal|aliyun|azure|aws"`
Hypervisor string `help:"Show server of hypervisor" choices:"kvm|esxi|container|baremetal|aliyun|azure|aws|huawei"`
Region string `help:"Show servers in cloudregion"`
WithEip *bool `help:"Show Servers with EIP"`
WithoutEip *bool `help:"Show Servers without EIP"`
+1 -1
View File
@@ -242,7 +242,7 @@ func (region *SRegion) AllocateEIP(bwMbps int, chargeType TInternetChargeType) (
return region.GetEip(eipId)
}
func (region *SRegion) CreateEIP(name string, bwMbps int, chargeType string) (cloudprovider.ICloudEIP, error) {
func (region *SRegion) CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (cloudprovider.ICloudEIP, error) {
var ctype TInternetChargeType
switch chargeType {
case models.EIP_CHARGE_TYPE_BY_TRAFFIC:
+1 -1
View File
@@ -236,7 +236,7 @@ func (self *SRegion) AllocateEIP(domainType string) (*SEipAddress, error) {
return self.GetEip(*eip.AllocationId)
}
func (self *SRegion) CreateEIP(name string, bwMbps int, chargeType string) (cloudprovider.ICloudEIP, error) {
func (self *SRegion) CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (cloudprovider.ICloudEIP, error) {
// todo: aws 不支持指定bwMbps, chargeType ?
log.Debugf("CreateEip: aws not support specific params name/bwMbps/chargeType.")
ieip, err := self.AllocateEIP("vpc")
+1 -1
View File
@@ -68,7 +68,7 @@ func (region *SRegion) AllocateEIP(eipName string) (*SEipAddress, error) {
return &eip, cloudprovider.WaitStatus(&eip, models.EIP_STATUS_READY, 10*time.Second, 300*time.Second)
}
func (region *SRegion) CreateEIP(eipName string, bwMbps int, chargeType string) (cloudprovider.ICloudEIP, error) {
func (region *SRegion) CreateEIP(eipName string, bwMbps int, chargeType string, bgpType string) (cloudprovider.ICloudEIP, error) {
return region.AllocateEIP(eipName)
}
+3 -1
View File
@@ -72,7 +72,9 @@ func signRequest(request requests.IRequest, signer Signer) error {
func fillRequiredHeaders(request requests.IRequest, t time.Time) {
request.AddHeaderParam("HOST", request.GetHost())
request.AddHeaderParam("X-Sdk-Date", formattedSignTime(t, "DateTime"))
request.AddHeaderParam("X-Project-Id", request.GetProjectId())
if len(request.GetProjectId()) > 0 {
request.AddHeaderParam("X-Project-Id", request.GetProjectId())
}
return
}
+27 -47
View File
@@ -12,20 +12,33 @@ type Client struct {
domainId string
projectId string
// 标记初始化状态
init bool
Balances *modules.SBalanceManager
Bandwidths *modules.SBandwidthManager
Disks *modules.SDiskManager
Domains *modules.SDomainManager
Eips *modules.SEipManager
Flavors *modules.SFlavorManager
Images *modules.SImageManager
OpenStackImages *modules.SImageManager
Interface *modules.SInterfaceManager
Jobs *modules.SJobManager
Keypairs *modules.SKeypairManager
Orders *modules.SOrderManager
Port *modules.SPortManager
Projects *modules.SProjectManager
Regions *modules.SRegionManager
SecurityGroupRules *modules.SSecgroupRuleManager
SecurityGroups *modules.SSecurityGroupManager
NovaSecurityGroups *modules.SSecurityGroupManager
Servers *modules.SServerManager
NovaServers *modules.SServerManager
Snapshots *modules.SSnapshotManager
OsSnapshots *modules.SSnapshotManager
Subnets *modules.SSubnetManager
Users *modules.SUserManager
Vpcs *modules.SVpcManager
Zones *modules.SZoneManager
}
@@ -62,69 +75,36 @@ func (self *Client) InitWithAccessKey(regionId, projectId, accessKey, secretKey
}
func (self *Client) initManagers() {
if self.Servers == nil {
if !self.init {
self.Servers = modules.NewServerManager(self.regionId, self.projectId, self.signer)
}
if self.Snapshots == nil {
self.NovaServers = modules.NewNovaServerManager(self.regionId, self.projectId, self.signer)
self.Snapshots = modules.NewSnapshotManager(self.regionId, self.projectId, self.signer)
}
if self.Images == nil {
self.Images = modules.NewImageManager(self.regionId, self.signer)
}
if self.Projects == nil {
self.OsSnapshots = modules.NewOsSnapshotManager(self.regionId, self.projectId, self.signer)
self.Images = modules.NewImageManager(self.regionId, self.projectId, self.signer)
self.OpenStackImages = modules.NewOpenstackImageManager(self.regionId, self.signer)
self.Projects = modules.NewProjectManager(self.signer)
}
if self.Regions == nil {
self.Regions = modules.NewRegionManager(self.signer)
}
if self.Zones == nil {
self.Zones = modules.NewZoneManager(self.regionId, self.projectId, self.signer)
}
if self.Vpcs == nil {
self.Vpcs = modules.NewVpcManager(self.regionId, self.projectId, self.signer)
}
if self.Eips == nil {
self.Eips = modules.NewEipManager(self.regionId, self.projectId, self.signer)
}
if self.Disks == nil {
self.Disks = modules.NewDiskManager(self.regionId, self.projectId, self.signer)
}
if self.Keypairs == nil {
self.Domains = modules.NewDomainManager(self.signer)
self.Keypairs = modules.NewKeypairManager(self.regionId, self.projectId, self.signer)
}
if self.SecurityGroupRules == nil {
self.Orders = modules.NewOrderManager(self.regionId, self.signer)
self.SecurityGroupRules = modules.NewSecgroupRuleManager(self.regionId, self.projectId, self.signer)
}
if self.SecurityGroups == nil {
self.SecurityGroups = modules.NewSecurityGroupManager(self.regionId, self.projectId, self.signer)
}
if self.Subnets == nil {
self.NovaSecurityGroups = modules.NewNovaSecurityGroupManager(self.regionId, self.projectId, self.signer)
self.Subnets = modules.NewSubnetManager(self.regionId, self.projectId, self.signer)
}
if self.Interface == nil {
self.Users = modules.NewUserManager(self.signer)
self.Interface = modules.NewInterfaceManager(self.regionId, self.projectId, self.signer)
}
if self.Bandwidths == nil {
self.Jobs = modules.NewJobManager(self.regionId, self.projectId, self.signer)
self.Balances = modules.NewBalanceManager(self.signer)
self.Bandwidths = modules.NewBandwidthManager(self.regionId, self.projectId, self.signer)
self.Port = modules.NewPortManager(self.regionId, self.projectId, self.signer)
self.Flavors = modules.NewFlavorManager(self.regionId, self.projectId, self.signer)
}
if self.Port == nil {
self.Port = modules.NewPortManager(self.regionId, self.signer)
}
self.init = true
}
// todo: init from envrioment
+14 -22
View File
@@ -1,25 +1,12 @@
package manager
import (
"fmt"
"net/url"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/util/huawei/client/responses"
)
type ManagerContext struct {
InstanceManager IManager
InstanceId string
}
func (self *ManagerContext) GetPath() string {
path := self.InstanceManager.KeyString()
if len(self.InstanceId) > 0 {
path += fmt.Sprintf("/%s", url.PathEscape(self.InstanceId))
}
return path
type IManagerContext interface {
GetPath() string
}
type IBaseManager interface {
@@ -34,30 +21,35 @@ type IManager interface {
// 获取资源列表 GET <base_url>/cloudservers/?<querys>
List(querys map[string]string) (*responses.ListResult, error)
// 根据上文获取资源列表 GET <base_url>/cloudservers/<cloudserver_id>/nics?<querys>
ListInContext(ctx *ManagerContext, spec string, querys map[string]string) (*responses.ListResult, error)
ListInContext(ctx IManagerContext, querys map[string]string) (*responses.ListResult, error)
ListInContextWithSpec(ctx IManagerContext, spec string, querys map[string]string, responseKey string) (*responses.ListResult, error)
// 查询单个资源 GET <base_url>/cloudservers/<cloudserver_id>?<querys>
Get(id string, querys map[string]string) (jsonutils.JSONObject, error)
// 根据上文获取资源查询单个资源 GET <base_url>/cloudservers/<cloudserver_id>/nics/<nic_id>?<querys>
GetInContext(ctx *ManagerContext, id string, querys map[string]string) (jsonutils.JSONObject, error)
GetInContext(ctx IManagerContext, id string, querys map[string]string) (jsonutils.JSONObject, error)
// 创建单个资源 POST <base_url>/cloudservers
Create(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
Create(params jsonutils.JSONObject) (jsonutils.JSONObject, error)
// 根据上文创建单个资源 POST <base_url>/cloudservers/<cloudserver_id>/nics/<nic_id>
CreateInContext(ctx *ManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
CreateInContext(ctx IManagerContext, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
// 异步任务创建 POST <base_url>/cloudservers. 返回异步任务 job_id。 todo:// 后续考虑返回一个task对象
AsyncCreate(params jsonutils.JSONObject) (string, error)
// 更新单个资源 PUT <base_url>/cloudservers/<cloudserver_id>
Update(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
// 根据上文更新单个资源 PUT <base_url>/cloudservers/<cloudserver_id>/nics/<nic_id>
UpdateInContext(ctx *ManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
UpdateInContext(ctx IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
// 根据上文和spec更新单个资源 PUT /v2.1/{project_id}/servers/{server_id}/os-reset-password
UpdateInContextWithSpec(ctx IManagerContext, id string, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error)
// 删除单个资源 DELETE <base_url>/cloudservers/<cloudserver_id>
Delete(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
// 根据上文删除单个资源 DELETE <base_url>/cloudservers/<cloudserver_id>/nics/<nic_id>
DeleteInContext(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
DeleteInContext(ctx IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
// 批量执行操作 POST <base_url>/cloudservers/<action>
BatchPerformAction(action string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
// BatchPerformAction(action string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
// 执行操作 POST <base_url>/cloudservers/<cloudserver_id>/<action>
PerformAction(action string, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
}
+12 -3
View File
@@ -13,9 +13,14 @@ import (
"yunion.io/x/onecloud/pkg/util/huawei/client/responses"
)
type IRequestHook interface {
Process(r requests.IRequest)
}
type BaseManager struct {
signer auth.Signer
httpClient *http.Client
signer auth.Signer
httpClient *http.Client
requestHook IRequestHook // 用于对request做特殊处理。非必要请不要使用!!!。目前只有port接口用到。
columns []string
debug bool
@@ -90,6 +95,10 @@ func (self *BaseManager) _get(request requests.IRequest, responseKey string) (js
func (self *BaseManager) jsonRequest(request requests.IRequest) (http.Header, jsonutils.JSONObject, error) {
ctx := context.Background()
// hook request
if self.requestHook != nil {
self.requestHook.Process(request)
}
// 拼接、编译、签名 requests here。
err := self.buildRequestWithSigner(request, self.signer)
if err != nil {
@@ -109,7 +118,7 @@ func (self *BaseManager) jsonRequest(request requests.IRequest) (http.Header, js
}
}
// 发送 request。
// 发送 request。todo: 支持debug
return httputils.JSONRequest(self.httpClient, ctx, httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), header, jsonBody, self.debug)
}
@@ -1,6 +1,7 @@
package modules
import (
"fmt"
"net/url"
"strings"
@@ -28,9 +29,24 @@ const (
ServiceNameOBS ServiceNameType = "obs" // 对象存储服务 OBS
ServiceNameVPC ServiceNameType = "vpc" // 虚拟私有云 VPC
ServiceNameELB ServiceNameType = "elb" // 弹性负载均衡 ELB
ServiceNameBSS ServiceNameType = "bss" // 合作伙伴运营能力
)
type ManagerContext struct {
InstanceManager manager.IManager
InstanceId string
}
func (self *ManagerContext) GetPath() string {
path := self.InstanceManager.KeyString()
if len(self.InstanceId) > 0 {
path += fmt.Sprintf("/%s", url.PathEscape(self.InstanceId))
}
return path
}
type ResourceManager struct {
BaseManager
ServiceName ServiceNameType // 服务名称: ecs
@@ -43,6 +59,14 @@ type ResourceManager struct {
ResourceKeyword string // 资源名称。url中使用
}
func getContent(params jsonutils.JSONObject) string {
if params == nil {
return ""
}
return params.String()
}
func (self *ResourceManager) Version() string {
return self.version
}
@@ -59,7 +83,7 @@ func (self *ResourceManager) GetColumns() []string {
return []string{}
}
func (self *ResourceManager) getReourcePath(ctx *manager.ManagerContext, rid string, spec string) string {
func (self *ResourceManager) getReourcePath(ctx manager.IManagerContext, rid string, spec string) string {
segs := []string{}
if ctx != nil {
segs = append(segs, ctx.GetPath())
@@ -72,81 +96,114 @@ func (self *ResourceManager) getReourcePath(ctx *manager.ManagerContext, rid str
}
if len(spec) > 0 {
segs = append(segs, url.PathEscape(spec))
specSegs := strings.Split(spec, "/")
for _, specSeg := range specSegs {
segs = append(segs, url.PathEscape(specSeg))
}
}
return strings.Join(segs, "/")
}
func (self *ResourceManager) newRequest(method, rid, spec string, ctx *manager.ManagerContext) *requests.SRequest {
func (self *ResourceManager) newRequest(method, rid, spec string, ctx manager.IManagerContext) *requests.SRequest {
resourcePath := self.getReourcePath(ctx, rid, spec)
return requests.NewResourceRequest(method, string(self.ServiceName), self.version, self.Region, self.ProjectId, resourcePath)
}
func (self *ResourceManager) List(querys map[string]string) (*responses.ListResult, error) {
return self.ListInContext(nil, "", querys)
return self.ListInContext(nil, querys)
}
func (self *ResourceManager) ListInContext(ctx *manager.ManagerContext, spec string, querys map[string]string) (*responses.ListResult, error) {
func (self *ResourceManager) ListInContext(ctx manager.IManagerContext, querys map[string]string) (*responses.ListResult, error) {
return self.ListInContextWithSpec(ctx, "", querys, self.KeywordPlural)
}
func (self *ResourceManager) ListInContextWithSpec(ctx manager.IManagerContext, spec string, querys map[string]string, responseKey string) (*responses.ListResult, error) {
request := self.newRequest("GET", "", spec, ctx)
for k, v := range querys {
request.AddQueryParam(k, v)
}
//if _, ok := request.GetQueryParams()["limit"]; !ok {
// request.AddQueryParam("limit", "50")
//}
//if _, ok := request.GetQueryParams()["offset"]; !ok {
// request.AddQueryParam("offset", "0")
//}
return self._list(request, self.KeywordPlural)
return self._list(request, responseKey)
}
func (self *ResourceManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) {
return self.GetInContext(nil, id, querys)
}
func (self *ResourceManager) GetInContext(ctx *manager.ManagerContext, id string, querys map[string]string) (jsonutils.JSONObject, error) {
request := self.newRequest("GET", id, "", nil)
func (self *ResourceManager) GetInContext(ctx manager.IManagerContext, id string, querys map[string]string) (jsonutils.JSONObject, error) {
return self.GetInContextWithSpec(ctx, id, "", querys, self.Keyword)
}
func (self *ResourceManager) GetInContextWithSpec(ctx manager.IManagerContext, id string, spec string, querys map[string]string, responseKey string) (jsonutils.JSONObject, error) {
request := self.newRequest("GET", id, spec, ctx)
for k, v := range querys {
request.AddQueryParam(k, v)
}
return self._get(request, self.Keyword)
return self._get(request, responseKey)
}
func (self *ResourceManager) Create(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
panic("implement me")
func (self *ResourceManager) Create(params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return self.CreateInContext(nil, params)
}
func (self *ResourceManager) CreateInContext(ctx *manager.ManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
panic("implement me")
func (self *ResourceManager) CreateInContext(ctx manager.IManagerContext, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return self.CreateInContextWithSpec(ctx, "", params, self.Keyword)
}
func (self *ResourceManager) CreateInContextWithSpec(ctx manager.IManagerContext, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) {
request := self.newRequest("POST", "", spec, ctx)
request.SetContent([]byte(params.String()))
return self._do(request, responseKey)
}
func (self *ResourceManager) AsyncCreate(params jsonutils.JSONObject) (string, error) {
return "", fmt.Errorf("not supported")
}
func (self *ResourceManager) Update(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
panic("implement me")
return self.UpdateInContext(nil, id, params)
}
func (self *ResourceManager) UpdateInContext(ctx *manager.ManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
panic("implement me")
func (self *ResourceManager) UpdateInContext(ctx manager.IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return self.UpdateInContextWithSpec(ctx, id, "", params, self.Keyword)
}
func (self *ResourceManager) UpdateInContextWithSpec(ctx manager.IManagerContext, id string, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) {
request := self.newRequest("PUT", id, spec, ctx)
content := getContent(params)
if len(content) > 0 {
request.SetContent([]byte(content))
}
return self._do(request, responseKey)
}
func (self *ResourceManager) Delete(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
panic("implement me")
return self.DeleteInContext(nil, id, params)
}
func (self *ResourceManager) DeleteInContext(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
panic("implement me")
}
func (self *ResourceManager) DeleteInContext(ctx manager.IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
request := self.newRequest("DELETE", id, "", ctx)
content := getContent(params)
if len(content) > 0 {
request.SetContent([]byte(content))
}
func (self *ResourceManager) BatchPerformAction(action string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
panic("implement me")
return self._do(request, self.Keyword)
}
func (self *ResourceManager) PerformAction(action string, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
panic("implement me")
return self.PerformAction2(action, id, params, self.Keyword)
}
func (self *ResourceManager) PerformAction2(action string, id string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) {
request := self.newRequest("POST", id, action, nil)
request.SetContent([]byte(getContent(params)))
return self._do(request, responseKey)
}
func (self *ResourceManager) SetVersion(v string) {
@@ -0,0 +1,59 @@
package modules
import (
"fmt"
"net/http"
"yunion.io/x/onecloud/pkg/util/huawei/client/auth"
"yunion.io/x/onecloud/pkg/util/huawei/client/responses"
)
/*
https://support.huaweicloud.com/api-oce/zh-cn_topic_0075195195.html
客户运营能力API的Endpoint为“bss.cn-north-1.myhuaweicloud.com”。该Endpoint为全局Endpoint,中国站所有区域均可使用。
如何获取合作伙伴ID https://support.huaweicloud.com/bpconsole_faq/zh-cn_topic_0081005893.html
注意事项:
客户查询自身的账户余额的时候,只允许使用客户自身的AK/SK或者Token调用。
*/
type SBalanceManager struct {
domainId string // 租户ID
ResourceManager
}
type balanceCtx struct {
domainId string
}
// https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0075213309.html
// 这个manager非常特殊。url hardcode
func (self *balanceCtx) GetPath() string {
return fmt.Sprintf("%s/customer/account-mgr", self.domainId)
}
// 这个manager非常特殊。只有List 和 SetDomainId方法可用。其他方法未验证
func NewBalanceManager(signer auth.Signer) *SBalanceManager {
return &SBalanceManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: ServiceNameBSS,
Region: "cn-north-1",
ProjectId: "",
version: "v1.0",
Keyword: "account_balance",
KeywordPlural: "account_balances",
ResourceKeyword: "balances",
}}
}
func (self *SBalanceManager) List(querys map[string]string) (*responses.ListResult, error) {
if len(self.domainId) == 0 {
return nil, fmt.Errorf("domainId is emtpy.Use SetDomainId method to set.")
}
ctx := &balanceCtx{domainId: self.domainId}
return self.ListInContext(ctx, querys)
}
func (self *SBalanceManager) SetDomainId(domainId string) {
self.domainId = domainId
}
+1 -1
View File
@@ -26,5 +26,5 @@ func NewDiskManager(regionId string, projectId string, signer auth.Signer) *SDis
}
func (self *SDiskManager) List(querys map[string]string) (*responses.ListResult, error) {
return self.ListInContext(nil, "detail", querys)
return self.ListInContextWithSpec(nil, "detail", querys, self.KeywordPlural)
}
@@ -0,0 +1,25 @@
package modules
import (
"net/http"
"yunion.io/x/onecloud/pkg/util/huawei/client/auth"
)
type SDomainManager struct {
ResourceManager
}
func NewDomainManager(signer auth.Signer) *SDomainManager {
return &SDomainManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: ServiceNameIAM,
Region: "",
ProjectId: "",
version: "v3/auth",
Keyword: "domain",
KeywordPlural: "domains",
ResourceKeyword: "domains",
}}
}
@@ -0,0 +1,25 @@
package modules
import (
"net/http"
"yunion.io/x/onecloud/pkg/util/huawei/client/auth"
)
type SFlavorManager struct {
ResourceManager
}
func NewFlavorManager(regionId string, projectId string, signer auth.Signer) *SFlavorManager {
return &SFlavorManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: ServiceNameECS,
Region: regionId,
ProjectId: projectId,
version: "v1",
Keyword: "flavor",
KeywordPlural: "flavors",
ResourceKeyword: "cloudservers/flavors", // 这个接口有点特殊,实际只用到了list一个方法。为了简便直接把cloudservers附上。
}}
}
+59 -2
View File
@@ -3,16 +3,34 @@ package modules
import (
"net/http"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/util/huawei/client/auth"
"yunion.io/x/onecloud/pkg/util/huawei/client/requests"
)
type SImageManager struct {
ResourceManager
}
func NewImageManager(regionId string, signer auth.Signer) *SImageManager {
type imageProject struct {
projectId string
}
// image创建接口若非默认project,需要在header中指定X-Project-ID。url中未携带project信息(与其他接口相比有一点特殊)
// 绕过了ResourceManager中的projectid。直接在发送json请求前注入X-Project-ID
func (self *imageProject) Process(request requests.IRequest) {
request.AddHeaderParam("X-Project-Id", self.projectId)
}
func NewImageManager(regionId string, projectId string, signer auth.Signer) *SImageManager {
var requestHook imageProject
if len(projectId) > 0 {
requestHook = imageProject{projectId: projectId}
}
return &SImageManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}, requestHook: &requestHook},
ServiceName: ServiceNameIMS,
Region: regionId,
ProjectId: "",
@@ -23,3 +41,42 @@ func NewImageManager(regionId string, signer auth.Signer) *SImageManager {
ResourceKeyword: "cloudimages",
}}
}
func (self *SImageManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) {
if querys == nil {
querys = make(map[string]string, 0)
}
querys["id"] = id
// 这里默认使用private
if t, exists := querys["__imagetype"]; !exists || len(t) == 0 {
querys["__imagetype"] = "private"
}
ret, err := self.ListInContext(nil, querys)
if err != nil {
return nil, err
}
if ret.Data == nil || len(ret.Data) == 0 {
return nil, httperrors.NewNotFoundError("image %s not found", id)
}
return ret.Data[0], nil
}
// https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092108.html
// 删除image只能用这个manager
func NewOpenstackImageManager(regionId string, signer auth.Signer) *SImageManager {
return &SImageManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: ServiceNameIMS,
Region: regionId,
ProjectId: "",
version: "v2",
Keyword: "image",
KeywordPlural: "images",
ResourceKeyword: "images",
}}
}
@@ -0,0 +1,57 @@
package modules
import (
"fmt"
"net/http"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/util/huawei/client/auth"
"yunion.io/x/onecloud/pkg/util/huawei/client/responses"
)
type SJobManager struct {
ResourceManager
}
func NewJobManager(regionId string, projectId string, signer auth.Signer) *SJobManager {
return &SJobManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: "",
Region: regionId,
ProjectId: projectId,
version: "v1",
Keyword: "",
KeywordPlural: "",
ResourceKeyword: "jobs",
}}
}
func (self *SJobManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) {
processedQuery, err := self.processQueryParam(querys)
if err != nil {
return nil, err
}
return self.GetInContext(nil, id, processedQuery)
}
func (self *SJobManager) List(querys map[string]string) (*responses.ListResult, error) {
processedQuery, err := self.processQueryParam(querys)
if err != nil {
return nil, err
}
return self.ListInContext(nil, processedQuery)
}
// 兼容查询不同ServiceName服务的Job做的特殊处理。
func (self *SJobManager) processQueryParam(querys map[string]string) (map[string]string, error) {
service_type, exists := querys["service_type"]
if !exists {
return querys, fmt.Errorf("must specific query parameter `service_type`. e.g. ecs|ims|iam")
}
self.ServiceName = ServiceNameType(service_type)
delete(querys, "service_type")
return querys, nil
}
@@ -0,0 +1,77 @@
package modules
import (
"fmt"
"net/http"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/util/huawei/client/auth"
"yunion.io/x/onecloud/pkg/util/huawei/client/manager"
"yunion.io/x/onecloud/pkg/util/huawei/client/responses"
)
// domian 客户账号ID https://support.huaweicloud.com/oce_faq/zh-cn_topic_0113714840.html
type SOrderManager struct {
orderCtx manager.IManagerContext
ResourceManager
}
type orderCtx struct {
domainId string
}
// {domain_id}/common/
// 这个manager非常特殊。url hardcode
func (self *orderCtx) GetPath() string {
return fmt.Sprintf("%s/common", self.domainId)
}
// https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html
func NewOrderManager(regionId string, signer auth.Signer) *SOrderManager {
return &SOrderManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: ServiceNameBSS,
Region: regionId,
ProjectId: "",
version: "v1.0",
Keyword: "",
KeywordPlural: "",
ResourceKeyword: "order-mgr",
}}
}
func (self *SOrderManager) SetDomainId(domainId string) error {
if len(domainId) == 0 {
return fmt.Errorf("SetDomainId domain id should not be emtpy")
}
self.orderCtx = &orderCtx{domainId: domainId}
return nil
}
// 查询客户包周期资源列表 https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html
func (self *SOrderManager) List(querys map[string]string) (*responses.ListResult, error) {
if self.orderCtx == nil {
return nil, fmt.Errorf("domainId is emtpy.Use SetDomainId method to set.")
}
return self.ListInContextWithSpec(self.orderCtx, "resources/detail", querys, "data")
}
// 查询订单的资源开通详情 https://support.huaweicloud.com/api-oce/api_order_00001.html
func (self *SOrderManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) {
if self.orderCtx == nil {
return nil, fmt.Errorf("domainId is emtpy.Use SetDomainId method to set.")
}
// !!!特殊调用
return self.GetInContextWithSpec(self.orderCtx, "orders-resource", id, querys, "")
}
func (self *SOrderManager) PerformAction(action string, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
request := self.newRequest("POST", id, action, self.orderCtx)
request.SetContent([]byte(getContent(params)))
return self._do(request, "")
}
+18 -2
View File
@@ -4,15 +4,31 @@ import (
"net/http"
"yunion.io/x/onecloud/pkg/util/huawei/client/auth"
"yunion.io/x/onecloud/pkg/util/huawei/client/requests"
)
type SPortManager struct {
ResourceManager
}
func NewPortManager(regionId string, signer auth.Signer) *SPortManager {
type portProject struct {
projectId string
}
// port接口查询时若非默认project,需要在header中指定X-Project-ID。url中未携带project信息(与其他接口相比有一点特殊)
// 绕过了ResourceManager中的projectid。直接在发送json请求前注入X-Project-ID
func (self *portProject) Process(request requests.IRequest) {
request.AddHeaderParam("X-Project-Id", self.projectId)
}
func NewPortManager(regionId string, projectId string, signer auth.Signer) *SPortManager {
var requestHook portProject
if len(projectId) > 0 {
requestHook = portProject{projectId: projectId}
}
return &SPortManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}, requestHook: &requestHook},
ServiceName: ServiceNameVPC,
Region: regionId,
ProjectId: "",
@@ -23,3 +23,17 @@ func NewSecurityGroupManager(regionId string, projectId string, signer auth.Sign
ResourceKeyword: "security-groups",
}}
}
func NewNovaSecurityGroupManager(regionId string, projectId string, signer auth.Signer) *SSecurityGroupManager {
return &SSecurityGroupManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: ServiceNameECS,
Region: regionId,
ProjectId: projectId,
version: "v2.1",
Keyword: "security_group",
KeywordPlural: "security_groups",
ResourceKeyword: "os-security-groups",
}}
}
+50 -1
View File
@@ -5,6 +5,7 @@ import (
"strconv"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/util/huawei/client/auth"
"yunion.io/x/onecloud/pkg/util/huawei/client/responses"
)
@@ -13,6 +14,8 @@ type SServerManager struct {
ResourceManager
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212668.html
// v.1.1 新增支持创建包年/包月的弹性云服务器。!!但是不支持查询等调用 https://support.huaweicloud.com/api-ecs/zh-cn_topic_0093055772.html
func NewServerManager(regionId, projectId string, signer auth.Signer) *SServerManager {
return &SServerManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
@@ -39,5 +42,51 @@ func (self *SServerManager) List(querys map[string]string) (*responses.ListResul
}
querys["offset"] = strconv.Itoa(n + 1)
}
return self.ListInContext(nil, "detail", querys)
return self.ListInContextWithSpec(nil, "detail", querys, self.KeywordPlural)
}
/*
返回job id 或者 order id
https://support.huaweicloud.com/api-ecs/zh-cn_topic_0093055772.html
创建按需的弹性云服务 ——> job_id 任务ID (返回数据uuid举例:"70a599e0-31e7-49b7-b260-868f441e862b")
包年包月机器 --> order_id (返回数据举例: "CS1711152257C60TL")
*/
func (self *SServerManager) AsyncCreate(params jsonutils.JSONObject) (string, error) {
origin_version := self.version
self.version = "v1.1"
defer func() { self.version = origin_version }()
ret, err := self.CreateInContextWithSpec(nil, "", params, "")
if err != nil {
return "", err
}
// 按需机器
jobId, err := ret.GetString("job_id")
if err == nil {
return jobId, nil
}
// 包年包月机器
return ret.GetString("order_id")
}
func (self *SServerManager) Create(params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return nil, fmt.Errorf("not supported.please use AsyncCreate")
}
// 不推荐使用这个manager
func NewNovaServerManager(regionId, projectId string, signer auth.Signer) *SServerManager {
return &SServerManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: ServiceNameECS,
Region: regionId,
ProjectId: projectId,
version: "v2.1",
Keyword: "server",
KeywordPlural: "servers",
ResourceKeyword: "servers",
}}
}
@@ -26,5 +26,22 @@ func NewSnapshotManager(regionId, projectId string, signer auth.Signer) *SSnapsh
}
func (self *SSnapshotManager) List(querys map[string]string) (*responses.ListResult, error) {
return self.ListInContext(nil, "detail", querys)
return self.ListInContextWithSpec(nil, "detail", querys, self.KeywordPlural)
}
// https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408629.html
// 回滚快照只能用这个manger。其他情况请不要使用
// 另外,香港-亚太还支持另外一个接口。https://support.huaweicloud.com/api-evs/zh-cn_topic_0142374138.html
func NewOsSnapshotManager(regionId string, projectId string, signer auth.Signer) *SSnapshotManager {
return &SSnapshotManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: ServiceNameEVS,
Region: regionId,
ProjectId: projectId,
version: "v2",
Keyword: "snapshot",
KeywordPlural: "snapshots",
ResourceKeyword: "os-vendor-snapshots",
}}
}
@@ -0,0 +1,25 @@
package modules
import (
"net/http"
"yunion.io/x/onecloud/pkg/util/huawei/client/auth"
)
type SUserManager struct {
ResourceManager
}
func NewUserManager(signer auth.Signer) *SUserManager {
return &SUserManager{ResourceManager: ResourceManager{
BaseManager: BaseManager{signer: signer, httpClient: &http.Client{}},
ServiceName: ServiceNameIAM,
Region: "",
ProjectId: "",
version: "v3",
Keyword: "user",
KeywordPlural: "users",
ResourceKeyword: "users",
}}
}
+18
View File
@@ -2,3 +2,21 @@ package huawei
// 华为云返回的时间格式
const DATETIME_FORMAT = "2006-01-02T15:04:05.999999999"
// Task status
const TASK_SUCCESS = "SUCCESS"
// Charging Type
const (
POST_PAID = "postPaid" // 按需付费
PRE_PAID = "prePaid" // 包年包月
)
// 资源类型 https://support.huaweicloud.com/api-oce/zh-cn_topic_0079291752.html
const (
RESOURCE_TYPE_VM = "hws.resource.type.vm" // ECS虚拟机
RESOURCE_TYPE_VOLUME = "hws.resource.type.volume" // EVS卷
RESOURCE_TYPE_BANDWIDTH = "hws.resource.type.bandwidth" // VPC带宽
RESOURCE_TYPE_IP = "hws.resource.type.ip" // VPC公网IP
RESOURCE_TYPE_IMAGE = "hws.resource.type.marketplace" // 市场镜像
)
+92 -8
View File
@@ -12,6 +12,20 @@ import (
"yunion.io/x/onecloud/pkg/compute/models"
)
/*
华为云云硬盘
======创建==========
1.磁盘只能挂载到同一可用区的云服务器内,创建后不支持更换可用区
2.计费模式 包年包月/按需计费
3.*支持自动备份
共享盘 和 普通盘:https://support.huaweicloud.com/productdesc-evs/zh-cn_topic_0032860759.html
根据是否支持挂载至多台云服务器可以将云硬盘分为非共享云硬盘和共享云硬盘。
一个非共享云硬盘只能挂载至一台云服务器,而一个共享云硬盘可以同时挂载至多台云服务器。
单个共享云硬盘最多可同时挂载给16个云服务器。目前,共享云硬盘只适用于数据盘,不支持系统盘。
*/
type Attachment struct {
ServerID string `json:"server_id"`
AttachmentID string `json:"attachment_id"`
@@ -41,7 +55,7 @@ type VolumeImageMetadata struct {
MinDisk string `json:"min_disk"`
SupportKVM string `json:"__support_kvm"`
VirtualEnvType string `json:"virtual_env_type"`
Size string `json:"size"`
SizeGB string `json:"size"`
OSVersion string `json:"__os_version"`
OSBit string `json:"__os_bit"`
SupportKVMHi1822Hiovs string `json:"__support_kvm_hi1822_hiovs"`
@@ -65,7 +79,7 @@ type SDisk struct {
Status string `json:"status"`
Attachments []Attachment `json:"attachments"`
Description string `json:"description"`
Size int64 `json:"size"`
SizeGB int `json:"size"`
Metadata DiskMeta `json:"metadata"`
Encrypted bool `json:"encrypted"`
Bootable string `json:"bootable"`
@@ -182,7 +196,7 @@ func (self *SDisk) GetDiskFormat() string {
}
func (self *SDisk) GetDiskSizeMB() int {
return int(self.Size * 1024)
return int(self.SizeGB * 1024)
}
func (self *SDisk) checkAutoDelete(attachments []Attachment) bool {
@@ -259,11 +273,26 @@ func (self *SDisk) GetAccessPath() string {
}
func (self *SDisk) Delete(ctx context.Context) error {
panic("implement me")
if _, err := self.storage.zone.region.GetDisk(self.GetId()); err == cloudprovider.ErrNotFound {
log.Errorf("Failed to find disk %s when delete", self.GetId())
return nil
}
return self.storage.zone.region.DeleteDisk(self.GetId())
}
func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) {
panic("implement me")
if snapshotId, err := self.storage.zone.region.CreateSnapshot(self.GetId(), name, desc); err != nil {
log.Errorf("createSnapshot fail %s", err)
return nil, err
} else if snapshot, err := self.getSnapshot(snapshotId); err != nil {
return nil, err
} else {
snapshot.region = self.storage.zone.region
if err := cloudprovider.WaitStatus(snapshot, models.SNAPSHOT_READY, 15*time.Second, 3600*time.Second); err != nil {
return nil, err
}
return snapshot, nil
}
}
func (self *SDisk) getSnapshot(snapshotId string) (*SSnapshot, error) {
@@ -302,15 +331,17 @@ func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
}
func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error {
panic("implement me")
sizeGb := newSizeMB / 1024
return self.storage.zone.region.resizeDisk(self.GetId(), sizeGb)
}
func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
panic("implement me")
return self.storage.zone.region.resetDisk(self.GetId(), snapshotId)
}
// 华为云不支持重置
func (self *SDisk) Rebuild(ctx context.Context) error {
panic("implement me")
return cloudprovider.ErrNotSupported
}
func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
@@ -332,3 +363,56 @@ func (self *SRegion) GetDisks(zoneId string, offset int, limit int) ([]SDisk, in
err := DoList(self.ecsClient.Disks.List, querys, &disks)
return disks, len(disks), err
}
// https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762427.html
func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, snapshotId string, desc string) (string, error) {
params := jsonutils.NewDict()
volumeObj := jsonutils.NewDict()
volumeObj.Add(jsonutils.NewString(name), "name")
volumeObj.Add(jsonutils.NewString(zoneId), "availability_zone")
volumeObj.Add(jsonutils.NewString(desc), "description")
volumeObj.Add(jsonutils.NewString(category), "volume_type")
volumeObj.Add(jsonutils.NewInt(int64(sizeGb)), "size")
if len(snapshotId) > 0 {
volumeObj.Add(jsonutils.NewString(snapshotId), "snapshot_id")
}
params.Add(volumeObj, "volume")
disk := SDisk{}
err := DoCreate(self.ecsClient.Disks.Create, params, &disk)
return disk.ID, err
}
func (self *SRegion) DeleteDisk(diskId string) error {
return DoDelete(self.ecsClient.Disks.Delete, diskId, nil, nil)
}
/*
扩容状态为available的云硬盘时,没有约束限制。
扩容状态为in-use的云硬盘时,有以下约束:
不支持共享云硬盘,即multiattach参数值必须为false。
云硬盘所挂载的云服务器状态必须为ACTIVE、PAUSED、SUSPENDED、SHUTOFF才支持扩容
*/
func (self *SRegion) resizeDisk(diskId string, sizeGB int64) error {
params := jsonutils.NewDict()
osExtendObj := jsonutils.NewDict()
osExtendObj.Add(jsonutils.NewInt(sizeGB), "new_size") // GB
params.Add(osExtendObj, "os-extend")
_, err := self.ecsClient.Disks.PerformAction2("action", diskId, params, "")
return err
}
/*
https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408629.html
只支持快照回滚到源云硬盘,不支持快照回滚到其它指定云硬盘。
只有云硬盘状态处于“available”或“error_rollbacking”状态才允许快照回滚到源云硬盘。
*/
func (self *SRegion) resetDisk(diskId, snapshotId string) (string, error) {
params := jsonutils.NewDict()
rollbackObj := jsonutils.NewDict()
rollbackObj.Add(jsonutils.NewString(diskId), "volume_id")
params.Add(rollbackObj, "rollback")
_, err := self.ecsClient.OsSnapshots.PerformAction2("rollback", snapshotId, params, "")
return diskId, err
}
+38
View File
@@ -0,0 +1,38 @@
package huawei
import "yunion.io/x/onecloud/pkg/util/huawei/client"
// https://support.huaweicloud.com/api-iam/zh-cn_topic_0057845574.html
// 租户列表
type SDomain struct {
Description string `json:"description"`
Enabled bool `json:"enabled"`
ID string `json:"id"`
Name string `json:"name"`
}
func (self *SHuaweiClient) getDomains() ([]SDomain, error) {
huawei, _ := client.NewClientWithAccessKey("", "", self.accessKey, self.secret)
domains := make([]SDomain, 0)
err := DoList(huawei.Domains.List, nil, &domains)
return domains, err
}
func (self *SHuaweiClient) getEnabledDomains() ([]SDomain, error) {
huawei, _ := client.NewClientWithAccessKey("", "", self.accessKey, self.secret)
domains := make([]SDomain, 0)
err := DoList(huawei.Domains.List, nil, &domains)
if err != nil {
return domains, err
}
enabledDomains := make([]SDomain, 0)
for i := range domains {
domain := domains[i]
if domain.Enabled {
enabledDomains = append(enabledDomains, domain)
}
}
return enabledDomains, err
}
+124 -20
View File
@@ -1,6 +1,7 @@
package huawei
import (
"fmt"
"time"
"yunion.io/x/jsonutils"
@@ -11,8 +12,8 @@ import (
type TInternetChargeType string
const (
InternetChargeByTraffic = TInternetChargeType("PayByTraffic")
InternetChargeByBandwidth = TInternetChargeType("PayByBandwidth")
InternetChargeByTraffic = TInternetChargeType("traffic")
InternetChargeByBandwidth = TInternetChargeType("bandwidth")
)
type Port struct {
@@ -143,7 +144,6 @@ func (self *SEipAddress) GetAssociationType() string {
}
func (self *SEipAddress) GetAssociationExternalId() string {
// todo: implement me返回关联的实例
// network/0273a359d61847fc83405926c958c746/ext-floatingips?tenantId=0273a359d61847fc83405926c958c746&limit=2000
// 只能通过 port id 反查device id.
if len(self.PortId) > 0 {
@@ -185,17 +185,35 @@ func (self *SEipAddress) Delete() error {
}
func (self *SEipAddress) Associate(instanceId string) error {
err := self.region.AssociateEip(self.ID, instanceId)
portId, err := self.region.GetInstancePortId(instanceId)
if err != nil {
return err
}
if len(self.PortId) > 0 {
if self.PortId == portId {
return nil
}
return fmt.Errorf("eip %s aready associate with port %s", self.GetId(), self.PortId)
}
err = self.region.AssociateEipWithPortId(self.ID, portId)
if err != nil {
return err
}
err = cloudprovider.WaitStatus(self, models.EIP_STATUS_READY, 10*time.Second, 180*time.Second)
return err
}
func (self *SEipAddress) Dissociate() error {
// todo : implement me
err := self.region.DissociateEip(self.ID, "")
port, err := self.region.GetPort(self.PortId)
if err != nil {
return err
}
err = self.region.DissociateEip(self.ID, port.DeviceID)
if err != nil {
return err
}
@@ -204,13 +222,48 @@ func (self *SEipAddress) Dissociate() error {
}
func (self *SEipAddress) ChangeBandwidth(bw int) error {
return self.region.UpdateEipBandwidth(self.ID, bw)
return self.region.UpdateEipBandwidth(self.BandwidthID, bw)
}
func (self *SRegion) GetInstancePortId(instanceId string) (string, error) {
// 目前只绑定一个网卡
// todo: 还需要按照ports状态进行过滤
ports, err := self.GetPorts(instanceId)
if err != nil {
return "", err
}
if len(ports) == 0 {
return "", fmt.Errorf("AssociateEip instance %s port is empty", instanceId)
}
return ports[0].ID, nil
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090596.html
func (region *SRegion) AllocateEIP(bwMbps int, chargeType TInternetChargeType) (*SEipAddress, error) {
// todo: implement me
return &SEipAddress{}, nil
func (self *SRegion) AllocateEIP(name string, bwMbps int, chargeType TInternetChargeType, bgpType string) (*SEipAddress, error) {
paramsStr := `
{
"publicip": {
"type": "%s",
"ip_version": 4
},
"bandwidth": {
"name": "%s",
"size": %d,
"share_type": "PER",
"charge_mode": "%s"
}
}
`
if len(bgpType) == 0 {
return nil, fmt.Errorf("AllocateEIP bgp type should not be empty")
}
paramsStr = fmt.Sprintf(paramsStr, bgpType, name, bwMbps, chargeType)
params, _ := jsonutils.ParseString(paramsStr)
eip := SEipAddress{}
err := DoCreate(self.ecsClient.Eips.Create, params, &eip)
return &eip, err
}
func (self *SRegion) GetEip(eipId string) (*SEipAddress, error) {
@@ -221,23 +274,62 @@ func (self *SRegion) GetEip(eipId string) (*SEipAddress, error) {
}
func (self *SRegion) DeallocateEIP(eipId string) error {
// todo : implement me
return cloudprovider.ErrNotSupported
_, err := self.ecsClient.Eips.Delete(eipId, nil)
return err
}
func (self *SRegion) AssociateEip(eipId string, instanceId string) error {
// todo : implement me
return cloudprovider.ErrNotSupported
portId, err := self.GetInstancePortId(instanceId)
if err != nil {
return err
}
return self.AssociateEipWithPortId(eipId, portId)
}
func (self *SRegion) AssociateEipWithPortId(eipId string, portId string) error {
params := jsonutils.NewDict()
publicIPObj := jsonutils.NewDict()
publicIPObj.Add(jsonutils.NewString(portId), "port_id")
params.Add(publicIPObj, "publicip")
_, err := self.ecsClient.Eips.Update(eipId, params)
return err
}
func (self *SRegion) DissociateEip(eipId string, instanceId string) error {
// todo : implement me
return cloudprovider.ErrNotSupported
eip, err := self.GetEip(eipId)
if err != nil {
return err
}
// 已经是解绑状态
if eip.Status == "DOWN" {
return nil
}
remoteInstanceId := eip.GetAssociationExternalId()
if remoteInstanceId != instanceId {
return fmt.Errorf("eip %s associate with another instance %s", eipId, remoteInstanceId)
}
paramsStr := `{"publicip":{"port_id":null}}`
params, _ := jsonutils.ParseString(paramsStr)
_, err = self.ecsClient.Eips.Update(eipId, params)
return err
}
func (self *SRegion) UpdateEipBandwidth(eipId string, bw int) error {
// todo : implement me
return cloudprovider.ErrNotSupported
func (self *SRegion) UpdateEipBandwidth(bandwidthId string, bw int) error {
paramStr := `{
"bandwidth":
{
"size": %d
}
}`
paramStr = fmt.Sprintf(paramStr, bw)
params, _ := jsonutils.ParseString(paramStr)
_, err := self.ecsClient.Bandwidths.Update(bandwidthId, params)
return err
}
func (self *SRegion) GetEipBandwidth(bandwidthId string) (Bandwidth, error) {
@@ -248,6 +340,18 @@ func (self *SRegion) GetEipBandwidth(bandwidthId string) (Bandwidth, error) {
func (self *SRegion) GetPort(portId string) (Port, error) {
port := Port{}
err := DoGet(self.ecsClient.Bandwidths.Get, portId, nil, &port)
err := DoGet(self.ecsClient.Port.Get, portId, nil, &port)
return port, err
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0030591299.html
func (self *SRegion) GetPorts(instanceId string) ([]Port, error) {
ports := make([]Port, 0)
querys := map[string]string{}
if len(instanceId) > 0 {
querys["device_id"] = instanceId
}
err := DoList(self.ecsClient.Port.List, querys, &ports)
return ports, err
}
+134 -9
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/util/billing"
@@ -53,7 +54,10 @@ func (self *SHost) GetIVMs() ([]cloudprovider.ICloudVM, error) {
return nil, err
}
vms = append(vms, parts...)
for _, vm := range parts {
vm.host = self
vms = append(vms, vm)
}
if count < limit {
break
@@ -80,6 +84,7 @@ func (self *SHost) GetIVMs() ([]cloudprovider.ICloudVM, error) {
func (self *SHost) GetIVMById(id string) (cloudprovider.ICloudVM, error) {
vm, err := self.zone.region.GetInstanceByID(id)
vm.host = self
return &vm, err
}
@@ -165,18 +170,138 @@ func (self *SHost) GetManagerId() string {
return self.zone.region.client.providerId
}
func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, vswitchId string, ipAddr string, desc string,
passwd string, storageType string, diskSizes []int, publicKey string, extSecGrpId string, userData string, billingCycle *billing.SBillingCycle) (cloudprovider.ICloudVM, error) {
// todo: implement me
panic("implement me")
func (self *SHost) GetInstanceById(instanceId string) (*SInstance, error) {
instance, err := self.zone.region.GetInstanceByID(instanceId)
if err != nil {
return nil, err
}
instance.host = self
return &instance, nil
}
func (self *SHost) CreateVM2(name string, imgId string, sysDiskSize int, instanceType string, vswitchId string, ipAddr string, desc string,
passwd string, storageType string, diskSizes []int, publicKey string, extSecGrpId string, userData string, billingCycle *billing.SBillingCycle) (cloudprovider.ICloudVM, error) {
// todo: implement me
panic("implement me")
func (self *SHost) CreateVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, networkId string, ipAddr string, desc string,
passwd string, storageType string, diskSizes []int, publicKey string, extSecGrpId string, userData string, bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) {
vmId, err := self._createVM(name, imgId, sysDiskSize, cpu, memMB, "", networkId, ipAddr, desc, passwd, storageType, diskSizes, publicKey, extSecGrpId, userData, bc)
if err != nil {
return nil, err
}
vm, err := self.GetInstanceById(vmId)
if err != nil {
return nil, err
}
return vm, err
}
func (self *SHost) CreateVM2(name string, imgId string, sysDiskSize int, instanceType string, networkId string, ipAddr string, desc string,
passwd string, storageType string, diskSizes []int, publicKey string, extSecGrpId string, userData string, bc *billing.SBillingCycle) (cloudprovider.ICloudVM, error) {
vmId, err := self._createVM(name, imgId, sysDiskSize, 0, 0, instanceType, networkId, ipAddr, desc, passwd, storageType, diskSizes, publicKey, extSecGrpId, userData, bc)
if err != nil {
return nil, err
}
vm, err := self.GetInstanceById(vmId)
if err != nil {
return nil, err
}
return vm, err
}
func (self *SHost) GetIHostNics() ([]cloudprovider.ICloudHostNetInterface, error) {
return nil, cloudprovider.ErrNotSupported
}
func (self *SHost) _createVM(name string, imgId string, sysDiskSize int, cpu int, memMB int, instanceType string,
networkId string, ipAddr string, desc string, passwd string,
storageType string, diskSizes []int, publicKey string, secgroupId string,
userData string, bc *billing.SBillingCycle) (string, error) {
net := self.zone.getNetworkById(networkId)
if net == nil {
return "", fmt.Errorf("invalid network ID %s", networkId)
}
if net.wire == nil {
log.Errorf("network's wire is empty")
return "", fmt.Errorf("network's wire is empty")
}
if net.wire.vpc == nil {
log.Errorf("wire's vpc is empty")
return "", fmt.Errorf("wire's vpc is empty")
}
// 同步keypair
var err error
keypair := ""
if len(publicKey) > 0 {
keypair, err = self.zone.region.syncKeypair(publicKey)
if err != nil {
return "", err
}
}
// 镜像及硬盘配置
img, err := self.zone.region.GetImage(imgId)
if err != nil {
log.Errorf("getiamge %s fail %s", imgId, err)
return "", err
}
if img.Status != ImageStatusActive {
log.Errorf("image %s status %s", imgId, img.Status)
return "", fmt.Errorf("image not ready")
}
disks := make([]SDisk, len(diskSizes)+1)
disks[0].SizeGB = img.SizeGB
if sysDiskSize > 0 && sysDiskSize > img.SizeGB {
disks[0].SizeGB = sysDiskSize
}
disks[0].VolumeType = storageType
for i, sz := range diskSizes {
disks[i+1].SizeGB = sz
disks[i+1].VolumeType = storageType
}
secgroup, err := self.zone.region.GetSecurityGroupDetails(secgroupId)
if err != nil {
return "", err
}
// 创建实例
if len(instanceType) > 0 {
log.Debugf("Try instancetype : %s", instanceType)
vmId, err := self.zone.region.CreateInstance(name, imgId, instanceType, networkId, secgroupId, secgroup.VpcID, self.zone.GetId(), desc, disks, ipAddr, keypair, passwd, userData, bc)
if err != nil {
log.Errorf("Failed for %s: %s", instanceType, err)
return "", fmt.Errorf("Failed to create, specification %s not supported", instanceType)
} else {
return vmId, nil
}
}
// 匹配实例类型
instanceTypes, err := self.zone.region.GetMatchInstanceTypes(cpu, memMB, self.zone.GetId())
if err != nil {
return "", err
}
if len(instanceTypes) == 0 {
return "", fmt.Errorf("instance type %dC%dMB not avaiable", cpu, memMB)
}
for _, instType := range instanceTypes {
instanceTypeId := instType.Name
log.Debugf("Try instancetype : %s", instanceTypeId)
vmId, err := self.zone.region.CreateInstance(name, imgId, instanceType, networkId, secgroupId, secgroup.VpcID, self.zone.GetId(), desc, disks, ipAddr, keypair, passwd, userData, bc)
if err != nil {
log.Errorf("Failed for %s: %s", instanceTypeId, err)
} else {
return vmId, nil
}
}
return "", fmt.Errorf("Failed to create, specification not supported")
}
+49 -2
View File
@@ -15,6 +15,7 @@ import (
1.同步的子账户中有一条空记录.需要查原因
2.安全组同步需要进一步确认
3.实例接口需要进一步确认
4.BGP type 目前是hard code在代码中。需要考虑从cloudmeta服务中查询
*/
const (
@@ -220,13 +221,59 @@ func (self *SHuaweiClient) GetIStorageById(id string) (cloudprovider.ICloudStora
return nil, cloudprovider.ErrNotFound
}
// 总账户余额
type SAccountBalance struct {
AvailableAmount float64
}
// 账户余额
// https://support.huaweicloud.com/api-oce/zh-cn_topic_0109685133.html
type SBalance struct {
Amount float64 `json:"amount"`
Currency string `json:"currency"`
AccountID string `json:"account_id"`
AccountType int64 `json:"account_type"`
DesignatedAmount *int64 `json:"designated_amount,omitempty"`
CreditAmount *int64 `json:"credit_amount,omitempty"`
MeasureUnit int64 `json:"measure_unit"`
}
// 这里的余额指的是所有租户的总余额
func (self *SHuaweiClient) QueryAccountBalance() (*SAccountBalance, error) {
// todo: implement me
return nil, nil
domains, err := self.getEnabledDomains()
if err != nil {
return nil, err
}
amount := float64(0)
for _, domain := range domains {
v, err := self.queryDomainBalance(domain.ID)
if err != nil {
return nil, err
}
amount += v
}
return &SAccountBalance{AvailableAmount: amount}, nil
}
// https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0075213309.html
func (self *SHuaweiClient) queryDomainBalance(domainId string) (float64, error) {
huawei, _ := client.NewClientWithAccessKey("", "", self.accessKey, self.secret)
huawei.Balances.SetDomainId(domainId)
balances := make([]SBalance, 0)
err := DoList(huawei.Balances.List, nil, &balances)
if err != nil {
return 0, err
}
amount := float64(0)
for _, balance := range balances {
amount += balance.Amount
}
return amount, nil
}
func (self *SHuaweiClient) GetVersion() string {
+177 -12
View File
@@ -2,18 +2,32 @@ package huawei
import (
"context"
"fmt"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
)
type ImageStatusType string
type ImageOwnerType string
const ImageOwnerSelf ImageOwnerType = "private"
const (
ImageOwnerPublic ImageOwnerType = "gold" // 公共镜像:gold
ImageOwnerSelf ImageOwnerType = "private" // 私有镜像:private
ImageOwnerShared ImageOwnerType = "shared" // 共享镜像:shared
)
const (
ImageStatusQueued = "queued" // queued:表示镜像元数据已经创建成功,等待上传镜像文件。
ImageStatusSaving = "saving" // saving:表示镜像正在上传文件到后端存储。
ImageStatusDeleted = "deleted" // deleted:表示镜像已经删除。
ImageStatusKilled = "killed" // killed:表示镜像上传错误。
ImageStatusActive = "active" // active:表示镜像可以正常使用
)
// https://support.huaweicloud.com/api-ims/zh-cn_topic_0020091565.html
type SImage struct {
@@ -39,7 +53,7 @@ type SImage struct {
Visibility string `json:"visibility"`
VirtualEnvType string `json:"virtual_env_type"`
Platform string `json:"__platform"`
Size int64 `json:"size"`
SizeGB int `json:"size"`
ImageSize int64 `json:"__image_size"`
OSBit string `json:"__os_bit"`
OSVersion string `json:"__os_version"`
@@ -63,11 +77,11 @@ func (self *SImage) GetGlobalId() string {
func (self *SImage) GetStatus() string {
switch self.Status {
case "queued":
case ImageStatusQueued:
return models.CACHED_IMAGE_STATUS_CACHING
case "active":
case ImageStatusActive:
return models.CACHED_IMAGE_STATUS_READY
case "killed":
case ImageStatusKilled:
return models.CACHED_IMAGE_STATUS_CACHE_FAILED
default:
return models.CACHED_IMAGE_STATUS_CACHE_FAILED
@@ -76,11 +90,11 @@ func (self *SImage) GetStatus() string {
func (self *SImage) GetImageStatus() string {
switch self.Status {
case "queued":
case ImageStatusQueued:
return cloudprovider.IMAGE_STATUS_QUEUED
case "active":
case ImageStatusActive:
return cloudprovider.IMAGE_STATUS_ACTIVE
case "killed":
case ImageStatusKilled:
return cloudprovider.IMAGE_STATUS_KILLED
default:
return cloudprovider.IMAGE_STATUS_KILLED
@@ -166,7 +180,6 @@ func (self *SImage) GetMetadata() *jsonutils.JSONDict {
}
func (self *SImage) Delete(ctx context.Context) error {
// todo: implement me
return self.storageCache.region.DeleteImage(self.GetId())
}
@@ -204,6 +217,158 @@ func (self *SRegion) GetImages(status string, imagetype ImageOwnerType, name str
}
func (self *SRegion) DeleteImage(imageId string) error {
// todo: implement me
return nil
return DoDelete(self.ecsClient.OpenStackImages.Delete, imageId, nil, nil)
}
func (self *SRegion) GetImageByName(name string) (*SImage, error) {
if len(name) == 0 {
return nil, fmt.Errorf("image name should not be empty")
}
images, _, err := self.GetImages("", ImageOwnerType(""), name, 1, "")
if err != nil {
return nil, err
}
if len(images) == 0 {
return nil, cloudprovider.ErrNotFound
}
log.Debugf("%d image found match name %s", len(images), name)
return &images[0], nil
}
/* https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092109.html
os version 取值范围: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html
用于创建私有镜像的源云服务器系统盘大小大于等于40GB且不超过1024GB。
目前支持vhd,zvhd、raw,qcow2
todo: 考虑使用镜像快速导入。 https://support.huaweicloud.com/api-ims/zh-cn_topic_0133188204.html
使用OBS文件创建镜像
* openstack原生接口支持的格式:https://support.huaweicloud.com/api-ims/zh-cn_topic_0031615566.html
*/
func (self *SRegion) ImportImageJob(name string, osDist string, osVersion string, osArch string, bucket string, key string, minDiskGB int64) (string, error) {
os_version, err := stdVersion(osDist, osVersion, osArch)
log.Debugf("%s %s %s: %s", osDist, osVersion, osArch, os_version)
if err != nil {
log.Debugf(err.Error())
}
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(name), "name")
image_url := fmt.Sprintf("%s:%s", bucket, key)
params.Add(jsonutils.NewString(image_url), "image_url")
if len(os_version) > 0 {
params.Add(jsonutils.NewString(os_version), "os_version")
}
params.Add(jsonutils.NewBool(true), "is_config_init")
params.Add(jsonutils.NewBool(true), "is_config")
params.Add(jsonutils.NewInt(minDiskGB), "min_disk")
ret, err := self.ecsClient.Images.PerformAction2("action", "", params, "")
if err != nil {
return "", err
}
return ret.GetString("job_id")
}
func formatVersion(osDist string, osVersion string) (string, error) {
err := fmt.Errorf("unsupport version %s.reference: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html", osVersion)
dist := strings.ToLower(osDist)
if dist == "ubuntu" || dist == "redhat" || dist == "centos" || dist == "oracle" || dist == "euleros" {
parts := strings.Split(osVersion, ".")
if len(parts) < 2 {
return "", err
}
return parts[0] + "." + parts[1], nil
}
if dist == "debian" {
parts := strings.Split(osVersion, ".")
if len(parts) < 3 {
return "", err
}
return parts[0] + "." + parts[1] + "." + parts[2], nil
}
if dist == "fedora" || dist == "windows" || dist == "suse" {
parts := strings.Split(osVersion, ".")
if len(parts) < 1 {
return "", err
}
return parts[0], nil
}
if dist == "opensuse" {
parts := strings.Split(osVersion, ".")
if len(parts) == 0 {
return "", err
}
if len(parts) == 1 {
return parts[0], nil
}
if len(parts) >= 2 {
return parts[0] + "." + parts[1], nil
}
}
return "", err
}
// todo: 如何保持同步更新
// https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html
func stdVersion(osDist string, osVersion string, osArch string) (string, error) {
// 架构
arch := ""
switch osArch {
case "64", "x86_64":
arch = "64bit"
case "32", "x86_32":
arch = "32bit"
default:
return "", fmt.Errorf("unsupported arch %s.reference: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html", osArch)
}
_dist := strings.Split(strings.TrimSpace(osDist), " ")[0]
_dist = strings.ToLower(_dist)
// 版本
ver, err := formatVersion(_dist, osVersion)
if err != nil {
return "", err
}
// 操作系统
dist := ""
switch _dist {
case "ubuntu":
return fmt.Sprintf("Ubuntu %s server %s", ver, arch), nil
case "redhat":
dist = "Redhat Linux Enterprise"
case "centos":
dist = "CentOS"
case "fedora":
dist = "Fedora"
case "debian":
dist = "Debian GNU/Linux"
case "windows":
dist = "Windows Server"
case "oracle":
dist = "Oracle Linux Server release"
case "suse":
dist = "SUSE Linux Enterprise Server"
case "opensuse":
dist = "OpenSUSE"
case "euleros":
dist = "EulerOS"
default:
return "", fmt.Errorf("unsupported os %s. reference: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html", dist)
}
return fmt.Sprintf("%s %s %s", dist, ver, arch), nil
}
+631 -22
View File
@@ -2,18 +2,28 @@ package huawei
import (
"context"
"fmt"
"strings"
"time"
"strconv"
"sort"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/util/billing"
"yunion.io/x/onecloud/pkg/util/huawei/client/modules"
"yunion.io/x/pkg/util/osprofile"
)
const (
InstanceStatusRunning = "ACTIVE"
InstanceStatusTerminated = "DELETED"
InstanceStatusStopped = "SHUTOFF"
)
type IpAddress struct {
Version string `json:"version"`
Addr string `json:"addr"`
@@ -67,10 +77,10 @@ type SysTag struct {
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html
// https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0100166287.html v1.1 支持创建包年/包月的弹性云服务器
type SInstance struct {
host *SHost
Fault interface{} `json:"fault"`
ID string `json:"id"`
Name string `json:"name"`
Addresses map[string][]IpAddress `json:"addresses"`
@@ -116,6 +126,36 @@ type SInstance struct {
SecurityGroups []SecurityGroup `json:"security_groups"`
}
func compareSet(currentSet []string, newSet []string) (add []string, remove []string, keep []string) {
sort.Strings(currentSet)
sort.Strings(newSet)
i, j := 0, 0
for i < len(currentSet) || j < len(newSet) {
if i < len(currentSet) && j < len(newSet) {
if currentSet[i] == newSet[j] {
keep = append(keep, currentSet[i])
i += 1
j += 1
} else if currentSet[i] < newSet[j] {
remove = append(remove, currentSet[i])
i += 1
} else {
add = append(add, newSet[j])
j += 1
}
} else if i >= len(currentSet) {
add = append(add, newSet[j])
j += 1
} else if j >= len(newSet) {
remove = append(remove, currentSet[i])
i += 1
}
}
return add, remove, keep
}
func (self *SInstance) GetId() string {
return self.ID
}
@@ -162,7 +202,13 @@ func (self *SInstance) GetInstanceType() string {
func (self *SInstance) GetMetadata() *jsonutils.JSONDict {
data := jsonutils.NewDict()
// todo: add price_key here
// cn-north-1::et2.2xlarge.16::win
lowerOs := self.GetOSType()
if strings.HasPrefix(lowerOs, "win") {
lowerOs = "win"
}
priceKey := fmt.Sprintf("%s::%s::%s", self.host.zone.region.GetId(), self.GetInstanceType(), lowerOs)
data.Add(jsonutils.NewString(priceKey), "price_key")
data.Add(jsonutils.NewString(self.host.zone.GetGlobalId()), "zone_ext_id")
if len(self.Image.ID) > 0 {
if image, err := self.host.zone.region.GetImage(self.Image.ID); err != nil {
@@ -171,11 +217,17 @@ func (self *SInstance) GetMetadata() *jsonutils.JSONDict {
data.Update(meta)
}
}
secgroupIds := jsonutils.NewArray()
for _, secgroup := range self.SecurityGroups {
secgroupIds.Add(jsonutils.NewString(secgroup.Name))
_secIds, err := self.host.zone.region.GetInstanceSecrityGroupIds(self.GetId())
if err != nil {
log.Errorf(err.Error())
} else {
secgroupIds := jsonutils.NewArray()
for _, secId := range _secIds {
secgroupIds.Add(jsonutils.NewString(secId))
}
data.Add(secgroupIds, "secgroupIds")
}
data.Add(secgroupIds, "secgroupIds")
return data
}
@@ -311,11 +363,22 @@ func (self *SInstance) GetMachine() string {
}
func (self *SInstance) AssignSecurityGroup(secgroupId string) error {
panic("implement me")
return self.AssignSecurityGroups([]string{secgroupId})
}
func (self *SInstance) AssignSecurityGroups(secgroupIds []string) error {
panic("implement me")
currentSecgroups, err := self.host.zone.region.GetInstanceSecrityGroupIds(self.GetId())
if err != nil {
return err
}
add, remove, _ := compareSet(currentSecgroups, secgroupIds)
err = self.host.zone.region.assignSecurityGroups(add, self.GetId())
if err != nil {
return err
}
return self.host.zone.region.unassignSecurityGroups(remove, self.GetId())
}
func (self *SInstance) GetHypervisor() string {
@@ -323,59 +386,125 @@ func (self *SInstance) GetHypervisor() string {
}
func (self *SInstance) StartVM(ctx context.Context) error {
panic("implement me")
if self.Status == InstanceStatusRunning {
return nil
}
timeout := 300 * time.Second
interval := 15 * time.Second
startTime := time.Now()
for time.Now().Sub(startTime) < timeout {
err := self.Refresh()
if err != nil {
return err
}
if self.GetStatus() == models.VM_RUNNING {
return nil
} else if self.GetStatus() == models.VM_READY {
err := self.host.zone.region.StartVM(self.GetId())
if err != nil {
return err
}
}
time.Sleep(interval)
}
return cloudprovider.ErrTimeout
}
func (self *SInstance) StopVM(ctx context.Context, isForce bool) error {
panic("implement me")
if self.Status == InstanceStatusStopped {
return nil
}
err := self.host.zone.region.StopVM(self.GetId(), isForce)
if err != nil {
return err
}
return cloudprovider.WaitStatus(self, models.VM_READY, 10*time.Second, 300*time.Second) // 5mintues
}
func (self *SInstance) DeleteVM(ctx context.Context) error {
panic("implement me")
for {
err := self.host.zone.region.DeleteVM(self.GetId())
if err != nil && self.Status != InstanceStatusTerminated {
log.Errorf("DeleteVM fail: %s", err)
return err
} else {
break
}
}
return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes
}
func (self *SInstance) UpdateVM(ctx context.Context, name string) error {
panic("implement me")
return self.host.zone.region.UpdateVM(self.GetId(), name)
}
// https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0032380449.html
// 创建云服务器过程中注入用户数据。支持注入文本、文本文件或gzip文件。
// 注入内容,需要进行base64格式编码。注入内容(编码之前的内容)最大长度32KB。
// 对于Linux弹性云服务器,adminPass参数传入时,user_data参数不生效。
func (self *SInstance) UpdateUserData(userData string) error {
panic("implement me")
return cloudprovider.ErrNotSupported
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876349.html 使用原镜像重装
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876971.html 更换系统盘操作系统
// 不支持调整系统盘大小
// todo: 支持注入user_data
func (self *SInstance) RebuildRoot(ctx context.Context, imageId string, passwd string, publicKey string, sysSizeGB int) (string, error) {
panic("implement me")
var err error
if self.Image.ID == imageId {
err = self.host.zone.region.RebuildRoot(ctx, self.GetId(), passwd, publicKey)
if err != nil {
return "", err
}
} else {
err = self.host.zone.region.ChangeRoot(ctx, self.GetId(), imageId, passwd, publicKey)
if err != nil {
return "", err
}
}
// todo: wait job finished here
return "", nil
}
func (self *SInstance) DeployVM(ctx context.Context, name string, password string, publicKey string, deleteKeypair bool, description string) error {
panic("implement me")
return self.host.zone.region.DeployVM(self.GetId(), name, password, publicKey, deleteKeypair, description)
}
func (self *SInstance) ChangeConfig(ctx context.Context, ncpu int, vmem int) error {
panic("implement me")
return self.host.zone.region.ChangeVMConfig(self.OSEXTAZAvailabilityZone, self.GetId(), ncpu, vmem, nil)
}
func (self *SInstance) ChangeConfig2(ctx context.Context, instanceType string) error {
panic("implement me")
return self.host.zone.region.ChangeVMConfig2(self.OSEXTAZAvailabilityZone, self.GetId(), instanceType, nil)
}
// todo:// 返回jsonobject感觉很诡异。不能直接知道内部细节
func (self *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) {
panic("implement me")
return self.host.zone.region.GetInstanceVNCUrl(self.GetId())
}
func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
panic("implement me")
// todo: calc device
return self.host.zone.region.AttachDisk(self.GetId(), diskId, "")
}
func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
panic("implement me")
return self.host.zone.region.DetachDisk(self.GetId(), diskId)
}
func (self *SInstance) CreateDisk(ctx context.Context, sizeMb int, uuid string, driver string) error {
panic("implement me")
return cloudprovider.ErrNotSupported
}
func (self *SInstance) Renew(bc billing.SBillingCycle) error {
panic("implement me")
return self.host.zone.region.RenewInstance(self.GetId(), bc)
}
func (self *SRegion) GetInstances(offset int, limit int) ([]SInstance, int, error) {
@@ -410,3 +539,483 @@ func (self *SRegion) GetInstanceByIds(ids []string) ([]SInstance, int, error) {
return instances, len(instances), nil
}
/*
系统盘大小取值范围:1-1024 GB,且必须不小于镜像min_disk.
*/
type SServerCreate struct {
AvailabilityZone string `json:"availability_zone"`
Name string `json:"name"`
ImageRef string `json:"imageRef"`
RootVolume RootVolume `json:"root_volume"`
DataVolumes []DataVolume `json:"data_volumes"`
FlavorRef string `json:"flavorRef"`
UserData string `json:"user_data"`
Vpcid string `json:"vpcid"`
SecurityGroups []SecGroup `json:"security_groups"`
Nics []NIC `json:"nics"`
KeyName string `json:"key_name"`
AdminPass string `json:"adminPass"`
Count int64 `json:"count"`
Extendparam ServerExtendparam `json:"extendparam"`
ServerTags []ServerTag `json:"server_tags"`
Description string `json:"description"`
}
type DataVolume struct {
Volumetype string `json:"volumetype"`
SizeGB int `json:"size"`
Extendparam *DataVolumeExtendparam `json:"extendparam,omitempty"`
Multiattach *bool `json:"multiattach,omitempty"`
HwPassthrough *string `json:"hw:passthrough,omitempty"`
}
type DataVolumeExtendparam struct {
SnapshotID string `json:"snapshotId"`
}
type ServerExtendparam struct {
ChargingMode string `json:"chargingMode"` // 计费模式 prePaid|postPaid
PeriodType string `json:"periodType"` // 周期类型:month|year
PeriodNum string `json:"periodNum"` // 订购周期数:periodType=month(周期类型为月)时,取值为[1,9]。periodType=year(周期类型为年)时,取值为1。
IsAutoRenew string `json:"isAutoRenew"` // 是否自动续订 true|false
IsAutoPay string `json:"isAutoPay"` // 是否自动从客户的账户中支付 true|false
RegionID string `json:"regionID"`
}
type NIC struct {
SubnetID string `json:"subnet_id"` // 网络ID. 与 SNetwork里的ID对应。统一使用这个ID
IpAddress string `json:"ip_address"`
}
type RootVolume struct {
Volumetype string `json:"volumetype"`
SizeGB int `json:"size"`
}
type SecGroup struct {
ID string `json:"id"`
}
type ServerTag struct {
Key string `json:"key"`
Value string `json:"value"`
}
/*
包月机器退订规则: https://support.huaweicloud.com/usermanual-billing/zh-cn_topic_0083138805.html
5天无理由全额退订:新购资源(不包含续费资源)在开通的五天内且退订次数不超过10次(每账号每年10次)的符合5天无理由全额退订。
非5天无理由退订:不符合5天无理由全额退订条件的退订,都属于非5天无理由退订。非5天无理由退订,不限制退订次数,但需要收取退订手续费。
退订资源的方法: https://support.huaweicloud.com/usermanual-billing/zh-cn_topic_0072297197.html
*/
func (self *SRegion) CreateInstance(name string, imageId string, instanceType string, SubnetId string,
securityGroupId string, vpcId string, zoneId string, desc string, disks []SDisk, ipAddr string,
keypair string, passwd string, userData string, bc *billing.SBillingCycle) (string, error) {
params := SServerCreate{}
params.AvailabilityZone = zoneId
params.Name = name
params.FlavorRef = instanceType
params.ImageRef = imageId
params.KeyName = keypair
params.AdminPass = passwd
params.UserData = userData
params.Description = desc
params.Count = 1
params.Nics = []NIC{{SubnetID: SubnetId, IpAddress: ipAddr}}
params.SecurityGroups = []SecGroup{{ID: securityGroupId}}
params.Vpcid = vpcId
for i, disk := range disks {
if i == 0 {
params.RootVolume.Volumetype = disk.VolumeType
params.RootVolume.SizeGB = disk.SizeGB
} else {
dataVolume := DataVolume{}
dataVolume.Volumetype = disk.VolumeType
dataVolume.SizeGB = disk.SizeGB
params.DataVolumes = append(params.DataVolumes, dataVolume)
}
}
// billing type
if bc != nil {
params.Extendparam.ChargingMode = PRE_PAID
if bc.GetMonths() <= 9 {
params.Extendparam.PeriodNum = strconv.Itoa(bc.GetMonths())
params.Extendparam.PeriodType = "month"
} else {
params.Extendparam.PeriodNum = strconv.Itoa(bc.GetYears())
params.Extendparam.PeriodType = "year"
}
params.Extendparam.RegionID = self.GetId()
params.Extendparam.IsAutoRenew = "false"
params.Extendparam.IsAutoPay = "true"
} else {
params.Extendparam.ChargingMode = POST_PAID
}
serverObj := jsonutils.Marshal(params)
createParams := jsonutils.NewDict()
createParams.Add(serverObj, "server")
_id, err := self.ecsClient.Servers.AsyncCreate(createParams)
if err != nil {
return "", err
}
var ids []string
if params.Extendparam.ChargingMode == POST_PAID {
// 按需计费
ids, err = self.GetAllSubTaskEntityIDs(self.ecsClient.Servers.ServiceType(), _id, "server_id")
} else {
// 包年包月
err = cloudprovider.WaitCreated(10*time.Second, 180*time.Second, func() bool {
ids, err = self.getAllResIdsByType(_id, RESOURCE_TYPE_VM)
if err != nil {
log.Debugf(err.Error())
return false
}
if len(ids) > 0 {
return true
}
return false
})
}
if err != nil {
return "", err
} else if len(ids) == 0 {
return "", fmt.Errorf("CreateInstance job %s result is emtpy", _id)
} else if len(ids) == 1 {
return ids[0], nil
} else {
return "", fmt.Errorf("CreateInstance job %s mutliple instance id returned. %s", _id, ids)
}
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067161469.html
// 添加多个安全组时,建议最多为弹性云服务器添加5个安全组。
// todo: 确认是否需要先删除,再进行添加操作
func (self *SRegion) assignSecurityGroups(secgroupIds []string, instanceId string) error {
_, err := self.GetInstanceByID(instanceId)
if err != nil {
return err
}
for i := range secgroupIds {
secId := secgroupIds[i]
params := jsonutils.NewDict()
secgroupObj := jsonutils.NewDict()
secgroupObj.Add(jsonutils.NewString(secId), "name")
params.Add(secgroupObj, "addSecurityGroup")
_, err := self.ecsClient.NovaServers.PerformAction("action", instanceId, params)
if err != nil {
return err
}
}
return nil
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067161717.html
func (self *SRegion) unassignSecurityGroups(secgroupIds []string, instanceId string) error {
for i := range secgroupIds {
secId := secgroupIds[i]
params := jsonutils.NewDict()
secgroupObj := jsonutils.NewDict()
secgroupObj.Add(jsonutils.NewString(secId), "name")
params.Add(secgroupObj, "removeSecurityGroup")
_, err := self.ecsClient.NovaServers.PerformAction("action", instanceId, params)
if err != nil {
return err
}
}
return nil
}
func (self *SRegion) GetInstanceStatus(instanceId string) (string, error) {
instance, err := self.GetInstanceByID(instanceId)
if err != nil {
return "", err
}
return instance.Status, nil
}
func (self *SRegion) instanceStatusChecking(instanceId, status string) error {
remoteStatus, err := self.GetInstanceStatus(instanceId)
if err != nil {
log.Errorf("Fail to get instance status: %s", err)
return err
}
if status != remoteStatus {
log.Errorf("instanceStatusChecking: vm status is %s expect %s", remoteStatus, status)
return cloudprovider.ErrInvalidStatus
}
return nil
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212207.html
func (self *SRegion) StartVM(instanceId string) error {
if err := self.instanceStatusChecking(instanceId, InstanceStatusStopped); err != nil {
return err
}
params := jsonutils.NewDict()
startObj := jsonutils.NewDict()
serversObj := jsonutils.NewArray()
serverObj := jsonutils.NewDict()
serverObj.Add(jsonutils.NewString(instanceId), "id")
serversObj.Add(serverObj)
startObj.Add(serversObj, "servers")
params.Add(startObj, "os-start")
_, err := self.ecsClient.Servers.PerformAction2("action", "", params, "")
return err
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212651.html
func (self *SRegion) StopVM(instanceId string, isForce bool) error {
if err := self.instanceStatusChecking(instanceId, InstanceStatusRunning); err != nil {
return err
}
params := jsonutils.NewDict()
stopObj := jsonutils.NewDict()
serversObj := jsonutils.NewArray()
serverObj := jsonutils.NewDict()
serverObj.Add(jsonutils.NewString(instanceId), "id")
serversObj.Add(serverObj)
stopObj.Add(serversObj, "servers")
if isForce {
stopObj.Add(jsonutils.NewString("HARD"), "type")
} else {
stopObj.Add(jsonutils.NewString("SOFT"), "type")
}
params.Add(stopObj, "os-stop")
_, err := self.ecsClient.Servers.PerformAction2("action", "", params, "")
return err
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212679.html
// 只删除主机。弹性IP和数据盘需要单独删除
func (self *SRegion) DeleteVM(instanceId string) error {
if err := self.instanceStatusChecking(instanceId, InstanceStatusStopped); err != nil {
return err
}
params := jsonutils.NewDict()
serversObj := jsonutils.NewArray()
serverObj := jsonutils.NewDict()
serverObj.Add(jsonutils.NewString(instanceId), "id")
serversObj.Add(serverObj)
params.Add(serversObj, "servers")
params.Add(jsonutils.NewBool(false), "delete_publicip")
params.Add(jsonutils.NewBool(false), "delete_volume")
_, err := self.ecsClient.Servers.PerformAction2("delete", "", params, "")
return err
}
func (self *SRegion) UpdateVM(instanceId, name string) error {
params := jsonutils.NewDict()
serversObj := jsonutils.NewArray()
serverObj := jsonutils.NewDict()
serverObj.Add(jsonutils.NewString(instanceId), "id")
serversObj.Add(serverObj)
params.Add(serversObj, "servers")
params.Add(jsonutils.NewString(name), "name")
_, err := self.ecsClient.Servers.PerformAction2("server-name", "", params, "")
return err
}
func (self *SRegion) RebuildRoot(ctx context.Context, instanceId, passwd, publicKeyName string) error {
params := jsonutils.NewDict()
reinstallObj := jsonutils.NewDict()
// meta := jsonutils.NewDict()
// meta.Add(jsonutils.NewString(""), "user_data")
if len(passwd) > 0 {
reinstallObj.Add(jsonutils.NewString(passwd), "adminpass")
} else if len(publicKeyName) > 0 {
reinstallObj.Add(jsonutils.NewString(publicKeyName), "keyname")
} else {
return fmt.Errorf("both password and publicKey are empty.")
}
params.Add(reinstallObj, "os-reinstall")
_, err := self.ecsClient.Servers.PerformAction2("reinstallos", instanceId, params, "")
return err
}
func (self *SRegion) ChangeRoot(ctx context.Context, instanceId, imageId, passwd, publicKeyName string) error {
params := jsonutils.NewDict()
changeOsObj := jsonutils.NewDict()
// meta := jsonutils.NewDict()
// meta.Add(jsonutils.NewString(""), "user_data")
if len(passwd) > 0 {
changeOsObj.Add(jsonutils.NewString(passwd), "adminpass")
} else if len(publicKeyName) > 0 {
changeOsObj.Add(jsonutils.NewString(publicKeyName), "keyname")
} else {
return fmt.Errorf("both password and publicKey are empty.")
}
changeOsObj.Add(jsonutils.NewString(imageId), "imageid")
params.Add(changeOsObj, "os-change")
_, err := self.ecsClient.Servers.PerformAction2("changeos", instanceId, params, "")
return err
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212692.html
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0110109377.html
// 一键式重置密码 需要安装安装一键式重置密码插件 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0068095385.html
// 目前不支持直接重置密钥
func (self *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error {
serverObj := jsonutils.NewDict()
if len(name) > 0 {
serverObj.Add(jsonutils.NewString(name), "name")
}
if len(description) > 0 {
serverObj.Add(jsonutils.NewString(description), "description")
}
if serverObj.Size() > 0 {
params := jsonutils.NewDict()
params.Add(serverObj, "server")
// 这里华为返回的image字段是字符串。和SInstance的定义的image是字典结构不一致。
err := DoUpdate(self.ecsClient.NovaServers.Update, instanceId, params, nil)
if err != nil {
return err
}
}
if len(password) > 0 {
params := jsonutils.NewDict()
passwdObj := jsonutils.NewDict()
passwdObj.Add(jsonutils.NewString(password), "new_password")
params.Add(passwdObj, "reset-password")
err := DoUpdateWithSpec(self.ecsClient.NovaServers.UpdateInContextWithSpec, instanceId, "os-reset-password", params)
if err != nil {
return err
}
}
return nil
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212653.html
func (self *SRegion) ChangeVMConfig(zoneId string, instanceId string, ncpu int, vmem int, disks []*SDisk) error {
instanceTypes, err := self.GetMatchInstanceTypes(ncpu, vmem, zoneId)
if err != nil {
return err
}
for _, t := range instanceTypes {
params := jsonutils.NewDict()
resizeObj := jsonutils.NewDict()
resizeObj.Add(jsonutils.NewString(t.ID), "flavorRef")
params.Add(resizeObj, "resize")
_, err := self.ecsClient.Servers.PerformAction2("resize", instanceId, params, "")
if err != nil {
log.Errorf("Failed for %s: %s", t.ID, err)
} else {
return nil
}
}
return fmt.Errorf("Failed to change vm config, specification not supported")
}
func (self *SRegion) ChangeVMConfig2(zoneId string, instanceId string, instanceType string, disks []*SDisk) error {
params := jsonutils.NewDict()
resizeObj := jsonutils.NewDict()
resizeObj.Add(jsonutils.NewString(instanceType), "flavorRef")
params.Add(resizeObj, "resize")
_, err := self.ecsClient.Servers.PerformAction2("resize", instanceId, params, "")
return err
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0142763126.html
// 微版本2.6及以上?
func (self *SRegion) GetInstanceVNCUrl(instanceId string) (jsonutils.JSONObject, error) {
params := jsonutils.NewDict()
vncObj := jsonutils.NewDict()
vncObj.Add(jsonutils.NewString("novnc"), "type")
vncObj.Add(jsonutils.NewString("vnc"), "protocol")
params.Add(vncObj, "remote_console")
ret, err := self.ecsClient.NovaServers.PerformAction2("remote-consoles", instanceId, params, "")
if err != nil {
return nil, err
}
return ret, nil
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022472987.html
// todo: 指定device
// XEN平台虚拟机device为必选参数。
func (self *SRegion) AttachDisk(instanceId string, diskId string, device string) error {
params := jsonutils.NewDict()
volumeObj := jsonutils.NewDict()
volumeObj.Add(jsonutils.NewString(diskId), "volumeId")
params.Add(volumeObj, "volumeAttachment")
_, err := self.ecsClient.Servers.PerformAction2("attachvolume", instanceId, params, "")
return err
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022472988.html
// 默认非强制卸载。delete_flag=0
func (self *SRegion) DetachDisk(instanceId string, diskId string) error {
path := fmt.Sprintf("%s/detachvolume/%s", instanceId, diskId)
return DoDelete(self.ecsClient.Servers.Delete, path, nil, nil)
}
// 目前无接口支持
func (region *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle) error {
return cloudprovider.ErrNotSupported
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0065817702.html
func (self *SRegion) GetInstanceSecrityGroupIds(instanceId string) ([]string, error) {
if len(instanceId) == 0 {
return nil, fmt.Errorf("GetInstanceSecrityGroups instanceId is empty")
}
securitygroups := make([]SSecurityGroup, 0)
ctx := &modules.ManagerContext{InstanceManager: self.ecsClient.NovaServers, InstanceId: instanceId}
err := DoListInContext(self.ecsClient.NovaSecurityGroups.ListInContext, ctx, nil, &securitygroups)
if err != nil {
return nil, err
}
securitygroupIds := []string{}
for _, secgroup := range securitygroups {
securitygroupIds = append(securitygroupIds, secgroup.GetId())
}
return securitygroupIds, nil
}
// https://support.huaweicloud.com/api-oce/zh-cn_topic_0082522030.html
func (self *SRegion) UnsubscribeInstance(instanceId string, domianId string) (jsonutils.JSONObject, error) {
unsubObj := jsonutils.NewDict()
unsubObj.Add(jsonutils.NewInt(1), "unSubType")
unsubObj.Add(jsonutils.NewInt(5), "unsubscribeReasonType")
unsubObj.Add(jsonutils.NewString("no reason"), "unsubscribeReason")
resList := jsonutils.NewArray()
resList.Add(jsonutils.NewString(instanceId))
unsubObj.Add(resList, "resourceIds")
self.ecsClient.Orders.SetDomainId(domianId)
return self.ecsClient.Orders.PerformAction("resources/delete", "", unsubObj)
}
+23 -7
View File
@@ -3,7 +3,7 @@ package huawei
import (
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/util/huawei/client/manager"
"yunion.io/x/onecloud/pkg/util/huawei/client/modules"
"yunion.io/x/pkg/util/netutils"
)
@@ -11,13 +11,31 @@ import (
type Interface struct {
PortState string `json:"port_state"`
FixedIPS []FixedIP `json:"fixed_ips"`
NetID string `json:"net_id"`
NetID string `json:"net_id"` // 网络ID. 与 SNetwork里的ID对应。统一使用这个ID
PortID string `json:"port_id"`
MACAddr string `json:"mac_addr"`
}
/*
subnet: {id: "b09877fc-90d4-4fc8-b343-e6e00cb2b233", name: "subnet-149c", cidr: "192.168.0.0/24",…}
availability_zone: "cn-north-1b"
cidr: "192.168.0.0/24"
dhcp_enable: true
dnsList: ["100.125.1.250", "100.125.21.250"]
gateway_ip: "192.168.0.1"
id: "b09877fc-90d4-4fc8-b343-e6e00cb2b233"
ipv6_enable: false
name: "subnet-149c"
neutron_network_id: "b09877fc-90d4-4fc8-b343-e6e00cb2b233"
neutron_subnet_id: "81fcfaa0-8e73-4472-9eba-3b2b7736d3a7"
primary_dns: "100.125.1.250"
secondary_dns: "100.125.21.250"
status: "ACTIVE"
tags: []
vpc_id: "877f1feb-3dc8-4c2d-92e9-0d94fd7d79dd"}
*/
type FixedIP struct {
SubnetID string `json:"subnet_id"`
SubnetID string `json:"subnet_id"` // 子网ID, 与SNetwork中的 neutron_subnet_id对应. 注意!!! 并不是SNetwork ID。
IPAddress string `json:"ip_address"`
}
@@ -64,7 +82,7 @@ func (self *SInstanceNic) GetINetwork() cloudprovider.ICloudNetwork {
}
func (self *SRegion) getSubnetIdsByInstanceId(instanceId string) ([]string, error) {
ctx := &manager.ManagerContext{InstanceManager: self.ecsClient.Servers, InstanceId: instanceId}
ctx := &modules.ManagerContext{InstanceManager: self.ecsClient.NovaServers, InstanceId: instanceId}
interfaces := make([]Interface, 0)
err := DoListInContext(self.ecsClient.Interface.ListInContext, ctx, nil, &interfaces)
if err != nil {
@@ -73,9 +91,7 @@ func (self *SRegion) getSubnetIdsByInstanceId(instanceId string) ([]string, erro
subnets := make([]string, 0)
for _, i := range interfaces {
for _, net := range i.FixedIPS {
subnets = append(subnets, net.SubnetID)
}
subnets = append(subnets, i.NetID)
}
return subnets, nil
+44
View File
@@ -0,0 +1,44 @@
package huawei
import "strconv"
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212656.html
type SInstanceType struct {
ID string `json:"id"`
Name string `json:"name"`
Vcpus string `json:"vcpus"`
RamMB int `json:"ram"` // 内存大小
OSExtraSpecs OSExtraSpecs `json:"os_extra_specs"` // 扩展规格
}
type OSExtraSpecs struct {
EcsPerformancetype string `json:"ecs:performancetype"`
}
func (self *SRegion) fetchInstanceTypes(zoneId string) ([]SInstanceType, error) {
querys := map[string]string{}
if len(zoneId) > 0 {
querys["availability_zone"] = zoneId
}
instanceTypes := make([]SInstanceType, 0)
err := DoList(self.ecsClient.Flavors.List, querys, &instanceTypes)
return instanceTypes, err
}
func (self *SRegion) GetMatchInstanceTypes(cpu int, memMB int, zoneId string) ([]SInstanceType, error) {
instanceTypes, err := self.fetchInstanceTypes(zoneId)
if err != nil {
return nil, err
}
ret := make([]SInstanceType, 0)
for _, t := range instanceTypes {
// cpu & mem & disk都匹配才行
if t.Vcpus == strconv.Itoa(cpu) && t.RamMB == memMB {
ret = append(ret, t)
}
}
return ret, nil
}
+93
View File
@@ -1,3 +1,96 @@
package huawei
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/aokoli/goutils"
"golang.org/x/crypto/ssh"
"yunion.io/x/jsonutils"
)
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212676.html
type SKeypair struct {
Fingerprint string `json:"fingerprint"`
Name string `json:"name"`
PublicKey string `json:"public_key"`
}
func (self *SRegion) getFingerprint(publicKey string) (string, error) {
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKey))
if err != nil {
return "", fmt.Errorf("publicKey error %s", err)
}
fingerprint := strings.Replace(ssh.FingerprintLegacyMD5(pk), ":", "", -1)
return fingerprint, nil
}
func (self *SRegion) GetKeypairs() ([]SKeypair, int, error) {
keypairs := make([]SKeypair, 0)
err := DoList(self.ecsClient.Keypairs.List, nil, &keypairs)
return keypairs, len(keypairs), err
}
func (self *SRegion) lookUpKeypair(publicKey string) (string, error) {
keypairs, _, err := self.GetKeypairs()
if err != nil {
return "", err
}
fingerprint, err := self.getFingerprint(publicKey)
if err != nil {
return "", err
}
for _, keypair := range keypairs {
if keypair.Fingerprint == fingerprint {
return keypair.Name, nil
}
}
return "", fmt.Errorf("keypair not found %s", err)
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212678.html
func (self *SRegion) ImportKeypair(name, publicKey string) (*SKeypair, error) {
fingerprint, err := self.getFingerprint(publicKey)
if err != nil {
return nil, err
}
keypair := SKeypair{
Name: name,
PublicKey: publicKey,
Fingerprint: fingerprint,
}
keypairObj := jsonutils.Marshal(keypair)
ret := SKeypair{}
err = DoCreate(self.ecsClient.Keypairs.Create, keypairObj, &ret)
return &ret, err
}
func (self *SRegion) importKeypair(publicKey string) (string, error) {
prefix, e := goutils.RandomAlphabetic(6)
if e != nil {
return "", fmt.Errorf("publicKey error %s", e)
}
name := prefix + strconv.FormatInt(time.Now().Unix(), 10)
if k, e := self.ImportKeypair(name, publicKey); e != nil {
return "", fmt.Errorf("keypair import error %s", e)
} else {
return k.Name, nil
}
}
func (self *SRegion) syncKeypair(publicKey string) (string, error) {
name, e := self.lookUpKeypair(publicKey)
if e == nil {
return name, nil
}
return self.importKeypair(publicKey)
}
+13 -1
View File
@@ -2,4 +2,16 @@ package huawei
import "yunion.io/x/onecloud/pkg/cloudprovider"
var LatitudeAndLongitude = map[string]cloudprovider.SGeographicInfo{}
// China: https://developer.huaweicloud.com/endpoint
// International: https://developer-intl.huaweicloud.com/endpoint
// ref: https://countrycode.org
var LatitudeAndLongitude = map[string]cloudprovider.SGeographicInfo{
"cn-east-2": {Latitude: 31.210344, Longitude: 121.455364, City: "Shanghai", CountryCode: "CN"},
"cn-north-1": {Latitude: 39.997743, Longitude: 116.304542, City: "Beijing", CountryCode: "CN"},
"cn-south-1": {Latitude: 23.12911, Longitude: 113.264385, City: "Guangzhou", CountryCode: "CN"},
"cn-south-2": {Latitude: 23.12911, Longitude: 113.264385, City: "Guangzhou", CountryCode: "CN"},
"ap-southeast-1": {Latitude: 22.396428, Longitude: 114.109497, City: "HongKong", CountryCode: "CN"},
"ap-southeast-2": {Latitude: 13.7563309, Longitude: 100.5017651, City: "Bangkok", CountryCode: "TH"},
"eu-west-0": {Latitude: 48.856614, Longitude: 2.3522219, City: "Paris", CountryCode: "FR"},
"cn-northeast-1": {Latitude: 38.91400300000001, Longitude: 121.614682, City: "Shanghai", CountryCode: "CN"},
}
+17 -5
View File
@@ -2,7 +2,7 @@ package huawei
import (
"strconv"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
@@ -50,13 +50,21 @@ func (self *SNetwork) GetGlobalId() string {
return self.ID
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090591.html
func (self *SNetwork) GetStatus() string {
return strings.ToLower(self.Status)
switch self.Status {
case "ACTIVE", "UNKNOWN":
return models.NETWORK_STATUS_AVAILABLE // ? todo: // UNKNOWN
case "ERROR":
return models.NETWORK_STATUS_UNKNOWN
default:
return models.NETWORK_STATUS_UNKNOWN
}
}
func (self *SNetwork) Refresh() error {
log.Debugf("network refresh %s", self.GetId())
new, err := self.wire.zone.region.getNetwork(self.GetId())
new, err := self.wire.region.getNetwork(self.GetId())
if err != nil {
return err
}
@@ -79,6 +87,7 @@ func (self *SNetwork) GetIpStart() string {
pref, _ := netutils.NewIPV4Prefix(self.CIDR)
startIp := pref.Address.NetAddr(pref.MaskLen) // 0
startIp = startIp.StepUp() // 1
startIp = startIp.StepUp() // 2
return startIp.String()
}
@@ -112,8 +121,7 @@ func (self *SNetwork) GetIsPublic() bool {
}
func (self *SNetwork) Delete() error {
// todo: implement me
return nil
return self.wire.region.deleteNetwork(self.GetId())
}
func (self *SNetwork) GetAllocTimeoutSeconds() int {
@@ -141,3 +149,7 @@ func (self *SRegion) GetNetwroks(vpcId string, limit int, marker string) ([]SNet
err := DoList(self.ecsClient.Subnets.List, querys, &networks)
return networks, len(networks), err
}
func (self *SRegion) deleteNetwork(networkId string) error {
return DoDelete(self.ecsClient.Subnets.Delete, networkId, nil, nil)
}
+371
View File
@@ -0,0 +1,371 @@
package obs
import (
"fmt"
"net/url"
"sort"
"strings"
"time"
)
func (obsClient ObsClient) doAuthTemporary(method, bucketName, objectKey string, params map[string]string,
headers map[string][]string, expires int64) (requestUrl string, err error) {
requestUrl, canonicalizedUrl := obsClient.conf.formatUrls(bucketName, objectKey, params, true)
parsedRequestUrl, err := url.Parse(requestUrl)
if err != nil {
return "", err
}
encodeHeaders(headers)
hostName := parsedRequestUrl.Host
isV4 := obsClient.conf.signature == SignatureV4
prepareHostAndDate(headers, hostName, isV4)
if obsClient.conf.securityProvider == nil || obsClient.conf.securityProvider.ak == "" || obsClient.conf.securityProvider.sk == "" {
doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
} else {
if obsClient.conf.securityProvider.securityToken != "" {
params[HEADER_STS_TOKEN_AMZ] = obsClient.conf.securityProvider.securityToken
}
if isV4 {
date, _ := time.Parse(RFC1123_FORMAT, headers[HEADER_DATE_CAMEL][0])
delete(headers, HEADER_DATE_CAMEL)
shortDate := date.Format(SHORT_DATE_FORMAT)
longDate := date.Format(LONG_DATE_FORMAT)
signedHeaders, _headers := getSignedHeaders(headers)
credential, scope := getCredential(obsClient.conf.securityProvider.ak, obsClient.conf.region, shortDate)
params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX
params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
params[PARAM_DATE_AMZ_CAMEL] = longDate
params[PARAM_EXPIRES_AMZ_CAMEL] = Int64ToString(expires)
params[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = strings.Join(signedHeaders, ";")
requestUrl, canonicalizedUrl = obsClient.conf.formatUrls(bucketName, objectKey, params, true)
parsedRequestUrl, _ = url.Parse(requestUrl)
stringToSign := getV4StringToSign(method, canonicalizedUrl, parsedRequestUrl.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, signedHeaders, _headers)
signature := getSignature(stringToSign, obsClient.conf.securityProvider.sk, obsClient.conf.region, shortDate)
requestUrl += fmt.Sprintf("&%s=%s", PARAM_SIGNATURE_AMZ_CAMEL, UrlEncode(signature, false))
} else {
originDate := headers[HEADER_DATE_CAMEL][0]
date, _ := time.Parse(RFC1123_FORMAT, originDate)
expires += date.Unix()
headers[HEADER_DATE_CAMEL] = []string{Int64ToString(expires)}
stringToSign := getV2StringToSign(method, canonicalizedUrl, headers, obsClient.conf.signature == SignatureObs)
signature := UrlEncode(Base64Encode(HmacSha1([]byte(obsClient.conf.securityProvider.sk), []byte(stringToSign))), false)
if strings.Index(requestUrl, "?") < 0 {
requestUrl += "?"
} else {
requestUrl += "&"
}
delete(headers, HEADER_DATE_CAMEL)
requestUrl += fmt.Sprintf("AWSAccessKeyId=%s&Expires=%d&Signature=%s", UrlEncode(obsClient.conf.securityProvider.ak, false),
expires, signature)
}
}
return
}
func (obsClient ObsClient) doAuth(method, bucketName, objectKey string, params map[string]string,
headers map[string][]string, hostName string) (requestUrl string, err error) {
isObs := obsClient.conf.signature == SignatureObs
requestUrl, canonicalizedUrl := obsClient.conf.formatUrls(bucketName, objectKey, params, true)
parsedRequestUrl, err := url.Parse(requestUrl)
if err != nil {
return "", err
}
encodeHeaders(headers)
if hostName == "" {
hostName = parsedRequestUrl.Host
}
isV4 := obsClient.conf.signature == SignatureV4
prepareHostAndDate(headers, hostName, isV4)
if obsClient.conf.securityProvider == nil || obsClient.conf.securityProvider.ak == "" || obsClient.conf.securityProvider.sk == "" {
doLog(LEVEL_WARN, "No ak/sk provided, skip to construct authorization")
} else {
if obsClient.conf.securityProvider.securityToken != "" {
headers[HEADER_STS_TOKEN_AMZ] = []string{obsClient.conf.securityProvider.securityToken}
}
ak := obsClient.conf.securityProvider.ak
sk := obsClient.conf.securityProvider.sk
var authorization string
if isV4 {
headers[HEADER_CONTENT_SHA256_AMZ] = []string{EMPTY_CONTENT_SHA256}
ret := v4Auth(ak, sk, obsClient.conf.region, method, canonicalizedUrl, parsedRequestUrl.RawQuery, headers)
authorization = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
} else {
ret := v2Auth(ak, sk, method, canonicalizedUrl, headers, isObs)
hashPrefix := V2_HASH_PREFIX
if isObs {
hashPrefix = OBS_HASH_PREFIX
}
authorization = fmt.Sprintf("%s %s:%s", hashPrefix, ak, ret["Signature"])
}
headers[HEADER_AUTH_CAMEL] = []string{authorization}
}
return
}
func prepareHostAndDate(headers map[string][]string, hostName string, isV4 bool) {
headers[HEADER_HOST_CAMEL] = []string{hostName}
if date, ok := headers[HEADER_DATE_AMZ]; ok {
flag := false
if len(date) == 1 {
if isV4 {
if t, err := time.Parse(LONG_DATE_FORMAT, date[0]); err == nil {
headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(t)}
flag = true
}
} else {
if strings.HasSuffix(date[0], "GMT") {
headers[HEADER_DATE_CAMEL] = []string{date[0]}
flag = true
}
}
}
if !flag {
delete(headers, HEADER_DATE_AMZ)
}
}
if _, ok := headers[HEADER_DATE_CAMEL]; !ok {
headers[HEADER_DATE_CAMEL] = []string{FormatUtcToRfc1123(time.Now().UTC())}
}
}
func encodeHeaders(headers map[string][]string) {
for key, values := range headers {
for index, value := range values {
values[index] = UrlEncode(value, true)
}
headers[key] = values
}
}
func attachHeaders(headers map[string][]string, isObs bool) string {
length := len(headers)
_headers := make(map[string][]string, length)
keys := make([]string, 0, length)
for key, value := range headers {
_key := strings.ToLower(strings.TrimSpace(key))
if _key != "" {
prefixheader := HEADER_PREFIX
if isObs {
prefixheader = HEADER_PREFIX_OBS
}
if _key == "content-md5" || _key == "content-type" || _key == "date" || strings.HasPrefix(_key, prefixheader) {
keys = append(keys, _key)
_headers[_key] = value
}
} else {
delete(headers, key)
}
}
for _, interestedHeader := range interested_headers {
if _, ok := _headers[interestedHeader]; !ok {
_headers[interestedHeader] = []string{""}
keys = append(keys, interestedHeader)
}
}
dateCamelHeader := PARAM_DATE_AMZ_CAMEL
dataHeader := HEADER_DATE_AMZ
if isObs {
dateCamelHeader = PARAM_DATE_OBS_CAMEL
dataHeader = HEADER_DATE_OBS
}
if _, ok := _headers[HEADER_DATE_CAMEL]; ok {
if _, ok := _headers[dataHeader]; ok {
_headers[HEADER_DATE_CAMEL] = []string{""}
} else if _, ok := headers[dateCamelHeader]; ok {
_headers[HEADER_DATE_CAMEL] = []string{""}
}
} else if _, ok := _headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok {
if _, ok := _headers[dataHeader]; ok {
_headers[HEADER_DATE_CAMEL] = []string{""}
} else if _, ok := headers[dateCamelHeader]; ok {
_headers[HEADER_DATE_CAMEL] = []string{""}
}
}
sort.Strings(keys)
stringToSign := make([]string, 0, len(keys))
for _, key := range keys {
var value string
prefixHeader := HEADER_PREFIX
prefixMetaHeader := HEADER_PREFIX_META
if isObs {
prefixHeader = HEADER_PREFIX_OBS
prefixMetaHeader = HEADER_PREFIX_META_OBS
}
if strings.HasPrefix(key, prefixHeader) {
if strings.HasPrefix(key, prefixMetaHeader) {
for index, v := range _headers[key] {
value += strings.TrimSpace(v)
if index != len(_headers[key])-1 {
value += ","
}
}
} else {
value = strings.Join(_headers[key], ",")
}
value = fmt.Sprintf("%s:%s", key, value)
} else {
value = strings.Join(_headers[key], ",")
}
stringToSign = append(stringToSign, value)
}
return strings.Join(stringToSign, "\n")
}
func getV2StringToSign(method, canonicalizedUrl string, headers map[string][]string, isObs bool) string {
stringToSign := strings.Join([]string{method, "\n", attachHeaders(headers, isObs), "\n", canonicalizedUrl}, "")
doLog(LEVEL_DEBUG, "The v2 auth stringToSign:\n%s", stringToSign)
return stringToSign
}
func v2Auth(ak, sk, method, canonicalizedUrl string, headers map[string][]string, isObs bool) map[string]string {
stringToSign := getV2StringToSign(method, canonicalizedUrl, headers, isObs)
return map[string]string{"Signature": Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign)))}
}
func getScope(region, shortDate string) string {
return fmt.Sprintf("%s/%s/%s/%s", shortDate, region, V4_SERVICE_NAME, V4_SERVICE_SUFFIX)
}
func getCredential(ak, region, shortDate string) (string, string) {
scope := getScope(region, shortDate)
return fmt.Sprintf("%s/%s", ak, scope), scope
}
func getV4StringToSign(method, canonicalizedUrl, queryUrl, scope, longDate, payload string, signedHeaders []string, headers map[string][]string) string {
canonicalRequest := make([]string, 0, 10+len(signedHeaders)*4)
canonicalRequest = append(canonicalRequest, method)
canonicalRequest = append(canonicalRequest, "\n")
canonicalRequest = append(canonicalRequest, canonicalizedUrl)
canonicalRequest = append(canonicalRequest, "\n")
canonicalRequest = append(canonicalRequest, queryUrl)
canonicalRequest = append(canonicalRequest, "\n")
for _, signedHeader := range signedHeaders {
values, _ := headers[signedHeader]
for _, value := range values {
canonicalRequest = append(canonicalRequest, signedHeader)
canonicalRequest = append(canonicalRequest, ":")
canonicalRequest = append(canonicalRequest, value)
canonicalRequest = append(canonicalRequest, "\n")
}
}
canonicalRequest = append(canonicalRequest, "\n")
canonicalRequest = append(canonicalRequest, strings.Join(signedHeaders, ";"))
canonicalRequest = append(canonicalRequest, "\n")
canonicalRequest = append(canonicalRequest, payload)
_canonicalRequest := strings.Join(canonicalRequest, "")
doLog(LEVEL_DEBUG, "The v4 auth canonicalRequest:\n%s", _canonicalRequest)
stringToSign := make([]string, 0, 7)
stringToSign = append(stringToSign, V4_HASH_PREFIX)
stringToSign = append(stringToSign, "\n")
stringToSign = append(stringToSign, longDate)
stringToSign = append(stringToSign, "\n")
stringToSign = append(stringToSign, scope)
stringToSign = append(stringToSign, "\n")
stringToSign = append(stringToSign, HexSha256([]byte(_canonicalRequest)))
_stringToSign := strings.Join(stringToSign, "")
doLog(LEVEL_DEBUG, "The v4 auth stringToSign:\n%s", _stringToSign)
return _stringToSign
}
func getSignedHeaders(headers map[string][]string) ([]string, map[string][]string) {
length := len(headers)
_headers := make(map[string][]string, length)
signedHeaders := make([]string, 0, length)
for key, value := range headers {
_key := strings.ToLower(strings.TrimSpace(key))
if _key != "" {
signedHeaders = append(signedHeaders, _key)
_headers[_key] = value
} else {
delete(headers, key)
}
}
sort.Strings(signedHeaders)
return signedHeaders, _headers
}
func getSignature(stringToSign, sk, region, shortDate string) string {
key := HmacSha256([]byte(V4_HASH_PRE+sk), []byte(shortDate))
key = HmacSha256(key, []byte(region))
key = HmacSha256(key, []byte(V4_SERVICE_NAME))
key = HmacSha256(key, []byte(V4_SERVICE_SUFFIX))
return Hex(HmacSha256(key, []byte(stringToSign)))
}
func V4Auth(ak, sk, region, method, canonicalizedUrl, queryUrl string, headers map[string][]string) map[string]string {
return v4Auth(ak, sk, region, method, canonicalizedUrl, queryUrl, headers)
}
func v4Auth(ak, sk, region, method, canonicalizedUrl, queryUrl string, headers map[string][]string) map[string]string {
var t time.Time
if val, ok := headers[HEADER_DATE_AMZ]; ok {
var err error
t, err = time.Parse(LONG_DATE_FORMAT, val[0])
if err != nil {
t = time.Now().UTC()
}
} else if val, ok := headers[PARAM_DATE_AMZ_CAMEL]; ok {
var err error
t, err = time.Parse(LONG_DATE_FORMAT, val[0])
if err != nil {
t = time.Now().UTC()
}
} else if val, ok := headers[HEADER_DATE_CAMEL]; ok {
var err error
t, err = time.Parse(RFC1123_FORMAT, val[0])
if err != nil {
t = time.Now().UTC()
}
} else if val, ok := headers[strings.ToLower(HEADER_DATE_CAMEL)]; ok {
var err error
t, err = time.Parse(RFC1123_FORMAT, val[0])
if err != nil {
t = time.Now().UTC()
}
} else {
t = time.Now().UTC()
}
shortDate := t.Format(SHORT_DATE_FORMAT)
longDate := t.Format(LONG_DATE_FORMAT)
signedHeaders, _headers := getSignedHeaders(headers)
credential, scope := getCredential(ak, region, shortDate)
payload := EMPTY_CONTENT_SHA256
if val, ok := headers[HEADER_CONTENT_SHA256_AMZ]; ok {
payload = val[0]
}
stringToSign := getV4StringToSign(method, canonicalizedUrl, queryUrl, scope, longDate, payload, signedHeaders, _headers)
signature := getSignature(stringToSign, sk, region, shortDate)
ret := make(map[string]string, 3)
ret["Credential"] = credential
ret["SignedHeaders"] = strings.Join(signedHeaders, ";")
ret["Signature"] = signature
return ret
}
+904
View File
@@ -0,0 +1,904 @@
package obs
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"sort"
"strings"
)
type ObsClient struct {
conf *config
httpClient *http.Client
}
func New(ak, sk, endpoint string, configurers ...configurer) (*ObsClient, error) {
conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk}, endpoint: endpoint}
conf.maxRetryCount = -1
for _, configurer := range configurers {
configurer(conf)
}
if err := conf.initConfigWithDefault(); err != nil {
return nil, err
}
err := conf.getTransport()
if err != nil {
return nil, err
}
if isWarnLogEnabled() {
info := make([]string, 3)
info[0] = fmt.Sprintf("[OBS SDK Version=%s", obs_sdk_version)
info[1] = fmt.Sprintf("Endpoint=%s", conf.endpoint)
accessMode := "Virtual Hosting"
if conf.pathStyle {
accessMode = "Path"
}
info[2] = fmt.Sprintf("Access Mode=%s]", accessMode)
doLog(LEVEL_WARN, strings.Join(info, "];["))
}
doLog(LEVEL_DEBUG, "Create obsclient with config:\n%s\n", conf)
obsClient := &ObsClient{conf: conf, httpClient: &http.Client{Transport: conf.transport, CheckRedirect: checkRedirectFunc}}
return obsClient, nil
}
func (obsClient ObsClient) Refresh(ak, sk, securityToken string) {
sp := &securityProvider{ak: strings.TrimSpace(ak), sk: strings.TrimSpace(sk), securityToken: strings.TrimSpace(securityToken)}
obsClient.conf.securityProvider = sp
}
func (obsClient ObsClient) Close() {
obsClient.httpClient = nil
obsClient.conf.transport.CloseIdleConnections()
obsClient.conf = nil
SyncLog()
}
func (obsClient ObsClient) ListBuckets(input *ListBucketsInput) (output *ListBucketsOutput, err error) {
if input == nil {
input = &ListBucketsInput{}
}
output = &ListBucketsOutput{}
err = obsClient.doActionWithoutBucket("ListBuckets", HTTP_GET, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) CreateBucket(input *CreateBucketInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("CreateBucketInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("CreateBucket", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucket(bucketName string) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doActionWithBucket("DeleteBucket", HTTP_DELETE, bucketName, defaultSerializable, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketStoragePolicy(input *SetBucketStoragePolicyInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketStoragePolicyInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketStoragePolicy", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) getBucketStoragePolicyS3(bucketName string) (output *GetBucketStoragePolicyOutput, err error) {
output = &GetBucketStoragePolicyOutput{}
var outputS3 *getBucketStoragePolicyOutputS3
outputS3 = &getBucketStoragePolicyOutputS3{}
err = obsClient.doActionWithBucket("GetBucketStoragePolicy", HTTP_GET, bucketName, newSubResourceSerial(SubResourceStoragePolicy), outputS3)
if err != nil {
output = nil
}
output.BaseModel = outputS3.BaseModel
output.StorageClass = fmt.Sprintf("%s", outputS3.StorageClass)
return
}
func (obsClient ObsClient) getBucketStoragePolicyObs(bucketName string) (output *GetBucketStoragePolicyOutput, err error) {
output = &GetBucketStoragePolicyOutput{}
var outputObs *getBucketStoragePolicyOutputObs
outputObs = &getBucketStoragePolicyOutputObs{}
err = obsClient.doActionWithBucket("GetBucketStoragePolicy", HTTP_GET, bucketName, newSubResourceSerial(SubResourceStorageClass), outputObs)
if err != nil {
output = nil
}
output.BaseModel = outputObs.BaseModel
output.StorageClass = outputObs.StorageClass
return
}
func (obsClient ObsClient) GetBucketStoragePolicy(bucketName string) (output *GetBucketStoragePolicyOutput, err error) {
if obsClient.conf.signature == SignatureObs {
return obsClient.getBucketStoragePolicyObs(bucketName)
}
return obsClient.getBucketStoragePolicyS3(bucketName)
}
func (obsClient ObsClient) ListObjects(input *ListObjectsInput) (output *ListObjectsOutput, err error) {
if input == nil {
return nil, errors.New("ListObjectsInput is nil")
}
output = &ListObjectsOutput{}
err = obsClient.doActionWithBucket("ListObjects", HTTP_GET, input.Bucket, input, output)
if err != nil {
output = nil
} else {
if location, ok := output.ResponseHeaders[HEADER_BUCKET_REGION]; ok {
output.Location = location[0]
}
}
return
}
func (obsClient ObsClient) ListVersions(input *ListVersionsInput) (output *ListVersionsOutput, err error) {
if input == nil {
return nil, errors.New("ListVersionsInput is nil")
}
output = &ListVersionsOutput{}
err = obsClient.doActionWithBucket("ListVersions", HTTP_GET, input.Bucket, input, output)
if err != nil {
output = nil
} else {
if location, ok := output.ResponseHeaders[HEADER_BUCKET_REGION]; ok {
output.Location = location[0]
}
}
return
}
func (obsClient ObsClient) ListMultipartUploads(input *ListMultipartUploadsInput) (output *ListMultipartUploadsOutput, err error) {
if input == nil {
return nil, errors.New("ListMultipartUploadsInput is nil")
}
output = &ListMultipartUploadsOutput{}
err = obsClient.doActionWithBucket("ListMultipartUploads", HTTP_GET, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketQuota(input *SetBucketQuotaInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketQuotaInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketQuota", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketQuota(bucketName string) (output *GetBucketQuotaOutput, err error) {
output = &GetBucketQuotaOutput{}
err = obsClient.doActionWithBucket("GetBucketQuota", HTTP_GET, bucketName, newSubResourceSerial(SubResourceQuota), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) HeadBucket(bucketName string) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doActionWithBucket("HeadBucket", HTTP_HEAD, bucketName, defaultSerializable, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketMetadata(input *GetBucketMetadataInput) (output *GetBucketMetadataOutput, err error) {
output = &GetBucketMetadataOutput{}
err = obsClient.doActionWithBucket("GetBucketMetadata", HTTP_HEAD, input.Bucket, input, output)
if err != nil {
output = nil
} else {
ParseGetBucketMetadataOutput(output)
}
return
}
func (obsClient ObsClient) GetBucketStorageInfo(bucketName string) (output *GetBucketStorageInfoOutput, err error) {
output = &GetBucketStorageInfoOutput{}
err = obsClient.doActionWithBucket("GetBucketStorageInfo", HTTP_GET, bucketName, newSubResourceSerial(SubResourceStorageInfo), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) getBucketLocationS3(bucketName string) (output *GetBucketLocationOutput, err error) {
output = &GetBucketLocationOutput{}
var outputS3 *getBucketLocationOutputS3
outputS3 = &getBucketLocationOutputS3{}
err = obsClient.doActionWithBucket("GetBucketLocation", HTTP_GET, bucketName, newSubResourceSerial(SubResourceLocation), outputS3)
if err != nil {
output = nil
} else {
output.BaseModel = outputS3.BaseModel
output.Location = outputS3.Location
}
return
}
func (obsClient ObsClient) getBucketLocationObs(bucketName string) (output *GetBucketLocationOutput, err error) {
output = &GetBucketLocationOutput{}
var outputObs *getBucketLocationOutputObs
outputObs = &getBucketLocationOutputObs{}
err = obsClient.doActionWithBucket("GetBucketLocation", HTTP_GET, bucketName, newSubResourceSerial(SubResourceLocation), outputObs)
if err != nil {
output = nil
} else {
output.BaseModel = outputObs.BaseModel
output.Location = outputObs.Location
}
return
}
func (obsClient ObsClient) GetBucketLocation(bucketName string) (output *GetBucketLocationOutput, err error) {
if obsClient.conf.signature == SignatureObs {
return obsClient.getBucketLocationObs(bucketName)
}
return obsClient.getBucketLocationS3(bucketName)
}
func (obsClient ObsClient) SetBucketAcl(input *SetBucketAclInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketAclInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketAcl", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) getBucketAclObs(bucketName string) (output *GetBucketAclOutput, err error) {
output = &GetBucketAclOutput{}
var outputObs *getBucketAclOutputObs
outputObs = &getBucketAclOutputObs{}
err = obsClient.doActionWithBucket("GetBucketAcl", HTTP_GET, bucketName, newSubResourceSerial(SubResourceAcl), outputObs)
if err != nil {
output = nil
} else {
output.BaseModel = outputObs.BaseModel
for i, valGrant := range outputObs.Grants {
output.Grants[i].Delivered = valGrant.Delivered
output.Grants[i].Permission = valGrant.Permission
output.Grants[i].Grantee.DisplayName = valGrant.Grantee.DisplayName
output.Grants[i].Grantee.ID = valGrant.Grantee.ID
output.Grants[i].Grantee.Type = valGrant.Grantee.Type
output.Grants[i].Grantee.URI = GroupAllUsers
}
}
return
}
func (obsClient ObsClient) GetBucketAcl(bucketName string) (output *GetBucketAclOutput, err error) {
output = &GetBucketAclOutput{}
if obsClient.conf.signature == SignatureObs {
return obsClient.getBucketAclObs(bucketName)
}
err = obsClient.doActionWithBucket("GetBucketAcl", HTTP_GET, bucketName, newSubResourceSerial(SubResourceAcl), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketPolicy(input *SetBucketPolicyInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketPolicy is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketPolicy", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketPolicy(bucketName string) (output *GetBucketPolicyOutput, err error) {
output = &GetBucketPolicyOutput{}
err = obsClient.doActionWithBucketV2("GetBucketPolicy", HTTP_GET, bucketName, newSubResourceSerial(SubResourcePolicy), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketPolicy(bucketName string) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doActionWithBucket("DeleteBucketPolicy", HTTP_DELETE, bucketName, newSubResourceSerial(SubResourcePolicy), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketCors(input *SetBucketCorsInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketCorsInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketCors", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketCors(bucketName string) (output *GetBucketCorsOutput, err error) {
output = &GetBucketCorsOutput{}
err = obsClient.doActionWithBucket("GetBucketCors", HTTP_GET, bucketName, newSubResourceSerial(SubResourceCors), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketCors(bucketName string) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doActionWithBucket("DeleteBucketCors", HTTP_DELETE, bucketName, newSubResourceSerial(SubResourceCors), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketVersioning(input *SetBucketVersioningInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketVersioningInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketVersioning", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketVersioning(bucketName string) (output *GetBucketVersioningOutput, err error) {
output = &GetBucketVersioningOutput{}
err = obsClient.doActionWithBucket("GetBucketVersioning", HTTP_GET, bucketName, newSubResourceSerial(SubResourceVersioning), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketWebsiteConfiguration(input *SetBucketWebsiteConfigurationInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketWebsiteConfigurationInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketWebsiteConfiguration", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketWebsiteConfiguration(bucketName string) (output *GetBucketWebsiteConfigurationOutput, err error) {
output = &GetBucketWebsiteConfigurationOutput{}
err = obsClient.doActionWithBucket("GetBucketWebsiteConfiguration", HTTP_GET, bucketName, newSubResourceSerial(SubResourceWebsite), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketWebsiteConfiguration(bucketName string) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doActionWithBucket("DeleteBucketWebsiteConfiguration", HTTP_DELETE, bucketName, newSubResourceSerial(SubResourceWebsite), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketLoggingConfiguration(input *SetBucketLoggingConfigurationInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketLoggingConfigurationInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketLoggingConfiguration", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketLoggingConfiguration(bucketName string) (output *GetBucketLoggingConfigurationOutput, err error) {
output = &GetBucketLoggingConfigurationOutput{}
err = obsClient.doActionWithBucket("GetBucketLoggingConfiguration", HTTP_GET, bucketName, newSubResourceSerial(SubResourceLogging), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketLifecycleConfiguration(input *SetBucketLifecycleConfigurationInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketLifecycleConfigurationInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketLifecycleConfiguration", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketLifecycleConfiguration(bucketName string) (output *GetBucketLifecycleConfigurationOutput, err error) {
output = &GetBucketLifecycleConfigurationOutput{}
err = obsClient.doActionWithBucket("GetBucketLifecycleConfiguration", HTTP_GET, bucketName, newSubResourceSerial(SubResourceLifecycle), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketLifecycleConfiguration(bucketName string) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doActionWithBucket("DeleteBucketLifecycleConfiguration", HTTP_DELETE, bucketName, newSubResourceSerial(SubResourceLifecycle), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketTagging(input *SetBucketTaggingInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketTaggingInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketTagging", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketTagging(bucketName string) (output *GetBucketTaggingOutput, err error) {
output = &GetBucketTaggingOutput{}
err = obsClient.doActionWithBucket("GetBucketTagging", HTTP_GET, bucketName, newSubResourceSerial(SubResourceTagging), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketTagging(bucketName string) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doActionWithBucket("DeleteBucketTagging", HTTP_DELETE, bucketName, newSubResourceSerial(SubResourceTagging), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketNotification(input *SetBucketNotificationInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetBucketNotificationInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucket("SetBucketNotification", HTTP_PUT, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketNotification(bucketName string) (output *GetBucketNotificationOutput, err error) {
if obsClient.conf.signature != SignatureObs {
return obsClient.getBucketNotificationS3(bucketName)
}
output = &GetBucketNotificationOutput{}
err = obsClient.doActionWithBucket("GetBucketNotification", HTTP_GET, bucketName, newSubResourceSerial(SubResourceNotification), output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) getBucketNotificationS3(bucketName string) (output *GetBucketNotificationOutput, err error) {
outputS3 := &getBucketNotificationOutputS3{}
err = obsClient.doActionWithBucket("GetBucketNotification", HTTP_GET, bucketName, newSubResourceSerial(SubResourceNotification), outputS3)
if err != nil {
return nil, err
}
output = &GetBucketNotificationOutput{}
output.BaseModel = outputS3.BaseModel
topicConfigurations := make([]TopicConfiguration, 0, len(outputS3.TopicConfigurations))
for _, topicConfigurationS3 := range outputS3.TopicConfigurations {
topicConfiguration := TopicConfiguration{}
topicConfiguration.ID = topicConfigurationS3.ID
topicConfiguration.Topic = topicConfigurationS3.Topic
topicConfiguration.FilterRules = topicConfigurationS3.FilterRules
events := make([]EventType, 0, len(topicConfigurationS3.Events))
for _, event := range topicConfigurationS3.Events {
events = append(events, ParseStringToEventType(event))
}
topicConfiguration.Events = events
topicConfigurations = append(topicConfigurations, topicConfiguration)
}
output.TopicConfigurations = topicConfigurations
return
}
func (obsClient ObsClient) DeleteObject(input *DeleteObjectInput) (output *DeleteObjectOutput, err error) {
if input == nil {
return nil, errors.New("DeleteObjectInput is nil")
}
output = &DeleteObjectOutput{}
err = obsClient.doActionWithBucketAndKey("DeleteObject", HTTP_DELETE, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
} else {
ParseDeleteObjectOutput(output)
}
return
}
func (obsClient ObsClient) DeleteObjects(input *DeleteObjectsInput) (output *DeleteObjectsOutput, err error) {
if input == nil {
return nil, errors.New("DeleteObjectsInput is nil")
}
output = &DeleteObjectsOutput{}
err = obsClient.doActionWithBucket("DeleteObjects", HTTP_POST, input.Bucket, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetObjectAcl(input *SetObjectAclInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("SetObjectAclInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucketAndKey("SetObjectAcl", HTTP_PUT, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetObjectAcl(input *GetObjectAclInput) (output *GetObjectAclOutput, err error) {
if input == nil {
return nil, errors.New("GetObjectAclInput is nil")
}
output = &GetObjectAclOutput{}
err = obsClient.doActionWithBucketAndKey("GetObjectAcl", HTTP_GET, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
} else {
if versionId, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
output.VersionId = versionId[0]
}
}
return
}
func (obsClient ObsClient) RestoreObject(input *RestoreObjectInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("RestoreObjectInput is nil")
}
output = &BaseModel{}
err = obsClient.doActionWithBucketAndKey("RestoreObject", HTTP_POST, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetObjectMetadata(input *GetObjectMetadataInput) (output *GetObjectMetadataOutput, err error) {
if input == nil {
return nil, errors.New("GetObjectMetadataInput is nil")
}
output = &GetObjectMetadataOutput{}
err = obsClient.doActionWithBucketAndKey("GetObjectMetadata", HTTP_HEAD, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
} else {
ParseGetObjectMetadataOutput(output)
}
return
}
func (obsClient ObsClient) GetObject(input *GetObjectInput) (output *GetObjectOutput, err error) {
if input == nil {
return nil, errors.New("GetObjectInput is nil")
}
output = &GetObjectOutput{}
err = obsClient.doActionWithBucketAndKey("GetObject", HTTP_GET, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
} else {
ParseGetObjectOutput(output)
}
return
}
func (obsClient ObsClient) PutObject(input *PutObjectInput) (output *PutObjectOutput, err error) {
if input == nil {
return nil, errors.New("PutObjectInput is nil")
}
if input.ContentType == "" && input.Key != "" {
if contentType, ok := mime_types[input.Key[strings.LastIndex(input.Key, ".")+1:]]; ok {
input.ContentType = contentType
}
}
output = &PutObjectOutput{}
var repeatable bool
if input.Body != nil {
_, repeatable = input.Body.(*strings.Reader)
if input.ContentLength > 0 {
input.Body = &readerWrapper{reader: input.Body, totalCount: input.ContentLength}
}
}
if repeatable {
err = obsClient.doActionWithBucketAndKey("PutObject", HTTP_PUT, input.Bucket, input.Key, input, output)
} else {
err = obsClient.doActionWithBucketAndKeyUnRepeatable("PutObject", HTTP_PUT, input.Bucket, input.Key, input, output)
}
if err != nil {
output = nil
} else {
ParsePutObjectOutput(output)
}
return
}
func (obsClient ObsClient) PutFile(input *PutFileInput) (output *PutObjectOutput, err error) {
if input == nil {
return nil, errors.New("PutFileInput is nil")
}
var body io.Reader
sourceFile := strings.TrimSpace(input.SourceFile)
if sourceFile != "" {
fd, err := os.Open(sourceFile)
if err != nil {
return nil, err
}
defer fd.Close()
stat, err := fd.Stat()
if err != nil {
return nil, err
}
fileReaderWrapper := &fileReaderWrapper{filePath: sourceFile}
fileReaderWrapper.reader = fd
if input.ContentLength > 0 {
if input.ContentLength > stat.Size() {
input.ContentLength = stat.Size()
}
fileReaderWrapper.totalCount = input.ContentLength
} else {
fileReaderWrapper.totalCount = stat.Size()
}
body = fileReaderWrapper
}
_input := &PutObjectInput{}
_input.PutObjectBasicInput = input.PutObjectBasicInput
_input.Body = body
if _input.ContentType == "" && _input.Key != "" {
if contentType, ok := mime_types[_input.Key[strings.LastIndex(_input.Key, ".")+1:]]; ok {
_input.ContentType = contentType
} else if contentType, ok := mime_types[sourceFile[strings.LastIndex(sourceFile, ".")+1:]]; ok {
_input.ContentType = contentType
}
}
output = &PutObjectOutput{}
err = obsClient.doActionWithBucketAndKey("PutFile", HTTP_PUT, _input.Bucket, _input.Key, _input, output)
if err != nil {
output = nil
} else {
ParsePutObjectOutput(output)
}
return
}
func (obsClient ObsClient) CopyObject(input *CopyObjectInput) (output *CopyObjectOutput, err error) {
if input == nil {
return nil, errors.New("CopyObjectInput is nil")
}
if strings.TrimSpace(input.CopySourceBucket) == "" {
return nil, errors.New("Source bucket is empty")
}
if strings.TrimSpace(input.CopySourceKey) == "" {
return nil, errors.New("Source key is empty")
}
output = &CopyObjectOutput{}
err = obsClient.doActionWithBucketAndKey("CopyObject", HTTP_PUT, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
} else {
ParseCopyObjectOutput(output)
}
return
}
func (obsClient ObsClient) AbortMultipartUpload(input *AbortMultipartUploadInput) (output *BaseModel, err error) {
if input == nil {
return nil, errors.New("AbortMultipartUploadInput is nil")
}
if input.UploadId == "" {
return nil, errors.New("UploadId is empty")
}
output = &BaseModel{}
err = obsClient.doActionWithBucketAndKey("AbortMultipartUpload", HTTP_DELETE, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) InitiateMultipartUpload(input *InitiateMultipartUploadInput) (output *InitiateMultipartUploadOutput, err error) {
if input == nil {
return nil, errors.New("InitiateMultipartUploadInput is nil")
}
if input.ContentType == "" && input.Key != "" {
if contentType, ok := mime_types[input.Key[strings.LastIndex(input.Key, ".")+1:]]; ok {
input.ContentType = contentType
}
}
output = &InitiateMultipartUploadOutput{}
err = obsClient.doActionWithBucketAndKey("InitiateMultipartUpload", HTTP_POST, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
} else {
ParseInitiateMultipartUploadOutput(output)
}
return
}
func (obsClient ObsClient) UploadPart(input *UploadPartInput) (output *UploadPartOutput, err error) {
if input == nil {
return nil, errors.New("UploadPartInput is nil")
}
if input.UploadId == "" {
return nil, errors.New("UploadId is empty")
}
output = &UploadPartOutput{}
var repeatable bool
if input.Body != nil {
_, repeatable = input.Body.(*strings.Reader)
if input.PartSize > 0 {
input.Body = &readerWrapper{reader: input.Body, totalCount: input.PartSize}
}
} else if sourceFile := strings.TrimSpace(input.SourceFile); sourceFile != "" {
fd, err := os.Open(sourceFile)
if err != nil {
return nil, err
}
defer fd.Close()
stat, err := fd.Stat()
if err != nil {
return nil, err
}
fileSize := stat.Size()
fileReaderWrapper := &fileReaderWrapper{filePath: sourceFile}
fileReaderWrapper.reader = fd
if input.Offset < 0 || input.Offset > fileSize {
input.Offset = 0
}
if input.PartSize <= 0 || input.PartSize > (fileSize-input.Offset) {
input.PartSize = fileSize - input.Offset
}
fileReaderWrapper.totalCount = input.PartSize
fd.Seek(input.Offset, 0)
input.Body = fileReaderWrapper
repeatable = true
}
if repeatable {
err = obsClient.doActionWithBucketAndKey("UploadPart", HTTP_PUT, input.Bucket, input.Key, input, output)
} else {
err = obsClient.doActionWithBucketAndKeyUnRepeatable("UploadPart", HTTP_PUT, input.Bucket, input.Key, input, output)
}
if err != nil {
output = nil
} else {
ParseUploadPartOutput(output)
output.PartNumber = input.PartNumber
}
return
}
func (obsClient ObsClient) CompleteMultipartUpload(input *CompleteMultipartUploadInput) (output *CompleteMultipartUploadOutput, err error) {
if input == nil {
return nil, errors.New("CompleteMultipartUploadInput is nil")
}
if input.UploadId == "" {
return nil, errors.New("UploadId is empty")
}
var parts partSlice = input.Parts
sort.Sort(parts)
output = &CompleteMultipartUploadOutput{}
err = obsClient.doActionWithBucketAndKey("CompleteMultipartUpload", HTTP_POST, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
} else {
ParseCompleteMultipartUploadOutput(output)
}
return
}
func (obsClient ObsClient) ListParts(input *ListPartsInput) (output *ListPartsOutput, err error) {
if input == nil {
return nil, errors.New("ListPartsInput is nil")
}
if input.UploadId == "" {
return nil, errors.New("UploadId is empty")
}
output = &ListPartsOutput{}
err = obsClient.doActionWithBucketAndKey("ListParts", HTTP_GET, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) CopyPart(input *CopyPartInput) (output *CopyPartOutput, err error) {
if input == nil {
return nil, errors.New("CopyPartInput is nil")
}
if input.UploadId == "" {
return nil, errors.New("UploadId is empty")
}
if strings.TrimSpace(input.CopySourceBucket) == "" {
return nil, errors.New("Source bucket is empty")
}
if strings.TrimSpace(input.CopySourceKey) == "" {
return nil, errors.New("Source key is empty")
}
output = &CopyPartOutput{}
err = obsClient.doActionWithBucketAndKey("CopyPart", HTTP_PUT, input.Bucket, input.Key, input, output)
if err != nil {
output = nil
} else {
ParseCopyPartOutput(output)
output.PartNumber = input.PartNumber
}
return
}
+375
View File
@@ -0,0 +1,375 @@
package obs
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
type securityProvider struct {
ak string
sk string
securityToken string
}
type urlHolder struct {
scheme string
host string
port int
}
type config struct {
securityProvider *securityProvider
urlHolder *urlHolder
endpoint string
signature SignatureType
pathStyle bool
region string
connectTimeout int
socketTimeout int
headerTimeout int
idleConnTimeout int
finalTimeout int
maxRetryCount int
proxyUrl string
maxConnsPerHost int
sslVerify bool
pemCerts []byte
transport *http.Transport
ctx context.Context
}
func (conf config) String() string {
return fmt.Sprintf("[endpoint:%s, signature:%s, pathStyle:%v, region:%s"+
"\nconnectTimeout:%d, socketTimeout:%dheaderTimeout:%d, idleConnTimeout:%d"+
"\nmaxRetryCount:%d, maxConnsPerHost:%d, sslVerify:%v, proxyUrl:%s]",
conf.endpoint, conf.signature, conf.pathStyle, conf.region,
conf.connectTimeout, conf.socketTimeout, conf.headerTimeout, conf.idleConnTimeout,
conf.maxRetryCount, conf.maxConnsPerHost, conf.sslVerify, conf.proxyUrl,
)
}
type configurer func(conf *config)
func WithSslVerify(sslVerify bool) configurer {
return WithSslVerifyAndPemCerts(sslVerify, nil)
}
func WithSslVerifyAndPemCerts(sslVerify bool, pemCerts []byte) configurer {
return func(conf *config) {
conf.sslVerify = sslVerify
conf.pemCerts = pemCerts
}
}
func WithHeaderTimeout(headerTimeout int) configurer {
return func(conf *config) {
conf.headerTimeout = headerTimeout
}
}
func WithProxyUrl(proxyUrl string) configurer {
return func(conf *config) {
conf.proxyUrl = proxyUrl
}
}
func WithMaxConnections(maxConnsPerHost int) configurer {
return func(conf *config) {
conf.maxConnsPerHost = maxConnsPerHost
}
}
func WithPathStyle(pathStyle bool) configurer {
return func(conf *config) {
conf.pathStyle = pathStyle
}
}
func WithSignature(signature SignatureType) configurer {
return func(conf *config) {
conf.signature = signature
}
}
func WithRegion(region string) configurer {
return func(conf *config) {
conf.region = region
}
}
func WithConnectTimeout(connectTimeout int) configurer {
return func(conf *config) {
conf.connectTimeout = connectTimeout
}
}
func WithSocketTimeout(socketTimeout int) configurer {
return func(conf *config) {
conf.socketTimeout = socketTimeout
}
}
func WithIdleConnTimeout(idleConnTimeout int) configurer {
return func(conf *config) {
conf.idleConnTimeout = idleConnTimeout
}
}
func WithMaxRetryCount(maxRetryCount int) configurer {
return func(conf *config) {
conf.maxRetryCount = maxRetryCount
}
}
func WithSecurityToken(securityToken string) configurer {
return func(conf *config) {
conf.securityProvider.securityToken = securityToken
}
}
func WithHttpTransport(transport *http.Transport) configurer {
return func(conf *config) {
conf.transport = transport
}
}
func WithRequestContext(ctx context.Context) configurer {
return func(conf *config) {
conf.ctx = ctx
}
}
func (conf *config) initConfigWithDefault() error {
conf.securityProvider.ak = strings.TrimSpace(conf.securityProvider.ak)
conf.securityProvider.sk = strings.TrimSpace(conf.securityProvider.sk)
conf.securityProvider.securityToken = strings.TrimSpace(conf.securityProvider.securityToken)
conf.endpoint = strings.TrimSpace(conf.endpoint)
if conf.endpoint == "" {
return errors.New("endpoint is not set")
}
if index := strings.Index(conf.endpoint, "?"); index > 0 {
conf.endpoint = conf.endpoint[:index]
}
for strings.LastIndex(conf.endpoint, "/") == len(conf.endpoint)-1 {
conf.endpoint = conf.endpoint[:len(conf.endpoint)-1]
}
if conf.signature == "" {
conf.signature = DEFAULT_SIGNATURE
}
urlHolder := &urlHolder{}
var address string
if strings.HasPrefix(conf.endpoint, "https://") {
urlHolder.scheme = "https"
address = conf.endpoint[len("https://"):]
} else if strings.HasPrefix(conf.endpoint, "http://") {
urlHolder.scheme = "http"
address = conf.endpoint[len("http://"):]
} else {
urlHolder.scheme = "http"
address = conf.endpoint
}
addr := strings.Split(address, ":")
if len(addr) == 2 {
if port, err := strconv.Atoi(addr[1]); err == nil {
urlHolder.port = port
}
}
urlHolder.host = addr[0]
if urlHolder.port == 0 {
if urlHolder.scheme == "https" {
urlHolder.port = 443
} else {
urlHolder.port = 80
}
}
if IsIP(urlHolder.host) {
conf.pathStyle = true
}
conf.urlHolder = urlHolder
conf.region = strings.TrimSpace(conf.region)
if conf.region == "" {
conf.region = DEFAULT_REGION
}
if conf.connectTimeout <= 0 {
conf.connectTimeout = DEFAULT_CONNECT_TIMEOUT
}
if conf.socketTimeout <= 0 {
conf.socketTimeout = DEFAULT_SOCKET_TIMEOUT
}
conf.finalTimeout = conf.socketTimeout * 10
if conf.headerTimeout <= 0 {
conf.headerTimeout = DEFAULT_HEADER_TIMEOUT
}
if conf.idleConnTimeout < 0 {
conf.idleConnTimeout = DEFAULT_IDLE_CONN_TIMEOUT
}
if conf.maxRetryCount < 0 {
conf.maxRetryCount = DEFAULT_MAX_RETRY_COUNT
}
if conf.maxConnsPerHost <= 0 {
conf.maxConnsPerHost = DEFAULT_MAX_CONN_PER_HOST
}
conf.proxyUrl = strings.TrimSpace(conf.proxyUrl)
return nil
}
func (conf *config) getTransport() error {
if conf.transport == nil {
conf.transport = &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(network, addr, time.Second*time.Duration(conf.connectTimeout))
if err != nil {
return nil, err
}
return getConnDelegate(conn, conf.socketTimeout, conf.finalTimeout), nil
},
MaxIdleConns: conf.maxConnsPerHost,
MaxIdleConnsPerHost: conf.maxConnsPerHost,
ResponseHeaderTimeout: time.Second * time.Duration(conf.headerTimeout),
IdleConnTimeout: time.Second * time.Duration(conf.idleConnTimeout),
}
if conf.proxyUrl != "" {
proxyUrl, err := url.Parse(conf.proxyUrl)
if err != nil {
return err
}
conf.transport.Proxy = http.ProxyURL(proxyUrl)
}
tlsConfig := &tls.Config{InsecureSkipVerify: !conf.sslVerify}
if conf.sslVerify && conf.pemCerts != nil {
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(conf.pemCerts)
tlsConfig.RootCAs = pool
}
conf.transport.TLSClientConfig = tlsConfig
}
return nil
}
func checkRedirectFunc(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
func DummyQueryEscape(s string) string {
return s
}
func (conf *config) formatUrls(bucketName, objectKey string, params map[string]string, escape bool) (requestUrl string, canonicalizedUrl string) {
urlHolder := conf.urlHolder
if bucketName == "" {
requestUrl = fmt.Sprintf("%s://%s:%d", urlHolder.scheme, urlHolder.host, urlHolder.port)
canonicalizedUrl = "/"
} else {
if conf.pathStyle {
requestUrl = fmt.Sprintf("%s://%s:%d/%s", urlHolder.scheme, urlHolder.host, urlHolder.port, bucketName)
canonicalizedUrl = "/" + bucketName
} else {
requestUrl = fmt.Sprintf("%s://%s.%s:%d", urlHolder.scheme, bucketName, urlHolder.host, urlHolder.port)
if conf.signature == "v2" {
canonicalizedUrl = "/" + bucketName + "/"
} else {
canonicalizedUrl = "/"
}
}
}
var escapeFunc func(s string) string
if escape {
escapeFunc = url.QueryEscape
} else {
escapeFunc = DummyQueryEscape
}
if objectKey != "" {
encodeObjectKey := escapeFunc(objectKey)
requestUrl += "/" + encodeObjectKey
if !strings.HasSuffix(canonicalizedUrl, "/") {
canonicalizedUrl += "/"
}
canonicalizedUrl += encodeObjectKey
}
keys := make([]string, 0, len(params))
for key := range params {
keys = append(keys, strings.TrimSpace(key))
}
sort.Strings(keys)
i := 0
for index, key := range keys {
if index == 0 {
requestUrl += "?"
} else {
requestUrl += "&"
}
_key := url.QueryEscape(key)
requestUrl += _key
_value := params[key]
if conf.signature == "v4" {
requestUrl += "=" + url.QueryEscape(_value)
} else {
if _value != "" {
requestUrl += "=" + url.QueryEscape(_value)
_value = "=" + _value
} else {
_value = ""
}
lowerKey := strings.ToLower(key)
_, ok := allowed_resource_parameter_names[lowerKey]
prefixHeader := HEADER_PREFIX
isObs := conf.signature == SignatureObs
if isObs {
prefixHeader = HEADER_PREFIX_OBS
}
ok = ok || strings.HasPrefix(lowerKey, prefixHeader)
if ok {
if i == 0 {
canonicalizedUrl += "?"
} else {
canonicalizedUrl += "&"
}
canonicalizedUrl += getQueryUrl(_key, _value)
i++
}
}
}
return
}
func getQueryUrl(key, value string) string {
queryUrl := ""
queryUrl += key
queryUrl += value
return queryUrl
}
+493
View File
@@ -0,0 +1,493 @@
package obs
const (
obs_sdk_version = "3.0.0"
USER_AGENT = "obs-sdk-go/" + obs_sdk_version
HEADER_PREFIX = "x-amz-"
HEADER_PREFIX_META = "x-amz-meta-"
HEADER_PREFIX_OBS = "x-obs-"
HEADER_PREFIX_META_OBS = "x-obs-meta-"
HEADER_DATE_AMZ = "x-amz-date"
HEADER_DATE_OBS = "x-obs-date"
HEADER_STS_TOKEN_AMZ = "x-amz-security-token"
HEADER_ACCESSS_KEY_AMZ = "AWSAccessKeyId"
PREFIX_META = "meta-"
HEADER_CONTENT_SHA256_AMZ = "x-amz-content-sha256"
HEADER_ACL_AMZ = "x-amz-acl"
HEADER_ACL_OBS = "x-obs-acl"
HEADER_ACL = "acl"
HEADER_LOCATION_AMZ = "location"
HEADER_BUCKET_LOCATION_OBS = "bucket-location"
HEADER_COPY_SOURCE = "copy-source"
HEADER_COPY_SOURCE_RANGE = "copy-source-range"
HEADER_RANGE = "Range"
HEADER_STORAGE_CLASS = "x-default-storage-class"
HEADER_STORAGE_CLASS_OBS = "x-obs-storage-class"
HEADER_VERSION_OBS = "version"
HEADER_GRANT_READ_OBS = "grant-read"
HEADER_GRANT_WRITE_OBS = "grant-write"
HEADER_GRANT_READ_ACP_OBS = "grant-read-acp"
HEADER_GRANT_WRITE_ACP_OBS = "grant-write-acp"
HEADER_GRANT_FULL_CONTROL_OBS = "grant-full-control"
HEADER_GRANT_READ_DELIVERED_OBS = "grant-read-delivered"
HEADER_GRANT_FULL_CONTROL_DELIVERED_OBS = "grant-full-control-delivered"
HEADER_REQUEST_ID = "request-id"
HEADER_BUCKET_REGION = "bucket-region"
HEADER_ACCESS_CONRTOL_ALLOW_ORIGIN = "access-control-allow-origin"
HEADER_ACCESS_CONRTOL_ALLOW_HEADERS = "access-control-allow-headers"
HEADER_ACCESS_CONRTOL_MAX_AGE = "access-control-max-age"
HEADER_ACCESS_CONRTOL_ALLOW_METHODS = "access-control-allow-methods"
HEADER_ACCESS_CONRTOL_EXPOSE_HEADERS = "access-control-expose-headers"
HEADER_EPID_HEADERS = "epid"
HEADER_VERSION_ID = "version-id"
HEADER_COPY_SOURCE_VERSION_ID = "copy-source-version-id"
HEADER_DELETE_MARKER = "delete-marker"
HEADER_WEBSITE_REDIRECT_LOCATION = "website-redirect-location"
HEADER_METADATA_DIRECTIVE = "metadata-directive"
HEADER_EXPIRATION = "expiration"
HEADER_EXPIRES_OBS = "x-obs-expires"
HEADER_RESTORE = "restore"
HEADER_OBJECT_TYPE = "object-type"
HEADER_NEXT_APPEND_POSITION = "next-append-position"
HEADER_STORAGE_CLASS2 = "storage-class"
HEADER_CONTENT_LENGTH = "content-length"
HEADER_CONTENT_TYPE = "content-type"
HEADER_CONTENT_LANGUAGE = "content-language"
HEADER_EXPIRES = "expires"
HEADER_CACHE_CONTROL = "cache-control"
HEADER_CONTENT_DISPOSITION = "content-disposition"
HEADER_CONTENT_ENCODING = "content-encoding"
HEADER_ETAG = "etag"
HEADER_LASTMODIFIED = "last-modified"
HEADER_COPY_SOURCE_IF_MATCH = "copy-source-if-match"
HEADER_COPY_SOURCE_IF_NONE_MATCH = "copy-source-if-none-match"
HEADER_COPY_SOURCE_IF_MODIFIED_SINCE = "copy-source-if-modified-since"
HEADER_COPY_SOURCE_IF_UNMODIFIED_SINCE = "copy-source-if-unmodified-since"
HEADER_IF_MATCH = "If-Match"
HEADER_IF_NONE_MATCH = "If-None-Match"
HEADER_IF_MODIFIED_SINCE = "If-Modified-Since"
HEADER_IF_UNMODIFIED_SINCE = "If-Unmodified-Since"
HEADER_SSEC_ENCRYPTION = "server-side-encryption-customer-algorithm"
HEADER_SSEC_KEY = "server-side-encryption-customer-key"
HEADER_SSEC_KEY_MD5 = "server-side-encryption-customer-key-MD5"
HEADER_SSEKMS_ENCRYPTION = "server-side-encryption"
HEADER_SSEKMS_KEY = "server-side-encryption-aws-kms-key-id"
HEADER_SSEKMS_ENCRYPT_KEY_OBS = "server-side-encryption-kms-key-id"
HEADER_SSEC_COPY_SOURCE_ENCRYPTION = "copy-source-server-side-encryption-customer-algorithm"
HEADER_SSEC_COPY_SOURCE_KEY = "copy-source-server-side-encryption-customer-key"
HEADER_SSEC_COPY_SOURCE_KEY_MD5 = "copy-source-server-side-encryption-customer-key-MD5"
HEADER_SSEKMS_KEY_AMZ = "x-amz-server-side-encryption-aws-kms-key-id"
HEADER_SSEKMS_KEY_OBS = "x-obs-server-side-encryption-kms-key-id"
HEADER_SUCCESS_ACTION_REDIRECT = "success_action_redirect"
HEADER_DATE_CAMEL = "Date"
HEADER_HOST_CAMEL = "Host"
HEADER_HOST = "host"
HEADER_AUTH_CAMEL = "Authorization"
HEADER_MD5_CAMEL = "Content-MD5"
HEADER_LOCATION_CAMEL = "Location"
HEADER_CONTENT_LENGTH_CAMEL = "Content-Length"
HEADER_CONTENT_TYPE_CAML = "Content-Type"
HEADER_USER_AGENT_CAMEL = "User-Agent"
HEADER_ORIGIN_CAMEL = "Origin"
HEADER_ACCESS_CONTROL_REQUEST_HEADER_CAMEL = "Access-Control-Request-Headers"
PARAM_VERSION_ID = "versionId"
PARAM_RESPONSE_CONTENT_TYPE = "response-content-type"
PARAM_RESPONSE_CONTENT_LANGUAGE = "response-content-language"
PARAM_RESPONSE_EXPIRES = "response-expires"
PARAM_RESPONSE_CACHE_CONTROL = "response-cache-control"
PARAM_RESPONSE_CONTENT_DISPOSITION = "response-content-disposition"
PARAM_RESPONSE_CONTENT_ENCODING = "response-content-encoding"
PARAM_IMAGE_PROCESS = "x-image-process"
PARAM_ALGORITHM_AMZ_CAMEL = "X-Amz-Algorithm"
PARAM_CREDENTIAL_AMZ_CAMEL = "X-Amz-Credential"
PARAM_DATE_AMZ_CAMEL = "X-Amz-Date"
PARAM_DATE_OBS_CAMEL = "X-Obs-Date"
PARAM_EXPIRES_AMZ_CAMEL = "X-Amz-Expires"
PARAM_SIGNEDHEADERS_AMZ_CAMEL = "X-Amz-SignedHeaders"
PARAM_SIGNATURE_AMZ_CAMEL = "X-Amz-Signature"
DEFAULT_SIGNATURE = SignatureV2
DEFAULT_REGION = "region"
DEFAULT_CONNECT_TIMEOUT = 60
DEFAULT_SOCKET_TIMEOUT = 60
DEFAULT_HEADER_TIMEOUT = 60
DEFAULT_IDLE_CONN_TIMEOUT = 30
DEFAULT_MAX_RETRY_COUNT = 3
DEFAULT_MAX_CONN_PER_HOST = 1000
EMPTY_CONTENT_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"
LONG_DATE_FORMAT = "20060102T150405Z"
SHORT_DATE_FORMAT = "20060102"
ISO8601_DATE_FORMAT = "2006-01-02T15:04:05Z"
ISO8601_MIDNIGHT_DATE_FORMAT = "2006-01-02T00:00:00Z"
RFC1123_FORMAT = "Mon, 02 Jan 2006 15:04:05 GMT"
V4_SERVICE_NAME = "s3"
V4_SERVICE_SUFFIX = "aws4_request"
V2_HASH_PREFIX = "AWS"
OBS_HASH_PREFIX = "OBS"
V4_HASH_PREFIX = "AWS4-HMAC-SHA256"
V4_HASH_PRE = "AWS4"
DEFAULT_SSE_KMS_ENCRYPTION = "aws:kms"
DEFAULT_SSE_KMS_ENCRYPTION_OBS = "kms"
DEFAULT_SSE_C_ENCRYPTION = "AES256"
HTTP_GET = "GET"
HTTP_POST = "POST"
HTTP_PUT = "PUT"
HTTP_DELETE = "DELETE"
HTTP_HEAD = "HEAD"
HTTP_OPTIONS = "OPTIONS"
)
type SignatureType string
const (
SignatureV2 SignatureType = "v2"
SignatureV4 SignatureType = "v4"
SignatureObs SignatureType = "OBS"
)
var (
interested_headers = []string{"content-md5", "content-type", "date"}
allowed_response_http_header_metadata_names = map[string]bool{
"content-type": true,
"content-md5": true,
"content-length": true,
"content-language": true,
"expires": true,
"origin": true,
"cache-control": true,
"content-disposition": true,
"content-encoding": true,
"x-default-storage-class": true,
"location": true,
"date": true,
"etag": true,
"host": true,
"last-modified": true,
"content-range": true,
"x-reserved": true,
"access-control-allow-origin": true,
"access-control-allow-headers": true,
"access-control-max-age": true,
"access-control-allow-methods": true,
"access-control-expose-headers": true,
"connection": true,
}
allowed_request_http_header_metadata_names = map[string]bool{
"content-type": true,
"content-md5": true,
"content-length": true,
"content-language": true,
"expires": true,
"origin": true,
"cache-control": true,
"content-disposition": true,
"content-encoding": true,
"access-control-request-method": true,
"access-control-request-headers": true,
"x-default-storage-class": true,
"location": true,
"date": true,
"etag": true,
"range": true,
"host": true,
"if-modified-since": true,
"if-unmodified-since": true,
"if-match": true,
"if-none-match": true,
"last-modified": true,
"content-range": true,
}
allowed_resource_parameter_names = map[string]bool{
"acl": true,
"backtosource": true,
"policy": true,
"torrent": true,
"logging": true,
"location": true,
"storageinfo": true,
"quota": true,
"storageclass": true,
"storagepolicy": true,
"requestpayment": true,
"versions": true,
"versioning": true,
"versionid": true,
"uploads": true,
"uploadid": true,
"partnumber": true,
"website": true,
"notification": true,
"lifecycle": true,
"deletebucket": true,
"delete": true,
"cors": true,
"restore": true,
"tagging": true,
"append": true,
"position": true,
"replication": true,
"response-content-type": true,
"response-content-language": true,
"response-expires": true,
"response-cache-control": true,
"response-content-disposition": true,
"response-content-encoding": true,
"x-image-process": true,
"x-oss-process": true,
}
mime_types = map[string]string{
"7z": "application/x-7z-compressed",
"aac": "audio/x-aac",
"ai": "application/postscript",
"aif": "audio/x-aiff",
"asc": "text/plain",
"asf": "video/x-ms-asf",
"atom": "application/atom+xml",
"avi": "video/x-msvideo",
"bmp": "image/bmp",
"bz2": "application/x-bzip2",
"cer": "application/pkix-cert",
"crl": "application/pkix-crl",
"crt": "application/x-x509-ca-cert",
"css": "text/css",
"csv": "text/csv",
"cu": "application/cu-seeme",
"deb": "application/x-debian-package",
"doc": "application/msword",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"dvi": "application/x-dvi",
"eot": "application/vnd.ms-fontobject",
"eps": "application/postscript",
"epub": "application/epub+zip",
"etx": "text/x-setext",
"flac": "audio/flac",
"flv": "video/x-flv",
"gif": "image/gif",
"gz": "application/gzip",
"htm": "text/html",
"html": "text/html",
"ico": "image/x-icon",
"ics": "text/calendar",
"ini": "text/plain",
"iso": "application/x-iso9660-image",
"jar": "application/java-archive",
"jpe": "image/jpeg",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "text/javascript",
"json": "application/json",
"latex": "application/x-latex",
"log": "text/plain",
"m4a": "audio/mp4",
"m4v": "video/mp4",
"mid": "audio/midi",
"midi": "audio/midi",
"mov": "video/quicktime",
"mp3": "audio/mpeg",
"mp4": "video/mp4",
"mp4a": "audio/mp4",
"mp4v": "video/mp4",
"mpe": "video/mpeg",
"mpeg": "video/mpeg",
"mpg": "video/mpeg",
"mpg4": "video/mp4",
"oga": "audio/ogg",
"ogg": "audio/ogg",
"ogv": "video/ogg",
"ogx": "application/ogg",
"pbm": "image/x-portable-bitmap",
"pdf": "application/pdf",
"pgm": "image/x-portable-graymap",
"png": "image/png",
"pnm": "image/x-portable-anymap",
"ppm": "image/x-portable-pixmap",
"ppt": "application/vnd.ms-powerpoint",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"ps": "application/postscript",
"qt": "video/quicktime",
"rar": "application/x-rar-compressed",
"ras": "image/x-cmu-raster",
"rss": "application/rss+xml",
"rtf": "application/rtf",
"sgm": "text/sgml",
"sgml": "text/sgml",
"svg": "image/svg+xml",
"swf": "application/x-shockwave-flash",
"tar": "application/x-tar",
"tif": "image/tiff",
"tiff": "image/tiff",
"torrent": "application/x-bittorrent",
"ttf": "application/x-font-ttf",
"txt": "text/plain",
"wav": "audio/x-wav",
"webm": "video/webm",
"wma": "audio/x-ms-wma",
"wmv": "video/x-ms-wmv",
"woff": "application/x-font-woff",
"wsdl": "application/wsdl+xml",
"xbm": "image/x-xbitmap",
"xls": "application/vnd.ms-excel",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"xml": "application/xml",
"xpm": "image/x-xpixmap",
"xwd": "image/x-xwindowdump",
"yaml": "text/yaml",
"yml": "text/yaml",
"zip": "application/zip",
}
)
type HttpMethodType string
const (
HttpMethodGet HttpMethodType = HTTP_GET
HttpMethodPut HttpMethodType = HTTP_PUT
HttpMethodPost HttpMethodType = HTTP_POST
HttpMethodDelete HttpMethodType = HTTP_DELETE
HttpMethodHead HttpMethodType = HTTP_HEAD
HttpMethodOptions HttpMethodType = HTTP_OPTIONS
)
type SubResourceType string
const (
SubResourceStoragePolicy SubResourceType = "storagePolicy"
SubResourceStorageClass SubResourceType = "storageClass"
SubResourceQuota SubResourceType = "quota"
SubResourceStorageInfo SubResourceType = "storageinfo"
SubResourceLocation SubResourceType = "location"
SubResourceAcl SubResourceType = "acl"
SubResourcePolicy SubResourceType = "policy"
SubResourceCors SubResourceType = "cors"
SubResourceVersioning SubResourceType = "versioning"
SubResourceWebsite SubResourceType = "website"
SubResourceLogging SubResourceType = "logging"
SubResourceLifecycle SubResourceType = "lifecycle"
SubResourceNotification SubResourceType = "notification"
SubResourceTagging SubResourceType = "tagging"
SubResourceDelete SubResourceType = "delete"
SubResourceVersions SubResourceType = "versions"
SubResourceUploads SubResourceType = "uploads"
SubResourceRestore SubResourceType = "restore"
)
type AclType string
const (
AclPrivate AclType = "private"
AclPublicRead AclType = "public-read"
AclPublicReadWrite AclType = "public-read-write"
AclAuthenticatedRead AclType = "authenticated-read"
AclBucketOwnerRead AclType = "bucket-owner-read"
AclBucketOwnerFullControl AclType = "bucket-owner-full-control"
AclLogDeliveryWrite AclType = "log-delivery-write"
AclPublicReadDelivery AclType = "public-read-delivered"
AclPublicReadWriteDelivery AclType = "public-read-write-delivered"
)
type StorageClassType string
const (
StorageClassStandard StorageClassType = "STANDARD"
StorageClassWarm StorageClassType = "STANDARD_IA"
StorageClassCold StorageClassType = "GLACIER"
)
type PermissionType string
const (
PermissionRead PermissionType = "READ"
PermissionWrite PermissionType = "WRITE"
PermissionReadAcp PermissionType = "READ_ACP"
PermissionWriteAcp PermissionType = "WRITE_ACP"
PermissionFullControl PermissionType = "FULL_CONTROL"
)
type GranteeType string
const (
GranteeGroup GranteeType = "Group"
GranteeUser GranteeType = "CanonicalUser"
)
type GroupUriType string
const (
GroupAllUsers GroupUriType = "AllUsers"
GroupAuthenticatedUsers GroupUriType = "AuthenticatedUsers"
GroupLogDelivery GroupUriType = "LogDelivery"
)
type VersioningStatusType string
const (
VersioningStatusEnabled VersioningStatusType = "Enabled"
VersioningStatusSuspended VersioningStatusType = "Suspended"
)
type ProtocolType string
const (
ProtocolHttp ProtocolType = "http"
ProtocolHttps ProtocolType = "https"
)
type RuleStatusType string
const (
RuleStatusEnabled RuleStatusType = "Enabled"
RuleStatusDisabled RuleStatusType = "Disabled"
)
type RestoreTierType string
const (
RestoreTierExpedited RestoreTierType = "Expedited"
RestoreTierStandard RestoreTierType = "Standard"
RestoreTierBulk RestoreTierType = "Bulk"
)
type MetadataDirectiveType string
const (
CopyMetadata MetadataDirectiveType = "COPY"
ReplaceMetadata MetadataDirectiveType = "REPLACE"
)
type EventType string
const (
ObjectCreatedAll EventType = "ObjectCreated:*"
ObjectCreatedPut EventType = "ObjectCreated:Put"
ObjectCreatedPost EventType = "ObjectCreated:Post"
ObjectCreatedCopy EventType = "ObjectCreated:Copy"
ObjectCreatedCompleteMultipartUpload EventType = "ObjectCreated:CompleteMultipartUpload"
ObjectRemovedAll EventType = "ObjectRemoved:*"
ObjectRemovedDelete EventType = "ObjectRemoved:Delete"
ObjectRemovedDeleteMarkerCreated EventType = "ObjectRemoved:DeleteMarkerCreated"
)
+699
View File
@@ -0,0 +1,699 @@
package obs
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
"time"
)
func cleanHeaderPrefix(header http.Header, isObs bool) map[string][]string {
responseHeaders := make(map[string][]string)
for key, value := range header {
if len(value) > 0 {
key = strings.ToLower(key)
headerPrefix := HEADER_PREFIX
if isObs {
headerPrefix = HEADER_PREFIX_OBS
}
if strings.HasPrefix(key, headerPrefix) {
key = key[len(headerPrefix):]
}
responseHeaders[key] = value
}
}
return responseHeaders
}
func ParseStringToEventType(value string) (ret EventType) {
switch value {
case "ObjectCreated:*", "s3:ObjectCreated:*":
ret = ObjectCreatedAll
case "ObjectCreated:Put", "s3:ObjectCreated:Put":
ret = ObjectCreatedPut
case "ObjectCreated:Post", "s3:ObjectCreated:Post":
ret = ObjectCreatedPost
case "ObjectCreated:Copy", "s3:ObjectCreated:Copy":
ret = ObjectCreatedCopy
case "ObjectCreated:CompleteMultipartUpload", "s3:ObjectCreated:CompleteMultipartUpload":
ret = ObjectCreatedCompleteMultipartUpload
case "ObjectRemoved:*", "s3:ObjectRemoved:*":
ret = ObjectRemovedAll
case "ObjectRemoved:Delete", "s3:ObjectRemoved:Delete":
ret = ObjectRemovedDelete
case "ObjectRemoved:DeleteMarkerCreated", "s3:ObjectRemoved:DeleteMarkerCreated":
ret = ObjectRemovedDeleteMarkerCreated
default:
ret = ""
}
return
}
func ParseStringToStorageClassType(value string) (ret StorageClassType) {
switch value {
case "STANDARD":
ret = StorageClassStandard
case "STANDARD_IA", "WARM":
ret = StorageClassWarm
case "GLACIER", "COLD":
ret = StorageClassCold
default:
ret = ""
}
return
}
func convertGrantToXml(grant Grant, isObs bool) string {
xml := make([]string, 0, 4)
if !isObs {
xml = append(xml, fmt.Sprintf("<Grant><Grantee xsi:type=\"%s\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">", grant.Grantee.Type))
} else {
xml = append(xml, fmt.Sprintf("<Grant><Grantee>"))
}
if grant.Grantee.Type == GranteeUser {
if grant.Grantee.ID != "" {
granteeID := XmlTranscoding(grant.Grantee.ID)
xml = append(xml, fmt.Sprintf("<ID>%s</ID>", granteeID))
}
if grant.Grantee.DisplayName != "" {
granteeDisplayName := XmlTranscoding(grant.Grantee.DisplayName)
xml = append(xml, fmt.Sprintf("<DisplayName>%s</DisplayName>", granteeDisplayName))
}
} else {
if !isObs {
if grant.Grantee.URI == GroupAllUsers || grant.Grantee.URI == GroupAuthenticatedUsers {
xml = append(xml, fmt.Sprintf("<URI>%s%s</URI>", "http://acs.amazonaws.com/groups/global/", grant.Grantee.URI))
} else if grant.Grantee.URI == GroupLogDelivery {
xml = append(xml, fmt.Sprintf("<URI>%s%s</URI>", "http://acs.amazonaws.com/groups/s3/", grant.Grantee.URI))
} else {
xml = append(xml, fmt.Sprintf("<URI>%s</URI>", grant.Grantee.URI))
}
} else if grant.Grantee.URI == GroupAllUsers {
xml = append(xml, fmt.Sprintf("<Canned>Everyone</Canned>"))
}
}
xml = append(xml, fmt.Sprintf("</Grantee>"))
xml = append(xml, fmt.Sprintf("<Permission>%s</Permission>", grant.Permission))
if isObs {
xml = append(xml, fmt.Sprintf("<Delivered>%t</Delivered>", grant.Delivered))
}
xml = append(xml, fmt.Sprintf("</Grant>"))
return strings.Join(xml, "")
}
func ConvertLoggingStatusToXml(input BucketLoggingStatus, returnMd5 bool, isObs bool) (data string, md5 string) {
grantsLength := len(input.TargetGrants)
xml := make([]string, 0, 8+grantsLength)
xml = append(xml, "<BucketLoggingStatus>")
if input.Agency != "" {
agency := XmlTranscoding(input.Agency)
xml = append(xml, fmt.Sprintf("<Agency>%s</Agency>", agency))
}
if input.TargetBucket != "" || input.TargetPrefix != "" {
xml = append(xml, "<LoggingEnabled>")
xml = append(xml, fmt.Sprintf("<TargetBucket>%s</TargetBucket>", input.TargetBucket))
targetPrefix := XmlTranscoding(input.TargetPrefix)
xml = append(xml, fmt.Sprintf("<TargetPrefix>%s</TargetPrefix>", targetPrefix))
if grantsLength > 0 {
xml = append(xml, "<TargetGrants>")
for _, grant := range input.TargetGrants {
xml = append(xml, convertGrantToXml(grant, isObs))
}
xml = append(xml, "</TargetGrants>")
}
xml = append(xml, "</LoggingEnabled>")
}
xml = append(xml, "</BucketLoggingStatus>")
data = strings.Join(xml, "")
if returnMd5 {
md5 = Base64Md5([]byte(data))
}
return
}
func ConvertAclToXml(input AccessControlPolicy, returnMd5 bool, isObs bool) (data string, md5 string) {
xml := make([]string, 0, 4+len(input.Grants))
ownerID := XmlTranscoding(input.Owner.ID)
xml = append(xml, fmt.Sprintf("<AccessControlPolicy><Owner><ID>%s</ID>", ownerID))
if input.Owner.DisplayName != "" {
ownerDisplayName := XmlTranscoding(input.Owner.DisplayName)
xml = append(xml, fmt.Sprintf("<DisplayName>%s</DisplayName>", ownerDisplayName))
}
xml = append(xml, "</Owner><AccessControlList>")
for _, grant := range input.Grants {
xml = append(xml, convertGrantToXml(grant, isObs))
}
xml = append(xml, "</AccessControlList></AccessControlPolicy>")
data = strings.Join(xml, "")
if returnMd5 {
md5 = Base64Md5([]byte(data))
}
return
}
func convertConditionToXml(condition Condition) string {
xml := make([]string, 0, 2)
if condition.KeyPrefixEquals != "" {
keyPrefixEquals := XmlTranscoding(condition.KeyPrefixEquals)
xml = append(xml, fmt.Sprintf("<KeyPrefixEquals>%s</KeyPrefixEquals>", keyPrefixEquals))
}
if condition.HttpErrorCodeReturnedEquals != "" {
xml = append(xml, fmt.Sprintf("<HttpErrorCodeReturnedEquals>%s</HttpErrorCodeReturnedEquals>", condition.HttpErrorCodeReturnedEquals))
}
if len(xml) > 0 {
return fmt.Sprintf("<Condition>%s</Condition>", strings.Join(xml, ""))
}
return ""
}
func ConvertWebsiteConfigurationToXml(input BucketWebsiteConfiguration, returnMd5 bool) (data string, md5 string) {
routingRuleLength := len(input.RoutingRules)
xml := make([]string, 0, 6+routingRuleLength*10)
xml = append(xml, "<WebsiteConfiguration>")
if input.RedirectAllRequestsTo.HostName != "" {
xml = append(xml, fmt.Sprintf("<RedirectAllRequestsTo><HostName>%s</HostName>", input.RedirectAllRequestsTo.HostName))
if input.RedirectAllRequestsTo.Protocol != "" {
xml = append(xml, fmt.Sprintf("<Protocol>%s</Protocol>", input.RedirectAllRequestsTo.Protocol))
}
xml = append(xml, "</RedirectAllRequestsTo>")
} else {
indexDocumentSuffix := XmlTranscoding(input.IndexDocument.Suffix)
xml = append(xml, fmt.Sprintf("<IndexDocument><Suffix>%s</Suffix></IndexDocument>", indexDocumentSuffix))
if input.ErrorDocument.Key != "" {
errorDocumentKey := XmlTranscoding(input.ErrorDocument.Key)
xml = append(xml, fmt.Sprintf("<ErrorDocument><Key>%s</Key></ErrorDocument>", errorDocumentKey))
}
if routingRuleLength > 0 {
xml = append(xml, "<RoutingRules>")
for _, routingRule := range input.RoutingRules {
xml = append(xml, "<RoutingRule>")
xml = append(xml, "<Redirect>")
if routingRule.Redirect.Protocol != "" {
xml = append(xml, fmt.Sprintf("<Protocol>%s</Protocol>", routingRule.Redirect.Protocol))
}
if routingRule.Redirect.HostName != "" {
xml = append(xml, fmt.Sprintf("<HostName>%s</HostName>", routingRule.Redirect.HostName))
}
if routingRule.Redirect.ReplaceKeyPrefixWith != "" {
replaceKeyPrefixWith := XmlTranscoding(routingRule.Redirect.ReplaceKeyPrefixWith)
xml = append(xml, fmt.Sprintf("<ReplaceKeyPrefixWith>%s</ReplaceKeyPrefixWith>", replaceKeyPrefixWith))
}
if routingRule.Redirect.ReplaceKeyWith != "" {
replaceKeyWith := XmlTranscoding(routingRule.Redirect.ReplaceKeyWith)
xml = append(xml, fmt.Sprintf("<ReplaceKeyWith>%s</ReplaceKeyWith>", replaceKeyWith))
}
if routingRule.Redirect.HttpRedirectCode != "" {
xml = append(xml, fmt.Sprintf("<HttpRedirectCode>%s</HttpRedirectCode>", routingRule.Redirect.HttpRedirectCode))
}
xml = append(xml, "</Redirect>")
if ret := convertConditionToXml(routingRule.Condition); ret != "" {
xml = append(xml, ret)
}
xml = append(xml, "</RoutingRule>")
}
xml = append(xml, "</RoutingRules>")
}
}
xml = append(xml, "</WebsiteConfiguration>")
data = strings.Join(xml, "")
if returnMd5 {
md5 = Base64Md5([]byte(data))
}
return
}
func convertTransitionsToXml(transitions []Transition, isObs bool) string {
if length := len(transitions); length > 0 {
xml := make([]string, 0, length)
for _, transition := range transitions {
var temp string
if transition.Days > 0 {
temp = fmt.Sprintf("<Days>%d</Days>", transition.Days)
} else if !transition.Date.IsZero() {
temp = fmt.Sprintf("<Date>%s</Date>", transition.Date.UTC().Format(ISO8601_MIDNIGHT_DATE_FORMAT))
}
if temp != "" {
if !isObs {
storageClass := "STANDARD"
if transition.StorageClass == "WARM" {
storageClass = "STANDARD_IA"
} else if transition.StorageClass == "COLD" {
storageClass = "GLACIER"
}
xml = append(xml, fmt.Sprintf("<Transition>%s<StorageClass>%s</StorageClass></Transition>", temp, storageClass))
} else {
xml = append(xml, fmt.Sprintf("<Transition>%s<StorageClass>%s</StorageClass></Transition>", temp, transition.StorageClass))
}
}
}
return strings.Join(xml, "")
}
return ""
}
func convertExpirationToXml(expiration Expiration) string {
if expiration.Days > 0 {
return fmt.Sprintf("<Expiration><Days>%d</Days></Expiration>", expiration.Days)
} else if !expiration.Date.IsZero() {
return fmt.Sprintf("<Expiration><Date>%s</Date></Expiration>", expiration.Date.UTC().Format(ISO8601_MIDNIGHT_DATE_FORMAT))
}
return ""
}
func convertNoncurrentVersionTransitionsToXml(noncurrentVersionTransitions []NoncurrentVersionTransition, isObs bool) string {
if length := len(noncurrentVersionTransitions); length > 0 {
xml := make([]string, 0, length)
for _, noncurrentVersionTransition := range noncurrentVersionTransitions {
if noncurrentVersionTransition.NoncurrentDays > 0 {
storageClass := string(noncurrentVersionTransition.StorageClass)
if !isObs {
if storageClass == "WARM" {
storageClass = "STANDARD_IA"
} else if storageClass == "COLD" {
storageClass = "GLACIER"
}
}
xml = append(xml, fmt.Sprintf("<NoncurrentVersionTransition><NoncurrentDays>%d</NoncurrentDays>"+
"<StorageClass>%s</StorageClass></NoncurrentVersionTransition>",
noncurrentVersionTransition.NoncurrentDays, storageClass))
}
}
return strings.Join(xml, "")
}
return ""
}
func convertNoncurrentVersionExpirationToXml(noncurrentVersionExpiration NoncurrentVersionExpiration) string {
if noncurrentVersionExpiration.NoncurrentDays > 0 {
return fmt.Sprintf("<NoncurrentVersionExpiration><NoncurrentDays>%d</NoncurrentDays></NoncurrentVersionExpiration>", noncurrentVersionExpiration.NoncurrentDays)
}
return ""
}
func ConvertLifecyleConfigurationToXml(input BucketLifecyleConfiguration, returnMd5 bool, isObs bool) (data string, md5 string) {
xml := make([]string, 0, 2+len(input.LifecycleRules)*9)
xml = append(xml, "<LifecycleConfiguration>")
for _, lifecyleRule := range input.LifecycleRules {
xml = append(xml, "<Rule>")
if lifecyleRule.ID != "" {
lifecyleRuleID := XmlTranscoding(lifecyleRule.ID)
xml = append(xml, fmt.Sprintf("<ID>%s</ID>", lifecyleRuleID))
}
lifecyleRulePrefix := XmlTranscoding(lifecyleRule.Prefix)
xml = append(xml, fmt.Sprintf("<Prefix>%s</Prefix>", lifecyleRulePrefix))
xml = append(xml, fmt.Sprintf("<Status>%s</Status>", lifecyleRule.Status))
if ret := convertTransitionsToXml(lifecyleRule.Transitions, isObs); ret != "" {
xml = append(xml, ret)
}
if ret := convertExpirationToXml(lifecyleRule.Expiration); ret != "" {
xml = append(xml, ret)
}
if ret := convertNoncurrentVersionTransitionsToXml(lifecyleRule.NoncurrentVersionTransitions, isObs); ret != "" {
xml = append(xml, ret)
}
if ret := convertNoncurrentVersionExpirationToXml(lifecyleRule.NoncurrentVersionExpiration); ret != "" {
xml = append(xml, ret)
}
xml = append(xml, "</Rule>")
}
xml = append(xml, "</LifecycleConfiguration>")
data = strings.Join(xml, "")
if returnMd5 {
md5 = Base64Md5([]byte(data))
}
return
}
func converntFilterRulesToXml(filterRules []FilterRule, isObs bool) string {
if length := len(filterRules); length > 0 {
xml := make([]string, 0, length*4)
for _, filterRule := range filterRules {
xml = append(xml, "<FilterRule>")
if filterRule.Name != "" {
filterRuleName := XmlTranscoding(filterRule.Name)
xml = append(xml, fmt.Sprintf("<Name>%s</Name>", filterRuleName))
}
if filterRule.Value != "" {
filterRuleValue := XmlTranscoding(filterRule.Value)
xml = append(xml, fmt.Sprintf("<Value>%s</Value>", filterRuleValue))
}
xml = append(xml, "</FilterRule>")
}
if !isObs {
return fmt.Sprintf("<Filter><S3Key>%s</S3Key></Filter>", strings.Join(xml, ""))
} else {
return fmt.Sprintf("<Filter><Object>%s</Object></Filter>", strings.Join(xml, ""))
}
}
return ""
}
func converntEventsToXml(events []EventType, isObs bool) string {
if length := len(events); length > 0 {
xml := make([]string, 0, length)
if !isObs {
for _, event := range events {
xml = append(xml, fmt.Sprintf("<Event>%s%s</Event>", "s3:", event))
}
} else {
for _, event := range events {
xml = append(xml, fmt.Sprintf("<Event>%s</Event>", event))
}
}
return strings.Join(xml, "")
}
return ""
}
func converntConfigureToXml(topicConfiguration TopicConfiguration, xmlElem string, isObs bool) string {
xml := make([]string, 0, 6)
xml = append(xml, xmlElem)
if topicConfiguration.ID != "" {
topicConfigurationID := XmlTranscoding(topicConfiguration.ID)
xml = append(xml, fmt.Sprintf("<Id>%s</Id>", topicConfigurationID))
}
topicConfigurationTopic := XmlTranscoding(topicConfiguration.Topic)
xml = append(xml, fmt.Sprintf("<Topic>%s</Topic>", topicConfigurationTopic))
if ret := converntEventsToXml(topicConfiguration.Events, isObs); ret != "" {
xml = append(xml, ret)
}
if ret := converntFilterRulesToXml(topicConfiguration.FilterRules, isObs); ret != "" {
xml = append(xml, ret)
}
xml = append(xml, xmlElem)
return strings.Join(xml, "")
}
func ConverntObsRestoreToXml(restoreObjectInput RestoreObjectInput) string {
xml := make([]string, 0, 2)
xml = append(xml, fmt.Sprintf("<RestoreRequest><Days>%d</Days>", restoreObjectInput.Days))
if restoreObjectInput.Tier != "Bulk" {
xml = append(xml, fmt.Sprintf("<RestoreJob><Tier>%s</Tier></RestoreJob>", restoreObjectInput.Tier))
}
xml = append(xml, fmt.Sprintf("</RestoreRequest>"))
data := strings.Join(xml, "")
return data
}
func ConvertNotificationToXml(input BucketNotification, returnMd5 bool, isObs bool) (data string, md5 string) {
xml := make([]string, 0, 2+len(input.TopicConfigurations)*6)
xml = append(xml, "<NotificationConfiguration>")
for _, topicConfiguration := range input.TopicConfigurations {
ret := converntConfigureToXml(topicConfiguration, "<TopicConfiguration>", isObs)
xml = append(xml, ret)
}
xml = append(xml, "</NotificationConfiguration>")
data = strings.Join(xml, "")
if returnMd5 {
md5 = Base64Md5([]byte(data))
}
return
}
func ConvertCompleteMultipartUploadInputToXml(input CompleteMultipartUploadInput, returnMd5 bool) (data string, md5 string) {
xml := make([]string, 0, 2+len(input.Parts)*4)
xml = append(xml, "<CompleteMultipartUpload>")
for _, part := range input.Parts {
xml = append(xml, "<Part>")
xml = append(xml, fmt.Sprintf("<PartNumber>%d</PartNumber>", part.PartNumber))
xml = append(xml, fmt.Sprintf("<ETag>%s</ETag>", part.ETag))
xml = append(xml, "</Part>")
}
xml = append(xml, "</CompleteMultipartUpload>")
data = strings.Join(xml, "")
if returnMd5 {
md5 = Base64Md5([]byte(data))
}
return
}
func parseSseHeader(responseHeaders map[string][]string) (sseHeader ISseHeader) {
if ret, ok := responseHeaders[HEADER_SSEC_ENCRYPTION]; ok {
sseCHeader := SseCHeader{Encryption: ret[0]}
if ret, ok = responseHeaders[HEADER_SSEC_KEY_MD5]; ok {
sseCHeader.KeyMD5 = ret[0]
}
sseHeader = sseCHeader
} else if ret, ok := responseHeaders[HEADER_SSEKMS_ENCRYPTION]; ok {
sseKmsHeader := SseKmsHeader{Encryption: ret[0]}
if ret, ok = responseHeaders[HEADER_SSEKMS_KEY]; ok {
sseKmsHeader.Key = ret[0]
} else if ret, ok = responseHeaders[HEADER_SSEKMS_ENCRYPT_KEY_OBS]; ok {
sseKmsHeader.Key = ret[0]
}
sseHeader = sseKmsHeader
}
return
}
func ParseGetObjectMetadataOutput(output *GetObjectMetadataOutput) {
if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
output.VersionId = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_WEBSITE_REDIRECT_LOCATION]; ok {
output.WebsiteRedirectLocation = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_EXPIRATION]; ok {
output.Expiration = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_RESTORE]; ok {
output.Restore = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_OBJECT_TYPE]; ok {
output.Restore = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_NEXT_APPEND_POSITION]; ok {
output.Restore = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok {
output.StorageClass = ParseStringToStorageClassType(ret[0])
}
if ret, ok := output.ResponseHeaders[HEADER_ETAG]; ok {
output.ETag = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_CONTENT_TYPE]; ok {
output.ContentType = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_ORIGIN]; ok {
output.AllowOrigin = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_HEADERS]; ok {
output.AllowHeader = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_MAX_AGE]; ok {
output.MaxAgeSeconds = StringToInt(ret[0], 0)
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_METHODS]; ok {
output.AllowMethod = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_EXPOSE_HEADERS]; ok {
output.ExposeHeader = ret[0]
}
output.SseHeader = parseSseHeader(output.ResponseHeaders)
if ret, ok := output.ResponseHeaders[HEADER_LASTMODIFIED]; ok {
ret, err := time.Parse(time.RFC1123, ret[0])
if err == nil {
output.LastModified = ret
}
}
if ret, ok := output.ResponseHeaders[HEADER_CONTENT_LENGTH]; ok {
output.ContentLength = StringToInt64(ret[0], 0)
}
output.Metadata = make(map[string]string)
for key, value := range output.ResponseHeaders {
if strings.HasPrefix(key, PREFIX_META) {
_key := key[len(PREFIX_META):]
output.ResponseHeaders[_key] = value
output.Metadata[_key] = value[0]
delete(output.ResponseHeaders, key)
}
}
}
func ParseCopyObjectOutput(output *CopyObjectOutput) {
if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
output.VersionId = ret[0]
}
output.SseHeader = parseSseHeader(output.ResponseHeaders)
if ret, ok := output.ResponseHeaders[HEADER_COPY_SOURCE_VERSION_ID]; ok {
output.CopySourceVersionId = ret[0]
}
}
func ParsePutObjectOutput(output *PutObjectOutput) {
if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
output.VersionId = ret[0]
}
output.SseHeader = parseSseHeader(output.ResponseHeaders)
if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok {
output.StorageClass = ParseStringToStorageClassType(ret[0])
}
if ret, ok := output.ResponseHeaders[HEADER_ETAG]; ok {
output.ETag = ret[0]
}
}
func ParseInitiateMultipartUploadOutput(output *InitiateMultipartUploadOutput) {
output.SseHeader = parseSseHeader(output.ResponseHeaders)
}
func ParseUploadPartOutput(output *UploadPartOutput) {
output.SseHeader = parseSseHeader(output.ResponseHeaders)
if ret, ok := output.ResponseHeaders[HEADER_ETAG]; ok {
output.ETag = ret[0]
}
}
func ParseCompleteMultipartUploadOutput(output *CompleteMultipartUploadOutput) {
output.SseHeader = parseSseHeader(output.ResponseHeaders)
if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
output.VersionId = ret[0]
}
}
func ParseCopyPartOutput(output *CopyPartOutput) {
output.SseHeader = parseSseHeader(output.ResponseHeaders)
}
func ParseGetBucketMetadataOutput(output *GetBucketMetadataOutput) {
if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS]; ok {
output.StorageClass = ParseStringToStorageClassType(ret[0])
} else if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS_OBS]; ok {
output.StorageClass = ParseStringToStorageClassType(ret[0])
}
if ret, ok := output.ResponseHeaders[HEADER_VERSION_OBS]; ok {
output.Version = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_BUCKET_REGION]; ok {
output.Location = ret[0]
} else if ret, ok := output.ResponseHeaders[HEADER_BUCKET_LOCATION_OBS]; ok {
output.Location = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_ORIGIN]; ok {
output.AllowOrigin = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_HEADERS]; ok {
output.AllowHeader = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_MAX_AGE]; ok {
output.MaxAgeSeconds = StringToInt(ret[0], 0)
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_METHODS]; ok {
output.AllowMethod = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_EXPOSE_HEADERS]; ok {
output.ExposeHeader = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_EPID_HEADERS]; ok {
output.Epid = ret[0]
}
}
func ParseDeleteObjectOutput(output *DeleteObjectOutput) {
if versionId, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
output.VersionId = versionId[0]
}
if deleteMarker, ok := output.ResponseHeaders[HEADER_DELETE_MARKER]; ok {
output.DeleteMarker = deleteMarker[0] == "true"
}
}
func ParseGetObjectOutput(output *GetObjectOutput) {
ParseGetObjectMetadataOutput(&output.GetObjectMetadataOutput)
if ret, ok := output.ResponseHeaders[HEADER_DELETE_MARKER]; ok {
output.DeleteMarker = ret[0] == "true"
}
if ret, ok := output.ResponseHeaders[HEADER_CACHE_CONTROL]; ok {
output.CacheControl = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_CONTENT_DISPOSITION]; ok {
output.ContentDisposition = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_CONTENT_ENCODING]; ok {
output.ContentEncoding = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_CONTENT_LANGUAGE]; ok {
output.ContentLanguage = ret[0]
}
if ret, ok := output.ResponseHeaders[HEADER_EXPIRES]; ok {
output.Expires = ret[0]
}
}
func ConvertRequestToIoReaderV2(req interface{}) (io.Reader, string, error) {
data, err := TransToXml(req)
if err == nil {
if isDebugLogEnabled() {
doLog(LEVEL_DEBUG, "Do http request with data: %s", string(data))
}
return bytes.NewReader(data), Base64Md5(data), nil
}
return nil, "", err
}
func ConvertRequestToIoReader(req interface{}) (io.Reader, error) {
body, err := TransToXml(req)
if err == nil {
if isDebugLogEnabled() {
doLog(LEVEL_DEBUG, "Do http request with data: %s", string(body))
}
return bytes.NewReader(body), nil
}
return nil, err
}
func ParseResponseToBaseModel(resp *http.Response, baseModel IBaseModel, xmlResult bool, isObs bool) (err error) {
readCloser, ok := baseModel.(IReadCloser)
if !ok {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err == nil && len(body) > 0 {
if xmlResult {
err = ParseXml(body, baseModel)
if err != nil {
doLog(LEVEL_ERROR, "Unmarshal error: %v", err)
}
} else {
s := reflect.TypeOf(baseModel).Elem()
for i := 0; i < s.NumField(); i++ {
if s.Field(i).Tag == "body" {
reflect.ValueOf(baseModel).Elem().FieldByName(s.Field(i).Name).SetString(string(body))
break
}
}
}
}
} else {
readCloser.setReadCloser(resp.Body)
}
baseModel.setStatusCode(resp.StatusCode)
responseHeaders := cleanHeaderPrefix(resp.Header, isObs)
baseModel.setResponseHeaders(responseHeaders)
if values, ok := responseHeaders[HEADER_REQUEST_ID]; ok {
baseModel.setRequestId(values[0])
}
return
}
func ParseResponseToObsError(resp *http.Response, isObs bool) error {
obsError := ObsError{}
ParseResponseToBaseModel(resp, &obsError, true, isObs)
obsError.Status = resp.Status
return obsError
}
+1
View File
@@ -0,0 +1 @@
package obs // import "yunion.io/x/onecloud/pkg/util/huawei/obs"
+21
View File
@@ -0,0 +1,21 @@
package obs
import (
"encoding/xml"
"fmt"
)
type ObsError struct {
BaseModel
Status string
XMLName xml.Name `xml:"Error"`
Code string `xml:"Code"`
Message string `xml:"Message"`
Resource string `xml:"Resource"`
HostId string `xml:"HostId"`
}
func (err ObsError) Error() string {
return fmt.Sprintf("obs: service returned error: Status=%s, Code=%s, Message=%s, RequestId=%s",
err.Status, err.Code, err.Message, err.RequestId)
}
+429
View File
@@ -0,0 +1,429 @@
package obs
import (
"bytes"
"errors"
"io"
"math/rand"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
)
func prepareHeaders(headers map[string][]string, meta bool, isObs bool) map[string][]string {
_headers := make(map[string][]string, len(headers))
if headers != nil {
for key, value := range headers {
key = strings.TrimSpace(key)
if key == "" {
continue
}
_key := strings.ToLower(key)
if _, ok := allowed_request_http_header_metadata_names[_key]; !ok && !strings.HasPrefix(key, HEADER_PREFIX) && !strings.HasPrefix(key, HEADER_PREFIX_OBS) {
if !meta {
continue
}
if !isObs {
_key = HEADER_PREFIX_META + _key
} else {
_key = HEADER_PREFIX_META_OBS + _key
}
} else {
_key = key
}
_headers[_key] = value
}
}
return _headers
}
func (obsClient ObsClient) doActionWithoutBucket(action, method string, input ISerializable, output IBaseModel) error {
return obsClient.doAction(action, method, "", "", input, output, true, true)
}
func (obsClient ObsClient) doActionWithBucketV2(action, method, bucketName string, input ISerializable, output IBaseModel) error {
if strings.TrimSpace(bucketName) == "" {
return errors.New("Bucket is empty")
}
return obsClient.doAction(action, method, bucketName, "", input, output, false, true)
}
func (obsClient ObsClient) doActionWithBucket(action, method, bucketName string, input ISerializable, output IBaseModel) error {
if strings.TrimSpace(bucketName) == "" {
return errors.New("Bucket is empty")
}
return obsClient.doAction(action, method, bucketName, "", input, output, true, true)
}
func (obsClient ObsClient) doActionWithBucketAndKey(action, method, bucketName, objectKey string, input ISerializable, output IBaseModel) error {
return obsClient._doActionWithBucketAndKey(action, method, bucketName, objectKey, input, output, true)
}
func (obsClient ObsClient) doActionWithBucketAndKeyUnRepeatable(action, method, bucketName, objectKey string, input ISerializable, output IBaseModel) error {
return obsClient._doActionWithBucketAndKey(action, method, bucketName, objectKey, input, output, false)
}
func (obsClient ObsClient) _doActionWithBucketAndKey(action, method, bucketName, objectKey string, input ISerializable, output IBaseModel, repeatable bool) error {
if strings.TrimSpace(bucketName) == "" {
return errors.New("Key is empty")
}
if strings.TrimSpace(objectKey) == "" {
return errors.New("Key is empty")
}
return obsClient.doAction(action, method, bucketName, objectKey, input, output, true, repeatable)
}
func (obsClient ObsClient) doAction(action, method, bucketName, objectKey string, input ISerializable, output IBaseModel, xmlResult bool, repeatable bool) error {
var resp *http.Response
var respError error
doLog(LEVEL_INFO, "Enter method %s...", action)
start := GetCurrentTimestamp()
params, headers, data := input.trans(obsClient.conf.signature == SignatureObs)
if params == nil {
params = make(map[string]string)
}
if headers == nil {
headers = make(map[string][]string)
}
switch method {
case HTTP_GET:
resp, respError = obsClient.doHttpGet(bucketName, objectKey, params, headers, data, repeatable)
case HTTP_POST:
resp, respError = obsClient.doHttpPost(bucketName, objectKey, params, headers, data, repeatable)
case HTTP_PUT:
resp, respError = obsClient.doHttpPut(bucketName, objectKey, params, headers, data, repeatable)
case HTTP_DELETE:
resp, respError = obsClient.doHttpDelete(bucketName, objectKey, params, headers, data, repeatable)
case HTTP_HEAD:
resp, respError = obsClient.doHttpHead(bucketName, objectKey, params, headers, data, repeatable)
case HTTP_OPTIONS:
resp, respError = obsClient.doHttpOptions(bucketName, objectKey, params, headers, data, repeatable)
default:
respError = errors.New("Unexpect http method error")
}
if respError == nil && output != nil {
respError = ParseResponseToBaseModel(resp, output, xmlResult, obsClient.conf.signature == SignatureObs)
if respError != nil {
doLog(LEVEL_WARN, "Parse response to BaseModel with error: %v", respError)
}
} else {
doLog(LEVEL_WARN, "Do http request with error: %v", respError)
}
if isDebugLogEnabled() {
doLog(LEVEL_DEBUG, "End method %s, obsclient cost %d ms", action, (GetCurrentTimestamp() - start))
}
return respError
}
func (obsClient ObsClient) doHttpGet(bucketName, objectKey string, params map[string]string,
headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
return obsClient.doHttp(HTTP_GET, bucketName, objectKey, params, prepareHeaders(headers, false, obsClient.conf.signature == SignatureObs), data, repeatable)
}
func (obsClient ObsClient) doHttpHead(bucketName, objectKey string, params map[string]string,
headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
return obsClient.doHttp(HTTP_HEAD, bucketName, objectKey, params, prepareHeaders(headers, false, obsClient.conf.signature == SignatureObs), data, repeatable)
}
func (obsClient ObsClient) doHttpOptions(bucketName, objectKey string, params map[string]string,
headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
return obsClient.doHttp(HTTP_OPTIONS, bucketName, objectKey, params, prepareHeaders(headers, false, obsClient.conf.signature == SignatureObs), data, repeatable)
}
func (obsClient ObsClient) doHttpDelete(bucketName, objectKey string, params map[string]string,
headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
return obsClient.doHttp(HTTP_DELETE, bucketName, objectKey, params, prepareHeaders(headers, false, obsClient.conf.signature == SignatureObs), data, repeatable)
}
func (obsClient ObsClient) doHttpPut(bucketName, objectKey string, params map[string]string,
headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
return obsClient.doHttp(HTTP_PUT, bucketName, objectKey, params, prepareHeaders(headers, true, obsClient.conf.signature == SignatureObs), data, repeatable)
}
func (obsClient ObsClient) doHttpPost(bucketName, objectKey string, params map[string]string,
headers map[string][]string, data interface{}, repeatable bool) (*http.Response, error) {
return obsClient.doHttp(HTTP_POST, bucketName, objectKey, params, prepareHeaders(headers, true, obsClient.conf.signature == SignatureObs), data, repeatable)
}
func (obsClient ObsClient) doHttpWithSignedUrl(action, method string, signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader, output IBaseModel, xmlResult bool) (respError error) {
req, err := http.NewRequest(method, signedUrl, data)
if err != nil {
return err
}
if obsClient.conf.ctx != nil {
req = req.WithContext(obsClient.conf.ctx)
}
var resp *http.Response
doLog(LEVEL_INFO, "Do %s with signedUrl %s...", action, signedUrl)
req.Header = actualSignedRequestHeaders
if value, ok := req.Header[HEADER_HOST_CAMEL]; ok {
req.Host = value[0]
delete(req.Header, HEADER_HOST_CAMEL)
} else if value, ok := req.Header[HEADER_HOST]; ok {
req.Host = value[0]
delete(req.Header, HEADER_HOST)
}
if value, ok := req.Header[HEADER_CONTENT_LENGTH_CAMEL]; ok {
req.ContentLength = StringToInt64(value[0], -1)
delete(req.Header, HEADER_CONTENT_LENGTH_CAMEL)
} else if value, ok := req.Header[HEADER_CONTENT_LENGTH]; ok {
req.ContentLength = StringToInt64(value[0], -1)
delete(req.Header, HEADER_CONTENT_LENGTH)
}
req.Header[HEADER_USER_AGENT_CAMEL] = []string{USER_AGENT}
start := GetCurrentTimestamp()
resp, err = obsClient.httpClient.Do(req)
if isInfoLogEnabled() {
doLog(LEVEL_INFO, "Do http request cost %d ms", (GetCurrentTimestamp() - start))
}
var msg interface{}
if err != nil {
respError = err
resp = nil
} else {
doLog(LEVEL_DEBUG, "Response headers: %v", resp.Header)
if resp.StatusCode >= 300 {
respError = ParseResponseToObsError(resp, obsClient.conf.signature == SignatureObs)
msg = resp.Status
resp = nil
} else {
if output != nil {
respError = ParseResponseToBaseModel(resp, output, xmlResult, obsClient.conf.signature == SignatureObs)
}
if respError != nil {
doLog(LEVEL_WARN, "Parse response to BaseModel with error: %v", respError)
}
}
}
if msg != nil {
doLog(LEVEL_ERROR, "Failed to send request with reason:%v", msg)
}
if isDebugLogEnabled() {
doLog(LEVEL_DEBUG, "End method %s, obsclient cost %d ms", action, (GetCurrentTimestamp() - start))
}
return
}
func (obsClient ObsClient) doHttp(method, bucketName, objectKey string, params map[string]string,
headers map[string][]string, data interface{}, repeatable bool) (resp *http.Response, respError error) {
bucketName = strings.TrimSpace(bucketName)
method = strings.ToUpper(method)
var redirectUrl string
var requestUrl string
maxRetryCount := obsClient.conf.maxRetryCount
var _data io.Reader
if data != nil {
if dataStr, ok := data.(string); ok {
doLog(LEVEL_DEBUG, "Do http request with string: %s", dataStr)
headers["Content-Length"] = []string{IntToString(len(dataStr))}
_data = strings.NewReader(dataStr)
} else if dataByte, ok := data.([]byte); ok {
doLog(LEVEL_DEBUG, "Do http request with byte array")
headers["Content-Length"] = []string{IntToString(len(dataByte))}
_data = bytes.NewReader(dataByte)
} else if dataReader, ok := data.(io.Reader); ok {
_data = dataReader
} else {
doLog(LEVEL_WARN, "Data is not a valid io.Reader")
return nil, errors.New("Data is not a valid io.Reader")
}
}
for i := 0; i <= maxRetryCount; i++ {
if redirectUrl != "" {
parsedRedirectUrl, err := url.Parse(redirectUrl)
if err != nil {
return nil, err
}
requestUrl, _ = obsClient.doAuth(method, bucketName, objectKey, params, headers, parsedRedirectUrl.Host)
if parsedRequestUrl, _ := url.Parse(requestUrl); parsedRequestUrl.RawQuery != "" && parsedRedirectUrl.RawQuery == "" {
redirectUrl += "?" + parsedRequestUrl.RawQuery
}
requestUrl = redirectUrl
} else {
var err error
requestUrl, err = obsClient.doAuth(method, bucketName, objectKey, params, headers, "")
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, requestUrl, _data)
if obsClient.conf.ctx != nil {
req = req.WithContext(obsClient.conf.ctx)
}
if err != nil {
return nil, err
}
doLog(LEVEL_DEBUG, "Do request with url [%s] and method [%s]", requestUrl, method)
if isDebugLogEnabled() {
auth := headers[HEADER_AUTH_CAMEL]
delete(headers, HEADER_AUTH_CAMEL)
doLog(LEVEL_DEBUG, "Request headers: %v", headers)
headers[HEADER_AUTH_CAMEL] = auth
}
for key, value := range headers {
if key == HEADER_HOST_CAMEL {
req.Host = value[0]
delete(headers, key)
} else if key == HEADER_CONTENT_LENGTH_CAMEL {
req.ContentLength = StringToInt64(value[0], -1)
delete(headers, key)
} else {
req.Header[key] = value
}
}
req.Header[HEADER_USER_AGENT_CAMEL] = []string{USER_AGENT}
start := GetCurrentTimestamp()
resp, err = obsClient.httpClient.Do(req)
if isInfoLogEnabled() {
doLog(LEVEL_INFO, "Do http request cost %d ms", (GetCurrentTimestamp() - start))
}
var msg interface{}
if err != nil {
msg = err
respError = err
resp = nil
} else {
doLog(LEVEL_DEBUG, "Response headers: %v", resp.Header)
if resp.StatusCode < 300 {
break
} else if !repeatable || (resp.StatusCode >= 400 && resp.StatusCode < 500) || resp.StatusCode == 304 {
respError = ParseResponseToObsError(resp, obsClient.conf.signature == SignatureObs)
resp = nil
break
} else if resp.StatusCode >= 300 && resp.StatusCode < 400 {
if location := resp.Header.Get(HEADER_LOCATION_CAMEL); location != "" {
redirectUrl = location
doLog(LEVEL_WARN, "Redirect request to %s", redirectUrl)
msg = resp.Status
maxRetryCount++
} else {
respError = ParseResponseToObsError(resp, obsClient.conf.signature == SignatureObs)
resp = nil
break
}
} else {
msg = resp.Status
}
}
if i != maxRetryCount {
if resp != nil {
resp.Body.Close()
resp = nil
}
if _, ok := headers[HEADER_AUTH_CAMEL]; ok {
delete(headers, HEADER_AUTH_CAMEL)
}
doLog(LEVEL_WARN, "Failed to send request with reason:%v, will try again", msg)
if r, ok := _data.(*strings.Reader); ok {
r.Seek(0, 0)
} else if r, ok := _data.(*bytes.Reader); ok {
r.Seek(0, 0)
} else if r, ok := _data.(*fileReaderWrapper); ok {
fd, err := os.Open(r.filePath)
if err != nil {
return nil, err
}
defer fd.Close()
fileReaderWrapper := &fileReaderWrapper{filePath: r.filePath}
fileReaderWrapper.mark = r.mark
fileReaderWrapper.reader = fd
fileReaderWrapper.totalCount = r.totalCount
_data = fileReaderWrapper
fd.Seek(r.mark, 0)
} else if r, ok := _data.(*readerWrapper); ok {
r.seek(0, 0)
}
time.Sleep(time.Duration(float64(i+2) * rand.Float64() * float64(time.Second)))
} else {
doLog(LEVEL_ERROR, "Failed to send request with reason:%v", msg)
if resp != nil {
respError = ParseResponseToObsError(resp, obsClient.conf.signature == SignatureObs)
resp = nil
}
}
}
return
}
type connDelegate struct {
conn net.Conn
socketTimeout time.Duration
finalTimeout time.Duration
}
func getConnDelegate(conn net.Conn, socketTimeout int, finalTimeout int) *connDelegate {
return &connDelegate{
conn: conn,
socketTimeout: time.Second * time.Duration(socketTimeout),
finalTimeout: time.Second * time.Duration(finalTimeout),
}
}
func (delegate *connDelegate) Read(b []byte) (n int, err error) {
delegate.SetReadDeadline(time.Now().Add(delegate.socketTimeout))
n, err = delegate.conn.Read(b)
delegate.SetReadDeadline(time.Now().Add(delegate.finalTimeout))
return n, err
}
func (delegate *connDelegate) Write(b []byte) (n int, err error) {
delegate.SetWriteDeadline(time.Now().Add(delegate.socketTimeout))
n, err = delegate.conn.Write(b)
finalTimeout := time.Now().Add(delegate.finalTimeout)
delegate.SetWriteDeadline(finalTimeout)
delegate.SetReadDeadline(finalTimeout)
return n, err
}
func (delegate *connDelegate) Close() error {
return delegate.conn.Close()
}
func (delegate *connDelegate) LocalAddr() net.Addr {
return delegate.conn.LocalAddr()
}
func (delegate *connDelegate) RemoteAddr() net.Addr {
return delegate.conn.RemoteAddr()
}
func (delegate *connDelegate) SetDeadline(t time.Time) error {
return delegate.conn.SetDeadline(t)
}
func (delegate *connDelegate) SetReadDeadline(t time.Time) error {
return delegate.conn.SetReadDeadline(t)
}
func (delegate *connDelegate) SetWriteDeadline(t time.Time) error {
return delegate.conn.SetWriteDeadline(t)
}
+277
View File
@@ -0,0 +1,277 @@
package obs
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
)
type Level int
const (
LEVEL_OFF Level = 500
LEVEL_ERROR Level = 400
LEVEL_WARN Level = 300
LEVEL_INFO Level = 200
LEVEL_DEBUG Level = 100
)
var logLevelMap = map[Level]string{
LEVEL_OFF: "[OFF]: ",
LEVEL_ERROR: "[ERROR]: ",
LEVEL_WARN: "[WARN]: ",
LEVEL_INFO: "[INFO]: ",
LEVEL_DEBUG: "[DEBUG]: ",
}
type logConfType struct {
level Level
logToConsole bool
logFullPath string
maxLogSize int64
backups int
}
func getDefaultLogConf() logConfType {
return logConfType{
level: LEVEL_WARN,
logToConsole: false,
logFullPath: "",
maxLogSize: 1024 * 1024 * 30, //30MB
backups: 10,
}
}
var logConf logConfType
type loggerWrapper struct {
fullPath string
fd *os.File
ch chan string
wg sync.WaitGroup
queue []string
logger *log.Logger
index int
cacheCount int
closed bool
}
func (lw *loggerWrapper) doInit() {
lw.queue = make([]string, 0, lw.cacheCount)
lw.logger = log.New(lw.fd, "", 0)
lw.ch = make(chan string, lw.cacheCount)
lw.wg.Add(1)
go lw.doWrite()
}
func (lw *loggerWrapper) rotate() {
stat, err := lw.fd.Stat()
if err != nil {
lw.fd.Close()
panic(err)
}
if stat.Size() >= logConf.maxLogSize {
lw.fd.Sync()
lw.fd.Close()
if lw.index > logConf.backups {
lw.index = 1
}
os.Rename(lw.fullPath, lw.fullPath+"."+IntToString(lw.index))
lw.index += 1
fd, err := os.OpenFile(lw.fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
lw.fd = fd
lw.logger.SetOutput(lw.fd)
}
}
func (lw *loggerWrapper) doFlush() {
lw.rotate()
for _, m := range lw.queue {
lw.logger.Println(m)
}
lw.fd.Sync()
}
func (lw *loggerWrapper) doClose() {
lw.closed = true
close(lw.ch)
lw.wg.Wait()
}
func (lw *loggerWrapper) doWrite() {
defer lw.wg.Done()
for {
msg, ok := <-lw.ch
if !ok {
lw.doFlush()
lw.fd.Close()
break
}
if len(lw.queue) >= lw.cacheCount {
lw.doFlush()
lw.queue = make([]string, 0, lw.cacheCount)
}
lw.queue = append(lw.queue, msg)
}
}
func (lw *loggerWrapper) Printf(format string, v ...interface{}) {
if !lw.closed {
msg := fmt.Sprintf(format, v...)
lw.ch <- msg
}
}
var consoleLogger *log.Logger
var fileLogger *loggerWrapper
var lock *sync.RWMutex = new(sync.RWMutex)
func isDebugLogEnabled() bool {
return logConf.level <= LEVEL_DEBUG
}
func isErrorLogEnabled() bool {
return logConf.level <= LEVEL_ERROR
}
func isWarnLogEnabled() bool {
return logConf.level <= LEVEL_WARN
}
func isInfoLogEnabled() bool {
return logConf.level <= LEVEL_INFO
}
func reset() {
if fileLogger != nil {
fileLogger.doClose()
fileLogger = nil
}
consoleLogger = nil
logConf = getDefaultLogConf()
}
func InitLog(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool) error {
return InitLogWithCacheCnt(logFullPath, maxLogSize, backups, level, logToConsole, 50)
}
func InitLogWithCacheCnt(logFullPath string, maxLogSize int64, backups int, level Level, logToConsole bool, cacheCnt int) error {
lock.Lock()
defer lock.Unlock()
if cacheCnt <= 0 {
cacheCnt = 50
}
reset()
if fullPath := strings.TrimSpace(logFullPath); fullPath != "" {
_fullPath, err := filepath.Abs(fullPath)
if err != nil {
return err
}
if !strings.HasSuffix(_fullPath, ".log") {
_fullPath += ".log"
}
stat, err := os.Stat(_fullPath)
if err == nil && stat.IsDir() {
return errors.New(fmt.Sprintf("logFullPath:[%s] is a directory", _fullPath))
} else if err := os.MkdirAll(filepath.Dir(_fullPath), os.ModePerm); err != nil {
return err
}
fd, err := os.OpenFile(_fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return err
}
if stat == nil {
stat, err = os.Stat(_fullPath)
if err != nil {
fd.Close()
return err
}
}
prefix := stat.Name() + "."
index := 1
walkFunc := func(path string, info os.FileInfo, err error) error {
if err == nil {
if name := info.Name(); strings.HasPrefix(name, prefix) {
if i := StringToInt(name[len(prefix):], 0); i >= index {
index = i + 1
}
}
}
return err
}
if err = filepath.Walk(filepath.Dir(_fullPath), walkFunc); err != nil {
fd.Close()
return err
}
fileLogger = &loggerWrapper{fullPath: _fullPath, fd: fd, index: index, cacheCount: cacheCnt, closed: false}
fileLogger.doInit()
}
if maxLogSize > 0 {
logConf.maxLogSize = maxLogSize
}
if backups > 0 {
logConf.backups = backups
}
logConf.level = level
if logToConsole {
consoleLogger = log.New(os.Stdout, "", log.LstdFlags)
}
return nil
}
func CloseLog() {
if logEnabled() {
lock.Lock()
defer lock.Unlock()
reset()
}
}
func SyncLog() {
}
func logEnabled() bool {
return consoleLogger != nil || fileLogger != nil
}
func DoLog(level Level, format string, v ...interface{}) {
doLog(level, format, v)
}
func doLog(level Level, format string, v ...interface{}) {
if logEnabled() && logConf.level <= level {
msg := fmt.Sprintf(format, v...)
if _, file, line, ok := runtime.Caller(1); ok {
index := strings.LastIndex(file, "/")
if index >= 0 {
file = file[index+1:]
}
msg = fmt.Sprintf("%s:%d|%s", file, line, msg)
}
prefix := logLevelMap[level]
if consoleLogger != nil {
consoleLogger.Printf("%s%s", prefix, msg)
}
if fileLogger != nil {
nowDate := FormatUtcNow("2006-01-02T15:04:05Z")
fileLogger.Printf("%s %s%s", nowDate, prefix, msg)
}
}
}
+931
View File
@@ -0,0 +1,931 @@
package obs
import (
"encoding/xml"
"io"
"net/http"
"time"
)
type BaseModel struct {
StatusCode int `xml:"-"`
RequestId string `xml:"RequestId"`
ResponseHeaders map[string][]string `xml:"-"`
}
type Bucket struct {
XMLName xml.Name `xml:"Bucket"`
Name string `xml:"Name"`
CreationDate time.Time `xml:"CreationDate"`
Location string `xml:"Location"`
}
type Owner struct {
XMLName xml.Name `xml:"Owner"`
ID string `xml:"ID"`
DisplayName string `xml:"DisplayName,omitempty"`
}
type Initiator struct {
XMLName xml.Name `xml:"Initiator"`
ID string `xml:"ID"`
DisplayName string `xml:"DisplayName,omitempty"`
}
type ListBucketsInput struct {
QueryLocation bool
}
type ListBucketsOutput struct {
BaseModel
XMLName xml.Name `xml:"ListAllMyBucketsResult"`
Owner Owner `xml:"Owner"`
Buckets []Bucket `xml:"Buckets>Bucket"`
}
type bucketLocationObs struct {
XMLName xml.Name `xml:"Location"`
Location string `xml:",chardata"`
}
type BucketLocation struct {
XMLName xml.Name `xml:"CreateBucketConfiguration"`
Location string `xml:"LocationConstraint,omitempty"`
}
type CreateBucketInput struct {
BucketLocation
Bucket string `xml:"-"`
ACL AclType `xml:"-"`
StorageClass StorageClassType `xml:"-"`
GrantReadId string `xml:"-"`
GrantWriteId string `xml:"-"`
GrantReadAcpId string `xml:"-"`
GrantWriteAcpId string `xml:"-"`
GrantFullControlId string `xml:"-"`
GrantReadDeliveredId string `xml:"-"`
GrantFullControlDeliveredId string `xml:"-"`
Epid string `xml:"-"`
}
type BucketStoragePolicy struct {
XMLName xml.Name `xml:"StoragePolicy"`
StorageClass StorageClassType `xml:"DefaultStorageClass"`
}
type SetBucketStoragePolicyInput struct {
Bucket string `xml:"-"`
BucketStoragePolicy
}
type getBucketStoragePolicyOutputS3 struct {
BaseModel
BucketStoragePolicy
}
type GetBucketStoragePolicyOutput struct {
BaseModel
StorageClass string
}
type bucketStoragePolicyObs struct {
XMLName xml.Name `xml:"StorageClass"`
StorageClass string `xml:",chardata"`
}
type getBucketStoragePolicyOutputObs struct {
BaseModel
bucketStoragePolicyObs
}
type ListObjsInput struct {
Prefix string
MaxKeys int
Delimiter string
Origin string
RequestHeader string
}
type ListObjectsInput struct {
ListObjsInput
Bucket string
Marker string
}
type Content struct {
XMLName xml.Name `xml:"Contents"`
Owner Owner `xml:"Owner"`
ETag string `xml:"ETag"`
Key string `xml:"Key"`
LastModified time.Time `xml:"LastModified"`
Size int64 `xml:"Size"`
StorageClass StorageClassType `xml:"StorageClass"`
}
type ListObjectsOutput struct {
BaseModel
XMLName xml.Name `xml:"ListBucketResult"`
Delimiter string `xml:"Delimiter"`
IsTruncated bool `xml:"IsTruncated"`
Marker string `xml:"Marker"`
NextMarker string `xml:"NextMarker"`
MaxKeys int `xml:"MaxKeys"`
Name string `xml:"Name"`
Prefix string `xml:"Prefix"`
Contents []Content `xml:"Contents"`
CommonPrefixes []string `xml:"CommonPrefixes>Prefix"`
Location string `xml:"-"`
}
type ListVersionsInput struct {
ListObjsInput
Bucket string
KeyMarker string
VersionIdMarker string
}
type Version struct {
DeleteMarker
XMLName xml.Name `xml:"Version"`
ETag string `xml:"ETag"`
Size int64 `xml:"Size"`
}
type DeleteMarker struct {
XMLName xml.Name `xml:"DeleteMarker"`
Key string `xml:"Key"`
VersionId string `xml:"VersionId"`
IsLatest bool `xml:"IsLatest"`
LastModified time.Time `xml:"LastModified"`
Owner Owner `xml:"Owner"`
StorageClass StorageClassType `xml:"StorageClass"`
}
type ListVersionsOutput struct {
BaseModel
XMLName xml.Name `xml:"ListVersionsResult"`
Delimiter string `xml:"Delimiter"`
IsTruncated bool `xml:"IsTruncated"`
KeyMarker string `xml:"KeyMarker"`
NextKeyMarker string `xml:"NextKeyMarker"`
VersionIdMarker string `xml:"VersionIdMarker"`
NextVersionIdMarker string `xml:"NextVersionIdMarker"`
MaxKeys int `xml:"MaxKeys"`
Name string `xml:"Name"`
Prefix string `xml:"Prefix"`
Versions []Version `xml:"Version"`
DeleteMarkers []DeleteMarker `xml:"DeleteMarker"`
CommonPrefixes []string `xml:"CommonPrefixes>Prefix"`
Location string `xml:"-"`
}
type ListMultipartUploadsInput struct {
Bucket string
Prefix string
MaxUploads int
Delimiter string
KeyMarker string
UploadIdMarker string
}
type Upload struct {
XMLName xml.Name `xml:"Upload"`
Key string `xml:"Key"`
UploadId string `xml:"UploadId"`
Initiated time.Time `xml:"Initiated"`
StorageClass StorageClassType `xml:"StorageClass"`
Owner Owner `xml:"Owner"`
Initiator Initiator `xml:"Initiator"`
}
type ListMultipartUploadsOutput struct {
BaseModel
XMLName xml.Name `xml:"ListMultipartUploadsResult"`
Bucket string `xml:"Bucket"`
KeyMarker string `xml:"KeyMarker"`
NextKeyMarker string `xml:"NextKeyMarker"`
UploadIdMarker string `xml:"UploadIdMarker"`
NextUploadIdMarker string `xml:"NextUploadIdMarker"`
Delimiter string `xml:"Delimiter"`
IsTruncated bool `xml:"IsTruncated"`
MaxUploads int `xml:"MaxUploads"`
Prefix string `xml:"Prefix"`
Uploads []Upload `xml:"Upload"`
CommonPrefixes []string `xml:"CommonPrefixes>Prefix"`
}
type BucketQuota struct {
XMLName xml.Name `xml:"Quota"`
Quota int64 `xml:"StorageQuota"`
}
type SetBucketQuotaInput struct {
Bucket string `xml:"-"`
BucketQuota
}
type GetBucketQuotaOutput struct {
BaseModel
BucketQuota
}
type GetBucketStorageInfoOutput struct {
BaseModel
XMLName xml.Name `xml:"GetBucketStorageInfoResult"`
Size int64 `xml:"Size"`
ObjectNumber int `xml:"ObjectNumber"`
}
type getBucketLocationOutputS3 struct {
BaseModel
BucketLocation
}
type getBucketLocationOutputObs struct {
BaseModel
bucketLocationObs
}
type GetBucketLocationOutput struct {
BaseModel
Location string `xml:"-"`
}
type Grantee struct {
XMLName xml.Name `xml:"Grantee"`
Type GranteeType `xml:"type,attr"`
ID string `xml:"ID,omitempty"`
DisplayName string `xml:"DisplayName,omitempty"`
URI GroupUriType `xml:"URI,omitempty"`
}
type granteeObs struct {
XMLName xml.Name `xml:"Grantee"`
Type GranteeType `xml:"type,attr"`
ID string `xml:"ID,omitempty"`
DisplayName string `xml:"DisplayName,omitempty"`
Canned string `xml:"Canned,omitempty"`
}
type Grant struct {
XMLName xml.Name `xml:"Grant"`
Grantee Grantee `xml:"Grantee"`
Permission PermissionType `xml:"Permission"`
Delivered bool `xml:"Delivered"`
}
type grantObs struct {
XMLName xml.Name `xml:"Grant"`
Grantee granteeObs `xml:"Grantee"`
Permission PermissionType `xml:"Permission"`
Delivered bool `xml:"Delivered"`
}
type AccessControlPolicy struct {
XMLName xml.Name `xml:"AccessControlPolicy"`
Owner Owner `xml:"Owner"`
Grants []Grant `xml:"AccessControlList>Grant"`
}
type accessControlPolicyObs struct {
XMLName xml.Name `xml:"AccessControlPolicy"`
Owner Owner `xml:"Owner"`
Grants []grantObs `xml:"AccessControlList>Grant"`
}
type GetBucketAclOutput struct {
BaseModel
AccessControlPolicy
}
type getBucketAclOutputObs struct {
BaseModel
accessControlPolicyObs
}
type SetBucketAclInput struct {
Bucket string `xml:"-"`
ACL AclType `xml:"-"`
AccessControlPolicy
}
type SetBucketPolicyInput struct {
Bucket string
Policy string
}
type GetBucketPolicyOutput struct {
BaseModel
Policy string `body`
}
type CorsRule struct {
XMLName xml.Name `xml:"CORSRule"`
ID string `xml:"ID,omitempty"`
AllowedOrigin []string `xml:"AllowedOrigin"`
AllowedMethod []string `xml:"AllowedMethod"`
AllowedHeader []string `xml:"AllowedHeader,omitempty"`
MaxAgeSeconds int `xml:"MaxAgeSeconds"`
ExposeHeader []string `xml:"ExposeHeader,omitempty"`
}
type BucketCors struct {
XMLName xml.Name `xml:"CORSConfiguration"`
CorsRules []CorsRule `xml:"CORSRule"`
}
type SetBucketCorsInput struct {
Bucket string `xml:"-"`
BucketCors
}
type GetBucketCorsOutput struct {
BaseModel
BucketCors
}
type BucketVersioningConfiguration struct {
XMLName xml.Name `xml:"VersioningConfiguration"`
Status VersioningStatusType `xml:"Status"`
}
type SetBucketVersioningInput struct {
Bucket string `xml:"-"`
BucketVersioningConfiguration
}
type GetBucketVersioningOutput struct {
BaseModel
BucketVersioningConfiguration
}
type IndexDocument struct {
Suffix string `xml:"Suffix"`
}
type ErrorDocument struct {
Key string `xml:"Key,omitempty"`
}
type Condition struct {
XMLName xml.Name `xml:"Condition"`
KeyPrefixEquals string `xml:"KeyPrefixEquals,omitempty"`
HttpErrorCodeReturnedEquals string `xml:"HttpErrorCodeReturnedEquals,omitempty"`
}
type Redirect struct {
XMLName xml.Name `xml:"Redirect"`
Protocol ProtocolType `xml:"Protocol,omitempty"`
HostName string `xml:"HostName,omitempty"`
ReplaceKeyPrefixWith string `xml:"ReplaceKeyPrefixWith,omitempty"`
ReplaceKeyWith string `xml:"ReplaceKeyWith,omitempty"`
HttpRedirectCode string `xml:"HttpRedirectCode,omitempty"`
}
type RoutingRule struct {
XMLName xml.Name `xml:"RoutingRule"`
Condition Condition `xml:"Condition,omitempty"`
Redirect Redirect `xml:"Redirect"`
}
type RedirectAllRequestsTo struct {
XMLName xml.Name `xml:"RedirectAllRequestsTo"`
Protocol ProtocolType `xml:"Protocol,omitempty"`
HostName string `xml:"HostName"`
}
type BucketWebsiteConfiguration struct {
XMLName xml.Name `xml:"WebsiteConfiguration"`
RedirectAllRequestsTo RedirectAllRequestsTo `xml:"RedirectAllRequestsTo,omitempty"`
IndexDocument IndexDocument `xml:"IndexDocument,omitempty"`
ErrorDocument ErrorDocument `xml:"ErrorDocument,omitempty"`
RoutingRules []RoutingRule `xml:"RoutingRules>RoutingRule,omitempty"`
}
type SetBucketWebsiteConfigurationInput struct {
Bucket string `xml:"-"`
BucketWebsiteConfiguration
}
type GetBucketWebsiteConfigurationOutput struct {
BaseModel
BucketWebsiteConfiguration
}
type GetBucketMetadataInput struct {
Bucket string
Origin string
RequestHeader string
}
type GetBucketMetadataOutput struct {
BaseModel
StorageClass StorageClassType
Location string
Version string
AllowOrigin string
AllowMethod string
AllowHeader string
MaxAgeSeconds int
ExposeHeader string
Epid string
}
type BucketLoggingStatus struct {
XMLName xml.Name `xml:"BucketLoggingStatus"`
Agency string `xml:"Agency,omitempty"`
TargetBucket string `xml:"LoggingEnabled>TargetBucket,omitempty"`
TargetPrefix string `xml:"LoggingEnabled>TargetPrefix,omitempty"`
TargetGrants []Grant `xml:"LoggingEnabled>TargetGrants>Grant,omitempty"`
}
type SetBucketLoggingConfigurationInput struct {
Bucket string `xml:"-"`
BucketLoggingStatus
}
type GetBucketLoggingConfigurationOutput struct {
BaseModel
BucketLoggingStatus
}
type Transition struct {
XMLName xml.Name `xml:"Transition"`
Date time.Time `xml:"Date,omitempty"`
Days int `xml:"Days,omitempty"`
StorageClass StorageClassType `xml:"StorageClass"`
}
type Expiration struct {
XMLName xml.Name `xml:"Expiration"`
Date time.Time `xml:"Date,omitempty"`
Days int `xml:"Days,omitempty"`
}
type NoncurrentVersionTransition struct {
XMLName xml.Name `xml:"NoncurrentVersionTransition"`
NoncurrentDays int `xml:"NoncurrentDays"`
StorageClass StorageClassType `xml:"StorageClass"`
}
type NoncurrentVersionExpiration struct {
XMLName xml.Name `xml:"NoncurrentVersionExpiration"`
NoncurrentDays int `xml:"NoncurrentDays"`
}
type LifecycleRule struct {
ID string `xml:"ID,omitempty"`
Prefix string `xml:"Prefix"`
Status RuleStatusType `xml:"Status"`
Transitions []Transition `xml:"Transition,omitempty"`
Expiration Expiration `xml:"Expiration,omitempty"`
NoncurrentVersionTransitions []NoncurrentVersionTransition `xml:"NoncurrentVersionTransition,omitempty"`
NoncurrentVersionExpiration NoncurrentVersionExpiration `xml:"NoncurrentVersionExpiration,omitempty"`
}
type BucketLifecyleConfiguration struct {
XMLName xml.Name `xml:"LifecycleConfiguration"`
LifecycleRules []LifecycleRule `xml:"Rule"`
}
type SetBucketLifecycleConfigurationInput struct {
Bucket string `xml:"-"`
BucketLifecyleConfiguration
}
type GetBucketLifecycleConfigurationOutput struct {
BaseModel
BucketLifecyleConfiguration
}
type Tag struct {
XMLName xml.Name `xml:"Tag"`
Key string `xml:"Key"`
Value string `xml:"Value"`
}
type BucketTagging struct {
XMLName xml.Name `xml:"Tagging"`
Tags []Tag `xml:"TagSet>Tag"`
}
type SetBucketTaggingInput struct {
Bucket string `xml:"-"`
BucketTagging
}
type GetBucketTaggingOutput struct {
BaseModel
BucketTagging
}
type FilterRule struct {
XMLName xml.Name `xml:"FilterRule"`
Name string `xml:"Name,omitempty"`
Value string `xml:"Value,omitempty"`
}
type TopicConfiguration struct {
XMLName xml.Name `xml:"TopicConfiguration"`
ID string `xml:"Id,omitempty"`
Topic string `xml:"Topic"`
Events []EventType `xml:"Event"`
FilterRules []FilterRule `xml:"Filter>Object>FilterRule"`
}
type BucketNotification struct {
XMLName xml.Name `xml:"NotificationConfiguration"`
TopicConfigurations []TopicConfiguration `xml:"TopicConfiguration"`
}
type SetBucketNotificationInput struct {
Bucket string `xml:"-"`
BucketNotification
}
type topicConfigurationS3 struct {
XMLName xml.Name `xml:"TopicConfiguration"`
ID string `xml:"Id,omitempty"`
Topic string `xml:"Topic"`
Events []string `xml:"Event"`
FilterRules []FilterRule `xml:"Filter>S3Key>FilterRule"`
}
type bucketNotificationS3 struct {
XMLName xml.Name `xml:"NotificationConfiguration"`
TopicConfigurations []topicConfigurationS3 `xml:"TopicConfiguration"`
}
type getBucketNotificationOutputS3 struct {
BaseModel
bucketNotificationS3
}
type GetBucketNotificationOutput struct {
BaseModel
BucketNotification
}
type DeleteObjectInput struct {
Bucket string
Key string
VersionId string
}
type DeleteObjectOutput struct {
BaseModel
VersionId string
DeleteMarker bool
}
type ObjectToDelete struct {
XMLName xml.Name `xml:"Object"`
Key string `xml:"Key"`
VersionId string `xml:"VersionId,omitempty"`
}
type DeleteObjectsInput struct {
Bucket string `xml:"-"`
XMLName xml.Name `xml:"Delete"`
Quiet bool `xml:"Quiet,omitempty"`
Objects []ObjectToDelete `xml:"Object"`
}
type Deleted struct {
XMLName xml.Name `xml:"Deleted"`
Key string `xml:"Key"`
VersionId string `xml:"VersionId"`
DeleteMarker bool `xml:"DeleteMarker"`
DeleteMarkerVersionId string `xml:"DeleteMarkerVersionId"`
}
type Error struct {
XMLName xml.Name `xml:"Error"`
Key string `xml:"Key"`
VersionId string `xml:"VersionId"`
Code string `xml:"Code"`
Message string `xml:"Message"`
}
type DeleteObjectsOutput struct {
BaseModel
XMLName xml.Name `xml:"DeleteResult"`
Deleteds []Deleted `xml:"Deleted"`
Errors []Error `xml:"Error"`
}
type SetObjectAclInput struct {
Bucket string `xml:"-"`
Key string `xml:"-"`
VersionId string `xml:"-"`
ACL AclType `xml:"-"`
AccessControlPolicy
}
type GetObjectAclInput struct {
Bucket string
Key string
VersionId string
}
type GetObjectAclOutput struct {
BaseModel
VersionId string
AccessControlPolicy
}
type RestoreObjectInput struct {
Bucket string `xml:"-"`
Key string `xml:"-"`
VersionId string `xml:"-"`
XMLName xml.Name `xml:"RestoreRequest"`
Days int `xml:"Days"`
Tier RestoreTierType `xml:"GlacierJobParameters>Tier,omitempty"`
}
type ISseHeader interface {
GetEncryption() string
GetKey() string
}
type SseKmsHeader struct {
Encryption string
Key string
isObs bool
}
type SseCHeader struct {
Encryption string
Key string
KeyMD5 string
}
type GetObjectMetadataInput struct {
Bucket string
Key string
VersionId string
Origin string
RequestHeader string
SseHeader ISseHeader
}
type GetObjectMetadataOutput struct {
BaseModel
VersionId string
WebsiteRedirectLocation string
Expiration string
Restore string
ObjectType string
NextAppendPosition string
StorageClass StorageClassType
ContentLength int64
ContentType string
ETag string
AllowOrigin string
AllowHeader string
AllowMethod string
ExposeHeader string
MaxAgeSeconds int
LastModified time.Time
SseHeader ISseHeader
Metadata map[string]string
}
type GetObjectInput struct {
GetObjectMetadataInput
IfMatch string
IfNoneMatch string
IfUnmodifiedSince time.Time
IfModifiedSince time.Time
RangeStart int64
RangeEnd int64
ImageProcess string
ResponseCacheControl string
ResponseContentDisposition string
ResponseContentEncoding string
ResponseContentLanguage string
ResponseContentType string
ResponseExpires string
}
type GetObjectOutput struct {
GetObjectMetadataOutput
DeleteMarker bool
CacheControl string
ContentDisposition string
ContentEncoding string
ContentLanguage string
Expires string
Body io.ReadCloser
}
type ObjectOperationInput struct {
Bucket string
Key string
ACL AclType
GrantReadId string
GrantReadAcpId string
GrantWriteAcpId string
GrantFullControlId string
StorageClass StorageClassType
WebsiteRedirectLocation string
Expires int64
SseHeader ISseHeader
Metadata map[string]string
}
type PutObjectBasicInput struct {
ObjectOperationInput
ContentType string
ContentMD5 string
ContentLength int64
}
type PutObjectInput struct {
PutObjectBasicInput
Body io.Reader
}
type PutFileInput struct {
PutObjectBasicInput
SourceFile string
}
type PutObjectOutput struct {
BaseModel
VersionId string
SseHeader ISseHeader
StorageClass StorageClassType
ETag string
}
type CopyObjectInput struct {
ObjectOperationInput
CopySourceBucket string
CopySourceKey string
CopySourceVersionId string
CopySourceIfMatch string
CopySourceIfNoneMatch string
CopySourceIfUnmodifiedSince time.Time
CopySourceIfModifiedSince time.Time
SourceSseHeader ISseHeader
CacheControl string
ContentDisposition string
ContentEncoding string
ContentLanguage string
ContentType string
Expires string
MetadataDirective MetadataDirectiveType
successActionRedirect string
}
type CopyObjectOutput struct {
BaseModel
CopySourceVersionId string `xml:"-"`
VersionId string `xml:"-"`
SseHeader ISseHeader `xml:"-"`
XMLName xml.Name `xml:"CopyObjectResult"`
LastModified time.Time `xml:"LastModified"`
ETag string `xml:"ETag"`
}
type AbortMultipartUploadInput struct {
Bucket string
Key string
UploadId string
}
type InitiateMultipartUploadInput struct {
ObjectOperationInput
ContentType string
}
type InitiateMultipartUploadOutput struct {
BaseModel
XMLName xml.Name `xml:"InitiateMultipartUploadResult"`
Bucket string `xml:"Bucket"`
Key string `xml:"Key"`
UploadId string `xml:"UploadId"`
SseHeader ISseHeader
}
type UploadPartInput struct {
Bucket string
Key string
PartNumber int
UploadId string
ContentMD5 string
SseHeader ISseHeader
Body io.Reader
SourceFile string
Offset int64
PartSize int64
}
type UploadPartOutput struct {
BaseModel
PartNumber int
ETag string
SseHeader ISseHeader
}
type Part struct {
XMLName xml.Name `xml:"Part"`
PartNumber int `xml:"PartNumber"`
ETag string `xml:"ETag"`
LastModified time.Time `xml:"LastModified,omitempty"`
Size int64 `xml:"Size,omitempty"`
}
type CompleteMultipartUploadInput struct {
Bucket string `xml:"-"`
Key string `xml:"-"`
UploadId string `xml:"-"`
XMLName xml.Name `xml:"CompleteMultipartUpload"`
Parts []Part `xml:"Part"`
}
type CompleteMultipartUploadOutput struct {
BaseModel
VersionId string `xml:"-"`
SseHeader ISseHeader `xml:"-"`
XMLName xml.Name `xml:"CompleteMultipartUploadResult"`
Location string `xml:"Location"`
Bucket string `xml:"Bucket"`
Key string `xml:"Key"`
ETag string `xml:"ETag"`
}
type ListPartsInput struct {
Bucket string
Key string
UploadId string
MaxParts int
PartNumberMarker int
}
type ListPartsOutput struct {
BaseModel
XMLName xml.Name `xml:"ListPartsResult"`
Bucket string `xml:"Bucket"`
Key string `xml:"Key"`
UploadId string `xml:"UploadId"`
PartNumberMarker int `xml:"PartNumberMarker"`
NextPartNumberMarker int `xml:"NextPartNumberMarker"`
MaxParts int `xml:"MaxParts"`
IsTruncated bool `xml:"IsTruncated"`
StorageClass StorageClassType `xml:"StorageClass"`
Initiator Initiator `xml:"Initiator“`
Owner Owner `xml:"Owner"`
Parts []Part `xml:"Part"`
}
type CopyPartInput struct {
Bucket string
Key string
UploadId string
PartNumber int
CopySourceBucket string
CopySourceKey string
CopySourceVersionId string
CopySourceRangeStart int64
CopySourceRangeEnd int64
SseHeader ISseHeader
SourceSseHeader ISseHeader
}
type CopyPartOutput struct {
BaseModel
XMLName xml.Name `xml:"CopyPartResult"`
PartNumber int `xml:"-"`
ETag string `xml:"ETag"`
LastModified time.Time `xml:"LastModified"`
SseHeader ISseHeader `xml:"-"`
}
type CreateSignedUrlInput struct {
Method HttpMethodType
Bucket string
Key string
SubResource SubResourceType
Expires int
Headers map[string]string
QueryParams map[string]string
}
type CreateSignedUrlOutput struct {
SignedUrl string
ActualSignedRequestHeaders http.Header
}
type CreateBrowserBasedSignatureInput struct {
Bucket string
Key string
Expires int
FormParams map[string]string
}
type CreateBrowserBasedSignatureOutput struct {
OriginPolicy string
Policy string
Algorithm string
Credential string
Date string
Signature string
}
+666
View File
@@ -0,0 +1,666 @@
package obs
import (
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func (obsClient ObsClient) CreateSignedUrl(input *CreateSignedUrlInput) (output *CreateSignedUrlOutput, err error) {
if input == nil {
return nil, errors.New("CreateSignedUrlInput is nil")
}
params := make(map[string]string, len(input.QueryParams))
for key, value := range input.QueryParams {
params[key] = value
}
if input.SubResource != "" {
params[string(input.SubResource)] = ""
}
headers := make(map[string][]string, len(input.Headers))
for key, value := range input.Headers {
headers[key] = []string{value}
}
if input.Expires <= 0 {
input.Expires = 300
}
requestUrl, err := obsClient.doAuthTemporary(string(input.Method), input.Bucket, input.Key, params, headers, int64(input.Expires))
if err != nil {
return nil, err
}
output = &CreateSignedUrlOutput{
SignedUrl: requestUrl,
ActualSignedRequestHeaders: headers,
}
return
}
func (obsClient ObsClient) CreateBrowserBasedSignature(input *CreateBrowserBasedSignatureInput) (output *CreateBrowserBasedSignatureOutput, err error) {
if input == nil {
return nil, errors.New("CreateBrowserBasedSignatureInput is nil")
}
params := make(map[string]string, len(input.FormParams))
for key, value := range input.FormParams {
params[key] = value
}
date := time.Now().UTC()
shortDate := date.Format(SHORT_DATE_FORMAT)
longDate := date.Format(LONG_DATE_FORMAT)
credential, _ := getCredential(obsClient.conf.securityProvider.ak, obsClient.conf.region, shortDate)
if input.Expires <= 0 {
input.Expires = 300
}
expiration := date.Add(time.Second * time.Duration(input.Expires)).Format(ISO8601_DATE_FORMAT)
params[PARAM_ALGORITHM_AMZ_CAMEL] = V4_HASH_PREFIX
params[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
params[PARAM_DATE_AMZ_CAMEL] = longDate
if obsClient.conf.securityProvider.securityToken != "" {
params[HEADER_STS_TOKEN_AMZ] = obsClient.conf.securityProvider.securityToken
}
matchAnyBucket := true
matchAnyKey := true
count := 5
if bucket := strings.TrimSpace(input.Bucket); bucket != "" {
params["bucket"] = bucket
matchAnyBucket = false
count--
}
if key := strings.TrimSpace(input.Key); key != "" {
params["key"] = key
matchAnyKey = false
count--
}
originPolicySlice := make([]string, 0, len(params)+count)
originPolicySlice = append(originPolicySlice, fmt.Sprintf("{\"expiration\":\"%s\",", expiration))
originPolicySlice = append(originPolicySlice, "\"conditions\":[")
for key, value := range params {
if _key := strings.TrimSpace(strings.ToLower(key)); _key != "" {
originPolicySlice = append(originPolicySlice, fmt.Sprintf("{\"%s\":\"%s\"},", _key, value))
}
}
if matchAnyBucket {
originPolicySlice = append(originPolicySlice, "[\"starts-with\", \"$bucket\", \"\"],")
}
if matchAnyKey {
originPolicySlice = append(originPolicySlice, "[\"starts-with\", \"$key\", \"\"],")
}
originPolicySlice = append(originPolicySlice, "]}")
originPolicy := strings.Join(originPolicySlice, "")
policy := Base64Encode([]byte(originPolicy))
signature := getSignature(policy, obsClient.conf.securityProvider.sk, obsClient.conf.region, shortDate)
output = &CreateBrowserBasedSignatureOutput{
OriginPolicy: originPolicy,
Policy: policy,
Algorithm: params[PARAM_ALGORITHM_AMZ_CAMEL],
Credential: params[PARAM_CREDENTIAL_AMZ_CAMEL],
Date: params[PARAM_DATE_AMZ_CAMEL],
Signature: signature,
}
return
}
func (obsClient ObsClient) ListBucketsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListBucketsOutput, err error) {
output = &ListBucketsOutput{}
err = obsClient.doHttpWithSignedUrl("ListBuckets", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) CreateBucketWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("CreateBucket", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("DeleteBucket", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketStoragePolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketStoragePolicy", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketStoragePolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketStoragePolicyOutput, err error) {
output = &GetBucketStoragePolicyOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketStoragePolicy", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) ListObjectsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListObjectsOutput, err error) {
output = &ListObjectsOutput{}
err = obsClient.doHttpWithSignedUrl("ListObjects", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
if location, ok := output.ResponseHeaders[HEADER_BUCKET_REGION]; ok {
output.Location = location[0]
}
}
return
}
func (obsClient ObsClient) ListVersionsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListVersionsOutput, err error) {
output = &ListVersionsOutput{}
err = obsClient.doHttpWithSignedUrl("ListVersions", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
if location, ok := output.ResponseHeaders[HEADER_BUCKET_REGION]; ok {
output.Location = location[0]
}
}
return
}
func (obsClient ObsClient) ListMultipartUploadsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListMultipartUploadsOutput, err error) {
output = &ListMultipartUploadsOutput{}
err = obsClient.doHttpWithSignedUrl("ListMultipartUploads", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketQuotaWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketQuota", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketQuotaWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketQuotaOutput, err error) {
output = &GetBucketQuotaOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketQuota", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) HeadBucketWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("HeadBucket", HTTP_HEAD, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketMetadataWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketMetadataOutput, err error) {
output = &GetBucketMetadataOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketMetadata", HTTP_HEAD, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
ParseGetBucketMetadataOutput(output)
}
return
}
func (obsClient ObsClient) GetBucketStorageInfoWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketStorageInfoOutput, err error) {
output = &GetBucketStorageInfoOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketStorageInfo", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketLocationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketLocationOutput, err error) {
output = &GetBucketLocationOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketLocation", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketAclWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketAcl", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketAclWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketAclOutput, err error) {
output = &GetBucketAclOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketAcl", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketPolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketPolicy", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketPolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketPolicyOutput, err error) {
output = &GetBucketPolicyOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketPolicy", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, false)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketPolicyWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("DeleteBucketPolicy", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketCorsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketCors", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketCorsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketCorsOutput, err error) {
output = &GetBucketCorsOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketCors", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketCorsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("DeleteBucketCors", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketVersioningWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketVersioning", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketVersioningWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketVersioningOutput, err error) {
output = &GetBucketVersioningOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketVersioning", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketWebsiteConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketWebsiteConfiguration", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketWebsiteConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketWebsiteConfigurationOutput, err error) {
output = &GetBucketWebsiteConfigurationOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketWebsiteConfiguration", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketWebsiteConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("DeleteBucketWebsiteConfiguration", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketLoggingConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketLoggingConfiguration", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketLoggingConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketLoggingConfigurationOutput, err error) {
output = &GetBucketLoggingConfigurationOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketLoggingConfiguration", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketLifecycleConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketLifecycleConfiguration", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketLifecycleConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketLifecycleConfigurationOutput, err error) {
output = &GetBucketLifecycleConfigurationOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketLifecycleConfiguration", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketLifecycleConfigurationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("DeleteBucketLifecycleConfiguration", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketTaggingWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketTagging", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketTaggingWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketTaggingOutput, err error) {
output = &GetBucketTaggingOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketTagging", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteBucketTaggingWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("DeleteBucketTagging", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetBucketNotificationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetBucketNotification", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetBucketNotificationWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetBucketNotificationOutput, err error) {
output = &GetBucketNotificationOutput{}
err = obsClient.doHttpWithSignedUrl("GetBucketNotification", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) DeleteObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *DeleteObjectOutput, err error) {
output = &DeleteObjectOutput{}
err = obsClient.doHttpWithSignedUrl("DeleteObject", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
ParseDeleteObjectOutput(output)
}
return
}
func (obsClient ObsClient) DeleteObjectsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *DeleteObjectsOutput, err error) {
output = &DeleteObjectsOutput{}
err = obsClient.doHttpWithSignedUrl("DeleteObjects", HTTP_POST, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) SetObjectAclWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("SetObjectAcl", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetObjectAclWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetObjectAclOutput, err error) {
output = &GetObjectAclOutput{}
err = obsClient.doHttpWithSignedUrl("GetObjectAcl", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
if versionId, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok {
output.VersionId = versionId[0]
}
}
return
}
func (obsClient ObsClient) RestoreObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("RestoreObject", HTTP_POST, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) GetObjectMetadataWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetObjectMetadataOutput, err error) {
output = &GetObjectMetadataOutput{}
err = obsClient.doHttpWithSignedUrl("GetObjectMetadata", HTTP_HEAD, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
ParseGetObjectMetadataOutput(output)
}
return
}
func (obsClient ObsClient) GetObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *GetObjectOutput, err error) {
output = &GetObjectOutput{}
err = obsClient.doHttpWithSignedUrl("GetObject", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
ParseGetObjectOutput(output)
}
return
}
func (obsClient ObsClient) PutObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *PutObjectOutput, err error) {
output = &PutObjectOutput{}
err = obsClient.doHttpWithSignedUrl("PutObject", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
} else {
ParsePutObjectOutput(output)
}
return
}
func (obsClient ObsClient) PutFileWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, sourceFile string) (output *PutObjectOutput, err error) {
var data io.Reader
sourceFile = strings.TrimSpace(sourceFile)
if sourceFile != "" {
fd, err := os.Open(sourceFile)
if err != nil {
return nil, err
}
defer fd.Close()
stat, err := fd.Stat()
if err != nil {
return nil, err
}
fileReaderWrapper := &fileReaderWrapper{filePath: sourceFile}
fileReaderWrapper.reader = fd
var contentLength int64
if value, ok := actualSignedRequestHeaders[HEADER_CONTENT_LENGTH_CAMEL]; ok {
contentLength = StringToInt64(value[0], -1)
} else if value, ok := actualSignedRequestHeaders[HEADER_CONTENT_LENGTH]; ok {
contentLength = StringToInt64(value[0], -1)
} else {
contentLength = stat.Size()
}
if contentLength > stat.Size() {
return nil, errors.New("ContentLength is larger than fileSize")
}
fileReaderWrapper.totalCount = contentLength
data = fileReaderWrapper
}
output = &PutObjectOutput{}
err = obsClient.doHttpWithSignedUrl("PutObject", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
} else {
ParsePutObjectOutput(output)
}
return
}
func (obsClient ObsClient) CopyObjectWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *CopyObjectOutput, err error) {
output = &CopyObjectOutput{}
err = obsClient.doHttpWithSignedUrl("CopyObject", HTTP_PUT, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
ParseCopyObjectOutput(output)
}
return
}
func (obsClient ObsClient) AbortMultipartUploadWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *BaseModel, err error) {
output = &BaseModel{}
err = obsClient.doHttpWithSignedUrl("AbortMultipartUpload", HTTP_DELETE, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) InitiateMultipartUploadWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *InitiateMultipartUploadOutput, err error) {
output = &InitiateMultipartUploadOutput{}
err = obsClient.doHttpWithSignedUrl("InitiateMultipartUpload", HTTP_POST, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
ParseInitiateMultipartUploadOutput(output)
}
return
}
func (obsClient ObsClient) UploadPartWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *UploadPartOutput, err error) {
output = &UploadPartOutput{}
err = obsClient.doHttpWithSignedUrl("UploadPart", HTTP_PUT, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
} else {
ParseUploadPartOutput(output)
}
return
}
func (obsClient ObsClient) CompleteMultipartUploadWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header, data io.Reader) (output *CompleteMultipartUploadOutput, err error) {
output = &CompleteMultipartUploadOutput{}
err = obsClient.doHttpWithSignedUrl("CompleteMultipartUpload", HTTP_POST, signedUrl, actualSignedRequestHeaders, data, output, true)
if err != nil {
output = nil
} else {
ParseCompleteMultipartUploadOutput(output)
}
return
}
func (obsClient ObsClient) ListPartsWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *ListPartsOutput, err error) {
output = &ListPartsOutput{}
err = obsClient.doHttpWithSignedUrl("ListParts", HTTP_GET, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
}
return
}
func (obsClient ObsClient) CopyPartWithSignedUrl(signedUrl string, actualSignedRequestHeaders http.Header) (output *CopyPartOutput, err error) {
output = &CopyPartOutput{}
err = obsClient.doHttpWithSignedUrl("CopyPart", HTTP_PUT, signedUrl, actualSignedRequestHeaders, nil, output, true)
if err != nil {
output = nil
} else {
ParseCopyPartOutput(output)
}
return
}
+701
View File
@@ -0,0 +1,701 @@
package obs
import (
"bytes"
"fmt"
"io"
"os"
"strings"
)
type IReadCloser interface {
setReadCloser(body io.ReadCloser)
}
func (output *GetObjectOutput) setReadCloser(body io.ReadCloser) {
output.Body = body
}
func setHeaders(headers map[string][]string, header string, headerValue []string, isObs bool) {
if isObs {
header = HEADER_PREFIX_OBS + header
headers[header] = headerValue
} else {
header = HEADER_PREFIX + header
headers[header] = headerValue
}
}
func setHeadersNext(headers map[string][]string, header string, headerNext string, headerValue []string, isObs bool) {
if isObs {
headers[header] = headerValue
} else {
headers[headerNext] = headerValue
}
}
type IBaseModel interface {
setStatusCode(statusCode int)
setRequestId(requestId string)
setResponseHeaders(responseHeaders map[string][]string)
}
type ISerializable interface {
trans(isObs bool) (map[string]string, map[string][]string, interface{})
}
type DefaultSerializable struct {
params map[string]string
headers map[string][]string
data interface{}
}
func (input DefaultSerializable) trans(isObs bool) (map[string]string, map[string][]string, interface{}) {
return input.params, input.headers, input.data
}
var defaultSerializable = &DefaultSerializable{}
func newSubResourceSerial(subResource SubResourceType) *DefaultSerializable {
return &DefaultSerializable{map[string]string{string(subResource): ""}, nil, nil}
}
func trans(subResource SubResourceType, input interface{}) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(subResource): ""}
data, _ = ConvertRequestToIoReader(input)
return
}
func (baseModel *BaseModel) setStatusCode(statusCode int) {
baseModel.StatusCode = statusCode
}
func (baseModel *BaseModel) setRequestId(requestId string) {
baseModel.RequestId = requestId
}
func (baseModel *BaseModel) setResponseHeaders(responseHeaders map[string][]string) {
baseModel.ResponseHeaders = responseHeaders
}
func (input ListBucketsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
headers = make(map[string][]string)
if input.QueryLocation && !isObs {
setHeaders(headers, HEADER_LOCATION_AMZ, []string{"true"}, isObs)
}
return
}
func (input CreateBucketInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
headers = make(map[string][]string)
if acl := string(input.ACL); acl != "" {
setHeaders(headers, HEADER_ACL, []string{acl}, isObs)
}
if storageClass := string(input.StorageClass); storageClass != "" {
if !isObs {
if storageClass == "WARM" {
storageClass = "STANDARD_IA"
} else if storageClass == "COLD" {
storageClass = "GLACIER"
}
}
setHeadersNext(headers, HEADER_STORAGE_CLASS_OBS, HEADER_STORAGE_CLASS, []string{storageClass}, isObs)
if epid := string(input.Epid); epid != "" {
setHeaders(headers, HEADER_EPID_HEADERS, []string{epid}, isObs)
}
}
if grantReadId := string(input.GrantReadId); grantReadId != "" {
setHeaders(headers, HEADER_GRANT_READ_OBS, []string{grantReadId}, isObs)
}
if grantWriteId := string(input.GrantWriteId); grantWriteId != "" {
setHeaders(headers, HEADER_GRANT_WRITE_OBS, []string{grantWriteId}, isObs)
}
if grantReadAcpId := string(input.GrantReadAcpId); grantReadAcpId != "" {
setHeaders(headers, HEADER_GRANT_READ_ACP_OBS, []string{grantReadAcpId}, isObs)
}
if grantWriteAcpId := string(input.GrantWriteAcpId); grantWriteAcpId != "" {
setHeaders(headers, HEADER_GRANT_WRITE_ACP_OBS, []string{grantWriteAcpId}, isObs)
}
if grantFullControlId := string(input.GrantFullControlId); grantFullControlId != "" {
setHeaders(headers, HEADER_GRANT_FULL_CONTROL_OBS, []string{grantFullControlId}, isObs)
}
if grantReadDeliveredId := string(input.GrantReadDeliveredId); grantReadDeliveredId != "" {
setHeaders(headers, HEADER_GRANT_READ_DELIVERED_OBS, []string{grantReadDeliveredId}, true)
}
if grantFullControlDeliveredId := string(input.GrantFullControlDeliveredId); grantFullControlDeliveredId != "" {
setHeaders(headers, HEADER_GRANT_FULL_CONTROL_DELIVERED_OBS, []string{grantFullControlDeliveredId}, true)
}
if location := strings.TrimSpace(input.Location); location != "" {
input.Location = location
data, _ = ConvertRequestToIoReader(input)
}
return
}
func (input SetBucketStoragePolicyInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
xml := make([]string, 0, 1)
if !isObs {
storageClass := "STANDARD"
if input.StorageClass == "WARM" {
storageClass = "STANDARD_IA"
} else if input.StorageClass == "COLD" {
storageClass = "GLACIER"
}
xml = append(xml, fmt.Sprintf("<StorageClass>%s</StorageClass>", storageClass))
} else {
params = map[string]string{string(SubResourceStorageClass): ""}
xml = append(xml, fmt.Sprintf("<StorageClass>%s</StorageClass>", input.StorageClass))
}
data = strings.Join(xml, "")
return
}
func (input ListObjsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = make(map[string]string)
if input.Prefix != "" {
params["prefix"] = input.Prefix
}
if input.Delimiter != "" {
params["delimiter"] = input.Delimiter
}
if input.MaxKeys > 0 {
params["max-keys"] = IntToString(input.MaxKeys)
}
headers = make(map[string][]string)
if origin := strings.TrimSpace(input.Origin); origin != "" {
headers[HEADER_ORIGIN_CAMEL] = []string{origin}
}
if requestHeader := strings.TrimSpace(input.RequestHeader); requestHeader != "" {
headers[HEADER_ACCESS_CONTROL_REQUEST_HEADER_CAMEL] = []string{requestHeader}
}
return
}
func (input ListObjectsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params, headers, data = input.ListObjsInput.trans(isObs)
if input.Marker != "" {
params["marker"] = input.Marker
}
return
}
func (input ListVersionsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params, headers, data = input.ListObjsInput.trans(isObs)
params[string(SubResourceVersions)] = ""
if input.KeyMarker != "" {
params["key-marker"] = input.KeyMarker
}
if input.VersionIdMarker != "" {
params["version-id-marker"] = input.VersionIdMarker
}
return
}
func (input ListMultipartUploadsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceUploads): ""}
if input.Prefix != "" {
params["prefix"] = input.Prefix
}
if input.Delimiter != "" {
params["delimiter"] = input.Delimiter
}
if input.MaxUploads > 0 {
params["max-uploads"] = IntToString(input.MaxUploads)
}
if input.KeyMarker != "" {
params["key-marker"] = input.KeyMarker
}
if input.UploadIdMarker != "" {
params["upload-id-marker"] = input.UploadIdMarker
}
return
}
func (input SetBucketQuotaInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
return trans(SubResourceQuota, input)
}
func (input SetBucketAclInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceAcl): ""}
headers = make(map[string][]string)
if acl := string(input.ACL); acl != "" {
setHeaders(headers, HEADER_ACL, []string{acl}, isObs)
} else {
data, _ = ConvertAclToXml(input.AccessControlPolicy, false, isObs)
}
return
}
func (input SetBucketPolicyInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourcePolicy): ""}
data = strings.NewReader(input.Policy)
return
}
func (input SetBucketCorsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceCors): ""}
data, md5, _ := ConvertRequestToIoReaderV2(input)
headers = map[string][]string{HEADER_MD5_CAMEL: {md5}}
return
}
func (input SetBucketVersioningInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
return trans(SubResourceVersioning, input)
}
func (input SetBucketWebsiteConfigurationInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceWebsite): ""}
data, _ = ConvertWebsiteConfigurationToXml(input.BucketWebsiteConfiguration, false)
return
}
func (input GetBucketMetadataInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
headers = make(map[string][]string)
if origin := strings.TrimSpace(input.Origin); origin != "" {
headers[HEADER_ORIGIN_CAMEL] = []string{origin}
}
if requestHeader := strings.TrimSpace(input.RequestHeader); requestHeader != "" {
headers[HEADER_ACCESS_CONTROL_REQUEST_HEADER_CAMEL] = []string{requestHeader}
}
return
}
func (input SetBucketLoggingConfigurationInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceLogging): ""}
data, _ = ConvertLoggingStatusToXml(input.BucketLoggingStatus, false, isObs)
return
}
func (input SetBucketLifecycleConfigurationInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceLifecycle): ""}
data, md5 := ConvertLifecyleConfigurationToXml(input.BucketLifecyleConfiguration, true, isObs)
headers = map[string][]string{HEADER_MD5_CAMEL: {md5}}
return
}
func (input SetBucketTaggingInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceTagging): ""}
data, md5, _ := ConvertRequestToIoReaderV2(input)
headers = map[string][]string{HEADER_MD5_CAMEL: {md5}}
return
}
func (input SetBucketNotificationInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceNotification): ""}
data, _ = ConvertNotificationToXml(input.BucketNotification, false, isObs)
return
}
func (input DeleteObjectInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = make(map[string]string)
if input.VersionId != "" {
params[PARAM_VERSION_ID] = input.VersionId
}
return
}
func (input DeleteObjectsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceDelete): ""}
data, md5, _ := ConvertRequestToIoReaderV2(input)
headers = map[string][]string{HEADER_MD5_CAMEL: {md5}}
return
}
func (input SetObjectAclInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceAcl): ""}
if input.VersionId != "" {
params[PARAM_VERSION_ID] = input.VersionId
}
headers = make(map[string][]string)
if acl := string(input.ACL); acl != "" {
setHeaders(headers, HEADER_ACL, []string{acl}, isObs)
} else {
data, _ = ConvertAclToXml(input.AccessControlPolicy, false, isObs)
}
return
}
func (input GetObjectAclInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceAcl): ""}
if input.VersionId != "" {
params[PARAM_VERSION_ID] = input.VersionId
}
return
}
func (input RestoreObjectInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{string(SubResourceRestore): ""}
if input.VersionId != "" {
params[PARAM_VERSION_ID] = input.VersionId
}
if !isObs {
data, _ = ConvertRequestToIoReader(input)
} else {
data = ConverntObsRestoreToXml(input)
}
return
}
func (header SseKmsHeader) GetEncryption() string {
if header.Encryption != "" {
return header.Encryption
}
if !header.isObs {
return DEFAULT_SSE_KMS_ENCRYPTION
} else {
return DEFAULT_SSE_KMS_ENCRYPTION_OBS
}
}
func (header SseKmsHeader) GetKey() string {
return header.Key
}
func (header SseCHeader) GetEncryption() string {
if header.Encryption != "" {
return header.Encryption
}
return DEFAULT_SSE_C_ENCRYPTION
}
func (header SseCHeader) GetKey() string {
return header.Key
}
func (header SseCHeader) GetKeyMD5() string {
if header.KeyMD5 != "" {
return header.KeyMD5
}
if ret, err := Base64Decode(header.GetKey()); err == nil {
return Base64Md5(ret)
}
return ""
}
func setSseHeader(headers map[string][]string, sseHeader ISseHeader, sseCOnly bool, isObs bool) {
if sseHeader != nil {
if sseCHeader, ok := sseHeader.(SseCHeader); ok {
setHeaders(headers, HEADER_SSEC_ENCRYPTION, []string{sseCHeader.GetEncryption()}, isObs)
setHeaders(headers, HEADER_SSEC_KEY, []string{sseCHeader.GetKey()}, isObs)
setHeaders(headers, HEADER_SSEC_KEY_MD5, []string{sseCHeader.GetEncryption()}, isObs)
} else if sseKmsHeader, ok := sseHeader.(SseKmsHeader); !sseCOnly && ok {
sseKmsHeader.isObs = isObs
setHeaders(headers, HEADER_SSEKMS_ENCRYPTION, []string{sseKmsHeader.GetEncryption()}, isObs)
setHeadersNext(headers, HEADER_SSEKMS_KEY_OBS, HEADER_SSEKMS_KEY_AMZ, []string{sseKmsHeader.GetKey()}, isObs)
}
}
}
func (input GetObjectMetadataInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = make(map[string]string)
if input.VersionId != "" {
params[PARAM_VERSION_ID] = input.VersionId
}
headers = make(map[string][]string)
if input.Origin != "" {
headers[HEADER_ORIGIN_CAMEL] = []string{input.Origin}
}
if input.RequestHeader != "" {
headers[HEADER_ACCESS_CONTROL_REQUEST_HEADER_CAMEL] = []string{input.RequestHeader}
}
setSseHeader(headers, input.SseHeader, true, isObs)
return
}
func (input GetObjectInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params, headers, data = input.GetObjectMetadataInput.trans(isObs)
if input.ResponseCacheControl != "" {
params[PARAM_RESPONSE_CACHE_CONTROL] = input.ResponseCacheControl
}
if input.ResponseContentDisposition != "" {
params[PARAM_RESPONSE_CONTENT_DISPOSITION] = input.ResponseContentDisposition
}
if input.ResponseContentEncoding != "" {
params[PARAM_RESPONSE_CONTENT_ENCODING] = input.ResponseContentEncoding
}
if input.ResponseContentLanguage != "" {
params[PARAM_RESPONSE_CONTENT_LANGUAGE] = input.ResponseContentLanguage
}
if input.ResponseContentType != "" {
params[PARAM_RESPONSE_CONTENT_TYPE] = input.ResponseContentType
}
if input.ResponseExpires != "" {
params[PARAM_RESPONSE_EXPIRES] = input.ResponseExpires
}
if input.ImageProcess != "" {
params[PARAM_IMAGE_PROCESS] = input.ImageProcess
}
if input.RangeStart >= 0 && input.RangeEnd > input.RangeStart {
headers[HEADER_RANGE] = []string{fmt.Sprintf("bytes=%d-%d", input.RangeStart, input.RangeEnd)}
}
if input.IfMatch != "" {
headers[HEADER_IF_MATCH] = []string{input.IfMatch}
}
if input.IfNoneMatch != "" {
headers[HEADER_IF_NONE_MATCH] = []string{input.IfNoneMatch}
}
if !input.IfModifiedSince.IsZero() {
headers[HEADER_IF_MODIFIED_SINCE] = []string{FormatUtcToRfc1123(input.IfModifiedSince)}
}
if !input.IfUnmodifiedSince.IsZero() {
headers[HEADER_IF_UNMODIFIED_SINCE] = []string{FormatUtcToRfc1123(input.IfUnmodifiedSince)}
}
return
}
func (input ObjectOperationInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
headers = make(map[string][]string)
params = make(map[string]string)
if acl := string(input.ACL); acl != "" {
setHeaders(headers, HEADER_ACL, []string{acl}, isObs)
}
if GrantReadId := string(input.GrantReadId); GrantReadId != "" {
setHeaders(headers, HEADER_GRANT_READ_OBS, []string{GrantReadId}, true)
}
if GrantReadAcpId := string(input.GrantReadAcpId); GrantReadAcpId != "" {
setHeaders(headers, HEADER_GRANT_READ_ACP_OBS, []string{GrantReadAcpId}, true)
}
if GrantWriteAcpId := string(input.GrantWriteAcpId); GrantWriteAcpId != "" {
setHeaders(headers, HEADER_GRANT_WRITE_ACP_OBS, []string{GrantWriteAcpId}, true)
}
if GrantFullControlId := string(input.GrantFullControlId); GrantFullControlId != "" {
setHeaders(headers, HEADER_GRANT_FULL_CONTROL_OBS, []string{GrantFullControlId}, true)
}
if storageClass := string(input.StorageClass); storageClass != "" {
if !isObs {
if storageClass == "WARM" {
storageClass = "STANDARD_IA"
} else if storageClass == "COLD" {
storageClass = "GLACIER"
}
}
setHeaders(headers, HEADER_STORAGE_CLASS2, []string{storageClass}, isObs)
}
if input.WebsiteRedirectLocation != "" {
setHeaders(headers, HEADER_WEBSITE_REDIRECT_LOCATION, []string{input.WebsiteRedirectLocation}, isObs)
}
setSseHeader(headers, input.SseHeader, false, isObs)
if input.Expires != 0 {
setHeaders(headers, HEADER_EXPIRES_OBS, []string{Int64ToString(input.Expires)}, true)
}
if input.Metadata != nil {
for key, value := range input.Metadata {
key = strings.TrimSpace(key)
setHeadersNext(headers, HEADER_PREFIX_META_OBS+key, HEADER_PREFIX_META+key, []string{value}, isObs)
}
}
return
}
func (input PutObjectBasicInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params, headers, data = input.ObjectOperationInput.trans(isObs)
if input.ContentMD5 != "" {
headers[HEADER_MD5_CAMEL] = []string{input.ContentMD5}
}
if input.ContentLength > 0 {
headers[HEADER_CONTENT_LENGTH_CAMEL] = []string{Int64ToString(input.ContentLength)}
}
if input.ContentType != "" {
headers[HEADER_CONTENT_TYPE_CAML] = []string{input.ContentType}
}
return
}
func (input PutObjectInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params, headers, data = input.PutObjectBasicInput.trans(isObs)
if input.Body != nil {
data = input.Body
}
return
}
func (input CopyObjectInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params, headers, data = input.ObjectOperationInput.trans(isObs)
var copySource string
if input.CopySourceVersionId != "" {
copySource = fmt.Sprintf("%s/%s?versionId=%s", input.CopySourceBucket, UrlEncode(input.CopySourceKey, false), input.CopySourceVersionId)
} else {
copySource = fmt.Sprintf("%s/%s", input.CopySourceBucket, UrlEncode(input.CopySourceKey, false))
}
setHeaders(headers, HEADER_COPY_SOURCE, []string{copySource}, isObs)
if directive := string(input.MetadataDirective); directive != "" {
setHeaders(headers, HEADER_METADATA_DIRECTIVE, []string{directive}, isObs)
}
if input.MetadataDirective == ReplaceMetadata {
if input.CacheControl != "" {
headers[HEADER_CACHE_CONTROL] = []string{input.CacheControl}
}
if input.ContentDisposition != "" {
headers[HEADER_CONTENT_DISPOSITION] = []string{input.ContentDisposition}
}
if input.ContentEncoding != "" {
headers[HEADER_CONTENT_ENCODING] = []string{input.ContentEncoding}
}
if input.ContentLanguage != "" {
headers[HEADER_CONTENT_LANGUAGE] = []string{input.ContentLanguage}
}
if input.ContentType != "" {
headers[HEADER_CONTENT_TYPE] = []string{input.ContentType}
}
if input.Expires != "" {
headers[HEADER_EXPIRES] = []string{input.Expires}
}
}
if input.CopySourceIfMatch != "" {
setHeaders(headers, HEADER_COPY_SOURCE_IF_MATCH, []string{input.CopySourceIfMatch}, isObs)
}
if input.CopySourceIfNoneMatch != "" {
setHeaders(headers, HEADER_COPY_SOURCE_IF_NONE_MATCH, []string{input.CopySourceIfNoneMatch}, isObs)
}
if !input.CopySourceIfModifiedSince.IsZero() {
setHeaders(headers, HEADER_COPY_SOURCE_IF_MODIFIED_SINCE, []string{FormatUtcToRfc1123(input.CopySourceIfModifiedSince)}, isObs)
}
if !input.CopySourceIfUnmodifiedSince.IsZero() {
setHeaders(headers, HEADER_COPY_SOURCE_IF_UNMODIFIED_SINCE, []string{FormatUtcToRfc1123(input.CopySourceIfUnmodifiedSince)}, isObs)
}
if input.SourceSseHeader != nil {
if sseCHeader, ok := input.SourceSseHeader.(SseCHeader); ok {
setHeaders(headers, HEADER_SSEC_COPY_SOURCE_ENCRYPTION, []string{sseCHeader.GetEncryption()}, isObs)
setHeaders(headers, HEADER_SSEC_COPY_SOURCE_KEY, []string{sseCHeader.GetKey()}, isObs)
setHeaders(headers, HEADER_SSEC_COPY_SOURCE_KEY_MD5, []string{sseCHeader.GetKeyMD5()}, isObs)
}
}
if input.successActionRedirect != "" {
headers[HEADER_SUCCESS_ACTION_REDIRECT] = []string{input.successActionRedirect}
}
return
}
func (input AbortMultipartUploadInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{"uploadId": input.UploadId}
return
}
func (input InitiateMultipartUploadInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params, headers, data = input.ObjectOperationInput.trans(isObs)
params[string(SubResourceUploads)] = ""
return
}
func (input UploadPartInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{"uploadId": input.UploadId, "partNumber": IntToString(input.PartNumber)}
headers = make(map[string][]string)
setSseHeader(headers, input.SseHeader, true, isObs)
if input.Body != nil {
data = input.Body
}
return
}
func (input CompleteMultipartUploadInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{"uploadId": input.UploadId}
data, _ = ConvertCompleteMultipartUploadInputToXml(input, false)
return
}
func (input ListPartsInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{"uploadId": input.UploadId}
if input.MaxParts > 0 {
params["max-parts"] = IntToString(input.MaxParts)
}
if input.PartNumberMarker > 0 {
params["part-number-marker"] = IntToString(input.PartNumberMarker)
}
return
}
func (input CopyPartInput) trans(isObs bool) (params map[string]string, headers map[string][]string, data interface{}) {
params = map[string]string{"uploadId": input.UploadId, "partNumber": IntToString(input.PartNumber)}
headers = make(map[string][]string, 1)
var copySource string
if input.CopySourceVersionId != "" {
copySource = fmt.Sprintf("%s/%s?versionId=%s", input.CopySourceBucket, UrlEncode(input.CopySourceKey, false), input.CopySourceVersionId)
} else {
copySource = fmt.Sprintf("%s/%s", input.CopySourceBucket, UrlEncode(input.CopySourceKey, false))
}
setHeaders(headers, HEADER_COPY_SOURCE, []string{copySource}, isObs)
if input.CopySourceRangeStart >= 0 && input.CopySourceRangeEnd > input.CopySourceRangeStart {
setHeaders(headers, HEADER_COPY_SOURCE_RANGE, []string{fmt.Sprintf("bytes=%d-%d", input.CopySourceRangeStart, input.CopySourceRangeEnd)}, isObs)
}
setSseHeader(headers, input.SseHeader, true, isObs)
if input.SourceSseHeader != nil {
if sseCHeader, ok := input.SourceSseHeader.(SseCHeader); ok {
setHeaders(headers, HEADER_SSEC_COPY_SOURCE_ENCRYPTION, []string{sseCHeader.GetEncryption()}, isObs)
setHeaders(headers, HEADER_SSEC_COPY_SOURCE_KEY, []string{sseCHeader.GetKey()}, isObs)
setHeaders(headers, HEADER_SSEC_COPY_SOURCE_KEY_MD5, []string{sseCHeader.GetKeyMD5()}, isObs)
}
}
return
}
type partSlice []Part
func (parts partSlice) Len() int {
return len(parts)
}
func (parts partSlice) Less(i, j int) bool {
return parts[i].PartNumber < parts[j].PartNumber
}
func (parts partSlice) Swap(i, j int) {
parts[i], parts[j] = parts[j], parts[i]
}
type readerWrapper struct {
reader io.Reader
mark int64
totalCount int64
readedCount int64
}
func (rw *readerWrapper) seek(offset int64, whence int) (int64, error) {
if r, ok := rw.reader.(*strings.Reader); ok {
return r.Seek(offset, whence)
} else if r, ok := rw.reader.(*bytes.Reader); ok {
return r.Seek(offset, whence)
} else if r, ok := rw.reader.(*os.File); ok {
return r.Seek(offset, whence)
}
return offset, nil
}
func (rw *readerWrapper) Read(p []byte) (n int, err error) {
if rw.totalCount == 0 {
return 0, io.EOF
}
if rw.totalCount > 0 {
n, err = rw.reader.Read(p)
readedOnce := int64(n)
if remainCount := rw.totalCount - rw.readedCount; remainCount > readedOnce {
rw.readedCount += readedOnce
return n, err
} else {
rw.readedCount += remainCount
return int(remainCount), io.EOF
}
}
return rw.reader.Read(p)
}
type fileReaderWrapper struct {
readerWrapper
filePath string
}
+394
View File
@@ -0,0 +1,394 @@
package obs
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/xml"
"fmt"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
var regex = regexp.MustCompile("^[\u4e00-\u9fa5]$")
var ipRegex = regexp.MustCompile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$")
var v4AuthRegex = regexp.MustCompile("Credential=(.+?),SignedHeaders=(.+?),Signature=.+")
var regionRegex = regexp.MustCompile(".+/\\d+/(.+?)/.+")
func StringContains(src string, subStr string, subTranscoding string) string {
return strings.Replace(src, subStr, subTranscoding, -1)
}
func XmlTranscoding(src string) string {
srcTmp := StringContains(src, "&", "&amp;")
srcTmp = StringContains(srcTmp, "<", "&lt;")
srcTmp = StringContains(srcTmp, ">", "&gt;")
srcTmp = StringContains(srcTmp, "'", "&apos;")
srcTmp = StringContains(srcTmp, "\"", "&quot;")
return srcTmp
}
func StringToInt(value string, def int) int {
ret, err := strconv.Atoi(value)
if err != nil {
ret = def
}
return ret
}
func StringToInt64(value string, def int64) int64 {
ret, err := strconv.ParseInt(value, 10, 64)
if err != nil {
ret = def
}
return ret
}
func IntToString(value int) string {
return strconv.Itoa(value)
}
func Int64ToString(value int64) string {
return strconv.FormatInt(value, 10)
}
func GetCurrentTimestamp() int64 {
return time.Now().UnixNano() / 1000000
}
func FormatUtcNow(format string) string {
return time.Now().UTC().Format(format)
}
func FormatUtcToRfc1123(t time.Time) string {
ret := t.UTC().Format(time.RFC1123)
return ret[:strings.LastIndex(ret, "UTC")] + "GMT"
}
func Md5(value []byte) []byte {
m := md5.New()
m.Write(value)
return m.Sum(nil)
}
func HmacSha1(key, value []byte) []byte {
mac := hmac.New(sha1.New, key)
mac.Write(value)
return mac.Sum(nil)
}
func HmacSha256(key, value []byte) []byte {
mac := hmac.New(sha256.New, key)
mac.Write(value)
return mac.Sum(nil)
}
func Base64Encode(value []byte) string {
return base64.StdEncoding.EncodeToString(value)
}
func Base64Decode(value string) ([]byte, error) {
return base64.StdEncoding.DecodeString(value)
}
func HexMd5(value []byte) string {
return Hex(Md5(value))
}
func Base64Md5(value []byte) string {
return Base64Encode(Md5(value))
}
func Sha256Hash(value []byte) []byte {
hash := sha256.New()
hash.Write(value)
return hash.Sum(nil)
}
func ParseXml(value []byte, result interface{}) error {
if len(value) == 0 {
return nil
}
return xml.Unmarshal(value, result)
}
func TransToXml(value interface{}) ([]byte, error) {
if value == nil {
return []byte{}, nil
}
return xml.Marshal(value)
}
func Hex(value []byte) string {
return hex.EncodeToString(value)
}
func HexSha256(value []byte) string {
return Hex(Sha256Hash(value))
}
func UrlDecode(value string) (string, error) {
ret, err := url.QueryUnescape(value)
if err == nil {
return ret, nil
}
return "", err
}
func IsIP(value string) bool {
return ipRegex.MatchString(value)
}
func UrlEncode(value string, chineseOnly bool) string {
if chineseOnly {
values := make([]string, 0, len(value))
for _, val := range value {
_value := string(val)
if regex.MatchString(_value) {
_value = url.QueryEscape(_value)
}
values = append(values, _value)
}
return strings.Join(values, "")
}
return url.QueryEscape(value)
}
func copyHeaders(m map[string][]string) (ret map[string][]string) {
if m != nil {
ret = make(map[string][]string, len(m))
for key, values := range m {
_values := make([]string, 0, len(values))
for _, value := range values {
_values = append(_values, value)
}
ret[strings.ToLower(key)] = _values
}
} else {
ret = make(map[string][]string)
}
return
}
func parseHeaders(headers map[string][]string) (signature string, region string, signedHeaders string) {
signature = "v2"
if receviedAuthorization, ok := headers[strings.ToLower(HEADER_AUTH_CAMEL)]; ok && len(receviedAuthorization) > 0 {
if strings.HasPrefix(receviedAuthorization[0], V4_HASH_PREFIX) {
signature = "v4"
matches := v4AuthRegex.FindStringSubmatch(receviedAuthorization[0])
if len(matches) >= 3 {
region = matches[1]
regions := regionRegex.FindStringSubmatch(region)
if len(regions) >= 2 {
region = regions[1]
}
signedHeaders = matches[2]
}
} else if strings.HasPrefix(receviedAuthorization[0], V2_HASH_PREFIX) {
signature = "v2"
}
}
return
}
func getTemporaryKeys() []string {
return []string{
"Signature",
"signature",
"X-Amz-Signature",
"x-amz-signature",
}
}
func getIsObs(isTemporary bool, querys []string, headers map[string][]string) bool {
isObs := true
if isTemporary {
for _, value := range querys {
keyPrefix := strings.ToLower(value)
if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
isObs = false
} else if strings.HasPrefix(value, HEADER_ACCESSS_KEY_AMZ) {
isObs = false
}
}
} else {
for key := range headers {
keyPrefix := strings.ToLower(key)
if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
isObs = false
break
}
}
}
return isObs
}
func GetAuthorization(ak, sk, method, bucketName, objectKey, queryUrl string, headers map[string][]string) (ret map[string]string) {
if strings.HasPrefix(queryUrl, "?") {
queryUrl = queryUrl[1:]
}
method = strings.ToUpper(method)
querys := strings.Split(queryUrl, "&")
querysResult := make([]string, 0)
for _, value := range querys {
if value != "=" && len(value) != 0 {
querysResult = append(querysResult, value)
}
}
params := make(map[string]string)
for _, value := range querysResult {
kv := strings.Split(value, "=")
length := len(kv)
if length == 1 {
key, _ := UrlDecode(kv[0])
params[key] = ""
} else if length >= 2 {
key, _ := UrlDecode(kv[0])
vals := make([]string, 0, length-1)
for i := 1; i < length; i++ {
val, _ := UrlDecode(kv[i])
vals = append(vals, val)
}
params[key] = strings.Join(vals, "=")
}
}
isTemporary := false
signature := "v2"
temporaryKeys := getTemporaryKeys()
for _, key := range temporaryKeys {
if _, ok := params[key]; ok {
isTemporary = true
if strings.ToLower(key) == "signature" {
signature = "v2"
} else if strings.ToLower(key) == "x-amz-signature" {
signature = "v4"
}
break
}
}
isObs := getIsObs(isTemporary, querysResult, headers)
headers = copyHeaders(headers)
pathStyle := false
if receviedHost, ok := headers[HEADER_HOST]; ok && len(receviedHost) > 0 && !strings.HasPrefix(receviedHost[0], bucketName+".") {
pathStyle = true
}
conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk},
urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443},
pathStyle: pathStyle}
if isTemporary {
return getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature, conf, params, headers, isObs)
} else {
signature, region, signedHeaders := parseHeaders(headers)
if signature == "v4" {
conf.signature = SignatureV4
requestUrl, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
parsedRequestUrl, _ := url.Parse(requestUrl)
headerKeys := strings.Split(signedHeaders, ";")
_headers := make(map[string][]string, len(headerKeys))
for _, headerKey := range headerKeys {
_headers[headerKey] = headers[headerKey]
}
ret = v4Auth(ak, sk, region, method, canonicalizedUrl, parsedRequestUrl.RawQuery, _headers)
ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
} else if signature == "v2" {
conf.signature = SignatureV2
_, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
ret = v2Auth(ak, sk, method, canonicalizedUrl, headers, isObs)
v2HashPrefix := V2_HASH_PREFIX
if isObs {
v2HashPrefix = OBS_HASH_PREFIX
}
ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"])
}
return
}
}
func getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature string, conf *config, params map[string]string,
headers map[string][]string, isObs bool) (ret map[string]string) {
if signature == "v4" {
conf.signature = SignatureV4
longDate, ok := params[PARAM_DATE_AMZ_CAMEL]
if !ok {
longDate = params[HEADER_DATE_AMZ]
}
shortDate := longDate[:8]
credential, ok := params[PARAM_CREDENTIAL_AMZ_CAMEL]
if !ok {
credential = params[strings.ToLower(PARAM_CREDENTIAL_AMZ_CAMEL)]
}
_credential, _ := UrlDecode(credential)
regions := regionRegex.FindStringSubmatch(_credential)
var region string
if len(regions) >= 2 {
region = regions[1]
}
_, scope := getCredential(ak, region, shortDate)
expires, ok := params[PARAM_EXPIRES_AMZ_CAMEL]
if !ok {
expires = params[strings.ToLower(PARAM_EXPIRES_AMZ_CAMEL)]
}
signedHeaders, ok := params[PARAM_SIGNEDHEADERS_AMZ_CAMEL]
if !ok {
signedHeaders = params[strings.ToLower(PARAM_SIGNEDHEADERS_AMZ_CAMEL)]
}
algorithm, ok := params[PARAM_ALGORITHM_AMZ_CAMEL]
if !ok {
algorithm = params[strings.ToLower(PARAM_ALGORITHM_AMZ_CAMEL)]
}
if _, ok := params[PARAM_SIGNATURE_AMZ_CAMEL]; ok {
delete(params, PARAM_SIGNATURE_AMZ_CAMEL)
} else if _, ok := params[strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL)]; ok {
delete(params, strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL))
}
ret = make(map[string]string, 6)
ret[PARAM_ALGORITHM_AMZ_CAMEL] = algorithm
ret[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
ret[PARAM_DATE_AMZ_CAMEL] = longDate
ret[PARAM_EXPIRES_AMZ_CAMEL] = expires
ret[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = signedHeaders
requestUrl, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
parsedRequestUrl, _ := url.Parse(requestUrl)
stringToSign := getV4StringToSign(method, canonicalizedUrl, parsedRequestUrl.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, strings.Split(signedHeaders, ";"), headers)
ret[PARAM_SIGNATURE_AMZ_CAMEL] = UrlEncode(getSignature(stringToSign, sk, region, shortDate), false)
} else if signature == "v2" {
conf.signature = SignatureV2
_, canonicalizedUrl := conf.formatUrls(bucketName, objectKey, params, false)
expires, ok := params["Expires"]
if !ok {
expires = params["expires"]
}
headers[HEADER_DATE_CAMEL] = []string{expires}
stringToSign := getV2StringToSign(method, canonicalizedUrl, headers, isObs)
ret = make(map[string]string, 3)
ret["Signature"] = UrlEncode(Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign))), false)
ret["AWSAccessKeyId"] = UrlEncode(ak, false)
ret["Expires"] = UrlEncode(expires, false)
}
return
}
+74
View File
@@ -0,0 +1,74 @@
package huawei
import "fmt"
type SResource struct {
ResourceID string `json:"resourceId"`
CloudServiceType string `json:"cloudServiceType"`
RegionCode string `json:"regionCode"`
ResourceType string `json:"resourceType"`
ResourceSpecCode string `json:"resourceSpecCode"`
Status int64 `json:"status"`
}
/*
获取订单信息 https://support.huaweicloud.com/api-oce/api_order_00001.html
*/
func (self *SRegion) GetAllResByOrderId(orderId string) ([]SResource, error) {
domains, err := self.client.getEnabledDomains()
if err != nil {
return nil, err
}
if domains == nil || len(domains) == 0 {
return nil, fmt.Errorf("GetAllResByOrderId domain is empty")
} else if len(domains) > 1 {
// not supported??
return nil, fmt.Errorf("GetAllResByOrderId mutliple domain(%d) found", len(domains))
}
err = self.ecsClient.Orders.SetDomainId(domains[0].ID)
if err != nil {
return nil, err
}
resp, err := self.ecsClient.Orders.Get(orderId, nil)
if err != nil {
return nil, err
}
resources := make([]SResource, 0)
err = resp.Unmarshal(&resources, "resources")
return resources, err
}
func (self *SRegion) getAllResByType(orderId string, resourceType string) ([]SResource, error) {
res, err := self.GetAllResByOrderId(orderId)
if err != nil {
return nil, err
}
ret := make([]SResource, 0)
for i := range res {
r := res[i]
if r.ResourceType == resourceType {
ret = append(ret, r)
}
}
return ret, nil
}
func (self *SRegion) getAllResIdsByType(orderId string, resourceType string) ([]string, error) {
res, err := self.getAllResByType(orderId, resourceType)
if err != nil {
return nil, err
}
ids := make([]string, 0)
for _, r := range res {
ids = append(ids, r.ResourceID)
}
return ids, nil
}
+1 -2
View File
@@ -14,7 +14,6 @@ type SHuaweiProviderFactory struct {
}
func (self *SHuaweiProviderFactory) ValidateChangeBandwidth(instanceId string, bandwidth int64) error {
// todo: implement me
return nil
}
@@ -111,7 +110,7 @@ func (self *SHuaweiProvider) GetIRegionById(extId string) (cloudprovider.ICloudR
}
func (self *SHuaweiProvider) GetOnPremiseIRegion() (cloudprovider.ICloudRegion, error) {
panic("implement me")
return nil, cloudprovider.ErrNotImplemented
}
func (self *SHuaweiProvider) GetBalance() (float64, error) {
+232 -29
View File
@@ -2,14 +2,18 @@ package huawei
import (
"fmt"
"sort"
"strconv"
"time"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/util/huawei/client"
"yunion.io/x/onecloud/pkg/util/huawei/obs"
"yunion.io/x/pkg/util/secrules"
)
@@ -22,6 +26,7 @@ type Locales struct {
type SRegion struct {
client *SHuaweiClient
ecsClient *client.Client
obsClient *obs.ObsClient // 对象存储client.请勿直接引用。
Description string `json:"description"`
ID string `json:"id"`
@@ -35,6 +40,10 @@ type SRegion struct {
storageCache *SStoragecache
}
func (self *SRegion) GetClient() *SHuaweiClient {
return self.client
}
func (self *SRegion) getECSClient() (*client.Client, error) {
var err error
@@ -63,6 +72,20 @@ func (self *SRegion) getECSClient() (*client.Client, error) {
return self.ecsClient, err
}
func (self *SRegion) getOBSClient() (*obs.ObsClient, error) {
if self.obsClient == nil {
endpoint := fmt.Sprintf("obs.%s.myhuaweicloud.com", self.GetId())
obsClient, err := obs.New(self.client.accessKey, self.client.secret, endpoint)
if err != nil {
return nil, err
}
self.obsClient = obsClient
}
return self.obsClient, nil
}
func (self *SRegion) fetchZones() error {
zones := make([]SZone, 0)
err := DoList(self.ecsClient.Zones.List, nil, &zones)
@@ -176,12 +199,13 @@ func (self *SRegion) fetchInfrastructure() error {
}
for i := 0; i < len(self.ivpcs); i += 1 {
vpc := self.ivpcs[i].(*SVpc)
wire := SWire{region: self, vpc: vpc}
vpc.addWire(&wire)
for j := 0; j < len(self.izones); j += 1 {
zone := self.izones[j].(*SZone)
vpc := self.ivpcs[i].(*SVpc)
wire := SWire{zone: zone, vpc: vpc}
zone.addWire(&wire)
vpc.addWire(&wire)
}
}
return nil
@@ -295,40 +319,55 @@ func (self *SRegion) GetIEipById(eipId string) (cloudprovider.ICloudEIP, error)
return &eip, err
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0060595555.html
func (self *SRegion) DeleteSecurityGroup(vpcId, secgroupId string) error {
// todo: implement me
return nil
return DoDelete(self.ecsClient.SecurityGroups.Delete, secgroupId, nil, nil)
}
func (self *SRegion) SyncSecurityGroup(secgroupId string, vpcId string, name string, desc string, rules []secrules.SecurityRule) (string, error) {
//if len(secgroupId) > 0 {
// _, total, err := self.GetSecurityGroups("", []string{secgroupId}, 0, 1)
// if err != nil {
// return "", err
// }
// if total == 0 {
// secgroupId = ""
// }
//}
//if len(secgroupId) == 0 {
// extID, err := self.CreateSecurityGroup(vpcId, name, desc)
// if err != nil {
// return "", err
// }
// secgroupId = extID
//}
//return secgroupId, self.syncSecgroupRules(secgroupId, rules)
// todo: implement me
return "", nil
if len(secgroupId) > 0 {
_, err := self.GetSecurityGroupDetails(secgroupId)
if err == cloudprovider.ErrNotSupported {
secgroupId = ""
} else if err != nil {
return "", err
}
}
if len(secgroupId) == 0 {
extID, err := self.CreateSecurityGroup(vpcId, name, desc)
if err != nil {
return "", err
}
secgroupId = extID
}
return secgroupId, self.syncSecgroupRules(secgroupId, rules)
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090608.html
func (self *SRegion) CreateIVpc(name string, desc string, cidr string) (cloudprovider.ICloudVpc, error) {
// todo: implement me
return self.GetIVpcById("")
params := jsonutils.NewDict()
vpcObj := jsonutils.NewDict()
vpcObj.Add(jsonutils.NewString(name), "name")
vpcObj.Add(jsonutils.NewString(cidr), "cidr")
params.Add(vpcObj, "vpc")
vpc := SVpc{}
err := DoCreate(self.ecsClient.Vpcs.Create, params, &vpc)
vpc.region = self
return &vpc, err
}
func (self *SRegion) CreateEIP(name string, bwMbps int, chargeType string) (cloudprovider.ICloudEIP, error) {
// todo: implement me
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090596.html
// size: 1Mbit/s~2000Mbit/s
// bgpType: 5_telcom,5_union,5_bgp,5_sbgp.
// 东北-大连:5_telcom、5_union
// 华南-广州:5_sbgp
// 华东-上海二:5_sbgp
// 华北-北京一:5_bgp、5_sbgp
// 亚太-香港:5_bgp
func (self *SRegion) CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (cloudprovider.ICloudEIP, error) {
var ctype TInternetChargeType
switch chargeType {
case models.EIP_CHARGE_TYPE_BY_TRAFFIC:
@@ -337,7 +376,27 @@ func (self *SRegion) CreateEIP(name string, bwMbps int, chargeType string) (clou
ctype = InternetChargeByBandwidth
}
eip, err := self.AllocateEIP(bwMbps, ctype)
// todo: 如何避免hardcode。集成到cloudmeta服务中?
if len(bgpType) == 0 {
switch self.GetId() {
case "cn-north-1", "cn-east-2", "cn-south-1":
bgpType = "5_sbgp"
case "cn-northeast-1":
bgpType = "5_telcom"
case "ap-southeast-1", "ap-southeast-2", "eu-west-0":
bgpType = "5_bgp"
default:
bgpType = ""
}
}
eip, err := self.AllocateEIP(name, bwMbps, ctype, bgpType)
eip.region = self
if err != nil {
return nil, err
}
err = cloudprovider.WaitStatus(eip, models.EIP_STATUS_READY, 5*time.Second, 60*time.Second)
return eip, err
}
@@ -442,3 +501,147 @@ func (self *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage, er
func (self *SRegion) GetProvider() string {
return CLOUD_PROVIDER_HUAWEI
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090615.html
// 目前desc字段并没有用到
func (self *SRegion) CreateSecurityGroup(vpcId string, name string, desc string) (string, error) {
// 华为不允许创建名称为default的安全组
if strings.ToLower(name) == "default" {
name = fmt.Sprintf("%s-%s", vpcId, name)
}
params := jsonutils.NewDict()
secgroupObj := jsonutils.NewDict()
secgroupObj.Add(jsonutils.NewString(name), "name")
if len(vpcId) > 0 {
secgroupObj.Add(jsonutils.NewString(vpcId), "vpc_id")
}
params.Add(secgroupObj, "security_group")
secgroup := SSecurityGroup{}
err := DoCreate(self.ecsClient.SecurityGroups.Create, params, &secgroup)
return secgroup.GetId(), err
}
func (self *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.SecurityRule) error {
var DeleteRules []secrules.SecurityRule
var AddRules []secrules.SecurityRule
if secgroup, err := self.GetSecurityGroupDetails(secgroupId); err != nil {
return err
} else {
remoteRules, err := secgroup.GetRulesWithExtId()
if err != nil {
return err
}
sort.Sort(secrules.SecurityRuleSet(rules))
sort.Sort(secrules.SecurityRuleSet(remoteRules))
i, j := 0, 0
for i < len(rules) || j < len(remoteRules) {
if i < len(rules) && j < len(remoteRules) {
permissionStr := remoteRules[j].String()
ruleStr := rules[i].String()
cmp := strings.Compare(permissionStr, ruleStr)
if cmp == 0 {
// DeleteRules = append(DeleteRules, remoteRules[j])
// AddRules = append(AddRules, rules[i])
i += 1
j += 1
} else if cmp > 0 {
DeleteRules = append(DeleteRules, remoteRules[j])
j += 1
} else {
AddRules = append(AddRules, rules[i])
i += 1
}
} else if i >= len(rules) {
DeleteRules = append(DeleteRules, remoteRules[j])
j += 1
} else if j >= len(remoteRules) {
AddRules = append(AddRules, rules[i])
i += 1
}
}
}
for _, r := range DeleteRules {
// r.Description 实际存储的是ruleId
if err := self.delSecurityGroupRule(r.Description); err != nil {
log.Errorf("delSecurityGroupRule %v error: %s", r, err.Error())
return err
}
}
for _, r := range AddRules {
if err := self.addSecurityGroupRules(secgroupId, &r); err != nil {
log.Errorf("addSecurityGroupRule %v error: %s", r, err.Error())
return err
}
}
return nil
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0087467071.html
func (self *SRegion) delSecurityGroupRule(secGrpRuleId string) error {
return DoDelete(self.ecsClient.SecurityGroupRules.Delete, secGrpRuleId, nil, nil)
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0087451723.html
// icmp port对应关系:https://support.huaweicloud.com/api-vpc/zh-cn_topic_0024109590.html
func (self *SRegion) addSecurityGroupRules(secGrpId string, rule *secrules.SecurityRule) error {
direction := ""
if rule.Direction == secrules.SecurityRuleIngress {
direction = "ingress"
} else {
direction = "egress"
}
protocal := rule.Protocol
if rule.Protocol == secrules.PROTO_ANY {
protocal = ""
}
if len(rule.Ports) > 0 {
for _, port := range rule.Ports {
portStr := fmt.Sprintf("%d", port)
err := self.addSecurityGroupRule(secGrpId, direction, portStr, portStr, protocal, rule.IPNet.String())
if err != nil {
return err
}
}
} else {
portStart := fmt.Sprintf("%d", rule.PortStart)
portEnd := fmt.Sprintf("%d", rule.PortEnd)
err := self.addSecurityGroupRule(secGrpId, direction, portStart, portEnd, protocal, rule.IPNet.String())
if err != nil {
return err
}
}
return nil
}
func (self *SRegion) addSecurityGroupRule(secGrpId, direction, portStart, portEnd, protocol, ipNet string) error {
params := jsonutils.NewDict()
secgroupObj := jsonutils.NewDict()
secgroupObj.Add(jsonutils.NewString(secGrpId), "security_group_id")
secgroupObj.Add(jsonutils.NewString(direction), "direction")
secgroupObj.Add(jsonutils.NewString(ipNet), "remote_ip_prefix")
secgroupObj.Add(jsonutils.NewString("IPV4"), "ethertype")
if len(portStart) > 0 && portStart != "0" {
secgroupObj.Add(jsonutils.NewString(portStart), "port_range_min")
}
if len(portEnd) > 0 && portEnd != "0" {
secgroupObj.Add(jsonutils.NewString(portEnd), "port_range_max")
}
if len(protocol) > 0 {
secgroupObj.Add(jsonutils.NewString(protocol), "protocol")
}
params.Add(secgroupObj, "security_group_rule")
rule := SecurityGroupRule{}
return DoCreate(self.ecsClient.SecurityGroupRules.Create, params, &rule)
}
+97 -10
View File
@@ -44,7 +44,8 @@ type SecurityGroupRuleDetail struct {
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090615.html
type SSecurityGroup struct {
vpc *SVpc
region *SRegion
vpc *SVpc // 安全组对应的vpc可能为空
ID string `json:"id"`
Name string `json:"name"`
@@ -54,6 +55,21 @@ type SSecurityGroup struct {
SecurityGroupRules []SecurityGroupRule `json:"security_group_rules"`
}
// 判断是否兼容云端安全组规则
func compatibleSecurityGroupRule(r SecurityGroupRule) bool {
// 忽略了源地址是安全组的规则
if len(r.RemoteGroupID) > 0 {
return false
}
// 忽略IPV6
if r.Ethertype == "IPv6" {
return false
}
return true
}
func (self *SSecurityGroup) GetId() string {
return self.ID
}
@@ -83,7 +99,7 @@ func (self *SSecurityGroup) GetStatus() string {
}
func (self *SSecurityGroup) Refresh() error {
if new, err := self.vpc.region.GetSecurityGroupDetails(self.GetId()); err != nil {
if new, err := self.region.GetSecurityGroupDetails(self.GetId()); err != nil {
return err
} else {
return jsonutils.Update(self, new)
@@ -103,15 +119,15 @@ func (self *SSecurityGroup) GetDescription() string {
return self.Description
}
// todo: 这里需要优化查询太多了
func (self *SSecurityGroup) GetRules() ([]secrules.SecurityRule, error) {
rules := make([]secrules.SecurityRule, 0)
for _, r := range self.SecurityGroupRules {
// 忽略了源地址是安全组的规则
if len(r.RemoteGroupID) > 0 {
if !compatibleSecurityGroupRule(r) {
continue
}
rule, err := self.GetSecurityRule(r.ID)
rule, err := self.GetSecurityRule(r.ID, false)
if err != nil {
return rules, err
}
@@ -122,9 +138,28 @@ func (self *SSecurityGroup) GetRules() ([]secrules.SecurityRule, error) {
return rules, nil
}
func (self *SSecurityGroup) GetSecurityRule(ruleId string) (secrules.SecurityRule, error) {
func (self *SSecurityGroup) GetRulesWithExtId() ([]secrules.SecurityRule, error) {
rules := make([]secrules.SecurityRule, 0)
for _, r := range self.SecurityGroupRules {
if !compatibleSecurityGroupRule(r) {
continue
}
rule, err := self.GetSecurityRule(r.ID, true)
if err != nil {
return rules, err
}
rules = append(rules, rule)
}
return rules, nil
}
// withRuleId.
func (self *SSecurityGroup) GetSecurityRule(ruleId string, withRuleId bool) (secrules.SecurityRule, error) {
remoteRule := SecurityGroupRuleDetail{}
err := DoGet(self.vpc.region.ecsClient.SecurityGroupRules.Get, ruleId, nil, &remoteRule)
err := DoGet(self.region.ecsClient.SecurityGroupRules.Get, ruleId, nil, &remoteRule)
if err != nil {
return secrules.SecurityRule{}, err
}
@@ -136,17 +171,30 @@ func (self *SSecurityGroup) GetSecurityRule(ruleId string) (secrules.SecurityRul
direction = secrules.SecurityRuleEgress
}
protocol := "any"
protocol := secrules.PROTO_ANY
if remoteRule.Protocol != "" {
protocol = remoteRule.Protocol
}
// todo: 没考虑ipv6。可能报错
ipNet := &net.IPNet{}
if len(remoteRule.RemoteIPPrefix) > 0 {
_, ipNet, err = net.ParseCIDR(remoteRule.RemoteIPPrefix)
} else {
_, ipNet, err = net.ParseCIDR("0.0.0.0/0")
}
if err != nil {
return secrules.SecurityRule{}, err
}
// withRuleId.将ruleId附加到description字段。该hook有特殊目的,仅在同步安全组时使用。
desc := ""
if withRuleId {
desc = ruleId
} else {
desc = remoteRule.Description
}
// todo: icmp 可能不兼容
rule := secrules.SecurityRule{
Priority: 0,
Action: secrules.SecurityRuleAllow,
@@ -156,14 +204,25 @@ func (self *SSecurityGroup) GetSecurityRule(ruleId string) (secrules.SecurityRul
PortStart: int(remoteRule.PortRangeMin),
PortEnd: int(remoteRule.PortRangeMax),
Ports: nil,
Description: remoteRule.Description,
Description: desc,
}
err = rule.ValidateRule()
return rule, err
}
func (self *SRegion) GetSecurityGroupDetails(secGroupId string) (*SSecurityGroup, error) {
securitygroup := SSecurityGroup{}
err := DoGet(self.ecsClient.SecurityGroups.Get, secGroupId, nil, &securitygroup)
if err != nil {
return nil, err
}
securitygroup.region = self
if len(securitygroup.VpcID) > 0 && securitygroup.VpcID != "default" {
securitygroup.vpc, err = self.getVpc(securitygroup.VpcID)
}
return &securitygroup, err
}
@@ -180,5 +239,33 @@ func (self *SRegion) GetSecurityGroups(vpcId string, limit int, marker string) (
querys["limit"] = strconv.Itoa(limit)
securitygroups := make([]SSecurityGroup, 0)
err := DoList(self.ecsClient.SecurityGroups.List, querys, &securitygroups)
if err != nil {
return nil, 0, err
}
vpcCache := map[string]*SVpc{}
for i := range securitygroups {
securitygroup := &securitygroups[i]
securitygroup.region = self
// 未绑定VPC的安全组
// todo:确认 vpc_id = default的安全组有什么含义?
if len(securitygroup.VpcID) == 0 || securitygroup.VpcID == "default" {
continue
}
if vpc, exists := vpcCache[securitygroup.VpcID]; exists {
securitygroup.vpc = vpc
} else {
vpc, err := self.getVpc(securitygroup.VpcID)
if err != nil {
return nil, 0, err
}
vpcCache[securitygroup.VpcID] = vpc
securitygroup.vpc = vpc
}
}
return securitygroups, len(securitygroups), err
}
+19
View File
@@ -0,0 +1,19 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type AccountBalanceOptions struct {
}
shellutils.R(&AccountBalanceOptions{}, "balance", "Get account balance", func(cli *huawei.SRegion, args *AccountBalanceOptions) error {
result, err := cli.GetClient().QueryAccountBalance()
if err != nil {
return err
}
printObject(result)
return nil
})
}
+33
View File
@@ -0,0 +1,33 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type DiskListOptions struct {
Zone string `help:"Zone ID"`
Offset int `help:"List offset"`
Limit int `help:"List limit"`
}
shellutils.R(&DiskListOptions{}, "disk-list", "List disks", func(cli *huawei.SRegion, args *DiskListOptions) error {
disks, total, e := cli.GetDisks(args.Zone, args.Offset, args.Limit)
if e != nil {
return e
}
printList(disks, total, args.Offset, args.Limit, []string{})
return nil
})
type DiskDeleteOptions struct {
ID string `help:"Disk ID"`
}
shellutils.R(&DiskDeleteOptions{}, "disk-delete", "List disks", func(cli *huawei.SRegion, args *DiskDeleteOptions) error {
e := cli.DeleteDisk(args.ID)
if e != nil {
return e
}
return nil
})
}
+1
View File
@@ -0,0 +1 @@
package shell // import "yunion.io/x/onecloud/pkg/util/huawei/shell"
+56
View File
@@ -0,0 +1,56 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type EipListOptions struct {
Marker string `help:"marker"`
Limit int `help:"List limit"`
}
shellutils.R(&EipListOptions{}, "eip-list", "List eips", func(cli *huawei.SRegion, args *EipListOptions) error {
eips, total, e := cli.GetEips(args.Marker, args.Limit)
if e != nil {
return e
}
printList(eips, total, 0, args.Limit, []string{})
return nil
})
type EipAllocateOptions struct {
Name string `help:"eip name"`
BW int `help:"Bandwidth limit in Mbps"`
BGP string `help:"bgp type" choices:"5_telcom|5_union|5_bgp|5_sbgp"`
}
shellutils.R(&EipAllocateOptions{}, "eip-create", "Allocate an EIP", func(cli *huawei.SRegion, args *EipAllocateOptions) error {
eip, err := cli.AllocateEIP(args.Name, args.BW, huawei.InternetChargeByTraffic, args.BGP)
if err != nil {
return err
}
printObject(eip)
return nil
})
type EipReleaseOptions struct {
ID string `help:"EIP allocation ID"`
}
shellutils.R(&EipReleaseOptions{}, "eip-delete", "Release an EIP", func(cli *huawei.SRegion, args *EipReleaseOptions) error {
err := cli.DeallocateEIP(args.ID)
return err
})
type EipAssociateOptions struct {
ID string `help:"EIP allocation ID"`
INSTANCE string `help:"Instance ID"`
}
shellutils.R(&EipAssociateOptions{}, "eip-associate", "Associate an EIP", func(cli *huawei.SRegion, args *EipAssociateOptions) error {
err := cli.AssociateEip(args.ID, args.INSTANCE)
return err
})
shellutils.R(&EipAssociateOptions{}, "eip-dissociate", "Dissociate an EIP", func(cli *huawei.SRegion, args *EipAssociateOptions) error {
err := cli.DissociateEip(args.ID, args.INSTANCE)
return err
})
}
+32
View File
@@ -0,0 +1,32 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type ImageListOptions struct {
Status string `help:"image status type" choices:"queued|saving|deleted|killed|active"`
Owner string `help:"Owner type" choices:"gold|private|shared"`
Id []string `help:"Image ID"`
Name string `help:"image name"`
Marker string `help:"marker"`
Limit int `help:"page Limit"`
}
shellutils.R(&ImageListOptions{}, "image-list", "List images", func(cli *huawei.SRegion, args *ImageListOptions) error {
images, total, e := cli.GetImages(args.Status, huawei.ImageOwnerType(args.Owner), args.Name, args.Limit, args.Marker)
if e != nil {
return e
}
printList(images, total, 0, args.Limit, []string{})
return nil
})
type ImageDeleteOptions struct {
ID string `help:"ID or Name to delete"`
}
shellutils.R(&ImageDeleteOptions{}, "image-delete", "Delete image", func(cli *huawei.SRegion, args *ImageDeleteOptions) error {
return cli.DeleteImage(args.ID)
})
}
+158
View File
@@ -0,0 +1,158 @@
package shell
import (
"context"
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type InstanceListOptions struct {
Limit int `help:"page size"`
Offset int `help:"page offset"`
}
shellutils.R(&InstanceListOptions{}, "instance-list", "List intances", func(cli *huawei.SRegion, args *InstanceListOptions) error {
instances, total, e := cli.GetInstances(args.Offset, args.Limit)
if e != nil {
return e
}
printList(instances, total, args.Offset, args.Limit, []string{})
return nil
})
type InstanceDiskOperationOptions struct {
ID string `help:"instance ID"`
DISK string `help:"disk ID"`
}
type InstanceDiskAttachOptions struct {
ID string `help:"instance ID"`
DISK string `help:"disk ID"`
DEVICE string `help:"disk device name. eg. /dev/sdb"`
}
shellutils.R(&InstanceDiskAttachOptions{}, "instance-attach-disk", "Attach a disk to instance", func(cli *huawei.SRegion, args *InstanceDiskAttachOptions) error {
err := cli.AttachDisk(args.ID, args.DISK, args.DEVICE)
if err != nil {
return err
}
return nil
})
shellutils.R(&InstanceDiskOperationOptions{}, "instance-detach-disk", "Detach a disk to instance", func(cli *huawei.SRegion, args *InstanceDiskOperationOptions) error {
err := cli.DetachDisk(args.ID, args.DISK)
if err != nil {
return err
}
return nil
})
type InstanceOperationOptions struct {
ID string `help:"instance ID"`
}
shellutils.R(&InstanceOperationOptions{}, "instance-start", "Start a instance", func(cli *huawei.SRegion, args *InstanceOperationOptions) error {
err := cli.StartVM(args.ID)
if err != nil {
return err
}
return nil
})
type InstanceStopOptions struct {
ID string `help:"instance ID"`
Force bool `help:"Force stop instance"`
}
shellutils.R(&InstanceStopOptions{}, "instance-stop", "Stop a instance", func(cli *huawei.SRegion, args *InstanceStopOptions) error {
err := cli.StopVM(args.ID, args.Force)
if err != nil {
return err
}
return nil
})
shellutils.R(&InstanceOperationOptions{}, "instance-delete", "Delete a instance", func(cli *huawei.SRegion, args *InstanceOperationOptions) error {
err := cli.DeleteVM(args.ID)
if err != nil {
return err
}
return nil
})
/*
server-change-config 更改系统配置
server-reset
*/
type InstanceDeployOptions struct {
ID string `help:"instance ID"`
Name string `help:"new instance name"`
Hostname string `help:"new hostname"`
Keypair string `help:"Keypair Name"`
DeleteKeypair bool `help:"Remove SSH keypair"`
Password string `help:"new password"`
// ResetPassword bool `help:"Force reset password"`
Description string `help:"new instances description"`
}
shellutils.R(&InstanceDeployOptions{}, "instance-deploy", "Deploy keypair/password to a stopped virtual server", func(cli *huawei.SRegion, args *InstanceDeployOptions) error {
err := cli.DeployVM(args.ID, args.Name, args.Password, args.Keypair, args.DeleteKeypair, args.Description)
if err != nil {
return err
}
return nil
})
type InstanceRebuildRootOptions struct {
ID string `help:"instance ID"`
Image string `help:"Image ID"`
Password string `help:"admin password"`
PublicKey string `help:"public key name"`
}
shellutils.R(&InstanceRebuildRootOptions{}, "instance-rebuild-root", "Reinstall virtual server system image", func(cli *huawei.SRegion, args *InstanceRebuildRootOptions) error {
ctx := context.Background()
err := cli.ChangeRoot(ctx, args.ID, args.Image, args.Password, args.PublicKey)
if err != nil {
return err
}
// fmt.Printf("New diskID is %s", diskID)
return nil
})
type InstanceChangeConfigOptions struct {
ID string `help:"instance ID"`
InstanceTypeId string `help:"instance type"`
Disk []int `help:"Data disk sizes int GB"`
}
shellutils.R(&InstanceChangeConfigOptions{}, "instance-change-config", "Deploy keypair/password to a stopped virtual server", func(cli *huawei.SRegion, args *InstanceChangeConfigOptions) error {
instance, e := cli.GetInstanceByID(args.ID)
if e != nil {
return e
}
// todo : add create disks
err := cli.ChangeVMConfig2(instance.GetId(), args.ID, args.InstanceTypeId, nil)
if err != nil {
return err
}
return nil
})
type InstanceOrderUnsubscribeOptions struct {
ID string `help:"instance ID"`
DOMAIN string `help:"domain ID"`
}
shellutils.R(&InstanceOrderUnsubscribeOptions{}, "instance-order-unsubscribe", "Unsubscribe a prepaid server", func(cli *huawei.SRegion, args *InstanceOrderUnsubscribeOptions) error {
instance, e := cli.GetInstanceByID(args.ID)
if e != nil {
return e
}
_, err := cli.UnsubscribeInstance(instance.GetId(), args.DOMAIN)
if err != nil {
return err
}
return nil
})
}
+22
View File
@@ -0,0 +1,22 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type InstanceMatchOptions struct {
CPU int `help:"CPU count"`
MEM int `help:"Memory in MB"`
Zone string `help:"Test in zone"`
}
shellutils.R(&InstanceMatchOptions{}, "instance-type-select", "Select matching instance types", func(cli *huawei.SRegion, args *InstanceMatchOptions) error {
instanceTypes, e := cli.GetMatchInstanceTypes(args.CPU, args.MEM, args.Zone)
if e != nil {
return e
}
printList(instanceTypes, 0, 0, 0, []string{})
return nil
})
}
+33
View File
@@ -0,0 +1,33 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
// todo: 需要进一步确认
type KeyPairListOptions struct {
}
shellutils.R(&KeyPairListOptions{}, "keypair-list", "List keypairs", func(cli *huawei.SRegion, args *KeyPairListOptions) error {
keypairs, total, e := cli.GetKeypairs()
if e != nil {
return e
}
printList(keypairs, total, 0, 0, []string{})
return nil
})
type KeyPairImportOptions struct {
NAME string `help:"Name of new keypair"`
PUBKEY string `help:"Public key string"`
}
shellutils.R(&KeyPairImportOptions{}, "keypair-import", "Import a keypair", func(cli *huawei.SRegion, args *KeyPairImportOptions) error {
keypair, err := cli.ImportKeypair(args.NAME, args.PUBKEY)
if err != nil {
return err
}
printObject(keypair)
return nil
})
}
+22
View File
@@ -0,0 +1,22 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type VSwitchListOptions struct {
Vpc string `help:"Vpc ID"`
Limit int `help:"page size"`
Marker string `help:"page marker"`
}
shellutils.R(&VSwitchListOptions{}, "vswitch-list", "List vswitches", func(cli *huawei.SRegion, args *VSwitchListOptions) error {
vswitches, total, e := cli.GetNetwroks(args.Vpc, args.Limit, args.Marker)
if e != nil {
return e
}
printList(vswitches, total, 0, args.Limit, []string{})
return nil
})
}
+11
View File
@@ -0,0 +1,11 @@
package shell
import "yunion.io/x/onecloud/pkg/util/printutils"
func printList(data interface{}, total, offset, limit int, columns []string) {
printutils.PrintInterfaceList(data, total, offset, limit, columns)
}
func printObject(obj interface{}) {
printutils.PrintInterfaceObject(obj)
}
+16
View File
@@ -0,0 +1,16 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type RegionListOptions struct {
}
shellutils.R(&RegionListOptions{}, "region-list", "List regions", func(cli *huawei.SRegion, args *RegionListOptions) error {
regions := cli.GetClient().GetRegions()
printList(regions, 0, 0, 0, nil)
return nil
})
}
+34
View File
@@ -0,0 +1,34 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type SecurityGroupListOptions struct {
VpcId string `help:"VPC ID"`
Limit int `help:"page size"`
Marker string `help:"page marker"`
}
shellutils.R(&SecurityGroupListOptions{}, "security-group-list", "List security group", func(cli *huawei.SRegion, args *SecurityGroupListOptions) error {
secgrps, total, e := cli.GetSecurityGroups(args.VpcId, args.Limit, args.Marker)
if e != nil {
return e
}
printList(secgrps, total, 0, 0, []string{})
return nil
})
type SecurityGroupShowOptions struct {
ID string `help:"ID or name of security group"`
}
shellutils.R(&SecurityGroupShowOptions{}, "security-group-show", "Show details of a security group", func(cli *huawei.SRegion, args *SecurityGroupShowOptions) error {
secgrp, err := cli.GetSecurityGroupDetails(args.ID)
if err != nil {
return err
}
printObject(secgrp)
return nil
})
}
+43
View File
@@ -0,0 +1,43 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type SnapshotListOptions struct {
DiskId string `help:"Disk ID"`
Name string `help:"Snapshot Name"`
Limit int `help:"page size"`
Offset int `help:"page offset"`
}
shellutils.R(&SnapshotListOptions{}, "snapshot-list", "List snapshot", func(cli *huawei.SRegion, args *SnapshotListOptions) error {
if snapshots, total, err := cli.GetSnapshots(args.DiskId, args.Name, args.Offset, args.Limit); err != nil {
return err
} else {
printList(snapshots, total, args.Offset, args.Limit, []string{})
return nil
}
})
type SnapshotDeleteOptions struct {
ID string `help:"Snapshot ID"`
}
shellutils.R(&SnapshotDeleteOptions{}, "snapshot-delete", "Delete snapshot", func(cli *huawei.SRegion, args *SnapshotDeleteOptions) error {
return cli.DeleteSnapshot(args.ID)
})
type SnapshotCreateOptions struct {
DiskId string `help:"Disk ID"`
Name string `help:"Snapeshot Name"`
Desc string `help:"Snapshot Desc"`
}
shellutils.R(&SnapshotCreateOptions{}, "snapshot-create", "Create snapshot", func(cli *huawei.SRegion, args *SnapshotCreateOptions) error {
_, err := cli.CreateSnapshot(args.DiskId, args.Name, args.Desc)
return err
})
}
+21
View File
@@ -0,0 +1,21 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type VpcListOptions struct {
Limit int `help:"page size"`
Marker string `help:"page marker"`
}
shellutils.R(&VpcListOptions{}, "vpc-list", "List vpcs", func(cli *huawei.SRegion, args *VpcListOptions) error {
vpcs, total, e := cli.GetVpcs(args.Limit, args.Marker)
if e != nil {
return e
}
printList(vpcs, total, 0, args.Limit, []string{})
return nil
})
}
+20
View File
@@ -0,0 +1,20 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/huawei"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func init() {
type ZoneListOptions struct {
}
shellutils.R(&ZoneListOptions{}, "zone-list", "List zones", func(cli *huawei.SRegion, args *ZoneListOptions) error {
zones, e := cli.GetIZones()
if e != nil {
return e
}
printList(zones, 0, 0, 0, []string{})
return nil
})
}
+20 -2
View File
@@ -120,6 +120,7 @@ func (self *SSnapshot) Delete() error {
return self.region.DeleteSnapshot(self.GetId())
}
// https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408627.html
func (self *SRegion) GetSnapshots(diskId string, snapshotName string, offset int, limit int) ([]SSnapshot, int, error) {
params := make(map[string]string)
params["limit"] = fmt.Sprintf("%d", limit)
@@ -147,7 +148,24 @@ func (self *SRegion) GetSnapshotById(snapshotId string) (SSnapshot, error) {
return snapshot, err
}
// 不能删除以autobk_snapshot_为前缀的快照。
// 当快照状态为available、error状态时,才可以删除。
func (self *SRegion) DeleteSnapshot(snapshotId string) error {
// todo: implement me
return nil
return DoDelete(self.ecsClient.Snapshots.Delete, snapshotId, nil, nil)
}
// https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408624.html
// 目前已设置force字段。云硬盘处于挂载状态时,能强制创建快照。
func (self *SRegion) CreateSnapshot(diskId, name, desc string) (string, error) {
params := jsonutils.NewDict()
snapshotObj := jsonutils.NewDict()
snapshotObj.Add(jsonutils.NewString(name), "name")
snapshotObj.Add(jsonutils.NewString(desc), "description")
snapshotObj.Add(jsonutils.NewString(diskId), "volume_id")
snapshotObj.Add(jsonutils.JSONTrue, "force")
params.Add(snapshotObj, "snapshot")
snapshot := SSnapshot{}
err := DoCreate(self.ecsClient.Snapshots.Create, params, &snapshot)
return snapshot.ID, err
}
+13 -3
View File
@@ -90,7 +90,7 @@ func (self *SStorage) GetStorageType() string {
}
func (self *SStorage) GetMediumType() string {
if self.storageType == models.STORAGE_GP2_SSD || self.storageType == models.STORAGE_IO1_SSD {
if self.storageType == models.STORAGE_HUAWEI_SSD {
return models.DISK_TYPE_SSD
} else {
return models.DISK_TYPE_ROTATE
@@ -115,8 +115,18 @@ func (self *SStorage) GetManagerId() string {
}
func (self *SStorage) CreateIDisk(name string, sizeGb int, desc string) (cloudprovider.ICloudDisk, error) {
// todo: implement me
return nil, nil
diskId, err := self.zone.region.CreateDisk(self.zone.GetId(), self.storageType, name, sizeGb, "", desc)
if err != nil {
log.Errorf("createDisk fail %s", err)
return nil, err
}
disk, err := self.zone.region.GetDisk(diskId)
if err != nil {
log.Errorf("getDisk fail %s", err)
return nil, err
}
disk.storage = self
return disk, nil
}
func (self *SStorage) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) {
+202 -3
View File
@@ -2,11 +2,21 @@ package huawei
import (
"context"
"strings"
"time"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/huawei/obs"
"yunion.io/x/onecloud/pkg/util/qemuimg"
)
type SStoragecache struct {
@@ -15,6 +25,10 @@ type SStoragecache struct {
iimages []cloudprovider.ICloudImage
}
func GetBucketName(regionId string, imageId string) string {
return fmt.Sprintf("imgcache-%s-%s", strings.ToLower(regionId), imageId)
}
func (self *SStoragecache) fetchImages() error {
limit := 100
marker := ""
@@ -92,16 +106,139 @@ func (self *SStoragecache) GetManagerId() string {
return self.region.client.providerId
}
// 目前支持使用vhd、zvhd、vmdk、qcow2、raw、zvhd2、vhdx、qcow、vdi或qed格式镜像文件创建私有镜像。
// 快速通道功能可快速完成镜像制作,但镜像文件需转换为raw或zvhd2格式并完成镜像优化。
// https://support.huaweicloud.com/api-ims/zh-cn_topic_0083905788.html
func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
panic("implement me")
if imageId, err := self.region.createIImage(snapshotId, imageName, imageDesc); err != nil {
return nil, err
} else if image, err := self.region.GetImage(imageId); err != nil {
return nil, err
} else {
image.storageCache = self
iimage := make([]cloudprovider.ICloudImage, 1)
iimage[0] = &image
if err := cloudprovider.WaitStatus(iimage[0], "avaliable", 15*time.Second, 3600*time.Second); err != nil {
return nil, err
}
return iimage[0], nil
}
}
func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) {
panic("implement me")
return self.downloadImage(userCred, imageId, extId)
}
func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imageId string, extId string) (jsonutils.JSONObject, error) {
return nil, cloudprovider.ErrNotImplemented
}
func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, imageId string, osArch, osType, osDist, osVersion string, extId string, isForce bool) (string, error) {
panic("implement me")
if len(extId) > 0 {
log.Debugf("UploadImage: Image external ID exists %s", extId)
image, err := self.region.GetImage(extId)
if err != nil {
log.Errorf("GetImageStatus error %s", err)
}
if image.Status == ImageStatusActive && !isForce {
return extId, nil
}
} else {
log.Debugf("UploadImage: no external ID")
}
return self.uploadImage(ctx, userCred, imageId, osArch, osType, osDist, osVersion, isForce)
}
func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, imageId string, osArch, osType, osDist string, osVersion string, isForce bool) (string, error) {
bucketName := GetBucketName(self.region.GetId(), imageId)
obsClient, err := self.region.getOBSClient()
if err != nil {
return "", err
}
// create bucket
input := &obs.CreateBucketInput{}
input.Bucket = bucketName
_, err = obsClient.CreateBucket(input)
if err != nil {
return "", err
}
defer obsClient.DeleteBucket(bucketName)
// upload to huawei cloud
s := auth.GetAdminSession(ctx, options.Options.Region, "")
meta, reader, err := modules.Images.Download(s, imageId, string(qemuimg.VMDK), false)
if err != nil {
return "", err
}
log.Debugf("Images meta data %s", meta)
_image, err := modules.Images.Get(s, imageId, nil)
if err != nil {
return "", err
}
minDiskGB, _ := _image.Int("min_disk")
if minDiskGB <= 0 {
minDiskGB = 40
}
// upload to huawei cloud
obj := &obs.PutObjectInput{}
obj.Bucket = bucketName
obj.Key = imageId
obj.Body = reader
_, err = obsClient.PutObject(obj)
if err != nil {
return "", err
}
objDelete := &obs.DeleteObjectInput{}
objDelete.Bucket = bucketName
objDelete.Key = imageId
defer obsClient.DeleteObject(objDelete) // remove object
// check image name, avoid name conflict
imageBaseName := imageId
if imageBaseName[0] >= '0' && imageBaseName[0] <= '9' {
imageBaseName = fmt.Sprintf("img%s", imageId)
}
imageName := imageBaseName
nameIdx := 1
for {
_, err = self.region.GetImageByName(imageName)
if err != nil {
if err == cloudprovider.ErrNotFound {
break
} else {
return "", err
}
}
imageName = fmt.Sprintf("%s-%d", imageBaseName, nameIdx)
nameIdx += 1
log.Debugf("uploadImage Match remote name %s", imageName)
}
jobId, err := self.region.ImportImageJob(imageName, osDist, osVersion, osArch, bucketName, imageId, minDiskGB)
if err != nil {
log.Errorf("ImportImage error %s %s %s %s", jobId, imageId, bucketName, err)
return "", err
}
// timeout: 1hour = 3600 seconds
serviceType := self.region.ecsClient.Images.ServiceType()
err = self.region.waitTaskStatus(serviceType, jobId, TASK_SUCCESS, 15*time.Second, 3600*time.Second)
if err != nil {
log.Errorf("waitTaskStatus %s", err)
return "", err
}
// https://support.huaweicloud.com/api-ims/zh-cn_topic_0022473688.html
return self.region.GetTaskEntityID(serviceType, jobId, "image_id")
}
func (self *SRegion) getStoragecache() *SStoragecache {
@@ -111,6 +248,68 @@ func (self *SRegion) getStoragecache() *SStoragecache {
return self.storageCache
}
type SJob struct {
Status string `json:"status"`
Entities map[string]string `json:"entities"`
JobID string `json:"job_id"`
JobType string `json:"job_type"`
BeginTime string `json:"begin_time"`
EndTime string `json:"end_time"`
ErrorCode string `json:"error_code"`
FailReason string `json:"fail_reason"`
}
// https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092109.html
func (self *SRegion) createIImage(snapshotId, imageName, imageDesc string) (string, error) {
snapshot, err := self.GetSnapshotById(snapshotId)
if err != nil {
return "", err
}
disk, err := self.GetDisk(snapshot.VolumeID)
if err != nil {
return "", err
}
if disk.GetDiskType() != models.DISK_TYPE_SYS {
return "", fmt.Errorf("disk type err, expected disk type %s", models.DISK_TYPE_SYS)
}
if len(disk.Attachments) == 0 {
return "", fmt.Errorf("disk is not attached.")
}
imageObj := jsonutils.NewDict()
imageObj.Add(jsonutils.NewString(disk.Attachments[0].ServerID), "instance_id")
imageObj.Add(jsonutils.NewString(imageName), "name")
imageObj.Add(jsonutils.NewString(imageDesc), "description")
ret, err := self.ecsClient.Images.PerformAction2("action", "", imageObj, "")
if err != nil {
return "", err
}
job := SJob{}
jobId, err := ret.GetString("job_id")
querys := map[string]string{"service_type": self.ecsClient.Images.ServiceType()}
err = DoGet(self.ecsClient.Jobs.Get, jobId, querys, &job)
if err != nil {
return "", err
}
if job.Status == "SUCCESS" {
imageId, exists := job.Entities["image_id"]
if exists {
return imageId, nil
} else {
return "", fmt.Errorf("image id not found in create image job %s", job.JobID)
}
} else {
return "", fmt.Errorf("create image failed, %s", job.FailReason)
}
}
func (self *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) {
storageCache := self.getStoragecache()
return []cloudprovider.ICloudStoragecache{storageCache}, nil
+80
View File
@@ -0,0 +1,80 @@
package huawei
import (
"time"
)
func (self *SRegion) waitTaskStatus(serviceType string, taskId string, targetStatus string, interval time.Duration, timeout time.Duration) error {
start := time.Now()
for time.Now().Sub(start) < timeout {
status, err := self.GetTaskStatus(serviceType, taskId)
if err != nil {
return err
}
if status == targetStatus {
break
} else {
time.Sleep(interval)
}
}
return nil
}
func (self *SRegion) GetTaskStatus(serviceType string, taskId string) (string, error) {
querys := map[string]string{"service_type": serviceType}
task, err := self.ecsClient.Jobs.Get(taskId, querys)
if err != nil {
return "", err
}
return task.GetString("status")
}
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022225398.html
// 数据结构 entities -> []job
func (self *SRegion) GetAllSubTaskEntityIDs(serviceType string, taskId string, entityKeyName string) ([]string, error) {
err := self.waitTaskStatus(serviceType, taskId, TASK_SUCCESS, 10*time.Second, 600*time.Second)
if err != nil {
return nil, err
}
querys := map[string]string{"service_type": serviceType}
ret, err := self.ecsClient.Jobs.Get(taskId, querys)
if err != nil {
return nil, err
}
entities, err := ret.GetArray("entities", "sub_jobs")
if err != nil {
return nil, err
}
ids := make([]string, 0)
for i := range entities {
entity := entities[i]
rid, err := entity.GetString("entities", entityKeyName)
if err != nil {
return nil, err
}
ids = append(ids, rid)
}
return ids, nil
}
// 数据结构 entities -> job
func (self *SRegion) GetTaskEntityID(serviceType string, taskId string, entityKeyName string) (string, error) {
err := self.waitTaskStatus(serviceType, taskId, TASK_SUCCESS, 10*time.Second, 600*time.Second)
if err != nil {
return "", err
}
querys := map[string]string{"service_type": serviceType}
ret, err := self.ecsClient.Jobs.Get(taskId, querys)
if err != nil {
return "", err
}
return ret.GetString("entities", entityKeyName)
}
+60 -14
View File
@@ -5,6 +5,8 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/huawei/client/manager"
"yunion.io/x/onecloud/pkg/util/huawei/client/responses"
)
@@ -12,7 +14,37 @@ import (
// 常用的方法
type listFunc func(querys map[string]string) (*responses.ListResult, error)
type getFunc func(id string, querys map[string]string) (jsonutils.JSONObject, error)
type listInCtxFunc func(ctx *manager.ManagerContext, spec string, querys map[string]string) (*responses.ListResult, error)
type createFunc func(params jsonutils.JSONObject) (jsonutils.JSONObject, error)
type updateFunc func(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
type updateFunc2 func(ctx manager.IManagerContext, id string, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error)
type deleteFunc func(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error)
type listInCtxFunc func(ctx manager.IManagerContext, querys map[string]string) (*responses.ListResult, error)
type listInCtxWithSpecFunc func(ctx manager.IManagerContext, spec string, querys map[string]string, responseKey string) (*responses.ListResult, error)
func unmarshalResult(resp jsonutils.JSONObject, respErr error, result interface{}) error {
if respErr != nil {
switch e := respErr.(type) {
case *httputils.JSONClientError:
if e.Code == 404 {
return cloudprovider.ErrNotFound
}
return e
default:
return e
}
}
if result == nil {
return nil
}
err := resp.Unmarshal(result)
if err != nil {
log.Errorf("unmarshal json error %s", err)
}
return err
}
func DoList(doList listFunc, querys map[string]string, result interface{}) error {
ret, err := doList(querys)
@@ -36,21 +68,11 @@ func DoGet(doGet getFunc, id string, querys map[string]string, result interface{
}
ret, err := doGet(id, querys)
if err != nil {
return err
}
err = ret.Unmarshal(result)
if err != nil {
log.Errorf("unmarshal json error %s", err)
return err
}
return nil
return unmarshalResult(ret, err, result)
}
func DoListInContext(listFunc listInCtxFunc, ctx *manager.ManagerContext, querys map[string]string, result interface{}) error {
ret, err := listFunc(ctx, "", querys)
func DoListInContext(listFunc listInCtxFunc, ctx manager.IManagerContext, querys map[string]string, result interface{}) error {
ret, err := listFunc(ctx, querys)
if err != nil {
return err
}
@@ -64,3 +86,27 @@ func DoListInContext(listFunc listInCtxFunc, ctx *manager.ManagerContext, querys
return nil
}
func DoCreate(createFunc createFunc, params jsonutils.JSONObject, result interface{}) error {
ret, err := createFunc(params)
return unmarshalResult(ret, err, result)
}
func DoUpdate(updateFunc updateFunc, id string, params jsonutils.JSONObject, result interface{}) error {
ret, err := updateFunc(id, params)
return unmarshalResult(ret, err, result)
}
func DoUpdateWithSpec(updateFunc updateFunc2, id string, spec string, params jsonutils.JSONObject) error {
_, err := updateFunc(nil, id, spec, params, "")
return err
}
func DoDelete(deleteFunc deleteFunc, id string, params jsonutils.JSONObject, result interface{}) error {
if len(id) == 0 {
return fmt.Errorf(" id should not be empty")
}
ret, err := deleteFunc(id, params)
return unmarshalResult(ret, err, result)
}
+42 -8
View File
@@ -1,6 +1,8 @@
package huawei
import (
"strconv"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
@@ -27,15 +29,15 @@ func (self *SVpc) addWire(wire *SWire) {
self.iwires = append(self.iwires, wire)
}
func (self *SVpc) getWireByZoneId(zoneId string) *SWire {
func (self *SVpc) getWireByRegionId(regionId string) *SWire {
if len(regionId) == 0 {
return nil
}
for i := 0; i < len(self.iwires); i++ {
wire := self.iwires[i].(*SWire)
if zoneId == "" {
return wire
}
if wire.zone.ZoneName == zoneId {
if wire.region.GetId() == regionId {
return wire
}
}
@@ -61,8 +63,13 @@ func (self *SVpc) fetchNetworks() error {
marker = parts[count-1].ID
}
if len(networks) == 0 {
self.iwires = append(self.iwires, &SWire{region: self.region, vpc: self})
return nil
}
for i := 0; i < len(networks); i += 1 {
wire := self.getWireByZoneId(networks[i].AvailabilityZone)
wire := self.getWireByRegionId(self.region.GetId())
networks[i].wire = wire
wire.addNetwork(&networks[i])
}
@@ -90,6 +97,7 @@ func (self *SVpc) fetchSecurityGroups() error {
}
self.secgroups = make([]cloudprovider.ICloudSecurityGroup, len(secgroups))
// 这里已经填充了vpc。 所以是不是不需要在GetSecurityGroups方法中填充vpc和region了?
for i := 0; i < len(secgroups); i++ {
secgroups[i].vpc = self
self.secgroups[i] = &secgroups[i]
@@ -175,7 +183,8 @@ func (self *SVpc) GetManagerId() string {
}
func (self *SVpc) Delete() error {
panic("implement me")
// todo: 确定删除VPC的逻辑
return self.region.DeleteVpc(self.GetId())
}
func (self *SVpc) GetIWireById(wireId string) (cloudprovider.ICloudWire, error) {
@@ -196,5 +205,30 @@ func (self *SVpc) GetIWireById(wireId string) (cloudprovider.ICloudWire, error)
func (self *SRegion) getVpc(vpcId string) (*SVpc, error) {
vpc := SVpc{}
err := DoGet(self.ecsClient.Vpcs.Get, vpcId, nil, &vpc)
vpc.region = self
return &vpc, err
}
func (self *SRegion) DeleteVpc(vpcId string) error {
return DoDelete(self.ecsClient.Vpcs.Delete, vpcId, nil, nil)
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090625.html
func (self *SRegion) GetVpcs(limit int, marker string) ([]SVpc, int, error) {
querys := map[string]string{"limit": "100"}
if limit > 0 {
querys["limit"] = strconv.Itoa(limit)
}
if len(marker) > 0 {
querys["marker"] = marker
}
vpcs := make([]SVpc, 0)
err := DoList(self.ecsClient.Vpcs.List, querys, &vpcs)
for i := range vpcs {
vpcs[i].region = self
}
return vpcs, len(vpcs), err
}
+77 -6
View File
@@ -2,20 +2,24 @@ package huawei
import (
"fmt"
"net"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
// 华为云的子网有点特殊。子网在整个region可用。
type SWire struct {
zone *SZone
vpc *SVpc
region *SRegion
vpc *SVpc
inetworks []cloudprovider.ICloudNetwork
}
func (self *SWire) GetId() string {
return fmt.Sprintf("%s-%s", self.vpc.GetId(), self.zone.GetId())
return fmt.Sprintf("%s-%s", self.vpc.GetId(), self.region.GetId())
}
func (self *SWire) GetName() string {
@@ -23,7 +27,7 @@ func (self *SWire) GetName() string {
}
func (self *SWire) GetGlobalId() string {
return fmt.Sprintf("%s-%s", self.vpc.GetGlobalId(), self.zone.GetGlobalId())
return fmt.Sprintf("%s-%s", self.vpc.GetGlobalId(), self.region.GetGlobalId())
}
func (self *SWire) GetStatus() string {
@@ -47,7 +51,7 @@ func (self *SWire) GetIVpc() cloudprovider.ICloudVpc {
}
func (self *SWire) GetIZone() cloudprovider.ICloudZone {
return self.zone
return nil
}
func (self *SWire) GetINetworks() ([]cloudprovider.ICloudNetwork, error) {
@@ -77,8 +81,35 @@ func (self *SWire) GetINetworkById(netid string) (cloudprovider.ICloudNetwork, e
return nil, cloudprovider.ErrNotFound
}
/*
华为云子网可用区,类似一个zone标签。即使指定了zone子网在整个region依然是可用。
通过华为web控制台创建子网需要指定可用区。这里是不指定的。
*/
func (self *SWire) CreateINetwork(name string, cidr string, desc string) (cloudprovider.ICloudNetwork, error) {
panic("implement me")
networkId, err := self.region.createNetwork(self.vpc.GetId(), name, cidr, desc)
if err != nil {
log.Errorf("createNetwork error %s", err)
return nil, err
}
var network *SNetwork
err = cloudprovider.WaitCreated(5*time.Second, 60*time.Second, func() bool {
self.inetworks = nil
network = self.getNetworkById(networkId)
if network == nil {
return false
} else {
return true
}
})
if err != nil {
log.Errorf("cannot find network after create????")
return nil, err
}
network.wire = self
return network, nil
}
func (self *SWire) addNetwork(network *SNetwork) {
@@ -112,3 +143,43 @@ func (self *SWire) getNetworkById(networkId string) *SNetwork {
}
return nil
}
func getDefaultGateWay(cidr string) (string, error) {
ip, _, err := net.ParseCIDR(cidr)
if err != nil {
return "", err
}
ipv4 := ip.To4()
if ipv4 == nil || len(ip.String()) == net.IPv6len {
return "", fmt.Errorf("ipv6 is not supported currently")
}
if ipv4[3] != 0 {
return "", fmt.Errorf("the last byte of ip address must be zero. e.g 192.168.0.0/16")
}
ipv4[3] = 1
return ipv4.String(), nil
}
// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090590.html
// cidr 掩码长度不能大于28
func (self *SRegion) createNetwork(vpcId string, name string, cidr string, desc string) (string, error) {
gateway, err := getDefaultGateWay(cidr)
if err != nil {
return "", err
}
params := jsonutils.NewDict()
subnetObj := jsonutils.NewDict()
subnetObj.Add(jsonutils.NewString(name), "name")
subnetObj.Add(jsonutils.NewString(vpcId), "vpc_id")
subnetObj.Add(jsonutils.NewString(cidr), "cidr")
subnetObj.Add(jsonutils.NewString(gateway), "gateway_ip")
params.Add(subnetObj, "subnet")
subnet := SNetwork{}
err = DoCreate(self.ecsClient.Subnets.Create, params, &subnet)
return subnet.ID, err
}
+11
View File
@@ -158,3 +158,14 @@ func (self *SRegion) getZoneById(id string) (*SZone, error) {
}
return nil, fmt.Errorf("no such zone %s", id)
}
func (self *SZone) getNetworkById(networkId string) *SNetwork {
for i := 0; i < len(self.iwires); i += 1 {
wire := self.iwires[i].(*SWire)
net := wire.getNetworkById(networkId)
if net != nil {
return net
}
}
return nil
}
+7 -5
View File
@@ -97,11 +97,12 @@ func normalizeOsVersion(imageName string, osDist string, osVersion string) strin
}
type ImageInfo struct {
Name string
OsArch string
OsType string
OsDistro string
OsVersion string
Name string
OsArch string
OsType string
OsDistro string
OsVersion string
OsFullVersion string
}
func NormalizeImageInfo(imageName, osArch, osType, osDist, osVersion string) ImageInfo {
@@ -111,5 +112,6 @@ func NormalizeImageInfo(imageName, osArch, osType, osDist, osVersion string) Ima
info.OsType = normalizeOsType(osType, info.OsDistro)
info.OsArch = normalizeOsArch(osArch, info.OsType, info.OsDistro)
info.OsVersion = normalizeOsVersion(imageName, info.OsDistro, osVersion)
info.OsFullVersion = osVersion
return info
}
+1 -1
View File
@@ -273,7 +273,7 @@ func (region *SRegion) GetIEips() ([]cloudprovider.ICloudEIP, error) {
return nil, cloudprovider.ErrNotImplemented
}
func (region *SRegion) CreateEIP(name string, bwMbps int, chargeType string) (cloudprovider.ICloudEIP, error) {
func (region *SRegion) CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (cloudprovider.ICloudEIP, error) {
return nil, cloudprovider.ErrNotSupported
}
+1 -1
View File
@@ -266,7 +266,7 @@ func (region *SRegion) AllocateEIP(name string, bwMbps int, chargeType TInternet
return nil, cloudprovider.ErrNotFound
}
func (region *SRegion) CreateEIP(name string, bwMbps int, chargeType string) (cloudprovider.ICloudEIP, error) {
func (region *SRegion) CreateEIP(name string, bwMbps int, chargeType string, bgpType string) (cloudprovider.ICloudEIP, error) {
var ctype TInternetChargeType
switch chargeType {
case models.EIP_CHARGE_TYPE_BY_TRAFFIC: