-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
provider/google: Cloud router resource #12411
Conversation
95318ad
to
05b2f10
Compare
641aceb
to
cc38e07
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @drebes, thanks for opening this PR! Sorry for the long wait on the review. A PR this size is pretty intimidating and takes a lot of time to review. In the future, a PR that tackled maybe one of these resources would probably get a faster review on it, just as a heads up.
I know there are a lot of comments, but a lot of them are repetitive and are pretty minor. You did a great job at the design level, and I don't see much here that would require a refactor or major shift in how you're doing things. You get bonus points for supporting import right out of the gate! I was hoping to see Exists support, as well, but I won't hold this up over it.
That being said, it'd also be great if you could add some docs for the new resources/update the docs for the existing resource you've modified so people know how to use this great new functionality. If you need an example of that, let me know.
I know the amount of comments here is intimidating, but I think you're super close to having this be merge-ready; just some find-and-replaces and a few tweaks away.
Thanks for all the effort! If you've moved on/don't have time anymore to push this over the finish line, let me know, and I'll tackle it. :)
builtin/providers/google/provider.go
Outdated
@@ -5,12 +5,17 @@ import ( | |||
"fmt" | |||
"strings" | |||
|
|||
"github.com/hashicorp/terraform/helper/mutexkv" | |||
//"github.com/hashicorp/terraform/helper/pathorcontents" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would love to remove commented out code.
if err != nil { | ||
return err | ||
} | ||
routersService := compute.NewRoutersService(config.clientCompute) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a benefit to doing this instead of using config.clientCompute.Routers
?
return fmt.Errorf("Error Inserting Router %s into network %s: %s", name, network, err) | ||
} | ||
|
||
err = computeOperationWaitRegion(config, op, project, region, "Inserting Router") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love to see a call to d.SetId
prior to the computeOperationWaitRegion
, which just helps Terraform recover in case the operation times out or Terraform crashes, or something. Keep in mind this means that if there's an error inserting, d.SetId("")
should be called, to clear out the ID. See an example in google_compute_instance
.
} | ||
|
||
name := d.Get("name").(string) | ||
routersService := compute.NewRoutersService(config.clientCompute) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, I think we want config.clientCompute.Routers
, just to be consistent, unless there's a difference or benefit I'm missing.
|
||
if err != nil { | ||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { | ||
log.Printf("[WARN] Removing Router %q because it's gone", d.Get("name").(string)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are routers unique in name, or only unique per-region? If the latter, I think we would want to log the region here, too, right? Just so the router can be uniquely identified?
} | ||
`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) | ||
|
||
var testAccComputeRouter_noRegion = fmt.Sprintf(` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would love to see this as a function instead of a variable.
} | ||
`, acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)) | ||
|
||
var testAccComputeRouter_networkLink = fmt.Sprintf(` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would love to see this as a function instead of a variable.
@@ -77,6 +78,7 @@ func resourceComputeVpnTunnel() *schema.Resource { | |||
Type: schema.TypeSet, | |||
Optional: true, | |||
ForceNew: true, | |||
Computed: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For posterity, why does this need to be computed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a tunnel is created attached to a router, it will have a default policy (local and remote traffic selectors) of "0.0.0.0/0". We already had local_traffic_selector as computed, but would not get a default remote_traffic_selector from the server until now. With router based tunnels, we get them, so the need for the Computed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
@@ -154,6 +173,67 @@ resource "google_compute_vpn_tunnel" "foobar" { | |||
acctest.RandString(10), acctest.RandString(10), acctest.RandString(10), | |||
acctest.RandString(10), acctest.RandString(10)) | |||
|
|||
var testAccComputeVpnTunnel_router = fmt.Sprintf(` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be great to turn this into a function.
target = "${google_compute_vpn_gateway.foobar.self_link}" | ||
} | ||
resource "google_compute_router" "foobar"{ | ||
name = "tunnel-test-router" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this have to be unique? If so, for resiliency, I'd like to have it randomized so we don't have collisions if the test runner fails and leaves dangling resources.
Hi, thanks for the feedback! I'll need some time to go through all the comments but will definitely do it. |
Awesome, thanks! Feel free to tag off or ask for help or clarification if we can assist somehow. :) |
e0b682b
to
97ef205
Compare
Hi @paddycarver , I've implemented most of the changes, and replied to the comments where just clarification was needed. Sorry for the large PR, creating a router without peers and interfaces is not functional so I thought it made more sense to send all as a single PR. Will send smaller, incremental ones in the future. What did you mean by "Exists" support? Can you point me to an example? I still need to add some docs, will work on it from now. |
7a7bfaa
to
9968166
Compare
- config.clientCompute.Routers - peer fields renamed - more consistent logging - better handling of SetId for error handling - function for router locks - test configs as functions - simplify exists logic - use getProject, getRegion logic on acceptance tests - CheckDestroy for peers an interfaces - dynamic router name for tunnel test - extra fields for BgpPeer - resource documentation
Done with the docs, just missing feedback on the points replied above. |
Resolved merge conflicts, will review now. :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, this sat open in my browser for literally weeks, accruing comments slowly over time. Sorry @drebes! Only a handful more minor comments. If you want, I'm happy to schedule a short chunk of time to sit and pair with you on this and we'll get it pushed over the barrier and merged. If you're (understandably!) tired of this PR, I'm happy to carry the torch, if you'd like.
For me, the last big open question re: design is why the mutexes are being used.
udp4500Rule := fmt.Sprintf("router-interface-import-test-%s", acctest.RandString(10)) | ||
router := fmt.Sprintf("router-interface-import-test-%s", acctest.RandString(10)) | ||
tunnel := fmt.Sprintf("router-interface-import-test-%s", acctest.RandString(10)) | ||
iface := fmt.Sprintf("router-interface-import-test-%s", acctest.RandString(10)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do all of these need to be controlled on a per-test basis? If not, it may be clearer to just put them in the function, instead of passing them in as args. Sorry if my last review wasn't super clear; generally, my rule of thumb is if the test would want to control the value, it should be an arg, otherwise it should be defined in the function. The important part is that we're not using fmt.Sprintf
as a templating function (you're not, here), and that each test case gets a unique value for things that require uniqueness. That can be achieved by just moving the variable declaration into the function, or moving the fmt.Sprintf
into the function (but it has to be inside a function, not just one big fmt.Sprintf
call assigned to a variable, otherwise it only gets defined once).
router := fmt.Sprintf("router-peer-import-test-%s", acctest.RandString(10)) | ||
tunnel := fmt.Sprintf("router-peer-import-test-%s", acctest.RandString(10)) | ||
iface := fmt.Sprintf("router-peer-import-test-%s", acctest.RandString(10)) | ||
peer := fmt.Sprintf("router-peer-import-test-%s", acctest.RandString(10)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing here.
resourceName := "google_compute_router.foobar" | ||
network := fmt.Sprintf("router-import-test-%s", acctest.RandString(10)) | ||
subnet := fmt.Sprintf("router-import-test-%s", acctest.RandString(10)) | ||
router := fmt.Sprintf("router-import-test-%s", acctest.RandString(10)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one, on the other hand, feels reasonable and appropriate.
"github.com/hashicorp/terraform/helper/schema" | ||
"github.com/hashicorp/terraform/terraform" | ||
"google.golang.org/api/compute/v1" | ||
"google.golang.org/api/googleapi" | ||
) | ||
|
||
// Global MutexKV | ||
var mutexKV = mutexkv.NewMutexKV() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still unclear, sorry, why we're using a mutex. Could you explain? I see that it seems to restrict it so only one API call for the same router (or its interfaces or peers) can happen at once, but why? Is that an API limitation? Could we comment the code with why this is necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interfaces and Peers are not first class objects in the API, but attributes of the routers. As they don't have their own create/delete methods, but instead are created/destroyed with updates to the router that hosts them, the mutex is to ensure that no concurrent access on the router due to different peers/interfaces breaks the read -> add/remove -> write create/delete logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, that's the same reason I test deletion of peers/interfaces by using a second test step with similar config. (with the omitted resource, but same testID). If I just tested destroy, this would delete the hosted router, which of course would delete the peer/interface, but not test the proper deletion of the peer/interface only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks!
Worth noting that this isn't entirely safe--if people are modifying things outside of Terraform, the read/modify/write loop would still overwrite the changes. But our stance is people shouldn't be doing that, so I'm fine accepting this limitation.
return config.Project, nil | ||
} | ||
return "", fmt.Errorf("%q: required field is not set", "project") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏💯
d.Set("peer_ip_address", peer.PeerIpAddress) | ||
d.Set("peer_asn", peer.PeerAsn) | ||
d.Set("advertised_route_priority", peer.AdvertisedRoutePriority) | ||
d.Set("ip_address", peer.IpAddress) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would love to see project and region persisted here, in case they change in the provider config.
func resourceComputeRouterPeerImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { | ||
parts := strings.Split(d.Id(), "/") | ||
if len(parts) != 3 { | ||
return nil, fmt.Errorf("Invalid router specifier. Expecting {region}/{router}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm missing something. Why are we expecting three parts (line 281) if we're expecting only a single /
as input? Where in that expecting string is the "name" portion that's being set in state below?
router := fmt.Sprintf("router-peer-test-%s", acctest.RandString(10)) | ||
tunnel := fmt.Sprintf("router-peer-test-%s", acctest.RandString(10)) | ||
iface := fmt.Sprintf("router-peer-test-%s", acctest.RandString(10)) | ||
peer := fmt.Sprintf("router-peer-test-%s", acctest.RandString(10)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing here.
@@ -77,6 +78,7 @@ func resourceComputeVpnTunnel() *schema.Resource { | |||
Type: schema.TypeSet, | |||
Optional: true, | |||
ForceNew: true, | |||
Computed: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
udp500Rule := fmt.Sprintf("router-interface-test-%s", acctest.RandString(10)) | ||
udp4500Rule := fmt.Sprintf("router-interface-test-%s", acctest.RandString(10)) | ||
router := fmt.Sprintf("router-interface-test-%s", acctest.RandString(10)) | ||
tunnel := fmt.Sprintf("router-interface-test-%s", acctest.RandString(10)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing here.
We have an Exists lifecycle method (like Read, Update, Destroy) that runs before some of the other methods, allowing Terraform to check if a resource with the given ID exists or not.
(from https://godoc.org/github.com/hashicorp/terraform/helper/schema#Resource) An example of it being used is here: terraform/builtin/providers/google/resource_container_node_pool.go Lines 167 to 191 in 2a36e75
It's not necessary, but is nice to have. At this point in the review of this resource, I'd be happy to accept it without Exists, and iterate Exists into... existence later. But if you feel the urge, there's the info. :) Thanks for your patience and your hard work on this. |
7f48364
to
5785f15
Compare
- test arguments - set region, project in state - fix import error messages - get rid of peerFound - linkDiffSuppress
Thanks for the clarification on the Exists support. Still not clear to me is when the function is called during the resource lifecycle, but I can check that. If that would give me reasonable confidence that I can get rid of the multiple checks for existence I do on read/create/delete, I see the value in adding it. The other changes should be implemented, and the mutex question I've answered above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, tests pass. Merging!
Thanks @drebes, for your Herculean patience and endurance on this one. This is great new functionality to add, and I'm excited for it!
"github.com/hashicorp/terraform/helper/schema" | ||
"github.com/hashicorp/terraform/terraform" | ||
"google.golang.org/api/compute/v1" | ||
"google.golang.org/api/googleapi" | ||
) | ||
|
||
// Global MutexKV | ||
var mutexKV = mutexkv.NewMutexKV() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks!
Worth noting that this isn't entirely safe--if people are modifying things outside of Terraform, the read/modify/write loop would still overwrite the changes. But our stance is people shouldn't be doing that, so I'm fine accepting this limitation.
I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. |
This adds support for Cloud Router, which allows BGP announcement of routes through Cloud VPN tunnels.
It's still missing documentation, I'll provide that once the code is reviewed.
It adds the following resources:
google_compute_router
google_compute_router_interface
google_compute_router_peer
It also modifies
google_compute_vpn_tunnel
to support Cloud Router.A full test is provided on resource_compute_router_peer_test.go