diff --git a/Makefile b/Makefile index d4b82151..8116c142 100644 --- a/Makefile +++ b/Makefile @@ -19,4 +19,4 @@ apply: terraform apply testacc: - TF_ACC=1 go test $(TEST) -v + TF_ACC=1 go test $(TEST) -v -timeout 20m diff --git a/client/client.go b/client/client.go index a494ec1d..2f209278 100644 --- a/client/client.go +++ b/client/client.go @@ -2,11 +2,13 @@ package client import ( "context" + "encoding/json" "errors" "fmt" "log" "net/http" "os" + "reflect" "time" gorillawebsocket "github.com/gorilla/websocket" @@ -87,8 +89,15 @@ 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) + err := c.rpc.Call(ctx, method, params, result, opt...) + var callRes interface{} + t := reflect.TypeOf(result) + if t == nil || t.Kind() != reflect.Ptr { + callRes = result + } else { + callRes = reflect.ValueOf(result).Elem() + } + log.Printf("[TRACE] Made rpc call `%s` with params: %v and received %+v: result with error: %v\n", method, params, callRes, err) if err != nil { rpcErr, ok := err.(*jsonrpc2.Error) @@ -109,8 +118,7 @@ func (c *Client) Call(ctx context.Context, method string, params, result interfa } type XoObject interface { - Compare(obj map[string]interface{}) bool - New(obj map[string]interface{}) XoObject + Compare(obj interface{}) bool } func (c *Client) GetAllObjectsOfType(obj XoObject, response interface{}) error { @@ -139,7 +147,7 @@ func (c *Client) GetAllObjectsOfType(obj XoObject, response interface{}) error { }, } ctx, _ := context.WithTimeout(context.Background(), 100*time.Second) - return c.Call(ctx, "xo.getAllObjects", params, &response) + return c.Call(ctx, "xo.getAllObjects", params, response) } func (c *Client) FindFromGetAllObjects(obj XoObject) (interface{}, error) { @@ -152,29 +160,35 @@ func (c *Client) FindFromGetAllObjects(obj XoObject) (interface{}, error) { } found := false - objs := make([]interface{}, 0) + t := reflect.TypeOf(obj) + value := reflect.New(t) + objs := reflect.MakeSlice(reflect.SliceOf(t), 0, 0) for _, resObj := range objsRes.Objects { v, ok := resObj.(map[string]interface{}) if !ok { return obj, errors.New("Could not coerce interface{} into map") } + b, err := json.Marshal(v) - if obj.Compare(v) { + if err != nil { + return objs, err + } + err = json.Unmarshal(b, value.Interface()) + if err != nil { + return objs, err + } + if obj.Compare(value.Elem().Interface()) { found = true - objs = append(objs, obj.New(v)) + objs = reflect.Append(objs, value.Elem()) } } if !found { - return obj, NotFound{Query: obj} + return objs, NotFound{Query: obj} } log.Printf("[TRACE] Found the following objects from xo.getAllObjects: %+v\n", objs) - if len(objs) == 1 { - - return objs[0], nil - } - return objs, nil + return objs.Interface(), nil } type handler struct{} diff --git a/client/network.go b/client/network.go index a2279bd8..ebe12798 100644 --- a/client/network.go +++ b/client/network.go @@ -12,45 +12,30 @@ import ( type Network struct { Id string `json:"id"` NameLabel string `json:"name_label"` - Bridge string - PoolId string + Bridge string `json:"bridge"` + PoolId string `json:"$poolId"` } -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 { +func (net Network) Compare(obj interface{}) bool { + otherNet := obj.(Network) + if net.Id == otherNet.Id { return true } labelsMatch := false - if net.NameLabel == nameLabel { + if net.NameLabel == otherNet.NameLabel { labelsMatch = true } if net.PoolId == "" && labelsMatch { return true - } else if net.PoolId == poolId && labelsMatch { + } else if net.PoolId == otherNet.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{}{ @@ -74,12 +59,13 @@ func (c *Client) GetNetwork(netReq Network) (*Network, error) { return nil, err } - if _, ok := obj.([]interface{}); ok { + nets := obj.([]Network) + + if len(nets) > 1 { 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 + return &nets[0], nil } func (c *Client) GetNetworks() ([]Network, error) { diff --git a/client/network_test.go b/client/network_test.go index 8227b0d1..9ba449e7 100644 --- a/client/network_test.go +++ b/client/network_test.go @@ -11,17 +11,17 @@ func TestNetworkCompare(t *testing.T) { cases := []struct { net Network result bool - obj map[string]interface{} + other Network }{ { 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", + other: Network{ + Id: "355ee47d-ff4c-4924-3db2-fd86ae629676", + NameLabel: nameLabel, + PoolId: "355ee47d-ff4c-4924-3db2-fd86ae629676", }, }, { @@ -30,10 +30,10 @@ func TestNetworkCompare(t *testing.T) { PoolId: poolId, }, result: false, - obj: map[string]interface{}{ - "id": "355ee47d-ff4c-4924-3db2-fd86ae629676", - "name_label": nameLabel, - "$poolId": "355ee47d-ff4c-4924-3db2-fd86ae629676", + other: Network{ + Id: "355ee47d-ff4c-4924-3db2-fd86ae629676", + NameLabel: "name_label", + PoolId: "355ee47d-ff4c-4924-3db2-fd86ae629676", }, }, { @@ -42,21 +42,21 @@ func TestNetworkCompare(t *testing.T) { PoolId: poolId, }, result: true, - obj: map[string]interface{}{ - "id": "355ee47d-ff4c-4924-3db2-fd86ae629676", - "name_label": nameLabel, - "$poolId": poolId, + other: Network{ + Id: "355ee47d-ff4c-4924-3db2-fd86ae629676", + NameLabel: nameLabel, + PoolId: poolId, }, }, } for _, test := range cases { net := test.net - obj := test.obj + other := test.other result := test.result - if net.Compare(obj) != result { - t.Errorf("expected network `%+v` to Compare '%t' with object `%v`", net, result, obj) + if net.Compare(other) != result { + t.Errorf("expected network `%+v` to Compare '%t' with other `%v`", net, result, other) } } } @@ -83,4 +83,12 @@ func TestGetNetwork(t *testing.T) { if net.NameLabel != testNetworkName { t.Errorf("expected network name_label `%s` to match `%s`", net.NameLabel, testNetworkName) } + + if net.Bridge == "" { + t.Errorf("expected network bridge to not be an empty string") + } + + if net.PoolId == "" { + t.Errorf("expected network pool id to not be an empty string") + } } diff --git a/client/pif.go b/client/pif.go index d9e0a079..2c6c5099 100644 --- a/client/pif.go +++ b/client/pif.go @@ -1,53 +1,60 @@ package client +import ( + "errors" +) + type PIF struct { - Device string - Host string - Network string - Id string - Uuid string - PoolId string - Attached bool - Vlan int + Device string `json:"device"` + Host string `json:"$host"` + Network string `json:"$network"` + Id string `json:"id"` + Uuid string `json:"uuid"` + PoolId string `json:"$poolId"` + Attached bool `json:"attached"` + Vlan int `json:"vlan"` } -func (p PIF) Compare(obj map[string]interface{}) bool { - device := obj["device"].(string) - vlan := int(obj["vlan"].(float64)) - if p.Vlan == vlan && p.Device == device { +func (p PIF) Compare(obj interface{}) bool { + otherPif := obj.(PIF) + + hostIdExists := p.Host != "" + if hostIdExists && p.Host != otherPif.Host { + return false + } + + if p.Vlan == otherPif.Vlan && p.Device == otherPif.Device { return true } return false } -func (p PIF) New(obj map[string]interface{}) XoObject { - id := obj["id"].(string) - device := obj["device"].(string) - attached := obj["attached"].(bool) - network := obj["$network"].(string) - uuid := obj["uuid"].(string) - poolId := obj["$poolId"].(string) - host := obj["$host"].(string) - vlan := int(obj["vlan"].(float64)) - return PIF{ - Device: device, - Host: host, - Network: network, - Id: id, - Uuid: uuid, - PoolId: poolId, - Attached: attached, - Vlan: vlan, +func (c *Client) GetPIFByDevice(dev string, vlan int) ([]PIF, error) { + obj, err := c.FindFromGetAllObjects(PIF{Device: dev, Vlan: vlan}) + + if err != nil { + return []PIF{}, err } + pifs, ok := obj.([]PIF) + + if !ok { + return pifs, errors.New("failed to coerce response into PIF slice") + } + + return pifs, nil } -func (c *Client) GetPIFByDevice(dev string, vlan int) (PIF, error) { - obj, err := c.FindFromGetAllObjects(PIF{Device: dev, Vlan: vlan}) - pif := obj.(PIF) +func (c *Client) GetPIF(pifReq PIF) (pifs []PIF, err error) { + obj, err := c.FindFromGetAllObjects(pifReq) if err != nil { - return pif, err + return + } + pifs, ok := obj.([]PIF) + + if !ok { + return pifs, errors.New("failed to coerce response into PIF slice") } - return pif, nil + return pifs, nil } diff --git a/client/pif_test.go b/client/pif_test.go index 4159b630..77609a7c 100644 --- a/client/pif_test.go +++ b/client/pif_test.go @@ -1,6 +1,8 @@ package client -import "testing" +import ( + "testing" +) func TestGetPIFByDevice(t *testing.T) { c, err := NewClient(GetConfigFromEnv()) @@ -11,12 +13,14 @@ func TestGetPIFByDevice(t *testing.T) { device := "eth0" vlan_id := -1 - pif, err := c.GetPIFByDevice(device, vlan_id) + pifs, err := c.GetPIFByDevice(device, vlan_id) if err != nil { - t.Errorf("failed to find PIF with device: %s with error: %v", device, err) + t.Fatalf("failed to find PIF with device: %s with error: %v", device, err) } + pif := pifs[0] + if pif.Device != device { t.Errorf("PIF's device %s should have matched %s", pif.Device, device) } diff --git a/client/pool.go b/client/pool.go index 0f659362..cc3e295c 100644 --- a/client/pool.go +++ b/client/pool.go @@ -6,54 +6,36 @@ import ( ) type Pool struct { - Id string - NameLabel string - Description string - Cpus struct { - Cores int64 - Sockets int64 - } + Id string `json:"id"` + NameLabel string `json:"name_label"` + Description string `json:"name_description"` + Cpus CpuInfo `json:"cpus"` + DefaultSR string `json:"default_SR"` + Master string `json:"master"` +} + +type CpuInfo struct { + Cores int64 `json:"cores,float64` + Sockets int64 `json:"sockets,float64` } -func (p Pool) Compare(obj map[string]interface{}) bool { - nameLabel := obj["name_label"].(string) +func (p Pool) Compare(obj interface{}) bool { + otherPool := obj.(Pool) - if nameLabel != p.NameLabel { + if otherPool.NameLabel != p.NameLabel { return false } return true } -func (p Pool) New(obj map[string]interface{}) XoObject { - nameLabel := obj["name_label"].(string) - id := obj["id"].(string) - description := obj["name_description"].(string) - cpus := obj["cpus"].(map[string]interface{}) - cores := cpus["cores"].(float64) - sockets := cpus["sockets"].(float64) - return Pool{ - Id: id, - NameLabel: nameLabel, - Description: description, - Cpus: struct { - Cores int64 - Sockets int64 - }{ - Cores: int64(cores), - Sockets: int64(sockets), - }, - } -} - -func (c *Client) GetPoolByName(name string) (Pool, error) { +func (c *Client) GetPoolByName(name string) (pools []Pool, err error) { obj, err := c.FindFromGetAllObjects(Pool{NameLabel: name}) - pool := obj.(Pool) - if err != nil { - return pool, err + return } + pools = obj.([]Pool) - return pool, nil + return pools, nil } func FindPoolForTests(pool *Pool) { @@ -65,10 +47,17 @@ func FindPoolForTests(pool *Pool) { } c, _ := NewClient(GetConfigFromEnv()) var err error - *pool, err = c.GetPoolByName(poolName) + pools, 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) } + + if len(pools) != 1 { + fmt.Printf("Found %d pools with name_label %s. Please use a label that is unique so tests are reproducible.\n", len(pools), poolName) + os.Exit(-1) + } + + *pool = pools[0] } diff --git a/client/pool_test.go b/client/pool_test.go index 87a3c4af..2e44036b 100644 --- a/client/pool_test.go +++ b/client/pool_test.go @@ -4,22 +4,22 @@ import "testing" func TestPoolCompare(t *testing.T) { tests := []struct { - object map[string]interface{} + other Pool pool Pool result bool }{ { - object: map[string]interface{}{ - "name_label": "xenserver-ddelnano", - "$poolId": "Sample pool id", + other: Pool{ + Id: "sample pool id", + NameLabel: "xenserver-ddelnano", }, pool: Pool{NameLabel: "xenserver-ddelnano"}, result: true, }, { - object: map[string]interface{}{ - "name_label": "Does not match", - "$poolId": "Sample pool id", + other: Pool{ + Id: "sample pool id", + NameLabel: "does not match", }, pool: Pool{NameLabel: "xenserver-ddelnano"}, result: false, @@ -28,10 +28,10 @@ func TestPoolCompare(t *testing.T) { for _, test := range tests { pool := test.pool - object := test.object + other := test.other result := test.result - if pool.Compare(object) != result { - t.Errorf("Expected Pool %v to Compare %t to %v", pool, result, object) + if pool.Compare(other) != result { + t.Errorf("Expected Pool %v to Compare %t to %v", pool, result, other) } } } @@ -43,9 +43,10 @@ func TestGetPoolByName(t *testing.T) { t.Errorf("failed to create client with error: %v", err) } - nameLabel := "xenserver-ddelnano" + nameLabel := accTestPool.NameLabel + pools, err := c.GetPoolByName(nameLabel) - pool, err := c.GetPoolByName(nameLabel) + pool := pools[0] if err != nil { t.Errorf("failed to get pool with error: %v", err) @@ -54,4 +55,12 @@ func TestGetPoolByName(t *testing.T) { if pool.NameLabel != nameLabel { t.Errorf("expected pool to have name `%s` received `%s` instead.", nameLabel, pool.NameLabel) } + + if pool.Cpus.Cores == 0 { + t.Errorf("expected pool cpu cores to be set") + } + + if pool.Cpus.Sockets == 0 { + t.Errorf("expected pool cpu sockets to be set") + } } diff --git a/client/setup_test.go b/client/setup_test.go index d3d1d86b..7477783e 100644 --- a/client/setup_test.go +++ b/client/setup_test.go @@ -1,6 +1,7 @@ package client import ( + "fmt" "os" "testing" ) @@ -31,12 +32,20 @@ func CreateNetwork() error { var integrationTestPrefix string = "xenorchestra-client-" var accTestPool Pool +var testTemplateName string func TestMain(m *testing.M) { FindPoolForTests(&accTestPool) CreateNetwork() CreateResourceSet(testResourceSet) + var found bool + testTemplateName, found = os.LookupEnv("XOA_TEMPLATE") + if !found { + fmt.Println("The XOA_TEMPLATE environment variable must be set for the tests") + os.Exit(-1) + } + code := m.Run() RemoveResourceSetsWithNamePrefix(integrationTestPrefix)("") diff --git a/client/storage_repository.go b/client/storage_repository.go index a468377a..33eb5866 100644 --- a/client/storage_repository.go +++ b/client/storage_repository.go @@ -1,53 +1,109 @@ package client +import ( + "errors" + "fmt" + "os" +) + type StorageRepository struct { - Id string - Uuid string - NameLabel string - PoolId string - SRType string + Id string `json:"id"` + Uuid string `json:"uuid"` + NameLabel string `json:"name_label"` + PoolId string `json:"$poolId"` + SRType string `json:"SR_type"` + Tags []string `json:"tags,omitempty"` } -func (s StorageRepository) Compare(obj map[string]interface{}) bool { - nameLabel := obj["name_label"].(string) - poolId := obj["$poolId"].(string) - if s.NameLabel != nameLabel { - return false - } +func (s StorageRepository) Compare(obj interface{}) bool { + otherSr := obj.(StorageRepository) - if s.PoolId == "" { + if s.Id != "" && s.Id == otherSr.Id { return true } - if s.PoolId == poolId { + if len(s.Tags) > 0 { + for _, tag := range s.Tags { + if !stringInSlice(tag, otherSr.Tags) { + return false + } + } + } + + labelsMatch := false + if s.NameLabel == otherSr.NameLabel { + labelsMatch = true + } + + if s.PoolId == "" && labelsMatch { + return true + } else if s.PoolId == otherSr.PoolId && labelsMatch { return true } return false } -func (s StorageRepository) New(obj map[string]interface{}) XoObject { - id := obj["id"].(string) - srType := obj["SR_type"].(string) - poolId := obj["$poolId"].(string) - nameLabel := obj["name_label"].(string) - uuid := obj["uuid"].(string) - return StorageRepository{ - Id: id, - NameLabel: nameLabel, - PoolId: poolId, - SRType: srType, - Uuid: uuid, +func stringInSlice(needle string, haystack []string) bool { + for _, s := range haystack { + if s == needle { + return true + } } + return false } -func (c *Client) GetStorageRepository(sr StorageRepository) (StorageRepository, error) { - obj, err := c.FindFromGetAllObjects(sr) - sr = obj.(StorageRepository) +func (c *Client) GetStorageRepositoryById(id string) (StorageRepository, error) { + obj, err := c.FindFromGetAllObjects(StorageRepository{Id: id}) + var sr StorageRepository if err != nil { return sr, err } + srs, ok := obj.([]StorageRepository) + + if !ok { + return sr, errors.New("failed to coerce response into StorageRepository slice") + } + + if len(srs) != 1 { + return sr, errors.New(fmt.Sprintf("expected a single storage respository to be returned, instead received: %d in the response: %v", len(srs), obj)) + } + + return srs[0], nil +} + +func (c *Client) GetStorageRepository(sr StorageRepository) ([]StorageRepository, error) { + obj, err := c.FindFromGetAllObjects(sr) + + if err != nil { + return nil, err + } + srs, ok := obj.([]StorageRepository) + + if !ok { + return nil, errors.New("failed to coerce response into StorageRepository slice") + } + + return srs, nil +} + +func FindStorageRepositoryForTests(pool Pool, sr *StorageRepository, tag string) { + c, _ := NewClient(GetConfigFromEnv()) + var err error + defaultSr, err := c.GetStorageRepositoryById(pool.DefaultSR) + + if err != nil { + fmt.Printf("failed to find the default storage repository with id: %s with error: %v\n", pool.DefaultSR, err) + os.Exit(-1) + } - return sr, nil + *sr = defaultSr + + err = c.AddTag(defaultSr.Id, tag) + + if err != nil { + fmt.Printf("failed to set tag on default storage repository with id: %s with error: %v\n", pool.DefaultSR, err) + os.Exit(-1) + } } diff --git a/client/storage_repository_test.go b/client/storage_repository_test.go index 47a2234b..442b0e49 100644 --- a/client/storage_repository_test.go +++ b/client/storage_repository_test.go @@ -6,63 +6,78 @@ import ( func TestStorageRepositoryCompare(t *testing.T) { tests := []struct { - object map[string]interface{} + other StorageRepository sr StorageRepository result bool }{ { - object: map[string]interface{}{ - "name_label": "Test", - "$poolId": "Sample pool id", + other: StorageRepository{ + NameLabel: "Test", + PoolId: "Sample pool id", }, sr: StorageRepository{NameLabel: "Test"}, result: true, }, { - object: map[string]interface{}{ - "name_label": "Test", - "$poolId": "Pool A", + other: StorageRepository{ + NameLabel: "Test", + PoolId: "Pool A", }, sr: StorageRepository{NameLabel: "Test", PoolId: "Pool A"}, result: true, }, { - object: map[string]interface{}{ - "name_label": "Test", - "$poolId": "Pool A", + other: StorageRepository{ + NameLabel: "Test", + PoolId: "does not match", }, sr: StorageRepository{NameLabel: "Test", PoolId: "Pool A"}, - result: true, + result: false, + }, + { + other: StorageRepository{ + NameLabel: "Test", + PoolId: "Pool A", + }, + sr: StorageRepository{ + NameLabel: "Test", + PoolId: "Pool A", + Tags: []string{"tag1"}, + }, + result: false, }, } for _, test := range tests { sr := test.sr - object := test.object + other := test.other result := test.result - if sr.Compare(object) != result { - t.Errorf("Expected Storage Repository %v to Compare %t to %v", sr, result, object) + if sr.Compare(other) != result { + t.Errorf("Expected Storage Repository %v to Compare %t to %v", sr, result, other) } } } -func TestGetStorageRepositoryByType(t *testing.T) { +func TestGetStorageRepositoryByNameLabel(t *testing.T) { c, err := NewClient(GetConfigFromEnv()) if err != nil { - t.Errorf("failed to create client with error: %v", err) + t.Fatalf("failed to create client with error: %v", err) } - expectedNameLabel := "XenServer Tools" - sr := StorageRepository{ - NameLabel: expectedNameLabel, + defaultSr, err := c.GetStorageRepositoryById(accTestPool.DefaultSR) + + if err != nil { + t.Fatalf("failed to retrieve storage repository by id with error: %v", err) } - sr, err = c.GetStorageRepository(sr) + + srs, err := c.GetStorageRepository(StorageRepository{NameLabel: defaultSr.NameLabel}) if err != nil { - t.Errorf("failed to get storage repository with error: %v", err) + t.Fatalf("failed to get storage repository by name label with error: %v", err) } - if sr.NameLabel != expectedNameLabel { - t.Errorf("expected storage repository to have name `%s` received `%s` instead.", expectedNameLabel, sr.NameLabel) + sr := srs[0] + if sr.NameLabel != defaultSr.NameLabel { + t.Errorf("expected storage repository to have name `%s` received `%s` instead.", defaultSr.NameLabel, sr.NameLabel) } } diff --git a/client/tag.go b/client/tag.go new file mode 100644 index 00000000..32cd5a6e --- /dev/null +++ b/client/tag.go @@ -0,0 +1,91 @@ +package client + +import ( + "context" + "errors" + "fmt" + "log" + "time" +) + +func (c *Client) AddTag(id, tag string) error { + var success bool + params := map[string]interface{}{ + "id": id, + "tag": tag, + } + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + err := c.Call(ctx, "tag.add", params, &success) + + if err != nil { + return err + } + return nil +} + +func (c *Client) RemoveTag(id, tag string) error { + var success bool + params := map[string]interface{}{ + "id": id, + "tag": tag, + } + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + err := c.Call(ctx, "tag.remove", params, &success) + + if err != nil { + return err + } + return nil +} + +func (c *Client) GetObjectsWithTags(tags []string) ([]string, error) { + var objsRes struct { + Objects map[string]interface{} `json:"-"` + } + params := map[string]interface{}{ + "filter": map[string][]string{ + "tags": tags, + }, + } + ctx, _ := context.WithTimeout(context.Background(), 100*time.Second) + c.Call(ctx, "xo.getAllObjects", params, &objsRes.Objects) + log.Printf("[DEBUG] Found objects with tags `%s`: %v\n", tags, objsRes) + + t := []string{} + for _, resObject := range objsRes.Objects { + obj, ok := resObject.(map[string]interface{}) + + if !ok { + return t, errors.New("Could not coerce interface{} into map") + } + + id := obj["id"].(string) + t = append(t, id) + } + return t, nil +} + +func RemoveTagFromAllObjects(tag string) func(string) error { + return func(_ string) error { + c, err := NewClient(GetConfigFromEnv()) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + + objects, err := c.GetObjectsWithTags([]string{tag}) + + if err != nil { + return err + } + + for _, object := range objects { + log.Printf("[DEBUG] Remove tag `%s` on object `%s`\n", tag, object) + err = c.RemoveTag(object, tag) + + if err != nil { + log.Printf("error remove tag `%s` during sweep: %v", tag, err) + } + } + return nil + } +} diff --git a/client/template.go b/client/template.go index ff83d76a..2ca698c8 100644 --- a/client/template.go +++ b/client/template.go @@ -1,38 +1,44 @@ package client +import ( + "errors" +) + type Template struct { - // TODO: Not sure the difference between these two - Id string - Uuid string - NameLabel string + Id string `json:"id"` + Uuid string `json:"uuid"` + NameLabel string `json:"name_label"` + PoolId string `json:"$poolId"` } -func (t Template) Compare(obj map[string]interface{}) bool { - name_label := obj["name_label"].(string) - if t.NameLabel == name_label { +func (t Template) Compare(obj interface{}) bool { + other := obj.(Template) + + labelsMatch := false + if t.NameLabel == other.NameLabel { + labelsMatch = true + } + + if t.PoolId == "" && labelsMatch { + return true + } else if t.PoolId == other.PoolId && labelsMatch { return true } return false } -func (p Template) New(obj map[string]interface{}) XoObject { - id := obj["id"].(string) - uuid := obj["uuid"].(string) - name_label := obj["name_label"].(string) - return Template{ - Id: id, - NameLabel: name_label, - Uuid: uuid, +func (c *Client) GetTemplate(template Template) ([]Template, error) { + obj, err := c.FindFromGetAllObjects(template) + var templates []Template + if err != nil { + return templates, err } -} -func (c *Client) GetTemplate(name string) (Template, error) { - obj, err := c.FindFromGetAllObjects(Template{NameLabel: name}) - template := obj.(Template) + templates, ok := obj.([]Template) - if err != nil { - return template, err + if !ok { + return templates, errors.New("failed to coerce response into Template slice") } - return template, nil + return templates, nil } diff --git a/client/template_test.go b/client/template_test.go index d5858dfc..385bb5ab 100644 --- a/client/template_test.go +++ b/client/template_test.go @@ -5,17 +5,15 @@ import ( ) func TestGetTemplate(t *testing.T) { - // TODO: Export this function so it can be used by the client tests - // xoa.testAccPreCheck(t) tests := []struct { templateName string template Template err error }{ { - templateName: "Asianux Server 4 (64-bit)", + templateName: testTemplateName, template: Template{ - NameLabel: "Asianux Server 4 (64-bit)", + NameLabel: testTemplateName, }, err: nil, }, @@ -35,12 +33,20 @@ func TestGetTemplate(t *testing.T) { for _, test := range tests { templateName := test.templateName - tmp, err := c.GetTemplate(templateName) + templates, err := c.GetTemplate(Template{NameLabel: templateName}) if test.err != err { - t.Errorf("failed to get template `%s` expected err: %v received: %v", templateName, test.err, err) + t.Fatalf("failed to get template `%s` expected err: %v received: %v", templateName, test.err, err) } + if _, ok := test.err.(NotFound); ok { + continue + } + + if len(templates) < 1 { + t.Fatalf("failed to find templates for the following name_label: %s", templateName) + } + tmp := templates[0] if test.template.NameLabel != "" && tmp.NameLabel != templateName { t.Errorf("template returned from xoa does not match. expected %s, found %s", tmp.NameLabel, templateName) } diff --git a/client/vif.go b/client/vif.go index 88339065..bba55ce5 100644 --- a/client/vif.go +++ b/client/vif.go @@ -2,49 +2,32 @@ package client import ( "context" + "errors" + "fmt" "log" "time" ) type VIF struct { - Id string - Attached bool - Network string - Device string - MacAddress string - VmId string + Id string `json:"id"` + Attached bool `json:"attached"` + Network string `json:"$network"` + Device string `json:"device"` + MacAddress string `json:"MAC"` + VmId string `json:"$VM"` } -func (v VIF) New(obj map[string]interface{}) XoObject { - id := obj["id"].(string) - attached := obj["attached"].(bool) - vmId := obj["$VM"].(string) - device := obj["device"].(string) - networkId := obj["$network"].(string) - macAddress := obj["MAC"].(string) - return VIF{ - Id: id, - Attached: attached, - Device: device, - MacAddress: macAddress, - Network: networkId, - VmId: vmId, - } -} - -func (v VIF) Compare(obj map[string]interface{}) bool { - id := obj["id"].(string) - if v.Id == id { +func (v VIF) Compare(obj interface{}) bool { + other := obj.(VIF) + if v.Id == other.Id { return true } - macAddress := obj["MAC"].(string) - if v.MacAddress == macAddress { + if v.MacAddress == other.MacAddress { return true } - vmId := obj["$VM"].(string) - if v.VmId == vmId { + if v.VmId == other.VmId { return true } return false @@ -56,16 +39,11 @@ func (c *Client) GetVIFs(vm *Vm) ([]VIF, error) { if err != nil { return nil, err } - - if vif, ok := obj.(VIF); ok { - return []VIF{vif}, nil + vifs, ok := obj.([]VIF) + if !ok { + return []VIF{}, errors.New("failed to coerce response into VIF slice") } - objs := obj.([]interface{}) - var vifs []VIF - for _, vif := range objs { - vifs = append(vifs, vif.(VIF)) - } return vifs, nil } @@ -80,8 +58,12 @@ func (c *Client) GetVIF(vifReq *VIF) (*VIF, error) { return nil, err } - vif := obj.(VIF) - return &vif, nil + vifs := obj.([]VIF) + + if len(vifs) > 1 { + return nil, errors.New(fmt.Sprintf("recieved %d VIFs but was expecting a single VIF to be returned", len(vifs))) + } + return &vifs[0], nil } func (c *Client) CreateVIF(vm *Vm, vif *VIF) (*VIF, error) { diff --git a/client/vif_test.go b/client/vif_test.go index b78e0eca..08f27923 100644 --- a/client/vif_test.go +++ b/client/vif_test.go @@ -1,6 +1,7 @@ package client import ( + "os" "testing" ) @@ -12,14 +13,9 @@ func TestGetVIFs(t *testing.T) { t.Fatalf("failed to create client with error: %v", err) } - vmName := "XOA" - vm, err := c.GetVm(Vm{NameLabel: vmName}) - - if err != nil { - t.Fatalf("failed to get VM with error: %v", err) - } + vm := findVmForTest(c, t) - vifs, err := c.GetVIFs(vm) + vifs, err := c.GetVIFs(&vm) for _, vif := range vifs { if vif.Device == "" { @@ -42,9 +38,9 @@ func TestGetVIFs(t *testing.T) { t.Errorf("expecting `Device` field to be set on VIF instead received: %s", vif.Device) } - if !vif.Attached { - t.Errorf("expecting `Attached` field to be true on VIF instead received: %t", vif.Attached) - } + // if !vif.Attached { + // t.Errorf("expecting `Attached` field to be true on VIF instead received: %t", vif.Attached) + // } } } @@ -56,14 +52,9 @@ func TestGetVIF(t *testing.T) { t.Fatalf("failed to create client with error: %v", err) } - vmName := "XOA" - vm, err := c.GetVm(Vm{NameLabel: vmName}) - - if err != nil { - t.Fatalf("failed to get VM with error: %v", err) - } + vm := findVmForTest(c, t) - vifs, err := c.GetVIFs(vm) + vifs, err := c.GetVIFs(&vm) expectedVIF := vifs[0] @@ -80,7 +71,25 @@ func TestGetVIF(t *testing.T) { } } +func findVmForTest(c *Client, t *testing.T) Vm { + + vms, err := c.GetVms() + + if err != nil { + t.Fatalf("failed to get VMs with error: %v", err) + } + + if len(vms) < 1 { + t.Fatalf("request returned %d vm results, expected atleast 1", len(vms)) + } + return vms[0] +} + func TestCreateVIF_DeleteVIF(t *testing.T) { + if p := os.Getenv("XOA_POOL"); p != "xenserver-ddelnano" { + t.Skip("This test isn't safe to run outside of my personal deployment yet!") + } + c, err := NewClient(GetConfigFromEnv()) if err != nil { @@ -94,12 +103,14 @@ func TestCreateVIF_DeleteVIF(t *testing.T) { t.Fatalf("failed to get VM with error: %v", err) } - pif, err := c.GetPIFByDevice("eth1", -1) + pifs, err := c.GetPIFByDevice("eth1", -1) if err != nil { t.Fatalf("failed to get PIF with error: %v", err) } + pif := pifs[0] + vif, err := c.CreateVIF(vm, &VIF{Network: pif.Network}) if err != nil { diff --git a/client/vm.go b/client/vm.go index 5aa663fa..f7089989 100644 --- a/client/vm.go +++ b/client/vm.go @@ -2,7 +2,7 @@ package client import ( "context" - "encoding/json" + "errors" "fmt" "log" "time" @@ -42,8 +42,8 @@ type Vm struct { ResourceSet string `json:"resourceSet,omitempty"` } -func (v Vm) Compare(obj map[string]interface{}) bool { - other := v.New(obj).(Vm) +func (v Vm) Compare(obj interface{}) bool { + other := obj.(Vm) if v.Id != "" && v.Id == other.Id { return true } @@ -55,24 +55,6 @@ func (v Vm) Compare(obj map[string]interface{}) bool { return false } -// TODO: Decide if a refactored version of this would work better -// than the existing XoObject pattern. -func (v Vm) New(obj map[string]interface{}) XoObject { - var vm Vm - - m, err := json.Marshal(obj) - - if err != nil { - panic(err) - } - - err = json.Unmarshal(m, &vm) - if err != nil { - panic(err) - } - return vm -} - type VDI struct { SrId string NameLabel string @@ -158,7 +140,7 @@ func (c *Client) UpdateVm(id string, cpus int, nameLabel, nameDescription, ha, r log.Printf("[DEBUG] VM params for vm.set: %#v", params) ctx, _ := context.WithTimeout(context.Background(), 10*time.Minute) var success bool - err := c.rpc.Call(ctx, "vm.set", params, &success) + err := c.Call(ctx, "vm.set", params, &success) if err != nil { return nil, err @@ -166,7 +148,7 @@ func (c *Client) UpdateVm(id string, cpus int, nameLabel, nameDescription, ha, r // TODO: This is a poor way to ensure that terraform will see the updated // attributes after calling vm.set. Need to investigate a better way to detect this. - time.Sleep(15 * time.Second) + time.Sleep(25 * time.Second) return c.GetVm(Vm{Id: id}) } @@ -188,14 +170,35 @@ func (c *Client) DeleteVm(id string) error { func (c *Client) GetVm(vmReq Vm) (*Vm, error) { obj, err := c.FindFromGetAllObjects(vmReq) - vm := obj.(Vm) if err != nil { - return &vm, err + return nil, err } + vms := obj.([]Vm) - log.Printf("[DEBUG] Found vm: %+v", vm) - return &vm, nil + if len(vms) != 1 { + return nil, errors.New(fmt.Sprintf("expected to find a single VM from request %+v, instead found %d", vmReq, len(vms))) + } + + log.Printf("[DEBUG] Found vm: %+v", vms[0]) + return &vms[0], nil +} + +func (c *Client) GetVms() ([]Vm, error) { + var response map[string]Vm + err := c.GetAllObjectsOfType(Vm{PowerState: "Running"}, &response) + + if err != nil { + return []Vm{}, err + } + + vms := make([]Vm, 0, len(response)) + for _, vm := range response { + vms = append(vms, vm) + } + + log.Printf("[DEBUG] Found vms: %+v", vms) + return vms, nil } func (c *Client) waitForModifyVm(id string, timeout time.Duration) error { diff --git a/docs/data-sources/pif.md b/docs/data-sources/pif.md index aa32a449..933d0ce1 100644 --- a/docs/data-sources/pif.md +++ b/docs/data-sources/pif.md @@ -22,6 +22,10 @@ resource "xenorchestra_vm" "demo-vm" { ## Argument Reference * device - (Required) The name of the network device. Examples include eth0, eth1, etc. See `ifconfig` for possible devices. * vlan - (Required) The VLAN the PIF belongs to. +* host_id - (Optional) The ID of the host that the PIF belongs to. + +**Note:** If there are multiple PIFs that match terraform will fail. +Ensure that your device, vlan, host_id and other arguments identify a unique PIF. ## Attributes Reference * id is set to the ID generated by the XO api. diff --git a/docs/data-sources/sr.md b/docs/data-sources/sr.md index 1e859998..8ab6190e 100644 --- a/docs/data-sources/sr.md +++ b/docs/data-sources/sr.md @@ -23,6 +23,10 @@ resource "xenorchestra_vm" "demo-vm" { ## Argument Reference * name_label - (Required) The name of the storage repository you want to look up. * pool_id - (Optional) The ID of the pool the storage repository belongs to. This is useful if you have storage repositories with the same name on different pools. +* tags - (Optional) List of tags that are applied to the storage repository. + +**Note:** If there are multiple storage repositories that match terraform will fail. +Ensure that your name_label, pool_id and tags identify a unique storage repository. ## Attributes Reference * id - Id of the storage repository. diff --git a/docs/data-sources/template.md b/docs/data-sources/template.md index 656ab0c7..f66da082 100644 --- a/docs/data-sources/template.md +++ b/docs/data-sources/template.md @@ -18,6 +18,10 @@ resource "xenorchestra_vm" "demo-vm" { ## Argument Reference * name_label - (Required) The name of the template you want to look up. +* pool_id - (Optional) The id of the pool that the template belongs to. + +**Note:** If there are multiple templates that match terraform will fail. +Ensure that your name_label and pool_id identify a unique template. ## Attributes Reference * id is set to the ID generated by the XO api. diff --git a/xoa/acc_setup_test.go b/xoa/acc_setup_test.go index bac57033..eb761a91 100644 --- a/xoa/acc_setup_test.go +++ b/xoa/acc_setup_test.go @@ -1,6 +1,7 @@ package xoa import ( + "fmt" "os" "testing" @@ -10,13 +11,23 @@ import ( var testObjectIndex int = 1 var accTestPrefix string = "terraform-acc" var accTestPool client.Pool +var accDefaultSr client.StorageRepository +var accTemplateName string func TestMain(m *testing.M) { client.FindPoolForTests(&accTestPool) + client.FindStorageRepositoryForTests(accTestPool, &accDefaultSr, accTestPrefix) + var found bool + accTemplateName, found = os.LookupEnv("XOA_TEMPLATE") + if !found { + fmt.Println("The XOA_TEMPLATE environment variable must be set for the tests") + os.Exit(-1) + } code := m.Run() client.RemoveNetworksWithNamePrefix(accTestPrefix)("") client.RemoveResourceSetsWithNamePrefix(accTestPrefix)("") + client.RemoveTagFromAllObjects(accTestPrefix)("") os.Exit(code) } diff --git a/xoa/data_source_pool.go b/xoa/data_source_pool.go index 91961cdb..8f4a4653 100644 --- a/xoa/data_source_pool.go +++ b/xoa/data_source_pool.go @@ -1,6 +1,7 @@ package xoa import ( + "errors" "fmt" "log" @@ -39,7 +40,7 @@ func dataSourcePoolRead(d *schema.ResourceData, m interface{}) error { nameLabel := d.Get("name_label").(string) - pool, err := c.GetPoolByName(nameLabel) + pools, err := c.GetPoolByName(nameLabel) if _, ok := err.(client.NotFound); ok { d.SetId("") @@ -50,6 +51,13 @@ func dataSourcePoolRead(d *schema.ResourceData, m interface{}) error { return err } + l := len(pools) + if l != 1 { + return errors.New(fmt.Sprintf("found `%d` pools with name `%s`. Pools must be uniquely named to use this data source", l, nameLabel)) + } + + pool := pools[0] + log.Printf("[DEBUG] Found pool with %+v", pool) d.SetId(pool.Id) cpus := map[string]string{ diff --git a/xoa/data_source_pool_test.go b/xoa/data_source_pool_test.go index 09ac325f..b2f7011f 100644 --- a/xoa/data_source_pool_test.go +++ b/xoa/data_source_pool_test.go @@ -9,8 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/terraform" ) -var poolNameLabel = "xenserver-ddelnano" - func TestAccXenorchestraDataSource_pool(t *testing.T) { resourceName := "data.xenorchestra_pool.pool" resource.Test(t, resource.TestCase{ @@ -26,7 +24,7 @@ func TestAccXenorchestraDataSource_pool(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "cpus.%", "2"), resource.TestCheckResourceAttrSet(resourceName, "cpus.sockets"), resource.TestCheckResourceAttrSet(resourceName, "cpus.cores"), - resource.TestCheckResourceAttr(resourceName, "name_label", poolNameLabel)), + resource.TestCheckResourceAttr(resourceName, "name_label", accTestPool.NameLabel)), }, }, }, @@ -53,5 +51,5 @@ func testAccXenorchestraDataSourcePoolConfig() string { data "xenorchestra_pool" "pool" { name_label = "%s" } -`, poolNameLabel) +`, accTestPool.NameLabel) } diff --git a/xoa/data_source_xenorchestra_pif.go b/xoa/data_source_xenorchestra_pif.go index f96404ca..c3d02271 100644 --- a/xoa/data_source_xenorchestra_pif.go +++ b/xoa/data_source_xenorchestra_pif.go @@ -1,6 +1,9 @@ package xoa import ( + "errors" + "fmt" + "github.com/ddelnano/terraform-provider-xenorchestra/client" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -25,6 +28,11 @@ func dataSourceXoaPIF() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "host_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Optional: true, + }, "pool_id": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -51,8 +59,15 @@ func dataSourcePIFRead(d *schema.ResourceData, m interface{}) error { device := d.Get("device").(string) vlan := d.Get("vlan").(int) + host := d.Get("host_id").(string) - pif, err := c.GetPIFByDevice(device, vlan) + pifReq := client.PIF{ + Device: device, + Vlan: vlan, + Host: host, + } + + pifs, err := c.GetPIF(pifReq) if err != nil { return err @@ -63,6 +78,13 @@ func dataSourcePIFRead(d *schema.ResourceData, m interface{}) error { return nil } + l := len(pifs) + if l != 1 { + return errors.New(fmt.Sprintf("found `%d` pifs with device `%s` and vlan `%d`. PIFs must be uniquely named to use this data source", l, device, vlan)) + } + + pif := pifs[0] + d.SetId(pif.Id) d.Set("uuid", pif.Uuid) d.Set("device", pif.Device) diff --git a/xoa/data_source_xenorchestra_pif_test.go b/xoa/data_source_xenorchestra_pif_test.go index 4ece50de..f24743e7 100644 --- a/xoa/data_source_xenorchestra_pif_test.go +++ b/xoa/data_source_xenorchestra_pif_test.go @@ -15,7 +15,7 @@ func TestAccXenorchestraDataSource_pif(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccXenorchestraDataSourcePIFConfig, + Config: testAccXenorchestraDataSourcePIFConfig(), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckXenorchestraDataSourcePIF(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id"), @@ -46,9 +46,12 @@ func testAccCheckXenorchestraDataSourcePIF(n string) resource.TestCheckFunc { } } -const testAccXenorchestraDataSourcePIFConfig = ` +func testAccXenorchestraDataSourcePIFConfig() string { + return fmt.Sprintf(` data "xenorchestra_pif" "pif" { device = "eth0" vlan = -1 + host_id = "%s" +} +`, accTestPool.Master) } -` diff --git a/xoa/data_source_xenorchestra_sr.go b/xoa/data_source_xenorchestra_sr.go index e6e8d4b6..393e751a 100644 --- a/xoa/data_source_xenorchestra_sr.go +++ b/xoa/data_source_xenorchestra_sr.go @@ -1,6 +1,9 @@ package xoa import ( + "errors" + "fmt" + "github.com/ddelnano/terraform-provider-xenorchestra/client" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -25,6 +28,13 @@ func dataSourceXoaStorageRepository() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "tags": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, }, } } @@ -38,17 +48,16 @@ func dataSourceStorageRepositoryRead(d *schema.ResourceData, m interface{}) erro } nameLabel := d.Get("name_label").(string) - poolId, found := d.GetOk("pool_id") + poolId := d.Get("pool_id").(string) + tags := d.Get("tags").([]interface{}) sr := client.StorageRepository{ NameLabel: nameLabel, + PoolId: poolId, + Tags: tagsFromInterfaceSlice(tags), } - if found { - sr.PoolId = poolId.(string) - } - - sr, err = c.GetStorageRepository(sr) + srs, err := c.GetStorageRepository(sr) if _, ok := err.(client.NotFound); ok { d.SetId("") @@ -59,9 +68,25 @@ func dataSourceStorageRepositoryRead(d *schema.ResourceData, m interface{}) erro return err } + l := len(srs) + if l != 1 { + return errors.New(fmt.Sprintf("found `%d` srs that match %+v. Storage repositories must be uniquely named to use this data source", l, srs)) + } + + sr = srs[0] + d.SetId(sr.Id) d.Set("sr_type", sr.SRType) d.Set("uuid", sr.Uuid) d.Set("pool_id", sr.PoolId) return nil } + +func tagsFromInterfaceSlice(values []interface{}) []string { + s := make([]string, 0, len(values)) + + for _, value := range values { + s = append(s, value.(string)) + } + return s +} diff --git a/xoa/data_source_xenorchestra_sr_test.go b/xoa/data_source_xenorchestra_sr_test.go index 63970772..de9e4c08 100644 --- a/xoa/data_source_xenorchestra_sr_test.go +++ b/xoa/data_source_xenorchestra_sr_test.go @@ -9,8 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/terraform" ) -var nameLabel = "XenServer Tools" -var poolId = "cadf25ab-91ff-6fc0-041f-5a7033c4bc78" var nonExistantPoolId = "does not exist" func TestAccXenorchestraDataSource_storageRepository(t *testing.T) { @@ -27,7 +25,7 @@ func TestAccXenorchestraDataSource_storageRepository(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "sr_type"), resource.TestCheckResourceAttrSet(resourceName, "pool_id"), resource.TestCheckResourceAttrSet(resourceName, "uuid"), - resource.TestCheckResourceAttr(resourceName, "name_label", nameLabel)), + resource.TestCheckResourceAttr(resourceName, "name_label", accDefaultSr.NameLabel)), }, }, }, @@ -41,14 +39,14 @@ func TestAccXenorchestraDataSource_storageRepositoryWithPoolId(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccXenorchestraDataSourceStorageRepositoryPoolConfig(poolId), + Config: testAccXenorchestraDataSourceStorageRepositoryPoolConfig(accDefaultSr.PoolId), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckXenorchestraDataSourceStorageRepository(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttrSet(resourceName, "sr_type"), resource.TestCheckResourceAttrSet(resourceName, "pool_id"), resource.TestCheckResourceAttrSet(resourceName, "uuid"), - resource.TestCheckResourceAttr(resourceName, "name_label", nameLabel)), + resource.TestCheckResourceAttr(resourceName, "name_label", accDefaultSr.NameLabel)), }, }, }, @@ -99,8 +97,11 @@ func testAccXenorchestraDataSourceStorageRepositoryConfig() string { return fmt.Sprintf(` data "xenorchestra_sr" "sr" { name_label = "%s" + tags = [ + "%s" + ] } -`, nameLabel) +`, accDefaultSr.NameLabel, accTestPrefix) } func testAccXenorchestraDataSourceStorageRepositoryPoolConfig(poolId string) string { @@ -108,7 +109,10 @@ func testAccXenorchestraDataSourceStorageRepositoryPoolConfig(poolId string) str data "xenorchestra_sr" "sr" { name_label = "%s" pool_id = "%s" + tags = [ + "%s" + ] } -`, nameLabel, poolId) +`, accDefaultSr.NameLabel, poolId, accTestPrefix) } diff --git a/xoa/data_source_xenorchestra_template.go b/xoa/data_source_xenorchestra_template.go index 492a644e..0de16e07 100644 --- a/xoa/data_source_xenorchestra_template.go +++ b/xoa/data_source_xenorchestra_template.go @@ -1,6 +1,9 @@ package xoa import ( + "errors" + "fmt" + "github.com/ddelnano/terraform-provider-xenorchestra/client" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -17,6 +20,10 @@ func dataSourceXoaTemplate() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "pool_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -29,9 +36,14 @@ func dataSourceTemplateRead(d *schema.ResourceData, m interface{}) error { return err } - templateName := d.Get("name_label").(string) + nameLabel := d.Get("name_label").(string) + poolId := d.Get("pool_id").(string) - tmpl, err := c.GetTemplate(templateName) + templateReq := client.Template{ + NameLabel: nameLabel, + PoolId: poolId, + } + templates, err := c.GetTemplate(templateReq) if err != nil { return err @@ -42,6 +54,13 @@ func dataSourceTemplateRead(d *schema.ResourceData, m interface{}) error { return nil } + l := len(templates) + if l != 1 { + return errors.New(fmt.Sprintf("found `%d` templates with query %+v. Templates must be uniquely named to use this data source", l, templateReq)) + } + + tmpl := templates[0] + d.SetId(tmpl.Id) d.Set("uuid", tmpl.Uuid) d.Set("name_label", tmpl.NameLabel) diff --git a/xoa/data_source_xenorchestra_template_test.go b/xoa/data_source_xenorchestra_template_test.go index e7bb3200..87d773fe 100644 --- a/xoa/data_source_xenorchestra_template_test.go +++ b/xoa/data_source_xenorchestra_template_test.go @@ -2,7 +2,6 @@ package xoa import ( "fmt" - "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" @@ -16,10 +15,10 @@ func TestAccXenorchestraDataSource_template(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccXenorchestraDataSourceTemplateConfig, + Config: testAccXenorchestraDataSourceTemplateConfig(accTestPool.Id), Check: resource.ComposeTestCheckFunc( testAccCheckXenorchestraDataSourceTemplate(resourceName), - resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile("^OpaqueRef:")), + resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttrSet(resourceName, "uuid"), ), }, @@ -42,8 +41,11 @@ func testAccCheckXenorchestraDataSourceTemplate(n string) resource.TestCheckFunc } } -const testAccXenorchestraDataSourceTemplateConfig = ` +func testAccXenorchestraDataSourceTemplateConfig(poolId string) string { + return fmt.Sprintf(` data "xenorchestra_template" "template" { - name_label = "Asianux Server 4 (64-bit)" + name_label = "%s" + pool_id = "%s" +} +`, accTemplateName, poolId) } -` diff --git a/xoa/provider_test.go b/xoa/provider_test.go index 36ffcbf8..3f1e0330 100644 --- a/xoa/provider_test.go +++ b/xoa/provider_test.go @@ -31,4 +31,7 @@ func testAccPreCheck(t *testing.T) { if v := os.Getenv("XOA_POOL"); v == "" { t.Fatal("The XOA_POOL environment variable must be set") } + if v := os.Getenv("XOA_TEMPLATE"); v == "" { + t.Fatal("The XOA_TEMPLATE environment variable must be set") + } } diff --git a/xoa/resource_xenorchestra_cloud_config_test.go b/xoa/resource_xenorchestra_cloud_config_test.go index d75fa2ac..06825386 100644 --- a/xoa/resource_xenorchestra_cloud_config_test.go +++ b/xoa/resource_xenorchestra_cloud_config_test.go @@ -10,6 +10,8 @@ import ( ) func TestAccXenorchestraCloudConfig_readAfterDelete(t *testing.T) { + templateName := "testing" + templateText := "template body" resourceName := "xenorchestra_cloud_config.bar" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -17,18 +19,18 @@ func TestAccXenorchestraCloudConfig_readAfterDelete(t *testing.T) { CheckDestroy: testAccCheckXenorchestraCloudConfigDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudConfigConfig(), + Config: testAccCloudConfigConfig(templateName, templateText), Check: resource.ComposeAggregateTestCheckFunc( testAccCloudConfigExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id")), }, { - Config: testAccCloudConfigConfig(), + Config: testAccCloudConfigConfig(templateName, templateText), Check: testAccCheckXenorchestraCloudConfigDestroyNow(resourceName), ExpectNonEmptyPlan: true, }, { - Config: testAccCloudConfigConfig(), + Config: testAccCloudConfigConfig(templateName, templateText), PlanOnly: true, ExpectNonEmptyPlan: true, }, @@ -38,13 +40,15 @@ func TestAccXenorchestraCloudConfig_readAfterDelete(t *testing.T) { func TestAccXenorchestraCloudConfig_create(t *testing.T) { resourceName := "xenorchestra_cloud_config.bar" + templateName := "testing" + templateText := "template body" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckXenorchestraCloudConfigDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudConfigConfig(), + Config: testAccCloudConfigConfig(templateName, templateText), Check: resource.ComposeAggregateTestCheckFunc( testAccCloudConfigExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id")), @@ -55,19 +59,23 @@ func TestAccXenorchestraCloudConfig_create(t *testing.T) { func TestAccXenorchestraCloudConfig_updateName(t *testing.T) { resourceName := "xenorchestra_cloud_config.bar" + templateName := "testing" + templateText := "template body" + + updatedName := "updated" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckXenorchestraCloudConfigDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudConfigConfig(), + Config: testAccCloudConfigConfig(templateName, templateText), }, { - Config: testAccCloudConfigConfigUpdatedName(), + Config: testAccCloudConfigConfig(updatedName, templateText), Check: resource.ComposeAggregateTestCheckFunc( testAccCloudConfigExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "name", "updated")), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("%s%s", accTestPrefix, updatedName))), }, }, }) @@ -75,19 +83,23 @@ func TestAccXenorchestraCloudConfig_updateName(t *testing.T) { func TestAccXenorchestraCloudConfig_updateTemplate(t *testing.T) { resourceName := "xenorchestra_cloud_config.bar" + templateName := "testing" + templateText := "template body" + + updatedTemplate := "new template" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckXenorchestraCloudConfigDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudConfigConfig(), + Config: testAccCloudConfigConfig(templateName, templateText), }, { - Config: testAccCloudConfigConfigUpdatedTemplate(), + Config: testAccCloudConfigConfig(templateName, updatedTemplate), Check: resource.ComposeAggregateTestCheckFunc( testAccCloudConfigExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "template", "updated")), + resource.TestCheckResourceAttr(resourceName, "template", updatedTemplate)), }, }, }) @@ -108,13 +120,15 @@ func TestAccXenorchestraCloudConfig_import(t *testing.T) { } return nil } + templateName := "testing" + templateText := "template body" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckXenorchestraCloudConfigDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudConfigConfig(), + Config: testAccCloudConfigConfig(templateName, templateText), }, { ResourceName: resourceName, @@ -129,31 +143,13 @@ func TestAccXenorchestraCloudConfig_import(t *testing.T) { }) } -func testAccCloudConfigConfig() string { - return ` -resource "xenorchestra_cloud_config" "bar" { - name = "testing" - template = "template" -} -` -} - -func testAccCloudConfigConfigUpdatedName() string { - return ` -resource "xenorchestra_cloud_config" "bar" { - name = "updated" - template = "template" -} -` -} - -func testAccCloudConfigConfigUpdatedTemplate() string { - return ` +func testAccCloudConfigConfig(name, template string) string { + return fmt.Sprintf(` resource "xenorchestra_cloud_config" "bar" { - name = "name" - template = "updated" + name = "%s%s" + template = "%s" } -` +`, accTestPrefix, name, template) } func testAccCloudConfigExists(resourceName string) resource.TestCheckFunc { diff --git a/xoa/resource_xenorchestra_vm.go b/xoa/resource_xenorchestra_vm.go index 14ffc6a9..04768948 100644 --- a/xoa/resource_xenorchestra_vm.go +++ b/xoa/resource_xenorchestra_vm.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "net" - "os" "strings" "time" @@ -152,7 +151,7 @@ func resourceRecord() *schema.Resource { macAddress := network["mac_address"].(string) networkId := network["network_id"].(string) v := fmt.Sprintf("%s-%s", macAddress, networkId) - fmt.Printf("[DEBUG] Setting network via %s\n", v) + log.Printf("[TRACE] Setting network via %s\n", v) return hashcode.String(v) }, @@ -313,6 +312,10 @@ func resourceVmUpdate(d *schema.ResourceData, m interface{}) error { vm, err := c.UpdateVm(d.Id(), cpus, nameLabel, nameDescription, ha, rs, autoPowerOn) log.Printf("[DEBUG] Retrieved vm after update: %+v\n", vm) + if err != nil { + return err + } + if d.HasChange("network") { origNet, newNet := d.GetChange("network") @@ -320,13 +323,10 @@ func resourceVmUpdate(d *schema.ResourceData, m interface{}) error { newNetSet := newNet.(*schema.Set) additions := expandNetworks(newNetSet.Difference(origNetSet).List()) - underTest := os.Getenv("TF_ACC") == "1" for _, addition := range additions { _, vifErr := c.CreateVIF(vm, addition) - // TODO: This nasty hack should be removed - // See https://github.com/terra-farm/terraform-provider-xenorchestra/issues/56 - // for more details - if vifErr != nil && !underTest { + + if vifErr != nil { return err } } @@ -336,19 +336,12 @@ func resourceVmUpdate(d *schema.ResourceData, m interface{}) error { for _, removal := range removals { vifErr := c.DeleteVIF(removal) - // TODO: This nasty hack should be removed - // See https://github.com/terra-farm/terraform-provider-xenorchestra/issues/56 - // for more details - if vifErr != nil && !underTest { + if vifErr != nil { return err } } } - if err != nil { - return err - } - vifs, err := c.GetVIFs(vm) if err != nil { diff --git a/xoa/resource_xenorchestra_vm_test.go b/xoa/resource_xenorchestra_vm_test.go index d680514e..42e113a2 100644 --- a/xoa/resource_xenorchestra_vm_test.go +++ b/xoa/resource_xenorchestra_vm_test.go @@ -23,7 +23,7 @@ func TestAccXenorchestraVm_create(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccVmExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id"), - internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.pif", "network")), + internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id")), }, }, }) @@ -45,13 +45,13 @@ func TestAccXenorchestraVm_createWithMacAddress(t *testing.T) { internal.TestCheckTypeSetElemNestedAttrs(resourceName, "network.*", map[string]string{ "mac_address": macAddress, }), - internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.pif", "network")), + internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id")), }, }, }) } -func TestAccVm_import(t *testing.T) { +func TestAccXenorchestraVm_import(t *testing.T) { resourceName := "xenorchestra_vm.bar" checkFn := func(s []*terraform.InstanceState) error { attrs := []string{"id", "name_label"} @@ -85,7 +85,7 @@ func TestAccVm_import(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttr(resourceName, "name_description", "description"), resource.TestCheckResourceAttr(resourceName, "name_label", "Terraform testing"), - internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.pif", "network")), + internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id")), }, }, }) @@ -128,7 +128,7 @@ func TestAccXenorchestraVm_updateVmWithSecondVif(t *testing.T) { testAccVmExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttr(resourceName, "network.#", "1"), - internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.pif", "network")), + internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id")), }, { Config: testAccVmConfigWithSecondVIF(), @@ -136,8 +136,8 @@ func TestAccXenorchestraVm_updateVmWithSecondVif(t *testing.T) { testAccVmExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttr(resourceName, "network.#", "2"), - internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.pif", "network"), - internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.eth0", "network")), + internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id"), + internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id")), }, }, }) @@ -158,8 +158,8 @@ func TestAccXenorchestraVm_updateVmWithSecondVif(t *testing.T) { // testAccVmExists(resourceName), // resource.TestCheckResourceAttrSet(resourceName, "id"), // resource.TestCheckResourceAttr(resourceName, "network.#", "2"), -// internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.pif", "network"), -// internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.eth0", "network")), +// internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id"), +// internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id")), // }, // { // Config: testAccVmConfig(), @@ -167,7 +167,7 @@ func TestAccXenorchestraVm_updateVmWithSecondVif(t *testing.T) { // testAccVmExists(resourceName), // resource.TestCheckResourceAttrSet(resourceName, "id"), // resource.TestCheckResourceAttr(resourceName, "network.#", "1"), -// internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.pif", "network")), +// internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id")), // }, // }, // }) @@ -180,8 +180,8 @@ func TestAccXenorchestraVm_updatesWithoutReboot(t *testing.T) { origNameDesc := "name label" origHa := "" origPowerOn := false - updatedNameLabel := "Updated name label" - updatedNameDesc := "Updated description" + updatedNameLabel := "Terraform Updated name label" + updatedNameDesc := "Terraform Updated description" updatedHa := "restart" updatedPowerOn := true resource.Test(t, resource.TestCase{ @@ -225,7 +225,7 @@ func TestAccXenorchestraVm_createAndUpdateWithResourceSet(t *testing.T) { testAccVmExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttrSet(resourceName, "resource_set"), - internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.pif", "network")), + internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id")), }, { Config: testAccVmConfigWithoutResourceSet(), @@ -233,7 +233,7 @@ func TestAccXenorchestraVm_createAndUpdateWithResourceSet(t *testing.T) { testAccVmExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttr(resourceName, "resource_set", ""), - internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_pif.pif", "network")), + internal.TestCheckTypeSetElemAttrPair(resourceName, "network.*.*", "data.xenorchestra_network.network", "id")), }, }, }) @@ -269,18 +269,15 @@ func testAccVmExists(resourceName string) resource.TestCheckFunc { } func testAccVmConfig() string { - return testAccCloudConfigConfig() + ` -data "xenorchestra_sr" "local_storage" { - name_label = "Local storage" -} - + return testAccCloudConfigConfig("vm-template", "template") + fmt.Sprintf(` data "xenorchestra_template" "template" { - name_label = "Focal Template" + name_label = "%s" } -data "xenorchestra_pif" "pif" { - device = "eth1" - vlan = -1 +data "xenorchestra_network" "network" { + // TODO: Replace this with a better solution + name_label = "Pool-wide network associated with eth0" + pool_id = "%s" } resource "xenorchestra_vm" "bar" { @@ -291,31 +288,28 @@ resource "xenorchestra_vm" "bar" { name_description = "description" template = "${data.xenorchestra_template.template.id}" network { - network_id = "${data.xenorchestra_pif.pif.network}" + network_id = "${data.xenorchestra_network.network.id}" } disk { - sr_id = "${data.xenorchestra_sr.local_storage.id}" + sr_id = "%s" name_label = "xo provider root" size = 10000000000 } } -` +`, accTemplateName, accTestPool.Id, accDefaultSr.Id) } func testAccVmConfigWithMacAddress(macAddress string) string { - return testAccCloudConfigConfig() + fmt.Sprintf(` -data "xenorchestra_sr" "local_storage" { - name_label = "Local storage" -} - + return testAccCloudConfigConfig("vm-template", "template") + fmt.Sprintf(` data "xenorchestra_template" "template" { - name_label = "Focal Template" + name_label = "%s" } -data "xenorchestra_pif" "pif" { - device = "eth1" - vlan = -1 +data "xenorchestra_network" "network" { + // TODO: Replace this with a better solution + name_label = "Pool-wide network associated with eth0" + pool_id = "%s" } resource "xenorchestra_vm" "bar" { @@ -326,37 +320,35 @@ resource "xenorchestra_vm" "bar" { name_description = "description" template = "${data.xenorchestra_template.template.id}" network { - network_id = "${data.xenorchestra_pif.pif.network}" + network_id = "${data.xenorchestra_network.network.id}" mac_address = "%s" } disk { - sr_id = "${data.xenorchestra_sr.local_storage.id}" + sr_id = "%s" name_label = "xo provider root" size = 10000000000 } } -`, macAddress) +`, accTemplateName, accTestPool.Id, macAddress, accDefaultSr.Id) } func testAccVmConfigWithSecondVIF() string { - return testAccCloudConfigConfig() + ` -data "xenorchestra_sr" "local_storage" { - name_label = "Local storage" -} - + return testAccCloudConfigConfig("vm-template", "template") + fmt.Sprintf(` data "xenorchestra_template" "template" { - name_label = "Focal Template" + name_label = "%s" } -data "xenorchestra_pif" "pif" { - device = "eth1" - vlan = -1 +data "xenorchestra_network" "network" { + // TODO: Replace this with a better solution + name_label = "Pool-wide network associated with eth0" + pool_id = "%s" } -data "xenorchestra_pif" "eth0" { - device = "eth0" - vlan = -1 +data "xenorchestra_network" "network2" { + // TODO: Replace this with a better solution + name_label = "Pool-wide network associated with eth1" + pool_id = "%[2]s" } resource "xenorchestra_vm" "bar" { @@ -367,37 +359,33 @@ resource "xenorchestra_vm" "bar" { name_description = "description" template = "${data.xenorchestra_template.template.id}" network { - network_id = "${data.xenorchestra_pif.pif.network}" + network_id = "${data.xenorchestra_network.network.id}" } - network { - network_id = "${data.xenorchestra_pif.eth0.network}" + network_id = "${data.xenorchestra_network.network2.id}" } disk { - sr_id = "${data.xenorchestra_sr.local_storage.id}" + sr_id = "%s" name_label = "xo provider root" size = 10000000000 } } -` +`, accTemplateName, accTestPool.Id, accDefaultSr.Id) } // Terraform config that tests changes to a VM that do not require halting // the VM prior to applying func testAccVmConfigUpdateAttrsHaltIrrelevant(nameLabel, nameDescription, ha string, powerOn bool) string { - return testAccCloudConfigConfig() + fmt.Sprintf(` -data "xenorchestra_sr" "local_storage" { - name_label = "Local storage" -} - + return testAccCloudConfigConfig("vm-template", "template") + fmt.Sprintf(` data "xenorchestra_template" "template" { - name_label = "Focal Template" + name_label = "%s" } -data "xenorchestra_pif" "pif" { - device = "eth1" - vlan = -1 +data "xenorchestra_network" "network" { + // TODO: Replace this with a better solution + name_label = "Pool-wide network associated with eth0" + pool_id = "%s" } resource "xenorchestra_vm" "bar" { @@ -410,63 +398,61 @@ resource "xenorchestra_vm" "bar" { high_availability = "%s" auto_poweron = "%t" network { - network_id = "${data.xenorchestra_pif.pif.network}" + network_id = "${data.xenorchestra_network.network.id}" } disk { - sr_id = "${data.xenorchestra_sr.local_storage.id}" + sr_id = "%s" name_label = "xo provider root" size = 10000000000 } } -`, nameLabel, nameDescription, ha, powerOn) +`, accTemplateName, accTestPool.Id, nameLabel, nameDescription, ha, powerOn, accDefaultSr.Id) } func testAccVmConfigWithResourceSet() string { - return testAccCloudConfigConfig() + testAccVmResourceSet + ` + return testAccCloudConfigConfig("vm-template", "template") + testAccVmResourceSet() + fmt.Sprintf(` resource "xenorchestra_vm" "bar" { memory_max = 256000000 cpus = 1 cloud_config = "${xenorchestra_cloud_config.bar.template}" - name_label = "Terraform testing" + name_label = "Terraform testing resource sets" name_description = "description" template = "${data.xenorchestra_template.template.id}" resource_set = "${xenorchestra_resource_set.rs.id}" network { - network_id = "${data.xenorchestra_pif.pif.network}" + network_id = "${data.xenorchestra_network.network.id}" } disk { - sr_id = "${data.xenorchestra_sr.local_storage.id}" + sr_id = "%s" name_label = "xo provider root" size = 10000000000 } } -` -} - -var testAccVmResourceSet string = ` -data "xenorchestra_sr" "local_storage" { - name_label = "Local storage" +`, accDefaultSr.Id) } +func testAccVmResourceSet() string { + return fmt.Sprintf(` data "xenorchestra_template" "template" { - name_label = "Focal Template" + name_label = "%s" } -data "xenorchestra_pif" "pif" { - device = "eth1" - vlan = -1 +data "xenorchestra_network" "network" { + // TODO: Replace this with a better solution + name_label = "Pool-wide network associated with eth0" + pool_id = "%s" } resource "xenorchestra_resource_set" "rs" { - name = "vm-acceptance-test" + name = "terraform-vm-acceptance-test" subjects = [] objects = [ "${data.xenorchestra_template.template.id}", - "${data.xenorchestra_sr.local_storage.id}", - "${data.xenorchestra_pif.pif.network}", + "%s", + "${data.xenorchestra_network.network.id}", ] limit { @@ -484,27 +470,28 @@ resource "xenorchestra_resource_set" "rs" { quantity = 12884901888 } } -` +`, accTemplateName, accTestPool.Id, accDefaultSr.Id) +} func testAccVmConfigWithoutResourceSet() string { - return testAccCloudConfigConfig() + testAccVmResourceSet + ` + return testAccCloudConfigConfig("vm-template", "template") + testAccVmResourceSet() + fmt.Sprintf(` resource "xenorchestra_vm" "bar" { memory_max = 256000000 cpus = 1 cloud_config = "${xenorchestra_cloud_config.bar.template}" - name_label = "Terraform testing" + name_label = "Terraform testing resource sets" name_description = "description" template = "${data.xenorchestra_template.template.id}" network { - network_id = "${data.xenorchestra_pif.pif.network}" + network_id = "${data.xenorchestra_network.network.id}" } disk { - sr_id = "${data.xenorchestra_sr.local_storage.id}" + sr_id = "%s" name_label = "xo provider root" size = 10000000000 } } -` +`, accDefaultSr.Id) }