Browse Source

Merge pull request #181 from aliyun/dev_versionning

add versionning
wangtaowei 6 năm trước cách đây
mục cha
commit
f8d6d645dc
18 tập tin đã thay đổi với 2397 bổ sung203 xóa
  1. 173 34
      oss/bucket.go
  2. 1322 9
      oss/bucket_test.go
  3. 129 8
      oss/client.go
  4. 123 71
      oss/client_test.go
  5. 16 10
      oss/conn.go
  6. 14 1
      oss/const.go
  7. 8 15
      oss/download.go
  8. 203 0
      oss/download_test.go
  9. 12 20
      oss/multicopy.go
  10. 92 0
      oss/multicopy_test.go
  11. 14 2
      oss/multipart.go
  12. 1 1
      oss/multipart_test.go
  13. 58 0
      oss/option.go
  14. 18 0
      oss/option_test.go
  15. 141 8
      oss/type.go
  16. 6 6
      oss/type_test.go
  17. 6 18
      oss/upload.go
  18. 61 0
      oss/upload_test.go

+ 173 - 34
oss/bucket.go

@@ -233,7 +233,17 @@ func (bucket Bucket) DoGetObject(request *GetObjectRequest, options []Option) (*
 //
 func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) {
 	var out CopyObjectResult
-	options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
+
+	//first find version id
+	versionIdKey := "versionId"
+	versionId, _ := findOption(options, versionIdKey, nil)
+	if versionId == nil {
+		options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
+	} else {
+		options = deleteOption(options, versionIdKey)
+		options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string)))
+	}
+
 	params := map[string]interface{}{}
 	resp, err := bucket.do("PUT", destObjectKey, params, options, nil, nil)
 	if err != nil {
@@ -281,7 +291,17 @@ func (bucket Bucket) CopyObjectFrom(srcBucketName, srcObjectKey, destObjectKey s
 
 func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) {
 	var out CopyObjectResult
-	options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
+
+	//first find version id
+	versionIdKey := "versionId"
+	versionId, _ := findOption(options, versionIdKey, nil)
+	if versionId == nil {
+		options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey)))
+	} else {
+		options = deleteOption(options, versionIdKey)
+		options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string)))
+	}
+
 	headers := make(map[string]string)
 	err := handleOptions(headers, options)
 	if err != nil {
@@ -289,6 +309,14 @@ func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, op
 	}
 	params := map[string]interface{}{}
 	resp, err := bucket.Client.Conn.Do("PUT", destBucketName, destObjectKey, params, headers, nil, 0, nil)
+
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return out, err
 	}
@@ -357,6 +385,14 @@ func (bucket Bucket) DoAppendObject(request *AppendObjectRequest, options []Opti
 	handleOptions(headers, opts)
 	resp, err := bucket.Client.Conn.Do("POST", bucket.BucketName, request.ObjectKey, params, headers,
 		request.Reader, initCRC, listener)
+
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return nil, err
 	}
