diff --git a/src/oca/go/src/goca/acl.go b/src/oca/go/src/goca/acl.go index e53a661b30..69a6469db3 100644 --- a/src/oca/go/src/goca/acl.go +++ b/src/oca/go/src/goca/acl.go @@ -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. diff --git a/src/oca/go/src/goca/cluster.go b/src/oca/go/src/goca/cluster.go index 94ac676f12..a2a5d45276 100644 --- a/src/oca/go/src/goca/cluster.go +++ b/src/oca/go/src/goca/cluster.go @@ -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) } diff --git a/src/oca/go/src/goca/datastore.go b/src/oca/go/src/goca/datastore.go index b662d37cc7..328d4746f6 100644 --- a/src/oca/go/src/goca/datastore.go +++ b/src/oca/go/src/goca/datastore.go @@ -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 } diff --git a/src/oca/go/src/goca/document.go b/src/oca/go/src/goca/document.go index 241806dab9..57b460a196 100644 --- a/src/oca/go/src/goca/document.go +++ b/src/oca/go/src/goca/document.go @@ -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 diff --git a/src/oca/go/src/goca/goca.go b/src/oca/go/src/goca/goca.go index 222ab5a8cc..b1bc568f67 100644 --- a/src/oca/go/src/goca/goca.go +++ b/src/oca/go/src/goca/goca.go @@ -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} diff --git a/src/oca/go/src/goca/group.go b/src/oca/go/src/goca/group.go index 06378c3c67..e811a20fb0 100644 --- a/src/oca/go/src/goca/group.go +++ b/src/oca/go/src/goca/group.go @@ -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. diff --git a/src/oca/go/src/goca/helper_test.go b/src/oca/go/src/goca/helper_test.go index 8a5adadeb9..b7dd31965d 100644 --- a/src/oca/go/src/goca/helper_test.go +++ b/src/oca/go/src/goca/helper_test.go @@ -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 } diff --git a/src/oca/go/src/goca/host.go b/src/oca/go/src/goca/host.go index 9c67dcfe65..f049a6e071 100644 --- a/src/oca/go/src/goca/host.go +++ b/src/oca/go/src/goca/host.go @@ -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 } diff --git a/src/oca/go/src/goca/image.go b/src/oca/go/src/goca/image.go index f1af00f876..7ab12cf746 100644 --- a/src/oca/go/src/goca/image.go +++ b/src/oca/go/src/goca/image.go @@ -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 diff --git a/src/oca/go/src/goca/image_test.go b/src/oca/go/src/goca/image_test.go index 184305b5c2..661b7a43d8 100644 --- a/src/oca/go/src/goca/image_test.go +++ b/src/oca/go/src/goca/image_test.go @@ -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") diff --git a/src/oca/go/src/goca/lock.go b/src/oca/go/src/goca/lock.go new file mode 100644 index 0000000000..c2ae86ecef --- /dev/null +++ b/src/oca/go/src/goca/lock.go @@ -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"` +} diff --git a/src/oca/go/src/goca/marketplace.go b/src/oca/go/src/goca/marketplace.go index 17548f42f1..74bb37a017 100644 --- a/src/oca/go/src/goca/marketplace.go +++ b/src/oca/go/src/goca/marketplace.go @@ -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) } diff --git a/src/oca/go/src/goca/marketplace_test.go b/src/oca/go/src/goca/marketplace_test.go index 8563ae1be2..e3607fa351 100644 --- a/src/oca/go/src/goca/marketplace_test.go +++ b/src/oca/go/src/goca/marketplace_test.go @@ -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) diff --git a/src/oca/go/src/goca/marketplaceapp.go b/src/oca/go/src/goca/marketplaceapp.go index b003227dec..87675cbac5 100644 --- a/src/oca/go/src/goca/marketplaceapp.go +++ b/src/oca/go/src/goca/marketplaceapp.go @@ -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. diff --git a/src/oca/go/src/goca/marketplaceapp_test.go b/src/oca/go/src/goca/marketplaceapp_test.go index 62093a0cff..bf077bb82c 100644 --- a/src/oca/go/src/goca/marketplaceapp_test.go +++ b/src/oca/go/src/goca/marketplaceapp_test.go @@ -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) diff --git a/src/oca/go/src/goca/permissions.go b/src/oca/go/src/goca/permissions.go new file mode 100644 index 0000000000..101a196192 --- /dev/null +++ b/src/oca/go/src/goca/permissions.go @@ -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 +} diff --git a/src/oca/go/src/goca/quota.go b/src/oca/go/src/goca/quota.go new file mode 100644 index 0000000000..293746862a --- /dev/null +++ b/src/oca/go/src/goca/quota.go @@ -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"` +} diff --git a/src/oca/go/src/goca/securitygroup.go b/src/oca/go/src/goca/securitygroup.go index fedb6ebf68..f2ef3db58f 100644 --- a/src/oca/go/src/goca/securitygroup.go +++ b/src/oca/go/src/goca/securitygroup.go @@ -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) } diff --git a/src/oca/go/src/goca/securitygroup_test.go b/src/oca/go/src/goca/securitygroup_test.go index b1f3cbde06..110a184f5d 100644 --- a/src/oca/go/src/goca/securitygroup_test.go +++ b/src/oca/go/src/goca/securitygroup_test.go @@ -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) diff --git a/src/oca/go/src/goca/snapshot.go b/src/oca/go/src/goca/snapshot.go new file mode 100644 index 0000000000..cfc562cfe0 --- /dev/null +++ b/src/oca/go/src/goca/snapshot.go @@ -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"` +} diff --git a/src/oca/go/src/goca/template.go b/src/oca/go/src/goca/template.go index 1972ff049f..7f2e2438d4 100644 --- a/src/oca/go/src/goca/template.go +++ b/src/oca/go/src/goca/template.go @@ -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 diff --git a/src/oca/go/src/goca/template_test.go b/src/oca/go/src/goca/template_test.go index 58b1471399..16840fb587 100644 --- a/src/oca/go/src/goca/template_test.go +++ b/src/oca/go/src/goca/template_test.go @@ -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 diff --git a/src/oca/go/src/goca/unmatched_tags.go b/src/oca/go/src/goca/unmatched_tags.go new file mode 100644 index 0000000000..2098cd13ee --- /dev/null +++ b/src/oca/go/src/goca/unmatched_tags.go @@ -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 +} diff --git a/src/oca/go/src/goca/user.go b/src/oca/go/src/goca/user.go index 58d98540ab..69dc6bc4a6 100644 --- a/src/oca/go/src/goca/user.go +++ b/src/oca/go/src/goca/user.go @@ -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) } diff --git a/src/oca/go/src/goca/vdc.go b/src/oca/go/src/goca/vdc.go index bfc627f4ba..940eb7d7de 100644 --- a/src/oca/go/src/goca/vdc.go +++ b/src/oca/go/src/goca/vdc.go @@ -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 diff --git a/src/oca/go/src/goca/virtualnetwork.go b/src/oca/go/src/goca/virtualnetwork.go index 416d014c9a..91593ae14c 100644 --- a/src/oca/go/src/goca/virtualnetwork.go +++ b/src/oca/go/src/goca/virtualnetwork.go @@ -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) } diff --git a/src/oca/go/src/goca/virtualnetwork_test.go b/src/oca/go/src/goca/virtualnetwork_test.go index cf475f4bcc..8e5985a0b9 100644 --- a/src/oca/go/src/goca/virtualnetwork_test.go +++ b/src/oca/go/src/goca/virtualnetwork_test.go @@ -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") diff --git a/src/oca/go/src/goca/virtualrouter.go b/src/oca/go/src/goca/virtualrouter.go index c21ac33752..3ed42283cd 100644 --- a/src/oca/go/src/goca/virtualrouter.go +++ b/src/oca/go/src/goca/virtualrouter.go @@ -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 diff --git a/src/oca/go/src/goca/virtualrouter_test.go b/src/oca/go/src/goca/virtualrouter_test.go index 209f065d4a..7bc0299a29 100644 --- a/src/oca/go/src/goca/virtualrouter_test.go +++ b/src/oca/go/src/goca/virtualrouter_test.go @@ -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 diff --git a/src/oca/go/src/goca/vm.go b/src/oca/go/src/goca/vm.go index 94c20034ce..50872da855 100644 --- a/src/oca/go/src/goca/vm.go +++ b/src/oca/go/src/goca/vm.go @@ -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 diff --git a/src/oca/go/src/goca/vm_test.go b/src/oca/go/src/goca/vm_test.go index 736de28d5f..799aa58a7f 100644 --- a/src/oca/go/src/goca/vm_test.go +++ b/src/oca/go/src/goca/vm_test.go @@ -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") } diff --git a/src/oca/go/src/goca/vntemplate.go b/src/oca/go/src/goca/vntemplate.go index 104dd86e0e..7b26a9e84a 100644 --- a/src/oca/go/src/goca/vntemplate.go +++ b/src/oca/go/src/goca/vntemplate.go @@ -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 diff --git a/src/oca/go/src/goca/xmlresource.go b/src/oca/go/src/goca/xmlresource.go index 2a908f4fbf..66608223ee 100644 --- a/src/oca/go/src/goca/xmlresource.go +++ b/src/oca/go/src/goca/xmlresource.go @@ -1,13 +1,5 @@ package goca -import ( - "bytes" - "errors" - "strconv" - - "gopkg.in/xmlpath.v2" -) - const ( // PoolWhoPrimaryGroup resources belonging to the user’s 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) -} diff --git a/src/oca/go/src/goca/zone.go b/src/oca/go/src/goca/zone.go index 9728c3171e..834130aea1 100644 --- a/src/oca/go/src/goca/zone.go +++ b/src/oca/go/src/goca/zone.go @@ -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 }