mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-01 15:07:17 +08:00
feature: 1. add ceph cloud provider based on generic s3 provider 2.
climc support AK/SK authentication
This commit is contained in:
+28
-18
@@ -49,14 +49,19 @@ type BaseOptions struct {
|
||||
|
||||
Completion string `default:"" help:"Generate climc auto complete script" choices:"bash"`
|
||||
UseCachedToken bool `default:"$YUNION_USE_CACHED_TOKEN|false" help:"Use cached token"`
|
||||
OsUsername string `default:"$OS_USERNAME" help:"Username, defaults to env[OS_USERNAME]"`
|
||||
OsPassword string `default:"$OS_PASSWORD" help:"Password, defaults to env[OS_PASSWORD]"`
|
||||
|
||||
OsUsername string `default:"$OS_USERNAME" help:"Username, defaults to env[OS_USERNAME]"`
|
||||
OsPassword string `default:"$OS_PASSWORD" help:"Password, defaults to env[OS_PASSWORD]"`
|
||||
// OsProjectId string `default:"$OS_PROJECT_ID" help:"Proejct ID, defaults to env[OS_PROJECT_ID]"`
|
||||
OsProjectName string `default:"$OS_PROJECT_NAME" help:"Project name, defaults to env[OS_PROJECT_NAME]"`
|
||||
OsProjectDomain string `default:"$OS_PROJECT_DOMAIN" help:"Domain name of project, defaults to env[OS_PROJECT_DOMAIN]"`
|
||||
OsDomainName string `default:"$OS_DOMAIN_NAME" help:"Domain name, defaults to env[OS_DOMAIN_NAME]"`
|
||||
|
||||
OsAccessKey string `default:"$OS_ACCESS_KEY" help:"ak/sk access key, defaults to env[OS_ACCESS_KEY]"`
|
||||
OsSecretKey string `default:"$OS_SECRET_KEY" help:"ak/s secret, defaults to env[OS_SECRET_KEY]"`
|
||||
|
||||
OsAuthURL string `default:"$OS_AUTH_URL" help:"Defaults to env[OS_AUTH_URL]"`
|
||||
|
||||
OsDomainName string `default:"$OS_DOMAIN_NAME" help:"Domain name, defaults to env[OS_DOMAIN_NAME]"`
|
||||
OsAuthURL string `default:"$OS_AUTH_URL" help:"Defaults to env[OS_AUTH_URL]"`
|
||||
OsRegionName string `default:"$OS_REGION_NAME" help:"Defaults to env[OS_REGION_NAME]"`
|
||||
OsZoneName string `default:"$OS_ZONE_NAME" help:"Defaults to env[OS_ZONE_NAME]"`
|
||||
OsEndpointType string `default:"$OS_ENDPOINT_TYPE|internalURL" help:"Defaults to env[OS_ENDPOINT_TYPE] or internalURL" choices:"publicURL|internalURL|adminURL"`
|
||||
@@ -124,20 +129,18 @@ func newClientSession(options *BaseOptions) (*mcclient.ClientSession, error) {
|
||||
if len(options.OsAuthURL) == 0 {
|
||||
return nil, fmt.Errorf("Missing OS_AUTH_URL")
|
||||
}
|
||||
if len(options.OsUsername) == 0 {
|
||||
return nil, fmt.Errorf("Missing OS_USERNAME")
|
||||
if len(options.OsUsername) == 0 && len(options.OsAccessKey) == 0 {
|
||||
return nil, fmt.Errorf("Missing OS_USERNAME or OS_ACCESS_KEY")
|
||||
}
|
||||
if len(options.OsPassword) == 0 {
|
||||
if len(options.OsUsername) > 0 && len(options.OsPassword) == 0 {
|
||||
return nil, fmt.Errorf("Missing OS_PASSWORD")
|
||||
}
|
||||
if len(options.OsAccessKey) > 0 && len(options.OsSecretKey) == 0 {
|
||||
return nil, fmt.Errorf("Missing OS_SECRET_KEY")
|
||||
}
|
||||
if len(options.OsRegionName) == 0 {
|
||||
return nil, fmt.Errorf("Missing OS_REGION_NAME")
|
||||
}
|
||||
// if len(options.OsProjectId) == 0 && len(options.OsProjectName) == 0 {
|
||||
// showErrorAndExit(fmt.Errorf("Missing OS_PROEJCT_ID or OS_PROJECT_NAME"))
|
||||
// if len(options.OsProjectName) == 0 {
|
||||
// return nil, fmt.Errorf("Missing OS_PROJECT_NAME")
|
||||
// }
|
||||
|
||||
logLevel := "info"
|
||||
if options.Debug {
|
||||
@@ -178,12 +181,19 @@ func newClientSession(options *BaseOptions) (*mcclient.ClientSession, error) {
|
||||
}
|
||||
|
||||
if cacheToken == nil {
|
||||
token, err := client.AuthenticateWithSource(options.OsUsername,
|
||||
options.OsPassword,
|
||||
options.OsDomainName,
|
||||
options.OsProjectName,
|
||||
options.OsProjectDomain,
|
||||
mcclient.AuthSourceCli)
|
||||
var token mcclient.TokenCredential
|
||||
var err error
|
||||
if len(options.OsAccessKey) > 0 {
|
||||
token, err = client.AuthenticateByAccessKey(options.OsAccessKey,
|
||||
options.OsSecretKey, mcclient.AuthSourceCli)
|
||||
} else {
|
||||
token, err = client.AuthenticateWithSource(options.OsUsername,
|
||||
options.OsPassword,
|
||||
options.OsDomainName,
|
||||
options.OsProjectName,
|
||||
options.OsProjectDomain,
|
||||
mcclient.AuthSourceCli)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -189,6 +189,17 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&options.SS3CloudAccountCreateOptions{}, "cloud-account-create-ceph", "Create a ceph object storage account", func(s *mcclient.ClientSession, args *options.SS3CloudAccountCreateOptions) error {
|
||||
params := jsonutils.Marshal(args)
|
||||
params.(*jsonutils.JSONDict).Add(jsonutils.NewString("Ceph"), "provider")
|
||||
result, err := modules.Cloudaccounts.Create(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
|
||||
type CloudaccountUpdateOptions struct {
|
||||
ID string `help:"ID or Name of cloud account"`
|
||||
Name string `help:"New name to update"`
|
||||
|
||||
@@ -185,9 +185,13 @@ func init() {
|
||||
})
|
||||
|
||||
R(&CredentialAkSkOptions{}, "credential-get-aksk", "Get AccessKey/Secret credential for user and project", func(s *mcclient.ClientSession, args *CredentialAkSkOptions) error {
|
||||
uid, err := modules.UsersV3.FetchId(s, args.User, args.UserDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
var uid string
|
||||
var err error
|
||||
if len(args.User) > 0 {
|
||||
uid, err = modules.UsersV3.FetchId(s, args.User, args.UserDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var pid string
|
||||
if len(args.Project) > 0 {
|
||||
|
||||
+9
-2
@@ -20,7 +20,10 @@ import (
|
||||
|
||||
"yunion.io/x/structarg"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/objectstore"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/objectstore/shell"
|
||||
"yunion.io/x/onecloud/pkg/util/shellutils"
|
||||
)
|
||||
@@ -31,6 +34,7 @@ type BaseOptions struct {
|
||||
AccessUrl string `help:"Access url" default:"$S3_ACCESS_URL"`
|
||||
AccessKey string `help:"Access key" default:"$S3_ACCESS_KEY"`
|
||||
Secret string `help:"Secret" default:"$S3_SECRET"`
|
||||
Backend string `help:"Backend driver" default:"$S3_BACKEND"`
|
||||
SUBCOMMAND string `help:"s3cli subcommand" subcommand:"true"`
|
||||
}
|
||||
|
||||
@@ -75,7 +79,7 @@ func showErrorAndExit(e error) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func newClient(options *BaseOptions) (*objectstore.SObjectStoreClient, error) {
|
||||
func newClient(options *BaseOptions) (cloudprovider.ICloudRegion, error) {
|
||||
if len(options.AccessUrl) == 0 {
|
||||
return nil, fmt.Errorf("Missing accessUrl")
|
||||
}
|
||||
@@ -88,6 +92,9 @@ func newClient(options *BaseOptions) (*objectstore.SObjectStoreClient, error) {
|
||||
return nil, fmt.Errorf("Missing secret")
|
||||
}
|
||||
|
||||
if options.Backend == api.CLOUD_PROVIDER_CEPH {
|
||||
return ceph.NewCephRados("", "", options.AccessUrl, options.AccessKey, options.Secret, options.Debug)
|
||||
}
|
||||
return objectstore.NewObjectStoreClient("", "", options.AccessUrl, options.AccessKey, options.Secret, options.Debug)
|
||||
}
|
||||
|
||||
@@ -116,7 +123,7 @@ func main() {
|
||||
if options.SUBCOMMAND == "help" {
|
||||
e = subcmd.Invoke(suboptions)
|
||||
} else {
|
||||
var client *objectstore.SObjectStoreClient
|
||||
var client cloudprovider.ICloudRegion
|
||||
client, e = newClient(options)
|
||||
if e != nil {
|
||||
showErrorAndExit(e)
|
||||
|
||||
@@ -41,6 +41,7 @@ const (
|
||||
CLOUD_PROVIDER_ZSTACK = "ZStack"
|
||||
|
||||
CLOUD_PROVIDER_GENERICS3 = "S3"
|
||||
CLOUD_PROVIDER_CEPH = "Ceph"
|
||||
|
||||
CLOUD_PROVIDER_HEALTH_NORMAL = "normal" // 远端处于健康状态
|
||||
CLOUD_PROVIDER_HEALTH_INSUFFICIENT = "insufficient" // 不足按需资源余额
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package compute
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
|
||||
@@ -16,13 +16,13 @@ package cloudprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/s3cli"
|
||||
|
||||
@@ -37,8 +37,10 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/compute/options"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"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/logclient"
|
||||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||||
@@ -488,10 +490,36 @@ func (bucket *SBucket) GetExtraDetails(ctx context.Context, userCred mcclient.To
|
||||
return bucket.getMoreDetails(extra), nil
|
||||
}
|
||||
|
||||
func joinPath(ep, path string) string {
|
||||
return strings.TrimRight(ep, "/") + "/" + strings.TrimLeft(path, "/")
|
||||
}
|
||||
|
||||
func (bucket *SBucket) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict {
|
||||
info := bucket.getCloudProviderInfo()
|
||||
extra.Update(jsonutils.Marshal(&info))
|
||||
|
||||
s3gwUrl, _ := auth.GetServiceURL("s3gateway", options.Options.Region, "", "public")
|
||||
if len(s3gwUrl) > 0 {
|
||||
accessUrls := make([]cloudprovider.SBucketAccessUrl, 0)
|
||||
err := bucket.AccessUrls.Unmarshal(&accessUrls)
|
||||
if err == nil {
|
||||
find := false
|
||||
for i := range accessUrls {
|
||||
if strings.HasPrefix(accessUrls[i].Url, s3gwUrl) {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !find {
|
||||
accessUrls = append(accessUrls, cloudprovider.SBucketAccessUrl{
|
||||
Url: joinPath(s3gwUrl, bucket.Name),
|
||||
Description: "s3gateway",
|
||||
})
|
||||
extra.Set("access_urls", jsonutils.Marshal(accessUrls))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extra
|
||||
}
|
||||
|
||||
|
||||
@@ -34,16 +34,8 @@ import (
|
||||
_ "yunion.io/x/onecloud/pkg/compute/regiondrivers"
|
||||
_ "yunion.io/x/onecloud/pkg/compute/storagedrivers"
|
||||
_ "yunion.io/x/onecloud/pkg/compute/tasks"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/aliyun/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/aws/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/azure/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/esxi/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/huawei/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/openstack/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/qcloud/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/ucloud/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/zstack/provider"
|
||||
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/loader"
|
||||
)
|
||||
|
||||
func StartService() {
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"io"
|
||||
"strings"
|
||||
api "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/util/netutils2"
|
||||
"yunion.io/x/onecloud/pkg/util/s3auth"
|
||||
@@ -70,3 +72,38 @@ func (this *Client) VerifyRequest(req http.Request, aksk s3auth.IAccessKeySecret
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func jsonReader(input interface{}) io.Reader {
|
||||
return strings.NewReader(jsonutils.Marshal(input).String())
|
||||
}
|
||||
|
||||
func (this *Client) AuthenticateByAccessKey(accessKey string, secret string, source string) (TokenCredential, error) {
|
||||
aCtx := SAuthContext{Source: source}
|
||||
|
||||
seedAksk := s3auth.NewV4Request()
|
||||
seedAksk.AccessKey = accessKey
|
||||
seedAksk.Location = "cn-beijing"
|
||||
input := SAuthenticationInputV3{}
|
||||
input.Auth.Identity.Methods = []string{api.AUTH_METHOD_AKSK}
|
||||
input.Auth.Identity.AccessKeyRequest = seedAksk.Encode()
|
||||
input.Auth.Context = aCtx
|
||||
|
||||
urlStr := joinUrl(this.authUrl, "/auth/tokens")
|
||||
req, err := http.NewRequest(http.MethodPost, urlStr, jsonReader(input))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "http.NewRequest")
|
||||
}
|
||||
newreq := s3auth.SignV4(*req, seedAksk.AccessKey, secret, seedAksk.Location, jsonReader(input))
|
||||
|
||||
aksk, err := s3auth.DecodeAccessKeyRequest(*newreq, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "s3auth.DecodeAccessKeyRequest")
|
||||
}
|
||||
|
||||
token, err := this._verifyKeySecret(aksk, aCtx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "this._verifyKeySecret")
|
||||
}
|
||||
|
||||
return token.Token, nil
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ type SZStackCloudAccountCreateOptions struct {
|
||||
type SS3CloudAccountCreateOptions struct {
|
||||
SCloudAccountCreateBaseOptions
|
||||
SAccessKeyCredential
|
||||
Endpoint string `help:"S3 endpoint" poisitional:"true" json:"endpoint"`
|
||||
Endpoint string `help:"S3 endpoint" required:"true" positional:"true" json:"endpoint"`
|
||||
}
|
||||
|
||||
// update credential options
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
"yunion.io/x/log"
|
||||
|
||||
// on-premise virtualization technologies
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/esxi/provider"
|
||||
|
||||
// private clouds
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/openstack/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/zstack/provider"
|
||||
|
||||
// public clouds
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/aliyun/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/aws/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/azure/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/huawei/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/qcloud/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/ucloud/provider"
|
||||
|
||||
// object storages
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.Infof("Loading cloud providers ...")
|
||||
}
|
||||
@@ -18,14 +18,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/s3cli"
|
||||
|
||||
"net/http"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/multicloud"
|
||||
)
|
||||
@@ -33,13 +33,16 @@ import (
|
||||
type SBucket struct {
|
||||
multicloud.SBaseBucket
|
||||
|
||||
client *SObjectStoreClient
|
||||
client IBucketProvider
|
||||
|
||||
Name string
|
||||
Location string
|
||||
CreatedAt time.Time
|
||||
StorageClass string
|
||||
Acl string
|
||||
}
|
||||
|
||||
func (bucket *SBucket) GetIBucketProvider() IBucketProvider {
|
||||
return bucket.client
|
||||
}
|
||||
|
||||
func (bucket *SBucket) GetId() string {
|
||||
@@ -104,10 +107,14 @@ func (bucket *SBucket) GetStats() cloudprovider.SBucketStats {
|
||||
return stats
|
||||
}
|
||||
|
||||
func joinPath(ep, path string) string {
|
||||
return strings.TrimRight(ep, "/") + "/" + strings.TrimLeft(path, "/")
|
||||
}
|
||||
|
||||
func (bucket *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
|
||||
return []cloudprovider.SBucketAccessUrl{
|
||||
{
|
||||
Url: path.Join(bucket.client.endpoint, bucket.Name),
|
||||
Url: joinPath(bucket.client.GetEndpoint(), bucket.Name),
|
||||
Description: fmt.Sprintf("%s", bucket.Location),
|
||||
Primary: true,
|
||||
},
|
||||
@@ -116,7 +123,7 @@ func (bucket *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
|
||||
|
||||
func (bucket *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) {
|
||||
ret := cloudprovider.SListObjectResult{}
|
||||
result, err := bucket.client.client.ListObjectsQuery(bucket.Name, prefix, marker, delimiter, maxCount)
|
||||
result, err := bucket.client.S3Client().ListObjectsQuery(bucket.Name, prefix, marker, delimiter, maxCount)
|
||||
if err != nil {
|
||||
return ret, errors.Wrap(err, "ListObjectsQuery")
|
||||
}
|
||||
@@ -154,7 +161,7 @@ func (bucket *SBucket) GetIObjects(prefix string, isRecursive bool) ([]cloudprov
|
||||
defer close(doneCh)
|
||||
|
||||
ret := make([]cloudprovider.ICloudObject, 0)
|
||||
objectCh := bucket.client.client.ListObjects(bucket.Name, prefix, isRecursive, doneCh)
|
||||
objectCh := bucket.client.S3Client().ListObjects(bucket.Name, prefix, isRecursive, doneCh)
|
||||
for object := range objectCh {
|
||||
if object.Err != nil {
|
||||
return nil, errors.Wrap(object.Err, "ListObjects")
|
||||
@@ -187,7 +194,7 @@ func (bucket *SBucket) PutObject(ctx context.Context, key string, input io.Reade
|
||||
opts.StorageClass = storageClassStr
|
||||
}
|
||||
opts.PartSize = uint64(cloudprovider.MAX_PUT_OBJECT_SIZEBYTES)
|
||||
_, err := bucket.client.client.PutObjectDo(ctx, bucket.Name, key, input, "", "", sizeBytes, opts)
|
||||
_, err := bucket.client.S3Client().PutObjectDo(ctx, bucket.Name, key, input, "", "", sizeBytes, opts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "PutObjectWithContext")
|
||||
}
|
||||
@@ -210,7 +217,7 @@ func (bucket *SBucket) NewMultipartUpload(ctx context.Context, key string, contT
|
||||
if len(storageClassStr) > 0 {
|
||||
opts.StorageClass = storageClassStr
|
||||
}
|
||||
result, err := bucket.client.client.InitiateMultipartUpload(ctx, bucket.Name, key, opts)
|
||||
result, err := bucket.client.S3Client().InitiateMultipartUpload(ctx, bucket.Name, key, opts)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "InitiateMultipartUpload")
|
||||
}
|
||||
@@ -218,7 +225,7 @@ func (bucket *SBucket) NewMultipartUpload(ctx context.Context, key string, contT
|
||||
}
|
||||
|
||||
func (bucket *SBucket) UploadPart(ctx context.Context, key string, uploadId string, partIndex int, input io.Reader, partSize int64) (string, error) {
|
||||
part, err := bucket.client.client.UploadPart(ctx, bucket.Name, key, uploadId, input, partIndex, "", "", partSize, nil)
|
||||
part, err := bucket.client.S3Client().UploadPart(ctx, bucket.Name, key, uploadId, input, partIndex, "", "", partSize, nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "UploadPart")
|
||||
}
|
||||
@@ -234,7 +241,7 @@ func (bucket *SBucket) CompleteMultipartUpload(ctx context.Context, key string,
|
||||
ETag: partEtags[i],
|
||||
}
|
||||
}
|
||||
_, err := bucket.client.client.CompleteMultipartUpload(ctx, bucket.Name, key, uploadId, complete)
|
||||
_, err := bucket.client.S3Client().CompleteMultipartUpload(ctx, bucket.Name, key, uploadId, complete)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "CompleteMultipartUpload")
|
||||
}
|
||||
@@ -242,7 +249,7 @@ func (bucket *SBucket) CompleteMultipartUpload(ctx context.Context, key string,
|
||||
}
|
||||
|
||||
func (bucket *SBucket) AbortMultipartUpload(ctx context.Context, key string, uploadId string) error {
|
||||
err := bucket.client.client.AbortMultipartUpload(ctx, bucket.Name, key, uploadId)
|
||||
err := bucket.client.S3Client().AbortMultipartUpload(ctx, bucket.Name, key, uploadId)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "AbortMultipartUpload")
|
||||
}
|
||||
@@ -250,7 +257,7 @@ func (bucket *SBucket) AbortMultipartUpload(ctx context.Context, key string, upl
|
||||
}
|
||||
|
||||
func (bucket *SBucket) DeleteObject(ctx context.Context, key string) error {
|
||||
err := bucket.client.client.RemoveObject(bucket.Name, key)
|
||||
err := bucket.client.S3Client().RemoveObject(bucket.Name, key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "RemoveObject")
|
||||
}
|
||||
@@ -261,7 +268,7 @@ func (bucket *SBucket) GetTempUrl(method string, key string, expire time.Duratio
|
||||
if method != "GET" && method != "PUT" && method != "DELETE" {
|
||||
return "", errors.Error("unsupported method")
|
||||
}
|
||||
url, err := bucket.client.client.Presign(method, bucket.Name, key, expire, nil)
|
||||
url, err := bucket.client.S3Client().Presign(method, bucket.Name, key, expire, nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Presign")
|
||||
}
|
||||
@@ -281,7 +288,7 @@ func (bucket *SBucket) CopyObject(ctx context.Context, destKey string, srcBucket
|
||||
return errors.Wrap(err, "NewDestinationInfo")
|
||||
}
|
||||
src := s3cli.NewSourceInfo(srcBucket, srcKey, nil)
|
||||
err = bucket.client.client.CopyObject(dest, src)
|
||||
err = bucket.client.S3Client().CopyObject(dest, src)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "CopyObject")
|
||||
}
|
||||
@@ -301,7 +308,7 @@ func (bucket *SBucket) GetObject(ctx context.Context, key string, rangeOpt *clou
|
||||
if rangeOpt != nil {
|
||||
opts.SetRange(rangeOpt.Start, rangeOpt.End)
|
||||
}
|
||||
output, err := bucket.client.client.GetObject(bucket.Name, key, opts)
|
||||
output, err := bucket.client.S3Client().GetObject(bucket.Name, key, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetObject")
|
||||
}
|
||||
@@ -309,7 +316,7 @@ func (bucket *SBucket) GetObject(ctx context.Context, key string, rangeOpt *clou
|
||||
}
|
||||
|
||||
func (bucket *SBucket) CopyPart(ctx context.Context, key string, uploadId string, partNumber int, srcBucket string, srcKey string, srcOffset int64, srcLength int64) (string, error) {
|
||||
result, err := bucket.client.client.CopyObjectPartDo(ctx, srcBucket, srcKey, bucket.Name, key, uploadId, partNumber, srcOffset, srcLength, nil)
|
||||
result, err := bucket.client.S3Client().CopyObjectPartDo(ctx, srcBucket, srcKey, bucket.Name, key, uploadId, partNumber, srcOffset, srcLength, nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "CopyObjectPartDo")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ceph
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/tristate"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/util/httputils"
|
||||
"yunion.io/x/onecloud/pkg/util/s3auth"
|
||||
)
|
||||
|
||||
type SCephAdminApi struct {
|
||||
adminPath string
|
||||
accessKey string
|
||||
secret string
|
||||
endpoint string
|
||||
client *http.Client
|
||||
debug bool
|
||||
}
|
||||
|
||||
func newCephAdminApi(ak, sk, ep string, debug bool, adminPath string) *SCephAdminApi {
|
||||
if adminPath == "" {
|
||||
adminPath = "admin"
|
||||
}
|
||||
return &SCephAdminApi{
|
||||
adminPath: adminPath,
|
||||
accessKey: ak,
|
||||
secret: sk,
|
||||
endpoint: ep,
|
||||
client: httputils.GetDefaultClient(),
|
||||
debug: debug,
|
||||
}
|
||||
}
|
||||
|
||||
func getJsonBodyReader(body jsonutils.JSONObject) io.Reader {
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
reqBody = strings.NewReader(body.String())
|
||||
}
|
||||
return reqBody
|
||||
}
|
||||
|
||||
func (api *SCephAdminApi) jsonRequest(ctx context.Context, method httputils.THttpMethod, path string, hdr http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
|
||||
urlStr := strings.TrimRight(api.endpoint, "/") + "/" + strings.TrimLeft(path, "/")
|
||||
req, err := http.NewRequest(string(method), urlStr, getJsonBodyReader(body))
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "http.NewRequest")
|
||||
}
|
||||
if hdr != nil {
|
||||
for k, vs := range hdr {
|
||||
for _, v := range vs {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newReq := s3auth.SignV4(*req, api.accessKey, api.secret, "cn-beijing", getJsonBodyReader(body))
|
||||
|
||||
resp, err := api.client.Do(newReq)
|
||||
|
||||
return httputils.ParseJSONResponse(resp, err, api.debug)
|
||||
}
|
||||
|
||||
func (api *SCephAdminApi) GetUsage(ctx context.Context, uid string) (jsonutils.JSONObject, error) {
|
||||
path := fmt.Sprintf("/%s/usage?format=json&uid=%s&show-entries=False&show-summary=True", api.adminPath, uid)
|
||||
_, resp, err := api.jsonRequest(ctx, httputils.GET, path, nil, nil)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// {"caps":[{"perm":"*","type":"buckets"},{"perm":"*","type":"usage"},{"perm":"*","type":"users"}],
|
||||
// "display_name":"First User","email":"",
|
||||
// "keys":[{"access_key":"70TTA6IU5D9LQ0IVA3Z6","secret_key":"AK3Kd9L1elPin9wEXNRuY9L2yxZ5U3mGsGYoyIxL","user":"testuser"}],
|
||||
// "max_buckets":1000,"subusers":[],"suspended":0,"swift_keys":[],"tenant":"","user_id":"testuser"}
|
||||
|
||||
type SUserInfo struct {
|
||||
Caps []SUserCapability
|
||||
DisplayName string
|
||||
Email string
|
||||
UserId string
|
||||
Tenant string
|
||||
Suspended int
|
||||
SubUsers []string
|
||||
MaxBuckets int
|
||||
Keys []SUserAccessKey
|
||||
}
|
||||
|
||||
type SUserCapability struct {
|
||||
Perm string
|
||||
Type string
|
||||
}
|
||||
|
||||
type SUserAccessKey struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
User string
|
||||
}
|
||||
|
||||
func (api *SCephAdminApi) GetUserInfo(ctx context.Context, uid string) (*SUserInfo, error) {
|
||||
path := fmt.Sprintf("/%s/user?format=json&uid=%s", api.adminPath, uid)
|
||||
_, resp, err := api.jsonRequest(ctx, httputils.GET, path, nil, nil)
|
||||
if err != nil {
|
||||
if httputils.ErrorCode(err) == 403 {
|
||||
msg := `add users read cap by: radosgw-admin caps add --uid %s --caps="users=read"`
|
||||
return nil, errors.Wrapf(httperrors.ErrForbidden, msg, uid)
|
||||
}
|
||||
return nil, errors.Wrap(err, "api.jsonRequest")
|
||||
}
|
||||
usrInfo := SUserInfo{}
|
||||
err = resp.Unmarshal(&usrInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "resp.Unmarshal")
|
||||
}
|
||||
return &usrInfo, nil
|
||||
}
|
||||
|
||||
type SQuotaQuery struct {
|
||||
QuotaType string `json:"quota-type"`
|
||||
Uid string `json:"uid"`
|
||||
Bucket string `json:"bucket"`
|
||||
}
|
||||
|
||||
type SQuota struct {
|
||||
Enabled tristate.TriState `json:"enabled"`
|
||||
CheckOnRaw bool `json:"check_on_raw,omitfalse"`
|
||||
MaxSize int64 `json:"max_size,omitzero"`
|
||||
MaxSizeKB int64 `json:"max_size_kb,omitzero"`
|
||||
MaxObjects int `json:"max_objects,omitzero"`
|
||||
}
|
||||
|
||||
func (q SQuotaQuery) Query() string {
|
||||
return "quota&format=json&" + jsonutils.Marshal(q).QueryString()
|
||||
}
|
||||
|
||||
func (q *SQuotaQuery) SetBucket(uid string, level string, bucket string) {
|
||||
q.Uid = uid
|
||||
q.Bucket = bucket
|
||||
q.QuotaType = level
|
||||
}
|
||||
|
||||
func (api *SCephAdminApi) GetUserQuota(ctx context.Context, uid string) (*SQuota, *SQuota, error) {
|
||||
userQuota, err := api.getQuota(ctx, uid, "user", "")
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "api.getQuota user level")
|
||||
}
|
||||
bucketQuota, err := api.getQuota(ctx, uid, "bucket", "")
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "api.getQuota bucket level")
|
||||
}
|
||||
return userQuota, bucketQuota, nil
|
||||
}
|
||||
|
||||
// ceph目前不支持设置bucket的quota,因此返回全局的bucket quota
|
||||
func (api *SCephAdminApi) GetBucketQuota(ctx context.Context, uid string, bucket string) (*SQuota, error) {
|
||||
/*quota, err := api.getQuota(ctx, uid, "", bucket)
|
||||
if err == nil {
|
||||
return quota, nil
|
||||
}*/
|
||||
return api.getQuota(ctx, uid, "bucket", "")
|
||||
}
|
||||
|
||||
func (api *SCephAdminApi) getQuota(ctx context.Context, uid string, level string, bucket string) (*SQuota, error) {
|
||||
query := SQuotaQuery{}
|
||||
query.SetBucket(uid, level, bucket)
|
||||
var path string
|
||||
if len(bucket) > 0 {
|
||||
path = fmt.Sprintf("/%s/bucket?%s", api.adminPath, query.Query())
|
||||
} else {
|
||||
path = fmt.Sprintf("/%s/user?%s", api.adminPath, query.Query())
|
||||
}
|
||||
_, resp, err := api.jsonRequest(ctx, httputils.GET, path, nil, nil)
|
||||
if err != nil {
|
||||
if httputils.ErrorCode(err) == 403 {
|
||||
var msg string
|
||||
if len(bucket) > 0 {
|
||||
msg = `add buckets read cap by: radosgw-admin caps add --uid %s --caps="buckets=read"`
|
||||
} else {
|
||||
msg = `add users read cap by: radosgw-admin caps add --uid %s --caps="users=read"`
|
||||
}
|
||||
return nil, errors.Wrapf(httperrors.ErrForbidden, msg, uid)
|
||||
}
|
||||
return nil, errors.Wrap(err, "api.jsonRequest")
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, httperrors.ErrNotSupported
|
||||
}
|
||||
quota := SQuota{}
|
||||
err = resp.Unmarshal("a)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "resp.Unmarshal")
|
||||
}
|
||||
return "a, nil
|
||||
}
|
||||
|
||||
func (api *SCephAdminApi) SetUserQuota(ctx context.Context, uid string, sizeBytes int64, objects int) error {
|
||||
_, err := api.setQuota(ctx, uid, "user", "", sizeBytes, objects)
|
||||
return errors.Wrap(err, "api.setQuota")
|
||||
}
|
||||
|
||||
func (api *SCephAdminApi) SetAllBucketQuota(ctx context.Context, uid string, sizeBytes int64, objects int) error {
|
||||
_, err := api.setQuota(ctx, uid, "bucket", "", sizeBytes, objects)
|
||||
return errors.Wrap(err, "api.setQuota")
|
||||
}
|
||||
|
||||
// ceph目前不支持设置quota,因此返回全局的bucket quota
|
||||
func (api *SCephAdminApi) SetBucketQuota(ctx context.Context, uid string, bucket string, sizeBytes int64, objects int) error {
|
||||
var err error
|
||||
_, err = api.setQuota(ctx, uid, "", bucket, sizeBytes, objects)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
_, err = api.setQuota(ctx, uid, "bucket", "", sizeBytes, objects)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
_, err = api.setQuota(ctx, uid, "user", "", sizeBytes, objects)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "api.setQuota")
|
||||
}
|
||||
|
||||
func (api *SCephAdminApi) setQuota(ctx context.Context, uid string, level string, bucket string, sizeBytes int64, objects int) (*SQuota, error) {
|
||||
query := SQuotaQuery{}
|
||||
query.SetBucket(uid, level, bucket)
|
||||
quota := SQuota{
|
||||
MaxSize: sizeBytes,
|
||||
MaxObjects: objects,
|
||||
}
|
||||
if sizeBytes <= 0 && objects <= 0 {
|
||||
quota.Enabled = tristate.False
|
||||
} else {
|
||||
quota.Enabled = tristate.True
|
||||
}
|
||||
var path string
|
||||
if len(bucket) > 0 {
|
||||
path = fmt.Sprintf("/%s/bucket?%s", api.adminPath, query.Query())
|
||||
} else {
|
||||
path = fmt.Sprintf("/%s/user?%s", api.adminPath, query.Query())
|
||||
}
|
||||
body := jsonutils.Marshal(quota)
|
||||
log.Debugf("request %s %s %s", httputils.PUT, path, body)
|
||||
_, resp, err := api.jsonRequest(ctx, httputils.PUT, path, nil, body)
|
||||
if err != nil {
|
||||
if httputils.ErrorCode(err) == 403 {
|
||||
var msg string
|
||||
if len(bucket) > 0 {
|
||||
msg = `add buckets write cap by: radosgw-admin caps add --uid %s --caps="buckets=write"`
|
||||
} else {
|
||||
msg = `add users write cap by: radosgw-admin caps add --uid %s --caps="users=write"`
|
||||
}
|
||||
return nil, errors.Wrapf(httperrors.ErrForbidden, msg, uid)
|
||||
}
|
||||
return nil, errors.Wrap(err, "api.jsonRequest")
|
||||
}
|
||||
log.Debugf("%s", resp)
|
||||
quota = SQuota{}
|
||||
err = resp.Unmarshal("a)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "resp.Unmarshal")
|
||||
}
|
||||
return "a, nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ceph
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/objectstore"
|
||||
)
|
||||
|
||||
type SCephRadosBucket struct {
|
||||
*objectstore.SBucket
|
||||
}
|
||||
|
||||
func (b *SCephRadosBucket) GetStats() cloudprovider.SBucketStats {
|
||||
_, hdr, _ := b.GetIBucketProvider().S3Client().BucketExists(b.Name)
|
||||
if hdr != nil {
|
||||
sizeBytesStr := hdr.Get("X-Rgw-Bytes-Used")
|
||||
sizeBytes, _ := strconv.ParseInt(sizeBytesStr, 10, 64)
|
||||
objCntStr := hdr.Get("X-Rgw-Object-Count")
|
||||
objCnt, _ := strconv.ParseInt(objCntStr, 10, 64)
|
||||
return cloudprovider.SBucketStats{
|
||||
SizeBytes: sizeBytes,
|
||||
ObjectCount: int(objCnt),
|
||||
}
|
||||
}
|
||||
return b.SBucket.GetStats()
|
||||
}
|
||||
|
||||
func (b *SCephRadosBucket) GetLimit() cloudprovider.SBucketStats {
|
||||
if cephCli, ok := b.GetIBucketProvider().(*SCephRadosClient); ok {
|
||||
quota, err := cephCli.adminApi.GetBucketQuota(context.Background(), cephCli.GetAccountId(), b.Name)
|
||||
if err == nil {
|
||||
limit := cloudprovider.SBucketStats{}
|
||||
if quota.Enabled.IsTrue() {
|
||||
limit.SizeBytes = quota.MaxSize
|
||||
limit.ObjectCount = quota.MaxObjects
|
||||
}
|
||||
return limit
|
||||
}
|
||||
}
|
||||
return b.SBucket.GetLimit()
|
||||
}
|
||||
|
||||
func (b *SCephRadosBucket) SetLimit(limit cloudprovider.SBucketStats) error {
|
||||
/*if cephCli, ok := b.GetIBucketProvider().(*SCephRadosClient); ok {
|
||||
err := cephCli.adminApi.SetBucketQuota(context.Background(), cephCli.GetAccountId(), b.Name, limit.SizeBytes, limit.ObjectCount)
|
||||
return errors.Wrap(err, "cephCli.adminApi.SetBucketQuota")
|
||||
}
|
||||
return b.SBucket.SetLimit(limit)*/
|
||||
return httperrors.ErrNotSupported
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ceph
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/s3cli"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/objectstore"
|
||||
)
|
||||
|
||||
type SCephRadosClient struct {
|
||||
*objectstore.SObjectStoreClient
|
||||
|
||||
adminApi *SCephAdminApi
|
||||
|
||||
userQuota *SQuota
|
||||
bucketQuota *SQuota
|
||||
userInfo *SUserInfo
|
||||
}
|
||||
|
||||
func NewCephRados(providerId string, providerName string, endpoint string, accessKey string, secret string, isDebug bool) (*SCephRadosClient, error) {
|
||||
s3store, err := objectstore.NewObjectStoreClientAndFetch(providerId, providerName, endpoint, accessKey, secret, isDebug, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "NewObjectStoreClient")
|
||||
}
|
||||
adminApi := newCephAdminApi(accessKey, secret, endpoint, isDebug, "")
|
||||
|
||||
client := SCephRadosClient{
|
||||
SObjectStoreClient: s3store,
|
||||
adminApi: adminApi,
|
||||
}
|
||||
|
||||
client.SetVirtualObject(&client)
|
||||
|
||||
err = client.FetchBuckets()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetchBuckets")
|
||||
}
|
||||
|
||||
userQuota, bucketQuota, err := adminApi.GetUserQuota(context.Background(), s3store.GetAccountId())
|
||||
if err != nil {
|
||||
if errors.Cause(err) != httperrors.ErrForbidden {
|
||||
return nil, errors.Wrap(err, "adminApi.GetUserQuota")
|
||||
} else {
|
||||
// skip the error
|
||||
log.Errorf("adminApi.GetUserQuota fail: %s", err)
|
||||
}
|
||||
}
|
||||
userInfo, err := adminApi.GetUserInfo(context.Background(), s3store.GetAccountId())
|
||||
if err != nil {
|
||||
if errors.Cause(err) != httperrors.ErrForbidden {
|
||||
return nil, errors.Wrap(err, "adminApi.GetUserInfo")
|
||||
} else {
|
||||
// skip the error
|
||||
log.Errorf("adminApi.GetUserInfo fail: %s", err)
|
||||
}
|
||||
}
|
||||
if isDebug {
|
||||
log.Debugf("%#v %#v %#v", userQuota, bucketQuota, userInfo)
|
||||
}
|
||||
client.userQuota = userQuota
|
||||
client.bucketQuota = bucketQuota
|
||||
client.userInfo = userInfo
|
||||
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
func (cli *SCephRadosClient) GetVersion() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (cli *SCephRadosClient) About() jsonutils.JSONObject {
|
||||
about := jsonutils.NewDict()
|
||||
if cli.userQuota != nil {
|
||||
about.Add(jsonutils.Marshal(cli.userQuota), "user_quota")
|
||||
}
|
||||
if cli.bucketQuota != nil {
|
||||
about.Add(jsonutils.Marshal(cli.bucketQuota), "bucket_quota")
|
||||
}
|
||||
if cli.userInfo != nil {
|
||||
about.Add(jsonutils.Marshal(cli.userInfo), "user_info")
|
||||
}
|
||||
return about
|
||||
}
|
||||
|
||||
func (cli *SCephRadosClient) GetProvider() string {
|
||||
return api.CLOUD_PROVIDER_CEPH
|
||||
}
|
||||
|
||||
func (cli *SCephRadosClient) NewBucket(bucket s3cli.BucketInfo) cloudprovider.ICloudBucket {
|
||||
generalBucket := cli.SObjectStoreClient.NewBucket(bucket)
|
||||
return &SCephRadosBucket{
|
||||
SBucket: generalBucket.(*objectstore.SBucket),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
"yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph"
|
||||
s3provider "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider"
|
||||
)
|
||||
|
||||
type SCephRadosProviderFactory struct {
|
||||
s3provider.SObjectStoreProviderFactory
|
||||
}
|
||||
|
||||
func (self *SCephRadosProviderFactory) GetId() string {
|
||||
return api.CLOUD_PROVIDER_CEPH
|
||||
}
|
||||
|
||||
func (self *SCephRadosProviderFactory) GetName() string {
|
||||
return api.CLOUD_PROVIDER_CEPH
|
||||
}
|
||||
|
||||
func (self *SCephRadosProviderFactory) GetProvider(providerId, providerName, url, account, secret string) (cloudprovider.ICloudProvider, error) {
|
||||
client, err := ceph.NewCephRados(providerId, providerName, url, account, secret, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s3provider.NewObjectStoreProvider(self, client), nil
|
||||
}
|
||||
|
||||
func (self *SCephRadosProviderFactory) GetClientRC(url, account, secret string) (map[string]string, error) {
|
||||
return map[string]string{
|
||||
"S3_ACCESS_KEY": account,
|
||||
"S3_SECRET": secret,
|
||||
"S3_ACCESS_URL": url,
|
||||
"S3_BACKEND": api.CLOUD_PROVIDER_CEPH,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
factory := SCephRadosProviderFactory{}
|
||||
cloudprovider.RegisterFactory(&factory)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package objectstore
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/s3cli"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
)
|
||||
|
||||
type IBucketProvider interface {
|
||||
cloudprovider.ICloudRegion
|
||||
|
||||
NewBucket(bucket s3cli.BucketInfo) cloudprovider.ICloudBucket
|
||||
|
||||
GetEndpoint() string
|
||||
|
||||
S3Client() *s3cli.Client
|
||||
|
||||
About() jsonutils.JSONObject
|
||||
GetVersion() string
|
||||
GetAccountId() string
|
||||
GetSubAccounts() ([]cloudprovider.SSubAccount, error)
|
||||
|
||||
GetObjectAcl(bucket, key string) (cloudprovider.TBucketACLType, error)
|
||||
SetObjectAcl(bucket, key string, cannedAcl cloudprovider.TBucketACLType) error
|
||||
GetIBucketAcl(name string) (cloudprovider.TBucketACLType, error)
|
||||
SetIBucketAcl(name string, cannedAcl cloudprovider.TBucketACLType) error
|
||||
}
|
||||
@@ -54,6 +54,10 @@ type SObjectStoreClient struct {
|
||||
}
|
||||
|
||||
func NewObjectStoreClient(providerId string, providerName string, endpoint string, accessKey string, secret string, isDebug bool) (*SObjectStoreClient, error) {
|
||||
return NewObjectStoreClientAndFetch(providerId, providerName, endpoint, accessKey, secret, isDebug, true)
|
||||
}
|
||||
|
||||
func NewObjectStoreClientAndFetch(providerId string, providerName string, endpoint string, accessKey string, secret string, isDebug bool, doFetch bool) (*SObjectStoreClient, error) {
|
||||
client := SObjectStoreClient{
|
||||
providerId: providerId,
|
||||
providerName: providerName,
|
||||
@@ -79,14 +83,15 @@ func NewObjectStoreClient(providerId string, providerName string, endpoint strin
|
||||
cli.SetCustomTransport(tr)
|
||||
|
||||
client.client = cli
|
||||
client.SetVirtualObject(&client)
|
||||
|
||||
err = client.fetchBuckets()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetchBuckets")
|
||||
if doFetch {
|
||||
err = client.FetchBuckets()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetchBuckets")
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("clientID: %s Name: %s", client.ownerId, client.ownerName)
|
||||
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
@@ -107,6 +112,10 @@ func (cli *SObjectStoreClient) GetIRegion() cloudprovider.ICloudRegion {
|
||||
return cli.GetVirtualObject().(cloudprovider.ICloudRegion)
|
||||
}
|
||||
|
||||
func (cli *SObjectStoreClient) GetIBucketProvider() IBucketProvider {
|
||||
return cli.GetVirtualObject().(IBucketProvider)
|
||||
}
|
||||
|
||||
func (cli *SObjectStoreClient) GetVersion() string {
|
||||
return ""
|
||||
}
|
||||
@@ -120,6 +129,24 @@ func (cli *SObjectStoreClient) GetProvider() string {
|
||||
return api.CLOUD_PROVIDER_GENERICS3
|
||||
}
|
||||
|
||||
////////////////////////////// IBucketProvider //////////////////////////////
|
||||
|
||||
func (cli *SObjectStoreClient) NewBucket(bucket s3cli.BucketInfo) cloudprovider.ICloudBucket {
|
||||
return &SBucket{
|
||||
client: cli.GetIBucketProvider(),
|
||||
Name: bucket.Name,
|
||||
CreatedAt: bucket.CreationDate,
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *SObjectStoreClient) GetEndpoint() string {
|
||||
return cli.endpoint
|
||||
}
|
||||
|
||||
func (cli *SObjectStoreClient) S3Client() *s3cli.Client {
|
||||
return cli.client
|
||||
}
|
||||
|
||||
///////////////////////////////// fake impletementations //////////////////////
|
||||
|
||||
func (cli *SObjectStoreClient) GetIZones() ([]cloudprovider.ICloudZone, error) {
|
||||
@@ -286,7 +313,7 @@ func (self *SObjectStoreClient) invalidateIBuckets() {
|
||||
|
||||
func (self *SObjectStoreClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
|
||||
if self.iBuckets == nil {
|
||||
err := self.fetchBuckets()
|
||||
err := self.FetchBuckets()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetchBuckets")
|
||||
}
|
||||
@@ -294,7 +321,7 @@ func (self *SObjectStoreClient) getIBuckets() ([]cloudprovider.ICloudBucket, err
|
||||
return self.iBuckets, nil
|
||||
}
|
||||
|
||||
func (cli *SObjectStoreClient) fetchBuckets() error {
|
||||
func (cli *SObjectStoreClient) FetchBuckets() error {
|
||||
result, err := cli.client.ListBuckets()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "client.ListBuckets")
|
||||
@@ -304,12 +331,8 @@ func (cli *SObjectStoreClient) fetchBuckets() error {
|
||||
buckets := result.Buckets.Bucket
|
||||
cli.iBuckets = make([]cloudprovider.ICloudBucket, len(buckets))
|
||||
for i := range buckets {
|
||||
b := SBucket{
|
||||
client: cli,
|
||||
Name: buckets[i].Name,
|
||||
CreatedAt: buckets[i].CreationDate,
|
||||
}
|
||||
cli.iBuckets[i] = &b
|
||||
b := cli.GetIBucketProvider().NewBucket(buckets[i])
|
||||
cli.iBuckets[i] = b
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -82,11 +82,7 @@ func (self *SObjectStoreProviderFactory) GetProvider(providerId, providerName, u
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.SetVirtualObject(client)
|
||||
return &SObjectStoreProvider{
|
||||
SBaseProvider: cloudprovider.NewBaseProvider(self),
|
||||
client: client,
|
||||
}, nil
|
||||
return NewObjectStoreProvider(self, client), nil
|
||||
}
|
||||
|
||||
func (self *SObjectStoreProviderFactory) GetClientRC(url, account, secret string) (map[string]string, error) {
|
||||
@@ -94,6 +90,7 @@ func (self *SObjectStoreProviderFactory) GetClientRC(url, account, secret string
|
||||
"S3_ACCESS_KEY": account,
|
||||
"S3_SECRET": secret,
|
||||
"S3_ACCESS_URL": url,
|
||||
"S3_BACKEND": api.CLOUD_PROVIDER_GENERICS3,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -104,7 +101,14 @@ func init() {
|
||||
|
||||
type SObjectStoreProvider struct {
|
||||
cloudprovider.SBaseProvider
|
||||
client *objectstore.SObjectStoreClient
|
||||
client objectstore.IBucketProvider
|
||||
}
|
||||
|
||||
func NewObjectStoreProvider(factory cloudprovider.ICloudProviderFactory, client objectstore.IBucketProvider) *SObjectStoreProvider {
|
||||
return &SObjectStoreProvider{
|
||||
SBaseProvider: cloudprovider.NewBaseProvider(factory),
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SObjectStoreProvider) GetIRegions() []cloudprovider.ICloudRegion {
|
||||
|
||||
@@ -80,6 +80,29 @@ func S3Shell() {
|
||||
return nil
|
||||
})
|
||||
|
||||
type BucketLimitOptions struct {
|
||||
NAME string `help:"name of bucket to set limit"`
|
||||
SizeGB int `help:"limit of volumes in GB"`
|
||||
Objects int `help:"limit of object count"`
|
||||
Off bool `help:"Turn off limit"`
|
||||
}
|
||||
shellutils.R(&BucketLimitOptions{}, "bucket-set-limit", "Set bucket limit", func(cli cloudprovider.ICloudRegion, args *BucketLimitOptions) error {
|
||||
bucket, err := cli.GetIBucketByName(args.NAME)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if args.Off {
|
||||
err = bucket.SetLimit(cloudprovider.SBucketStats{})
|
||||
} else {
|
||||
fmt.Println("set limit")
|
||||
err = bucket.SetLimit(cloudprovider.SBucketStats{SizeBytes: int64(args.SizeGB * 1000 * 1000 * 1000), ObjectCount: args.Objects})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
type BucketExistOptions struct {
|
||||
NAME string `help:"name of bucket to delete"`
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/onecloud/pkg/appctx"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
@@ -31,6 +32,8 @@ const (
|
||||
|
||||
func s3authenticate(f appsrv.FilterHandler) appsrv.FilterHandler {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
log.Debugf("%s %s %s %s", r.Method, r.Host, r.URL, r.Header)
|
||||
|
||||
o, err := getObjectRequest(r)
|
||||
if err != nil {
|
||||
SendError(w, BadRequest(ctx, err.Error()))
|
||||
|
||||
@@ -26,13 +26,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/s3gateway/handlers"
|
||||
"yunion.io/x/onecloud/pkg/s3gateway/options"
|
||||
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/aliyun/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/aws/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/azure/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/huawei/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/qcloud/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/ucloud/provider"
|
||||
_ "yunion.io/x/onecloud/pkg/multicloud/loader"
|
||||
)
|
||||
|
||||
func StartService() {
|
||||
|
||||
@@ -231,9 +231,14 @@ func (aksk SAccessKeyRequestV2) Encode() string {
|
||||
return jsonutils.Marshal(aksk).String()
|
||||
}
|
||||
|
||||
func NewV2Request() SAccessKeyRequestV2 {
|
||||
req := SAccessKeyRequestV2{}
|
||||
req.Algorithm = signV2Algorithm
|
||||
return req
|
||||
}
|
||||
|
||||
func decodeAuthHeaderV2(authStr string) (*SAccessKeyRequestV2, error) {
|
||||
akskReq := SAccessKeyRequestV2{}
|
||||
akskReq.Algorithm = signV2Algorithm
|
||||
akskReq := NewV2Request()
|
||||
pos := strings.IndexByte(authStr, ':')
|
||||
if pos <= 0 {
|
||||
return nil, errors.Error("illegal authorization header")
|
||||
|
||||
+81
-3
@@ -19,6 +19,7 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -26,6 +27,9 @@ import (
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/util/streamutils"
|
||||
"yunion.io/x/pkg/util/timeutils"
|
||||
)
|
||||
|
||||
// Signature and API related constants.
|
||||
@@ -188,13 +192,25 @@ func getCanonicalHeaders(req http.Request, signedHeaders []string) string {
|
||||
// request header names.
|
||||
func getSignedHeaders(req http.Request, ignoredHeaders map[string]bool) []string {
|
||||
var headers []string
|
||||
hasHost := false
|
||||
hasContentHash := false
|
||||
for k := range req.Header {
|
||||
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
|
||||
continue // Ignored header found continue.
|
||||
}
|
||||
if strings.EqualFold(k, "host") {
|
||||
hasHost = true
|
||||
} else if strings.EqualFold(k, "x-amz-content-sha256") {
|
||||
hasContentHash = true
|
||||
}
|
||||
headers = append(headers, strings.ToLower(k))
|
||||
}
|
||||
headers = append(headers, "host")
|
||||
if !hasHost {
|
||||
headers = append(headers, "host")
|
||||
}
|
||||
if !hasContentHash {
|
||||
headers = append(headers, "x-amz-content-sha256")
|
||||
}
|
||||
sort.Strings(headers)
|
||||
return headers
|
||||
}
|
||||
@@ -217,11 +233,16 @@ type SAccessKeyRequestV4 struct {
|
||||
SignDate time.Time
|
||||
}
|
||||
|
||||
func NewV4Request() SAccessKeyRequestV4 {
|
||||
req := SAccessKeyRequestV4{}
|
||||
req.Algorithm = signV4Algorithm
|
||||
return req
|
||||
}
|
||||
|
||||
// AWS4-HMAC-SHA256
|
||||
// Credential=xxxx/20190824/us-east-1/s3/aws4_request,SignedHeaders=date;host;x-amz-content-sha256;x-amz-date,Signature=27a135c6f51cc
|
||||
func decodeAuthHeaderV4(authStr string) (*SAccessKeyRequestV4, error) {
|
||||
req := SAccessKeyRequestV4{}
|
||||
req.Algorithm = signV4Algorithm
|
||||
req := NewV4Request()
|
||||
parts := strings.Split(authStr, ",")
|
||||
if len(parts) != 3 ||
|
||||
!strings.HasPrefix(parts[0], "Credential=") ||
|
||||
@@ -268,3 +289,60 @@ func (aksk SAccessKeyRequestV4) Verify(secret string) error {
|
||||
func (aksk SAccessKeyRequestV4) Encode() string {
|
||||
return jsonutils.Marshal(aksk).String()
|
||||
}
|
||||
|
||||
// GetCredential generate a credential string.
|
||||
func getCredential(accessKeyID, location string, t time.Time) string {
|
||||
scope := getScope(location, t)
|
||||
return accessKeyID + "/" + scope
|
||||
}
|
||||
|
||||
// SignV4 sign the request before Do(), in accordance with
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html.
|
||||
func SignV4(req http.Request, accessKey, secretAccessKey, location string, body io.Reader) *http.Request {
|
||||
// Signature calculation is not needed for anonymous credentials.
|
||||
if accessKey == "" || secretAccessKey == "" {
|
||||
return &req
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
if body != nil {
|
||||
streamutils.StreamPipe(body, h, false)
|
||||
}
|
||||
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(h.Sum(nil)))
|
||||
|
||||
// Initial time.
|
||||
t := time.Now().UTC()
|
||||
|
||||
// Set x-amz-date.
|
||||
req.Header.Set("X-Amz-Date", t.Format(iso8601DateFormat))
|
||||
req.Header.Set("Date", timeutils.RFC2882Time(t))
|
||||
|
||||
signedHeaders := getSignedHeaders(req, v4IgnoredHeaders)
|
||||
// Get canonical request.
|
||||
canonicalRequest := getCanonicalRequest(req, signedHeaders)
|
||||
|
||||
// Get string to sign from canonical request.
|
||||
stringToSign := getStringToSignV4(t, location, canonicalRequest)
|
||||
|
||||
// Get hmac signing key.
|
||||
signingKey := getSigningKey(secretAccessKey, location, t)
|
||||
|
||||
// Get credential string.
|
||||
credential := getCredential(accessKey, location, t)
|
||||
|
||||
// Calculate signature.
|
||||
signature := getSignature(signingKey, stringToSign)
|
||||
|
||||
// If regular request, construct the final authorization header.
|
||||
parts := []string{
|
||||
signV4Algorithm + " Credential=" + credential,
|
||||
"SignedHeaders=" + strings.Join(signedHeaders, ";"),
|
||||
"Signature=" + signature,
|
||||
}
|
||||
|
||||
// Set authorization header.
|
||||
auth := strings.Join(parts, ",")
|
||||
req.Header.Set("Authorization", auth)
|
||||
|
||||
return &req
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package s3auth
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSignV4(t *testing.T) {
|
||||
getReq, _ := http.NewRequest(http.MethodGet, "https://api.aliyun-cs.com/2018-06-23/DescribeInstances?limit=10&offset=0", nil)
|
||||
putBody := "<xml><123></123></xml>"
|
||||
putReq, _ := http.NewRequest(http.MethodPut, "https://api.aliyun-cs.com/2018-06-23/ModifyInstance?InstanceId=aabbccdd", strings.NewReader(putBody))
|
||||
|
||||
cases := []struct {
|
||||
request http.Request
|
||||
accessKey string
|
||||
secret string
|
||||
location string
|
||||
body io.Reader
|
||||
}{
|
||||
{
|
||||
request: *getReq,
|
||||
accessKey: "1234567890",
|
||||
secret: "1234567890",
|
||||
location: "cn-beijing",
|
||||
body: nil,
|
||||
},
|
||||
{
|
||||
request: *putReq,
|
||||
accessKey: "1234567890",
|
||||
secret: "1234567890",
|
||||
location: "cn-beijing",
|
||||
body: strings.NewReader(putBody),
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
newreq := SignV4(c.request, c.accessKey, c.secret, c.location, c.body)
|
||||
t.Logf("%#v", newreq)
|
||||
|
||||
req, err := DecodeAccessKeyRequest(*newreq, false)
|
||||
if err != nil {
|
||||
t.Errorf("DecodeAccessKeyRequest fail %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = req.Verify(c.secret)
|
||||
if err != nil {
|
||||
t.Errorf("verify fail %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user