diff --git a/aws/dx_vif.go b/aws/dx_vif.go index ea659c7b6c9..ebf0a54e045 100644 --- a/aws/dx_vif.go +++ b/aws/dx_vif.go @@ -26,6 +26,24 @@ func dxVirtualInterfaceRead(id string, conn *directconnect.DirectConnect) (*dire func dxVirtualInterfaceUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dxconn + req := &directconnect.UpdateVirtualInterfaceAttributesInput{ + VirtualInterfaceId: aws.String(d.Id()), + } + + requestUpdate := false + if d.HasChange("mtu") { + req.Mtu = aws.Int64(int64(d.Get("mtu").(int))) + requestUpdate = true + } + + if requestUpdate { + log.Printf("[DEBUG] Modifying Direct Connect virtual interface attributes: %#v", req) + _, err := conn.UpdateVirtualInterfaceAttributes(req) + if err != nil { + return fmt.Errorf("Error modifying Direct Connect virtual interface (%s) attributes, error: %s", d.Id(), err) + } + } + if err := setTagsDX(conn, d, d.Get("arn").(string)); err != nil { return err } @@ -97,17 +115,17 @@ func dxVirtualInterfaceStateRefresh(conn *directconnect.DirectConnect, vifId str } } -func dxVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect, pending, target []string) error { +func dxVirtualInterfaceWaitUntilAvailable(conn *directconnect.DirectConnect, vifId string, timeout time.Duration, pending, target []string) error { stateConf := &resource.StateChangeConf{ Pending: pending, Target: target, - Refresh: dxVirtualInterfaceStateRefresh(conn, d.Id()), - Timeout: d.Timeout(schema.TimeoutCreate), + Refresh: dxVirtualInterfaceStateRefresh(conn, vifId), + Timeout: timeout, Delay: 10 * time.Second, MinTimeout: 5 * time.Second, } if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for Direct Connect virtual interface (%s) to become available: %s", d.Id(), err) + return fmt.Errorf("Error waiting for Direct Connect virtual interface (%s) to become available: %s", vifId, err) } return nil diff --git a/aws/resource_aws_dx_hosted_private_virtual_interface.go b/aws/resource_aws_dx_hosted_private_virtual_interface.go index f0ce581ff78..c03bcccf808 100644 --- a/aws/resource_aws_dx_hosted_private_virtual_interface.go +++ b/aws/resource_aws_dx_hosted_private_virtual_interface.go @@ -125,7 +125,7 @@ func resourceAwsDxHostedPrivateVirtualInterfaceCreate(d *schema.ResourceData, me }.String() d.Set("arn", arn) - if err := dxHostedPrivateVirtualInterfaceWaitUntilAvailable(d, conn); err != nil { + if err := dxHostedPrivateVirtualInterfaceWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return err } @@ -175,10 +175,11 @@ func resourceAwsDxHostedPrivateVirtualInterfaceImport(d *schema.ResourceData, me return []*schema.ResourceData{d}, nil } -func dxHostedPrivateVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect) error { +func dxHostedPrivateVirtualInterfaceWaitUntilAvailable(conn *directconnect.DirectConnect, vifId string, timeout time.Duration) error { return dxVirtualInterfaceWaitUntilAvailable( - d, conn, + vifId, + timeout, []string{ directconnect.VirtualInterfaceStatePending, }, diff --git a/aws/resource_aws_dx_hosted_private_virtual_interface_accepter.go b/aws/resource_aws_dx_hosted_private_virtual_interface_accepter.go index 855061c9eef..87ed7673b19 100644 --- a/aws/resource_aws_dx_hosted_private_virtual_interface_accepter.go +++ b/aws/resource_aws_dx_hosted_private_virtual_interface_accepter.go @@ -90,7 +90,7 @@ func resourceAwsDxHostedPrivateVirtualInterfaceAccepterCreate(d *schema.Resource }.String() d.Set("arn", arn) - if err := dxHostedPrivateVirtualInterfaceAccepterWaitUntilAvailable(d, conn); err != nil { + if err := dxHostedPrivateVirtualInterfaceAccepterWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return err } @@ -153,10 +153,11 @@ func resourceAwsDxHostedPrivateVirtualInterfaceAccepterImport(d *schema.Resource return []*schema.ResourceData{d}, nil } -func dxHostedPrivateVirtualInterfaceAccepterWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect) error { +func dxHostedPrivateVirtualInterfaceAccepterWaitUntilAvailable(conn *directconnect.DirectConnect, vifId string, timeout time.Duration) error { return dxVirtualInterfaceWaitUntilAvailable( - d, conn, + vifId, + timeout, []string{ directconnect.VirtualInterfaceStateConfirming, directconnect.VirtualInterfaceStatePending, diff --git a/aws/resource_aws_dx_hosted_public_virtual_interface.go b/aws/resource_aws_dx_hosted_public_virtual_interface.go index a8cede3c7bb..850acd898f4 100644 --- a/aws/resource_aws_dx_hosted_public_virtual_interface.go +++ b/aws/resource_aws_dx_hosted_public_virtual_interface.go @@ -147,7 +147,7 @@ func resourceAwsDxHostedPublicVirtualInterfaceCreate(d *schema.ResourceData, met }.String() d.Set("arn", arn) - if err := dxHostedPublicVirtualInterfaceWaitUntilAvailable(d, conn); err != nil { + if err := dxHostedPublicVirtualInterfaceWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return err } @@ -198,10 +198,11 @@ func resourceAwsDxHostedPublicVirtualInterfaceImport(d *schema.ResourceData, met return []*schema.ResourceData{d}, nil } -func dxHostedPublicVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect) error { +func dxHostedPublicVirtualInterfaceWaitUntilAvailable(conn *directconnect.DirectConnect, vifId string, timeout time.Duration) error { return dxVirtualInterfaceWaitUntilAvailable( - d, conn, + vifId, + timeout, []string{ directconnect.VirtualInterfaceStatePending, }, diff --git a/aws/resource_aws_dx_hosted_public_virtual_interface_accepter.go b/aws/resource_aws_dx_hosted_public_virtual_interface_accepter.go index 46bafff5917..b3cfc696358 100644 --- a/aws/resource_aws_dx_hosted_public_virtual_interface_accepter.go +++ b/aws/resource_aws_dx_hosted_public_virtual_interface_accepter.go @@ -65,7 +65,7 @@ func resourceAwsDxHostedPublicVirtualInterfaceAccepterCreate(d *schema.ResourceD }.String() d.Set("arn", arn) - if err := dxHostedPublicVirtualInterfaceAccepterWaitUntilAvailable(d, conn); err != nil { + if err := dxHostedPublicVirtualInterfaceAccepterWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return err } @@ -127,10 +127,11 @@ func resourceAwsDxHostedPublicVirtualInterfaceAccepterImport(d *schema.ResourceD return []*schema.ResourceData{d}, nil } -func dxHostedPublicVirtualInterfaceAccepterWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect) error { +func dxHostedPublicVirtualInterfaceAccepterWaitUntilAvailable(conn *directconnect.DirectConnect, vifId string, timeout time.Duration) error { return dxVirtualInterfaceWaitUntilAvailable( - d, conn, + vifId, + timeout, []string{ directconnect.VirtualInterfaceStateConfirming, directconnect.VirtualInterfaceStatePending, diff --git a/aws/resource_aws_dx_private_virtual_interface.go b/aws/resource_aws_dx_private_virtual_interface.go index cd92f28b626..f5851bd9da4 100644 --- a/aws/resource_aws_dx_private_virtual_interface.go +++ b/aws/resource_aws_dx_private_virtual_interface.go @@ -84,11 +84,22 @@ func resourceAwsDxPrivateVirtualInterface() *schema.Resource { Computed: true, ForceNew: true, }, + "mtu": { + Type: schema.TypeInt, + Default: 1500, + Optional: true, + ValidateFunc: validateIntegerInSlice([]int{1500, 9001}), + }, + "jumbo_frame_capable": { + Type: schema.TypeBool, + Computed: true, + }, "tags": tagsSchema(), }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), Delete: schema.DefaultTimeout(10 * time.Minute), }, } @@ -111,6 +122,7 @@ func resourceAwsDxPrivateVirtualInterfaceCreate(d *schema.ResourceData, meta int Vlan: aws.Int64(int64(d.Get("vlan").(int))), Asn: aws.Int64(int64(d.Get("bgp_asn").(int))), AddressFamily: aws.String(d.Get("address_family").(string)), + Mtu: aws.Int64(int64(d.Get("mtu").(int))), }, } if vgwOk && vgwIdRaw.(string) != "" { @@ -145,7 +157,7 @@ func resourceAwsDxPrivateVirtualInterfaceCreate(d *schema.ResourceData, meta int }.String() d.Set("arn", arn) - if err := dxPrivateVirtualInterfaceWaitUntilAvailable(d, conn); err != nil { + if err := dxPrivateVirtualInterfaceWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return err } @@ -175,6 +187,8 @@ func resourceAwsDxPrivateVirtualInterfaceRead(d *schema.ResourceData, meta inter d.Set("amazon_address", vif.AmazonAddress) d.Set("vpn_gateway_id", vif.VirtualGatewayId) d.Set("dx_gateway_id", vif.DirectConnectGatewayId) + d.Set("mtu", vif.Mtu) + d.Set("jumbo_frame_capable", vif.JumboFrameCapable) if err := getTagsDX(conn, d, d.Get("arn").(string)); err != nil { return err } @@ -187,6 +201,10 @@ func resourceAwsDxPrivateVirtualInterfaceUpdate(d *schema.ResourceData, meta int return err } + if err := dxPrivateVirtualInterfaceWaitUntilAvailable(meta.(*AWSClient).dxconn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return err + } + return resourceAwsDxPrivateVirtualInterfaceRead(d, meta) } @@ -207,10 +225,11 @@ func resourceAwsDxPrivateVirtualInterfaceImport(d *schema.ResourceData, meta int return []*schema.ResourceData{d}, nil } -func dxPrivateVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect) error { +func dxPrivateVirtualInterfaceWaitUntilAvailable(conn *directconnect.DirectConnect, vifId string, timeout time.Duration) error { return dxVirtualInterfaceWaitUntilAvailable( - d, conn, + vifId, + timeout, []string{ directconnect.VirtualInterfaceStatePending, }, diff --git a/aws/resource_aws_dx_private_virtual_interface_test.go b/aws/resource_aws_dx_private_virtual_interface_test.go index 760d8126b84..17214c4acc7 100644 --- a/aws/resource_aws_dx_private_virtual_interface_test.go +++ b/aws/resource_aws_dx_private_virtual_interface_test.go @@ -79,6 +79,49 @@ func TestAccAwsDxPrivateVirtualInterface_dxGateway(t *testing.T) { }) } +func TestAccAwsDxPrivateVirtualInterface_mtuUpdate(t *testing.T) { + key := "DX_CONNECTION_ID" + connectionId := os.Getenv(key) + if connectionId == "" { + t.Skipf("Environment variable %s is not set", key) + } + vifName := fmt.Sprintf("terraform-testacc-dxvif-%s", acctest.RandString(5)) + bgpAsn := randIntRange(64512, 65534) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsDxPrivateVirtualInterfaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDxPrivateVirtualInterfaceConfig_noTags(connectionId, vifName, bgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxPrivateVirtualInterfaceExists("aws_dx_private_virtual_interface.foo"), + resource.TestCheckResourceAttr("aws_dx_private_virtual_interface.foo", "name", vifName), + resource.TestCheckResourceAttr("aws_dx_private_virtual_interface.foo", "mtu", "1500"), + resource.TestCheckResourceAttr("aws_dx_private_virtual_interface.foo", "jumbo_frame_capable", "true"), + ), + }, + { + Config: testAccDxPrivateVirtualInterfaceConfig_jumboFrames(connectionId, vifName, bgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxPrivateVirtualInterfaceExists("aws_dx_private_virtual_interface.foo"), + resource.TestCheckResourceAttr("aws_dx_private_virtual_interface.foo", "name", vifName), + resource.TestCheckResourceAttr("aws_dx_private_virtual_interface.foo", "mtu", "9001"), + ), + }, + { + Config: testAccDxPrivateVirtualInterfaceConfig_noTags(connectionId, vifName, bgpAsn), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsDxPrivateVirtualInterfaceExists("aws_dx_private_virtual_interface.foo"), + resource.TestCheckResourceAttr("aws_dx_private_virtual_interface.foo", "name", vifName), + resource.TestCheckResourceAttr("aws_dx_private_virtual_interface.foo", "mtu", "1500"), + ), + }, + }, + }) +} + func testAccCheckAwsDxPrivateVirtualInterfaceDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).dxconn @@ -177,3 +220,24 @@ resource "aws_dx_private_virtual_interface" "foo" { } `, n, amzAsn, cid, n, bgpAsn) } + +func testAccDxPrivateVirtualInterfaceConfig_jumboFrames(cid, n string, bgpAsn int) string { + return fmt.Sprintf(` +resource "aws_vpn_gateway" "foo" { + tags { + Name = "%s" + } +} + +resource "aws_dx_private_virtual_interface" "foo" { + connection_id = "%s" + + vpn_gateway_id = "${aws_vpn_gateway.foo.id}" + name = "%s" + vlan = 4094 + address_family = "ipv4" + bgp_asn = %d + mtu = 9001 +} +`, n, cid, n, bgpAsn) +} diff --git a/aws/resource_aws_dx_public_virtual_interface.go b/aws/resource_aws_dx_public_virtual_interface.go index bc072fe6d0d..26d3cb3a1a3 100644 --- a/aws/resource_aws_dx_public_virtual_interface.go +++ b/aws/resource_aws_dx_public_virtual_interface.go @@ -131,7 +131,7 @@ func resourceAwsDxPublicVirtualInterfaceCreate(d *schema.ResourceData, meta inte }.String() d.Set("arn", arn) - if err := dxPublicVirtualInterfaceWaitUntilAvailable(d, conn); err != nil { + if err := dxPublicVirtualInterfaceWaitUntilAvailable(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return err } @@ -208,10 +208,11 @@ func resourceAwsDxPublicVirtualInterfaceCustomizeDiff(diff *schema.ResourceDiff, return nil } -func dxPublicVirtualInterfaceWaitUntilAvailable(d *schema.ResourceData, conn *directconnect.DirectConnect) error { +func dxPublicVirtualInterfaceWaitUntilAvailable(conn *directconnect.DirectConnect, vifId string, timeout time.Duration) error { return dxVirtualInterfaceWaitUntilAvailable( - d, conn, + vifId, + timeout, []string{ directconnect.VirtualInterfaceStatePending, }, diff --git a/aws/validators.go b/aws/validators.go index 0518fc26259..3d800ee44a4 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -359,6 +359,25 @@ func validateCloudWatchLogResourcePolicyDocument(v interface{}, k string) (ws [] return } +func validateIntegerInSlice(valid []int) schema.SchemaValidateFunc { + return func(i interface{}, k string) (s []string, es []error) { + v, ok := i.(int) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be int", k)) + return + } + + for _, in := range valid { + if v == in { + return + } + } + + es = append(es, fmt.Errorf("expected %s to be one of %v, got %d", k, valid, v)) + return + } +} + func validateCloudWatchEventTargetId(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if len(value) > 64 { diff --git a/aws/validators_test.go b/aws/validators_test.go index d449cdadbe9..93e2d59b09f 100644 --- a/aws/validators_test.go +++ b/aws/validators_test.go @@ -656,6 +656,56 @@ func TestValidateS3BucketLifecycleTimestamp(t *testing.T) { } } +func TestValidateIntegerInSlice(t *testing.T) { + cases := []struct { + val interface{} + f schema.SchemaValidateFunc + expectedErr *regexp.Regexp + }{ + { + val: 42, + f: validateIntegerInSlice([]int{2, 4, 42, 420}), + }, + { + val: 42, + f: validateIntegerInSlice([]int{0, 43}), + expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[0 43\\], got 42"), + }, + { + val: "42", + f: validateIntegerInSlice([]int{0, 42}), + expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"), + }, + } + + matchErr := func(errs []error, r *regexp.Regexp) bool { + // err must match one provided + for _, err := range errs { + if r.MatchString(err.Error()) { + return true + } + } + + return false + } + + for i, tc := range cases { + _, errs := tc.f(tc.val, "test_property") + + if len(errs) == 0 && tc.expectedErr == nil { + continue + } + + if len(errs) != 0 && tc.expectedErr == nil { + t.Fatalf("expected test case %d to produce no errors, got %v", i, errs) + } + + if !matchErr(errs, tc.expectedErr) { + t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs) + } + } +} + func TestResourceAWSElastiCacheClusterIdValidation(t *testing.T) { cases := []struct { Value string diff --git a/website/docs/r/dx_private_virtual_interface.html.markdown b/website/docs/r/dx_private_virtual_interface.html.markdown index 2503eb17fa0..10ffd4219f2 100644 --- a/website/docs/r/dx_private_virtual_interface.html.markdown +++ b/website/docs/r/dx_private_virtual_interface.html.markdown @@ -33,6 +33,8 @@ The following arguments are supported: * `name` - (Required) The name for the virtual interface. * `vlan` - (Required) The VLAN ID. * `amazon_address` - (Optional) The IPv4 CIDR address to use to send traffic to Amazon. Required for IPv4 BGP peers. +* `mtu` - (Optional) The maximum transmission unit (MTU) is the size, in bytes, of the largest permissible packet that can be passed over the connection. +The MTU of a virtual private interface can be either `1500` or `9001` (jumbo frames). Default is `1500`. * `bgp_auth_key` - (Optional) The authentication key for BGP configuration. * `customer_address` - (Optional) The IPv4 CIDR destination address to which Amazon should send traffic. Required for IPv4 BGP peers. * `dx_gateway_id` - (Optional) The ID of the Direct Connect gateway to which to connect the virtual interface. @@ -45,6 +47,7 @@ In addition to all arguments above, the following attributes are exported: * `id` - The ID of the virtual interface. * `arn` - The ARN of the virtual interface. +* `jumbo_frame_capable` - Indicates whether jumbo frames (9001 MTU) are supported. ## Timeouts @@ -52,6 +55,7 @@ In addition to all arguments above, the following attributes are exported: [Timeouts](/docs/configuration/resources.html#timeouts) configuration options: - `create` - (Default `10 minutes`) Used for creating virtual interface +- `update` - (Default `10 minutes`) Used for virtual interface modifications - `delete` - (Default `10 minutes`) Used for destroying virtual interface ## Import