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

instances: Patch CNS processing during instance creation #32

Merged
merged 3 commits into from
Jul 31, 2017

Conversation

jwreagor
Copy link
Contributor

CNS was not respected when creating a new instance. This addresses that concern, adds a thorough acceptance test for instance creation, and includes an example for others.

Copy link
Contributor

@jen20 jen20 left a comment

Choose a reason for hiding this comment

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

One minor nit, but other than that this looks correct to me. 👍 for the test.

@@ -963,8 +974,7 @@ func (api *_Instance) toNative() (*Instance, error) {
// submit an API call to the vmapi machine endpoint
func (mcns *InstanceCNS) toTags(m map[string]interface{}) {
if mcns.Disable != nil {
s := fmt.Sprintf("%t", mcns.Disable)
m[CNSTagDisable] = &s
m[CNSTagDisable] = fmt.Sprintf("%t", *mcns.Disable)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should likely use strconv.FormatBool from the standard library instead of fmt.Sprintf

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough, I think I'm going to remove the pointers here as well. I'm not sure why they're used by the InstanceCNS struct values.

@jwreagor
Copy link
Contributor Author

Actually, looks like there are some changes for supporting CNS updates through the ReplaceTags endpoint. I'll include them here as well.

Disable *bool
ReversePTR *string
Disable bool
ReversePTR string
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should be fine changing these semantics considering this struct was renamed under the new API.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✔️

@jwreagor jwreagor requested a review from jen20 July 29, 2017 00:19
@jwreagor
Copy link
Contributor Author

Nevermind... found another last minute change.

@jwreagor jwreagor removed the request for review from jen20 July 29, 2017 00:38
m[CNSTagDisable] = &s
func (cns *InstanceCNS) toTags(m map[string]interface{}) {
if cns.Disable {
m[CNSTagDisable] = cns.Disable
Copy link
Contributor Author

@jwreagor jwreagor Jul 29, 2017

Choose a reason for hiding this comment

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

Just confirmed that this actually needs to be a boolean and posted as such through the tags API (as JSON).

Copy link
Contributor

Choose a reason for hiding this comment

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

A bool or a "string masquerading as a bool" ? Regardless, this may be worth calling out as a comment in the code:

// NOTE(justinwr@): The API requires the CNSTagDisable attribute to be a boolean, not a bool string.

That comment probably needs to chase into a Validator() function, actually.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A bool bool since the JSON encoder will keep it a boolean. Doesn't look like we had any other examples before this.

I'll add the comment here as well.

@jwreagor
Copy link
Contributor Author

Ok, this is ready for review again.

@jwreagor jwreagor requested a review from sean- July 29, 2017 02:43
Copy link
Contributor

@sean- sean- left a comment

Choose a reason for hiding this comment

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

A few misc items and comments.

input.CNS.toTags(result)
for key, value := range result {
if key == CNSTagServices {
result[fmt.Sprintf("tag.%s", key)] = value
Copy link
Contributor

Choose a reason for hiding this comment

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

This is actually one of those cases where I'd skip fmt.Sprintf() and would just use + to concatenate the two strings.

That said, can you sanitize the input for key to make sure that it conforms to what we're expecting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can definitely clean this up a bit and rely more on CNSTagServices. That is the only tag we need to prepend with tag. here, anything else slips through.

}

// toAPI() merges both Tags and CNS tags into the same map of strings used as
// the body of our ReplaceTags request.
Copy link
Contributor

Choose a reason for hiding this comment

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

Append something like:

Used to join Tags and CNS tags into the same JSON object before sending an API request to the API gateway.

So that in the future we know we should be able to remove this method if/when CNS and Tags become first-class API citizens with their own API endpoints.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✔️

func (input ReplaceTagsInput) toAPI() map[string]interface{} {
result := map[string]interface{}{}
for key, value := range input.Tags {
result[key] = value
Copy link
Contributor

Choose a reason for hiding this comment

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

These values have been sanitized at this point, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we're alright since we're liberal with what can be a tag and we're also relying on the JSON encoder to marshal types going into CloudAPI. But I can see how the use of interface{} would normally allow other types through in cases like this.

m[CNSTagDisable] = &s
func (cns *InstanceCNS) toTags(m map[string]interface{}) {
if cns.Disable {
m[CNSTagDisable] = cns.Disable
Copy link
Contributor

Choose a reason for hiding this comment

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

A bool or a "string masquerading as a bool" ? Regardless, this may be worth calling out as a comment in the code:

// NOTE(justinwr@): The API requires the CNSTagDisable attribute to be a boolean, not a bool string.

That comment probably needs to chase into a Validator() function, actually.

if len(mcns.Services) > 0 {
m[CNSTagServices] = strings.Join(mcns.Services, ",")
if len(cns.Services) > 0 {
m[CNSTagServices] = strings.Join(cns.Services, ",")
Copy link
Contributor

Choose a reason for hiding this comment

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

In order to get a stable comparison, in both the *Read call and here, do we need to sort cns.Services? I don't know but seem to recall there wasn't a sorting guarantee here (I also don't think we should depend on it if there is one currently).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And we specifically spoke about this when I was in town too... I haven't seen a single issue but I'll test more variations.

Correct me if I'm wrong but we'd want to handle this on the Terraform/consumer side (here and here)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say this is something Terraform should handle rather than the library since that is where the sorted version is required.

@@ -30,6 +35,202 @@ func getAnyInstanceID(t *testing.T, client *compute.ComputeClient) (string, erro
return "", errors.New("no machines configured")
}

func RandInt() int {
reseed()
return rand.New(rand.NewSource(time.Now().UnixNano())).Int()
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm going to stump the seeding library here, either for explicit use or to crib an example from: https://github.com/sean-/seed

Copy link

Choose a reason for hiding this comment

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

Given we actually need cryptographically secure random number generation in the authentication package (ref https://github.com/joyent/triton-go/blob/5f7872646922b73632f434afa5e45fcd18aa9d5b/authentication/private_key_signer.go#L77) which is imported into the client, which is in turn imported everywhere, maybe we should put any seeding there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 I agree and I'll fit this into a secondary PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ref: #33 ✔️


func main() {
keyID := os.Getenv("SDC_KEY_ID")
accountName := os.Getenv("SDC_ACCOUNT")
Copy link
Contributor

Choose a reason for hiding this comment

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

It may be worth using os.LookupEnv here instead. This is just an example, but these values can't be empty strings without blowing up the downstream callers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✔️

createInput := &compute.CreateInstanceInput{
Name: "go-test1",
Package: "g4-highcpu-512M",
Image: "1f32508c-e6e9-11e6-bc05-8fea9e979940",
Copy link
Contributor

Choose a reason for hiding this comment

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

What datacenter is this instance out of? It may be worth noting the image ID is tied to a DC atm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was sloppy. I had written out the example for someone else and refactored into the test. I just need to pull the image ID dynamically as I did there.


select {
case instance := <-state:
fmt.Println("\nDuration:", time.Since(startTime).String())
Copy link
Contributor

Choose a reason for hiding this comment

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

fmt.Printf("\nDuration: %s\n", time.Since(startTime))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✔️

log.Fatalf("Create(): %v", err)
}

// Wait for provisioning to complete...
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be a helper function that we write and expose to consumers because most API consumers are going to want a synchronous API and semantics (including Terraform), not async.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I agree. I'll push a PR separately for that so we can define the API.

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems reasonable. We likely want to define a uniform way of dealing with this for as many resources as possible in order to reduce the amount of API internals which need to be understood to consume this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ref: #34 ✔️

@jwreagor
Copy link
Contributor Author

Think we're good to merge here.

@jen20
Copy link
Contributor

jen20 commented Jul 31, 2017

Looks good to me!

@jwreagor jwreagor merged commit e48f42a into TritonDataCenter:master Jul 31, 2017
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.

4 participants