Skip to content

Commit

Permalink
Create network data source (#68)
Browse files Browse the repository at this point in the history
* Create network data source and get initial acceptance tests passing

* Refactor FindFromGetAllObjects to use shared logic

* Use XOA_POOL environment variable to parameterize integration / acceptance tests

* Add docs for the data source

* Fix verbose logging

* Add stronger assertions and add tests for compare logic

* Refactor setup code to be shared between the client and acceptance tests
  • Loading branch information
ddelnano authored Sep 29, 2020
1 parent b199dab commit 7838056
Show file tree
Hide file tree
Showing 14 changed files with 502 additions and 18 deletions.
24 changes: 13 additions & 11 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"time"
Expand Down Expand Up @@ -87,6 +88,7 @@ func NewClient(config Config) (*Client, error) {

func (c *Client) Call(ctx context.Context, method string, params, result interface{}, opt ...jsonrpc2.CallOption) error {
err := c.rpc.Call(ctx, method, params, &result, opt...)
log.Printf("[TRACE] Made rpc call `%s` with params: %v and received %+v: result with error: %v\n", method, params, result, err)

if err != nil {
rpcErr, ok := err.(*jsonrpc2.Error)
Expand All @@ -111,10 +113,11 @@ type XoObject interface {
New(obj map[string]interface{}) XoObject
}

func (c *Client) FindFromGetAllObjects(obj XoObject) (interface{}, error) {

func (c *Client) GetAllObjectsOfType(obj XoObject, response interface{}) error {
xoApiType := ""
switch t := obj.(type) {
case Network:
xoApiType = "network"
case PIF:
xoApiType = "PIF"
case Pool:
Expand All @@ -135,12 +138,15 @@ func (c *Client) FindFromGetAllObjects(obj XoObject) (interface{}, error) {
"type": xoApiType,
},
}
ctx, _ := context.WithTimeout(context.Background(), 100*time.Second)
return c.Call(ctx, "xo.getAllObjects", params, &response)
}

func (c *Client) FindFromGetAllObjects(obj XoObject) (interface{}, error) {
var objsRes struct {
Objects map[string]interface{} `json:"-"`
}
ctx, _ := context.WithTimeout(context.Background(), 100*time.Second)
err := c.Call(ctx, "xo.getAllObjects", params, &objsRes.Objects)

err := c.GetAllObjectsOfType(obj, &objsRes.Objects)
if err != nil {
return obj, err
}
Expand All @@ -153,20 +159,16 @@ func (c *Client) FindFromGetAllObjects(obj XoObject) (interface{}, error) {
return obj, errors.New("Could not coerce interface{} into map")
}

if v["type"].(string) != xoApiType {
continue
}

if obj.Compare(v) {
found = true
objs = append(objs, obj.New(v))
}
}
if !found {
return obj, NotFound{Type: xoApiType, Query: obj}
return obj, NotFound{Query: obj}
}

fmt.Printf("[DEBUG] Found the following objects from xo.getAllObjects: %+v\n", objs)
log.Printf("[TRACE] Found the following objects from xo.getAllObjects: %+v\n", objs)
if len(objs) == 1 {

return objs[0], nil
Expand Down
7 changes: 4 additions & 3 deletions client/errors.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package client

import "fmt"
import (
"fmt"
)

type NotFound struct {
Query XoObject
Type string
}

func (e NotFound) Error() string {
return fmt.Sprintf("Could not find %s with query: %+v", e.Type, e.Query)
return fmt.Sprintf("Could not find %[1]T with query: %+[1]v", e.Query)
}
4 changes: 1 addition & 3 deletions client/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@ import (
)

func TestNotFoundErrorMessage(t *testing.T) {
vifType := "VIF"
vif := VIF{
MacAddress: "E8:61:7E:8E:F1:81",
}
err := NotFound{
Query: vif,
Type: vifType,
}

expectedMsg := fmt.Sprintf("Could not find %s with query: %+v", vifType, vif)
expectedMsg := fmt.Sprintf("Could not find client.VIF with query: %+v", vif)
msg := err.Error()

if expectedMsg != msg {
Expand Down
133 changes: 133 additions & 0 deletions client/network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package client

import (
"context"
"errors"
"fmt"
"log"
"strings"
"time"
)

type Network struct {
Id string `json:"id"`
NameLabel string `json:"name_label"`
Bridge string
PoolId string
}

func (net Network) Compare(obj map[string]interface{}) bool {
id := obj["id"].(string)
nameLabel := obj["name_label"].(string)
poolId := obj["$poolId"].(string)
if net.Id == id {
return true
}

labelsMatch := false
if net.NameLabel == nameLabel {
labelsMatch = true
}

if net.PoolId == "" && labelsMatch {
return true
} else if net.PoolId == poolId && labelsMatch {
return true
}

return false
}

func (net Network) New(obj map[string]interface{}) XoObject {
id := obj["id"].(string)
poolId := obj["$poolId"].(string)
nameLabel := obj["name_label"].(string)
bridge := obj["bridge"].(string)
return Network{
Id: id,
Bridge: bridge,
PoolId: poolId,
NameLabel: nameLabel,
}
}

func (c *Client) CreateNetwork(netReq Network) (*Network, error) {
var id string
params := map[string]interface{}{
"pool": netReq.PoolId,
"name": netReq.NameLabel,
}

ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
err := c.Call(ctx, "network.create", params, &id)

if err != nil {
return nil, err
}
return c.GetNetwork(Network{Id: id})
}

func (c *Client) GetNetwork(netReq Network) (*Network, error) {
obj, err := c.FindFromGetAllObjects(netReq)

if err != nil {
return nil, err
}

if _, ok := obj.([]interface{}); ok {
return nil, errors.New("Your query returned more than one result. Use `pool_id` or other attributes to filter the result down to a single network")
}

net := obj.(Network)
return &net, nil
}

func (c *Client) GetNetworks() ([]Network, error) {
var response map[string]Network
err := c.GetAllObjectsOfType(Network{}, &response)

nets := make([]Network, 0, len(response))
for _, net := range response {
nets = append(nets, net)
}
return nets, err
}

func (c *Client) DeleteNetwork(id string) error {
var success bool
params := map[string]interface{}{
"id": id,
}

ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
err := c.Call(ctx, "network.delete", params, &success)

return err
}

func RemoveNetworksWithNamePrefix(prefix string) func(string) error {
return func(_ string) error {
c, err := NewClient(GetConfigFromEnv())
if err != nil {
return fmt.Errorf("error getting client: %s", err)
}

nets, err := c.GetNetworks()

if err != nil {
return err
}

for _, net := range nets {
if strings.HasPrefix(net.NameLabel, prefix) {
log.Printf("[DEBUG] Deleting network: %v\n", net)
err = c.DeleteNetwork(net.Id)

if err != nil {
log.Printf("error destroying network `%s` during sweep: %v", net.NameLabel, err)
}
}
}
return nil
}
}
86 changes: 86 additions & 0 deletions client/network_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package client

import "testing"

var testNetworkName string = integrationTestPrefix + "network"

func TestNetworkCompare(t *testing.T) {

nameLabel := "network label"
poolId := "pool id"
cases := []struct {
net Network
result bool
obj map[string]interface{}
}{
{
net: Network{
NameLabel: nameLabel,
},
result: true,
obj: map[string]interface{}{
"id": "355ee47d-ff4c-4924-3db2-fd86ae629676",
"name_label": nameLabel,
"$poolId": "355ee47d-ff4c-4924-3db2-fd86ae629676",
},
},
{
net: Network{
NameLabel: nameLabel,
PoolId: poolId,
},
result: false,
obj: map[string]interface{}{
"id": "355ee47d-ff4c-4924-3db2-fd86ae629676",
"name_label": nameLabel,
"$poolId": "355ee47d-ff4c-4924-3db2-fd86ae629676",
},
},
{
net: Network{
NameLabel: nameLabel,
PoolId: poolId,
},
result: true,
obj: map[string]interface{}{
"id": "355ee47d-ff4c-4924-3db2-fd86ae629676",
"name_label": nameLabel,
"$poolId": poolId,
},
},
}

for _, test := range cases {
net := test.net
obj := test.obj
result := test.result

if net.Compare(obj) != result {
t.Errorf("expected network `%+v` to Compare '%t' with object `%v`", net, result, obj)
}
}
}

func TestGetNetwork(t *testing.T) {
c, err := NewClient(GetConfigFromEnv())

if err != nil {
t.Fatalf("failed to create client with error: %v", err)
}

net, err := c.GetNetwork(Network{
NameLabel: testNetworkName,
})

if err != nil {
t.Fatalf("failed to retrieve network `%s` with error: %v", testNetworkName, err)
}

if net == nil {
t.Fatalf("should have received network, instead received nil")
}

if net.NameLabel != testNetworkName {
t.Errorf("expected network name_label `%s` to match `%s`", net.NameLabel, testNetworkName)
}
}
22 changes: 22 additions & 0 deletions client/pool.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package client

import (
"fmt"
"os"
)

type Pool struct {
Id string
NameLabel string
Expand Down Expand Up @@ -50,3 +55,20 @@ func (c *Client) GetPoolByName(name string) (Pool, error) {

return pool, nil
}

func FindPoolForTests(pool *Pool) {
poolName, found := os.LookupEnv("XOA_POOL")

if !found {
fmt.Println("The XOA_POOL environment variable must be set")
os.Exit(-1)
}
c, _ := NewClient(GetConfigFromEnv())
var err error
*pool, err = c.GetPoolByName(poolName)

if err != nil {
fmt.Printf("failed to find a pool with name: %v with error: %v\n", poolName, err)
os.Exit(-1)
}
}
34 changes: 34 additions & 0 deletions client/setup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package client

import (
"os"
"testing"
)

func CreateNetwork() error {

c, err := NewClient(GetConfigFromEnv())

if err != nil {
return err
}

_, err = c.CreateNetwork(Network{
NameLabel: testNetworkName,
PoolId: accTestPool.Id,
})
return err
}

var integrationTestPrefix string = "xenorchestra-client-"
var accTestPool Pool

func TestMain(m *testing.M) {
FindPoolForTests(&accTestPool)
CreateNetwork()
code := m.Run()

RemoveNetworksWithNamePrefix(integrationTestPrefix)("")

os.Exit(code)
}
Loading

0 comments on commit 7838056

Please sign in to comment.