diff --git a/client/pif.go b/client/pif.go index c6b6807e..32231c66 100644 --- a/client/pif.go +++ b/client/pif.go @@ -20,15 +20,24 @@ type PIF struct { func (p PIF) Compare(obj interface{}) bool { otherPif := obj.(PIF) - if p.Id != "" && otherPif.Id == p.Id { - return true + if p.Id != "" { + if otherPif.Id == p.Id { + return true + } else { + return false + } } hostIdExists := p.Host != "" if hostIdExists && p.Host != otherPif.Host { return false } - if p.Vlan == otherPif.Vlan && p.Device == otherPif.Device { + networkIdExists := p.Network != "" + if networkIdExists && p.Network != otherPif.Network { + return false + } + + if p.Vlan == otherPif.Vlan && (p.Device == "" || (p.Device == otherPif.Device)) { return true } return false diff --git a/docs/resources/network.md b/docs/resources/network.md index 34ea8851..dfe9c8d1 100644 --- a/docs/resources/network.md +++ b/docs/resources/network.md @@ -17,16 +17,17 @@ data "xenorchestra_host" "host1" { name_label = "Your host" } -data "xenorchestra_pif" "pif" { - device = "eth0" - vlan = -1 - host_id = data.xenorchestra_host.host1.id +# Create a single server network private network +resource "xenorchestra_network" "private_network" { + name_label = "new network name" + pool_id = data.xenorchestra_host.host1.pool_id } -resource "xenorchestra_network" "network" { +# Create a network with a 22 VLAN tag from the eth0 device +resource "xenorchestra_network" "vlan_network" { name_label = "new network name" pool_id = data.xenorchestra_host.host1.pool_id - pif_id = data.xenorchestra_pif.pif.id + source_pif_device = "eth0" vlan = 22 } ``` @@ -46,7 +47,7 @@ resource "xenorchestra_network" "network" { - `mtu` (Number) The MTU of the network. Defaults to `1500` if unspecified. - `name_description` (String) - `nbd` (Boolean) Whether the network should use a network block device. Defaults to `false` if unspecified. -- `pif_id` (String) The pif (uuid) that should be used for this network. +- `source_pif_device` (String) The PIF device (eth0, eth1, etc) that will be used as an input during network creation. This parameter is required if a vlan is specified. - `vlan` (Number) The vlan to use for the network. Defaults to `0` meaning no VLAN. ### Read-Only diff --git a/examples/resources/xenorchestra_network/resource.tf b/examples/resources/xenorchestra_network/resource.tf index fb516b5b..b6b83a7e 100644 --- a/examples/resources/xenorchestra_network/resource.tf +++ b/examples/resources/xenorchestra_network/resource.tf @@ -2,15 +2,16 @@ data "xenorchestra_host" "host1" { name_label = "Your host" } -data "xenorchestra_pif" "pif" { - device = "eth0" - vlan = -1 - host_id = data.xenorchestra_host.host1.id +# Create a single server network private network +resource "xenorchestra_network" "private_network" { + name_label = "new network name" + pool_id = data.xenorchestra_host.host1.pool_id } -resource "xenorchestra_network" "network" { +# Create a network with a 22 VLAN tag from the eth0 device +resource "xenorchestra_network" "vlan_network" { name_label = "new network name" pool_id = data.xenorchestra_host.host1.pool_id - pif_id = data.xenorchestra_pif.pif.id + source_pif_device = "eth0" vlan = 22 } diff --git a/xoa/resource_xenorchestra_network.go b/xoa/resource_xenorchestra_network.go index 645a5550..c4011699 100644 --- a/xoa/resource_xenorchestra_network.go +++ b/xoa/resource_xenorchestra_network.go @@ -2,6 +2,8 @@ package xoa import ( "errors" + "fmt" + "log" "github.com/ddelnano/terraform-provider-xenorchestra/client" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -40,21 +42,21 @@ func resourceXoaNetwork() *schema.Resource { Optional: true, Default: netDefaultDesc, }, - "pif_id": &schema.Schema{ + "source_pif_device": &schema.Schema{ Type: schema.TypeString, Optional: true, RequiredWith: []string{ "vlan", }, ForceNew: true, - Description: "The pif (uuid) that should be used for this network.", + Description: "The PIF device (eth0, eth1, etc) that will be used as an input during network creation. This parameter is required if a vlan is specified.", }, "vlan": &schema.Schema{ Type: schema.TypeInt, Optional: true, Default: 0, RequiredWith: []string{ - "pif_id", + "source_pif_device", }, ForceNew: true, Description: "The vlan to use for the network. Defaults to `0` meaning no VLAN.", @@ -85,6 +87,16 @@ func resourceXoaNetwork() *schema.Resource { func resourceNetworkCreate(d *schema.ResourceData, m interface{}) error { c := m.(client.XOClient) + var pifId string + if sourcePIFDevice := d.Get("source_pif_device").(string); sourcePIFDevice != "" { + pif, err := getNetworkCreationSourcePIF(c, sourcePIFDevice, d.Get("pool_id").(string)) + + if err != nil { + return err + } + pifId = pif.Id + } + network, err := c.CreateNetwork(client.CreateNetworkRequest{ Automatic: d.Get("automatic").(bool), DefaultIsLocked: d.Get("default_is_locked").(bool), @@ -94,31 +106,63 @@ func resourceNetworkCreate(d *schema.ResourceData, m interface{}) error { Mtu: d.Get("mtu").(int), Nbd: d.Get("nbd").(bool), Vlan: d.Get("vlan").(int), - PIF: d.Get("pif_id").(string), + PIF: pifId, }) if err != nil { return err } - vlan, err := getVlanForNetwork(c, network) + vlan, pifDevice, err := getVlanForNetwork(c, network) if err != nil { return err } - return networkToData(network, vlan, d) + return networkToData(network, vlan, pifDevice, d) } -func getVlanForNetwork(c client.XOClient, net *client.Network) (int, error) { +// This function returns the PIF specified the given device name on the pool's primary host. In order to create +// networks with a VLAN, a PIF for the given device must be provided. Xen Orchestra uses the primary host's PIF +// for this and so we emulate that behavior. +func getNetworkCreationSourcePIF(c client.XOClient, pifDevice, poolId string) (*client.PIF, error) { + pools, err := c.GetPools(client.Pool{Id: poolId}) + if err != nil { + return nil, err + } + + if len(pools) != 1 { + return nil, errors.New(fmt.Sprintf("expected to find a single pool, instead found %d", len(pools))) + } + + pool := pools[0] + pifs, err := c.GetPIF(client.PIF{ + Host: pool.Master, + Vlan: -1, + Device: pifDevice, + }) + + if err != nil { + return nil, err + } + + if len(pifs) != 1 { + return nil, errors.New(fmt.Sprintf("expected to find a single PIF, instead found %d. %+v", len(pifs), pifs)) + } + + return &pifs[0], nil +} + +// Returns the VLAN and device name for the given network. +func getVlanForNetwork(c client.XOClient, net *client.Network) (int, string, error) { if len(net.PIFs) > 0 { pifs, err := c.GetPIF(client.PIF{Id: net.PIFs[0]}) if err != nil { - return -1, err + return -1, "", err } if len(pifs) != 1 { - return -1, errors.New("expected to find single PIF") + return -1, "", errors.New("expected to find single PIF") } - return pifs[0].Vlan, nil + return pifs[0].Vlan, pifs[0].Device, nil } - return 0, nil + return 0, "", nil } func resourceNetworkRead(d *schema.ResourceData, m interface{}) error { @@ -135,11 +179,11 @@ func resourceNetworkRead(d *schema.ResourceData, m interface{}) error { return err } - vlan, err := getVlanForNetwork(c, net) + vlan, pifDevice, err := getVlanForNetwork(c, net) if err != nil { return err } - return networkToData(net, vlan, d) + return networkToData(net, vlan, pifDevice, d) } func resourceNetworkUpdate(d *schema.ResourceData, m interface{}) error { @@ -178,11 +222,12 @@ func resourceNetworkDelete(d *schema.ResourceData, m interface{}) error { return nil } -func networkToData(network *client.Network, vlan int, d *schema.ResourceData) error { +func networkToData(network *client.Network, vlan int, pifDevice string, d *schema.ResourceData) error { d.SetId(network.Id) if err := d.Set("name_label", network.NameLabel); err != nil { return err } + log.Printf("This is being called\n") if err := d.Set("name_description", network.NameDescription); err != nil { return err } @@ -204,5 +249,8 @@ func networkToData(network *client.Network, vlan int, d *schema.ResourceData) er if err := d.Set("vlan", vlan); err != nil { return err } + if err := d.Set("source_pif_device", pifDevice); err != nil { + return err + } return nil } diff --git a/xoa/resource_xenorchestra_network_test.go b/xoa/resource_xenorchestra_network_test.go index beaa75f1..7cd081f3 100644 --- a/xoa/resource_xenorchestra_network_test.go +++ b/xoa/resource_xenorchestra_network_test.go @@ -35,6 +35,42 @@ func TestAccXONetwork_create(t *testing.T) { ) } +func TestAccXONetwork_import(t *testing.T) { + if accTestPIF.Id == "" { + t.Skip() + } + resourceName := "xenorchestra_network.network" + nameLabel := fmt.Sprintf("%s - %s", accTestPrefix, t.Name()) + desc := "Non default description" + nbd := "false" + mtu := "1500" + vlan := "23" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccXenorchestraNetworkConfigNonDefaultsWithVlan(nameLabel, desc, mtu, nbd, vlan), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckXenorchestraNetwork(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "name_label"), + resource.TestCheckResourceAttrSet(resourceName, "name_description"), + resource.TestCheckResourceAttrSet(resourceName, "pool_id"), + resource.TestCheckResourceAttrSet(resourceName, "mtu"), + resource.TestCheckResourceAttrSet(resourceName, "source_pif_device"), + resource.TestCheckResourceAttrSet(resourceName, "vlan")), + }, + }, + }, + ) +} + func TestAccXONetwork_createWithVlanRequiresPIF(t *testing.T) { if accTestPIF.Id == "" { t.Skip() @@ -46,11 +82,11 @@ func TestAccXONetwork_createWithVlanRequiresPIF(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccXenorchestraNetworkConfigWithoutVlan(nameLabel), - ExpectError: regexp.MustCompile("all of `pif_id,vlan` must be specified"), + ExpectError: regexp.MustCompile("all of `source_pif_device,vlan` must be specified"), }, { Config: testAccXenorchestraNetworkConfigWithoutPIF(nameLabel), - ExpectError: regexp.MustCompile("all of `pif_id,vlan` must be specified"), + ExpectError: regexp.MustCompile("all of `source_pif_device,vlan` must be specified"), }, }, }, @@ -80,6 +116,7 @@ func TestAccXONetwork_createWithNonDefaults(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name_description", desc), resource.TestCheckResourceAttr(resourceName, "pool_id", accTestPIF.PoolId), resource.TestCheckResourceAttr(resourceName, "mtu", mtu), + resource.TestCheckResourceAttrSet(resourceName, "source_pif_device"), resource.TestCheckResourceAttr(resourceName, "vlan", vlan), resource.TestCheckResourceAttr(resourceName, "nbd", nbd)), }, @@ -205,7 +242,7 @@ var testAccXenorchestraNetworkConfigWithoutVlan = func(name string) string { resource "xenorchestra_network" "network" { name_label = "%s" pool_id = "%s" - pif_id = data.xenorchestra_pif.pif.id + source_pif_device = "eth1" } `, name, accTestPIF.PoolId) } @@ -234,22 +271,16 @@ resource "xenorchestra_network" "network" { var testAccXenorchestraNetworkConfigNonDefaultsWithVlan = func(name, desc, mtu, nbd, vlan string) string { return fmt.Sprintf(` -data "xenorchestra_pif" "pif" { - device = "eth0" - vlan = -1 - host_id = "%s" -} - resource "xenorchestra_network" "network" { name_label = "%s" name_description = "%s" pool_id = "%s" mtu = %s nbd = %s - pif_id = data.xenorchestra_pif.pif.id + source_pif_device = "eth0" vlan = %s } -`, accTestPIF.Host, name, desc, accTestPIF.PoolId, mtu, nbd, vlan) +`, name, desc, accTestPIF.PoolId, mtu, nbd, vlan) } var testAccXenorchestraNetworkConfigInPlaceUpdates = func(name, desc, nbd, automatic, isLocked string) string {