Merge pull request #2965 from swordqiu/feature/qj-xsky-object-store

feature: support xsky object storage
This commit is contained in:
yunion-ci-robot
2019-09-19 21:58:24 +08:00
committed by GitHub
31 changed files with 1316 additions and 12 deletions
+11
View File
@@ -200,6 +200,17 @@ func init() {
return nil
})
R(&options.SS3CloudAccountCreateOptions{}, "cloud-account-create-xsky", "Create a xsky object storage account", func(s *mcclient.ClientSession, args *options.SS3CloudAccountCreateOptions) error {
params := jsonutils.Marshal(args)
params.(*jsonutils.JSONDict).Add(jsonutils.NewString("Xsky"), "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"`
+3
View File
@@ -25,6 +25,7 @@ import (
"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/multicloud/objectstore/xsky"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
@@ -94,6 +95,8 @@ func newClient(options *BaseOptions) (cloudprovider.ICloudRegion, error) {
if options.Backend == api.CLOUD_PROVIDER_CEPH {
return ceph.NewCephRados("", "", options.AccessUrl, options.AccessKey, options.Secret, options.Debug)
} else if options.Backend == api.CLOUD_PROVIDER_XSKY {
return xsky.NewXskyClient("", "", options.AccessUrl, options.AccessKey, options.Secret, options.Debug)
}
return objectstore.NewObjectStoreClient("", "", options.AccessUrl, options.AccessKey, options.Secret, options.Debug)
}
+1 -1
View File
@@ -151,7 +151,7 @@ require (
sigs.k8s.io/yaml v1.1.0 // indirect
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051
yunion.io/x/log v0.0.0-20190629062853-9f6483a7103d
yunion.io/x/pkg v0.0.0-20190902093114-59ba154a6861
yunion.io/x/pkg v0.0.0-20190917154624-e89986e4e4d8
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e
yunion.io/x/sqlchemy v0.0.0-20190823062008-bb710661356f
yunion.io/x/structarg v0.0.0-20190809075558-115bed041de3
+2 -2
View File
@@ -583,8 +583,8 @@ yunion.io/x/log v0.0.0-20190629062853-9f6483a7103d h1:59zrDL7Ft+hDukguJRmLr/Gdu/
yunion.io/x/log v0.0.0-20190629062853-9f6483a7103d/go.mod h1:LC6f/4FozL0iaAbnFt2eDX9jlsyo3WiOUPm03d7+U4U=
yunion.io/x/pkg v0.0.0-20190620104149-945c25821dbf h1:OsKC+2ghZHwp+Ztm/MwKlLKKRiE7QcPG8eTp0GmsHbg=
yunion.io/x/pkg v0.0.0-20190620104149-945c25821dbf/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
yunion.io/x/pkg v0.0.0-20190902093114-59ba154a6861 h1:4TDb45M/HGZ3fWvts253g+U3Rb80jgEiNf8R8Ex7HjQ=
yunion.io/x/pkg v0.0.0-20190902093114-59ba154a6861/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
yunion.io/x/pkg v0.0.0-20190917154624-e89986e4e4d8 h1:wutgIFVln8xNRH9WvrzINQuT1feByaUp0abXq9b7eL4=
yunion.io/x/pkg v0.0.0-20190917154624-e89986e4e4d8/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e h1:v+EzIadodSwkdZ/7bremd7J8J50Cise/HCylsOJngmo=
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
yunion.io/x/sqlchemy v0.0.0-20190823062008-bb710661356f h1:maHGG78d6vLAyT/lGSOOsXWrylBfjdu+NMCGXFhLuhA=
+1
View File
@@ -42,6 +42,7 @@ const (
CLOUD_PROVIDER_GENERICS3 = "S3"
CLOUD_PROVIDER_CEPH = "Ceph"
CLOUD_PROVIDER_XSKY = "Xsky"
CLOUD_PROVIDER_HEALTH_NORMAL = "normal" // 远端处于健康状态
CLOUD_PROVIDER_HEALTH_INSUFFICIENT = "insufficient" // 不足按需资源余额
+14
View File
@@ -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 baremetal
import (
+38 -1
View File
@@ -23,7 +23,6 @@ import (
"time"
"github.com/minio/minio-go/pkg/s3utils"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
@@ -42,6 +41,7 @@ import (
"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/modulebase"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/rbacutils"
)
@@ -587,6 +587,9 @@ func (bucket *SBucket) GetDetailsObjects(
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
iBucket, err := bucket.GetIBucket()
if err != nil {
return nil, httperrors.NewInternalServerError("fail to find external bucket: %s", err)
@@ -620,6 +623,10 @@ func (bucket *SBucket) PerformTempUrl(
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
method, _ := data.GetString("method")
key, _ := data.GetString("key")
expire, _ := data.Int("expire_seconds")
@@ -661,6 +668,10 @@ func (bucket *SBucket) PerformMakedir(
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
key, _ := data.GetString("key")
key = strings.Trim(key, " /")
if len(key) == 0 {
@@ -728,6 +739,10 @@ func (bucket *SBucket) PerformDelete(
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
keys, _ := data.Get("keys")
if keys == nil {
return nil, httperrors.NewInputParameterError("missing keys")
@@ -777,6 +792,10 @@ func (bucket *SBucket) PerformUpload(
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
appParams := appsrv.AppContextGetParams(ctx)
key := appParams.Request.Header.Get(api.BUCKET_UPLOAD_OBJECT_KEY_HEADER)
@@ -881,6 +900,10 @@ func (bucket *SBucket) PerformAcl(
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
aclStr, _ := data.GetString("acl")
switch cloudprovider.TBucketACLType(aclStr) {
case cloudprovider.ACLPrivate, cloudprovider.ACLAuthRead, cloudprovider.ACLPublicRead, cloudprovider.ACLPublicReadWrite:
@@ -959,6 +982,10 @@ func (bucket *SBucket) PerformSync(
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
statsOnly := jsonutils.QueryBoolean(data, "stats_only", false)
iBucket, err := bucket.GetIBucket()
@@ -998,6 +1025,9 @@ func (bucket *SBucket) GetDetailsAcl(
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
iBucket, err := bucket.GetIBucket()
if err != nil {
return nil, httperrors.NewInternalServerError("fail to find external bucket: %s", err)
@@ -1107,6 +1137,10 @@ func (bucket *SBucket) PerformLimit(
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
limit := cloudprovider.SBucketStats{}
err := data.Unmarshal(&limit, "limit")
if err != nil {
@@ -1154,6 +1188,9 @@ func (bucket *SBucket) GetDetailsAccessInfo(
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(bucket.ExternalId) == 0 {
return nil, httperrors.NewInvalidStatusError("no external bucket")
}
manager := bucket.GetCloudprovider()
if manager == nil {
return nil, httperrors.NewInternalServerError("missing manager?")
@@ -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 tasks
import (
+14
View File
@@ -1 +1,15 @@
// 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 cas // import "yunion.io/x/onecloud/pkg/keystone/driver/cas"
+14
View File
@@ -1 +1,15 @@
// 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 modulebase // import "yunion.io/x/onecloud/pkg/mcclient/modulebase"
+14
View File
@@ -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 modulebase
import (
+14
View File
@@ -1 +1,15 @@
// 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/onecloud/pkg/multicloud/loader"
+1
View File
@@ -35,6 +35,7 @@ import (
// object storages
_ "yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph/provider"
_ "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider"
_ "yunion.io/x/onecloud/pkg/multicloud/objectstore/xsky/provider"
)
func init() {
+14
View File
@@ -1 +1,15 @@
// 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 "yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph"
@@ -1 +1,15 @@
// 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 "yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph/provider"
@@ -152,6 +152,15 @@ func (cli *SObjectStoreClient) S3Client() *s3cli.Client {
return cli.client
}
func (cli *SObjectStoreClient) GetClientRC() map[string]string {
return map[string]string{
"S3_ACCESS_KEY": cli.accessKey,
"S3_SECRET": cli.secret,
"S3_ACCESS_URL": cli.endpoint,
"S3_BACKEND": api.CLOUD_PROVIDER_GENERICS3,
}
}
///////////////////////////////// fake impletementations //////////////////////
func (cli *SObjectStoreClient) GetIZones() ([]cloudprovider.ICloudZone, error) {
+518
View File
@@ -0,0 +1,518 @@
// 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 xsky
import (
"context"
"fmt"
"io"
"math/rand"
"net/http"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/util/httputils"
)
type SXskyAdminApi struct {
endpoint string
username string
password string
token *sLoginResponse
client *http.Client
debug bool
}
func newXskyAdminApi(user, passwd, ep string, debug bool) *SXskyAdminApi {
return &SXskyAdminApi{
endpoint: ep,
username: user,
password: passwd,
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 *SXskyAdminApi) 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)
}
}
}
if api.isValidToken() {
req.Header.Set("xms-auth-token", api.token.Token.Uuid)
}
if api.debug {
log.Debugf("request: %s %s %s %s", method, urlStr, req.Header, body)
}
resp, err := api.client.Do(req)
return httputils.ParseJSONResponse(resp, err, api.debug)
}
type sLoginResponse struct {
Token struct {
Create time.Time
Expires time.Time
Roles []string
User struct {
Create time.Time
Name string
Email string
Enabled bool
Id int
PasswordLastUpdate time.Time
}
Uuid string
Valid bool
}
}
func (api *SXskyAdminApi) isValidToken() bool {
if api.token != nil && len(api.token.Token.Uuid) > 0 && api.token.Token.Expires.After(time.Now()) {
return true
} else {
return false
}
}
func (api *SXskyAdminApi) auth(ctx context.Context) (*sLoginResponse, error) {
input := STokenCreateReq{}
input.Auth.Identity.Password.User.Name = api.username
input.Auth.Identity.Password.User.Password = api.password
_, resp, err := api.jsonRequest(ctx, httputils.POST, "/api/v1/auth/tokens", nil, jsonutils.Marshal(input))
if err != nil {
return nil, errors.Wrap(err, "api.jsonRequest")
}
output := sLoginResponse{}
err = resp.Unmarshal(&output)
if err != nil {
return nil, errors.Wrap(err, "resp.Unmarshal")
}
return &output, err
}
type STokenCreateReq struct {
Auth STokenCreateReqAuth `json:"auth"`
}
type STokenCreateReqAuth struct {
Identity STokenCreateReqAuthIdentity `json:"identity"`
}
type STokenCreateReqAuthIdentity struct {
// password for auth
Password SAuthPasswordReq `json:"password,omitempty"`
// token for auth
Token SAuthTokenReq `json:"token,omitempty"`
}
type SAuthPasswordReq struct {
User SAuthPasswordReqUser `json:"user"`
}
type SAuthPasswordReqUser struct {
// user email for auth
Email string `json:"email,omitempty"`
// user id for auth
Id int64 `json:"id,omitzero"`
// user name or email for auth
Name string `json:"name,omitempty"`
// password for auth
Password string `json:"password"`
}
type SAuthTokenReq struct {
// uuid of authorized token
Uuid string `json:"uuid"`
}
func (api *SXskyAdminApi) authRequest(ctx context.Context, method httputils.THttpMethod, path string, hdr http.Header, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) {
if !api.isValidToken() {
loginResp, err := api.auth(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "api.auth")
}
api.token = loginResp
}
return api.jsonRequest(ctx, method, path, hdr, body)
}
type sUser struct {
BucketNum int
BucketQuotaMaxObjects int
BucketQuotaMaxSize int64
Create time.Time
DisplayName string
Email string
Id int
MaxBuckets int
Name string
OpMask string
Status string
Suspended bool
Update time.Time
UserQuotaMaxObjects int
UserQuotaMaxSize int64
samples []sSample
Keys []sKey
}
func (u sUser) getMinKey() string {
minKey := ""
for i := range u.Keys {
if len(minKey) == 0 || minKey > u.Keys[i].AccessKey {
minKey = u.Keys[i].AccessKey
}
}
return minKey
}
type sKey struct {
AccessKey string
Create time.Time
Id int
Reserved bool
SecretKey string
Status string
Type string
Update time.Time
User struct {
Id int
Name string
}
}
type sSample struct {
AllocatedObjects int
AllocatedSize int64
Create time.Time
DelOpsPm int
RxBandwidthKbyte int
RxOpsPm int
TxBandwidthKbyte int
TxOpsPm int
TotalDelOps int
TotalDelSuccessOps int
TotalRxBytes int64
TotalRxOps int
TotalRxSuccessOps int
TotalTxBytes int64
TotalTxOps int
TotalTxSuccessKbyte int
}
type sPaging struct {
Count int
Limit int
Offset int
TotalCount int
}
type sUsersResponse struct {
OsUsers []sUser
Paging sPaging
}
func (api *SXskyAdminApi) getUsers(ctx context.Context) ([]sUser, error) {
usrs := make([]sUser, 0)
totalCount := 0
for totalCount <= 0 || len(usrs) < totalCount {
_, resp, err := api.authRequest(ctx, httputils.GET, fmt.Sprintf("/api/v1/os-users/?limit=1000&offset=%d", len(usrs)), nil, nil)
if err != nil {
return nil, errors.Wrap(err, "api.authRequest")
}
output := sUsersResponse{}
err = resp.Unmarshal(&output)
if err != nil {
return nil, errors.Wrap(err, "resp.Unmarshal")
}
usrs = append(usrs, output.OsUsers...)
totalCount = output.Paging.TotalCount
}
return usrs, nil
}
func (api *SXskyAdminApi) findUserByAccessKey(ctx context.Context, accessKey string) (*sUser, *sKey, error) {
usrs, err := api.getUsers(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "api.getUsers")
}
for i := range usrs {
for j := range usrs[i].Keys {
if usrs[i].Keys[j].AccessKey == accessKey {
return &usrs[i], &usrs[i].Keys[j], nil
}
}
}
return nil, nil, httperrors.ErrNotFound
}
func (api *SXskyAdminApi) findFirstUserWithAccessKey(ctx context.Context) (*sUser, *sKey, error) {
usrs, err := api.getUsers(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "api.getUsers")
}
for i := range usrs {
if len(usrs[i].Keys) > 0 {
return &usrs[i], &usrs[i].Keys[0], nil
}
}
return nil, nil, httperrors.ErrNotFound
}
type sBucket struct {
ActionStatus string
AllUserPermission string
AuthUserPermission string
BucketPolicy string
Create time.Time
Flag struct {
Versioned bool
VersionsSuspended bool
Worm bool
}
Id int
// LifeCycle
MetadataSearchEnabled bool
Name string
NfsClientNum int
OsReplicationPathNum int
OsReplicationZoneNum int
// osZone
OsZoneUuid string
Owner struct {
Id string
Name string
}
OwnerPermission string
Policy sPolicy
PolicyEnabled bool
QuotaMaxObjects int
QuotaMaxSize int64
// RemteClusters
ReplicationUuid string
Samples []sSample
Shards int
Status string
Update time.Time
Virtual bool
// NfsGatewayMaps
}
type sPolicy struct {
BucketNum int
Compress bool
Create time.Time
Crypto bool
DataPool struct {
Id int
Name string
}
Default bool
Description string
Id int
IndexPool struct {
Id int
Name string
}
Name string
ObjectSizeThreshold int64
PolicyName string
Status string
Update time.Time
}
type sBucketsResponse struct {
OsBuckets []sBucket
Paging sPaging
}
func (api *SXskyAdminApi) getBuckets(ctx context.Context) ([]sBucket, error) {
buckets := make([]sBucket, 0)
totalCount := 0
for totalCount <= 0 || len(buckets) < totalCount {
_, resp, err := api.authRequest(ctx, httputils.GET, fmt.Sprintf("/api/v1/os-buckets/?limit=1000&offset=%d", len(buckets)), nil, nil)
if err != nil {
return nil, errors.Wrap(err, "api.authRequest")
}
output := sBucketsResponse{}
err = resp.Unmarshal(&output)
if err != nil {
return nil, errors.Wrap(err, "resp.Unmarshal")
}
buckets = append(buckets, output.OsBuckets...)
totalCount = output.Paging.TotalCount
}
return buckets, nil
}
func (api *SXskyAdminApi) getBucketByName(ctx context.Context, name string) (*sBucket, error) {
buckets, err := api.getBuckets(ctx)
if err != nil {
return nil, errors.Wrap(err, "api.getBuckets")
}
for i := range buckets {
if buckets[i].Name == name {
return &buckets[i], nil
}
}
return nil, cloudprovider.ErrNotFound
}
type sBucketQuotaInput struct {
OsBucket struct {
QuotaMaxSize int64
QuotaMaxObjects int
}
}
func (api *SXskyAdminApi) setBucketQuota(ctx context.Context, bucketId int, input sBucketQuotaInput) error {
_, _, err := api.authRequest(ctx, httputils.PATCH, fmt.Sprintf("/api/v1/os-buckets/%d", bucketId), nil, jsonutils.Marshal(&input))
if err != nil {
return errors.Wrap(err, "api.authRequest")
}
return nil
}
type sS3LbGroup struct {
ActionStatus string
Create time.Time
Description string
HttpsPort int
Id int
Name string
Port int
Roles []string
SearchHttpsPort int
SearchPort int
Status string
SyncPort int
Update time.Time
S3LoadBalancers []sS3LoadBalancer `json:"s3_load_balancers"`
}
type sS3LoadBalancer struct {
Create time.Time
Description string
Group struct {
Id int
Name string
Status string
}
Host struct {
AdminIp string
Id int
Name string
}
HttpsPort int
Id int
InterfaceName string
Ip string
Name string
Port int
Roles []string
Samples []struct {
ActiveAconnects int
CpuUtil float64
Create time.Time
DownBandwidthKbytes int64
FailureRequests int
MemUsagePercent float64
SuccessRequests int
UpBandwidthKbyte int64
}
SearchHttpsPort int
SearchPort int
SslCertificate interface{}
Status string
SyncPort int
Update time.Time
Vip string
VipMask int
Vips string
}
func (lb sS3LoadBalancer) GetGatewayEndpoint() string {
if lb.SslCertificate == nil {
return fmt.Sprintf("http://%s:%d", lb.Vip, lb.Port)
} else {
return fmt.Sprintf("https://%s:%d", lb.Vip, lb.HttpsPort)
}
}
type sS3LbGroupResponse struct {
S3LoadBalancerGroups []sS3LbGroup `json:"s3_load_balancer_groups"`
Paging sPaging
}
func (api *SXskyAdminApi) getS3LbGroup(ctx context.Context) ([]sS3LbGroup, error) {
lbGroups := make([]sS3LbGroup, 0)
totalCount := 0
for totalCount <= 0 || len(lbGroups) < totalCount {
_, resp, err := api.authRequest(ctx, httputils.GET, fmt.Sprintf("/api/v1/s3-load-balancer-groups/?limit=1000&offset=%d", len(lbGroups)), nil, nil)
if err != nil {
return nil, errors.Wrap(err, "api.authRequest")
}
output := sS3LbGroupResponse{}
err = resp.Unmarshal(&output)
if err != nil {
return nil, errors.Wrap(err, "resp.Unmarshal")
}
lbGroups = append(lbGroups, output.S3LoadBalancerGroups...)
totalCount = output.Paging.TotalCount
}
return lbGroups, nil
}
func (api *SXskyAdminApi) getS3GatewayEndpoint(ctx context.Context) (string, error) {
s3LbGrps, err := api.getS3LbGroup(ctx)
if err != nil {
return "", errors.Wrap(err, "api.getS3LbGroup")
}
lbs := make([]sS3LoadBalancer, 0)
for i := range s3LbGrps {
lbs = append(lbs, s3LbGrps[i].S3LoadBalancers...)
}
if len(lbs) == 0 {
return "", errors.Wrap(httperrors.ErrNotFound, "empty S3 Lb group")
}
lb := lbs[rand.Intn(len(lbs))]
return lb.GetGatewayEndpoint(), nil
}
+82
View File
@@ -0,0 +1,82 @@
// 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 xsky
import (
"context"
"strconv"
"time"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/multicloud/objectstore"
)
type SXskyBucket struct {
*objectstore.SBucket
client *SXskyClient
}
func (b *SXskyBucket) 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 *SXskyBucket) GetLimit() cloudprovider.SBucketStats {
limit := cloudprovider.SBucketStats{}
bucket, err := b.client.adminApi.getBucketByName(context.Background(), b.Name)
if err != nil {
log.Errorf("b.client.adminApi.getBucketByName error %s", err)
} else {
limit.SizeBytes = bucket.QuotaMaxSize
limit.ObjectCount = bucket.QuotaMaxObjects
}
return limit
}
func (b *SXskyBucket) SetLimit(limit cloudprovider.SBucketStats) error {
bucket, err := b.client.adminApi.getBucketByName(context.Background(), b.Name)
if err != nil {
return errors.Wrap(err, "b.client.adminApi.getBucketByName")
}
input := sBucketQuotaInput{}
input.OsBucket.QuotaMaxObjects = limit.ObjectCount
input.OsBucket.QuotaMaxSize = limit.SizeBytes
err = b.client.adminApi.setBucketQuota(context.Background(), bucket.Id, input)
if err != nil {
return errors.Wrap(err, "b.client.adminApi.setBucketQuota")
}
cloudprovider.Wait(time.Second, 30*time.Second, func() (bool, error) {
target := b.GetLimit()
if target.SizeBytes == limit.SizeBytes && target.ObjectCount == limit.ObjectCount {
return true, nil
}
return false, nil
})
return nil
}
+15
View File
@@ -0,0 +1,15 @@
// 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 xsky // import "yunion.io/x/onecloud/pkg/multicloud/objectstore/xsky"
@@ -0,0 +1,15 @@
// 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 "yunion.io/x/onecloud/pkg/multicloud/objectstore/xsky/provider"
@@ -0,0 +1,55 @@
// 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"
s3provider "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider"
"yunion.io/x/onecloud/pkg/multicloud/objectstore/xsky"
)
type SXskyProviderFactory struct {
s3provider.SObjectStoreProviderFactory
}
func (self *SXskyProviderFactory) GetId() string {
return api.CLOUD_PROVIDER_XSKY
}
func (self *SXskyProviderFactory) GetName() string {
return api.CLOUD_PROVIDER_XSKY
}
func (self *SXskyProviderFactory) GetProvider(providerId, providerName, url, account, secret string) (cloudprovider.ICloudProvider, error) {
client, err := xsky.NewXskyClient(providerId, providerName, url, account, secret, false)
if err != nil {
return nil, err
}
return s3provider.NewObjectStoreProvider(self, client), nil
}
func (self *SXskyProviderFactory) GetClientRC(url, account, secret string) (map[string]string, error) {
client, err := xsky.NewXskyClient("", "", url, account, secret, false)
if err != nil {
return nil, err
}
return client.GetClientRC(), nil
}
func init() {
factory := SXskyProviderFactory{}
cloudprovider.RegisterFactory(&factory)
}
+154
View File
@@ -0,0 +1,154 @@
// 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 xsky
import (
"context"
"fmt"
"strings"
"yunion.io/x/jsonutils"
"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/multicloud/objectstore"
)
type SXskyClient struct {
*objectstore.SObjectStoreClient
adminApi *SXskyAdminApi
adminUser *sUser
initAccount string
}
func parseAccount(account string) (user string, accessKey string) {
accountInfo := strings.Split(account, "/")
user = accountInfo[0]
if len(accountInfo) > 1 {
accessKey = strings.Join(accountInfo[1:], "/")
}
return
}
func NewXskyClient(providerId string, providerName string, endpoint string, account string, password string, isDebug bool) (*SXskyClient, error) {
usrname, accessKey := parseAccount(account)
adminApi := newXskyAdminApi(usrname, password, endpoint, isDebug)
gwEp, err := adminApi.getS3GatewayEndpoint(context.Background())
if err != nil {
return nil, errors.Wrap(err, "adminApi.getS3GatewayIP")
}
var usr *sUser
var key *sKey
if len(accessKey) > 0 {
usr, key, err = adminApi.findUserByAccessKey(context.Background(), accessKey)
if err != nil {
return nil, errors.Wrap(err, "adminApi.findUserByAccessKey")
}
} else {
usr, key, err = adminApi.findFirstUserWithAccessKey(context.Background())
if err != nil {
return nil, errors.Wrap(err, "adminApi.findFirstUserWithAccessKey")
}
}
s3store, err := objectstore.NewObjectStoreClientAndFetch(providerId, providerName, gwEp, accessKey, key.SecretKey, isDebug, false)
if err != nil {
return nil, errors.Wrap(err, "NewObjectStoreClient")
}
client := SXskyClient{
SObjectStoreClient: s3store,
adminApi: adminApi,
adminUser: usr,
}
if len(accessKey) > 0 {
client.initAccount = account
}
client.SetVirtualObject(&client)
err = client.FetchBuckets()
if err != nil {
return nil, errors.Wrap(err, "fetchBuckets")
}
return &client, nil
}
func (cli *SXskyClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
if len(cli.initAccount) > 0 {
return []cloudprovider.SSubAccount{
{
Account: cli.initAccount,
Name: cli.adminUser.Name,
HealthStatus: api.CLOUD_PROVIDER_HEALTH_NORMAL,
},
}, nil
}
usrs, err := cli.adminApi.getUsers(context.Background())
if err != nil {
return nil, errors.Wrap(err, "api.getUsers")
}
subAccounts := make([]cloudprovider.SSubAccount, 0)
for i := range usrs {
ak := usrs[i].getMinKey()
if len(ak) > 0 {
subAccount := cloudprovider.SSubAccount{
Account: fmt.Sprintf("%s/%s", cli.adminApi.username, ak),
Name: usrs[i].Name,
HealthStatus: api.CLOUD_PROVIDER_HEALTH_NORMAL,
}
subAccounts = append(subAccounts, subAccount)
}
}
return subAccounts, nil
}
func (cli *SXskyClient) GetAccountId() string {
return cli.adminApi.username
}
func (cli *SXskyClient) GetVersion() string {
return ""
}
func (cli *SXskyClient) About() jsonutils.JSONObject {
about := jsonutils.NewDict()
if cli.adminUser != nil {
about.Add(jsonutils.Marshal(cli.adminUser), "admin_user")
}
return about
}
func (cli *SXskyClient) GetProvider() string {
return api.CLOUD_PROVIDER_XSKY
}
func (cli *SXskyClient) NewBucket(bucket s3cli.BucketInfo) cloudprovider.ICloudBucket {
if cli.SObjectStoreClient == nil {
return nil
}
generalBucket := cli.SObjectStoreClient.NewBucket(bucket)
return &SXskyBucket{
SBucket: generalBucket.(*objectstore.SBucket),
client: cli,
}
}
+14
View File
@@ -1 +1,15 @@
// 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 handlers // import "yunion.io/x/onecloud/pkg/s3gateway/handlers"
+14
View File
@@ -1 +1,15 @@
// 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 models // import "yunion.io/x/onecloud/pkg/s3gateway/models"
+14
View File
@@ -1 +1,15 @@
// 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 session // import "yunion.io/x/onecloud/pkg/s3gateway/session"
+14
View File
@@ -1 +1,15 @@
// 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 "yunion.io/x/onecloud/pkg/util/s3auth"
+14
View File
@@ -1 +1,15 @@
// 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 validate // import "yunion.io/x/onecloud/pkg/util/validate"
+1 -1
View File
@@ -768,7 +768,7 @@ yunion.io/x/jsonutils
# yunion.io/x/log v0.0.0-20190629062853-9f6483a7103d
yunion.io/x/log
yunion.io/x/log/hooks
# yunion.io/x/pkg v0.0.0-20190902093114-59ba154a6861
# yunion.io/x/pkg v0.0.0-20190917154624-e89986e4e4d8
yunion.io/x/pkg/util/version
yunion.io/x/pkg/utils
yunion.io/x/pkg/util/regutils
+199
View File
@@ -0,0 +1,199 @@
// 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 errors
import (
"errors"
"fmt"
)
// MessageCountMap contains occurance for each error message.
type MessageCountMap map[string]int
// Aggregate represents an object that contains multiple errors, but does not
// necessarily have singular semantic meaning.
type Aggregate interface {
error
Errors() []error
}
// NewAggregate converts a slice of errors into an Aggregate interface, which
// is itself an implementation of the error interface. If the slice is empty,
// this returns nil.
// It will check if any of the element of input error list is nil, to avoid
// nil pointer panic when call Error().
func NewAggregate(errlist []error) Aggregate {
if len(errlist) == 0 {
return nil
}
// In case of input error list contains nil
var errs []error
for _, e := range errlist {
if e != nil {
errs = append(errs, e)
}
}
if len(errs) == 0 {
return nil
}
return aggregate(errs)
}
// This helper implements the error and Errors interfaces. Keeping it private
// prevents people from making an aggregate of 0 errors, which is not
// an error, but does satisfy the error interface.
type aggregate []error
// Error is part of the error interface.
func (agg aggregate) Error() string {
if len(agg) == 0 {
// This should never happen, really.
return ""
}
if len(agg) == 1 {
return agg[0].Error()
}
result := fmt.Sprintf("[%s", agg[0].Error())
for i := 1; i < len(agg); i++ {
result += fmt.Sprintf(", %s", agg[i].Error())
}
result += "]"
return result
}
// Errors is part of the Aggregate interface.
func (agg aggregate) Errors() []error {
return []error(agg)
}
// Matcher is used to match errors. Returns true if the error matches.
type Matcher func(error) bool
// FilterOut removes all errors that match any of the matchers from the input
// error. If the input is a singular error, only that error is tested. If the
// input implements the Aggregate interface, the list of errors will be
// processed recursively.
//
// This can be used, for example, to remove known-OK errors (such as io.EOF or
// os.PathNotFound) from a list of errors.
func FilterOut(err error, fns ...Matcher) error {
if err == nil {
return nil
}
if agg, ok := err.(Aggregate); ok {
return NewAggregate(filterErrors(agg.Errors(), fns...))
}
if !matchesError(err, fns...) {
return err
}
return nil
}
// matchesError returns true if any Matcher returns true
func matchesError(err error, fns ...Matcher) bool {
for _, fn := range fns {
if fn(err) {
return true
}
}
return false
}
// filterErrors returns any errors (or nested errors, if the list contains
// nested Errors) for which all fns return false. If no errors
// remain a nil list is returned. The resulting silec will have all
// nested slices flattened as a side effect.
func filterErrors(list []error, fns ...Matcher) []error {
result := []error{}
for _, err := range list {
r := FilterOut(err, fns...)
if r != nil {
result = append(result, r)
}
}
return result
}
// Flatten takes an Aggregate, which may hold other Aggregates in arbitrary
// nesting, and flattens them all into a single Aggregate, recursively.
func Flatten(agg Aggregate) Aggregate {
result := []error{}
if agg == nil {
return nil
}
for _, err := range agg.Errors() {
if a, ok := err.(Aggregate); ok {
r := Flatten(a)
if r != nil {
result = append(result, r.Errors()...)
}
} else {
if err != nil {
result = append(result, err)
}
}
}
return NewAggregate(result)
}
// CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate
func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate {
if m == nil {
return nil
}
result := make([]error, 0, len(m))
for errStr, count := range m {
var countStr string
if count > 1 {
countStr = fmt.Sprintf(" (repeated %v times)", count)
}
result = append(result, fmt.Errorf("%v%v", errStr, countStr))
}
return NewAggregate(result)
}
// Reduce will return err or, if err is an Aggregate and only has one item,
// the first item in the aggregate.
func Reduce(err error) error {
if agg, ok := err.(Aggregate); ok && err != nil {
switch len(agg.Errors()) {
case 1:
return agg.Errors()[0]
case 0:
return nil
}
}
return err
}
// AggregateGoroutines runs the provided functions in parallel, stuffing all
// non-nil errors into the returned Aggregate.
// Returns nil if all the functions complete successfully.
func AggregateGoroutines(funcs ...func() error) Aggregate {
errChan := make(chan error, len(funcs))
for _, f := range funcs {
go func(f func() error) { errChan <- f() }(f)
}
errs := make([]error, 0)
for i := 0; i < cap(errChan); i++ {
if err := <-errChan; err != nil {
errs = append(errs, err)
}
}
return NewAggregate(errs)
}
// ErrPreconditionViolated is returned when the precondition is violated
var ErrPreconditionViolated = errors.New("precondition is violated")
+3 -3
View File
@@ -65,9 +65,9 @@ func init() {
MONTH_REG = regexp.MustCompile(`^\d{4}-\d{2}$`)
DATE_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`)
DATE_COMPACT_REG = regexp.MustCompile(`^\d{8}$`)
ISO_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$`)
ISO_NO_SECOND_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z$`)
FULLISO_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$`)
ISO_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|\+\d{2}:\d{2})$`)
ISO_NO_SECOND_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(Z|\+\d{2}:\d{2})$`)
FULLISO_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3,9}(Z|\+\d{2}:\d{2})$`)
COMPACT_TIME_REG = regexp.MustCompile(`^\d{14}$`)
ZSTACK_TIME_REG = regexp.MustCompile(`^\w+ \d{1,2}, \d{4} \d{1,2}:\d{1,2}:\d{1,2} (AM|PM)$`) //ZStack time format "Apr 1, 2019 3:23:17 PM"
MYSQL_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`)
+26 -4
View File
@@ -17,6 +17,7 @@ package timeutils
import (
"fmt"
"time"
"strings"
"yunion.io/x/pkg/util/regutils"
)
@@ -42,9 +43,10 @@ func Localify(now time.Time) time.Time {
}
const (
IsoTimeFormat = "2006-01-02T15:04:05Z"
IsoNoSecondTimeFormat = "2006-01-02T15:04Z"
FullIsoTimeFormat = "2006-01-02T15:04:05.000000Z"
IsoTimeFormat = "2006-01-02T15:04:05Z07:00"
IsoNoSecondTimeFormat = "2006-01-02T15:04Z07:00"
FullIsoTimeFormat = "2006-01-02T15:04:05.000000Z07:00"
FullIsoNanoTimeFormat = "2006-01-02T15:04:05.000000000Z07:00"
MysqlTimeFormat = "2006-01-02 15:04:05"
NormalTimeFormat = "2006-01-02T15:04:05"
FullNormalTimeFormat = "2006-01-02T15:04:05.000000"
@@ -68,6 +70,10 @@ func FullIsoTime(now time.Time) string {
return Utcify(now).Format(FullIsoTimeFormat)
}
func FullIsoNanoTime(now time.Time) string {
return Utcify(now).Format(FullIsoNanoTimeFormat)
}
func MysqlTime(now time.Time) string {
return Utcify(now).Format(MysqlTimeFormat)
}
@@ -100,8 +106,23 @@ func ParseIsoNoSecondTime(str string) (time.Time, error) {
return time.Parse(IsoNoSecondTimeFormat, str)
}
func toFullIsoNanoTimeFormat(str string) string {
// 2019-09-17T20:50:17.66667134+08:00
subsecStr := str[20:]
pos := strings.IndexByte(subsecStr, 'Z')
if pos < 0 {
pos = strings.IndexByte(subsecStr, '+')
}
leftOver := subsecStr[pos:]
subsecStr = subsecStr[:pos]
for len(subsecStr) < 9 {
subsecStr += "0"
}
return str[:20] + subsecStr + leftOver
}
func ParseFullIsoTime(str string) (time.Time, error) {
return time.Parse(FullIsoTimeFormat, str)
return time.Parse(FullIsoNanoTimeFormat, toFullIsoNanoTimeFormat(str))
}
func ParseMysqlTime(str string) (time.Time, error) {
@@ -137,6 +158,7 @@ func ParseZStackDate(str string) (time.Time, error) {
}
func ParseTimeStr(str string) (time.Time, error) {
str = strings.TrimSpace(str)
if regutils.MatchFullISOTime(str) {
return ParseFullIsoTime(str)
} else if regutils.MatchISOTime(str) {