diff --git a/docs/resources/softlayer_subnet.md b/docs/resources/softlayer_subnet.md new file mode 100644 index 000000000..0afd2ec2c --- /dev/null +++ b/docs/resources/softlayer_subnet.md @@ -0,0 +1,110 @@ +#### `softlayer_subnet` + +`softlayer_subnet` provides portable and static subnets that consist of either IPv4 and IPv6 addresses. Users are able to create +public portable subnets, private portable subnets, and public static subnets with an IPv4 option and public portable subnets and public static +subnets with an IPv6 option. + +The portable IPv4 subnet is created as a seconday subnet on a VLAN. IP addresses in the portable subnet can be assigned as secondary IP + addresses for SoftLayer resources in the VLAN. Each portable subnet has a default gateway IP address, network IP address, and broadcast + IP address. For example, if a portable subnet is `10.0.0.0/30`, `10.0.0.0` is a network IP address, `10.0.0.1` is a default gateway IP address, + and `10.0.0.3` is a broadcast IP address. Therefore, only `10.0.0.2` can be assigned to SoftLayer resources as a secondary IP address. + Number of usuable IP addresses is `capacity` - 3. If `capacity` is 4, the number of usuable IP addresses is 4 - 3 = 1. If `capacity` is 8, the + number of usuable IP addresses is 8 - 3 = 5. For additional details, refer to [Static and Portable IP blocks](https://knowledgelayer.softlayer.com/articles/static-and-portable-ip-blocks). + +The static IPv4 subnet provides secondary IP addresses for primary IP addresses. It provides secondary IP addresses for SoftLayer resources such as +virtual servers, bare metal servers, and netscaler VPXs. Suppose that a virtual server requires secondary IP addresses. Then, users can create +a static subnet on the public IP address of the virtual server. Unlike the portable subnet, `capacity` is same with a number of usuable IP address. +For example, if a static subnet is `10.0.0.0/30`, `capacity` is 4 and four IP addresses(10.0.0.0 ~ 10.0.0.3) can be used as secondary IP addresses. +For additional details, refer to [Subnet](https://knowledgelayer.softlayer.com/topic/subnets). + +Both the public portable IPv6 subnet and the public static IP only accept `64` as a value of `capacity` attribute. They provide 2^64 IP addresses. For additional detail, refer to [IPv6 address](http://blog.softlayer.com/tag/ipv6) + +The following example will create a private portable subnet which has one available IPv4 address. +##### Example Usage of portable subnet + +```hcl +# Create a new portable subnet +resource "softlayer_subnet" "portable_subnet" { + type = "Portable" + private = true + ip_version = 4 + capacity = 4 + vlan_id = 1234567 + notes = "portable_subnet" +} +``` + +The following example will create a public static subnet which has four available IPv4 address. +##### Example Usage of static subnet + +```hcl +# Create a new static subnet +resource "softlayer_subnet" "static_subnet" { + type = "Static" + private = false + ip_version = 4 + capacity = 4 + endpoint_ip="151.1.1.1" + notes = "static_subnet_updated" +} +``` + +Sometimes, users need to get IP addresses on a subnet. Terraform built-in functions can be used to get IP addresses from `subnet`. +The following example returns first IP address in the subnet `test`: +```hcl +resource "softlayer_subnet" "test" { + type = "Static" + private = false + ip_version = 4 + capacity = 4 + endpoint_ip="159.8.181.82" +} + +# Use a built-in function cidrhost with index 0. +output "first_ip_address" { + value = "${cidrhost(softlayer_subnet.test.subnet,0)}" +} +``` + +##### Argument Reference + +The following arguments are supported: + +* `private` | *boolean* + * Set the network property of the subnet if it is public or private. + * **Required** +* `type` | *string* + * Set the type of the subnet. Accepted values are Portable and Static. + * **Required** +* `ip_version` | *int* + * Set the IP version of the subnet. Accepted values are 4 and 6. + * *Default*: true + * **Optional** +* `capacity` | *int* + * Set the size of the subnet. + * Accepted values for a public portable IPv4 subnet are 4, 8, 16, and 32. + * Accepted values for a private portable IPv4 subnet are 4, 8, 16, 32, and 64. + * Accepted values for a public static IPv4 subnet are 1, 2, 4, 8, 16, and 32. + * Accepted value for a public portable IPv6 subnet is 64. /64 block is created and 2^64 IP addresses are provided. + * Accepted value for a public static IPv6 subnet is 64. /64 block is created and 2^64 IP addresses are provided. + * **Required** +* `vlan_id` | *int* + * VLAN id for portable subnet. It should be configured when the subnet is a portable subnet. Both public VLAN ID and private VLAN ID can + be configured. Accepted values can be found [here](https://control.softlayer.com/network/vlans). Click on the desired VLAN and note the + ID on the resulting URL. Or, you can also [refer to a VLAN by name using a data source](https://github.com/softlayer/terraform-provider-softlayer/blob/master/docs/datasources/softlayer_vlan.md). + * **Optional** +* `endpoint_ip` | *string* + * Target primary IP address for static subnet. It should be configured when the subnet is a static subnet. Only public IP address can be + configured as a `endpoint_ip`. It can be public IP address of virtual servers, bare metal servers, and netscaler VPXs. `static subnet` will + be created on VLAN where `endpoint_ip` is located in. + * **Optional** +* `notes` | *string* + * Set comments for the subnet. + * **Optional** + +##### Attributes Reference + +The following attributes are exported: + +* `id` - id of the subnet. +* `subnet_cidr` - It rovides IP address/cidr format (ex. 10.10.10.10/28). It can be used to get an available IP address in `subnet`. \ No newline at end of file diff --git a/softlayer/provider.go b/softlayer/provider.go index c331e467a..760f00ca1 100644 --- a/softlayer/provider.go +++ b/softlayer/provider.go @@ -79,6 +79,7 @@ func Provider() terraform.ResourceProvider { "softlayer_file_storage": resourceSoftLayerFileStorage(), "softlayer_block_storage": resourceSoftLayerBlockStorage(), "softlayer_dns_secondary": resourceSoftLayerDnsSecondary(), + "softlayer_subnet": resourceSoftLayerSubnet(), }, ConfigureFunc: providerConfigure, diff --git a/softlayer/resource_softlayer_subnet.go b/softlayer/resource_softlayer_subnet.go new file mode 100644 index 000000000..d24e6e9bb --- /dev/null +++ b/softlayer/resource_softlayer_subnet.go @@ -0,0 +1,390 @@ +package softlayer + +import ( + "errors" + "fmt" + "log" + "strconv" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/softlayer/softlayer-go/datatypes" + "github.com/softlayer/softlayer-go/filter" + "github.com/softlayer/softlayer-go/helpers/product" + "github.com/softlayer/softlayer-go/services" + "github.com/softlayer/softlayer-go/session" + "github.com/softlayer/softlayer-go/sl" +) + +const ( + SubnetMask = "id,addressSpace,subnetType,version,ipAddressCount," + + "networkIdentifier,cidr,note,endPointIpAddress[ipAddress],networkVlan[id],totalIpAddresses" +) + +var ( + // Map subnet types to product package keyname in SoftLayer_Product_Item + subnetPackageTypeMap = map[string]string{ + "Static": "ADDITIONAL_SERVICES_STATIC_IP_ADDRESSES", + "Portable": "ADDITIONAL_SERVICES_PORTABLE_IP_ADDRESSES", + } +) + +func resourceSoftLayerSubnet() *schema.Resource { + return &schema.Resource{ + Create: resourceSoftLayerSubnetCreate, + Read: resourceSoftLayerSubnetRead, + Update: resourceSoftLayerSubnetUpdate, + Delete: resourceSoftLayerSubnetDelete, + Exists: resourceSoftLayerSubnetExists, + Importer: &schema.ResourceImporter{}, + + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeInt, + Computed: true, + }, + + "private": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errs []error) { + typeStr := v.(string) + if typeStr != "Portable" && typeStr != "Static" { + errs = append(errs, errors.New( + "type should be either Portable or Static.")) + } + return + }, + }, + + // IP version 4 or IP version 6 + "ip_version": { + Type: schema.TypeInt, + Optional: true, + Default: 4, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errs []error) { + ipVersion := v.(int) + if ipVersion != 4 && ipVersion != 6 { + errs = append(errs, errors.New( + "ip version should be either 4 or 6.")) + } + return + }, + }, + + "capacity": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + // vlan_id should be configured when type is "Portable" + "vlan_id": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"endpoint_ip"}, + }, + + // endpoint_ip should be configured when type is "Static" + "endpoint_ip": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"vlan_id"}, + }, + + // Provides IP address/cidr format (ex. 10.10.10.10/28) + "subnet_cidr": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "notes": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceSoftLayerSubnetCreate(d *schema.ResourceData, meta interface{}) error { + sess := meta.(ProviderConfig).SoftLayerSession() + + // Find price items with AdditionalServicesSubnetAddresses + productOrderContainer, err := buildSubnetProductOrderContainer(d, sess) + if err != nil { + return fmt.Errorf("Error creating subnet: %s", err) + } + + log.Println("[INFO] Creating subnet") + + receipt, err := services.GetProductOrderService(sess). + PlaceOrder(productOrderContainer, sl.Bool(false)) + if err != nil { + return fmt.Errorf("Error during creation of subnet: %s", err) + } + + Subnet, err := findSubnetByOrderId(sess, *receipt.OrderId) + if err != nil { + return fmt.Errorf("Error during creation of subnet: %s", err) + } + + d.SetId(fmt.Sprintf("%d", *Subnet.Id)) + + return resourceSoftLayerSubnetUpdate(d, meta) +} + +func resourceSoftLayerSubnetRead(d *schema.ResourceData, meta interface{}) error { + sess := meta.(ProviderConfig).SoftLayerSession() + service := services.GetNetworkSubnetService(sess) + + subnetId, err := strconv.Atoi(d.Id()) + if err != nil { + return fmt.Errorf("Not a valid subnet ID, must be an integer: %s", err) + } + + subnet, err := service.Id(subnetId).Mask(SubnetMask).GetObject() + if err != nil { + return fmt.Errorf("Error retrieving a subnet: %s", err) + } + + if *subnet.AddressSpace == "PRIVATE" { + d.Set("private", true) + } else if *subnet.AddressSpace == "PUBLIC" { + d.Set("private", false) + } + + if subnet.SubnetType == nil { + return fmt.Errorf("Invalid vlan type: the subnet type is null.") + } + if strings.Contains(*subnet.SubnetType, "STATIC") { + d.Set("type", "Static") + } else if strings.Contains(*subnet.SubnetType, "VLAN") { + d.Set("type", "Portable") + } else { + return fmt.Errorf("Invalid vlan type: %s", *subnet.SubnetType) + } + d.Set("ip_version", *subnet.Version) + d.Set("capacity", *subnet.TotalIpAddresses) + if *subnet.Version == 6 { + d.Set("capacity", 64) + } + d.Set("subnet_cidr", *subnet.NetworkIdentifier+"/"+strconv.Itoa(*subnet.Cidr)) + if subnet.Note != nil { + d.Set("notes", *subnet.Note) + } + if subnet.EndPointIpAddress != nil { + d.Set("endpoint_ip", *subnet.EndPointIpAddress.IpAddress) + } + d.Set("notes", sl.Get(subnet.Note, nil)) + + return nil +} + +func resourceSoftLayerSubnetUpdate(d *schema.ResourceData, meta interface{}) error { + sess := meta.(ProviderConfig).SoftLayerSession() + service := services.GetNetworkSubnetService(sess) + + subnetId, err := strconv.Atoi(d.Id()) + if err != nil { + return fmt.Errorf("Not a valid subnet ID, must be an integer: %s", err) + } + + if d.HasChange("notes") { + _, err = service.Id(subnetId).EditNote(sl.String(d.Get("notes").(string))) + if err != nil { + return fmt.Errorf("Error updating subnet: %s", err) + } + } + return resourceSoftLayerSubnetRead(d, meta) +} + +func resourceSoftLayerSubnetDelete(d *schema.ResourceData, meta interface{}) error { + sess := meta.(ProviderConfig).SoftLayerSession() + service := services.GetNetworkSubnetService(sess) + + subnetId, err := strconv.Atoi(d.Id()) + if err != nil { + return fmt.Errorf("Not a valid subnet ID, must be an integer: %s", err) + } + + billingItem, err := service.Id(subnetId).GetBillingItem() + if err != nil { + return fmt.Errorf("Error deleting subnet: %s", err) + } + + if billingItem.Id == nil { + return nil + } + + _, err = services.GetBillingItemService(sess).Id(*billingItem.Id).CancelService() + + return err +} + +func resourceSoftLayerSubnetExists(d *schema.ResourceData, meta interface{}) (bool, error) { + sess := meta.(ProviderConfig).SoftLayerSession() + service := services.GetNetworkSubnetService(sess) + + subnetId, err := strconv.Atoi(d.Id()) + if err != nil { + return false, fmt.Errorf("Not a valid ID, must be an integer: %s", err) + } + + result, err := service.Id(subnetId).GetObject() + if err != nil { + if apiErr, ok := err.(sl.Error); ok && apiErr.StatusCode == 404 { + return false, nil + } + return false, fmt.Errorf("Error retrieving subnet: %s", err) + } + return result.Id != nil && *result.Id == subnetId, nil +} + +func findSubnetByOrderId(sess *session.Session, orderId int) (datatypes.Network_Subnet, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{"pending"}, + Target: []string{"complete"}, + Refresh: func() (interface{}, string, error) { + subnets, err := services.GetAccountService(sess). + Filter(filter.Path("subnets.billingItem.orderItem.order.id"). + Eq(strconv.Itoa(orderId)).Build()). + Mask("id,activeTransaction"). + GetSubnets() + if err != nil { + return datatypes.Network_Subnet{}, "", err + } + + if len(subnets) == 1 && subnets[0].ActiveTransaction == nil { + return subnets[0], "complete", nil + } + return nil, "pending", nil + }, + Timeout: 30 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + NotFoundChecks: 1440, + } + + pendingResult, err := stateConf.WaitForState() + + if err != nil { + return datatypes.Network_Subnet{}, err + } + + if result, ok := pendingResult.(datatypes.Network_Subnet); ok { + return result, nil + } + + return datatypes.Network_Subnet{}, + fmt.Errorf("Cannot find a subnet with order id '%d'", orderId) +} + +func buildSubnetProductOrderContainer(d *schema.ResourceData, sess *session.Session) ( + *datatypes.Container_Product_Order_Network_Subnet, error) { + + // 1. Get a package + typeStr := d.Get("type").(string) + vlanId := d.Get("vlan_id").(int) + private := d.Get("private").(bool) + network := "PUBLIC" + if private { + network = "PRIVATE" + } + + pkg, err := product.GetPackageByType(sess, subnetPackageTypeMap[typeStr]) + if err != nil { + return &datatypes.Container_Product_Order_Network_Subnet{}, err + } + + // 2. Get all prices for the package + productItems, err := product.GetPackageProducts(sess, *pkg.Id) + if err != nil { + return &datatypes.Container_Product_Order_Network_Subnet{}, err + } + + // 3. Select items which have a matching capacity, network, and IP version. + capacity := d.Get("capacity").(int) + ipVersionStr := "_IP_" + if d.Get("ip_version").(int) == 6 { + ipVersionStr = "_IPV6_" + } + SubnetItems := []datatypes.Product_Item{} + for _, item := range productItems { + if int(*item.Capacity) == d.Get("capacity").(int) && + strings.Contains(*item.KeyName, network) && + strings.Contains(*item.KeyName, ipVersionStr) { + SubnetItems = append(SubnetItems, item) + } + } + + if len(SubnetItems) == 0 { + return &datatypes.Container_Product_Order_Network_Subnet{}, + fmt.Errorf("No product items matching with capacity %d could be found", capacity) + } + + productOrderContainer := datatypes.Container_Product_Order_Network_Subnet{ + Container_Product_Order: datatypes.Container_Product_Order{ + PackageId: pkg.Id, + Prices: []datatypes.Product_Item_Price{ + { + Id: SubnetItems[0].Prices[0].Id, + }, + }, + Quantity: sl.Int(1), + }, + EndPointVlanId: sl.Int(vlanId), + } + + if endpointIp, ok := d.GetOk("endpoint_ip"); ok { + if typeStr != "Static" { + return &datatypes.Container_Product_Order_Network_Subnet{}, + fmt.Errorf("endpoint_ip is only available when type is Static.") + } + endpointIpStr := endpointIp.(string) + subnet, err := services.GetNetworkSubnetService(sess).Mask("ipAddresses").GetSubnetForIpAddress(sl.String(endpointIpStr)) + if err != nil { + return &datatypes.Container_Product_Order_Network_Subnet{}, err + } + for _, ipSubnet := range subnet.IpAddresses { + if *ipSubnet.IpAddress == endpointIpStr { + productOrderContainer.EndPointIpAddressId = ipSubnet.Id + } + } + if productOrderContainer.EndPointIpAddressId == nil { + return &datatypes.Container_Product_Order_Network_Subnet{}, + fmt.Errorf("Unable to find an ID of ipAddress: %s", endpointIpStr) + } + } + return &productOrderContainer, nil +} + +func getVlanType(sess *session.Session, vlanId int) (string, error) { + vlan, err := services.GetNetworkVlanService(sess).Id(vlanId).Mask(VlanMask).GetObject() + + if err != nil { + return "", fmt.Errorf("Error retrieving vlan: %s", err) + } + + if vlan.PrimaryRouter != nil { + if strings.HasPrefix(*vlan.PrimaryRouter.Hostname, "fcr") { + return "PUBLIC", nil + } else { + return "PRIVATE", nil + } + } + return "", fmt.Errorf("Unable to determine network.") +} diff --git a/softlayer/resource_softlayer_subnet_test.go b/softlayer/resource_softlayer_subnet_test.go new file mode 100644 index 000000000..01d93fbb7 --- /dev/null +++ b/softlayer/resource_softlayer_subnet_test.go @@ -0,0 +1,196 @@ +package softlayer + +import ( + "github.com/hashicorp/terraform/helper/resource" + "regexp" + "testing" +) + +func TestAccSoftLayerSubnet_Basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckSoftLayerSubnetConfig_basic, + Check: resource.ComposeTestCheckFunc( + // Check portable IPv4 + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet", "type", "Portable"), + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet", "private", "true"), + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet", "ip_version", "4"), + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet", "capacity", "4"), + testAccCheckSoftLayerResources("softlayer_subnet.portable_subnet", "vlan_id", + "softlayer_virtual_guest.subnetvm1", "private_vlan_id"), + resource.TestMatchResourceAttr("softlayer_subnet.portable_subnet", "subnet_cidr", + regexp.MustCompile(`^(([01]?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))\.){3}([01]?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))`)), + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet", "notes", "portable_subnet"), + + // Check static IPv4 + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet", "type", "Static"), + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet", "private", "false"), + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet", "ip_version", "4"), + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet", "capacity", "4"), + testAccCheckSoftLayerResources("softlayer_subnet.static_subnet", "endpoint_ip", + "softlayer_virtual_guest.subnetvm1", "ipv4_address"), + resource.TestMatchResourceAttr("softlayer_subnet.static_subnet", "subnet_cidr", + regexp.MustCompile(`^(([01]?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))\.){3}([01]?[0-9]?[0-9]|2([0-4][0-9]|5[0-5]))`)), + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet", "notes", "static_subnet"), + + // Check portable IPv6 + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet_v6", "type", "Portable"), + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet_v6", "private", "false"), + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet_v6", "ip_version", "6"), + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet_v6", "capacity", "64"), + testAccCheckSoftLayerResources("softlayer_subnet.portable_subnet_v6", "vlan_id", + "softlayer_virtual_guest.subnetvm1", "public_vlan_id"), + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet_v6", "notes", "portable_subnet"), + // Check static IPv6 + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet_v6", "type", "Static"), + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet_v6", "private", "false"), + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet_v6", "ip_version", "6"), + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet_v6", "capacity", "64"), + testAccCheckSoftLayerResources("softlayer_subnet.static_subnet_v6", "endpoint_ip", + "softlayer_virtual_guest.subnetvm1", "ipv6_address"), + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet_v6", "notes", "static_subnet"), + ), + }, + + resource.TestStep{ + Config: testAccCheckSoftLayerSubnetConfig_notes_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "softlayer_subnet.portable_subnet", "notes", "portable_subnet_updated"), + resource.TestCheckResourceAttr( + "softlayer_subnet.static_subnet", "notes", "static_subnet_updated"), + ), + }, + }, + }) +} + +const testAccCheckSoftLayerSubnetConfig_basic = ` +resource "softlayer_virtual_guest" "subnetvm1" { + hostname = "subnetvm1" + domain = "example.com" + os_reference_code = "DEBIAN_7_64" + datacenter = "wdc04" + network_speed = 100 + hourly_billing = true + private_network_only = false + cores = 1 + memory = 1024 + disks = [25] + local_disk = false + ipv6_enabled = true +} + +resource "softlayer_subnet" "portable_subnet" { + type = "Portable" + private = true + ip_version = 4 + capacity = 4 + vlan_id = "${softlayer_virtual_guest.subnetvm1.private_vlan_id}" + notes = "portable_subnet" +} + +resource "softlayer_subnet" "static_subnet" { + type = "Static" + private = false + ip_version = 4 + capacity = 4 + endpoint_ip="${softlayer_virtual_guest.subnetvm1.ipv4_address}" + notes = "static_subnet" +} + +resource "softlayer_subnet" "portable_subnet_v6" { + type = "Portable" + private = false + ip_version = 6 + capacity = 64 + vlan_id = "${softlayer_virtual_guest.subnetvm1.public_vlan_id}" + notes = "portable_subnet" +} + +resource "softlayer_subnet" "static_subnet_v6" { + type = "Static" + private = false + ip_version = 6 + capacity = 64 + endpoint_ip="${softlayer_virtual_guest.subnetvm1.ipv6_address}" + notes = "static_subnet" +} +` + +const testAccCheckSoftLayerSubnetConfig_notes_update = ` +resource "softlayer_virtual_guest" "subnetvm1" { + hostname = "subnetvm1" + domain = "example.com" + os_reference_code = "DEBIAN_7_64" + datacenter = "wdc04" + network_speed = 100 + hourly_billing = true + private_network_only = false + cores = 1 + memory = 1024 + disks = [25] + local_disk = false + ipv6_enabled = true +} + +resource "softlayer_subnet" "portable_subnet" { + type = "Portable" + private = true + ip_version = 4 + capacity = 4 + vlan_id = "${softlayer_virtual_guest.subnetvm1.private_vlan_id}" + notes = "portable_subnet_updated" +} + +resource "softlayer_subnet" "static_subnet" { + type = "Static" + private = false + ip_version = 4 + capacity = 4 + endpoint_ip="${softlayer_virtual_guest.subnetvm1.ipv4_address}" + notes = "static_subnet_updated" +} + +resource "softlayer_subnet" "portable_subnet_v6" { + type = "Portable" + private = false + ip_version = 6 + capacity = 64 + vlan_id = "${softlayer_virtual_guest.subnetvm1.public_vlan_id}" + notes = "portable_subnet" +} + +resource "softlayer_subnet" "static_subnet_v6" { + type = "Static" + private = false + ip_version = 6 + capacity = 64 + endpoint_ip="${softlayer_virtual_guest.subnetvm1.ipv6_address}" + notes = "static_subnet" +} +`