Skip to content

Commit

Permalink
Add xenorchestra_network resource (#249)
Browse files Browse the repository at this point in the history
* Add `xenorchestra_network` resource support for non bonded networks
  • Loading branch information
ddelnano committed Jul 26, 2023
1 parent 2473ebb commit f87dece
Show file tree
Hide file tree
Showing 16 changed files with 712 additions and 22 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 6 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions client/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
2 changes: 2 additions & 0 deletions client/go.sum
Original file line number Diff line number Diff line change
@@ -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=
124 changes: 114 additions & 10 deletions client/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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, &params)

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, &params)

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) {
Expand Down
34 changes: 34 additions & 0 deletions client/pif.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package client

import (
"errors"
"fmt"
"os"
)

type PIF struct {
Expand All @@ -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
Expand Down Expand Up @@ -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]
}
10 changes: 10 additions & 0 deletions client/pif_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
6 changes: 3 additions & 3 deletions client/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
54 changes: 54 additions & 0 deletions docs/resources/network.md
Original file line number Diff line number Diff line change
@@ -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 generated by tfplugindocs -->
## 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.
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 2 additions & 0 deletions xoa/acc_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand Down
14 changes: 7 additions & 7 deletions xoa/data_source_xenorchestra_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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,
}
}

Expand All @@ -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"),
Expand Down Expand Up @@ -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")),
Expand Down
Loading

0 comments on commit f87dece

Please sign in to comment.