1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-21 14:50:08 +03:00

F #2830: GOCA - migrate from xmlpath to encoding/xml package (#2832)

This commit is contained in:
treywelsh 2019-02-07 16:42:46 +01:00 committed by Ruben S. Montero
parent 683cbcf26e
commit 732b408dad
34 changed files with 1690 additions and 610 deletions

View File

@ -1,8 +1,15 @@
package goca
import "encoding/xml"
// ACLPool represents an OpenNebula ACL list pool
type ACLPool struct {
XMLResource
ID uint `xml:"ID"`
User int `xml:"USER"`
Resource int `xml:"RESOURCE"`
Rights int `xml:"RIGHTS"`
Zone int `xml:"ZONE"`
String string `xml:"STRING"`
}
// NewACLPool returns an acl pool. A connection to OpenNebula is
@ -13,9 +20,13 @@ func NewACLPool() (*ACLPool, error) {
return nil, err
}
aclpool := &ACLPool{XMLResource{body: response.Body()}}
aclPool := &ACLPool{}
err = xml.Unmarshal([]byte(response.Body()), aclPool)
if err != nil {
return nil, err
}
return aclpool, err
return aclPool, nil
}
// CreateACLRule adds a new ACL rule.

View File

@ -1,15 +1,30 @@
package goca
// Cluster represents an OpenNebula Cluster
type Cluster struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
)
// ClusterPool represents an OpenNebula ClusterPool
type ClusterPool struct {
XMLResource
Clusters []Cluster `xml:"CLUSTER"`
}
// Cluster represents an OpenNebula Cluster
type Cluster struct {
ID uint `xml:"ID"`
Name string `xml:"NAME"`
HostsID []int `xml:"HOSTS>ID"`
DatastoresID []int `xml:"DATASTORES>ID"`
VnetsID []int `xml:"VNETS>ID"`
Template clusterTemplate `xml:"TEMPLATE"`
}
type clusterTemplate struct {
// Example of reservation: https://github.com/OpenNebula/addon-storpool/blob/ba9dd3462b369440cf618c4396c266f02e50f36f/misc/reserved.sh
ReservedMem string `xml:"RESERVED_MEM"`
ReservedCpu string `xml:"RESERVED_CPU"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
// NewClusterPool returns a cluster pool. A connection to OpenNebula is
@ -20,9 +35,13 @@ func NewClusterPool() (*ClusterPool, error) {
return nil, err
}
clusterpool := &ClusterPool{XMLResource{body: response.Body()}}
clusterPool := &ClusterPool{}
err = xml.Unmarshal([]byte(response.Body()), clusterPool)
if err != nil {
return nil, err
}
return clusterpool, err
return clusterPool, nil
}
@ -35,14 +54,26 @@ func NewCluster(id uint) *Cluster {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the cluster.
func NewClusterFromName(name string) (*Cluster, error) {
var id uint
clusterPool, err := NewClusterPool()
if err != nil {
return nil, err
}
id, err := clusterPool.GetIDFromName(name, "/CLUSTER_POOL/CLUSTER")
if err != nil {
return nil, err
match := false
for i := 0; i < len(clusterPool.Clusters); i++ {
if clusterPool.Clusters[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = clusterPool.Clusters[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewCluster(id), nil
@ -129,6 +160,5 @@ func (cluster *Cluster) Info() error {
if err != nil {
return err
}
cluster.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), cluster)
}

View File

@ -1,15 +1,66 @@
package goca
// Datastore represents an OpenNebula Datastore
type Datastore struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
"fmt"
)
// DatastorePool represents an OpenNebula DatastorePool
type DatastorePool struct {
XMLResource
Datastores []Datastore `xml:"DATASTORE"`
}
// Datastore represents an OpenNebula Datastore
type Datastore struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
Permissions *Permissions `xml:"PERMISSIONS"`
DSMad string `xml:"DS_MAD"`
TMMad string `xml:"TM_MAD"`
BasePath string `xml:"BASE_PATH"`
Type string `xml:"TYPE"`
DiskType string `xml:"DISK_TYPE"`
StateRaw int `xml:"STATE"`
ClustersID []int `xml:"CLUSTERS>ID"`
TotalMB int `xml:"TOTAL_MB"`
FreeMB int `xml:"FREE_MB"`
UsedMB int `xml:"USED_MB"`
ImagesID []int `xml:"IMAGES>ID"`
Template datastoreTemplate `xml:"TEMPLATE"`
}
type datastoreTemplate struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
// DatastoreState is the state of an OpenNebula datastore
type DatastoreState int
const (
// DatastoreReady datastore is ready
DatastoreReady = iota
// DatastoreDisable datastore is disabled
DatastoreDisable
)
func (st DatastoreState) isValid() bool {
if st >= DatastoreReady && st <= DatastoreDisable {
return true
}
return false
}
func (st DatastoreState) String() string {
return [...]string{
"READY",
"DISABLE",
}[st]
}
// NewDatastorePool returns a datastore pool. A connection to OpenNebula is
@ -20,9 +71,13 @@ func NewDatastorePool() (*DatastorePool, error) {
return nil, err
}
datastorepool := &DatastorePool{XMLResource{body: response.Body()}}
datastorePool := &DatastorePool{}
err = xml.Unmarshal([]byte(response.Body()), datastorePool)
if err != nil {
return nil, err
}
return datastorepool, err
return datastorePool, nil
}
// NewDatastore finds a datastore object by ID. No connection to OpenNebula.
@ -34,14 +89,26 @@ func NewDatastore(id uint) *Datastore {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the datastore.
func NewDatastoreFromName(name string) (*Datastore, error) {
var id uint
datastorePool, err := NewDatastorePool()
if err != nil {
return nil, err
}
id, err := datastorePool.GetIDFromName(name, "/DATASTORE_POOL/DATASTORE")
if err != nil {
return nil, err
match := false
for i := 0; i < len(datastorePool.Datastores); i++ {
if datastorePool.Datastores[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = datastorePool.Datastores[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewDatastore(id), nil
@ -116,6 +183,23 @@ func (datastore *Datastore) Info() error {
if err != nil {
return err
}
datastore.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), datastore)
}
// State looks up the state of the image and returns the DatastoreState
func (datastore *Datastore) State() (DatastoreState, error) {
state := DatastoreState(datastore.StateRaw)
if !state.isValid() {
return -1, fmt.Errorf("Datastore State: this state value is not currently handled: %d\n", datastore.StateRaw)
}
return state, nil
}
// StateString returns the state in string format
func (datastore *Datastore) StateString() (string, error) {
state := DatastoreState(datastore.StateRaw)
if !state.isValid() {
return "", fmt.Errorf("Datastore StateString: this state value is not currently handled: %d\n", datastore.StateRaw)
}
return state.String(), nil
}

View File

@ -1,17 +1,31 @@
package goca
import "errors"
// Document represents an OpenNebula Document
type Document struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
)
// DocumentPool represents an OpenNebula DocumentPool
type DocumentPool struct {
XMLResource
Documents []Document `xml:"DOCUMENT"`
}
// Document represents an OpenNebula Document
type Document struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
Type string `xml:"TYPE"`
Permissions *Permissions `xml:"PERMISSIONS"`
LockInfos *Lock `xml:"LOCK"`
Template documentTemplate `xml:"TEMPLATE"`
}
type documentTemplate struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
// NewDocumentPool returns a document pool. A connection to OpenNebula is
@ -41,9 +55,13 @@ func NewDocumentPool(documentType int, args ...int) (*DocumentPool, error) {
return nil, err
}
documentpool := &DocumentPool{XMLResource{body: response.Body()}}
documentPool := &DocumentPool{}
err = xml.Unmarshal([]byte(response.Body()), documentPool)
if err != nil {
return nil, err
}
return documentpool, err
return documentPool, nil
}
// NewDocument finds a document object by ID. No connection to OpenNebula.
@ -55,14 +73,26 @@ func NewDocument(id uint) *Document {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the document.
func NewDocumentFromName(name string, documentType int) (*Document, error) {
var id uint
documentPool, err := NewDocumentPool(documentType)
if err != nil {
return nil, err
}
id, err := documentPool.GetIDFromName(name, "/DOCUMENT_POOL/DOCUMENT")
if err != nil {
return nil, err
match := false
for i := 0; i < len(documentPool.Documents); i++ {
if documentPool.Documents[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = documentPool.Documents[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewDocument(id), nil

View File

@ -38,15 +38,6 @@ type response struct {
bodyBool bool
}
// Resource implements an OpenNebula Resource methods. *XMLResource implements
// all these methods
type Resource interface {
Body() string
XPath(string) (string, bool)
XPathIter(string) *XMLIter
GetIDFromName(string, string) (uint, error)
}
// Initializes the client variable, used as a singleton
func init() {
SetClient(NewConfig("", "", ""))
@ -123,6 +114,10 @@ func SystemConfig() (string, error) {
// Call is an XML-RPC wrapper. It returns a pointer to response and an error.
func (c *oneClient) Call(method string, args ...interface{}) (*response, error) {
return c.endpointCall(c.url, method, args...)
}
func (c *oneClient) endpointCall(url string, method string, args ...interface{}) (*response, error) {
var (
ok bool
@ -144,7 +139,7 @@ func (c *oneClient) Call(method string, args ...interface{}) (*response, error)
&ClientError{Code: ClientReqBuild, msg: "xmlrpc request encoding", err: err}
}
req, err := http.NewRequest("POST", c.url, bytes.NewBuffer(buf))
req, err := http.NewRequest("POST", url, bytes.NewBuffer(buf))
if err != nil {
return nil,
&ClientError{Code: ClientReqBuild, msg: "http request build", err: err}

View File

@ -1,15 +1,34 @@
package goca
// Group represents an OpenNebula Group
type Group struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
)
// GroupPool represents an OpenNebula GroupPool
type GroupPool struct {
XMLResource
Groups []groupBase `xml:"GROUP"`
Quotas []quotas `xml:"QUOTAS"`
DefaultUserQuotas quotasList `xml:"DEFAULT_USER_QUOTAS"`
}
// Group represents an OpenNebula Group
type Group struct {
groupBase
quotasList
DefaultUserQuotas quotasList `xml:"DEFAULT_USER_QUOTAS"`
}
type groupBase struct {
ID uint `xml:"ID"`
Name string `xml:"NAME"`
Users []int `xml:"USERS>ID"`
Admins []int `xml:"ADMINS>ID"`
Template groupTemplate `xml:"TEMPLATE"`
}
type groupTemplate struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
// NewGroupPool returns a group pool. A connection to OpenNebula is
@ -20,28 +39,44 @@ func NewGroupPool() (*GroupPool, error) {
return nil, err
}
grouppool := &GroupPool{XMLResource{body: response.Body()}}
groupPool := &GroupPool{}
err = xml.Unmarshal([]byte(response.Body()), groupPool)
if err != nil {
return nil, err
}
return grouppool, err
return groupPool, nil
}
// NewGroup finds a group object by ID. No connection to OpenNebula.
func NewGroup(id uint) *Group {
return &Group{ID: id}
return &Group{groupBase: groupBase{ID: id}}
}
// NewGroupFromName finds a group object by name. It connects to
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the group.
func NewGroupFromName(name string) (*Group, error) {
var id uint
groupPool, err := NewGroupPool()
if err != nil {
return nil, err
}
id, err := groupPool.GetIDFromName(name, "/GROUP_POOL/GROUP")
if err != nil {
return nil, err
match := false
for i := 0; i < len(groupPool.Groups); i++ {
if groupPool.Groups[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = groupPool.Groups[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewGroup(id), nil
@ -69,8 +104,7 @@ func (group *Group) Info() error {
if err != nil {
return err
}
group.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), group)
}
// Update replaces the group template contents.

View File

@ -8,23 +8,6 @@ import (
"time"
)
// Extracts the ID of a resource
func GetID(t *testing.T, r Resource, s string) (uint, error) {
path := fmt.Sprintf("/%s/ID", s)
sIDFromXML, ok := r.XPath(path)
if !ok {
t.Error("Could not find ID")
}
idFromXML, err := strconv.ParseUint(sIDFromXML, 10, strconv.IntSize)
if err != nil {
t.Error(err)
}
return uint(idFromXML), nil
}
// Appends a random string to a name
func GenName(name string) string {
t := strconv.FormatInt(time.Now().UnixNano(), 10)
@ -57,11 +40,6 @@ func GetUserGroup(t *testing.T, user string) (string, error){
t.Error("Cannot retreive caller user Info")
}
// Get Caller Group
ugroup, ok := u.XPath("/USER/GNAME")
if !ok {
t.Errorf("Could not get caller group name")
}
return u.GName, nil
return ugroup, nil
}

View File

@ -1,20 +1,71 @@
package goca
import (
"encoding/xml"
"errors"
"strconv"
"fmt"
)
// Host represents an OpenNebula Host
type Host struct {
XMLResource
ID uint
Name string
}
// HostPool represents an OpenNebula HostPool
type HostPool struct {
XMLResource
Hosts []Host `xml:"HOST"`
}
// Host represents an OpenNebula Host
type Host struct {
ID uint `xml:"ID"`
Name string `xml:"NAME"`
StateRaw int `xml:"STATE"`
IMMAD string `xml:"IM_MAD"`
VMMAD string `xml:"VM_MAD"`
LastMonTime int `xml:"LAST_MON_TIME"`
ClusterID int `xml:"CLUSTER_ID"`
Cluster string `xml:"CLUSTER"`
Share hostShare `xml:"HOST_SHARE"`
VMsID []int `xml:"VMS>ID"`
Template hostTemplate `xml:"TEMPLATE"`
}
type hostShare struct {
DiskUsage int `xml:"DISK_USAGE"`
MemUsage int `xml:"MEM_USAGE"`
CPUUsage int `xml:"CPU_USAGE"`
TotalMem int `xml:"TOTAL_MEM"`
TotalCPU int `xml:"TOTAL_CPU"`
MaxDisk int `xml:"MAX_DISK"`
MaxMem int `xml:"MAX_MEM"`
MaxCPU int `xml:"MAX_CPU"`
FreeDisk int `xml:"FREE_DISK"`
FreeMem int `xml:"FREE_MEM"`
FreeCPU int `xml:"FREE_CPU"`
UsedDisk int `xml:"USED_DISK"`
UsedMem int `xml:"USED_MEM"`
UsedCPU int `xml:"USED_CPU"`
RunningVMs int `xml:"RUNNING_VMS"`
Stores hostDataStores `xml:"DATASTORES"`
PCIDevices interface{} `xml:"PCI_DEVICES>PCI"`
}
type hostDataStores struct {
DSs []hostDS `xml:"DS"`
}
type hostDS struct {
ID int `xml:"ID"`
UsedMB int `xml:"USED_MB"`
FreeMB int `xml:"FREE_MB"`
TotalMB int `xml:"TOTAL_MB"`
}
type hostTemplate struct {
// Example of reservation: https://github.com/OpenNebula/addon-storpool/blob/ba9dd3462b369440cf618c4396c266f02e50f36f/misc/reserved.sh
ReservedMem int `xml:"RESERVED_MEM"`
ReservedCpu int `xml:"RESERVED_CPU"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
// HostState is the state of an OpenNebula Host
@ -49,6 +100,13 @@ const (
HostOffline
)
func (st HostState) isValid() bool {
if st >= HostInit && st <= HostOffline {
return true
}
return false
}
func (st HostState) String() string {
return [...]string{
"INIT",
@ -71,9 +129,12 @@ func NewHostPool() (*HostPool, error) {
return nil, err
}
hostpool := &HostPool{XMLResource{body: response.Body()}}
return hostpool, err
hostPool := &HostPool{}
err = xml.Unmarshal([]byte(response.Body()), &hostPool)
if err != nil {
return nil, err
}
return hostPool, nil
}
// NewHost finds a host object by ID. No connection to OpenNebula.
@ -85,14 +146,26 @@ func NewHost(id uint) *Host {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the host.
func NewHostFromName(name string) (*Host, error) {
var id uint
hostPool, err := NewHostPool()
if err != nil {
return nil, err
}
id, err := hostPool.GetIDFromName(name, "/HOST_POOL/HOST")
if err != nil {
return nil, err
match := false
for i := 0; i < len(hostPool.Hosts); i++ {
if hostPool.Hosts[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = hostPool.Hosts[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewHost(id), nil
@ -146,8 +219,7 @@ func (host *Host) Info() error {
if err != nil {
return err
}
host.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), host)
}
// Monitoring returns the host monitoring records.
@ -156,23 +228,20 @@ func (host *Host) Monitoring() error {
return err
}
// State returns the HostState
// State looks up the state of the image and returns the ImageState
func (host *Host) State() (HostState, error) {
stateString, ok := host.XPath("/HOST/STATE")
if ok != true {
return -1, errors.New("Unable to parse host State")
state := HostState(host.StateRaw)
if !state.isValid() {
return -1, fmt.Errorf("Host State: this state value is not currently handled: %d\n", host.StateRaw)
}
state, _ := strconv.Atoi(stateString)
return HostState(state), nil
return state, nil
}
// StateString returns the HostState as string
// StateString returns the state in string format
func (host *Host) StateString() (string, error) {
state, err := host.State()
if err != nil {
return "", err
state := HostState(host.StateRaw)
if !state.isValid() {
return "", fmt.Errorf("Host StateString: this state value is not currently handled: %d\n", host.StateRaw)
}
return HostState(state).String(), nil
return state.String(), nil
}

View File

@ -1,20 +1,50 @@
package goca
import (
"encoding/xml"
"errors"
"strconv"
"fmt"
)
// Image represents an OpenNebula Image
type Image struct {
XMLResource
ID uint
Name string
}
// ImagePool represents an OpenNebula Image pool
type ImagePool struct {
XMLResource
Images []Image `xml:"IMAGE"`
}
// Image represents an OpenNebula Image
type Image struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
LockInfos *Lock `xml:"LOCK"`
Permissions *Permissions `xml:"PERMISSIONS"`
Type int `xml:"TYPE"`
DiskType int `xml:"DISK_TYPE"`
PersistentValue int `xml:"PERSISTENT"`
RegTime int `xml:"REGTIME"`
Source string `xml:"SOURCE"`
Path string `xml:"PATH"`
FsType string `xml:"FSTYPE"`
Size int `xml:"SIZE"`
StateRaw int `xml:"STATE"`
RunningVMs int `xml:"RUNNING_VMS"`
CloningOps int `xml:"CLONING_OPS"`
CloningID int `xml:"CLONING_ID"`
TargetSnapshot int `xml:"TARGET_SNAPSHOT"`
DatastoreID int `xml:"DATASTORE_ID"`
Datastore string `xml:"DATASTORE"`
VMsID []int `xml:"VMS>ID"`
ClonesID []int `xml:"CLONES>ID"`
AppClonesID []int `xml:"APP_CLONES>ID"`
Snapshots ImageSnapshot `xml:"SNAPSHOTS"`
Template imageTemplate `xml:"TEMPLATE"`
}
type imageTemplate struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
// ImageState is the state of the Image
@ -55,6 +85,13 @@ const (
ImageLockUsedPers
)
func (st ImageState) isValid() bool {
if st >= ImageInit && st <= ImageLockUsedPers {
return true
}
return false
}
// String returns the string version of the ImageState
func (s ImageState) String() string {
return [...]string{
@ -84,7 +121,7 @@ func CreateImage(template string, dsid uint) (uint, error) {
}
// NewImagePool returns a new image pool. It accepts the scope of the query. It
// performs an OpenNebula connectio to fetch the information.
// performs an OpenNebula connection to fetch the information.
func NewImagePool(args ...int) (*ImagePool, error) {
var who, start, end int
@ -106,10 +143,13 @@ func NewImagePool(args ...int) (*ImagePool, error) {
return nil, err
}
imagepool := &ImagePool{XMLResource{body: response.Body()}}
return imagepool, err
imagePool := &ImagePool{}
err = xml.Unmarshal([]byte(response.Body()), imagePool)
if err != nil {
return nil, err
}
return imagePool, nil
}
// NewImage finds an image by ID returns a new Image object. At this stage no
@ -122,14 +162,26 @@ func NewImage(id uint) *Image {
// to OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the image.
func NewImageFromName(name string) (*Image, error) {
var id uint
imagePool, err := NewImagePool()
if err != nil {
return nil, err
}
id, err := imagePool.GetIDFromName(name, "/IMAGE_POOL/IMAGE")
if err != nil {
return nil, err
match := false
for i := 0; i < len(imagePool.Images); i++ {
if imagePool.Images[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = imagePool.Images[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewImage(id), nil
@ -141,29 +193,25 @@ func (image *Image) Info() error {
if err != nil {
return err
}
image.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), image)
}
// State looks up the state of the image and returns the ImageState
func (image *Image) State() (ImageState, error) {
stateString, ok := image.XPath("/IMAGE/STATE")
if ok != true {
return -1, errors.New("Unable to parse Image State")
state := ImageState(image.StateRaw)
if !state.isValid() {
return -1, fmt.Errorf("Image State: this state value is not currently handled: %d\n", image.StateRaw)
}
state, _ := strconv.Atoi(stateString)
return ImageState(state), nil
return state, nil
}
// StateString returns the state in string format
func (image *Image) StateString() (string, error) {
state, err := image.State()
if err != nil {
return "", err
state := ImageState(image.StateRaw)
if !state.isValid() {
return "", fmt.Errorf("Image State: this state value is not currently handled: %d\n", image.StateRaw)
}
return ImageState(state).String(), nil
return state.String(), nil
}
// Clone clones an existing image. It returns the clone ID

View File

@ -33,7 +33,7 @@ func ImageExpectState(image *Image, state string) func() bool {
}
// Helper to create a Image
func createImage(t *testing.T) *Image {
func createImage(t *testing.T) (*Image, uint) {
// Datastore ID 1 means default for image
id, err := CreateImage(imageTpl, 1)
if err != nil {
@ -48,26 +48,21 @@ func createImage(t *testing.T) *Image {
t.Error(err)
}
return image
return image, id
}
func TestImage(t *testing.T) {
image := createImage(t)
var err error
idParse, err := GetID(t, image, "IMAGE")
if err != nil {
t.Error(err)
}
image, idOrig := createImage(t)
if idParse != image.ID {
idParse := image.ID
if idParse != idOrig {
t.Errorf("Image ID does not match")
}
// Get image by Name
name, ok := image.XPath("/IMAGE/NAME")
if !ok {
t.Errorf("Could not get name")
}
name := image.Name
image, err = NewImageFromName(name)
if err != nil {
@ -79,9 +74,8 @@ func TestImage(t *testing.T) {
t.Error(err)
}
idParse, err = GetID(t, image, "IMAGE")
if idParse != image.ID {
idParse = image.ID
if idParse != idOrig {
t.Errorf("Image ID does not match")
}
@ -104,16 +98,10 @@ func TestImage(t *testing.T) {
}
// Get Image Owner Name
uname, ok := image.XPath("/IMAGE/UNAME")
if !ok {
t.Errorf("Could not get user name")
}
uname := image.UName
// Get Image owner group Name
gname, ok := image.XPath("/IMAGE/GNAME")
if !ok {
t.Errorf("Could not get group name")
}
gname := image.GName
// Compare with caller username
caller := strings.Split(client.token, ":")[0]
@ -143,16 +131,10 @@ func TestImage(t *testing.T) {
}
// Get Image Owner Name
uname, ok = image.XPath("/IMAGE/UNAME")
if !ok {
t.Errorf("Could not get user name")
}
uname = image.UName
// Get Image Owner Name
gname, ok = image.XPath("/IMAGE/GNAME")
if !ok {
t.Errorf("Could not get user name")
}
// Get Image owner group Name
gname = image.GName
if "serveradmin" != uname {
t.Error("Image owner is not oneadmin")

View File

@ -0,0 +1,8 @@
package goca
type Lock struct {
Locked int `xml:"LOCKED"`
Owner int `xml:"OWNER"`
Time int `xml:"TIME"`
ReqID int `xml:"REQ_ID"`
}

View File

@ -1,17 +1,36 @@
package goca
import "errors"
// MarketPlace represents an OpenNebula MarketPlace
type MarketPlace struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
)
// MarketPlacePool represents an OpenNebula MarketPlacePool
type MarketPlacePool struct {
XMLResource
MarketPlaces []MarketPlace `xml:"MARKETPLACE"`
}
// MarketPlace represents an OpenNebula MarketPlace
type MarketPlace struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
MarketMad string `xml:"MARKET_MAD"`
ZoneID string `xml:"ZONE_ID"`
TotalMB int `xml:"TOTAL_MB"`
FreeMB int `xml:"FREE_MB"`
UsedMB int `xml:"USED_MB"`
MarketPlaceAppsIDs []int `xml:"MARKETPLACEAPPS>ID"`
Permissions *Permissions `xml:"PERMISSIONS"`
Template marketPlaceTemplate `xml:"TEMPLATE"`
}
// MarketPlaceTemplate represent the template part of the MarketPlace
type marketPlaceTemplate struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
// NewMarketPlacePool returns a marketplace pool. A connection to OpenNebula is
@ -41,9 +60,13 @@ func NewMarketPlacePool(args ...int) (*MarketPlacePool, error) {
return nil, err
}
marketpool := &MarketPlacePool{XMLResource{body: response.Body()}}
marketPool := &MarketPlacePool{}
err = xml.Unmarshal([]byte(response.Body()), marketPool)
if err != nil {
return nil, err
}
return marketpool, err
return marketPool, nil
}
// NewMarketPlace finds a marketplace object by ID. No connection to OpenNebula.
@ -55,14 +78,26 @@ func NewMarketPlace(id uint) *MarketPlace {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the marketplace.
func NewMarketPlaceFromName(name string) (*MarketPlace, error) {
var id uint
marketPool, err := NewMarketPlacePool()
if err != nil {
return nil, err
}
id, err := marketPool.GetIDFromName(name, "/MARKETPLACE_POOL/MARKETPLACE")
if err != nil {
return nil, err
match := false
for i := 0; i < len(marketPool.MarketPlaces); i++ {
if marketPool.MarketPlaces[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = marketPool.MarketPlaces[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewMarketPlace(id), nil
@ -129,6 +164,5 @@ func (market *MarketPlace) Info() error {
if err != nil {
return err
}
market.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), market)
}

View File

@ -4,7 +4,6 @@ import (
"testing"
)
func TestMarketplace(t *testing.T){
var mkt_name string = "marketplace_test_go"
@ -25,7 +24,7 @@ func TestMarketplace(t *testing.T){
market = NewMarketPlace(market_id)
market.Info()
actual, _:= market.XMLResource.XPath("/MARKETPLACE/NAME")
actual := market.Name
if actual != mkt_name {
t.Errorf("Test failed, expected: '%s', got: '%s'", mkt_name, actual)
@ -42,17 +41,20 @@ func TestMarketplace(t *testing.T){
market.Info()
actual_mm, _ := market.XMLResource.XPath("/MARKETPLACE/TEMPLATE/MARKET_MAD")
actual_1, _ := market.XMLResource.XPath("/MARKETPLACE/TEMPLATE/ATT1")
actual_mm := market.MarketMad
actual_1, err := market.Template.Dynamic.GetContentByName("ATT1")
if err != nil {
t.Errorf("Test failed, can't retrieve '%s', error: %s", "ATT1", err.Error())
} else {
if actual_1 != "VAL1" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL1", actual_1)
}
}
if actual_mm != "http" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "http", actual_mm)
}
if actual_1 != "VAL1" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL1", actual_1)
}
//Change permissions for Marketpkace
err = market.Chmod(1,1,1,1,1,1,1,1,1)
@ -62,11 +64,11 @@ func TestMarketplace(t *testing.T){
market.Info()
expected := "111111111"
actual, _ = market.XMLResource.XPath("/MARKETPLACE/PERMISSIONS")
expected_perm := Permissions{ 1, 1, 1, 1, 1, 1, 1, 1, 1 }
actual_perm := *market.Permissions
if actual != expected {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected, actual)
if actual_perm != expected_perm {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected_perm.String(), actual_perm.String())
}
//Change owner of Marketpkace
@ -78,17 +80,17 @@ func TestMarketplace(t *testing.T){
market.Info()
expected_usr := "1"
expected_grp := "1"
actual_usr, _ :=market.XMLResource.XPath("/MARKETPLACE/UID")
actual_grp, _ :=market.XMLResource.XPath("/MARKETPLACE/GID")
expected_usr := 1
expected_grp := 1
actual_usr := market.UID
actual_grp := market.GID
if actual_usr != expected_usr {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected_usr, actual_usr)
t.Errorf("Test failed, expected: '%d', got: '%d'", expected_usr, actual_usr)
}
if actual_grp != expected_grp {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected_grp, actual_grp)
t.Errorf("Test failed, expected: '%d', got: '%d'", expected_grp, actual_grp)
}
rename := mkt_name + "-renamed"
@ -102,7 +104,7 @@ func TestMarketplace(t *testing.T){
market.Info()
actual, _ = market.XMLResource.XPath("/MARKETPLACE/NAME")
actual = market.Name
if actual != rename {
t.Errorf("Test failed, expected: '%s', got: '%s'", rename, actual)

View File

@ -1,17 +1,44 @@
package goca
import "errors"
// MarketPlaceApp represents an OpenNebula MarketPlaceApp
type MarketPlaceApp struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
)
// MarketPlaceAppPool represents an OpenNebula MarketPlaceAppPool
type MarketPlaceAppPool struct {
XMLResource
MarketPlaceApps []MarketPlaceApp `xml:"MARKETPLACEAPP"`
}
// MarketPlaceApp represents an OpenNebula MarketPlaceApp
type MarketPlaceApp struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
LockInfos *Lock `xml:"LOCK"`
Permissions *Permissions `xml:"PERMISSIONS"`
RegTime int `xml:"REGTIME"`
Name string `xml:"NAME"`
ZoneId string `xml:"ZONE_ID"`
OriginId string `xml:"ORIGIN_ID"`
Source string `xml:"SOURCE"`
MD5 string `xml:"MD5"`
Size int `xml:"SIZE"`
Description string `xml:"DESCRIPTION"`
Version string `xml:"VERSION"`
Format string `xml:"FORMAT"`
AppTemplate64 string `xml:"APPTEMPLATE64"`
MarketPlaceID int `xml:"MARKETPLACEID"`
MarketPlace string `xml:"MARKETPLACE"`
State int `xml:"STATE"`
Type int `xml:"TYPE"`
Template marketPlaceAppTemplate `xml:"TEMPLATE"`
}
type marketPlaceAppTemplate struct {
Dynamic unmatchedTagsSlice `xml:,any`
}
// NewMarketPlaceAppPool returns a marketplace app pool. A connection to OpenNebula is
@ -41,9 +68,13 @@ func NewMarketPlaceAppPool(args ...int) (*MarketPlaceAppPool, error) {
return nil, err
}
marketapppool := &MarketPlaceAppPool{XMLResource{body: response.Body()}}
marketappPool := &MarketPlaceAppPool{}
err = xml.Unmarshal([]byte(response.Body()), marketappPool)
if err != nil {
return nil, err
}
return marketapppool, err
return marketappPool, nil
}
// NewMarketPlaceApp finds a marketplace app object by ID. No connection to OpenNebula.
@ -55,14 +86,26 @@ func NewMarketPlaceApp(id uint) *MarketPlaceApp {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the marketplace app.
func NewMarketPlaceAppFromName(name string) (*MarketPlaceApp, error) {
var id uint
marketAppPool, err := NewMarketPlaceAppPool()
if err != nil {
return nil, err
}
id, err := marketAppPool.GetIDFromName(name, "/MARKETPLACEAPP_POOL/MARKETPLACEAPP")
if err != nil {
return nil, err
match := false
for i := 0; i < len(marketAppPool.MarketPlaceApps); i++ {
if marketAppPool.MarketPlaceApps[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = marketAppPool.MarketPlaceApps[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewMarketPlaceApp(id), nil
@ -137,8 +180,7 @@ func (marketApp *MarketPlaceApp) Info() error {
if err != nil {
return err
}
marketApp.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), marketApp)
}
// Lock locks the marketplace app depending on blocking level.

View File

@ -53,7 +53,7 @@ func TestMarketplaceApp(t *testing.T){
mkt_app = NewMarketPlaceApp(app_id)
mkt_app.Info()
actual, _:= mkt_app.XMLResource.XPath("/MARKETPLACEAPP/NAME")
actual := mkt_app.Name
if actual != mkt_app_name {
t.Errorf("Test failed, expected: '%s', got: '%s'", mkt_app_name, actual)

View File

@ -0,0 +1,23 @@
package goca
//import "fmt"
type Permissions struct {
OwnerU int `xml:"OWNER_U"`
OwnerM int `xml:"OWNER_M"`
OwnerA int `xml:"OWNER_A"`
GroupU int `xml:"GROUP_U"`
GroupM int `xml:"GROUP_M"`
GroupA int `xml:"GROUP_A"`
OtherU int `xml:"OTHER_U"`
OtherM int `xml:"OTHER_M"`
OtherA int `xml:"OTHER_A"`
}
func (p *Permissions) String() string {
permStr := [8]string{"uma", "um-", "u-a", "u--", "-ma", "-m-", "--a", "---"}
owner := permStr[p.OwnerU<<2|p.OwnerM<<1|p.OwnerA]
group := permStr[p.GroupU<<2|p.GroupM<<1|p.GroupA]
other := permStr[p.OtherU<<2|p.OtherM<<1|p.OtherA]
return owner + group + other
}

View File

@ -0,0 +1,50 @@
package goca
type quotas struct {
ID uint `xml:"ID"`
quotasList
}
type quotasList struct {
DatastoreQuotas []datastoreQuota `xml:"DATASTORE_QUOTA>DATASTORE"`
NetworkQuotas []networkQuota `xml:"NETWORK_QUOTA>NETWORK"`
VMQuotas []vmQuota `xml:"VM_QUOTA>VM"`
ImageQuotas []imageQuota `xml:"IMAGE_QUOTA>IMAGE"`
}
type datastoreQuota struct {
ID string `xml:"ID"`
Images string `xml:"IMAGES"`
ImagesUsed string `xml:"IMAGES_USED"`
Size string `xml:"SIZE"`
SizeUsed string `xml:"SIZE_USED"`
}
type networkQuota struct {
ID string `xml:"ID"`
Leases string `xml:"LEASES"`
LeasesUsed string `xml:"LEASES_USED"`
}
type vmQuota struct {
CPU string `xml:"CPU"`
CPUUsed string `xml:"CPU_USED"`
Memory string `xml:"MEMORY"`
MemoryUsed string `xml:"MEMORY_USED"`
RunningCpu string `xml:"RUNNING_CPU"`
RunningCpuUsed string `xml:"RUNNING_CPU_USED"`
RunningMemory string `xml:"RUNNING_MEMORY"`
RunningMemoryUsed string `xml:"RUNNING_MEMORY_USED"`
RunningVMs string `xml:"RUNNING_VMS"`
RunningVMsUsed string `xml:"RUNNING_VMS_USED"`
SystemDiskSize string `xml:"SYSTEM_DISK_SIZE"`
SystemDiskSizeUsed string `xml:"SYSTEM_DISK_SIZE_USED"`
VMs string `xml:"VMS"`
VMsUsed string `xml:"VMS_USED"`
}
type imageQuota struct {
ID string `xml:"ID"`
RVMs string `xml:"RVMS"`
RVMsUsed string `xml:"RVMS_USED"`
}

View File

@ -1,17 +1,41 @@
package goca
import "errors"
// SecurityGroup represents an OpenNebula SecurityGroup
type SecurityGroup struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
)
// SecurityGroupPool represents an OpenNebula SecurityGroupPool
type SecurityGroupPool struct {
XMLResource
SecurityGroups []SecurityGroup `xml:"SECURITY_GROUP"`
}
// SecurityGroup represents an OpenNebula SecurityGroup
type SecurityGroup struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
Permissions *Permissions `xml:"PERMISSIONS"`
UpdatedVMs []int `xml:"UPDATED_VMS>ID"`
OutdatedVMs []int `xml:"OUTDATED_VMS>ID"`
UpdatingVMs []int `xml:"UPDATING_VMS>ID"`
ErrorVMs []int `xml:"ERROR_VMS>ID"`
Template securityGroupTemplate `xml:"TEMPLATE"`
}
// VirtualRouterTemplate represent the template part of the OpenNebula VirtualRouter
type securityGroupTemplate struct {
Description string `xml:"DESCRIPTION"`
Rules []securityGroupRule `xml:"RULE"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
type securityGroupRule struct {
Protocol string `xml:"PROTOCOL"`
RuleType string `xml:"RULE_TYPE"`
}
// NewSecurityGroupPool returns a security group pool. A connection to OpenNebula is
@ -41,9 +65,13 @@ func NewSecurityGroupPool(args ...int) (*SecurityGroupPool, error) {
return nil, err
}
secgrouppool := &SecurityGroupPool{XMLResource{body: response.Body()}}
secgroupPool := &SecurityGroupPool{}
err = xml.Unmarshal([]byte(response.Body()), secgroupPool)
if err != nil {
return nil, err
}
return secgrouppool, err
return secgroupPool, nil
}
// NewSecurityGroup finds a security group object by ID. No connection to OpenNebula.
@ -55,14 +83,26 @@ func NewSecurityGroup(id uint) *SecurityGroup {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the security group.
func NewSecurityGroupFromName(name string) (*SecurityGroup, error) {
var id uint
secgroupPool, err := NewSecurityGroupPool()
if err != nil {
return nil, err
}
id, err := secgroupPool.GetIDFromName(name, "/SECURITY_GROUP_POOL/SECURITY_GROUP")
if err != nil {
return nil, err
match := false
for i := 0; i < len(secgroupPool.SecurityGroups); i++ {
if secgroupPool.SecurityGroups[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = secgroupPool.SecurityGroups[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewSecurityGroup(id), nil
@ -146,6 +186,5 @@ func (sg *SecurityGroup) Info() error {
if err != nil {
return err
}
sg.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), sg)
}

View File

@ -22,7 +22,7 @@ func TestSGAllocate(t *testing.T){
sg = NewSecurityGroup(sg_id)
sg.Info()
actual, _:= sg.XMLResource.XPath("/SECURITY_GROUP/NAME")
actual := sg.Name
if actual != sg_name {
t.Errorf("Test failed, expected: '%s', got: '%s'", sg_name, actual)
@ -39,15 +39,22 @@ func TestSGAllocate(t *testing.T){
sg.Info()
actual_1, _ := sg.XMLResource.XPath("/SECURITY_GROUP/TEMPLATE/ATT1")
actual_3, _ := sg.XMLResource.XPath("/SECURITY_GROUP/TEMPLATE/ATT3")
if actual_1 != "VAL1" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL1", actual_1)
actual_1, err := sg.Template.Dynamic.GetContentByName("ATT1")
if err != nil {
t.Errorf("Test failed, can't retrieve '%s', error: %s", "ATT1", err.Error())
} else {
if actual_1 != "VAL1" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL1", actual_1)
}
}
if actual_3 != "VAL3" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL3", actual_3)
actual_3, err := sg.Template.Dynamic.GetContentByName("ATT3")
if err != nil {
t.Errorf("Test failed, can't retrieve '%s', error: %s", "ATT3", err.Error())
} else {
if actual_3 != "VAL3" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL3", actual_3)
}
}
clone_name := sg_name + "-cloned"
@ -62,7 +69,7 @@ func TestSGAllocate(t *testing.T){
clone := NewSecurityGroup(clone_id)
clone.Info()
actual, _ = clone.XMLResource.XPath("/SECURITY_GROUP/NAME")
actual = sg.Name
if actual != clone_name {
t.Errorf("Test failed, expected: '%s', got: '%s'", clone_name, actual)
@ -79,11 +86,11 @@ func TestSGAllocate(t *testing.T){
sg.Info()
expected := "111111111"
actual, _ = sg.XMLResource.XPath("/SECURITY_GROUP/PERMISSIONS")
expected_perm := Permissions{1, 1, 1, 1, 1, 1, 1, 1, 1}
actual_perm := sg.Permissions
if actual != expected {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected, actual)
if actual_perm == nil || *actual_perm != expected_perm {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected_perm.String(), actual_perm.String())
}
//Change owner of SG
@ -95,17 +102,17 @@ func TestSGAllocate(t *testing.T){
sg.Info()
expected_usr := "1"
expected_grp := "1"
actual_usr, _ := sg.XMLResource.XPath("/SECURITY_GROUP/UID")
actual_grp, _ := sg.XMLResource.XPath("/SECURITY_GROUP/GID")
expected_usr := 1
expected_grp := 1
actual_usr := sg.UID
actual_grp := sg.GID
if actual_usr != expected_usr {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected_usr, actual_usr)
t.Errorf("Test failed, expected: '%d', got: '%d'", expected_usr, actual_usr)
}
if actual_grp != expected_grp {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected_grp, actual_grp)
t.Errorf("Test failed, expected: '%d', got: '%d'", expected_grp, actual_grp)
}
//Rename SG
@ -118,7 +125,7 @@ func TestSGAllocate(t *testing.T){
sg.Info()
actual, _ = sg.XMLResource.XPath("/SECURITY_GROUP/NAME")
actual = sg.Name
if actual != rename {
t.Errorf("Test failed, expected: '%s', got: '%s'", rename, actual)

View File

@ -0,0 +1,40 @@
package goca
// An user can take snapshot on VM, or on VM disks
// Common part
type snapshot struct {
Children string `xml:"CHILDREN"` //minOccur=0
Active string `xml:"ACTIVE"` //minOccur=0
Date int `xml:"DATE"`
ID int `xml:"ID"`
Name string `xml:"NAME"` //minOccur=0
Parent int `xml:"PARENT"`
Size int `xml:"SIZE"`
}
// Image entity related
type ImageSnapshot struct {
AllowOrphans string `xml:"ALLOW_ORPHANS"`
CurrentBase int `xml:"CURRENT_BASE"`
NextSnapshot int `xml:"NEXT_SNAPSHOT"`
Snapshots []snapshot `xml:"SNAPSHOT"`
}
// VM entity related
type VMSnapshot struct {
HypervisorID string `xml:"HYPERVISOR_ID"`
Name string `xml:"NAME"`
ID int `xml:"SNAPSHOT_ID"`
Time string `xml:"TIME"`
}
type vmHistoryRecordSnapshot struct {
ImageSnapshot
DiskID int `xml:"DISK_ID"`
}
type vmMonitoringSnapshotSize struct {
DiskID int `xml:"DISK_ID"`
Size int `xml:"SIZE"`
}

View File

@ -1,19 +1,65 @@
package goca
import (
"encoding/xml"
"errors"
)
// Template represents an OpenNebula Template
type Template struct {
XMLResource
ID uint
Name string
}
// TemplatePool represents an OpenNebula TemplatePool
type TemplatePool struct {
XMLResource
Templates []Template `xml:"VMTEMPLATE"`
}
// Template represents an OpenNebula Template
type Template struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
LockInfos *Lock `xml:"LOCK"`
Permissions *Permissions `xml:"PERMISSIONS"`
RegTime int `xml:"REGTIME"`
Template templateTemplate `xml:"TEMPLATE"`
}
// templateTemplate represent the template part of the OpenNebula Template
type templateTemplate struct {
CPU float64 `xml:"CPU"`
Memory int `xml:"MEMORY"`
Context *templateContext `xml:"CONTEXT"`
Disk []templateDisk `xml:"DISK"`
Graphics *templateGraphics `xml:"GRAPHICS"`
NICDefault *templateNicDefault `xml:"NIC_DEFAULT"`
OS *templateOS `xml:"OS"`
UserInputs templateUserInputs `xml:"USER_INPUTS"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
type templateContext struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
type templateDisk struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
type templateGraphics struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
type templateUserInputs struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
type templateNicDefault struct {
Model string `xml:"MODEL"`
}
type templateOS struct {
Arch string `xml:"ARCH"`
Boot string `xml:"BOOT"`
}
// NewTemplatePool returns a template pool. A connection to OpenNebula is
@ -39,10 +85,13 @@ func NewTemplatePool(args ...int) (*TemplatePool, error) {
return nil, err
}
templatepool := &TemplatePool{XMLResource{body: response.Body()}}
return templatepool, err
templatePool := &TemplatePool{}
err = xml.Unmarshal([]byte(response.Body()), templatePool)
if err != nil {
return nil, err
}
return templatePool, nil
}
// NewTemplate finds a template object by ID. No connection to OpenNebula.
@ -54,14 +103,26 @@ func NewTemplate(id uint) *Template {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the template.
func NewTemplateFromName(name string) (*Template, error) {
var id uint
templatePool, err := NewTemplatePool()
if err != nil {
return nil, err
}
id, err := templatePool.GetIDFromName(name, "/VMTEMPLATE_POOL/VMTEMPLATE")
if err != nil {
return nil, err
match := false
for i := 0; i < len(templatePool.Templates); i++ {
if templatePool.Templates[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = templatePool.Templates[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewTemplate(id), nil
@ -83,8 +144,7 @@ func (template *Template) Info() error {
if err != nil {
return err
}
template.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), template)
}
// Update will modify the template. If appendTemplate is 0, it will

View File

@ -5,7 +5,7 @@ import (
)
// Helper to create a template
func createTemplate(t *testing.T) *Template {
func createTemplate(t *testing.T) (*Template, uint) {
templateName := GenName("template")
// Create template
@ -28,28 +28,22 @@ func createTemplate(t *testing.T) *Template {
t.Error(err)
}
return template
return template, id
}
func TestTemplateCreateAndDelete(t *testing.T) {
template := createTemplate(t)
var err error
idParse, err := GetID(t, template, "VMTEMPLATE")
if err != nil {
t.Error(err)
}
template, idOrig := createTemplate(t)
if idParse != template.ID {
idParse := template.ID
if idParse != idOrig {
t.Errorf("Template ID does not match")
}
// Get template by Name
templateName, ok := template.XPath("/VMTEMPLATE/NAME")
if !ok {
t.Errorf("Could not get name")
}
template, err = NewTemplateFromName(templateName)
name := template.Name
template, err = NewTemplateFromName(name)
if err != nil {
t.Fatal(err)
}
@ -59,9 +53,8 @@ func TestTemplateCreateAndDelete(t *testing.T) {
t.Error(err)
}
idParse, err = GetID(t, template, "VMTEMPLATE")
if idParse != template.ID {
idParse = template.ID
if idParse != idOrig {
t.Errorf("Template ID does not match")
}
@ -107,7 +100,7 @@ func TestTemplateInstantiate(t *testing.T) {
}
func TestTemplateUpdate(t *testing.T) {
template := createTemplate(t)
template, _ := createTemplate(t)
tpl := NewTemplateBuilder()
tpl.AddValue("A", "B")
@ -120,8 +113,13 @@ func TestTemplateUpdate(t *testing.T) {
t.Error(err)
}
if val, ok := template.XPath("/VMTEMPLATE/TEMPLATE/A"); !ok || val != "B" {
t.Errorf("Expecting A=B")
val, err := template.Template.Dynamic.GetContentByName("A")
if err != nil {
t.Errorf("Test failed, can't retrieve '%s', error: %s", "A", err.Error())
} else {
if val != "B" {
t.Errorf("Expecting A=B")
}
}
// Delete template

View File

@ -0,0 +1,101 @@
package goca
import (
"encoding/xml"
"fmt"
)
// Common part
// UnmatchedTag contains the tag informations
type UnmatchedTag struct {
XMLName xml.Name
Content string `xml:",chardata"`
//FullContent string `xml:",innerxml"` // for debug purpose, allow to see what's inside some tags
}
// Store unmatched tags in a map
// Inspired from: https://stackoverflow.com/questions/30928770/marshall-map-to-xml-in-go/33110881
// NOTE: to be used in flat xml part with distinct tag names
// If it's not flat: the hash will contains key with empty values
// If there is several tags with the same name : only the last value will be stored
// UnmatchedTagsMap store tags not handled by Unmarshal in a map, it should be labelled with `xml",any"`
type unmatchedTagsMap map[string]string
func (m *unmatchedTagsMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
if *m == nil {
*m = unmatchedTagsMap{}
}
e := UnmatchedTag{}
err := d.DecodeElement(&e, &start)
if err != nil {
return err
}
// Fail the parsing of the whole xml
//if _, ok := (*m)[e.XMLName.Local]; ok {
// return fmt.Errorf("UnmatchedTagsMap: UnmarshalXML: Tag %s: multiple entries with the same name", e.XMLName.Local)
//}
(*m)[e.XMLName.Local] = e.Content
return nil
}
func (u *unmatchedTagsMap) GetContentByName(name string) string {
return ((map[string]string)(*u))[name]
}
// Store unmatched tags in a slice
// NOTE: to be used in flat xml part
// UnmatchedTagsSlice store tags not handled by Unmarshal in a slice, it should be labelled with `xml",any"`
type unmatchedTagsSlice struct {
Tags []UnmatchedTag
}
func (u *unmatchedTagsSlice) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var e UnmatchedTag
err := d.DecodeElement(&e, &start)
if err != nil {
return err
}
u.Tags = append(u.Tags, e)
return nil
}
// Retrieve slice of tags with given name
func (u *unmatchedTagsSlice) GetContentSliceByName(name string) []string {
content := make([]string, 0, 1)
for _, t := range u.Tags {
if t.XMLName.Local != name {
continue
}
content = append(content, t.Content)
}
return content
}
// Retrieve a tag with given name, fail if not present or present more than once
func (u *unmatchedTagsSlice) GetContentByName(name string) (string, error) {
var content string
match := false
for _, t := range u.Tags {
if t.XMLName.Local != name {
continue
}
if match == true {
return "", fmt.Errorf("GetContentByName: multiple entries with the name %s", name)
}
content = t.Content
match = true
}
if match == false {
return "", fmt.Errorf("GetContentByName: tag %s not found", name)
}
return content, nil
}

View File

@ -1,15 +1,46 @@
package goca
// User represents an OpenNebula User
type User struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
)
// UserPool represents an OpenNebula UserPool
type UserPool struct {
XMLResource
Users []userBase `xml:"USER"`
Quotas []quotas `xml:"QUOTAS"`
DefaultUserQuotas quotasList `xml:"DEFAULT_USER_QUOTAS"`
}
// User represents an OpenNebula user
type User struct {
userBase
quotasList
DefaultUserQuotas quotasList `xml:"DEFAULT_USER_QUOTAS"`
}
// User represents an OpenNebula User
type userBase struct {
ID uint `xml:"ID"`
GID int `xml:"GID"`
GroupsID []int `xml:"GROUPS>ID"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
Password string `xml:"PASSWORD"`
AuthDriver string `xml:"AUTH_DRIVER"`
Enabled int `xml:"ENABLED"`
LoginTokens []loginToken `xml:"LOGIN_TOKEN"`
Template userTemplate `xml:"TEMPLATE"`
}
type userTemplate struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
type loginToken struct {
Token string `xml:"TOKEN"`
ExpirationTime int `xml:"EXPIRATION_TIME"`
EGID int `xml:"EGID"`
}
// NewUserPool returns a user pool. A connection to OpenNebula is
@ -20,28 +51,44 @@ func NewUserPool() (*UserPool, error) {
return nil, err
}
userpool := &UserPool{XMLResource{body: response.Body()}}
userpool := &UserPool{}
err = xml.Unmarshal([]byte(response.Body()), userpool)
if err != nil {
return nil, err
}
return userpool, err
return userpool, nil
}
// NewUser finds a user object by ID. No connection to OpenNebula.
func NewUser(id uint) *User {
return &User{ID: id}
return &User{userBase: userBase{ID: id}}
}
// NewUserFromName finds a user object by name. It connects to
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the user.
func NewUserFromName(name string) (*User, error) {
var id uint
userPool, err := NewUserPool()
if err != nil {
return nil, err
}
id, err := userPool.GetIDFromName(name, "/USER_POOL/USER")
if err != nil {
return nil, err
match := false
for i := 0; i < len(userPool.Users); i++ {
if userPool.Users[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = userPool.Users[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewUser(id), nil
@ -133,6 +180,5 @@ func (user *User) Info() error {
if err != nil {
return err
}
user.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), user)
}

View File

@ -1,15 +1,49 @@
package goca
// Vdc represents an OpenNebula Vdc
type Vdc struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
)
// VdcPool represents an OpenNebula VdcPool
type VdcPool struct {
XMLResource
Vdcs []Vdc `xml:"VDC"`
}
// Vdc represents an OpenNebula Vdc
type Vdc struct {
ID uint `xml:"ID"`
Name string `xml:"NAME"`
GroupsID []int `xml:"GROUPS>ID"`
Clusters []vdcCluster `xml:"CLUSTERS>CLUSTER"`
Hosts []vdcHost `xml:"HOSTS>HOST"`
Datastores []vdcDatastore `xml:"DATASTORES>DATASTORE"`
VNets []vdcVNet `xml:"VNETS>VNET"`
Template vdcTemplate `xml:"TEMPLATE"`
}
type vdcTemplate struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
type vdcCluster struct {
ZoneID int `xml:"ZONE_ID"`
ClusterID int `xml:"CLUSTER_ID"`
}
type vdcHost struct {
ZoneID int `xml:"ZONE_ID"`
HostID int `xml:"HOST_ID"`
}
type vdcDatastore struct {
ZoneID int `xml:"ZONE_ID"`
DatastoreID int `xml:"DATASTORE_ID"`
}
type vdcVNet struct {
ZoneID int `xml:"ZONE_ID"`
VnetID int `xml:"VNET_ID"`
}
// NewVdcPool returns a vdc pool. A connection to OpenNebula is
@ -20,9 +54,13 @@ func NewVdcPool() (*VdcPool, error) {
return nil, err
}
vdcpool := &VdcPool{XMLResource{body: response.Body()}}
vdcPool := &VdcPool{}
err = xml.Unmarshal([]byte(response.Body()), vdcPool)
if err != nil {
return nil, err
}
return vdcpool, err
return vdcPool, nil
}
// NewVdc finds a vdc object by ID. No connection to OpenNebula.
@ -34,14 +72,26 @@ func NewVdc(id uint) *Vdc {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the vdc.
func NewVdcFromName(name string) (*Vdc, error) {
var id uint
vdcPool, err := NewVdcPool()
if err != nil {
return nil, err
}
id, err := vdcPool.GetIDFromName(name, "/VDC_POOL/VDC")
if err != nil {
return nil, err
match := false
for i := 0; i < len(vdcPool.Vdcs); i++ {
if vdcPool.Vdcs[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = vdcPool.Vdcs[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewVdc(id), nil
@ -88,8 +138,7 @@ func (vdc *Vdc) Info() error {
if err != nil {
return err
}
vdc.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), vdc)
}
// AddGroup adds a group to the VDC

View File

@ -1,17 +1,97 @@
package goca
import "errors"
// VirtualNetwork represents an OpenNebula VirtualNetwork
type VirtualNetwork struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
)
// VirtualNetworkPool represents an OpenNebula VirtualNetworkPool
type VirtualNetworkPool struct {
XMLResource
VirtualNetworks []virtualNetworkPoolBase `xml:"VNET"`
}
// VirtualNetwork represents an OpenNebula VirtualNetwork
type VirtualNetwork struct {
virtualNetworkBase
Lock *Lock `xml:"LOCK"`
ARs []virtualNetworkAR `xml:"AR_POOL>AR"`
}
// VirtualNetworkBase represents common attributes for parts of VirtualNetworkPool and VirtualNetwork
type virtualNetworkBase struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
Permissions *Permissions `xml:"PERMISSIONS"`
Clusters []int `xml:"CLUSTERS>ID"`
Bridge string `xml:"BRIDGE"`
BridgeType string `xml:"BRIDGE_TYPE"` // minOccurs=0
ParentNetworkID string `xml:"PARENT_NETWORK_ID"`
VNMad string `xml:"VN_MAD"`
PhyDev string `xml:"PHYDEV"`
VlanID string `xml:"VLAN_ID"` // minOccurs=0
OuterVlanID string `xml:"OUTER_VLAN_ID"` // minOccurs=0
VlanIDAutomatic string `xml:"VLAN_ID_AUTOMATIC"`
OuterVlanIDAutomatic string `xml:"OUTER_VLAN_ID_AUTOMATIC"`
UsedLeases int `xml:"USED_LEASES"`
VRouters []int `xml:"VROUTERS>ID"`
Template virtualNetworkTemplate `xml:"TEMPLATE"`
}
type virtualNetworkTemplate struct {
Dynamic unmatchedTagsSlice `xml:",any"`
}
// AR represent an Address Range
type virtualNetworkPoolBase struct {
virtualNetworkBase
ARs []virtualNetworkARPool `xml:"AR_POOL>AR"`
}
type virtualNetworkARBase struct {
ARID string `xml:"AR_ID"`
GlobalPrefix string `xml:"GLOBAL_PREFIX"` // minOccurs=0
IP string `xml:"IP"` // minOccurs=0
MAC string `xml:"MAC"`
ParentNetworkARID string `xml:"PARENT_NETWORK_AR_ID"` // minOccurs=0
Size int `xml:"SIZE"`
Type string `xml:"TYPE"`
ULAPrefix string `xml:"ULA_PREFIX"` // minOccurs=0
VNMAD string `xml:"VN_MAD"` // minOccurs=0
}
type virtualNetworkARPool struct {
virtualNetworkARBase
Allocated string `xml:ALLOCATED`
}
type virtualNetworkAR struct {
virtualNetworkARBase
MACEnd string `xml:"MAC_END"`
IPEnd string `xml:"IP_END"`
IP6ULA string `xml:"IP6_ULA"`
IP6ULAEnd string `xml:"IP6_ULA_END"`
IP6Global string `xml:"IP6_GLOBAL"`
IP6GlobalEnd string `xml:"IP6_GLOBAL_END"`
IP6 string `xml:"IP6"`
IP6End string `xml:"IP6_END"`
UsedLeases string `xml:"USED_LEASES"`
Leases []lease `xml:"LEASES>LEASE"`
}
type lease struct {
IP string `xml:"IP"`
IP6 string `xml:"IP6"`
IP6Global string `xml:"IP6Global"`
IP6Link string `xml:"IP6Link"`
IP6ULA string `xml:"IP6ULA"`
MAC string `xml:"MAC"`
VM int `xml:"VM"`
VNet int `xml:"VNet"`
VRouter int `xml:"VRouter"`
}
// NewVirtualNetworkPool returns a virtualnetwork pool. A connection to OpenNebula is
@ -41,28 +121,44 @@ func NewVirtualNetworkPool(args ...int) (*VirtualNetworkPool, error) {
return nil, err
}
vnpool := &VirtualNetworkPool{XMLResource{body: response.Body()}}
vnPool := &VirtualNetworkPool{}
err = xml.Unmarshal([]byte(response.Body()), &vnPool)
if err != nil {
return nil, err
}
return vnpool, err
return vnPool, nil
}
// NewVirtualNetwork finds a virtualnetwork object by ID. No connection to OpenNebula.
func NewVirtualNetwork(id uint) *VirtualNetwork {
return &VirtualNetwork{ID: id}
return &VirtualNetwork{virtualNetworkBase: virtualNetworkBase{ID: id}}
}
// NewVirtualNetworkFromName finds a virtualnetwork object by name. It connects to
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the virtualnetwork.
func NewVirtualNetworkFromName(name string) (*VirtualNetwork, error) {
virtualnetworkPool, err := NewVirtualNetworkPool()
var id uint
virtualNetworkPool, err := NewVirtualNetworkPool()
if err != nil {
return nil, err
}
id, err := virtualnetworkPool.GetIDFromName(name, "/VNET_POOL/VNET")
if err != nil {
return nil, err
match := false
for i := 0; i < len(virtualNetworkPool.VirtualNetworks); i++ {
if virtualNetworkPool.VirtualNetworks[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = virtualNetworkPool.VirtualNetworks[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewVirtualNetwork(id), nil
@ -179,6 +275,5 @@ func (vn *VirtualNetwork) Info() error {
if err != nil {
return err
}
vn.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), vn)
}

View File

@ -15,7 +15,7 @@ VN_MAD = "vxlan"
`
// Helper to create a Virtual Network
func createVirtualNetwork(t *testing.T) *VirtualNetwork {
func createVirtualNetwork(t *testing.T) (*VirtualNetwork, uint) {
id, err := CreateVirtualNetwork(vnTpl, -1)
if err != nil {
t.Error(err)
@ -29,26 +29,21 @@ func createVirtualNetwork(t *testing.T) *VirtualNetwork {
t.Error(err)
}
return vnet
return vnet, id
}
func TestVirtualNetwork(t *testing.T) {
vnet := createVirtualNetwork(t)
var err error
idParse, err := GetID(t, vnet, "VNET")
if err != nil {
t.Error(err)
}
vnet, idOrig := createVirtualNetwork(t)
if idParse != vnet.ID {
idParse := vnet.ID
if idParse != idOrig {
t.Errorf("Virtual Network ID does not match")
}
// Get virtual network by Name
name, ok := vnet.XPath("/VNET/NAME")
if !ok {
t.Errorf("Could not get name")
}
name := vnet.Name
vnet, err = NewVirtualNetworkFromName(name)
if err != nil {
@ -60,9 +55,8 @@ func TestVirtualNetwork(t *testing.T) {
t.Error(err)
}
idParse, err = GetID(t, vnet, "VNET")
if idParse != vnet.ID {
idParse = vnet.ID
if idParse != idOrig {
t.Errorf("Virtual Network ID does not match")
}
@ -78,16 +72,10 @@ func TestVirtualNetwork(t *testing.T) {
}
// Get Virtual Network Owner Name
uname, ok := vnet.XPath("/VNET/UNAME")
if !ok {
t.Errorf("Could not get user name")
}
uname := vnet.UName
// Get Virtual Network owner group Name
gname, ok := vnet.XPath("/VNET/GNAME")
if !ok {
t.Errorf("Could not get group name")
}
// Get Image owner group Name
gname := vnet.GName
// Compare with caller username
caller := strings.Split(client.token, ":")[0]
@ -117,16 +105,10 @@ func TestVirtualNetwork(t *testing.T) {
}
// Get Virtual Network Owner Name
uname, ok = vnet.XPath("/VNET/UNAME")
if !ok {
t.Errorf("Could not get user name")
}
uname = vnet.UName
// Get Virtual Network Owner Name
gname, ok = vnet.XPath("/VNET/GNAME")
if !ok {
t.Errorf("Could not get user name")
}
// Get Image owner group Name
gname = vnet.GName
if "serveradmin" != uname {
t.Error("Virtual network owner is not oenadmin")

View File

@ -1,19 +1,41 @@
package goca
import (
"encoding/xml"
"errors"
)
// VirtualRouter represents an OpenNebula VirtualRouter
type VirtualRouter struct {
XMLResource
ID uint
Name string
}
// VirtualRouterPool represents an OpenNebula VirtualRouterPool
type VirtualRouterPool struct {
XMLResource
VirtualRouters []VirtualRouter `xml:"VROUTER"`
}
// VirtualRouter represents an OpenNebula VirtualRouter
type VirtualRouter struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
LockInfos *Lock `xml:"LOCK"`
Permissions *Permissions `xml:"PERMISSIONS"`
Type int `xml:"TYPE"`
DiskType int `xml:"DISK_TYPE"`
Persistent int `xml:"PERSISTENT"`
VMsID []int `xml:"VMS>ID"`
Template virtualRouterTemplate `xml:"TEMPLATE"`
}
// VirtualRouterTemplate represent the template part of the OpenNebula VirtualRouter
type virtualRouterTemplate struct {
NIC []virtualRouterNIC `xml:"NIC"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
type virtualRouterNIC struct {
NICID int `xml:"NIC_ID"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
// NewVirtualRouterPool returns a virtual router pool. A connection to OpenNebula is
@ -39,10 +61,14 @@ func NewVirtualRouterPool(args ...int) (*VirtualRouterPool, error) {
return nil, err
}
vrouterpool := &VirtualRouterPool{XMLResource{body: response.Body()}}
vrouterPool := &VirtualRouterPool{}
return vrouterpool, err
err = xml.Unmarshal([]byte(response.Body()), vrouterPool)
if err != nil {
return nil, err
}
return vrouterPool, nil
}
// NewVirtualRouter finds a virtual router object by ID. No connection to OpenNebula.
@ -54,14 +80,25 @@ func NewVirtualRouter(id uint) *VirtualRouter {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the virtual router.
func NewVirtualRouterFromName(name string) (*VirtualRouter, error) {
var id uint
vrouterPool, err := NewVirtualRouterPool()
if err != nil {
return nil, err
}
id, err := vrouterPool.GetIDFromName(name, "/VROUTER_POOL/VROUTER")
if err != nil {
return nil, err
match := false
for i := 0; i < len(vrouterPool.VirtualRouters); i++ {
if vrouterPool.VirtualRouters[i].Name == name {
if match {
return nil, errors.New("multiple resources with that name")
}
id = vrouterPool.VirtualRouters[i].ID
match = true
}
}
if !match {
return nil, errors.New("resource not found")
}
return NewVirtualRouter(id), nil
@ -84,8 +121,7 @@ func (vr *VirtualRouter) Info() error {
if err != nil {
return err
}
vr.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), vr)
}
// Update will modify the virtual router. If appendVirtualRouter is 0, it will

View File

@ -24,7 +24,7 @@ func TestVirtualRouter(t *testing.T){
vr = NewVirtualRouter(vr_id)
vr.Info()
actual, _:= vr.XMLResource.XPath("/VROUTER/NAME")
actual := vr.Name
if actual != vr_name {
t.Errorf("Test failed, expected: '%s', got: '%s'", vr_name, actual)
@ -41,15 +41,22 @@ func TestVirtualRouter(t *testing.T){
vr.Info()
actual_1, _ := vr.XMLResource.XPath("/VROUTER/TEMPLATE/ATT1")
actual_3, _ := vr.XMLResource.XPath("/VROUTER/TEMPLATE/ATT3")
if actual_1 != "VAL1" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL1", actual_1)
actual_1, err := vr.Template.Dynamic.GetContentByName("ATT1")
if err != nil {
t.Errorf("Test failed, can't retrieve '%s', error: %s", "ATT1", err.Error())
} else {
if actual_1 != "VAL1" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL1", actual_1)
}
}
if actual_3 != "VAL3" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL3", actual_3)
actual_3, err := vr.Template.Dynamic.GetContentByName("ATT3")
if err != nil {
t.Errorf("Test failed, can't retrieve '%s', error: %s", "ATT3", err.Error())
} else {
if actual_3 != "VAL3" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "VAL3", actual_3)
}
}
//Change permissions of VirtualRouter
@ -61,11 +68,11 @@ func TestVirtualRouter(t *testing.T){
vr.Info()
expected := "111111111"
actual, _ = vr.XMLResource.XPath("/VROUTER/PERMISSIONS")
expected_perm := Permissions{1, 1, 1, 1, 1, 1, 1, 1, 1}
actual_perm := vr.Permissions
if actual != expected {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected, actual)
if actual_perm == nil || *actual_perm != expected_perm {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected_perm.String(), actual_perm.String())
}
//Change owner of VirtualRouter
@ -77,17 +84,17 @@ func TestVirtualRouter(t *testing.T){
vr.Info()
expected_usr := "1"
expected_grp := "1"
actual_usr, _ := vr.XMLResource.XPath("/VROUTER/UID")
actual_grp, _ := vr.XMLResource.XPath("/VROUTER/GID")
expected_usr := 1
expected_grp := 1
actual_usr := vr.UID
actual_grp := vr.GID
if actual_usr != expected_usr {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected_usr, actual_usr)
t.Errorf("Test failed, expected: '%d', got: '%d'", expected_usr, actual_usr)
}
if actual_grp != expected_grp {
t.Errorf("Test failed, expected: '%s', got: '%s'", expected_grp, actual_grp)
t.Errorf("Test failed, expected: '%d', got: '%d'", expected_grp, actual_grp)
}
rename := vr_name + "-renamed"
@ -101,7 +108,7 @@ func TestVirtualRouter(t *testing.T){
vr.Info()
actual, _ = vr.XMLResource.XPath("/VROUTER/NAME")
actual = vr.Name
if actual != rename {
t.Errorf("Test failed, expected: '%s', got: '%s'", rename, actual)
@ -149,10 +156,13 @@ func TestVirtualRouter(t *testing.T){
vr.Info()
actual, _ = vr.XMLResource.XPath("/VROUTER/TEMPLATE/NIC/NETWORK")
if actual != "go-net" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "go-net", actual)
actualNet, err := vr.Template.Dynamic.GetContentByName("NETWORK")
if err != nil {
t.Errorf("Test failed, can't retrieve '%s', error: %s", "NETWORK", err.Error())
} else {
if actualNet != "go-net" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "go-net", actualNet)
}
}
vnet := NewVirtualNetwork(vnet_id)
@ -174,9 +184,12 @@ func TestVirtualRouter(t *testing.T){
vr.Info()
actual, _ = vr.XMLResource.XPath("/VROUTER/LOCK/LOCKED")
if actual != "4" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "4", actual)
actualLock := vr.LockInfos
if actualLock == nil {
t.Errorf("Test failed, expected: '%s', got: '%s'", "LockInfos", "nil")
}
if actualLock.Locked != 4 {
t.Errorf("Test failed, expected: '%d', got: '%d'", 4, actualLock.Locked)
}
//Unlock VirtualRouter
@ -188,9 +201,13 @@ func TestVirtualRouter(t *testing.T){
vr.Info()
actual, _= vr.XMLResource.XPath("/VROUTER/LOCK/LOCKED")
if actual != "" {
t.Errorf("Test failed, expected: '%s', got: '%s'", "", actual)
actualLock = vr.LockInfos
if actualLock == nil {
t.Errorf("Test failed, expected: '%s', got: '%s'", "LockInfos", "nil")
}
// XXX is it useful ? fail at previous part ?
if actualLock.Locked != 0 {
t.Errorf("Test failed, expected: '%d', got: '%d'", 0, actualLock.Locked)
}
//Delete VirtualRouter

View File

@ -1,20 +1,146 @@
package goca
import (
"encoding/xml"
"errors"
"strconv"
"fmt"
)
// VM represents an OpenNebula Virtual Machine
type VM struct {
XMLResource
ID uint
Name string
}
// VMPool represents an OpenNebula Virtual Machine pool
type VMPool struct {
XMLResource
VMs []vmBase `xml:"VM"`
}
// VM represents an OpenNebula Virtual Machine
type VM struct {
vmBase
LockInfos *Lock `xml:"LOCK"`
}
type vmBase struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
Permissions *Permissions `xml:"PERMISSIONS"`
LastPoll int `xml:"LAST_POLL"`
StateRaw int `xml:"STATE"`
LCMStateRaw int `xml:"LCM_STATE"`
PrevStateRaw int `xml:"PREV_STATE"`
PrevLCMStateRaw int `xml:"PREV_LCM_STATE"`
ReschedValue int `xml:"RESCHED"`
STime int `xml:"STIME"`
ETime int `xml:"ETIME"`
DeployID string `xml:"DEPLOY_ID"`
Monitoring vmMonitoring `xml:"MONITORING"`
Template vmTemplate `xml:"TEMPLATE"`
UserTemplate *vmUserTemplate `xml:"USER_TEMPLATE"`
HistoryRecords []vmHistoryRecord `xml:"HISTORY_RECORDS>HISTORY"`
}
type vmMonitoring struct {
DiskSize []vmMonitoringDiskSize `xml:"DISK_SIZE"`
SnapshotSize []vmMonitoringSnapshotSize `xml:"SNAPSHOT_SIZE"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
type vmMonitoringDiskSize struct {
ID int `xml:"ID"`
Size int `xml:"SIZE"`
}
// History records
type vmHistoryRecord struct {
OID int `xml:"OID"`
SEQ int `xml:"SEQ"`
Hostname string `xml:"HOSTNAME"`
HID int `xml:"HID"`
CID int `xml:"CID"`
DSID int `xml:"DS_ID"`
Action int `xml:"ACTION"`
UID int `xml:"UID"`
GID int `xml:"GID"`
RequestID string `xml:"REQUEST_ID"`
PSTime int `xml:"PSTIME"`
PETime int `xml:"PETIME"`
RSTime int `xml:"RSTIME"`
RETime int `xml:"RETIME"`
ESTime int `xml:"ESTIME"`
EETime int `xml:"EETIME"`
STime int `xml:"STIME"`
ETime int `xml:"ETIME"`
VMMad string `xml:"VM_MAD"`
TMMad string `xml:"TM_MAD"`
Snapshots []vmHistoryRecordSnapshot `xml:"SNAPSHOTS"`
}
// VMUserTemplate contain custom attributes
type vmUserTemplate struct {
Error string `xml:"ERROR"`
SchedMessage string `xml:"SCHED_MESSAGE"`
Dynamic unmatchedTagsMap `xml:",any"`
}
type vmTemplate struct {
CPU float64 `xml:"CPU"`
Memory int `xml:"MEMORY"`
NIC []vmNic `xml:"NIC"`
NICAlias []vmNicAlias `xml:"NIC_ALIAS"`
Context *vmContext `xml:"CONTEXT"`
Disk []vmDisk `xml:"DISK"`
Graphics *vmGraphics `xml:"GRAPHICS"`
OS *vmOS `xml:"OS"`
Snapshot []VMSnapshot `xml:"SNAPSHOT"`
SecurityGroupRule []vmSecurityGroupRule `xml:"SECURITY_GROUP_RULE"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
type vmContext struct {
Dynamic unmatchedTagsMap `xml:",any"`
}
type vmNic struct {
ID int `xml:"NIC_ID"`
Network string `xml:"NETWORK"`
IP string `xml:"IP"`
MAC string `xml:"MAC"`
PhyDev string `xml:"PHYDEV"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
type vmNicAlias struct {
ID int `xml:"NIC_ID"` // minOccurs=1
Parent string `xml:"PARENT"` // minOccurs=1
ParentId string `xml:"PARENT_ID"` // minOccurs=1
}
type vmGraphics struct {
Listen string `xml:"LISTEN"`
Port string `xml:"PORT"`
Type string `xml:"TYPE"`
}
type vmDisk struct {
ID int `xml:"DISK_ID"`
Datastore string `xml:"DATASTORE"`
DiskType string `xml:"DISK_TYPE"`
Image string `xml:"IMAGE"`
Driver string `xml:"DRIVER"`
OriginalSize int `xml:"ORIGINAL_SIZE"`
Size int `xml:"SIZE"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
type vmOS struct {
Arch string `xml:"ARCH"`
Boot string `xml:"BOOT"`
}
type vmSecurityGroupRule struct {
securityGroupRule
SecurityGroup string `xml:"SECURITY_GROUP_NAME"`
}
// VMState is the state of the Virtual Machine
@ -58,6 +184,14 @@ const (
CloningFailure VMState = 11
)
func (st VMState) isValid() bool {
if (st >= Init && st <= Done) ||
(st >= Poweroff && st <= CloningFailure) {
return true
}
return false
}
func (s VMState) String() string {
switch s {
case Init:
@ -286,6 +420,15 @@ const (
DiskResizeUndeployed LCMState = 64
)
func (st LCMState) isValid() bool {
if (st >= LcmInit && st <= Shutdown) ||
(st >= CleanupResubmit && st <= DiskSnapshot) ||
(st >= DiskSnapshotDelete && st <= DiskResizeUndeployed) {
return true
}
return false
}
func (l LCMState) String() string {
switch l {
case LcmInit:
@ -301,7 +444,7 @@ func (l LCMState) String() string {
case SaveStop:
return "SAVE_STOP"
case SaveSuspend:
return "SAVESuspend"
return "SAVE_SUSPEND"
case SaveMigrate:
return "SAVE_MIGRATE"
case PrologMigrate:
@ -327,7 +470,7 @@ func (l LCMState) String() string {
case BootPoweroff:
return "BOOT_POWEROFF"
case BootSuspended:
return "BOOTSuspendED"
return "BOOT_SUSPENDED"
case BootStopped:
return "BOOT_STOPPED"
case CleanupDelete:
@ -341,7 +484,7 @@ func (l LCMState) String() string {
case HotplugSaveasPoweroff:
return "HOTPLUG_SAVEAS_POWEROFF"
case HotplugSaveasSuspended:
return "HOTPLUG_SAVEASSuspendED"
return "HOTPLUG_SAVEAS_SUSPENDED"
case ShutdownUndeploy:
return "SHUTDOWN_UNDEPLOY"
case EpilogUndeploy:
@ -375,9 +518,9 @@ func (l LCMState) String() string {
case PrologMigratePoweroffFailure:
return "PROLOG_MIGRATE_POWEROFF_FAILURE"
case PrologMigrateSuspend:
return "PROLOG_MIGRATESuspend"
return "PROLOG_MIGRATE_SUSPEND"
case PrologMigrateSuspendFailure:
return "PROLOG_MIGRATESuspend_FAILURE"
return "PROLOG_MIGRATE_SUSPEND_FAILURE"
case BootUndeployFailure:
return "BOOT_UNDEPLOY_FAILURE"
case BootStoppedFailure:
@ -393,11 +536,11 @@ func (l LCMState) String() string {
case DiskSnapshotDeletePoweroff:
return "DISK_SNAPSHOT_DELETE_POWEROFF"
case DiskSnapshotSuspended:
return "DISK_SNAPSHOTSuspendED"
return "DISK_SNAPSHOT_SUSPENDED"
case DiskSnapshotRevertSuspended:
return "DISK_SNAPSHOT_REVERTSuspendED"
return "DISK_SNAPSHOT_REVERT_SUSPENDED"
case DiskSnapshotDeleteSuspended:
return "DISK_SNAPSHOT_DELETESuspendED"
return "DISK_SNAPSHOT_DELETE_SUSPENDED"
case DiskSnapshot:
return "DISK_SNAPSHOT"
case DiskSnapshotDelete:
@ -451,10 +594,13 @@ func NewVMPool(args ...int) (*VMPool, error) {
return nil, err
}
vmpool := &VMPool{XMLResource{body: response.Body()}}
return vmpool, err
vmPool := &VMPool{}
err = xml.Unmarshal([]byte(response.Body()), vmPool)
if err != nil {
return nil, err
}
return vmPool, nil
}
// Monitoring returns all the virtual machine monitorin records
@ -529,51 +675,62 @@ func CreateVM(template string, pending bool) (uint, error) {
// NewVM finds an VM by ID returns a new VM object. At this stage no
// connection to OpenNebula is performed.
func NewVM(id uint) *VM {
return &VM{ID: id}
return &VM{vmBase: vmBase{ID: id}}
}
// NewVMFromName finds the VM by name and returns a VM object. It connects to
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the VM.
func NewVMFromName(name string) (*VM, error) {
vmpool, err := NewVMPool()
var id uint
vmPool, err := NewVMPool()
if err != nil {
return nil, err
}
id, err := vmpool.GetIDFromName(name, "/VM_POOL/VM")
if err != nil {
return nil, err
match := false
for i := 0; i < len(vmPool.VMs); i++ {
if vmPool.VMs[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = vmPool.VMs[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewVM(id), nil
}
// State returns the VMState and LCMState
func (vm *VM) State() (VMState, LCMState, error) {
vmStateString, ok := vm.XPath("/VM/STATE")
if ok != true {
return -1, -1, errors.New("Unable to parse VM State")
func (vm *vmBase) State() (VMState, LCMState, error) {
state := VMState(vm.StateRaw)
if !state.isValid() {
return -1, -1, fmt.Errorf("VM State: this state value is not currently handled: %d\n", vm.StateRaw)
}
lcmStateString, ok := vm.XPath("/VM/LCM_STATE")
if ok != true {
return -1, -1, errors.New("Unable to parse LCM State")
lcmState := LCMState(vm.LCMStateRaw)
if !lcmState.isValid() {
return state, -1, fmt.Errorf("VM LCMState: this state value is not currently handled: %d\n", vm.LCMStateRaw)
}
vmState, _ := strconv.Atoi(vmStateString)
lcmState, _ := strconv.Atoi(lcmStateString)
return VMState(vmState), LCMState(lcmState), nil
return state, lcmState, nil
}
// StateString returns the VMState and LCMState as strings
func (vm *VM) StateString() (string, string, error) {
vmState, lcmState, err := vm.State()
if err != nil {
return "", "", err
func (vm *vmBase) StateString() (string, string, error) {
state := VMState(vm.StateRaw)
if !state.isValid() {
return "", "", fmt.Errorf("VM State: this state value is not currently handled: %d\n", vm.StateRaw)
}
return VMState(vmState).String(), LCMState(lcmState).String(), nil
lcmState := LCMState(vm.LCMStateRaw)
if !lcmState.isValid() {
return state.String(), "", fmt.Errorf("VM LCMState: this state value is not currently handled: %d\n", vm.LCMStateRaw)
}
return state.String(), lcmState.String(), nil
}
// Action is the generic method to run any action on the VM
@ -588,8 +745,7 @@ func (vm *VM) Info() error {
if err != nil {
return err
}
vm.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), vm)
}
// Update will modify the VM's template. If appendTemplate is 0, it will

View File

@ -104,8 +104,7 @@ func (s *VMSuite) TestVMUpdate(c *C) {
err = vm.Info()
c.Assert(err, IsNil)
val, ok := vm.XPath("/VM/USER_TEMPLATE/A")
c.Assert(ok, Equals, true)
val := vm.UserTemplate.Dynamic.GetContentByName("A")
c.Assert(val, Equals, "B")
}
@ -194,11 +193,7 @@ func (s *VMSuite) TestVMResize(c *C) {
err = vm.Info()
c.Assert(err, IsNil)
cpu, ok := vm.XPath("/VM/TEMPLATE/CPU")
c.Assert(ok, Equals, true)
c.Assert(cpu, Equals, "2.5")
c.Assert(vm.Template.CPU, Equals, "2.5")
memory, ok := vm.XPath("/VM/TEMPLATE/MEMORY")
c.Assert(ok, Equals, true)
c.Assert(memory, Equals, "512")
c.Assert(vm.Template.Memory, Equals, "512")
}

View File

@ -1,19 +1,34 @@
package goca
// Since version 5.8 of OpenNebula
import (
"encoding/xml"
"errors"
)
// VNTemplate represents an OpenNebula Virtual Network Template
type VNTemplate struct {
XMLResource
ID uint
Name string
}
// VNTemplatePool represents an OpenNebula Virtual Network TemplatePool
type VNTemplatePool struct {
XMLResource
VNTemplates []VNTemplate `xml:"VNTEMPLATE"`
}
// VNTemplate represents an OpenNebula Virtual Network Template
type VNTemplate struct {
ID uint `xml:"ID"`
UID int `xml:"UID"`
GID int `xml:"GID"`
UName string `xml:"UNAME"`
GName string `xml:"GNAME"`
Name string `xml:"NAME"`
LockInfos *Lock `xml:"LOCK"`
Permissions Permissions `xml:"PERMISSIONS"`
RegTime string `xml:"REGTIME"`
Template vnTemplateTemplate `xml:"TEMPLATE"`
}
type vnTemplateTemplate struct {
VNMad string `xml:"VN_MAD"`
Dynamic unmatchedTagsSlice `xml:",any"`
}
// NewVNTemplatePool returns a vntemplate pool. A connection to OpenNebula is
@ -39,9 +54,13 @@ func NewVNTemplatePool(args ...int) (*VNTemplatePool, error) {
return nil, err
}
vntemplatepool := &VNTemplatePool{XMLResource{body: response.Body()}}
vnTemplatePool := &VNTemplatePool{}
err = xml.Unmarshal([]byte(response.Body()), vnTemplatePool)
if err != nil {
return nil, err
}
return vntemplatepool, err
return vnTemplatePool, nil
}
@ -54,14 +73,26 @@ func NewVNTemplate(id uint) *VNTemplate {
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the vntemplate.
func NewVNTemplateFromName(name string) (*VNTemplate, error) {
vntemplatePool, err := NewVNTemplatePool()
var id uint
vnTemplatePool, err := NewVNTemplatePool()
if err != nil {
return nil, err
}
id, err := vntemplatePool.GetIDFromName(name, "/VNTEMPLATE_POOL/VNTEMPLATE")
if err != nil {
return nil, err
match := false
for i := 0; i < len(vnTemplatePool.VNTemplates); i++ {
if vnTemplatePool.VNTemplates[i].Name != name {
continue
}
if match {
return nil, errors.New("multiple resources with that name")
}
id = vnTemplatePool.VNTemplates[i].ID
match = true
}
if !match {
return nil, errors.New("resource not found")
}
return NewVNTemplate(id), nil
@ -80,8 +111,10 @@ func CreateVNTemplate(vntemplate string) (uint, error) {
// Info connects to OpenNebula and fetches the information of the VNTemplate
func (vntemplate *VNTemplate) Info() error {
response, err := client.Call("one.vntemplate.info", vntemplate.ID)
vntemplate.body = response.Body()
return err
if err != nil {
return err
}
return xml.Unmarshal([]byte(response.Body()), vntemplate)
}
// Update will modify the vntemplate. If appendTemplate is 0, it will

View File

@ -1,13 +1,5 @@
package goca
import (
"bytes"
"errors"
"strconv"
"gopkg.in/xmlpath.v2"
)
const (
// PoolWhoPrimaryGroup resources belonging to the users primary group.
PoolWhoPrimaryGroup = -4
@ -24,89 +16,3 @@ const (
// the query.
PoolWhoGroup = -1
)
// XMLResource contains an XML body field. All the resources in OpenNebula are
// of this kind.
type XMLResource struct {
body string
}
// XMLIter is used to iterate over XML xpaths in an object.
type XMLIter struct {
iter *xmlpath.Iter
}
// XMLNode represent an XML node.
type XMLNode struct {
node *xmlpath.Node
}
// Body accesses the body of an XMLResource
func (r *XMLResource) Body() string {
return r.body
}
// XPath returns the string pointed at by xpath, for an XMLResource
func (r *XMLResource) XPath(xpath string) (string, bool) {
path := xmlpath.MustCompile(xpath)
b := bytes.NewBufferString(r.Body())
root, _ := xmlpath.Parse(b)
return path.String(root)
}
// XPathIter returns an XMLIter object pointed at by the xpath
func (r *XMLResource) XPathIter(xpath string) *XMLIter {
path := xmlpath.MustCompile(xpath)
b := bytes.NewBufferString(string(r.Body()))
root, _ := xmlpath.Parse(b)
return &XMLIter{iter: path.Iter(root)}
}
// GetIDFromName finds the a resource by ID by looking at an xpath contained
// in that resource
func (r *XMLResource) GetIDFromName(name string, xpath string) (uint, error) {
var id int
var match = false
iter := r.XPathIter(xpath)
for iter.Next() {
node := iter.Node()
n, _ := node.XPath("NAME")
if n == name {
if match {
return 0, errors.New("multiple resources with that name")
}
idString, _ := node.XPath("ID")
id, _ = strconv.Atoi(idString)
match = true
}
}
if !match {
return 0, errors.New("resource not found")
}
return uint(id), nil
}
// Next moves on to the next resource
func (i *XMLIter) Next() bool {
return i.iter.Next()
}
// Node returns the XMLNode
func (i *XMLIter) Node() *XMLNode {
return &XMLNode{node: i.iter.Node()}
}
// XPath returns an XMLNode pointed at by xpath
func (n *XMLNode) XPath(xpath string) (string, bool) {
path := xmlpath.MustCompile(xpath)
return path.String(n.node)
}

View File

@ -1,17 +1,83 @@
package goca
// Zone represents an OpenNebula Zone
type Zone struct {
XMLResource
ID uint
Name string
}
import (
"encoding/xml"
"errors"
"fmt"
)
// ZonePool represents an OpenNebula ZonePool
type ZonePool struct {
XMLResource
ID uint `xml:"ZONE>ID"`
Name string `xml:"ZONE>NAME"`
Template zoneTemplate `xml:"ZONE>TEMPLATE"`
ServerPool []zoneServer `xml:"ZONE>SERVER_POOL>SERVER"`
}
// Zone represents an OpenNebula Zone
type Zone struct {
ID uint `xml:"ID"`
Name string `xml:"NAME"`
Template zoneTemplate `xml:"TEMPLATE"`
ServerPool []zoneServer `xml:"SERVER_POOL>SERVER"`
}
type zoneServer struct {
ID int `xml:"ID"`
Name string `xml:"NAME"`
Endpoint string `xml:"ENDPOINT"`
}
type zoneTemplate struct {
Endpoint string `xml:"ENDPOINT"`
}
// ZoneServerRaftStatus contains the raft status datas of a server
type ZoneServerRaftStatus struct {
ID int `xml:"SERVER_ID"`
StateRaw int `xml:"STATE"`
Term int `xml:"TERM"`
Votedfor int `xml:"VOTEDFOR"`
Commit int `xml:"COMMIT"`
LogIndex int `xml:"LOG_INDEX"`
FedlogIndex int `xml:"FEDLOG_INDEX"`
}
// ZoneServerRaftState is the state of an OpenNebula server from a zone (See HA and Raft)
type ZoneServerRaftState int
const (
// ZoneServerRaftSolo is the initial leader
ZoneServerRaftSolo ZoneServerRaftState = 0
// ZoneServerRaftCandidate when the server is candidate to election
ZoneServerRaftCandidate = 1
// ZoneServerRaftFollower when the server is a follower
ZoneServerRaftFollower = 2
// ZoneServerRaftLeader when the server is the leader
ZoneServerRaftLeader = 3
)
func (st ZoneServerRaftState) isValid() bool {
if st >= ZoneServerRaftSolo && st <= ZoneServerRaftLeader {
return true
}
return false
}
func (st ZoneServerRaftState) String() string {
return [...]string{
"SOLO",
"CANDIDATE",
"FOLLOWER",
"LEADER",
}[st]
}
// NewZonePool returns a zone pool. A connection to OpenNebula is
// NewZonePool returns a zone pool. A connection to OpenNebula is
// performed.
func NewZonePool() (*ZonePool, error) {
@ -20,9 +86,13 @@ func NewZonePool() (*ZonePool, error) {
return nil, err
}
zonepool := &ZonePool{XMLResource{body: response.Body()}}
zonePool := &ZonePool{}
err = xml.Unmarshal([]byte(response.Body()), zonePool)
if err != nil {
return nil, err
}
return zonepool, err
return zonePool, err
}
// NewZone finds a zone object by ID. No connection to OpenNebula.
@ -39,12 +109,11 @@ func NewZoneFromName(name string) (*Zone, error) {
return nil, err
}
id, err := zonePool.GetIDFromName(name, "/ZONE_POOL/ZONE")
if err != nil {
return nil, err
if zonePool.Name != name {
return nil, errors.New("resource not found")
}
return NewZone(id), nil
return NewZone(zonePool.ID), nil
}
// CreateZone allocates a new zone. It returns the new zone ID.
@ -87,6 +156,37 @@ func (zone *Zone) Info() error {
if err != nil {
return err
}
zone.body = response.Body()
return nil
return xml.Unmarshal([]byte(response.Body()), zone)
}
//GetRaftStatus give the raft status of the server behind the current RPC endpoint. To get endpoints make an info call.
func GetRaftStatus(serverUrl string) (*ZoneServerRaftStatus, error) {
response, err := client.endpointCall(serverUrl, "one.zone.raftstatus")
if err != nil {
return nil, err
}
s := &ZoneServerRaftStatus{}
err = xml.Unmarshal([]byte(response.Body()), s)
if err != nil {
return nil, err
}
return s, nil
}
// State looks up the state of the zone server and returns the ZoneServerRaftState
func (server *ZoneServerRaftStatus) State() (ZoneServerRaftState, error) {
state := ZoneServerRaftState(server.StateRaw)
if !state.isValid() {
return -1, fmt.Errorf("Zone server State: this state value is not currently handled: %d\n", server.StateRaw)
}
return state, nil
}
// StateString returns the state in string format
func (server *ZoneServerRaftStatus) StateString() (string, error) {
state := ZoneServerRaftState(server.StateRaw)
if !state.isValid() {
return "", fmt.Errorf("Zone server StateString: this state value is not currently handled: %d\n", server.StateRaw)
}
return state.String(), nil
}