diff --git a/client/pif.go b/client/pif.go index c6b6807e..62ce1854 100644 --- a/client/pif.go +++ b/client/pif.go @@ -28,7 +28,12 @@ func (p PIF) Compare(obj interface{}) bool { 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/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 {