Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure table implementation #217

Merged
merged 73 commits into from
May 3, 2016
Merged

Azure table implementation #217

merged 73 commits into from
May 3, 2016

Conversation

MindFlavor
Copy link
Contributor

Azure tables

An implementation of Azure Tables REST API using JSON instead of XML where possible. I strove to be aligned with the existing storage code so the usage is pretty straightforward:

err := cli.CreateTable(tn)
err := cli.DeleteTable(tn)

I could have mapped the entities to maps (map[string]interface{}) like it's done in the python Azure SDK but that would be awkward in golang: json and xml packages are able to handle custom structs using reflection so I did here too. For insert just pass the storage.TableEntity implementing struct and it will be marshalled automatically:

ce := &CustomEntity{Name: "Luke", Surname: "Skywalker", SomeDate: time.Now(), Number: 60, PKey: "pkey", RKey: "5"}

err := cli.InsertOrReplaceEntity(tn, ce)

As for unmarshaling you have to specify the type you are expecting:

entries, contToken, err = cli.QueryTableEntities(tn, nil, reflect.TypeOf(ce), 10, "")

Note that these methods store in Azure table rows (without hierarchy) so nested structs should not work by design.

There are more examples in the test files.

storage.TableEntity interface

type TableEntity interface {
    PartitionKey() string
    RowKey() string
    SetPartitionKey(string) error
    SetRowKey(string) error
}

Implement this interface to store your structs in Azure Tables.
This interface requires you to specify the partition and row keys as getter/setter. This way you can are free to represent the Azure Table primary key in your struct as you see fit.

table tag

The marshaler allows you to avoid persisting in Azure tables certain attributes. Just append the table:- (table minus) tag and it will be ignored. For example:

type CustomEntity struct {
    Name     string `json:"name"`
    Surname  string `json:"surname"`
    SomeDate time.Time
    Number   int
    PKey     string `json:"pk" table:"-"`
    RKey     string `json:"rk" table:"-"`
}

In this case the fields PKey and RKey won't be stored. This is useful because Azure Tables always require you to have the PartitionKey and RowKey fields so if you were to store PKey and RKey too you will end up duplicating the information.
The code will call your TableEntity.SetPartitionKey and TableEntity.SetRowKey methods during unmarshal allowing you to restore the ignored fields correctly.

Comments

The code is very poorly commented. Most methods lack the proper description. I will add them as soon as I can.

ToDo

  • The biggest todo is to return the If-Match header in the Entity get methods. Without it you cannot use optimistic concurrency in deletes. The DeleteEntity method supports is anyway so it's just a matter to return it to the GetEntity callers.
  • Better support for OData queries. Right now you can specify only the $filter param and you have to construct it by hand. Azure Tables also support the $select parameter for projection but I have not inserted the code here. It should be easy though. What would be harder is to implement the odata protocol. There is a project on that here (https://github.com/amsokol/go-odata) but I haven't looked at it yet.

req.Header.Add(k, v)
}

httpClient := http.Client{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @MindFlavor I just merged #298 which reads this from Client.HTTPClient (and defaults to net/http#DefaultClient). In this case you'll probably need to rebase your code on top of master and refactor this part sometime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it still looks like this part is not addressed. can you just git fetch origin && git rebase origin/master and git push -f

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well of course, I was merging from the wrong repo 😊 ... It should be ok now...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MindFlavor you should still update this part to use c.Client. Please see the change in #298.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again, sorry :-). Let's hope I got it right this time 😊

@MindFlavor
Copy link
Contributor Author

Sorry @ahmetalpbalkan I missed your comment

@MindFlavor I just didn't understand this part: we call SetPartitionKey/SetRowKey only when deserializing from Azure Storage response right? There's no way it can be invalid. Users should not store user-domain data in PK/RK as far as I know. Even in the C# SDK, these fields are just strings and you're going to be given whatever server gives you, you can't error out in that case? :)

I'll fix it right away removing the error and push again :)

@MindFlavor
Copy link
Contributor Author

I've also noticed something weird in Travis' test suite. As you can see from the last log:

table_test.go:263:
    c.Assert(entries[0].(*CustomEntity), chk.DeepEquals, ceList[0])
... obtained *storage.CustomEntity = &storage.CustomEntity{Name:"Test", Surname:"Test2", SomeDate:time.Time{sec:63594497276, nsec:347889129, loc:(*time.Location)(0xb75f40)}, Number:0, PKey:"pkey", RKey:"r0"}
... expected *storage.CustomEntity = &storage.CustomEntity{Name:"Test", Surname:"Test2", SomeDate:time.Time{sec:63594497276, nsec:347889129, loc:(*time.Location)(0xb7a2a0)}, Number:0, PKey:"pkey", RKey:"r0"}

The DeepEquals does not work as expected, it does not follow the loc:(*time.Location) pointer and compares the addresses instead (loc:(*time.Location)(0xb75f40) in one case and loc:(*time.Location)(0xb7a2a0) in the other). It should compare the Deep internal values instead :).
This is clearly a bug (and on my pc works correctly but... that's always the case of course 😊 )

To work around this I've removed the indirect time field in the tests for the time being. I think we should reinstate it sooner or later though (and that's why I had a separated testEquality method I think).