@@ -384,9 +420,9 @@ func (bucket Bucket) DoAppendObject(request *AppendObjectRequest, options []Opti
 //
 // error    it's nil if no error, otherwise it's an error object.
 //
-func (bucket Bucket) DeleteObject(objectKey string) error {
-	params := map[string]interface{}{}
-	resp, err := bucket.do("DELETE", objectKey, params, nil, nil, nil)
+func (bucket Bucket) DeleteObject(objectKey string, options ...Option) error {
+	params, _ := getRawParams(options)
+	resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil)
 	if err != nil {
 		return err
 	}
@@ -409,6 +445,63 @@ func (bucket Bucket) DeleteObjects(objectKeys []string, options ...Option) (Dele
 	for _, key := range objectKeys {
 		dxml.Objects = append(dxml.Objects, DeleteObject{Key: key})
 	}
+
+	isQuiet, _ := findOption(options, deleteObjectsQuiet, false)
+	dxml.Quiet = isQuiet.(bool)
+
+	bs, err := xml.Marshal(dxml)
+	if err != nil {
+		return out, err
+	}
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+
+	contentType := http.DetectContentType(buffer.Bytes())
+	options = append(options, ContentType(contentType))
+	sum := md5.Sum(bs)
+	b64 := base64.StdEncoding.EncodeToString(sum[:])
+	options = append(options, ContentMD5(b64))
+
+	params := map[string]interface{}{}
+	params["delete"] = nil
+	params["encoding-type"] = "url"
+
+	resp, err := bucket.do("POST", "", params, options, buffer, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.Body.Close()
+
+	deletedResult := DeleteObjectVersionsResult{}
+	if !dxml.Quiet {
+		if err = xmlUnmarshal(resp.Body, &deletedResult); err == nil {
+			err = decodeDeleteObjectsResult(&deletedResult)
+		}
+	}
+
+	// Keep compatibility:need convert to struct DeleteObjectsResult
+	out.XMLName = deletedResult.XMLName
+	for _, v := range deletedResult.DeletedObjectsDetail {
+		out.DeletedObjects = append(out.DeletedObjects, v.Key)
+	}
+
+	return out, err
+}
+
+// DeleteObjectVersions deletes multiple object versions.
+//
+// objectVersions    the object keys and versions to delete.
+// options    the options for deleting objects.
+//            Supported option is DeleteObjectsQuiet which means it will not return error even deletion failed (not recommended). By default it's not used.
+//
+// DeleteObjectVersionsResult    the result object.
+// error    it's nil if no error, otherwise it's an error object.
+//
+func (bucket Bucket) DeleteObjectVersions(objectVersions []DeleteObject, options ...Option) (DeleteObjectVersionsResult, error) {
+	out := DeleteObjectVersionsResult{}
+	dxml := deleteXML{}
+	dxml.Objects = objectVersions
+
 	isQuiet, _ := findOption(options, deleteObjectsQuiet, false)
 	dxml.Quiet = isQuiet.(bool)
 
@@ -509,6 +602,32 @@ func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) {
 	return out, err
 }
 
+// ListObjectVersions lists objects of all versions under the current bucket.
+func (bucket Bucket) ListObjectVersions(options ...Option) (ListObjectVersionsResult, error) {
+	var out ListObjectVersionsResult
+
+	options = append(options, EncodingType("url"))
+	params, err := getRawParams(options)
+	if err != nil {
+		return out, err
+	}
+	params["versions"] = nil
+
+	resp, err := bucket.do("GET", "", params, options, nil, nil)
+	if err != nil {
+		return out, err
+	}
+	defer resp.Body.Close()
+
+	err = xmlUnmarshal(resp.Body, &out)
+	if err != nil {
+		return out, err
+	}
+
+	err = decodeListObjectVersionsResult(&out)
+	return out, err
+}
+
 // SetObjectMeta sets the metadata of the Object.
 //
 // objectKey    object
@@ -533,7 +652,7 @@ func (bucket Bucket) SetObjectMeta(objectKey string, options ...Option) error {
 // error    it's nil if no error, otherwise it's an error object.
 //
 func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) (http.Header, error) {
-	params := map[string]interface{}{}
+	params, _ := getRawParams(options)
 	resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil)
 	if err != nil {
 		return nil, err
@@ -554,7 +673,7 @@ func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option)
 // error    it's nil if no error, otherwise it's an error object.
 //
 func (bucket Bucket) GetObjectMeta(objectKey string, options ...Option) (http.Header, error) {
-	params := map[string]interface{}{}
+	params, _ := getRawParams(options)
 	params["objectMeta"] = nil
 	//resp, err := bucket.do("GET", objectKey, "?objectMeta", "", nil, nil, nil)
 	resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil)
@@ -582,9 +701,9 @@ func (bucket Bucket) GetObjectMeta(objectKey string, options ...Option) (http.He
 //
 // error    it's nil if no error, otherwise it's an error object.
 //
-func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType) error {
-	options := []Option{ObjectACL(objectACL)}
-	params := map[string]interface{}{}
+func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType, options ...Option) error {
+	options = append(options, ObjectACL(objectACL))
+	params, _ := getRawParams(options)
 	params["acl"] = nil
 	resp, err := bucket.do("PUT", objectKey, params, options, nil, nil)
 	if err != nil {
@@ -601,11 +720,11 @@ func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType) error {
 // GetObjectACLResult    the result object when error is nil. GetObjectACLResult.Acl is the object ACL.
 // error    it's nil if no error, otherwise it's an error object.
 //
-func (bucket Bucket) GetObjectACL(objectKey string) (GetObjectACLResult, error) {
+func (bucket Bucket) GetObjectACL(objectKey string, options ...Option) (GetObjectACLResult, error) {
 	var out GetObjectACLResult
-	params := map[string]interface{}{}
+	params, _ := getRawParams(options)
 	params["acl"] = nil
-	resp, err := bucket.do("GET", objectKey, params, nil, nil, nil)
+	resp, err := bucket.do("GET", objectKey, params, options, nil, nil)
 	if err != nil {
 		return out, err
 	}
@@ -630,7 +749,7 @@ func (bucket Bucket) GetObjectACL(objectKey string) (GetObjectACLResult, error)
 //
 func (bucket Bucket) PutSymlink(symObjectKey string, targetObjectKey string, options ...Option) error {
 	options = append(options, symlinkTarget(url.QueryEscape(targetObjectKey)))
-	params := map[string]interface{}{}
+	params, _ := getRawParams(options)
 	params["symlink"] = nil
 	resp, err := bucket.do("PUT", symObjectKey, params, options, nil, nil)
 	if err != nil {
@@ -648,10 +767,10 @@ func (bucket Bucket) PutSymlink(symObjectKey string, targetObjectKey string, opt
 // error    it's nil if no error, otherwise it's an error object.
 //          When error is nil, the target file key is in the X-Oss-Symlink-Target header of the returned object.
 //
-func (bucket Bucket) GetSymlink(objectKey string) (http.Header, error) {
-	params := map[string]interface{}{}
+func (bucket Bucket) GetSymlink(objectKey string, options ...Option) (http.Header, error) {
+	params, _ := getRawParams(options)
 	params["symlink"] = nil
-	resp, err := bucket.do("GET", objectKey, params, nil, nil, nil)
+	resp, err := bucket.do("GET", objectKey, params, options, nil, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -678,10 +797,10 @@ func (bucket Bucket) GetSymlink(objectKey string) (http.Header, error) {
 //
 // error    it's nil if no error, otherwise it's an error object.
 //
-func (bucket Bucket) RestoreObject(objectKey string) error {
-	params := map[string]interface{}{}
+func (bucket Bucket) RestoreObject(objectKey string, options ...Option) error {
+	params, _ := getRawParams(options)
 	params["restore"] = nil
-	resp, err := bucket.do("POST", objectKey, params, nil, nil, nil)
+	resp, err := bucket.do("POST", objectKey, params, options, nil, nil)
 	if err != nil {
 		return err
 	}
@@ -911,9 +1030,9 @@ func (bucket Bucket) DoGetObjectWithURL(signedURL string, options []Option) (*Ge
 //
 // error    it's nil if no error, otherwise it's an error object.
 //
-func (bucket Bucket) ProcessObject(objectKey string, process string) (ProcessObjectResult, error) {
+func (bucket Bucket) ProcessObject(objectKey string, process string, options ...Option) (ProcessObjectResult, error) {
 	var out ProcessObjectResult
-	params := map[string]interface{}{}
+	params, _ := getRawParams(options)
 	params["x-oss-process"] = nil
 	processData := fmt.Sprintf("%v=%v", "x-oss-process", process)
 	data := strings.NewReader(processData)
@@ -935,7 +1054,7 @@ func (bucket Bucket) ProcessObject(objectKey string, process string) (ProcessObj
 //
 // error        nil if success, otherwise error
 //
-func (bucket Bucket) PutObjectTagging(objectKey string, tagging Tagging) error {
+func (bucket Bucket) PutObjectTagging(objectKey string, tagging Tagging, options ...Option) error {
 	bs, err := xml.Marshal(tagging)
 	if err != nil {
 		return err
@@ -944,9 +1063,9 @@ func (bucket Bucket) PutObjectTagging(objectKey string, tagging Tagging) error {
 	buffer := new(bytes.Buffer)
 	buffer.Write(bs)
 
-	params := map[string]interface{}{}
+	params, _ := getRawParams(options)
 	params["tagging"] = nil
-	resp, err := bucket.do("PUT", objectKey, params, nil, buffer, nil)
+	resp, err := bucket.do("PUT", objectKey, params, options, buffer, nil)
 	if err != nil {
 		return err
 	}
@@ -962,13 +1081,13 @@ func (bucket Bucket) PutObjectTagging(objectKey string, tagging Tagging) error {
 //
 // Tagging
 // error      nil if success, otherwise error
-//
-func (bucket Bucket) GetObjectTagging(objectKey string) (GetObjectTaggingResult, error) {
+
+func (bucket Bucket) GetObjectTagging(objectKey string, options ...Option) (GetObjectTaggingResult, error) {
 	var out GetObjectTaggingResult
-	params := map[string]interface{}{}
+	params, _ := getRawParams(options)
 	params["tagging"] = nil
 
-	resp, err := bucket.do("GET", objectKey, params, nil, nil, nil)
+	resp, err := bucket.do("GET", objectKey, params, options, nil, nil)
 	if err != nil {
 		return out, err
 	}
@@ -985,15 +1104,15 @@ func (bucket Bucket) GetObjectTagging(objectKey string) (GetObjectTaggingResult,
 //
 // error      nil if success, otherwise error
 //
-func (bucket Bucket) DeleteObjectTagging(objectKey string) error {
-	params := map[string]interface{}{}
+func (bucket Bucket) DeleteObjectTagging(objectKey string, options ...Option) error {
+	params, _ := getRawParams(options)
 	params["tagging"] = nil
 
 	if objectKey == "" {
 		return fmt.Errorf("invalid argument: object name is empty")
 	}
 
-	resp, err := bucket.do("DELETE", objectKey, params, nil, nil, nil)
+	resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil)
 	if err != nil {
 		return err
 	}
@@ -1010,8 +1129,18 @@ func (bucket Bucket) do(method, objectName string, params map[string]interface{}
 	if err != nil {
 		return nil, err
 	}
-	return bucket.Client.Conn.Do(method, bucket.BucketName, objectName,
+
+	resp, err := bucket.Client.Conn.Do(method, bucket.BucketName, objectName,
 		params, headers, data, 0, listener)
+
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
+	return resp, err
 }
 
 func (bucket Bucket) doURL(method HTTPMethod, signedURL string, params map[string]interface{}, options []Option,
@@ -1021,7 +1150,17 @@ func (bucket Bucket) doURL(method HTTPMethod, signedURL string, params map[strin
 	if err != nil {
 		return nil, err
 	}
-	return bucket.Client.Conn.DoURL(method, signedURL, headers, data, 0, listener)
+
+	resp, err := bucket.Client.Conn.DoURL(method, signedURL, headers, data, 0, listener)
+
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
+	return resp, err
 }
 
 func (bucket Bucket) getConfig() *Config {

+ 1322 - 9
oss/bucket_test.go

@@ -126,13 +126,14 @@ func (s *OssBucketSuite) TearDownTest(c *C) {
 }
 
 // TestPutObject
-func (s *OssBucketSuite) TestPutObject(c *C) {
+func (s *OssBucketSuite) TestPutObjectOnly(c *C) {
 	objectName := objectNamePrefix + randStr(8)
 	objectValue := "大江东去,浪淘尽,千古风流人物。 故垒西边,人道是、三国周郎赤壁。 乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。" +
 		"遥想公谨当年,小乔初嫁了,雄姿英发。 羽扇纶巾,谈笑间、樯橹灰飞烟灭。故国神游,多情应笑我,早生华发,人生如梦,一尊还酹江月。"
 
 	// Put string
-	err := s.bucket.PutObject(objectName, strings.NewReader(objectValue))
+	var respHeader http.Header
+	err := s.bucket.PutObject(objectName, strings.NewReader(objectValue), GetResponseHeader(&respHeader))
 	c.Assert(err, IsNil)
 
 	// Check
@@ -2384,7 +2385,6 @@ func putObjectRoutin(bucket *Bucket, object string, textBuffer *string, notifyCh
 }
 
 func (s *OssBucketSuite) TestPutManyObjectLimitSpeed(c *C) {
-
 	// create client and bucket
 	client, err := New(endpoint, accessID, accessKey)
 	c.Assert(err, IsNil)
@@ -2784,11 +2784,11 @@ func (s *OssBucketSuite) TestPutObjectTagging(c *C) {
 	objectName := objectNamePrefix + randStr(8)
 	tag1 := Tag{
 		Key:   randStr(8),
-		Value: randStr(16),
+		Value: randStr(9),
 	}
 	tag2 := Tag{
-		Key:   randStr(8),
-		Value: randStr(16),
+		Key:   randStr(10),
+		Value: randStr(11),
 	}
 	tagging := Tagging{
 		Tags: []Tag{tag1, tag2},
@@ -2903,11 +2903,11 @@ func (s *OssBucketSuite) TestGetObjectTagging(c *C) {
 	objectName := objectNamePrefix + randStr(8)
 	tag1 := Tag{
 		Key:   randStr(8),
-		Value: randStr(16),
+		Value: randStr(9),
 	}
 	tag2 := Tag{
-		Key:   randStr(8),
-		Value: randStr(16),
+		Key:   randStr(10),
+		Value: randStr(11),
 	}
 
 	taggingInfo := Tagging{
@@ -3004,3 +3004,1316 @@ func (s *OssBucketSuite) TestDeleteObjectTagging(c *C) {
 
 	s.bucket.DeleteObject(objectName)
 }
+
+func (s *OssBucketSuite) TestVersioningBucketVerison(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	// Get default bucket info
+	bucketResult, err := client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+
+	c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+	c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "")
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err = client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+	// put bucket version:Suspended
+	versioningConfig.Status = string(VersionSuspended)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err = client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionSuspended))
+
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningPutAndGetObject(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err := client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	// get object v1
+	body, err := bucket.GetObject(objectName, VersionId(versionIdV1))
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	body.Close()
+	c.Assert(str, Equals, contextV1)
+
+	// get object v2
+	body, err = bucket.GetObject(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	body.Close()
+	c.Assert(str, Equals, contextV2)
+
+	// get object without version
+	body, err = bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	body.Close()
+	c.Assert(str, Equals, contextV2)
+
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningHeadObject(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err := client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	// head object v1
+	headResultV1, err := bucket.GetObjectMeta(objectName, VersionId(versionIdV1))
+	objLen, err := strconv.Atoi(headResultV1.Get("Content-Length"))
+	c.Assert(objLen, Equals, len(contextV1))
+
+	headResultV1, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV1))
+	objLen, err = strconv.Atoi(headResultV1.Get("Content-Length"))
+	c.Assert(objLen, Equals, len(contextV1))
+
+	// head object v2
+	headResultV2, err := bucket.GetObjectMeta(objectName, VersionId(versionIdV2))
+	objLen, err = strconv.Atoi(headResultV2.Get("Content-Length"))
+	c.Assert(objLen, Equals, len(contextV2))
+
+	headResultV2, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV2))
+	objLen, err = strconv.Atoi(headResultV2.Get("Content-Length"))
+	c.Assert(objLen, Equals, len(contextV2))
+
+	// head object without version
+	// head object v2
+	headResult, err := bucket.GetObjectMeta(objectName)
+	objLen, err = strconv.Atoi(headResult.Get("Content-Length"))
+	c.Assert(objLen, Equals, len(contextV2))
+
+	headResult, err = bucket.GetObjectDetailedMeta(objectName)
+	objLen, err = strconv.Atoi(headResultV2.Get("Content-Length"))
+	c.Assert(objLen, Equals, len(contextV2))
+
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningDeleteLatestVersionObject(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err := client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	// delete v2 object:permently delete
+	options := []Option{VersionId(versionIdV2), GetResponseHeader(&respHeader)}
+	err = bucket.DeleteObject(objectName, options...)
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, versionIdV2)
+
+	// get v2 object failure
+	body, err := bucket.GetObject(objectName, VersionId(versionIdV2))
+	c.Assert(err, NotNil)
+	c.Assert(err.(ServiceError).Code, Equals, "NoSuchVersion")
+
+	// get v1 object success
+	body, err = bucket.GetObject(objectName, VersionId(versionIdV1))
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	body.Close()
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, contextV1)
+
+	// get default object success:v1
+	body, err = bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	body.Close()
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, contextV1)
+
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningDeleteOldVersionObject(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err := client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	// delete v1 object:permently delete
+	options := []Option{VersionId(versionIdV1), GetResponseHeader(&respHeader)}
+	err = bucket.DeleteObject(objectName, options...)
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, versionIdV1)
+
+	// get v2 object success
+	body, err := bucket.GetObject(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	body.Close()
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, contextV2)
+
+	// get v1 object faliure
+	body, err = bucket.GetObject(objectName, VersionId(versionIdV1))
+	c.Assert(err, NotNil)
+	c.Assert(err.(ServiceError).Code, Equals, "NoSuchVersion")
+
+	// get default object success:v2
+	body, err = bucket.GetObject(objectName)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	body.Close()
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, contextV2)
+
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningDeleteDefaultVersionObject(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err := client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	// delete default object:mark delete v2
+	options := []Option{GetResponseHeader(&respHeader)}
+	err = bucket.DeleteObject(objectName, options...)
+	c.Assert(err, IsNil)
+
+	markVersionId := GetVersionId(respHeader)
+	c.Assert(len(markVersionId) > 0, Equals, true)
+	c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+
+	// get v2 object success
+	body, err := bucket.GetObject(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	body.Close()
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, contextV2)
+
+	// get v1 object success
+	body, err = bucket.GetObject(objectName, VersionId(versionIdV1))
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	body.Close()
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, contextV1)
+
+	// get default object failure:marker v2
+	body, err = bucket.GetObject(objectName, GetResponseHeader(&respHeader))
+	c.Assert(err, NotNil)
+	c.Assert(err.(ServiceError).Code, Equals, "NoSuchKey")
+	c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+
+	// delete mark v2
+	options = []Option{VersionId(markVersionId), GetResponseHeader(&respHeader)}
+	err = bucket.DeleteObject(objectName, options...)
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, markVersionId)
+
+	// get default object success:v2
+	body, err = bucket.GetObject(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	body.Close()
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, contextV2)
+
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV1))
+	err = bucket.DeleteObject(objectName, VersionId(versionIdV2))
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningListObjectVersions(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err := client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	// delete default object:mark delete v2
+	options := []Option{GetResponseHeader(&respHeader)}
+	err = bucket.DeleteObject(objectName, options...)
+	c.Assert(err, IsNil)
+
+	markVersionId := GetVersionId(respHeader)
+	c.Assert(len(markVersionId) > 0, Equals, true)
+	c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+
+	// delete default object again:mark delete v2
+	err = bucket.DeleteObject(objectName, options...)
+	c.Assert(err, IsNil)
+	markVersionIdAgain := GetVersionId(respHeader)
+	c.Assert(len(markVersionIdAgain) > 0, Equals, true)
+	c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+	c.Assert(markVersionId != markVersionIdAgain, Equals, true)
+
+	// list bucket versions
+	listResult, err := bucket.ListObjectVersions()
+	c.Assert(err, IsNil)
+	c.Assert(len(listResult.ObjectDeleteMarkers), Equals, 2)
+	c.Assert(len(listResult.ObjectVersions), Equals, 2)
+	mapMarkVersion := map[string]string{}
+	mapMarkVersion[listResult.ObjectDeleteMarkers[0].VersionId] = listResult.ObjectDeleteMarkers[0].VersionId
+	mapMarkVersion[listResult.ObjectDeleteMarkers[1].VersionId] = listResult.ObjectDeleteMarkers[1].VersionId
+
+	// check delete mark
+	_, ok := mapMarkVersion[markVersionId]
+	c.Assert(ok == true, Equals, true)
+	_, ok = mapMarkVersion[markVersionIdAgain]
+	c.Assert(ok == true, Equals, true)
+
+	// check versionId
+	mapVersion := map[string]string{}
+	mapVersion[listResult.ObjectVersions[0].VersionId] = listResult.ObjectVersions[0].VersionId
+	mapVersion[listResult.ObjectVersions[1].VersionId] = listResult.ObjectVersions[1].VersionId
+	_, ok = mapVersion[versionIdV1]
+	c.Assert(ok == true, Equals, true)
+	_, ok = mapVersion[versionIdV2]
+	c.Assert(ok == true, Equals, true)
+
+	// delete deleteMark v2
+	options = []Option{VersionId(markVersionId), GetResponseHeader(&respHeader)}
+	err = bucket.DeleteObject(objectName, options...)
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, markVersionId)
+
+	// delete deleteMark v2 again
+	options = []Option{VersionId(markVersionIdAgain), GetResponseHeader(&respHeader)}
+	err = bucket.DeleteObject(objectName, options...)
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, markVersionIdAgain)
+
+	// delete versionId
+	bucket.DeleteObject(objectName, VersionId(versionIdV1))
+	bucket.DeleteObject(objectName, VersionId(versionIdV2))
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningBatchDeleteVersionObjects(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err := client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+	// put object v1
+	objectName1 := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	objectName2 := objectNamePrefix + randStr(8)
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	//batch delete objects
+	versionIds := []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1},
+		DeleteObject{Key: objectName2, VersionId: versionIdV2}}
+	deleteResult, err := bucket.DeleteObjectVersions(versionIds)
+	c.Assert(err, IsNil)
+	c.Assert(len(deleteResult.DeletedObjectsDetail), Equals, 2)
+
+	// check delete detail info:key
+	deleteMap := map[string]string{}
+	deleteMap[deleteResult.DeletedObjectsDetail[0].Key] = deleteResult.DeletedObjectsDetail[0].VersionId
+	deleteMap[deleteResult.DeletedObjectsDetail[1].Key] = deleteResult.DeletedObjectsDetail[1].VersionId
+	id1, ok := deleteMap[objectName1]
+	c.Assert(ok, Equals, true)
+	c.Assert(id1, Equals, versionIdV1)
+
+	id2, ok := deleteMap[objectName2]
+	c.Assert(ok, Equals, true)
+	c.Assert(id2, Equals, versionIdV2)
+
+	// list bucket versions
+	listResult, err := bucket.ListObjectVersions()
+	c.Assert(err, IsNil)
+	c.Assert(len(listResult.ObjectDeleteMarkers), Equals, 0)
+	c.Assert(len(listResult.ObjectVersions), Equals, 0)
+
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningBatchDeleteDefaultVersionObjects(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	bucketResult, err := client.GetBucketInfo(bucketName)
+	c.Assert(err, IsNil)
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled))
+
+	// put object v1
+	objectName1 := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	objectName2 := objectNamePrefix + randStr(8)
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	//batch delete objects
+	versionIds := []DeleteObject{DeleteObject{Key: objectName1, VersionId: ""},
+		DeleteObject{Key: objectName2, VersionId: ""}}
+	deleteResult, err := bucket.DeleteObjectVersions(versionIds)
+	c.Assert(err, IsNil)
+
+	// check delete detail info:key
+	deleteDetailMap := map[string]DeletedKeyInfo{}
+	deleteDetailMap[deleteResult.DeletedObjectsDetail[0].Key] = deleteResult.DeletedObjectsDetail[0]
+	deleteDetailMap[deleteResult.DeletedObjectsDetail[1].Key] = deleteResult.DeletedObjectsDetail[1]
+	keyInfo1, ok := deleteDetailMap[objectName1]
+	c.Assert(ok, Equals, true)
+	c.Assert(keyInfo1.Key, Equals, objectName1)
+	c.Assert(keyInfo1.VersionId, Equals, "")
+	c.Assert(keyInfo1.DeleteMarker, Equals, true)
+	c.Assert(keyInfo1.DeleteMarkerVersionId != versionIdV1, Equals, true)
+
+	keyInfo2, ok := deleteDetailMap[objectName2]
+	c.Assert(ok, Equals, true)
+	c.Assert(keyInfo2.Key, Equals, objectName2)
+	c.Assert(keyInfo2.VersionId, Equals, "")
+	c.Assert(keyInfo2.DeleteMarker, Equals, true)
+	c.Assert(keyInfo2.DeleteMarkerVersionId != versionIdV2, Equals, true)
+
+	// list bucket versions
+	listResult, err := bucket.ListObjectVersions()
+	c.Assert(err, IsNil)
+	c.Assert(len(listResult.ObjectDeleteMarkers), Equals, 2)
+	c.Assert(len(listResult.ObjectVersions), Equals, 2)
+
+	// delete version object
+	versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1},
+		DeleteObject{Key: objectName2, VersionId: versionIdV2}}
+	deleteResult, err = bucket.DeleteObjectVersions(versionIds)
+	c.Assert(err, IsNil)
+
+	// delete deleteMark object
+	versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: keyInfo1.DeleteMarkerVersionId},
+		DeleteObject{Key: objectName2, VersionId: keyInfo2.DeleteMarkerVersionId}}
+	deleteResult, err = bucket.DeleteObjectVersions(versionIds)
+	c.Assert(err, IsNil)
+
+	forceDeleteBucket(client, bucketName, c)
+}
+
+// bucket has no versioning flag
+func (s *OssBucketSuite) TestVersioningBatchDeleteNormalObjects(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	// not put bucket versioning
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put object v1
+	objectName1 := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1), Equals, 0)
+
+	// put object v2
+	objectName2 := objectNamePrefix + randStr(8)
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2), Equals, 0)
+
+	//batch delete objects
+	keys := []string{objectName1, objectName2}
+	deleteResult, err := bucket.DeleteObjects(keys)
+	c.Assert(len(deleteResult.DeletedObjects), Equals, 2)
+
+	// check delete info
+	deleteMap := map[string]string{}
+	deleteMap[deleteResult.DeletedObjects[0]] = deleteResult.DeletedObjects[0]
+	deleteMap[deleteResult.DeletedObjects[1]] = deleteResult.DeletedObjects[1]
+	_, ok := deleteMap[objectName1]
+	c.Assert(ok, Equals, true)
+	_, ok = deleteMap[objectName2]
+	c.Assert(ok, Equals, true)
+
+	forceDeleteBucket(client, bucketName, c)
+	c.Assert(err, IsNil)
+}
+
+func (s *OssBucketSuite) TestVersioningSymlink(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// put object 1
+	objectName1 := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object 2
+	objectName2 := objectNamePrefix + randStr(8)
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	// put symlink for object 1
+	linkName := objectNamePrefix + randStr(8)
+	err = bucket.PutSymlink(linkName, objectName1, GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	linkVersionIdV1 := GetVersionId(respHeader)
+
+	// PutSymlink for object 2
+	err = bucket.PutSymlink(linkName, objectName2, GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	linkVersionIdV2 := GetVersionId(respHeader)
+
+	// check v1 and v2
+	c.Assert(linkVersionIdV1 != linkVersionIdV2, Equals, true)
+
+	// GetSymlink for object1
+	getResult, err := bucket.GetSymlink(linkName, VersionId(linkVersionIdV1))
+	c.Assert(err, IsNil)
+	c.Assert(getResult.Get("x-oss-symlink-target"), Equals, objectName1)
+
+	// GetSymlink for object2
+	getResult, err = bucket.GetSymlink(linkName, VersionId(linkVersionIdV2))
+	c.Assert(err, IsNil)
+	c.Assert(getResult.Get("x-oss-symlink-target"), Equals, objectName2)
+
+	bucket.DeleteObject(linkName)
+	bucket.DeleteObject(objectName1)
+	bucket.DeleteObject(objectName2)
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningObjectAcl(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	// put Acl for v1
+	err = bucket.SetObjectACL(objectName, ACLPublicRead, VersionId(versionIdV1))
+	c.Assert(err, IsNil)
+
+	// put Acl for v2
+	err = bucket.SetObjectACL(objectName, ACLPublicReadWrite, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+
+	// GetAcl for v1
+	getResult, err := bucket.GetObjectACL(objectName, VersionId(versionIdV1))
+	c.Assert(err, IsNil)
+	c.Assert(getResult.ACL, Equals, string(ACLPublicRead))
+
+	// GetAcl for v2
+	getResult, err = bucket.GetObjectACL(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	c.Assert(getResult.ACL, Equals, string(ACLPublicReadWrite))
+
+	// delete default version
+	err = bucket.DeleteObject(objectName, GetResponseHeader(&respHeader))
+	c.Assert(len(GetVersionId(respHeader)) > 0, Equals, true)
+	c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true")
+
+	// GetAcl for v1 agagin
+	getResult, err = bucket.GetObjectACL(objectName, VersionId(versionIdV1))
+	c.Assert(err, IsNil)
+	c.Assert(getResult.ACL, Equals, string(ACLPublicRead))
+
+	// GetAcl for v2 again
+	getResult, err = bucket.GetObjectACL(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	c.Assert(getResult.ACL, Equals, string(ACLPublicReadWrite))
+
+	// GetAcl for default failure
+	getResult, err = bucket.GetObjectACL(objectName)
+	c.Assert(err, NotNil)
+
+	bucket.DeleteObject(objectName)
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningAppendObject(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// append object
+	var nextPos int64 = 0
+	var respHeader http.Header
+	objectName := objectNamePrefix + randStr(8)
+	nextPos, err = bucket.AppendObject(objectName, strings.NewReader("123"), nextPos, GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, NullVersion)
+
+	nextPos, err = bucket.AppendObject(objectName, strings.NewReader("456"), nextPos, GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, NullVersion)
+
+	// delete object
+	err = bucket.DeleteObject(objectName, GetResponseHeader(&respHeader))
+	markVersionId := GetVersionId(respHeader)
+
+	// get default object failure
+	_, err = bucket.GetObject(objectName)
+	c.Assert(err, NotNil)
+
+	// get null version success
+	body, err := bucket.GetObject(objectName, VersionId(NullVersion))
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, "123456")
+
+	// append object again:failure
+	nextPos, err = bucket.AppendObject(objectName, strings.NewReader("789"), nextPos, GetResponseHeader(&respHeader))
+	c.Assert(err, NotNil)
+
+	// delete deletemark
+	options := []Option{VersionId(markVersionId), GetResponseHeader(&respHeader)}
+	err = bucket.DeleteObject(objectName, options...)
+	c.Assert(markVersionId, Equals, GetVersionId(respHeader))
+
+	// append object again:success
+	nextPos, err = bucket.AppendObject(objectName, strings.NewReader("789"), nextPos, GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	c.Assert(int(nextPos), Equals, 9)
+
+	bucket.DeleteObject(objectName)
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningCopyObject(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// check v1 and v2
+	c.Assert(versionIdV1 != versionIdV2, Equals, true)
+
+	destObjectKey := objectNamePrefix + randStr(8)
+
+	// copyobject default
+	_, err = bucket.CopyObject(objectName, destObjectKey, GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	srcVersionId := GetCopySrcVersionId(respHeader)
+	c.Assert(srcVersionId, Equals, versionIdV2)
+
+	body, err := bucket.GetObject(destObjectKey)
+	c.Assert(err, IsNil)
+	str, err := readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, contextV2)
+
+	//  copyobject v1
+	options := []Option{VersionId(versionIdV1), GetResponseHeader(&respHeader)}
+	_, err = bucket.CopyObject(objectName, destObjectKey, options...)
+	c.Assert(err, IsNil)
+	srcVersionId = GetCopySrcVersionId(respHeader)
+	c.Assert(srcVersionId, Equals, versionIdV1)
+
+	body, err = bucket.GetObject(destObjectKey)
+	c.Assert(err, IsNil)
+	str, err = readBody(body)
+	c.Assert(err, IsNil)
+	c.Assert(str, Equals, contextV1)
+
+	// delete object
+	err = bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+
+	// default copyobject again,failuer
+	_, err = bucket.CopyObject(objectName, destObjectKey, GetResponseHeader(&respHeader))
+	c.Assert(err, NotNil)
+
+	bucket.DeleteObject(objectName)
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningCompleteMultipartUpload(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	objectName := objectNamePrefix + randStr(8)
+	var fileName = "test-file-" + randStr(8)
+	content := randStr(500 * 1024)
+	createFile(fileName, content, c)
+
+	chunks, err := SplitFileByPartNum(fileName, 3)
+	c.Assert(err, IsNil)
+
+	options := []Option{
+		Expires(futureDate), Meta("my", "myprop"),
+	}
+
+	fd, err := os.Open(fileName)
+	c.Assert(err, IsNil)
+	defer fd.Close()
+
+	imur, err := bucket.InitiateMultipartUpload(objectName, options...)
+	c.Assert(err, IsNil)
+	var parts []UploadPart
+	for _, chunk := range chunks {
+		fd.Seek(chunk.Offset, os.SEEK_SET)
+		part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number)
+		c.Assert(err, IsNil)
+		parts = append(parts, part)
+	}
+
+	var respHeader http.Header
+	_, err = bucket.CompleteMultipartUpload(imur, parts, GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+
+	//get versionId
+	versionIdV1 := GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	meta, err := bucket.GetObjectDetailedMeta(objectName)
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop")
+	c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat))
+	c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart")
+
+	// put object agagin
+	err = bucket.PutObject(objectName, strings.NewReader(""), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 := GetVersionId(respHeader)
+	c.Assert(versionIdV1 == versionIdV2, Equals, false)
+
+	// get meta v1
+	meta, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV1))
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(len(content)))
+
+	// get meta v2
+	meta, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(0))
+
+	os.Remove(fileName)
+	bucket.DeleteObject(objectName)
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningUploadPartCopy(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// upload mutlipart object with v1
+	multiName := objectNamePrefix + randStr(8)
+	var parts []UploadPart
+	imur, err := bucket.InitiateMultipartUpload(multiName)
+	c.Assert(err, IsNil)
+
+	part, err := bucket.UploadPartCopy(imur, bucketName, objectName, 0, int64(len(contextV1)), 1, VersionId(versionIdV1))
+	parts = []UploadPart{part}
+	c.Assert(err, IsNil)
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts, GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+
+	//get versionId
+	partVersionIdV1 := GetVersionId(respHeader)
+	c.Assert(len(partVersionIdV1) > 0, Equals, true)
+
+	// get meta v1
+	meta, err := bucket.GetObjectDetailedMeta(multiName, VersionId(partVersionIdV1))
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(len(contextV1)))
+
+	// upload mutlipart object with v2
+	imur, err = bucket.InitiateMultipartUpload(multiName)
+	part, err = bucket.UploadPartCopy(imur, bucketName, objectName, 0, int64(len(contextV2)), 1, VersionId(versionIdV2))
+	parts = []UploadPart{part}
+
+	_, err = bucket.CompleteMultipartUpload(imur, parts, GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+
+	//get versionId
+	partVersionIdV2 := GetVersionId(respHeader)
+	c.Assert(len(partVersionIdV2) > 0, Equals, true)
+
+	// get meta v2
+	meta, err = bucket.GetObjectDetailedMeta(multiName, VersionId(partVersionIdV2))
+	c.Assert(err, IsNil)
+	c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(len(contextV2)))
+
+	bucket.DeleteObject(objectName)
+	bucket.DeleteObject(multiName)
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningRestoreObject(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName, StorageClass(StorageArchive))
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// RestoreObject v1
+	options := []Option{GetResponseHeader(&respHeader), VersionId(versionIdV1)}
+	err = bucket.RestoreObject(objectName, options...)
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, versionIdV1)
+
+	// RestoreObject v2
+	options = []Option{GetResponseHeader(&respHeader), VersionId(versionIdV2)}
+	err = bucket.RestoreObject(objectName, options...)
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, versionIdV2)
+
+	bucket.DeleteObject(objectName)
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssBucketSuite) TestVersioningObjectTagging(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName, StorageClass(StorageArchive))
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// put object v1
+	objectName := objectNamePrefix + randStr(8)
+	contextV1 := randStr(100)
+	versionIdV1 := ""
+
+	var respHeader http.Header
+	err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV1 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV1) > 0, Equals, true)
+
+	// put object v2
+	contextV2 := randStr(200)
+	versionIdV2 := ""
+	err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader))
+	c.Assert(err, IsNil)
+	versionIdV2 = GetVersionId(respHeader)
+	c.Assert(len(versionIdV2) > 0, Equals, true)
+
+	// ObjectTagging v1
+	var tagging1 Tagging
+	tagging1.Tags = []Tag{Tag{Key: "testkey1", Value: "testvalue1"}}
+	err = bucket.PutObjectTagging(objectName, tagging1, VersionId(versionIdV1))
+	c.Assert(err, IsNil)
+	getResult, err := bucket.GetObjectTagging(objectName, VersionId(versionIdV1))
+	c.Assert(err, IsNil)
+	c.Assert(getResult.Tags[0].Key, Equals, tagging1.Tags[0].Key)
+	c.Assert(getResult.Tags[0].Value, Equals, tagging1.Tags[0].Value)
+
+	// ObjectTagging v2
+	var tagging2 Tagging
+	tagging2.Tags = []Tag{Tag{Key: "testkey2", Value: "testvalue2"}}
+	err = bucket.PutObjectTagging(objectName, tagging2, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	getResult, err = bucket.GetObjectTagging(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	c.Assert(getResult.Tags[0].Key, Equals, tagging2.Tags[0].Key)
+	c.Assert(getResult.Tags[0].Value, Equals, tagging2.Tags[0].Value)
+
+	// delete ObjectTagging v2
+	err = bucket.DeleteObjectTagging(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+
+	getResult, err = bucket.GetObjectTagging(objectName, VersionId(versionIdV2))
+	c.Assert(err, IsNil)
+	c.Assert(len(getResult.Tags), Equals, 0)
+
+	bucket.DeleteObject(objectName)
+	forceDeleteBucket(client, bucketName, c)
+}

+ 129 - 8
oss/client.go

@@ -113,6 +113,14 @@ func (client Client) CreateBucket(bucketName string, options ...Option) error {
 
 	params := map[string]interface{}{}
 	resp, err := client.do("PUT", bucketName, params, headers, buffer)
+
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return err
 	}
@@ -140,6 +148,14 @@ func (client Client) ListBuckets(options ...Option) (ListBucketsResult, error) {
 	}
 
 	resp, err := client.do("GET", "", params, nil, nil)
+
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return out, err
 	}
@@ -666,10 +682,72 @@ func (client Client) GetBucketInfo(bucketName string) (GetBucketInfoResult, erro
 	return out, err
 }
 
+// SetBucketVersioning set bucket versioning:Enabled、Suspended
+// bucketName    the bucket name.
+// error    it's nil if no error, otherwise it's an error object.
+func (client Client) SetBucketVersioning(bucketName string, versioningConfig VersioningConfig, options ...Option) error {
+	var err error
+	var bs []byte
+	bs, err = xml.Marshal(versioningConfig)
+
+	if err != nil {
+		return err
+	}
+
+	buffer := new(bytes.Buffer)
+	buffer.Write(bs)
+
+	contentType := http.DetectContentType(buffer.Bytes())
+	headers := map[string]string{}
+	headers[HTTPHeaderContentType] = contentType
+
+	params := map[string]interface{}{}
+	params["versioning"] = nil
+	resp, err := client.do("PUT", bucketName, params, headers, buffer)
+
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	return checkRespCode(resp.StatusCode, []int{http.StatusOK})
+}
+
+// GetBucketVersioning get bucket versioning status:Enabled、Suspended
+// bucketName    the bucket name.
+// error    it's nil if no error, otherwise it's an error object.
+func (client Client) GetBucketVersioning(bucketName string, options ...Option) (GetBucketVersioningResult, error) {
+	var out GetBucketVersioningResult
+	params := map[string]interface{}{}
+	params["versioning"] = nil
+	resp, err := client.do("GET", bucketName, params, nil, nil)
+
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
+	if err != nil {
+		return out, err
+	}
+	defer resp.Body.Close()
+
+	err = xmlUnmarshal(resp.Body, &out)
+	return out, err
+}
+
 // SetBucketEncryption set bucket encryption config
 // bucketName    the bucket name.
 // error    it's nil if no error, otherwise it's an error object.
-func (client Client) SetBucketEncryption(bucketName string, encryptionRule ServerEncryptionRule) error {
+func (client Client) SetBucketEncryption(bucketName string, encryptionRule ServerEncryptionRule, options ...Option) error {
 	var err error
 	var bs []byte
 	bs, err = xml.Marshal(encryptionRule)
@@ -689,6 +767,13 @@ func (client Client) SetBucketEncryption(bucketName string, encryptionRule Serve
 	params["encryption"] = nil
 	resp, err := client.do("PUT", bucketName, params, headers, buffer)
 
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return err
 	}
@@ -696,15 +781,22 @@ func (client Client) SetBucketEncryption(bucketName string, encryptionRule Serve
 	return checkRespCode(resp.StatusCode, []int{http.StatusOK})
 }
 
-// GetBucketEncryption get bucket encryption config
+// GetBucketEncryption get bucket encryption
 // bucketName    the bucket name.
 // error    it's nil if no error, otherwise it's an error object.
-func (client Client) GetBucketEncryption(bucketName string) (GetBucketEncryptionResult, error) {
+func (client Client) GetBucketEncryption(bucketName string, options ...Option) (GetBucketEncryptionResult, error) {
 	var out GetBucketEncryptionResult
 	params := map[string]interface{}{}
 	params["encryption"] = nil
 	resp, err := client.do("GET", bucketName, params, nil, nil)
 
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return out, err
 	}
@@ -716,12 +808,19 @@ func (client Client) GetBucketEncryption(bucketName string) (GetBucketEncryption
 
 // DeleteBucketEncryption delete bucket encryption config
 // bucketName    the bucket name.
-// error    it's nil if no error, otherwise it's an error object.
-func (client Client) DeleteBucketEncryption(bucketName string) error {
+// error    it's nil if no error, otherwise it's an error bucket
+func (client Client) DeleteBucketEncryption(bucketName string, options ...Option) error {
 	params := map[string]interface{}{}
 	params["encryption"] = nil
 	resp, err := client.do("DELETE", bucketName, params, nil, nil)
 
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return err
 	}
@@ -734,7 +833,7 @@ func (client Client) DeleteBucketEncryption(bucketName string) error {
 // bucketName  name of bucket
 // tagging    tagging to be added
 // error        nil if success, otherwise error
-func (client Client) SetBucketTagging(bucketName string, tagging Tagging) error {
+func (client Client) SetBucketTagging(bucketName string, tagging Tagging, options ...Option) error {
 	var err error
 	var bs []byte
 	bs, err = xml.Marshal(tagging)
@@ -753,6 +852,14 @@ func (client Client) SetBucketTagging(bucketName string, tagging Tagging) error
 	params := map[string]interface{}{}
 	params["tagging"] = nil
 	resp, err := client.do("PUT", bucketName, params, headers, buffer)
+
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return err
 	}
@@ -763,12 +870,19 @@ func (client Client) SetBucketTagging(bucketName string, tagging Tagging) error
 // GetBucketTagging get tagging of the bucket
 // bucketName  name of bucket
 // error      nil if success, otherwise error
-func (client Client) GetBucketTagging(bucketName string) (GetBucketTaggingResult, error) {
+func (client Client) GetBucketTagging(bucketName string, options ...Option) (GetBucketTaggingResult, error) {
 	var out GetBucketTaggingResult
 	params := map[string]interface{}{}
 	params["tagging"] = nil
 	resp, err := client.do("GET", bucketName, params, nil, nil)
 
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return out, err
 	}
@@ -783,11 +897,18 @@ func (client Client) GetBucketTagging(bucketName string) (GetBucketTaggingResult
 // bucketName  name of bucket
 // error      nil if success, otherwise error
 //
-func (client Client) DeleteBucketTagging(bucketName string) error {
+func (client Client) DeleteBucketTagging(bucketName string, options ...Option) error {
 	params := map[string]interface{}{}
 	params["tagging"] = nil
 	resp, err := client.do("DELETE", bucketName, params, nil, nil)
 
+	// get response header
+	respHeader, _ := findOption(options, responseHeader, nil)
+	if respHeader != nil {
+		pRespHeader := respHeader.(*http.Header)
+		*pRespHeader = resp.Headers
+	}
+
 	if err != nil {
 		return err
 	}

+ 123 - 71
oss/client_test.go

@@ -85,37 +85,7 @@ func randLowStr(n int) string {
 	return strings.ToLower(randStr(n))
 }
 
-// SetUpSuite runs once when the suite starts running
-func (s *OssClientSuite) SetUpSuite(c *C) {
-	client, err := New(endpoint, accessID, accessKey)
-	c.Assert(err, IsNil)
-
-	lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000))
-	c.Assert(err, IsNil)
-
-	for _, bucket := range lbr.Buckets {
-		s.deleteBucket(client, bucket.Name, c)
-	}
-
-	testLogger.Println("test client started")
-}
-
-// TearDownSuite runs before each test or benchmark starts running
-func (s *OssClientSuite) TearDownSuite(c *C) {
-	client, err := New(endpoint, accessID, accessKey)
-	c.Assert(err, IsNil)
-
-	lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000))
-	c.Assert(err, IsNil)
-
-	for _, bucket := range lbr.Buckets {
-		s.deleteBucket(client, bucket.Name, c)
-	}
-
-	testLogger.Println("test client completed")
-}
-
-func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) {
+func forceDeleteBucket(client *Client, bucketName string, c *C) {
 	bucket, err := client.Bucket(bucketName)
 	c.Assert(err, IsNil)
 
@@ -134,8 +104,37 @@ func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) {
 		}
 	}
 
-	// Delete Part
+	// Delete Object Versions and DeleteMarks
 	keyMarker := KeyMarker("")
+	versionIdMarker := VersionIdMarker("")
+	options := []Option{keyMarker, versionIdMarker}
+	for {
+		lor, err := bucket.ListObjectVersions(options...)
+		if err != nil {
+			break
+		}
+
+		for _, object := range lor.ObjectDeleteMarkers {
+			err = bucket.DeleteObject(object.Key, VersionId(object.VersionId))
+			c.Assert(err, IsNil)
+		}
+
+		for _, object := range lor.ObjectVersions {
+			err = bucket.DeleteObject(object.Key, VersionId(object.VersionId))
+			c.Assert(err, IsNil)
+		}
+
+		keyMarker = KeyMarker(lor.NextKeyMarker)
+		versionIdMarker := VersionIdMarker(lor.NextVersionIdMarker)
+		options = []Option{keyMarker, versionIdMarker}
+
+		if !lor.IsTruncated {
+			break
+		}
+	}
+
+	// Delete Part
+	keyMarker = KeyMarker("")
 	uploadIDMarker := UploadIDMarker("")
 	for {
 		lmur, err := bucket.ListMultipartUploads(keyMarker, uploadIDMarker)
@@ -158,6 +157,40 @@ func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) {
 	c.Assert(err, IsNil)
 }
 
+// SetUpSuite runs once when the suite starts running
+func (s *OssClientSuite) SetUpSuite(c *C) {
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000))
+	c.Assert(err, IsNil)
+
+	for _, bucket := range lbr.Buckets {
+		forceDeleteBucket(client, bucket.Name, c)
+	}
+
+	testLogger.Println("test client started")
+}
+
+// TearDownSuite runs before each test or benchmark starts running
+func (s *OssClientSuite) TearDownSuite(c *C) {
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000))
+	c.Assert(err, IsNil)
+
+	for _, bucket := range lbr.Buckets {
+		s.deleteBucket(client, bucket.Name, c)
+	}
+
+	testLogger.Println("test client completed")
+}
+
+func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) {
+	forceDeleteBucket(client, bucketName, c)
+}
+
 // SetUpTest runs after each test or benchmark runs
 func (s *OssClientSuite) SetUpTest(c *C) {
 }
@@ -997,7 +1030,7 @@ func (s *OssClientSuite) TestBucketRefererNegative(c *C) {
 // TestSetBucketLogging
 func (s *OssClientSuite) TestSetBucketLogging(c *C) {
 	var bucketNameTest = bucketNamePrefix + randLowStr(6)
-	var bucketNameTarget = bucketNamePrefix + randLowStr(6)
+	var bucketNameTarget = bucketNameTest + "-target"
 
 	client, err := New(endpoint, accessID, accessKey)
 	c.Assert(err, IsNil)
@@ -1037,7 +1070,7 @@ func (s *OssClientSuite) TestSetBucketLogging(c *C) {
 // TestDeleteBucketLogging
 func (s *OssClientSuite) TestDeleteBucketLogging(c *C) {
 	var bucketNameTest = bucketNamePrefix + randLowStr(6)
-	var bucketNameTarget = bucketNamePrefix + randLowStr(6)
+	var bucketNameTarget = bucketNameTest + "-target"
 
 	client, err := New(endpoint, accessID, accessKey)
 	c.Assert(err, IsNil)
@@ -1096,7 +1129,7 @@ func (s *OssClientSuite) TestDeleteBucketLogging(c *C) {
 // TestSetBucketLoggingNegative
 func (s *OssClientSuite) TestSetBucketLoggingNegative(c *C) {
 	var bucketNameTest = bucketNamePrefix + randLowStr(6)
-	var bucketNameTarget = bucketNamePrefix + randLowStr(6)
+	var bucketNameTarget = bucketNameTest + "-target"
 
 	client, err := New(endpoint, accessID, accessKey)
 	c.Assert(err, IsNil)
@@ -1853,12 +1886,17 @@ func (s *OssClientSuite) TestBucketEncyptionError(c *C) {
 	encryptionRule.SSEDefault.SSEAlgorithm = string(AESAlgorithm)
 	encryptionRule.SSEDefault.KMSMasterKeyID = "123"
 
-	err = client.SetBucketEncryption(bucketName, encryptionRule)
+	var responseHeader http.Header
+	err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
 	c.Assert(err, NotNil)
+	requestId := GetRequestId(responseHeader)
+	c.Assert(len(requestId) > 0, Equals, true)
 
 	// GetBucketEncryption
-	_, err = client.GetBucketEncryption(bucketName)
+	_, err = client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
 	c.Assert(err, NotNil)
+	requestId = GetRequestId(responseHeader)
+	c.Assert(len(requestId) > 0, Equals, true)
 
 	// Get default bucket info
 	bucketResult, err := client.GetBucketInfo(bucketName)
@@ -1866,6 +1904,7 @@ func (s *OssClientSuite) TestBucketEncyptionError(c *C) {
 
 	c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "")
 	c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
 
 	err = client.DeleteBucket(bucketName)
 	c.Assert(err, IsNil)
@@ -1883,12 +1922,17 @@ func (s *OssClientSuite) TestBucketEncyptionPutAndGetAndDelete(c *C) {
 	encryptionRule := ServerEncryptionRule{}
 	encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm)
 
-	err = client.SetBucketEncryption(bucketName, encryptionRule)
+	var responseHeader http.Header
+	err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
 	c.Assert(err, IsNil)
+	requestId := GetRequestId(responseHeader)
+	c.Assert(len(requestId) > 0, Equals, true)
 
 	// GetBucketEncryption
-	getResult, err := client.GetBucketEncryption(bucketName)
+	getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
 	c.Assert(err, IsNil)
+	requestId = GetRequestId(responseHeader)
+	c.Assert(len(requestId) > 0, Equals, true)
 
 	// check encryption value
 	c.Assert(encryptionRule.SSEDefault.SSEAlgorithm, Equals, getResult.SSEDefault.SSEAlgorithm)
@@ -1899,8 +1943,10 @@ func (s *OssClientSuite) TestBucketEncyptionPutAndGetAndDelete(c *C) {
 	c.Assert(err, IsNil)
 
 	// GetBucketEncryption failure
-	_, err = client.GetBucketEncryption(bucketName)
+	_, err = client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
 	c.Assert(err, NotNil)
+	requestId = GetRequestId(responseHeader)
+	c.Assert(len(requestId) > 0, Equals, true)
 
 	// Get default bucket info
 	bucketResult, err := client.GetBucketInfo(bucketName)
@@ -1908,6 +1954,7 @@ func (s *OssClientSuite) TestBucketEncyptionPutAndGetAndDelete(c *C) {
 
 	c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "")
 	c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
 
 	err = client.DeleteBucket(bucketName)
 	c.Assert(err, IsNil)
@@ -1925,12 +1972,17 @@ func (s *OssClientSuite) TestBucketEncyptionPutObjectSuccess(c *C) {
 	encryptionRule := ServerEncryptionRule{}
 	encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm)
 
-	err = client.SetBucketEncryption(bucketName, encryptionRule)
+	var responseHeader http.Header
+	err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
 	c.Assert(err, IsNil)
+	requestId := GetRequestId(responseHeader)
+	c.Assert(len(requestId) > 0, Equals, true)
 
 	// GetBucketEncryption
-	getResult, err := client.GetBucketEncryption(bucketName)
+	getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
 	c.Assert(err, IsNil)
+	requestId = GetRequestId(responseHeader)
+	c.Assert(len(requestId) > 0, Equals, true)
 
 	// check encryption value
 	c.Assert(encryptionRule.SSEDefault.SSEAlgorithm, Equals, getResult.SSEDefault.SSEAlgorithm)
@@ -1942,6 +1994,27 @@ func (s *OssClientSuite) TestBucketEncyptionPutObjectSuccess(c *C) {
 
 	c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "KMS")
 	c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "")
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
+
+	// put and get object success
+	//bucket, err := client.Bucket(bucketName)
+	//c.Assert(err, IsNil)
+
+	// put object success
+	//objectName := objectNamePrefix + randStr(8)
+	//context := randStr(100)
+	//err = bucket.PutObject(objectName, strings.NewReader(context))
+	//c.Assert(err, IsNil)
+
+	// get object success
+	//body, err := bucket.GetObject(objectName)
+	//c.Assert(err, IsNil)
+	//str, err := readBody(body)
+	//c.Assert(err, IsNil)
+	//body.Close()
+	//c.Assert(str, Equals, context)
+
+	//bucket.DeleteObject(objectName)
 	err = client.DeleteBucket(bucketName)
 	c.Assert(err, IsNil)
 }
@@ -1959,12 +2032,17 @@ func (s *OssClientSuite) TestBucketEncyptionPutObjectError(c *C) {
 	encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm)
 	encryptionRule.SSEDefault.KMSMasterKeyID = "123"
 
-	err = client.SetBucketEncryption(bucketName, encryptionRule)
+	var responseHeader http.Header
+	err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader))
 	c.Assert(err, IsNil)
+	requestId := GetRequestId(responseHeader)
+	c.Assert(len(requestId) > 0, Equals, true)
 
 	// GetBucketEncryption
-	getResult, err := client.GetBucketEncryption(bucketName)
+	getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader))
 	c.Assert(err, IsNil)
+	requestId = GetRequestId(responseHeader)
+	c.Assert(len(requestId) > 0, Equals, true)
 
 	// check encryption value
 	c.Assert(encryptionRule.SSEDefault.SSEAlgorithm, Equals, getResult.SSEDefault.SSEAlgorithm)
@@ -1976,6 +2054,7 @@ func (s *OssClientSuite) TestBucketEncyptionPutObjectError(c *C) {
 
 	c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "KMS")
 	c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "123")
+	c.Assert(bucketResult.BucketInfo.Versioning, Equals, "")
 
 	// put and get object failure
 	bucket, err := client.Bucket(bucketName)
@@ -2048,30 +2127,3 @@ func (s *OssClientSuite) TestListBucketsTagging(c *C) {
 	client.DeleteBucket(bucketName1)
 	client.DeleteBucket(bucketName2)
 }
-
-func (s *OssClientSuite) TestGetBucketStat(c *C) {
-	client, err := New(endpoint, accessID, accessKey)
-	c.Assert(err, IsNil)
-
-	bucketName := bucketNamePrefix + randLowStr(5)
-	err = client.CreateBucket(bucketName)
-	c.Assert(err, IsNil)
-
-	bucket, err := client.Bucket(bucketName)
-	c.Assert(err, IsNil)
-
-	// put object
-	objectName := objectNamePrefix + randLowStr(5)
-	err = bucket.PutObject(objectName, strings.NewReader(randStr(10)))
-	c.Assert(err, IsNil)
-
-	bucket.DeleteObject(objectName)
-	err = bucket.PutObject(objectName, strings.NewReader(randStr(10)))
-	c.Assert(err, IsNil)
-	bucket.DeleteObject(objectName)
-
-	_, err = client.GetBucketStat(bucketName)
-	c.Assert(err, IsNil)
-
-	client.DeleteBucket(bucketName)
-}

+ 16 - 10
oss/conn.go

@@ -27,16 +27,22 @@ type Conn struct {
 	client *http.Client
 }
 
-var signKeyList = []string{"acl", "uploads", "location", "cors", "logging",
-	"website", "referer", "lifecycle", "delete", "append", "tagging", "objectMeta",
-	"uploadId", "partNumber", "security-token", "position", "img", "style", "styleName",
-	"replication", "replicationProgress", "replicationLocation", "cname", "bucketInfo",
-	"comp", "qos", "live", "status", "vod", "startTime", "endTime", "symlink",
-	"x-oss-process", "response-content-type", "response-content-language",
-	"response-expires", "response-cache-control", "response-content-disposition",
-	"response-content-encoding", "udf", "udfName", "udfImage", "udfId", "udfImageDesc",
-	"udfApplication", "comp", "udfApplicationLog", "restore", "callback", "callback-var",
-	"policy", "tagging", "stat", "encryption"}
+var signKeyList = []string{"acl", "uploads", "location", "cors",
+	"logging", "website", "referer", "lifecycle",
+	"delete", "append", "tagging", "objectMeta",
+	"uploadId", "partNumber", "security-token",
+	"position", "img", "style", "styleName",
+	"replication", "replicationProgress",
+	"replicationLocation", "cname", "bucketInfo",
+	"comp", "qos", "live", "status", "vod",
+	"startTime", "endTime", "symlink",
+	"x-oss-process", "response-content-type",
+	"response-content-language", "response-expires",
+	"response-cache-control", "response-content-disposition",
+	"response-content-encoding", "udf", "udfName", "udfImage",
+	"udfId", "udfImageDesc", "udfApplication", "comp",
+	"udfApplicationLog", "restore", "callback", "callback-var",
+	"policy", "stat", "encryption", "versions", "versioning", "versionId"}
 
 // init initializes Conn
 func (conn *Conn) init(config *Config, urlMaker *urlMaker, client *http.Client) error {

+ 14 - 1
oss/const.go

@@ -19,6 +19,17 @@ const (
 	ACLDefault ACLType = "default"
 )
 
+// bucket versioning status
+type VersioningStatus string
+
+const (
+	// Versioning Status definition: Enabled
+	VersionEnabled VersioningStatus = "Enabled"
+
+	// Versioning Status definition: Suspended
+	VersionSuspended VersioningStatus = "Suspended"
+)
+
 // MetadataDirectiveType specifying whether use the metadata of source object when copying object.
 type MetadataDirectiveType string
 
@@ -163,5 +174,7 @@ const (
 
 	CheckpointFileSuffix = ".cp" // Checkpoint file suffix
 
-	Version = "v1.9.8" // Go SDK version
+	NullVersion = "null"
+
+	Version = "v1.9.9" // Go SDK version
 )

+ 8 - 15
oss/download.go

@@ -225,12 +225,6 @@ func (bucket Bucket) downloadFile(objectKey, filePath string, partSize int64, op
 	tempFilePath := filePath + TempFileSuffix
 	listener := getProgressListener(options)
 
-	payerOptions := []Option{}
-	payer := getPayer(options)
-	if payer != "" {
-		payerOptions = append(payerOptions, RequestPayer(PayerType(payer)))
-	}
-
 	// If the file does not exist, create one. If exists, the download will overwrite it.
 	fd, err := os.OpenFile(tempFilePath, os.O_WRONLY|os.O_CREATE, FilePermMode)
 	if err != nil {
@@ -238,7 +232,10 @@ func (bucket Bucket) downloadFile(objectKey, filePath string, partSize int64, op
 	}
 	fd.Close()
 
-	meta, err := bucket.GetObjectDetailedMeta(objectKey, payerOptions...)
+	// Get the object detailed meta for object whole size
+	// must delete header:range to get whole object size
+	skipOptions := deleteOption(options, HTTPHeaderRange)
+	meta, err := bucket.GetObjectDetailedMeta(objectKey, skipOptions...)
 	if err != nil {
 		return err
 	}
@@ -474,12 +471,6 @@ func (bucket Bucket) downloadFileWithCp(objectKey, filePath string, partSize int
 	tempFilePath := filePath + TempFileSuffix
 	listener := getProgressListener(options)
 
-	payerOptions := []Option{}
-	payer := getPayer(options)
-	if payer != "" {
-		payerOptions = append(payerOptions, RequestPayer(PayerType(payer)))
-	}
-
 	// Load checkpoint data.
 	dcp := downloadCheckpoint{}
 	err := dcp.load(cpFilePath)
@@ -487,8 +478,10 @@ func (bucket Bucket) downloadFileWithCp(objectKey, filePath string, partSize int
 		os.Remove(cpFilePath)
 	}
 
-	// Get the object detailed meta.
-	meta, err := bucket.GetObjectDetailedMeta(objectKey, payerOptions...)
+	// Get the object detailed meta for object whole size
+	// must delete header:range to get whole object size
+	skipOptions := deleteOption(options, HTTPHeaderRange)
+	meta, err := bucket.GetObjectDetailedMeta(objectKey, skipOptions...)
 	if err != nil {
 		return err
 	}

+ 203 - 0
oss/download_test.go

@@ -3,7 +3,9 @@ package oss
 import (
 	"bytes"
 	"fmt"
+	"net/http"
 	"os"
+	"strings"
 	"time"
 
 	. "gopkg.in/check.v1"
@@ -673,3 +675,204 @@ func compareFilesWithRange(fileL string, offsetL int64, fileR string, offsetR in
 
 	return true, nil
 }
+
+func (s *OssDownloadSuite) TestVersioningDownloadWithoutCheckPoint(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// begin test
+	objectName := objectNamePrefix + randStr(8)
+	fileName := "test-file-" + randStr(8)
+	fileData := randStr(500 * 1024)
+	createFile(fileName, fileData, c)
+
+	newFile := randStr(8) + ".jpg"
+	newFileGet := randStr(8) + "-.jpg"
+
+	// Upload a file
+	var respHeader http.Header
+	options := []Option{Routines(3), GetResponseHeader(&respHeader)}
+	err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+	c.Assert(err, IsNil)
+	versionId := GetVersionId(respHeader)
+	c.Assert(len(versionId) > 0, Equals, true)
+
+	fileSize, err := getFileSize(fileName)
+	c.Assert(err, IsNil)
+
+	// overwrite emtpy object
+	err = bucket.PutObject(objectName, strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	// Download with range, from 1024 to 4096
+	os.Remove(newFile)
+	options = []Option{Routines(3), Range(1024, 4095), VersionId(versionId)}
+	err = bucket.DownloadFile(objectName, newFile, 100*1024, options...)
+	c.Assert(err, IsNil)
+
+	// Check
+	eq, err := compareFilesWithRange(fileName, 1024, newFile, 0, 3072)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	os.Remove(newFileGet)
+	options = []Option{Range(1024, 4095), VersionId(versionId)}
+	err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+	c.Assert(err, IsNil)
+
+	// Compare get and download
+	eq, err = compareFiles(newFile, newFileGet)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	// Download with range, from 1024 to 4096
+	os.Remove(newFile)
+	options = []Option{Routines(3), NormalizedRange("1024-4095"), VersionId(versionId)}
+	err = bucket.DownloadFile(objectName, newFile, 1024, options...)
+	c.Assert(err, IsNil)
+
+	// Check
+	eq, err = compareFilesWithRange(fileName, 1024, newFile, 0, 3072)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	os.Remove(newFileGet)
+	options = []Option{NormalizedRange("1024-4095"), VersionId(versionId)}
+	err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+	c.Assert(err, IsNil)
+
+	// Compare get and download
+	eq, err = compareFiles(newFile, newFileGet)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	// Download with range, from 2048 to the end
+	os.Remove(newFile)
+	options = []Option{NormalizedRange("2048-"), VersionId(versionId)}
+	err = bucket.DownloadFile(objectName, newFile, 1024*1024, options...)
+	c.Assert(err, IsNil)
+
+	// Check
+	eq, err = compareFilesWithRange(fileName, 2048, newFile, 0, fileSize-2048)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	os.Remove(newFileGet)
+	options = []Option{NormalizedRange("2048-"), VersionId(versionId)}
+	err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+	c.Assert(err, IsNil)
+
+	// Compare get and download
+	eq, err = compareFiles(newFile, newFileGet)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	// Download with range, the last 4096
+	os.Remove(newFile)
+	options = []Option{Routines(3), NormalizedRange("-4096"), VersionId(versionId)}
+	err = bucket.DownloadFile(objectName, newFile, 1024, options...)
+	c.Assert(err, IsNil)
+
+	// Check
+	eq, err = compareFilesWithRange(fileName, fileSize-4096, newFile, 0, 4096)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	os.Remove(newFileGet)
+	options = []Option{NormalizedRange("-4096"), VersionId(versionId)}
+	err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+	c.Assert(err, IsNil)
+
+	// Compare get and download
+	eq, err = compareFiles(newFile, newFileGet)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	// download whole file
+	os.Remove(newFileGet)
+	options = []Option{Routines(3), VersionId(versionId)}
+	err = bucket.GetObjectToFile(objectName, newFileGet, options...)
+	c.Assert(err, IsNil)
+
+	// Compare get and download
+	eq, err = compareFiles(fileName, newFileGet)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	os.Remove(fileName)
+	os.Remove(newFileGet)
+	err = bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+	forceDeleteBucket(client, bucketName, c)
+}
+
+func (s *OssDownloadSuite) TestVersioningDownloadWithCheckPoint(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// begin test
+	objectName := objectNamePrefix + randStr(8)
+	fileName := "test-file-" + randStr(8)
+	fileData := randStr(500 * 1024)
+	createFile(fileName, fileData, c)
+	newFile := randStr(8) + ".jpg"
+
+	// Upload a file
+	var respHeader http.Header
+	options := []Option{Routines(3), GetResponseHeader(&respHeader)}
+	err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+	c.Assert(err, IsNil)
+	versionId := GetVersionId(respHeader)
+	c.Assert(len(versionId) > 0, Equals, true)
+
+	// Resumable download with checkpoint dir
+	os.Remove(newFile)
+	downloadPartHooker = DownErrorHooker
+	options = []Option{CheckpointDir(true, "./"), VersionId(versionId)}
+	err = bucket.DownloadFile(objectName, newFile, 100*1024, options...)
+	c.Assert(err, NotNil)
+	c.Assert(err.Error(), Equals, "ErrorHooker")
+
+	// download again
+	downloadPartHooker = defaultDownloadPartHook
+	options = []Option{CheckpointDir(true, "./"), VersionId(versionId), GetResponseHeader(&respHeader)}
+	err = bucket.DownloadFile(objectName, newFile, 100*1024, options...)
+	c.Assert(err, IsNil)
+	c.Assert(GetVersionId(respHeader), Equals, versionId)
+
+	eq, err := compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	os.Remove(fileName)
+	os.Remove(newFile)
+	err = bucket.DeleteObject(objectName)
+	c.Assert(err, IsNil)
+	forceDeleteBucket(client, bucketName, c)
+}

+ 12 - 20
oss/multicopy.go

@@ -142,13 +142,9 @@ func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destO
 	srcBucket, err := bucket.Client.Bucket(srcBucketName)
 	listener := getProgressListener(options)
 
-	payerOptions := []Option{}
-	payer := getPayer(options)
-	if payer != "" {
-		payerOptions = append(payerOptions, RequestPayer(PayerType(payer)))
-	}
-
-	meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, payerOptions...)
+	// for get whole length
+	skipOptions := deleteOption(options, HTTPHeaderRange)
+	meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, skipOptions...)
 	if err != nil {
 		return err
 	}
@@ -177,7 +173,7 @@ func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destO
 	publishProgress(listener, event)
 
 	// Start to copy workers
-	arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, payerOptions, copyPartHooker}
+	arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, options, copyPartHooker}
 	for w := 1; w <= routines; w++ {
 		go copyWorker(w, arg, jobs, results, failed, die)
 	}
@@ -198,7 +194,7 @@ func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destO
 			publishProgress(listener, event)
 		case err := <-failed:
 			close(die)
-			descBucket.AbortMultipartUpload(imur, payerOptions...)
+			descBucket.AbortMultipartUpload(imur, options...)
 			event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes)
 			publishProgress(listener, event)
 			return err
@@ -213,9 +209,9 @@ func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destO
 	publishProgress(listener, event)
 
 	// Complete the multipart upload
-	_, err = descBucket.CompleteMultipartUpload(imur, ups, payerOptions...)
+	_, err = descBucket.CompleteMultipartUpload(imur, ups, options...)
 	if err != nil {
-		bucket.AbortMultipartUpload(imur, payerOptions...)
+		bucket.AbortMultipartUpload(imur, options...)
 		return err
 	}
 	return nil
@@ -385,12 +381,6 @@ func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName,
 	srcBucket, err := bucket.Client.Bucket(srcBucketName)
 	listener := getProgressListener(options)
 
-	payerOptions := []Option{}
-	payer := getPayer(options)
-	if payer != "" {
-		payerOptions = append(payerOptions, RequestPayer(PayerType(payer)))
-	}
-
 	// Load CP data
 	ccp := copyCheckpoint{}
 	err = ccp.load(cpFilePath)
@@ -399,7 +389,9 @@ func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName,
 	}
 
 	// Make sure the object is not updated.
-	meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, payerOptions...)
+	// get whole length
+	skipOptions := deleteOption(options, HTTPHeaderRange)
+	meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, skipOptions...)
 	if err != nil {
 		return err
 	}
@@ -430,7 +422,7 @@ func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName,
 	publishProgress(listener, event)
 
 	// Start the worker coroutines
-	arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, payerOptions, copyPartHooker}
+	arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, options, copyPartHooker}
 	for w := 1; w <= routines; w++ {
 		go copyWorker(w, arg, jobs, results, failed, die)
 	}
@@ -464,5 +456,5 @@ func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName,
 	event = newProgressEvent(TransferCompletedEvent, completedBytes, ccp.ObjStat.Size)
 	publishProgress(listener, event)
 
-	return ccp.complete(descBucket, ccp.CopyParts, cpFilePath, payerOptions)
+	return ccp.complete(descBucket, ccp.CopyParts, cpFilePath, options)
 }

+ 92 - 0
oss/multicopy_test.go

@@ -2,7 +2,9 @@ package oss
 
 import (
 	"fmt"
+	"net/http"
 	"os"
+	"strings"
 	"time"
 
 	. "gopkg.in/check.v1"
@@ -496,3 +498,93 @@ func (s *OssCopySuite) TestCopyFileCrossBucket(c *C) {
 	err = s.client.DeleteBucket(destBucketName)
 	c.Assert(err, IsNil)
 }
+
+func (s *OssCopySuite) TestVersioningCopyFileCrossBucket(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// begin test
+	objectName := objectNamePrefix + randStr(8)
+	fileName := "test-file-" + randStr(8)
+	fileData := randStr(500 * 1024)
+	createFile(fileName, fileData, c)
+	newFile := "test-file-" + randStr(8)
+	destBucketName := bucketName + "-desc"
+	srcObjectName := objectNamePrefix + randStr(8)
+	destObjectName := srcObjectName + "-dest"
+
+	// Create dest bucket
+	err = client.CreateBucket(destBucketName)
+	c.Assert(err, IsNil)
+	destBucket, err := client.Bucket(destBucketName)
+	c.Assert(err, IsNil)
+
+	err = client.SetBucketVersioning(destBucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// Upload source file
+	var respHeader http.Header
+	options := []Option{Routines(3), GetResponseHeader(&respHeader)}
+	err = bucket.UploadFile(srcObjectName, fileName, 100*1024, options...)
+	versionId := GetVersionId(respHeader)
+	c.Assert(len(versionId) > 0, Equals, true)
+
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// overwrite emtpy object
+	err = bucket.PutObject(srcObjectName, strings.NewReader(""))
+	c.Assert(err, IsNil)
+
+	// Copy files
+	var respCopyHeader http.Header
+	options = []Option{Routines(5), Checkpoint(true, destObjectName+".cp"), GetResponseHeader(&respCopyHeader), VersionId(versionId)}
+	err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, options...)
+	c.Assert(err, IsNil)
+	versionIdCopy := GetVersionId(respCopyHeader)
+	c.Assert(len(versionIdCopy) > 0, Equals, true)
+
+	err = destBucket.GetObjectToFile(destObjectName, newFile, VersionId(versionIdCopy))
+	c.Assert(err, IsNil)
+
+	eq, err := compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	err = destBucket.DeleteObject(destObjectName)
+	c.Assert(err, IsNil)
+	os.Remove(newFile)
+
+	// Copy file with options meta
+	options = []Option{Routines(10), Checkpoint(true, "copy.cp"), Meta("myprop", "mypropval"), GetResponseHeader(&respCopyHeader), VersionId(versionId)}
+	err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, options...)
+	c.Assert(err, IsNil)
+	versionIdCopy = GetVersionId(respCopyHeader)
+
+	err = destBucket.GetObjectToFile(destObjectName, newFile, VersionId(versionIdCopy))
+	c.Assert(err, IsNil)
+
+	eq, err = compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	os.Remove(fileName)
+	os.Remove(newFile)
+	destBucket.DeleteObject(destObjectName)
+	bucket.DeleteObject(objectName)
+	forceDeleteBucket(client, bucketName, c)
+	forceDeleteBucket(client, destBucketName, c)
+}

+ 14 - 2
oss/multipart.go

@@ -151,10 +151,22 @@ func (bucket Bucket) UploadPartCopy(imur InitiateMultipartUploadResult, srcBucke
 	startPosition, partSize int64, partNumber int, options ...Option) (UploadPart, error) {
 	var out UploadPartCopyResult
 	var part UploadPart
+	var opts []Option
+
+	//first find version id
+	versionIdKey := "versionId"
+	versionId, _ := findOption(options, versionIdKey, nil)
+	if versionId == nil {
+		opts = []Option{CopySource(srcBucketName, url.QueryEscape(srcObjectKey)),
+			CopySourceRange(startPosition, partSize)}
+	} else {
+		opts = []Option{CopySourceVersion(srcBucketName, url.QueryEscape(srcObjectKey), versionId.(string)),
+			CopySourceRange(startPosition, partSize)}
+		options = deleteOption(options, versionIdKey)
+	}
 
-	opts := []Option{CopySource(srcBucketName, url.QueryEscape(srcObjectKey)),
-		CopySourceRange(startPosition, partSize)}
 	opts = append(opts, options...)
+
 	params := map[string]interface{}{}
 	params["partNumber"] = strconv.Itoa(partNumber)
 	params["uploadId"] = imur.UploadID

+ 1 - 1
oss/multipart_test.go

@@ -843,7 +843,7 @@ func (s *OssBucketMultipartSuite) TestUploadFile(c *C) {
 	acl, err := s.bucket.GetObjectACL(objectName)
 	c.Assert(err, IsNil)
 	testLogger.Println("GetObjectAcl:", acl)
-	c.Assert(acl.ACL, Equals, "default")
+	c.Assert(acl.ACL, Equals, "public-read")
 
 	meta, err := s.bucket.GetObjectDetailedMeta(objectName)
 	c.Assert(err, IsNil)

+ 58 - 0
oss/option.go

@@ -24,6 +24,7 @@ const (
 	initCRC64          = "init-crc64"
 	progressListener   = "x-progress-listener"
 	storageClass       = "storage-class"
+	responseHeader     = "x-response-header"
 )
 
 type (
@@ -126,6 +127,11 @@ func CopySource(sourceBucket, sourceObject string) Option {
 	return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject)
 }
 
+// CopySourceVersion is an option to set X-Oss-Copy-Source header,include versionId
+func CopySourceVersion(sourceBucket, sourceObject string, versionId string) Option {
+	return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject+"?"+"versionId="+versionId)
+}
+
 // CopySourceRange is an option to set X-Oss-Copy-Source header
 func CopySourceRange(startPosition, partSize int64) Option {
 	val := "bytes=" + strconv.FormatInt(startPosition, 10) + "-" +
@@ -259,6 +265,16 @@ func KeyMarker(value string) Option {
 	return addParam("key-marker", value)
 }
 
+// VersionIdMarker is an option to set version-id-marker parameter
+func VersionIdMarker(value string) Option {
+	return addParam("version-id-marker", value)
+}
+
+// VersionId is an option to set versionId parameter
+func VersionId(value string) Option {
+	return addParam("versionId", value)
+}
+
 // TagKey is an option to set tag key parameter
 func TagKey(value string) Option {
 	return addParam("tag-key", value)
@@ -326,6 +342,11 @@ func Progress(listener ProgressListener) Option {
 	return addArg(progressListener, listener)
 }
 
+// GetResponseHeader for get response http header
+func GetResponseHeader(respHeader *http.Header) Option {
+	return addArg(responseHeader, respHeader)
+}
+
 // ResponseContentType is an option to set response-content-type param
 func ResponseContentType(value string) Option {
 	return addParam("response-content-type", value)
@@ -463,3 +484,40 @@ func isOptionSet(options []Option, option string) (bool, interface{}, error) {
 	}
 	return false, nil, nil
 }
+
+func deleteOption(options []Option, strKey string) []Option {
+	var outOption []Option
+	params := map[string]optionValue{}
+	for _, option := range options {
+		if option != nil {
+			option(params)
+			_, exist := params[strKey]
+			if !exist {
+				outOption = append(outOption, option)
+			} else {
+				delete(params, strKey)
+			}
+		}
+	}
+	return outOption
+}
+
+func GetRequestId(header http.Header) string {
+	return header.Get("x-oss-request-id")
+}
+
+func GetVersionId(header http.Header) string {
+	return header.Get("x-oss-version-id")
+}
+
+func GetCopySrcVersionId(header http.Header) string {
+	return header.Get("x-oss-copy-source-version-id")
+}
+
+func GetDeleteMark(header http.Header) bool {
+	value := header.Get("x-oss-delete-marker")
+	if strings.ToUpper(value) == "TRUE" {
+		return true
+	}
+	return false
+}

+ 18 - 0
oss/option_test.go

@@ -297,3 +297,21 @@ func (s *OssOptionSuite) TestFindOption(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(str, Equals, "")
 }
+
+func (s *OssOptionSuite) TestDeleteOption(c *C) {
+	options := []Option{VersionId("123"), VersionIdMarker("456"), KeyMarker("789")}
+	str, err := findOption(options, "versionId", "")
+	c.Assert(str, Equals, "123")
+	c.Assert(err, IsNil)
+
+	skipOption := deleteOption(options, "versionId")
+	str, err = findOption(skipOption, "versionId", "")
+	c.Assert(str, Equals, "")
+
+	str, err = findOption(skipOption, "version-id-marker", "")
+	c.Assert(str, Equals, "456")
+
+	str, err = findOption(skipOption, "key-marker", "")
+	c.Assert(str, Equals, "789")
+
+}

+ 141 - 8
oss/type.go

@@ -237,6 +237,7 @@ type BucketInfo struct {
 	Owner            Owner     `xml:"Owner"`                    // Bucket owner
 	StorageClass     string    `xml:"StorageClass"`             // Bucket storage class
 	SseRule          SSERule   `xml:"ServerSideEncryptionRule"` // Bucket ServerSideEncryptionRule
+	Versioning       string    `xml:"Versioning"`               // Bucket Versioning
 }
 
 type SSERule struct {
@@ -270,6 +271,46 @@ type ObjectProperties struct {
 	StorageClass string    `xml:"StorageClass"` // Object storage class (Standard, IA, Archive)
 }
 
+// ListObjectVersionsResult defines the result from ListObjectVersions request
+type ListObjectVersionsResult struct {
+	XMLName             xml.Name                       `xml:"ListVersionsResult"`
+	Name                string                         `xml:"Name"`                  // The Bucket Name
+	Owner               Owner                          `xml:"Owner"`                 // The owner of bucket
+	Prefix              string                         `xml:"Prefix"`                // The object prefix
+	KeyMarker           string                         `xml:"KeyMarker"`             // The start marker filter.
+	VersionIdMarker     string                         `xml:"VersionIdMarker"`       // The start VersionIdMarker filter.
+	MaxKeys             int                            `xml:"MaxKeys"`               // Max keys to return
+	Delimiter           string                         `xml:"Delimiter"`             // The delimiter for grouping objects' name
+	IsTruncated         bool                           `xml:"IsTruncated"`           // Flag indicates if all results are returned (when it's false)
+	NextKeyMarker       string                         `xml:"NextKeyMarker"`         // The start point of the next query
+	NextVersionIdMarker string                         `xml:"NextVersionIdMarker"`   // The start point of the next query
+	CommonPrefixes      []string                       `xml:"CommonPrefixes>Prefix"` // You can think of commonprefixes as "folders" whose names end with the delimiter
+	ObjectDeleteMarkers []ObjectDeleteMarkerProperties `xml:"DeleteMarker"`          // DeleteMarker list
+	ObjectVersions      []ObjectVersionProperties      `xml:"Version"`               // version list
+}
+
+type ObjectDeleteMarkerProperties struct {
+	XMLName      xml.Name  `xml:"DeleteMarker"`
+	Key          string    `xml:"Key"`          // The Object Key
+	VersionId    string    `xml:"VersionId"`    // The Object VersionId
+	IsLatest     bool      `xml:"IsLatest"`     // is current version or not
+	LastModified time.Time `xml:"LastModified"` // Object last modified time
+	Owner        Owner     `xml:"Owner"`        // bucket owner element
+}
+
+type ObjectVersionProperties struct {
+	XMLName      xml.Name  `xml:"Version"`
+	Key          string    `xml:"Key"`          // The Object Key
+	VersionId    string    `xml:"VersionId"`    // The Object VersionId
+	IsLatest     bool      `xml:"IsLatest"`     // is latest version or not
+	LastModified time.Time `xml:"LastModified"` // Object last modified time
+	Type         string    `xml:"Type"`         // Object type
+	Size         int64     `xml:"Size"`         // Object size
+	ETag         string    `xml:"ETag"`         // Object ETag
+	StorageClass string    `xml:"StorageClass"` // Object storage class (Standard, IA, Archive)
+	Owner        Owner     `xml:"Owner"`        // bucket owner element
+}
+
 // Owner defines Bucket/Object's owner
 type Owner struct {
 	XMLName     xml.Name `xml:"Owner"`
@@ -295,14 +336,30 @@ type deleteXML struct {
 
 // DeleteObject defines the struct for deleting object
 type DeleteObject struct {
-	XMLName xml.Name `xml:"Object"`
-	Key     string   `xml:"Key"` // Object name
+	XMLName   xml.Name `xml:"Object"`
+	Key       string   `xml:"Key"`                 // Object name
+	VersionId string   `xml:"VersionId,omitempty"` // Object VersionId
 }
 
 // DeleteObjectsResult defines result of DeleteObjects request
 type DeleteObjectsResult struct {
-	XMLName        xml.Name `xml:"DeleteResult"`
-	DeletedObjects []string `xml:"Deleted>Key"` // Deleted object list
+	XMLName        xml.Name
+	DeletedObjects []string // Deleted object key list
+}
+
+// DeleteObjectsResult_inner defines result of DeleteObjects request
+type DeleteObjectVersionsResult struct {
+	XMLName              xml.Name         `xml:"DeleteResult"`
+	DeletedObjectsDetail []DeletedKeyInfo `xml:"Deleted"` // Deleted object detail info
+}
+
+// DeleteKeyInfo defines object delete info
+type DeletedKeyInfo struct {
+	XMLName               xml.Name `xml:"Deleted"`
+	Key                   string   `xml:"Key"`                   // Object key
+	VersionId             string   `xml:"VersionId"`             // VersionId
+	DeleteMarker          bool     `xml:"DeleteMarker"`          // Object DeleteMarker
+	DeleteMarkerVersionId string   `xml:"DeleteMarkerVersionId"` // Object DeleteMarkerVersionId
 }
 
 // InitiateMultipartUploadResult defines result of InitiateMultipartUpload request
@@ -409,10 +466,10 @@ type ProcessObjectResult struct {
 }
 
 // decodeDeleteObjectsResult decodes deleting objects result in URL encoding
-func decodeDeleteObjectsResult(result *DeleteObjectsResult) error {
+func decodeDeleteObjectsResult(result *DeleteObjectVersionsResult) error {
 	var err error
-	for i := 0; i < len(result.DeletedObjects); i++ {
-		result.DeletedObjects[i], err = url.QueryUnescape(result.DeletedObjects[i])
+	for i := 0; i < len(result.DeletedObjectsDetail); i++ {
+		result.DeletedObjectsDetail[i].Key, err = url.QueryUnescape(result.DeletedObjectsDetail[i].Key)
 		if err != nil {
 			return err
 		}
@@ -454,6 +511,73 @@ func decodeListObjectsResult(result *ListObjectsResult) error {
 	return nil
 }
 
+// decodeListObjectVersionsResult decodes list version objects result in URL encoding
+func decodeListObjectVersionsResult(result *ListObjectVersionsResult) error {
+	var err error
+
+	// decode:Delimiter
+	result.Delimiter, err = url.QueryUnescape(result.Delimiter)
+	if err != nil {
+		return err
+	}
+
+	// decode Prefix
+	result.Prefix, err = url.QueryUnescape(result.Prefix)
+	if err != nil {
+		return err
+	}
+
+	// decode KeyMarker
+	result.KeyMarker, err = url.QueryUnescape(result.KeyMarker)
+	if err != nil {
+		return err
+	}
+
+	// decode VersionIdMarker
+	result.VersionIdMarker, err = url.QueryUnescape(result.VersionIdMarker)
+	if err != nil {
+		return err
+	}
+
+	// decode NextKeyMarker
+	result.NextKeyMarker, err = url.QueryUnescape(result.NextKeyMarker)
+	if err != nil {
+		return err
+	}
+
+	// decode NextVersionIdMarker
+	result.NextVersionIdMarker, err = url.QueryUnescape(result.NextVersionIdMarker)
+	if err != nil {
+		return err
+	}
+
+	// decode CommonPrefixes
+	for i := 0; i < len(result.CommonPrefixes); i++ {
+		result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i])
+		if err != nil {
+			return err
+		}
+	}
+
+	// decode deleteMarker
+	for i := 0; i < len(result.ObjectDeleteMarkers); i++ {
+		result.ObjectDeleteMarkers[i].Key, err = url.QueryUnescape(result.ObjectDeleteMarkers[i].Key)
+		if err != nil {
+			return err
+		}
+	}
+
+	// decode ObjectVersions
+	for i := 0; i < len(result.ObjectVersions); i++ {
+		result.ObjectVersions[i].Key, err = url.QueryUnescape(result.ObjectVersions[i].Key)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // decodeListUploadedPartsResult decodes
 func decodeListUploadedPartsResult(result *ListUploadedPartsResult) error {
 	var err error
@@ -609,14 +733,23 @@ type Tag struct {
 	Value   string   `xml:"Value"`
 }
 
-// ObjectTagging tagset for the object
+// Tagging tagset for the object
 type Tagging struct {
 	XMLName xml.Name `xml:"Tagging"`
 	Tags    []Tag    `xml:"TagSet>Tag,omitempty"`
 }
 
+// for GetObjectTagging return value
 type GetObjectTaggingResult Tagging
 
+// VersioningConfig for the bucket
+type VersioningConfig struct {
+	XMLName xml.Name `xml:"VersioningConfiguration"`
+	Status  string   `xml:"Status"`
+}
+
+type GetBucketVersioningResult VersioningConfig
+
 // Server Encryption rule for the bucket
 type ServerEncryptionRule struct {
 	XMLName    xml.Name       `xml:"ServerSideEncryptionRule"`

+ 6 - 6
oss/type_test.go

@@ -19,20 +19,20 @@ var (
 )
 
 func (s *OssTypeSuite) TestDecodeDeleteObjectsResult(c *C) {
-	var res DeleteObjectsResult
+	var res DeleteObjectVersionsResult
 	err := decodeDeleteObjectsResult(&res)
 	c.Assert(err, IsNil)
 
-	res.DeletedObjects = []string{""}
+	res.DeletedObjectsDetail = []DeletedKeyInfo{DeletedKeyInfo{Key: ""}}
 	err = decodeDeleteObjectsResult(&res)
 	c.Assert(err, IsNil)
-	c.Assert(res.DeletedObjects[0], Equals, "")
+	c.Assert(res.DeletedObjectsDetail[0].Key, Equals, "")
 
-	res.DeletedObjects = []string{goURLStr, chnURLStr}
+	res.DeletedObjectsDetail = []DeletedKeyInfo{DeletedKeyInfo{Key: goURLStr}, DeletedKeyInfo{Key: chnURLStr}}
 	err = decodeDeleteObjectsResult(&res)
 	c.Assert(err, IsNil)
-	c.Assert(res.DeletedObjects[0], Equals, goStr)
-	c.Assert(res.DeletedObjects[1], Equals, chnStr)
+	c.Assert(res.DeletedObjectsDetail[0].Key, Equals, goStr)
+	c.Assert(res.DeletedObjectsDetail[1].Key, Equals, chnStr)
 }
 
 func (s *OssTypeSuite) TestDecodeListObjectsResult(c *C) {

+ 6 - 18
oss/upload.go

@@ -175,12 +175,6 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti
 		return err
 	}
 
-	payerOptions := []Option{}
-	payer := getPayer(options)
-	if payer != "" {
-		payerOptions = append(payerOptions, RequestPayer(PayerType(payer)))
-	}
-
 	// Initialize the multipart upload
 	imur, err := bucket.InitiateMultipartUpload(objectKey, options...)
 	if err != nil {
@@ -198,7 +192,7 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti
 	publishProgress(listener, event)
 
 	// Start the worker coroutine
-	arg := workerArg{&bucket, filePath, imur, payerOptions, uploadPartHooker}
+	arg := workerArg{&bucket, filePath, imur, options, uploadPartHooker}
 	for w := 1; w <= routines; w++ {
 		go worker(w, arg, jobs, results, failed, die)
 	}
@@ -221,7 +215,7 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti
 			close(die)
 			event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes)
 			publishProgress(listener, event)
-			bucket.AbortMultipartUpload(imur, payerOptions...)
+			bucket.AbortMultipartUpload(imur, options...)
 			return err
 		}
 
@@ -234,9 +228,9 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti
 	publishProgress(listener, event)
 
 	// Complete the multpart upload
-	_, err = bucket.CompleteMultipartUpload(imur, parts, payerOptions...)
+	_, err = bucket.CompleteMultipartUpload(imur, parts, options...)
 	if err != nil {
-		bucket.AbortMultipartUpload(imur, payerOptions...)
+		bucket.AbortMultipartUpload(imur, options...)
 		return err
 	}
 	return nil
@@ -448,12 +442,6 @@ func complete(cp *uploadCheckpoint, bucket *Bucket, parts []UploadPart, cpFilePa
 func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64, options []Option, cpFilePath string, routines int) error {
 	listener := getProgressListener(options)
 
-	payerOptions := []Option{}
-	payer := getPayer(options)
-	if payer != "" {
-		payerOptions = append(payerOptions, RequestPayer(PayerType(payer)))
-	}
-
 	// Load CP data
 	ucp := uploadCheckpoint{}
 	err := ucp.load(cpFilePath)
@@ -486,7 +474,7 @@ func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64
 	publishProgress(listener, event)
 
 	// Start the workers
-	arg := workerArg{&bucket, filePath, imur, payerOptions, uploadPartHooker}
+	arg := workerArg{&bucket, filePath, imur, options, uploadPartHooker}
 	for w := 1; w <= routines; w++ {
 		go worker(w, arg, jobs, results, failed, die)
 	}
@@ -521,6 +509,6 @@ func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64
 	publishProgress(listener, event)
 
 	// Complete the multipart upload
-	err = complete(&ucp, &bucket, ucp.allParts(), cpFilePath, payerOptions)
+	err = complete(&ucp, &bucket, ucp.allParts(), cpFilePath, options)
 	return err
 }

+ 61 - 0
oss/upload_test.go

@@ -3,6 +3,7 @@ package oss
 import (
 	"fmt"
 	"io"
+	"net/http"
 	"os"
 	"time"
 
@@ -474,3 +475,63 @@ func copyFile(src, dst string) error {
 	_, err = io.Copy(dstFile, srcFile)
 	return err
 }
+
+func (s *OssUploadSuite) TestVersioningUploadRoutineWithRecovery(c *C) {
+	// create a bucket with default proprety
+	client, err := New(endpoint, accessID, accessKey)
+	c.Assert(err, IsNil)
+
+	bucketName := bucketNamePrefix + randLowStr(6)
+	err = client.CreateBucket(bucketName)
+	c.Assert(err, IsNil)
+
+	bucket, err := client.Bucket(bucketName)
+
+	// put bucket version:enabled
+	var versioningConfig VersioningConfig
+	versioningConfig.Status = string(VersionEnabled)
+	err = client.SetBucketVersioning(bucketName, versioningConfig)
+	c.Assert(err, IsNil)
+
+	// begin test
+	objectName := objectNamePrefix + randStr(8)
+	fileName := "test-file-" + randStr(8)
+	fileData := randStr(500 * 1024)
+	createFile(fileName, fileData, c)
+	newFile := "test-file-" + randStr(8)
+
+	// Use default routines and default CP file path (fileName+.cp)Header
+	// First upload for 4 parts
+	var respHeader http.Header
+	uploadPartHooker = ErrorHooker
+	options := []Option{Checkpoint(true, fileName+".cp"), GetResponseHeader(&respHeader)}
+	err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+	c.Assert(err, NotNil)
+	c.Assert(err.Error(), Equals, "ErrorHooker")
+	c.Assert(GetVersionId(respHeader), Equals, "")
+
+	uploadPartHooker = defaultUploadPart
+
+	// Second upload, finish the remaining part
+	options = []Option{Checkpoint(true, fileName+".cp"), GetResponseHeader(&respHeader)}
+	err = bucket.UploadFile(objectName, fileName, 100*1024, options...)
+	c.Assert(err, IsNil)
+	versionIdUp := GetVersionId(respHeader)
+	c.Assert(len(versionIdUp) > 0, Equals, true)
+
+	os.Remove(newFile)
+	var respHeaderDown http.Header
+	err = bucket.GetObjectToFile(objectName, newFile, GetResponseHeader(&respHeaderDown))
+	versionIdDown := GetVersionId(respHeaderDown)
+	c.Assert(err, IsNil)
+	c.Assert(versionIdUp, Equals, versionIdDown)
+
+	eq, err := compareFiles(fileName, newFile)
+	c.Assert(err, IsNil)
+	c.Assert(eq, Equals, true)
+
+	os.Remove(fileName)
+	os.Remove(newFile)
+	bucket.DeleteObject(objectName)
+	forceDeleteBucket(client, bucketName, c)
+}