mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
683cbcf26e
commit
732b408dad
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
8
src/oca/go/src/goca/lock.go
Normal file
8
src/oca/go/src/goca/lock.go
Normal file
@ -0,0 +1,8 @@
|
||||
package goca
|
||||
|
||||
type Lock struct {
|
||||
Locked int `xml:"LOCKED"`
|
||||
Owner int `xml:"OWNER"`
|
||||
Time int `xml:"TIME"`
|
||||
ReqID int `xml:"REQ_ID"`
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
23
src/oca/go/src/goca/permissions.go
Normal file
23
src/oca/go/src/goca/permissions.go
Normal file
@ -0,0 +1,23 @@
|
||||
package goca
|
||||
|
||||
//import "fmt"
|
||||
|
||||
type Permissions struct {
|
||||
OwnerU int `xml:"OWNER_U"`
|
||||
OwnerM int `xml:"OWNER_M"`
|
||||
OwnerA int `xml:"OWNER_A"`
|
||||
GroupU int `xml:"GROUP_U"`
|
||||
GroupM int `xml:"GROUP_M"`
|
||||
GroupA int `xml:"GROUP_A"`
|
||||
OtherU int `xml:"OTHER_U"`
|
||||
OtherM int `xml:"OTHER_M"`
|
||||
OtherA int `xml:"OTHER_A"`
|
||||
}
|
||||
|
||||
func (p *Permissions) String() string {
|
||||
permStr := [8]string{"uma", "um-", "u-a", "u--", "-ma", "-m-", "--a", "---"}
|
||||
owner := permStr[p.OwnerU<<2|p.OwnerM<<1|p.OwnerA]
|
||||
group := permStr[p.GroupU<<2|p.GroupM<<1|p.GroupA]
|
||||
other := permStr[p.OtherU<<2|p.OtherM<<1|p.OtherA]
|
||||
return owner + group + other
|
||||
}
|
50
src/oca/go/src/goca/quota.go
Normal file
50
src/oca/go/src/goca/quota.go
Normal file
@ -0,0 +1,50 @@
|
||||
package goca
|
||||
|
||||
type quotas struct {
|
||||
ID uint `xml:"ID"`
|
||||
quotasList
|
||||
}
|
||||
|
||||
type quotasList struct {
|
||||
DatastoreQuotas []datastoreQuota `xml:"DATASTORE_QUOTA>DATASTORE"`
|
||||
NetworkQuotas []networkQuota `xml:"NETWORK_QUOTA>NETWORK"`
|
||||
VMQuotas []vmQuota `xml:"VM_QUOTA>VM"`
|
||||
ImageQuotas []imageQuota `xml:"IMAGE_QUOTA>IMAGE"`
|
||||
}
|
||||
|
||||
type datastoreQuota struct {
|
||||
ID string `xml:"ID"`
|
||||
Images string `xml:"IMAGES"`
|
||||
ImagesUsed string `xml:"IMAGES_USED"`
|
||||
Size string `xml:"SIZE"`
|
||||
SizeUsed string `xml:"SIZE_USED"`
|
||||
}
|
||||
|
||||
type networkQuota struct {
|
||||
ID string `xml:"ID"`
|
||||
Leases string `xml:"LEASES"`
|
||||
LeasesUsed string `xml:"LEASES_USED"`
|
||||
}
|
||||
|
||||
type vmQuota struct {
|
||||
CPU string `xml:"CPU"`
|
||||
CPUUsed string `xml:"CPU_USED"`
|
||||
Memory string `xml:"MEMORY"`
|
||||
MemoryUsed string `xml:"MEMORY_USED"`
|
||||
RunningCpu string `xml:"RUNNING_CPU"`
|
||||
RunningCpuUsed string `xml:"RUNNING_CPU_USED"`
|
||||
RunningMemory string `xml:"RUNNING_MEMORY"`
|
||||
RunningMemoryUsed string `xml:"RUNNING_MEMORY_USED"`
|
||||
RunningVMs string `xml:"RUNNING_VMS"`
|
||||
RunningVMsUsed string `xml:"RUNNING_VMS_USED"`
|
||||
SystemDiskSize string `xml:"SYSTEM_DISK_SIZE"`
|
||||
SystemDiskSizeUsed string `xml:"SYSTEM_DISK_SIZE_USED"`
|
||||
VMs string `xml:"VMS"`
|
||||
VMsUsed string `xml:"VMS_USED"`
|
||||
}
|
||||
|
||||
type imageQuota struct {
|
||||
ID string `xml:"ID"`
|
||||
RVMs string `xml:"RVMS"`
|
||||
RVMsUsed string `xml:"RVMS_USED"`
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
40
src/oca/go/src/goca/snapshot.go
Normal file
40
src/oca/go/src/goca/snapshot.go
Normal file
@ -0,0 +1,40 @@
|
||||
package goca
|
||||
|
||||
// An user can take snapshot on VM, or on VM disks
|
||||
|
||||
// Common part
|
||||
type snapshot struct {
|
||||
Children string `xml:"CHILDREN"` //minOccur=0
|
||||
Active string `xml:"ACTIVE"` //minOccur=0
|
||||
Date int `xml:"DATE"`
|
||||
ID int `xml:"ID"`
|
||||
Name string `xml:"NAME"` //minOccur=0
|
||||
Parent int `xml:"PARENT"`
|
||||
Size int `xml:"SIZE"`
|
||||
}
|
||||
|
||||
// Image entity related
|
||||
type ImageSnapshot struct {
|
||||
AllowOrphans string `xml:"ALLOW_ORPHANS"`
|
||||
CurrentBase int `xml:"CURRENT_BASE"`
|
||||
NextSnapshot int `xml:"NEXT_SNAPSHOT"`
|
||||
Snapshots []snapshot `xml:"SNAPSHOT"`
|
||||
}
|
||||
|
||||
// VM entity related
|
||||
type VMSnapshot struct {
|
||||
HypervisorID string `xml:"HYPERVISOR_ID"`
|
||||
Name string `xml:"NAME"`
|
||||
ID int `xml:"SNAPSHOT_ID"`
|
||||
Time string `xml:"TIME"`
|
||||
}
|
||||
|
||||
type vmHistoryRecordSnapshot struct {
|
||||
ImageSnapshot
|
||||
DiskID int `xml:"DISK_ID"`
|
||||
}
|
||||
|
||||
type vmMonitoringSnapshotSize struct {
|
||||
DiskID int `xml:"DISK_ID"`
|
||||
Size int `xml:"SIZE"`
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
101
src/oca/go/src/goca/unmatched_tags.go
Normal file
101
src/oca/go/src/goca/unmatched_tags.go
Normal file
@ -0,0 +1,101 @@
|
||||
package goca
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Common part
|
||||
|
||||
// UnmatchedTag contains the tag informations
|
||||
type UnmatchedTag struct {
|
||||
XMLName xml.Name
|
||||
Content string `xml:",chardata"`
|
||||
//FullContent string `xml:",innerxml"` // for debug purpose, allow to see what's inside some tags
|
||||
}
|
||||
|
||||
// Store unmatched tags in a map
|
||||
// Inspired from: https://stackoverflow.com/questions/30928770/marshall-map-to-xml-in-go/33110881
|
||||
|
||||
// NOTE: to be used in flat xml part with distinct tag names
|
||||
// If it's not flat: the hash will contains key with empty values
|
||||
// If there is several tags with the same name : only the last value will be stored
|
||||
|
||||
// UnmatchedTagsMap store tags not handled by Unmarshal in a map, it should be labelled with `xml",any"`
|
||||
type unmatchedTagsMap map[string]string
|
||||
|
||||
func (m *unmatchedTagsMap) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
if *m == nil {
|
||||
*m = unmatchedTagsMap{}
|
||||
}
|
||||
|
||||
e := UnmatchedTag{}
|
||||
err := d.DecodeElement(&e, &start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fail the parsing of the whole xml
|
||||
//if _, ok := (*m)[e.XMLName.Local]; ok {
|
||||
// return fmt.Errorf("UnmatchedTagsMap: UnmarshalXML: Tag %s: multiple entries with the same name", e.XMLName.Local)
|
||||
//}
|
||||
(*m)[e.XMLName.Local] = e.Content
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *unmatchedTagsMap) GetContentByName(name string) string {
|
||||
return ((map[string]string)(*u))[name]
|
||||
}
|
||||
|
||||
// Store unmatched tags in a slice
|
||||
|
||||
// NOTE: to be used in flat xml part
|
||||
|
||||
// UnmatchedTagsSlice store tags not handled by Unmarshal in a slice, it should be labelled with `xml",any"`
|
||||
type unmatchedTagsSlice struct {
|
||||
Tags []UnmatchedTag
|
||||
}
|
||||
|
||||
func (u *unmatchedTagsSlice) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var e UnmatchedTag
|
||||
err := d.DecodeElement(&e, &start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Tags = append(u.Tags, e)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve slice of tags with given name
|
||||
func (u *unmatchedTagsSlice) GetContentSliceByName(name string) []string {
|
||||
content := make([]string, 0, 1)
|
||||
for _, t := range u.Tags {
|
||||
if t.XMLName.Local != name {
|
||||
continue
|
||||
}
|
||||
content = append(content, t.Content)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// Retrieve a tag with given name, fail if not present or present more than once
|
||||
func (u *unmatchedTagsSlice) GetContentByName(name string) (string, error) {
|
||||
var content string
|
||||
match := false
|
||||
for _, t := range u.Tags {
|
||||
if t.XMLName.Local != name {
|
||||
continue
|
||||
}
|
||||
if match == true {
|
||||
return "", fmt.Errorf("GetContentByName: multiple entries with the name %s", name)
|
||||
}
|
||||
content = t.Content
|
||||
match = true
|
||||
}
|
||||
if match == false {
|
||||
return "", fmt.Errorf("GetContentByName: tag %s not found", name)
|
||||
}
|
||||
return content, nil
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user