diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e090ded..bb48bfac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,7 @@ The following environment variables must be set: - XOA_ISO - The name of an ISO that exists on the same pool as `XOA_POOL` - XOA_ISO_SR - The name of an ISO storage repository that exists on the same pool as `XOA_POOL`. This SR must be writable since the tests will upload an ISO to it. - XOA_NETWORK - The name of a network that is PXE capable. If a non PXE capable network is used some tests may fail. +- XOA_PIF - The UUID of a PIF that will be used for testing VLAN network creation. This has the potential to disrupt network traffic, so this PIF should be an unused interface. I typically keep these in a ~/.xoa file and run the following before running the test suite diff --git a/client/client.go b/client/client.go index e8da02b9..a24f6434 100644 --- a/client/client.go +++ b/client/client.go @@ -70,8 +70,9 @@ type XOClient interface { GetCurrentUser() (*User, error) DeleteUser(userReq User) error - CreateNetwork(netReq Network) (*Network, error) + CreateNetwork(netReq CreateNetworkRequest) (*Network, error) GetNetwork(netReq Network) (*Network, error) + UpdateNetwork(netReq UpdateNetworkRequest) (*Network, error) GetNetworks() ([]Network, error) DeleteNetwork(id string) error @@ -255,6 +256,10 @@ func (c *Client) Call(method string, params, result interface{}, opt ...jsonrpc2 return nil } +type RefreshComparison interface { + Propagated(obj interface{}) bool +} + type XoObject interface { Compare(obj interface{}) bool } diff --git a/client/go.mod b/client/go.mod index 43ce546d..673eb25f 100644 --- a/client/go.mod +++ b/client/go.mod @@ -4,5 +4,6 @@ go 1.16 require ( github.com/gorilla/websocket v1.4.2 + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/sourcegraph/jsonrpc2 v0.0.0-20210201082850-366fbb520750 ) diff --git a/client/go.sum b/client/go.sum index 4b47ceef..6f7f09c4 100644 --- a/client/go.sum +++ b/client/go.sum @@ -1,5 +1,7 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/sourcegraph/jsonrpc2 v0.0.0-20210201082850-366fbb520750 h1:j3HKQAXXj5vV3oHyg9pjK3uIM4bidukvv+tR2iJCvFA= github.com/sourcegraph/jsonrpc2 v0.0.0-20210201082850-366fbb520750/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= diff --git a/client/network.go b/client/network.go index 25b6dd8b..22ecd7a0 100644 --- a/client/network.go +++ b/client/network.go @@ -5,14 +5,25 @@ import ( "fmt" "log" "os" + "strconv" "strings" + "time" + + "github.com/mitchellh/mapstructure" ) type Network struct { - Id string `json:"id"` - NameLabel string `json:"name_label"` - Bridge string `json:"bridge"` - PoolId string `json:"$poolId"` + Automatic bool `json:"automatic,omitempty"` + Id string `json:"id"` + NameLabel string `json:"name_label"` + NameDescription string `json:"name_description"` + Bridge string `json:"bridge"` + DefaultIsLocked bool `json:"defaultIsLocked"` + PoolId string `json:"$poolId"` + MTU int `json:"MTU"` + PIFs []string `json:"PIFs"` + Nbd bool `json:"nbd"` + InsecureNbd bool `json:"insecureNbd"` } func (net Network) Compare(obj interface{}) bool { @@ -35,19 +46,112 @@ func (net Network) Compare(obj interface{}) bool { return false } -func (c *Client) CreateNetwork(netReq Network) (*Network, error) { - var id string - params := map[string]interface{}{ - "pool": netReq.PoolId, - "name": netReq.NameLabel, +type CreateNetworkRequest struct { + Pool string `mapstructure:"pool"` + Name string `mapstructure:"name"` + Nbd bool `mapstructure:"nbd,omitempty"` + Description string `mapstructure:"description,omitempty"` + Mtu int `mapstructure:"mtu,omitempty"` + PIF string `mapstructure:"pif,omitempty"` + Vlan int `mapstructure:"vlan,omitempty"` + Automatic bool `mapstructure:"automatic"` + DefaultIsLocked bool `mapstructure:"defaultIsLocked"` +} + +// Nbd and Automatic are eventually consistent. This ensures that waitForModifyNetwork will +// poll until the values are correct. +func (c CreateNetworkRequest) Propagated(obj interface{}) bool { + otherNet := obj.(Network) + + if otherNet.Automatic == c.Automatic && + otherNet.Nbd == c.Nbd { + return true } + return false +} + +type UpdateNetworkRequest struct { + Id string `mapstructure:"id"` + Automatic bool `mapstructure:"automatic"` + DefaultIsLocked bool `mapstructure:"defaultIsLocked"` + NameDescription *string `mapstructure:"name_description,omitempty"` + NameLabel *string `mapstructure:"name_label,omitempty"` + Nbd bool `mapstructure:"nbd"` +} + +// Nbd and Automatic are eventually consistent. This ensures that waitForModifyNetwork will +// poll until the values are correct. +func (c UpdateNetworkRequest) Propagated(obj interface{}) bool { + otherNet := obj.(Network) + + if otherNet.Automatic == c.Automatic && + otherNet.Nbd == c.Nbd { + return true + } + return false +} + +func (c *Client) CreateNetwork(netReq CreateNetworkRequest) (*Network, error) { + var id string + var params map[string]interface{} + mapstructure.Decode(netReq, ¶ms) + delete(params, "automatic") + delete(params, "defaultIsLocked") + + log.Printf("[DEBUG] params for network.create: %#v", params) err := c.Call("network.create", params, &id) if err != nil { return nil, err } - return c.GetNetwork(Network{Id: id}) + + // Neither automatic nor defaultIsLocked can be specified in the network.create RPC. + // Update them afterwards if the user requested it during creation. + if netReq.Automatic || netReq.DefaultIsLocked { + _, err = c.UpdateNetwork(UpdateNetworkRequest{ + Id: id, + Automatic: netReq.Automatic, + DefaultIsLocked: netReq.DefaultIsLocked, + }) + } + + return c.waitForModifyNetwork(id, netReq, 10*time.Second) +} + +func (c *Client) waitForModifyNetwork(id string, target RefreshComparison, timeout time.Duration) (*Network, error) { + refreshFn := func() (result interface{}, state string, err error) { + network, err := c.GetNetwork(Network{Id: id}) + + if err != nil { + return network, "", err + } + + equal := strconv.FormatBool(target.Propagated(*network)) + + return network, equal, nil + } + stateConf := &StateChangeConf{ + Pending: []string{"false"}, + Refresh: refreshFn, + Target: []string{"true"}, + Timeout: timeout, + } + network, err := stateConf.WaitForState() + return network.(*Network), err +} + +func (c *Client) UpdateNetwork(netReq UpdateNetworkRequest) (*Network, error) { + var params map[string]interface{} + mapstructure.Decode(netReq, ¶ms) + + var success bool + err := c.Call("network.set", params, &success) + if err != nil { + return nil, err + } + + return c.waitForModifyNetwork(netReq.Id, netReq, 10*time.Second) } func (c *Client) GetNetwork(netReq Network) (*Network, error) { diff --git a/client/pif.go b/client/pif.go index 2c6c5099..c6b6807e 100644 --- a/client/pif.go +++ b/client/pif.go @@ -2,6 +2,8 @@ package client import ( "errors" + "fmt" + "os" ) type PIF struct { @@ -18,6 +20,9 @@ type PIF struct { func (p PIF) Compare(obj interface{}) bool { otherPif := obj.(PIF) + if p.Id != "" && otherPif.Id == p.Id { + return true + } hostIdExists := p.Host != "" if hostIdExists && p.Host != otherPif.Host { return false @@ -58,3 +63,32 @@ func (c *Client) GetPIF(pifReq PIF) (pifs []PIF, err error) { return pifs, nil } + +func FindPIFForTests(pif *PIF) { + pifId, found := os.LookupEnv("XOA_PIF") + + if !found { + fmt.Println("The XOA_PIF environment variable must be set to run the network resource tests") + return + } + + c, err := NewClient(GetConfigFromEnv()) + if err != nil { + fmt.Printf("failed to create client with error: %v", err) + os.Exit(-1) + } + + pifs, err := c.GetPIF(PIF{Id: pifId}) + + if err != nil { + fmt.Printf("[ERROR] Failed to get pif with error: %v", err) + os.Exit(1) + } + + if len(pifs) != 1 { + fmt.Printf("[ERROR] expected to find a single pif. Found %d PIFs instead: %v", len(pifs), pifs) + os.Exit(1) + } + + *pif = pifs[0] +} diff --git a/client/pif_test.go b/client/pif_test.go index 77609a7c..2ce9287f 100644 --- a/client/pif_test.go +++ b/client/pif_test.go @@ -28,4 +28,14 @@ func TestGetPIFByDevice(t *testing.T) { if pif.Vlan != vlan_id { t.Errorf("PIF's vlan %d should have matched %d", pif.Vlan, vlan_id) } + + id := pif.Id + pifs, err = c.GetPIF(PIF{Id: id}) + if err != nil { + t.Fatalf("failed to find PIF with id: %s with error: %v", id, err) + } + + if len(pifs) != 1 { + t.Errorf("expected to find single PIF instead found: %d, %v", len(pifs), pifs) + } } diff --git a/client/setup_test.go b/client/setup_test.go index 3f8728e3..79d0033b 100644 --- a/client/setup_test.go +++ b/client/setup_test.go @@ -24,9 +24,9 @@ func CreateNetwork(network *Network) { os.Exit(1) } - net, err := c.CreateNetwork(Network{ - NameLabel: testNetworkName, - PoolId: accTestPool.Id, + net, err := c.CreateNetwork(CreateNetworkRequest{ + Name: testNetworkName, + Pool: accTestPool.Id, }) if err != nil { diff --git a/docs/resources/network.md b/docs/resources/network.md new file mode 100644 index 00000000..34ea8851 --- /dev/null +++ b/docs/resources/network.md @@ -0,0 +1,54 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "xenorchestra_network Resource - terraform-provider-xenorchestra" +subcategory: "" +description: |- + +--- + +# xenorchestra_network (Resource) + + + +## Example Usage + +```terraform +data "xenorchestra_host" "host1" { + name_label = "Your host" +} + +data "xenorchestra_pif" "pif" { + device = "eth0" + vlan = -1 + host_id = data.xenorchestra_host.host1.id +} + +resource "xenorchestra_network" "network" { + name_label = "new network name" + pool_id = data.xenorchestra_host.host1.pool_id + pif_id = data.xenorchestra_pif.pif.id + vlan = 22 +} +``` + + +## Schema + +### Required + +- `name_label` (String) The name label of the network. +- `pool_id` (String) The pool id that this network should belong to. + +### Optional + +- `automatic` (Boolean) +- `default_is_locked` (Boolean) This argument controls whether the network should enforce VIF locking. This defaults to `false` which means that no filtering rules are applied. +- `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. +- `vlan` (Number) The vlan to use for the network. Defaults to `0` meaning no VLAN. + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/go.sum b/go.sum index ac91a7d0..26013d0c 100644 --- a/go.sum +++ b/go.sum @@ -241,6 +241,8 @@ github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= diff --git a/xoa/acc_setup_test.go b/xoa/acc_setup_test.go index 8a0adfd9..39b76b6d 100644 --- a/xoa/acc_setup_test.go +++ b/xoa/acc_setup_test.go @@ -19,6 +19,7 @@ var accIsoSr client.StorageRepository var accDefaultNetwork client.Network var accUser client.User = client.User{Email: fmt.Sprintf("%s-%s", accTestPrefix, "regular-user")} var testTemplate client.Template +var accTestPIF client.PIF var disklessTestTemplate client.Template var testIsoName string @@ -36,6 +37,7 @@ func TestMain(m *testing.M) { if runSetup { client.FindPoolForTests(&accTestPool) + client.FindPIFForTests(&accTestPIF) client.FindTemplateForTests(&testTemplate, accTestPool.Id, "XOA_TEMPLATE") client.FindTemplateForTests(&disklessTestTemplate, accTestPool.Id, "XOA_DISKLESS_TEMPLATE") client.FindHostForTests(accTestPool.Master, &accTestHost) diff --git a/xoa/data_source_xenorchestra_network_test.go b/xoa/data_source_xenorchestra_network_test.go index 039bbec0..b61b09ca 100644 --- a/xoa/data_source_xenorchestra_network_test.go +++ b/xoa/data_source_xenorchestra_network_test.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -var createNetwork = func(net client.Network, t *testing.T, times int) func() { +var createNetwork = func(net client.CreateNetworkRequest, t *testing.T, times int) func() { return func() { for i := 0; i < times; i++ { c, err := client.NewClient(client.GetConfigFromEnv()) @@ -28,12 +28,12 @@ var createNetwork = func(net client.Network, t *testing.T, times int) func() { } } -var getTestNetwork = func(poolId string) client.Network { +var getTestNetwork = func(poolId string) client.CreateNetworkRequest { nameLabel := fmt.Sprintf("%s-network-%d", accTestPrefix, testObjectIndex) testObjectIndex++ - return client.Network{ - NameLabel: nameLabel, - PoolId: poolId, + return client.CreateNetworkRequest{ + Name: nameLabel, + Pool: poolId, } } @@ -46,7 +46,7 @@ func TestAccXONetworkDataSource_read(t *testing.T) { Steps: []resource.TestStep{ { PreConfig: createNetwork(net, t, 1), - Config: testAccXenorchestraDataSourceNetworkConfig(net.NameLabel), + Config: testAccXenorchestraDataSourceNetworkConfig(net.Name), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckXenorchestraDataSourceNetwork(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id"), @@ -89,7 +89,7 @@ func TestAccXONetworkDataSource_multipleCauseError(t *testing.T) { Steps: []resource.TestStep{ { PreConfig: createNetwork(net, t, 2), - Config: testAccXenorchestraDataSourceNetworkConfig(net.NameLabel), + Config: testAccXenorchestraDataSourceNetworkConfig(net.Name), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckXenorchestraDataSourceNetwork(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id")), diff --git a/xoa/provider.go b/xoa/provider.go index 9da30f8c..a456f3be 100644 --- a/xoa/provider.go +++ b/xoa/provider.go @@ -37,6 +37,7 @@ func Provider() *schema.Provider { ResourcesMap: map[string]*schema.Resource{ "xenorchestra_acl": resourceAcl(), "xenorchestra_cloud_config": resourceCloudConfigRecord(), + "xenorchestra_network": resourceXoaNetwork(), "xenorchestra_vm": resourceRecord(), "xenorchestra_resource_set": resourceResourceSet(), "xenorchestra_vdi": resourceVDIRecord(), diff --git a/xoa/resource_xenorchestra_network.go b/xoa/resource_xenorchestra_network.go new file mode 100644 index 00000000..645a5550 --- /dev/null +++ b/xoa/resource_xenorchestra_network.go @@ -0,0 +1,208 @@ +package xoa + +import ( + "errors" + + "github.com/ddelnano/terraform-provider-xenorchestra/client" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var netDefaultDesc string = "Created with Xen Orchestra" + +func resourceXoaNetwork() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkCreate, + Delete: resourceNetworkDelete, + Read: resourceNetworkRead, + Update: resourceNetworkUpdate, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "automatic": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "default_is_locked": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "This argument controls whether the network should enforce VIF locking. This defaults to `false` which means that no filtering rules are applied.", + }, + "name_label": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The name label of the network.", + }, + "name_description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: netDefaultDesc, + }, + "pif_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + RequiredWith: []string{ + "vlan", + }, + ForceNew: true, + Description: "The pif (uuid) that should be used for this network.", + }, + "vlan": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 0, + RequiredWith: []string{ + "pif_id", + }, + ForceNew: true, + Description: "The vlan to use for the network. Defaults to `0` meaning no VLAN.", + }, + "pool_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The pool id that this network should belong to.", + }, + "mtu": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 1500, + ForceNew: true, + Description: "The MTU of the network. Defaults to `1500` if unspecified.", + }, + "nbd": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether the network should use a network block device. Defaults to `false` if unspecified.", + }, + }, + } +} + +func resourceNetworkCreate(d *schema.ResourceData, m interface{}) error { + c := m.(client.XOClient) + + network, err := c.CreateNetwork(client.CreateNetworkRequest{ + Automatic: d.Get("automatic").(bool), + DefaultIsLocked: d.Get("default_is_locked").(bool), + Name: d.Get("name_label").(string), + Description: d.Get("name_description").(string), + Pool: d.Get("pool_id").(string), + Mtu: d.Get("mtu").(int), + Nbd: d.Get("nbd").(bool), + Vlan: d.Get("vlan").(int), + PIF: d.Get("pif_id").(string), + }) + if err != nil { + return err + } + vlan, err := getVlanForNetwork(c, network) + if err != nil { + return err + } + return networkToData(network, vlan, d) +} + +func getVlanForNetwork(c client.XOClient, net *client.Network) (int, error) { + if len(net.PIFs) > 0 { + pifs, err := c.GetPIF(client.PIF{Id: net.PIFs[0]}) + if err != nil { + return -1, err + } + + if len(pifs) != 1 { + return -1, errors.New("expected to find single PIF") + } + return pifs[0].Vlan, nil + } + return 0, nil +} + +func resourceNetworkRead(d *schema.ResourceData, m interface{}) error { + c := m.(client.XOClient) + net, err := c.GetNetwork( + client.Network{Id: d.Id()}) + + if _, ok := err.(client.NotFound); ok { + d.SetId("") + return nil + } + + if err != nil { + return err + } + + vlan, err := getVlanForNetwork(c, net) + if err != nil { + return err + } + return networkToData(net, vlan, d) +} + +func resourceNetworkUpdate(d *schema.ResourceData, m interface{}) error { + c := m.(client.XOClient) + + netUpdateReq := client.UpdateNetworkRequest{ + Id: d.Id(), + Automatic: d.Get("automatic").(bool), + DefaultIsLocked: d.Get("default_is_locked").(bool), + Nbd: d.Get("nbd").(bool), + } + if d.HasChange("name_label") { + nameLabel := d.Get("name_label").(string) + netUpdateReq.NameLabel = &nameLabel + } + if d.HasChange("name_description") { + nameDescription := d.Get("name_description").(string) + netUpdateReq.NameDescription = &nameDescription + } + _, err := c.UpdateNetwork(netUpdateReq) + if err != nil { + return err + } + return resourceNetworkRead(d, m) +} + +func resourceNetworkDelete(d *schema.ResourceData, m interface{}) error { + c := m.(client.XOClient) + + err := c.DeleteNetwork(d.Id()) + + if err != nil { + return err + } + d.SetId("") + return nil +} + +func networkToData(network *client.Network, vlan int, d *schema.ResourceData) error { + d.SetId(network.Id) + if err := d.Set("name_label", network.NameLabel); err != nil { + return err + } + if err := d.Set("name_description", network.NameDescription); err != nil { + return err + } + if err := d.Set("pool_id", network.PoolId); err != nil { + return err + } + if err := d.Set("mtu", network.MTU); err != nil { + return err + } + if err := d.Set("nbd", network.Nbd); err != nil { + return err + } + if err := d.Set("automatic", network.Automatic); err != nil { + return err + } + if err := d.Set("default_is_locked", network.DefaultIsLocked); err != nil { + return err + } + if err := d.Set("vlan", vlan); err != nil { + return err + } + return nil +} diff --git a/xoa/resource_xenorchestra_network_test.go b/xoa/resource_xenorchestra_network_test.go new file mode 100644 index 00000000..beaa75f1 --- /dev/null +++ b/xoa/resource_xenorchestra_network_test.go @@ -0,0 +1,266 @@ +package xoa + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccXONetwork_create(t *testing.T) { + if accTestPIF.Id == "" { + t.Skip() + } + resourceName := "xenorchestra_network.network" + nameLabel := fmt.Sprintf("%s - %s", accTestPrefix, t.Name()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccXenorchestraNetworkConfig(nameLabel), + 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, "vlan")), + }, + }, + }, + ) +} + +func TestAccXONetwork_createWithVlanRequiresPIF(t *testing.T) { + if accTestPIF.Id == "" { + t.Skip() + } + nameLabel := fmt.Sprintf("%s - %s", accTestPrefix, t.Name()) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccXenorchestraNetworkConfigWithoutVlan(nameLabel), + ExpectError: regexp.MustCompile("all of `pif_id,vlan` must be specified"), + }, + { + Config: testAccXenorchestraNetworkConfigWithoutPIF(nameLabel), + ExpectError: regexp.MustCompile("all of `pif_id,vlan` must be specified"), + }, + }, + }, + ) +} + +func TestAccXONetwork_createWithNonDefaults(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 := "true" + mtu := "950" + vlan := "22" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccXenorchestraNetworkConfigNonDefaultsWithVlan(nameLabel, desc, mtu, nbd, vlan), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckXenorchestraNetwork(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "name_label"), + resource.TestCheckResourceAttr(resourceName, "name_description", desc), + resource.TestCheckResourceAttr(resourceName, "pool_id", accTestPIF.PoolId), + resource.TestCheckResourceAttr(resourceName, "mtu", mtu), + resource.TestCheckResourceAttr(resourceName, "vlan", vlan), + resource.TestCheckResourceAttr(resourceName, "nbd", nbd)), + }, + }, + }, + ) +} + +func TestAccXONetwork_updateInPlace(t *testing.T) { + if accTestPIF.Id == "" { + t.Skip() + } + resourceName := "xenorchestra_network.network" + nameLabel := fmt.Sprintf("%s - %s", accTestPrefix, t.Name()) + isLocked := "false" + automatic := "true" + desc := netDefaultDesc + nbd := "false" + + updatedNameLabel := nameLabel + " updated" + updatedDesc := "Non default description" + updatedNbd := "true" + updatedAutomatic := "false" + updatedIsLocked := "true" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccXenorchestraNetworkConfigInPlaceUpdates(nameLabel, desc, nbd, automatic, isLocked), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckXenorchestraNetwork(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "name_label"), + resource.TestCheckResourceAttr(resourceName, "name_description", desc), + resource.TestCheckResourceAttr(resourceName, "pool_id", accTestPIF.PoolId), + resource.TestCheckResourceAttr(resourceName, "automatic", automatic), + resource.TestCheckResourceAttr(resourceName, "default_is_locked", isLocked), + resource.TestCheckResourceAttr(resourceName, "nbd", nbd)), + }, + { + Config: testAccXenorchestraNetworkConfigInPlaceUpdates(updatedNameLabel, updatedDesc, updatedNbd, updatedAutomatic, updatedIsLocked), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckXenorchestraNetwork(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "name_label", updatedNameLabel), + resource.TestCheckResourceAttr(resourceName, "name_description", updatedDesc), + resource.TestCheckResourceAttr(resourceName, "pool_id", accTestPIF.PoolId), + resource.TestCheckResourceAttr(resourceName, "automatic", updatedAutomatic), + resource.TestCheckResourceAttr(resourceName, "default_is_locked", updatedIsLocked), + resource.TestCheckResourceAttr(resourceName, "nbd", updatedNbd)), + }, + }, + }, + ) +} + +func TestAccXONetwork_updateForceNew(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" + origMtu := "950" + updatedMtu := "1000" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccXenorchestraNetworkConfigNonDefaults(nameLabel, desc, origMtu, nbd), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckXenorchestraNetwork(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "name_label"), + resource.TestCheckResourceAttr(resourceName, "name_description", desc), + resource.TestCheckResourceAttr(resourceName, "pool_id", accTestPIF.PoolId), + resource.TestCheckResourceAttr(resourceName, "mtu", origMtu)), + }, + { + Config: testAccXenorchestraNetworkConfigNonDefaults(nameLabel, desc, updatedMtu, nbd), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckXenorchestraNetwork(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "name_label"), + resource.TestCheckResourceAttr(resourceName, "name_description", desc), + resource.TestCheckResourceAttr(resourceName, "pool_id", accTestPIF.PoolId), + resource.TestCheckResourceAttr(resourceName, "mtu", updatedMtu)), + }, + }, + }, + ) +} + +func testAccCheckXenorchestraNetwork(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Can't find Network resource: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Network resource ID not set") + } + return nil + } +} + +var testAccXenorchestraNetworkConfig = func(name string) string { + return fmt.Sprintf(` +resource "xenorchestra_network" "network" { + name_label = "%s" + pool_id = "%s" +} +`, name, accTestPIF.PoolId) +} + +var testAccXenorchestraNetworkConfigWithoutVlan = func(name string) string { + return testAccXenorchestraDataSourcePIFConfig(accTestPIF.Host) + fmt.Sprintf(` +resource "xenorchestra_network" "network" { + name_label = "%s" + pool_id = "%s" + pif_id = data.xenorchestra_pif.pif.id +} +`, name, accTestPIF.PoolId) +} + +var testAccXenorchestraNetworkConfigWithoutPIF = func(name string) string { + return fmt.Sprintf(` +resource "xenorchestra_network" "network" { + name_label = "%s" + pool_id = "%s" + vlan = 10 +} +`, name, accTestPIF.PoolId) +} + +var testAccXenorchestraNetworkConfigNonDefaults = func(name, desc, mtu, nbd string) string { + return fmt.Sprintf(` +resource "xenorchestra_network" "network" { + name_label = "%s" + name_description = "%s" + pool_id = "%s" + mtu = %s + nbd = %s +} +`, name, desc, accTestPIF.PoolId, mtu, nbd) +} + +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 + vlan = %s +} +`, accTestPIF.Host, name, desc, accTestPIF.PoolId, mtu, nbd, vlan) +} + +var testAccXenorchestraNetworkConfigInPlaceUpdates = func(name, desc, nbd, automatic, isLocked string) string { + return fmt.Sprintf(` +resource "xenorchestra_network" "network" { + name_label = "%s" + name_description = "%s" + pool_id = "%s" + nbd = %s + automatic = %s + default_is_locked = %s +} +`, name, desc, accTestPIF.PoolId, nbd, automatic, isLocked) +} diff --git a/xoa/resource_xenorchestra_vm_test.go b/xoa/resource_xenorchestra_vm_test.go index 5b4866e2..62fe7284 100644 --- a/xoa/resource_xenorchestra_vm_test.go +++ b/xoa/resource_xenorchestra_vm_test.go @@ -1572,7 +1572,7 @@ resource "xenorchestra_vm" "bar" { size = 10001317888 } } -`, testIsoName, accTestPool.Id, accDefaultNetwork, accTestPool.Id, vmName, accDefaultSr.Id) +`, testIsoName, accTestPool.Id, accDefaultNetwork.NameLabel, accTestPool.Id, vmName, accDefaultSr.Id) } func testAccVmConfigWithTags(vmName, tag, secondTag string) string {