Skip to content

Commit

Permalink
Add xenorchestra_vdi resource (#225)
Browse files Browse the repository at this point in the history
* Impelment xenrochestra_vdi resource

* Remove unused NameDescription member

* Add more detail to requiring git lfs and how to get setup
  • Loading branch information
ddelnano authored Dec 21, 2022
1 parent 4124093 commit 0a81d65
Show file tree
Hide file tree
Showing 13 changed files with 667 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/*.iso filter=lfs diff=lfs merge=lfs -text
5 changes: 4 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Any and all contributions are welcome! Don't hesitate to reach out to ask if you

- [Terraform](https://www.terraform.io/downloads.html) 0.12+
- [Go](https://golang.org/doc/install) 1.16 (to build the provider plugin)
- git lfs must be installed and `git lfs install` must be run after cloning

## Testing the Provider

Expand All @@ -24,6 +25,7 @@ The following environment variables must be set:
- XOA_TEMPLATE - A VM template that has an existing OS **already installed**
- XOA_DISKLESS_TEMPLATE - A VM template that does not have an existing OS (found from `xe template-list`)
- 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.

I typically keep these in a ~/.xoa file and run the following before running the test suite
Expand All @@ -36,7 +38,8 @@ export XOA_USER=username
export XOA_PASSWORD=password
export XOA_POOL=pool-1
export XOA_TEMPLATE='Debian 10 Cloudinit'
export XOA_TEMPLATE='Debian Buster 10'

[ ... ]

# Source the environment variables inside the file
eval $(cat ~/.xoa)
Expand Down
72 changes: 62 additions & 10 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import (
"fmt"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"reflect"
"strings"
"time"

gorillawebsocket "github.com/gorilla/websocket"
Expand Down Expand Up @@ -80,8 +83,12 @@ type XOClient interface {

GetTemplate(template Template) ([]Template, error)

GetAllVDIs() ([]VDI, error)
GetVDIs(vdiReq VDI) ([]VDI, error)
GetVDI(vdiReq VDI) (VDI, error)
CreateVDI(vdiReq CreateVDIReq) (VDI, error)
UpdateVDI(d Disk) error
DeleteVDI(id string) error

CreateAcl(acl Acl) (*Acl, error)
GetAcl(aclReq Acl) (*Acl, error)
Expand Down Expand Up @@ -109,7 +116,9 @@ type XOClient interface {
}

type Client struct {
rpc jsonrpc2.JSONRPC2
rpc jsonrpc2.JSONRPC2
httpClient http.Client
restApiURL *url.URL
}

type Config struct {
Expand All @@ -125,12 +134,12 @@ var dialer = gorillawebsocket.Dialer{
}

func GetConfigFromEnv() Config {
var url string
var wsURL string
var username string
var password string
insecure := false
if v := os.Getenv("XOA_URL"); v != "" {
url = v
wsURL = v
}
if v := os.Getenv("XOA_USER"); v != "" {
username = v
Expand All @@ -142,24 +151,24 @@ func GetConfigFromEnv() Config {
insecure = true
}
return Config{
Url: url,
Url: wsURL,
Username: username,
Password: password,
InsecureSkipVerify: insecure,
}
}

func NewClient(config Config) (XOClient, error) {
url := config.Url
wsURL := config.Url
username := config.Username
password := config.Password
skipVerify := config.InsecureSkipVerify

if skipVerify {
dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
tlsConfig := &tls.Config{
InsecureSkipVerify: config.InsecureSkipVerify,
}
dialer.TLSClientConfig = tlsConfig

ws, _, err := dialer.Dial(fmt.Sprintf("%s/api/", url), http.Header{})
ws, _, err := dialer.Dial(fmt.Sprintf("%s/api/", wsURL), http.Header{})

if err != nil {
return nil, err
Expand All @@ -179,8 +188,41 @@ func NewClient(config Config) (XOClient, error) {
if err != nil {
return nil, err
}

var token string
err = c.Call(context.Background(), "token.create", map[string]interface{}{}, &token)
if err != nil {
return nil, err
}

jar, err := cookiejar.New(&cookiejar.Options{})
if err != nil {
return nil, err
}

restApiURL, err := convertWebsocketURLToRestApi(wsURL)
if err != nil {
return nil, err
}

jar.SetCookies(restApiURL, []*http.Cookie{
&http.Cookie{
Name: "authenticationToken",
Value: token,
MaxAge: 0,
},
})

httpClient := http.Client{
Jar: jar,
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
}
return &Client{
rpc: c,
rpc: c,
httpClient: httpClient,
restApiURL: restApiURL,
}, nil
}

Expand Down Expand Up @@ -306,3 +348,13 @@ type signInResponse struct {
Email string `json:"email,omitempty"`
Id string `json:"id,omitempty"`
}

// This function must be used after a successful JSONRPC websocket request
// is made. This is to verify that we can trust the websocket URL is well-formed
// so our simple ws/wss -> http/https translation will be done correctly
func convertWebsocketURLToRestApi(wsURL string) (*url.URL, error) {
if !strings.HasPrefix(wsURL, "ws") {
return nil, fmt.Errorf("expected `%s` to begin with ws in order to munge the URL to its http/https equivalent\n", wsURL)
}
return url.Parse(strings.Replace(wsURL, "ws", "http", 1))
}
57 changes: 57 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/url"
"testing"

"github.com/sourcegraph/jsonrpc2"
Expand Down Expand Up @@ -105,3 +106,59 @@ func TestCall_withNonJsonRPC2Error(t *testing.T) {
t.Errorf("Call method should return an error as is if not of type `jsonrpc2.Error`. Expected: %v received: %v", expectedErr, err)
}
}

func Test_convertWebsocketURLToRestApi(t *testing.T) {
tests := []struct {
inputURL string
expectedURL *url.URL
err error
}{
{
// Use a URL that contains 'ws' more than once to verify
// string replacement only modifies the prefix
inputURL: "ws://example.com/wss",
expectedURL: &url.URL{
Scheme: "http",
Host: "example.com",
Path: "/wss",
},
err: nil,
},
{
// Use a URL that contains 'ws' more than once to verify
// string replacement only modifies the prefix
inputURL: "wss://example.com/wss",
expectedURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/wss",
},
err: nil,
},
{
inputURL: "ftp://example.com",
expectedURL: nil,
err: fmt.Errorf("expected `%s` to begin with ws in order to munge the URL to its http/https equivalent\n", "ftp://example.com"),
},
}

for _, tt := range tests {
url, err := convertWebsocketURLToRestApi(tt.inputURL)

if (tt.err == nil && err != tt.err) || (tt.err != nil && tt.err.Error() != err.Error()) {
t.Errorf("expected error `%v` to match `%v`\n", err, tt.err)
}

if tt.expectedURL == nil && tt.expectedURL != url {
t.Errorf("expected url creation to return nil but instead received `%v`\n", err)
}

if tt.expectedURL != nil {
urlStr := url.String()
expectedURLStr := tt.expectedURL.String()
if expectedURLStr != urlStr {
t.Errorf("expected `%s` and `%s` to match\n", expectedURLStr, urlStr)
}
}
}
}
41 changes: 41 additions & 0 deletions client/storage_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,44 @@ func FindStorageRepositoryForTests(pool Pool, sr *StorageRepository, tag string)
os.Exit(-1)
}
}

func FindIsoStorageRepositoryForTests(pool Pool, sr *StorageRepository, tag, isoSrEnvVar string) {
isoSrName, found := os.LookupEnv(isoSrEnvVar)
if !found {
fmt.Println(fmt.Sprintf("The %s environment variable must be set for the tests", isoSrEnvVar))
os.Exit(-1)
}

c, err := NewClient(GetConfigFromEnv())
if err != nil {
fmt.Printf("failed to create client with error: %v", err)
os.Exit(-1)
}

isoSrReq := StorageRepository{
PoolId: pool.Id,
NameLabel: isoSrName,
SRType: "iso",
}
isoSrs, err := c.GetStorageRepository(isoSrReq)

if err != nil {
fmt.Printf("failed to find an iso storage repository with error: %v\n", err)
os.Exit(-1)
}

if len(isoSrs) != 1 {
fmt.Printf("expected iso srs req `%v` to only return single sr, instead found %d", isoSrReq, len(isoSrs))
os.Exit(-1)
}

isoSr := isoSrs[0]
*sr = isoSr

err = c.AddTag(isoSr.Id, tag)

if err != nil {
fmt.Printf("failed to set tag on iso storage repository with id: %s with error: %v\n", isoSr.Id, err)
os.Exit(-1)
}
}
Loading

0 comments on commit 0a81d65

Please sign in to comment.