Value string `json:"value"`
}

type odataErrorMessageInternal struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MindFlavor do you ever use these error message/code fields in generating a human-readable error? perhaps we should return a typed-error that people can cast into and read these fields?

@cyonix
Copy link

cyonix commented Apr 27, 2016

Would be nice to see this pull request get merged in soon guys!!

@ahmetb
Copy link
Contributor

ahmetb commented May 3, 2016

Sorry for the delay here. We are okay with merging this, thanks a lot to @MindFlavor staying on top of this.
@MindFlavor there are still a few comments above, if you feel like addressing them I appreciate follow up requests. I will now squash this and merge.

@ahmetb ahmetb merged commit d4e45a6 into Azure:master May 3, 2016
daemonfire300 added a commit to daemonfire300/azure-sdk-for-go that referenced this pull request Jun 4, 2016
* Add support for creating Clients from a previously read settings file

* refactoring

* more refactoring to remove some xml duplication

* Reconcile publishSettings methods

* can have more than 1 WinRMListener when creating machine

* adding util functions for setting WinRM endpoints

* adding integration test for windows box with winrm

* tidy up some debug code

* fixing casing error picked up by go lint

* running go fmt

* Alpha of ARM packages

Signed-off-by: Brendan Dixon <[email protected]>

* Made examples a package

Signed-off-by: Brendan Dixon <[email protected]>

* Removed runner

Signed-off-by: Brendan Dixon <[email protected]>

* Switched to using log for errors

Signed-off-by: Brendan Dixon <[email protected]>

* Removed unnecessary return statements

Signed-off-by: Brendan Dixon <[email protected]>

* Made more use of log.Fatalf

Signed-off-by: Brendan Dixon <[email protected]>

* Converted examples to use Test for execution

Signed-off-by: Brendan Dixon <[email protected]>

* Corrected HTTP handling

Signed-off-by: Brendan Dixon <[email protected]>

