From b02ed0f3f70cceef7620997218235217657f7b86 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 19 Apr 2016 09:42:29 -0500 Subject: [PATCH 1/2] triton: add nics --- builtin/providers/triton/resource_machine.go | 138 ++++++++++++-- .../providers/triton/resource_machine_test.go | 172 +++++++++++++++++- 2 files changed, 297 insertions(+), 13 deletions(-) diff --git a/builtin/providers/triton/resource_machine.go b/builtin/providers/triton/resource_machine.go index a16c9a964..a744cbfc1 100644 --- a/builtin/providers/triton/resource_machine.go +++ b/builtin/providers/triton/resource_machine.go @@ -6,6 +6,7 @@ import ( "regexp" "time" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/joyent/gosdc/cloudapi" ) @@ -108,17 +109,53 @@ func resourceMachine() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "networks": { - Description: "desired network IDs", - Type: schema.TypeList, - Optional: true, + "nic": { + Description: "network interface", + Type: schema.TypeSet, Computed: true, - // TODO: this really should ForceNew but the Network IDs don't seem to - // be returned by the API, meaning if we track them here TF will replace - // the resource on every run. - // ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, + Optional: true, + Set: func(v interface{}) int { + m := v.(map[string]interface{}) + return hashcode.String(m["network"].(string)) + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip": { + Description: "NIC's IPv4 address", + Computed: true, + Type: schema.TypeString, + }, + "mac": { + Description: "NIC's MAC address", + Computed: true, + Type: schema.TypeString, + }, + "primary": { + Description: "Whether this is the machine's primary NIC", + Computed: true, + Type: schema.TypeBool, + }, + "netmask": { + Description: "IPv4 netmask", + Computed: true, + Type: schema.TypeString, + }, + "gateway": { + Description: "IPv4 gateway", + Computed: true, + Type: schema.TypeString, + }, + "state": { + Description: "describes the state of the NIC (e.g. provisioning, running, or stopped)", + Computed: true, + Type: schema.TypeString, + }, + "network": { + Description: "Network ID this NIC is attached to", + Required: true, + Type: schema.TypeString, + }, + }, }, }, "firewall_enabled": { @@ -153,6 +190,18 @@ func resourceMachine() *schema.Resource { Optional: true, Computed: true, }, + + // deprecated fields + "networks": { + Description: "desired network IDs", + Type: schema.TypeList, + Optional: true, + Computed: true, + Deprecated: "Networks is deprecated, please use `nic`", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, }, } } @@ -164,6 +213,11 @@ func resourceMachineCreate(d *schema.ResourceData, meta interface{}) error { for _, network := range d.Get("networks").([]interface{}) { networks = append(networks, network.(string)) } + nics := d.Get("nic").(*schema.Set) + for _, nicI := range nics.List() { + nic := nicI.(map[string]interface{}) + networks = append(networks, nic["network"].(string)) + } metadata := map[string]string{} for schemaName, metadataKey := range resourceMachineMetadataKeys { @@ -221,6 +275,11 @@ func resourceMachineRead(d *schema.ResourceData, meta interface{}) error { return err } + nics, err := client.ListNICs(d.Id()) + if err != nil { + return err + } + d.SetId(machine.Id) d.Set("name", machine.Name) d.Set("type", machine.Type) @@ -235,9 +294,31 @@ func resourceMachineRead(d *schema.ResourceData, meta interface{}) error { d.Set("package", machine.Package) d.Set("image", machine.Image) d.Set("primaryip", machine.PrimaryIP) - d.Set("networks", machine.Networks) d.Set("firewall_enabled", machine.FirewallEnabled) + // create and update NICs + var ( + machineNICs []map[string]interface{} + networks []string + ) + for _, nic := range nics { + machineNICs = append( + machineNICs, + map[string]interface{}{ + "ip": nic.IP, + "mac": nic.MAC, + "primary": nic.Primary, + "netmask": nic.Netmask, + "gateway": nic.Gateway, + "state": nic.State, + "network": nic.Network, + }, + ) + networks = append(networks, nic.Network) + } + d.Set("nic", machineNICs) + d.Set("networks", networks) + // computed attributes from metadata for schemaName, metadataKey := range resourceMachineMetadataKeys { d.Set(schemaName, machine.Metadata[metadataKey]) @@ -336,6 +417,41 @@ func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error { d.SetPartial("firewall_enabled") } + if d.HasChange("nic") { + o, n := d.GetChange("nic") + if o == nil { + o = new(schema.Set) + } + if n == nil { + n = new(schema.Set) + } + + oldNICs := o.(*schema.Set) + newNICs := o.(*schema.Set) + + // add new NICs that are not in old NICs + for _, nicI := range newNICs.Difference(oldNICs).List() { + nic := nicI.(map[string]interface{}) + fmt.Printf("adding %+v\n", nic) + _, err := client.AddNIC(d.Id(), nic["network"].(string)) + if err != nil { + return err + } + } + + // remove old NICs that are not in new NICs + for _, nicI := range oldNICs.Difference(newNICs).List() { + nic := nicI.(map[string]interface{}) + fmt.Printf("removing %+v\n", nic) + err := client.RemoveNIC(d.Id(), nic["mac"].(string)) + if err != nil { + return err + } + } + + d.SetPartial("nic") + } + // metadata stuff metadata := map[string]string{} for schemaName, metadataKey := range resourceMachineMetadataKeys { diff --git a/builtin/providers/triton/resource_machine_test.go b/builtin/providers/triton/resource_machine_test.go index 2fd13afca..1df000d2a 100644 --- a/builtin/providers/triton/resource_machine_test.go +++ b/builtin/providers/triton/resource_machine_test.go @@ -34,6 +34,62 @@ func TestAccTritonMachine_basic(t *testing.T) { }) } +func TestAccTritonMachine_nic(t *testing.T) { + machineName := fmt.Sprintf("acctest-%d", acctest.RandInt()) + config := fmt.Sprintf(testAccTritonMachine_withnic, machineName, machineName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckTritonMachineDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckTritonMachineExists("triton_machine.test"), + func(*terraform.State) error { + time.Sleep(10 * time.Second) + return nil + }, + testCheckTritonMachineHasFabric("triton_machine.test", "triton_fabric.test"), + ), + }, + }, + }) +} + +func TestAccTritonMachine_addnic(t *testing.T) { + machineName := fmt.Sprintf("acctest-%d", acctest.RandInt()) + without := fmt.Sprintf(testAccTritonMachine_withoutnic, machineName, machineName) + with := fmt.Sprintf(testAccTritonMachine_withnic, machineName, machineName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckTritonMachineDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: without, + Check: resource.ComposeTestCheckFunc( + testCheckTritonMachineExists("triton_machine.test"), + func(*terraform.State) error { + time.Sleep(10 * time.Second) + return nil + }, + testCheckTritonMachineHasNoFabric("triton_machine.test", "triton_fabric.test"), + ), + }, + resource.TestStep{ + Config: with, + Check: resource.ComposeTestCheckFunc( + testCheckTritonMachineExists("triton_machine.test"), + testCheckTritonMachineHasFabric("triton_machine.test", "triton_fabric.test"), + ), + }, + }, + }) +} + func testCheckTritonMachineExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -56,6 +112,66 @@ func testCheckTritonMachineExists(name string) resource.TestCheckFunc { } } +func testCheckTritonMachineHasFabric(name, fabricName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + machine, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + network, ok := s.RootModule().Resources[fabricName] + if !ok { + return fmt.Errorf("Not found: %s", fabricName) + } + conn := testAccProvider.Meta().(*cloudapi.Client) + + nics, err := conn.ListNICs(machine.Primary.ID) + if err != nil { + return fmt.Errorf("Bad: Check NICs Exist: %s", err) + } + + fmt.Printf("%+v\n", machine.Primary) + for _, nic := range nics { + fmt.Printf("---\n%+v\n%+v\n", nic, network.Primary) + if nic.Network == network.Primary.ID { + return nil + } + } + + return fmt.Errorf("Bad: Machine %q does not have Fabric %q", machine.Primary.ID, network.Primary.ID) + } +} + +func testCheckTritonMachineHasNoFabric(name, fabricName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + machine, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + network, ok := s.RootModule().Resources[fabricName] + if !ok { + return fmt.Errorf("Not found: %s", fabricName) + } + conn := testAccProvider.Meta().(*cloudapi.Client) + + nics, err := conn.ListNICs(machine.Primary.ID) + if err != nil { + return fmt.Errorf("Bad: Check NICs Exist: %s", err) + } + + for _, nic := range nics { + if nic.Network == network.Primary.ID { + return fmt.Errorf("Bad: Machine %q has Fabric %q", machine.Primary.ID, network.Primary.ID) + } + } + + return nil + } +} + func testCheckTritonMachineDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*cloudapi.Client) @@ -84,7 +200,59 @@ resource "triton_machine" "test" { image = "842e6fa6-6e9b-11e5-8402-1b490459e334" tags = { - test = "hello!" - } + test = "hello!" + } +} +` + +var testAccTritonMachine_withnic = ` +resource "triton_fabric" "test" { + name = "%s-network" + description = "test network" + vlan_id = 2 # every DC seems to have a vlan 2 available + + subnet = "10.0.0.0/22" + gateway = "10.0.0.1" + provision_start_ip = "10.0.0.5" + provision_end_ip = "10.0.3.250" + + resolvers = ["8.8.8.8", "8.8.4.4"] +} + +resource "triton_machine" "test" { + name = "%s" + package = "g3-standard-0.25-smartos" + image = "842e6fa6-6e9b-11e5-8402-1b490459e334" + + tags = { + test = "hello!" + } + + nic { network = "${triton_fabric.test.id}" } +} +` + +var testAccTritonMachine_withoutnic = ` +resource "triton_fabric" "test" { + name = "%s-network" + description = "test network" + vlan_id = 2 # every DC seems to have a vlan 2 available + + subnet = "10.0.0.0/22" + gateway = "10.0.0.1" + provision_start_ip = "10.0.0.5" + provision_end_ip = "10.0.3.250" + + resolvers = ["8.8.8.8", "8.8.4.4"] +} + +resource "triton_machine" "test" { + name = "%s" + package = "g3-standard-0.25-smartos" + image = "842e6fa6-6e9b-11e5-8402-1b490459e334" + + tags = { + test = "hello!" + } } ` From a4eb8452ebe063f2613752bc4daba9e6ad8025d4 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Fri, 29 Apr 2016 10:22:49 -0500 Subject: [PATCH 2/2] remove debug statements from test --- builtin/providers/triton/resource_machine_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/builtin/providers/triton/resource_machine_test.go b/builtin/providers/triton/resource_machine_test.go index 1df000d2a..38a5e5806 100644 --- a/builtin/providers/triton/resource_machine_test.go +++ b/builtin/providers/triton/resource_machine_test.go @@ -131,9 +131,7 @@ func testCheckTritonMachineHasFabric(name, fabricName string) resource.TestCheck return fmt.Errorf("Bad: Check NICs Exist: %s", err) } - fmt.Printf("%+v\n", machine.Primary) for _, nic := range nics { - fmt.Printf("---\n%+v\n%+v\n", nic, network.Primary) if nic.Network == network.Primary.ID { return nil }