* Exposing UnexpectedStatusCodeError.Got to support (fixes Azure#197)

* Queue - Get and Set metadata with user defined data (fixes Azure#200, fixes Azure#199)

* storage/file: Preliminary support for File Service

Support for CreateShare/DeleteShare calls.

* Add CreateBlockBlobWithData and CreateBlockBlobFromReader

* Add SetBlobMetadata and GetBlobMetadata

* Remove redundant CreateBlockBlobWithData. Add godoc hint about size limits.

* Use userDefinedMetadataHeaderPrefix const for "x-ms-meta-"

* Use HasPrefix instead of string slices

* Clarify test case

* Note size limits for PutBlock*

* Fix typos in auth header names

* v0.1.0-beta

Signed-off-by: Brendan Dixon <[email protected]>

* Added golint / govet to Travis CI for arm packages

Signed-off-by: Brendan Dixon <[email protected]>

* Fix typo in arm/README.md

Fix typo in arm/README.md

* Fix typo

* Use x/crypto/pkcs12 instead of Azure repo

* Use the Content-Length header if it is set

The Content-Length header is never used by the Go runtime.
Instead, the ContentLength field on the request struct is used to set the header
when writing the actual request. This field is only set for certain types of io.Readers.
Azure storage signatures include the Content-Length header, hence, it needs to be
respected, even if the io.Reader is some other type that is not recognized by the Go
runtime.

Signed-off-by: Paul Meyer <[email protected]>

* Adding AddRole method so that this bug can be fixed: hashicorp/terraform#3568

* Added comment with REST API reference - we need to add the other API referenced in that link to azure-sdk-for-go

* hashicorp/terraform#3568

* fix golint issue & go fmt

* DeleteRole REST API needs a query parameter to also delete the VHD disk from storage while deleting the VM. Added the parameter

* Adding boolean parameter to DeleteRole based on which the disk will be deleted or retained - as per review comment on this pull request - Azure#237

* Address outstanding issues, upgrade to latest Swagger

Signed-off-by: Brendan Dixon <[email protected]>

* A little bit better markdown

* Corrected client and enum name generation

Signed-off-by: Brendan Dixon <[email protected]>

* godep update github.com/Azure/go-autorest to 2.0.0

* Fixing PR Azure#187

* Make DeleteDisk return synchronously

* Fix DataDiskConfigurations XML unmarshalling in VMImage entity

* Allow filtering when listing VM images

* Add DeleteVirtualMachineImage

* Distinguish between deploying Public VM images and User VM images

* Follow management API endpoint redirects

* Added QueryParameterName and QueryParameterValue to Error func output

* gofmt

* add PutBlockWithContentType

* remove PutBlockWithContentType, add extraHeaders to PutBlockWithLength and PutPage

* Revision 2016-01-01

* fix blob_test.go

* add extraHeaders to CreateBlockBlobFromReader

* fix test

* Updated DNS to latest Swager

* fix storage test

* [glide] remove godeps

* [glide] remove Godep path rewriting

* [glide] glide init

* [glide] gofmt everything

* [glide] fix .travis.yml

* updated for glide.yaml

* ran 'glide up' to update dependenices to latest

* Update CHANGELOG.md

* updated CHANGELOG.md

* updated CHANGELOG.md

* version change

* Updated for godoc

* Updated for godoc

* Updated for the godoc comments

* [storage] Adds support for Append Blobs

Adds PutAppendBlob and AppendBlock methods
Changes DefaultAPIVersion to 2015-02-21
Updates storage client signing code to support newer version

Signed-off-by: Brian Bland <[email protected]>

* Add support of PostShudownAction for shutdown vm

* updated for long-running operations support

* Remove variedic argument in vw.ShutdownRole

* Fix for review comments

* Remove unused packages of patched legacy stdlibs

* Polling changes (go-autorest v6.0.0 updates)

* glide.lock changed

* Fix travis build

* Pin travis to go:1.6

* example/helpers/helpers.go

* ARM examples updated

* add support for extra headers in AppendBlock

* add support for extra headers in PutPage

* Version updated to 2.0.0-beta

* Reuse http.Client across requests, and allow caller to provide its own.

Enables connection sharing with http keepalive, and allows callers to
use Go's other http client/transport features like request timeouts.

* Allow caller to provide headers for DeleteBlob methods.

* Remove tests for If-[Un]modified-Since headers that Azure seems to ignore.

* Update for Polling and Asynchronous calls

Uses go-autorest version v7.0.0.

* Revert "Update for Polling and Asynchronous calls"

* Update polling logic for long-running operations

Uses go-autorest version v7.0.2.
Handle polling for both Asynchronous and Location headers.
Add cancel channel argument to cancel polling.

* Uses go-autorest v7.0.3

* storage: Add BlobPrefixes and Delimiter to BlobListResponse

Per the MSDN docs, these would be required in order to traverse
the blob storage as if it were a file system.

As with this example:

```
func traveseBlobs(client storage.BlobStorageClient, prefix string, depth int) {
	blobParams := storage.ListBlobsParameters{
		Delimiter: "/",
		Prefix:    prefix,
	}

	response, err := client.ListBlobs(accountContainer, blobParams)
	checkError(err)

	pad := strings.Repeat(" ", depth*2)

	// Print folders
	folders := response.BlobPrefixes
	for _, folder := range folders {
		folderWithoutParent := strings.TrimPrefix(folder, prefix)
		fmt.Printf("%sd %s\n", pad, folderWithoutParent)
		traveseBlobs(client, folder, depth+1)
	}

	// Print blob names
	blobs := response.Blobs
	for _, blob := range blobs {
		blobWithoutParent := strings.TrimPrefix(blob.Name, prefix)
		fmt.Printf("%s- %s\n", pad, blobWithoutParent)
	}
}
```

This would produce (with correct blobs in storage):

```
d root/
  d folderA/
    - fileAA.txt
    - fileAB.txt
    - fileAC.txt
  d folderB/
    d subfolderC/
      - fileBCA.txt
    - fileBA.txt
    - fileBB.txt
    - fileBC.txt
d some_folder/
  d some_subfolder/
    - xxx.txt
  - bar.txt
```

* set location to UTC to meet formatting requirements of Shared Access Signatures

* Better error messages for long running operation failures. Uses go-autorest v7.0.4.

* SDK version update

* Add extra headers support to GetBlobRange

* List blob metadata (Azure#319)

storage: Add Metadata to Blob in BlobListResponse.

* storage: Azure Table implementation (Azure#217)

Initial implementation for Azure Table Storage

* Implement xml.Marshaler for BlobMetadata type

Implement xml.Marshaler for BlobMetadata type

* Expose the public IP address of virtual machines (Azure#325)

The public IP name and timeout were provided, but there was no way to
get the actual address.

* Add support for extraHeaders in SetBlobMetadata (Azure#327)

* Adding LeaseStatus to storage.BlobProperties (Azure#336)

* Adding LeaseStatus to storage.BlobProperties

Particularly in case of a Page Blob, this property is useful in determining if the VHD is being used by a VM or not.

* Fixing Xml tag name for LeaseStatus Blob property (Fixes Azure#335)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants