1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-21 18:03:38 +03:00

Docker integration and GO API (#1889)

* added docker integration files

* changed dependencies for docker-integration files

* README.md deleted

* Deleted LICENSE
This commit is contained in:
Christian González 2018-03-23 12:58:29 +01:00 committed by Tino Vázquez
parent 4f880598e9
commit c3b113c5f2
34 changed files with 4155 additions and 1 deletions

View File

@ -167,6 +167,9 @@ main_env.Append(rubygems=ARGUMENTS.get('rubygems', 'no'))
# Sunstone minified files generation
main_env.Append(sunstone=ARGUMENTS.get('sunstone', 'no'))
# Docker-machine addon generation
main_env.Append(docker_machine=ARGUMENTS.get('docker_machine', 'no'))
if not main_env.GetOption('clean'):
try:
if mysql=='yes':
@ -263,7 +266,8 @@ build_scripts=[
'src/sunstone/public/SConstruct',
'share/rubygems/SConstruct',
'src/im_mad/collectd/SConstruct',
'src/client/SConstruct'
'src/client/SConstruct',
'src/docker_machine/SConstruct'
]
for script in build_scripts:

View File

@ -591,6 +591,7 @@ BIN_FILES="src/nebula/oned \
src/cli/onevcenter \
src/onedb/onedb \
src/mad/utils/tty_expect \
src/docker_machine/src/docker_machine/bin/docker-machine-driver-opennebula \
share/scripts/one"
#-------------------------------------------------------------------------------

View File

@ -0,0 +1,29 @@
# SConstruct for share/scripts/rubygems
# -------------------------------------------------------------------------- #
# Copyright 2002-2018, OpenNebula Project, OpenNebula Systems #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
import os
Import('env')
if env['docker_machine']=='yes':
print "Generating docker-machine-driver-opennebula\n"
exit_code=os.system("./generate.sh")
if exit_code != 0:
print "Error generating docker-machine-driver-opennebula\n"
exit(-1)

8
src/docker_machine/generate.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
GOCA_PATH=$PWD/../oca/go
ADDON_PATH=$PWD
export GOPATH=$GOPATH:$GOCA_PATH:$ADDON_PATH
cd src/docker_machine/
make build

View File

@ -0,0 +1,95 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/Azure/go-ansiterm"
packages = [
".",
"winterm"
]
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
[[projects]]
branch = "master"
name = "github.com/docker/docker"
packages = [
"pkg/term",
"pkg/term/windows"
]
revision = "0c1006f1abc1af7aa6b9847754370d054dfa6c68"
[[projects]]
name = "github.com/docker/machine"
packages = [
"libmachine/drivers",
"libmachine/drivers/plugin",
"libmachine/drivers/plugin/localbinary",
"libmachine/drivers/rpc",
"libmachine/log",
"libmachine/mcnflag",
"libmachine/mcnutils",
"libmachine/ssh",
"libmachine/state",
"libmachine/version",
"version"
]
revision = "89b833253d9412716a0291cbdccc94454c33d1b5"
version = "v0.14.0"
[[projects]]
branch = "master"
name = "github.com/kolo/xmlrpc"
packages = ["."]
revision = "0826b98aaa29c0766956cb40d45cf7482a597671"
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = [
"curve25519",
"ed25519",
"ed25519/internal/edwards25519",
"internal/chacha20",
"poly1305",
"ssh",
"ssh/terminal"
]
revision = "c3a3ad6d03f7a915c0f7e194b7152974bb73d287"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = [
"html",
"html/atom"
]
revision = "6078986fec03a1dcc236c34816c71b0e05018fda"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
]
revision = "7ceb54c8418b8f9cdf0177b511d5cbb06e9fae39"
[[projects]]
branch = "v2"
name = "gopkg.in/xmlpath.v2"
packages = ["."]
revision = "860cbeca3ebcc600db0b213c0e83ad6ce91f5739"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "8e5052c0521dfead3dd5b8672b0d2f066c70177a5e5a3ba4abd1449d087b98aa"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -0,0 +1,43 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
required = ["github.com/kolo/xmlrpc", "gopkg.in/xmlpath.v2"]
[[constraint]]
name = "github.com/docker/machine"
version = "0.14.0"
[[constraint]]
branch = "master"
name = "github.com/kolo/xmlrpc"
[[constraint]]
branch = "v2"
name = "gopkg.in/xmlpath.v2"
[prune]
go-tests = true
unused-packages = true

View File

@ -0,0 +1,17 @@
GODEP_BIN := $(word 1, $(subst :, ,$(GOPATH)))/bin/dep
default: build
bin/docker-machine-driver-opennebula:
$(GODEP_BIN) ensure
mv vendor/* ..
go build -o ./bin/docker-machine-driver-opennebula ./bin
build: clean bin/docker-machine-driver-opennebula
clean:
$(RM) bin/docker-machine-driver-opennebula
install: bin/docker-machine-driver-opennebula
cp -f ./bin/docker-machine-driver-opennebula $(GOPATH)/bin/
.PHONY: clean build install

View File

@ -0,0 +1,10 @@
package main
import (
"docker_machine"
"github.com/docker/machine/libmachine/drivers/plugin"
)
func main() {
plugin.RegisterDriver(opennebula.NewDriver("", ""))
}

View File

@ -0,0 +1,731 @@
package opennebula
import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"strconv"
"time"
"goca"
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnflag"
"github.com/docker/machine/libmachine/ssh"
"github.com/docker/machine/libmachine/state"
)
type Driver struct {
*drivers.BaseDriver
TemplateName string
TemplateID string
NetworkName string
NetworkOwner string
NetworkID string
ImageName string
ImageOwner string
ImageID string
CPU string
VCPU string
Memory string
DiskSize string
ImageDevPrefix string
B2DSize string
User string
Password string
Xmlrpcurl string
Config goca.OneConfig
DisableVNC bool
}
const (
defaultTimeout = 1 * time.Second
defaultSSHUser = "docker"
defaultCPU = "1"
defaultVCPU = "1"
defaultMemory = "1024"
// This is the contextualization script that will be executed by OpenNebula
contextScript = `#!/bin/sh
if [ -f /etc/boot2docker ]; then
USERNAME=docker
USER_HOME=/home/docker
else
USERNAME=$DOCKER_SSH_USER
GROUPNAME=$DOCKER_SSH_USER
if ! getent group $GROUPNAME; then
groupadd $GROUPNAME
fi
if ! getent passwd $USERNAME; then
USER_HOME=/var/lib/$DOCKER_SSH_USER
useradd -m -d $USER_HOME -g $USERNAME $GROUPNAME
else
USER_HOME=$(getent passwd $USERNAME | cut -d: -f 6)
fi
# Write sudoers
if [ ! -f /etc/sudoers.d/$USERNAME ]; then
echo -n "Defaults:$USERNAME " >> /etc/sudoers.d/$USERNAME
echo '!requiretty' >> /etc/sudoers.d/$USERNAME
echo "$USERNAME ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers.d/$USERNAME
fi
fi
# Add DOCKER_SSH_PUBLIC_KEY
AUTH_DIR="${USER_HOME}/.ssh"
AUTH_FILE="${AUTH_DIR}/authorized_keys"
mkdir -m0700 -p $AUTH_DIR
echo "$DOCKER_SSH_PUBLIC_KEY" >> $AUTH_FILE
chown "${USERNAME}": ${AUTH_DIR} ${AUTH_FILE}
chmod 600 $AUTH_FILE`
)
func NewDriver(hostName, storePath string) *Driver {
return &Driver{
BaseDriver: &drivers.BaseDriver{
SSHUser: defaultSSHUser,
MachineName: hostName,
StorePath: storePath,
},
}
}
func (d *Driver) buildConfig() {
d.Config = goca.NewConfig(d.User, d.Password, d.Xmlrpcurl)
}
func (d *Driver) setClient() error {
d.buildConfig()
return goca.SetClient(d.Config)
}
// GetCreateFlags registers the flags this driver adds to
// "docker hosts create"
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
return []mcnflag.Flag{
mcnflag.StringFlag{
Name: "opennebula-cpu",
Usage: fmt.Sprintf("CPU value for the VM. Default: %d", defaultCPU),
EnvVar: "ONE_CPU",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-vcpu",
Usage: fmt.Sprintf("VCPUs for the VM. Default: %d", defaultVCPU),
EnvVar: "ONE_VCPU",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-memory",
Usage: fmt.Sprintf("Size of memory for VM in MB. Default: %d", defaultMemory),
EnvVar: "ONE_MEMORY",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-template-name",
Usage: "Template to use",
EnvVar: "ONE_TEMPLATE_NAME",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-template-id",
Usage: "Template ID to use",
EnvVar: "ONE_TEMPLATE_ID",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-network-name",
Usage: "Network to connect the machine to",
EnvVar: "ONE_NETWORK_NAME",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-network-id",
Usage: "Network ID to connect the machine to",
EnvVar: "ONE_NETWORK_ID",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-network-owner",
Usage: "User ID of the Network to connect the machine to",
EnvVar: "ONE_NETWORK_OWNER",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-image-name",
Usage: "Image to use as the OS",
EnvVar: "ONE_IMAGE_NAME",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-image-id",
Usage: "Image ID to use as the OS",
EnvVar: "ONE_IMAGE_ID",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-image-owner",
Usage: "Owner of the image to use as the OS",
EnvVar: "ONE_IMAGE_OWNER",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-dev-prefix",
Usage: "Dev prefix to use for the images: 'vd', 'sd', 'hd', etc..",
EnvVar: "ONE_IMAGE_DEV_PREFIX",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-disk-resize",
Usage: "Size of disk for VM in MB",
EnvVar: "ONE_DISK_SIZE",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-b2d-size",
Usage: "Size of the Volatile disk in MB (only for b2d)",
EnvVar: "ONE_B2D_DATA_SIZE",
Value: "",
},
mcnflag.StringFlag{
Name: "opennebula-ssh-user",
Usage: "Set the name of the SSH user",
EnvVar: "ONE_SSH_USER",
Value: defaultSSHUser,
},
mcnflag.BoolFlag{
Name: "opennebula-disable-vnc",
Usage: "VNC is enabled by default. Disable it with this flag",
EnvVar: "ONE_DISABLE_VNC",
},
mcnflag.StringFlag{
Name: "opennebula-user",
Usage: "Set the user for authentication",
EnvVar: "ONE_USER",
},
mcnflag.StringFlag{
Name: "opennebula-password",
Usage: "Set the password for authentication",
EnvVar: "ONE_PASSWORD",
},
mcnflag.StringFlag{
Name: "opennebula-xmlrpcurl",
Usage: "Set the url for one xmlrpc server",
EnvVar: "ONE_XMLRPC",
},
}
}
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
d.SetSwarmConfigFromFlags(flags)
// Authentication
d.User = flags.String("opennebula-user")
d.Password = flags.String("opennebula-password")
d.Xmlrpcurl = flags.String("opennebula-xmlrpcurl")
// Capacity
d.CPU = flags.String("opennebula-cpu")
d.VCPU = flags.String("opennebula-vcpu")
d.Memory = flags.String("opennebula-memory")
// Template
d.TemplateName = flags.String("opennebula-template-name")
d.TemplateID = flags.String("opennebula-template-id")
// Network
d.NetworkName = flags.String("opennebula-network-name")
d.NetworkID = flags.String("opennebula-network-id")
d.NetworkOwner = flags.String("opennebula-network-owner")
// Storage
d.ImageID = flags.String("opennebula-image-id")
d.ImageName = flags.String("opennebula-image-name")
d.ImageOwner = flags.String("opennebula-image-owner")
d.ImageDevPrefix = flags.String("opennebula-dev-prefix")
d.DiskSize = flags.String("opennebula-disk-resize")
d.B2DSize = flags.String("opennebula-b2d-size")
// Provision
d.SSHUser = flags.String("opennebula-ssh-user")
// VNC
d.DisableVNC = flags.Bool("opennebula-disable-vnc")
// Either TemplateName or TemplateID
if d.TemplateName != "" && d.TemplateID != "" {
return errors.New("specify only one of: --opennebula-template-name or --opennebula-template-id, not both")
}
// Either NetworkName or NetworkID
if d.NetworkName != "" && d.NetworkID != "" {
return errors.New("specify only one of: --opennebula-network-name or --opennebula-network-id, not both")
}
// Either ImageName or ImageID
if d.ImageName != "" && d.ImageID != "" {
return errors.New("specify only one of: --opennebula-image-name or --opennebula-image-id, not both")
}
// Required and incompatible options for Template
if d.TemplateName != "" || d.TemplateID != "" {
// Template has been specified:
// ImageName and ImageID are incompatible
if d.ImageName != "" || d.ImageID != "" {
return errors.New("options --opennebula-image-* are incompatible with --opennebula-template-*")
}
// ImageDevPrefix is incompatible
if d.ImageDevPrefix != "" {
return errors.New("option: --opennebula-dev-prefix is incompatible with --opennebula-template-*")
}
// DiskSize is incompatible
if d.DiskSize != "" {
return errors.New("option: --opennebula-disk-resize is incompatible with --opennebula-template-*")
}
// B2DSize is incompatible
if d.B2DSize != "" {
return errors.New("option: --opennebula-disk-resize is incompatible with --opennebula-template-*")
}
// DisableVNC is incompatible
if d.DisableVNC {
return errors.New("option: --opennebula-disable-vnc is incompatible with --opennebula-template-*")
}
} else {
//Template has NOT been specified:
// ImageName or ImageID is required
if d.ImageName == "" && d.ImageID == "" {
return errors.New("specify a image to use as the OS with --opennebula-image-name or --opennebula-image-id")
}
// NetworkName or NetworkID is required
if d.NetworkName == "" && d.NetworkID == "" {
return errors.New("specify a network to connect to with --opennebula-network-name or --opennebula-network-id")
}
// Assign default capacity values
if d.CPU == "" {
d.CPU = defaultCPU
}
if d.VCPU == "" {
d.VCPU = defaultVCPU
}
if d.Memory == "" {
d.Memory = defaultMemory
}
}
return nil
}
func (d *Driver) DriverName() string {
return "opennebula"
}
func (d *Driver) GetSSHHostname() (string, error) {
return d.GetIP()
}
func (d *Driver) GetSSHUsername() string {
return d.SSHUser
}
func (d *Driver) PreCreateCheck() error {
return nil
}
func (d *Driver) Create() error {
var (
vector *goca.TemplateBuilderVector
vmtemplate *goca.Template
err error
)
// build config and set the xmlrpc client
d.setClient()
log.Infof("Creating SSH key..")
if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
return err
}
pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath())
if err != nil {
return err
}
// Create template
template := goca.NewTemplateBuilder()
if d.TemplateName != "" || d.TemplateID != "" {
// Template has been specified
} else {
// Template has NOT been specified
template.AddValue("NAME", d.MachineName)
// OS Boot
vector = template.NewVector("OS")
vector.AddValue("BOOT", "disk0")
// OS Disk
vector = template.NewVector("DISK")
if d.ImageID != "" {
vector.AddValue("IMAGE_ID", d.ImageID)
} else {
vector.AddValue("IMAGE", d.ImageName)
if d.ImageOwner != "" {
vector.AddValue("IMAGE_UNAME", d.ImageOwner)
}
}
if d.DiskSize != "" {
vector.AddValue("SIZE", d.DiskSize)
}
if d.ImageDevPrefix != "" {
vector.AddValue("DEV_PREFIX", d.ImageDevPrefix)
}
// Add a volatile disk for b2d
if d.B2DSize != "" {
vector = template.NewVector("DISK")
vector.AddValue("SIZE", d.B2DSize)
vector.AddValue("TYPE", "fs")
vector.AddValue("FORMAT", "raw")
}
// VNC
if !d.DisableVNC {
vector = template.NewVector("GRAPHICS")
vector.AddValue("LISTEN", "0.0.0.0")
vector.AddValue("TYPE", "vnc")
}
}
// Capacity
if d.CPU != "" {
template.AddValue("CPU", d.CPU)
}
if d.Memory != "" {
template.AddValue("MEMORY", d.Memory)
}
if d.VCPU != "" {
template.AddValue("VCPU", d.VCPU)
}
// Network
if d.NetworkName != "" || d.NetworkID != "" {
vector = template.NewVector("NIC")
if d.NetworkName != "" {
vector.AddValue("NETWORK", d.NetworkName)
if d.NetworkOwner != "" {
vector.AddValue("NETWORK_UNAME", d.NetworkOwner)
}
}
if d.NetworkID != "" {
vector.AddValue("NETWORK_ID", d.NetworkID)
}
}
// Context
vector = template.NewVector("CONTEXT")
vector.AddValue("NETWORK", "YES")
vector.AddValue("SSH_PUBLIC_KEY", "$USER[SSH_PUBLIC_KEY]")
vector.AddValue("DOCKER_SSH_USER", d.SSHUser)
vector.AddValue("DOCKER_SSH_PUBLIC_KEY", string(pubKey))
contextScript64 := base64.StdEncoding.EncodeToString([]byte(contextScript))
vector.AddValue("START_SCRIPT_BASE64", contextScript64)
// Instantiate
log.Infof("Starting VM..")
if d.TemplateName != "" || d.TemplateID != "" {
if d.TemplateName != "" {
vmtemplate, err = goca.NewTemplateFromName(d.TemplateName)
if err != nil {
return err
}
} else {
templateID, err := strconv.Atoi(d.TemplateID)
if err != nil {
return err
}
vmtemplate = goca.NewTemplate(uint(templateID))
}
_, err = vmtemplate.Instantiate(d.MachineName, false, template.String())
} else {
_, err = goca.CreateVM(template.String(), false)
}
if err != nil {
return err
}
if d.IPAddress, err = d.GetIP(); err != nil {
return err
}
return d.Start()
}
func (d *Driver) GetURL() (string, error) {
ip, err := d.GetIP()
if err != nil {
return "", err
}
return fmt.Sprintf("tcp://%s:2376", ip), nil
}
func (d *Driver) GetIP() (string, error) {
d.setClient()
vm, err := goca.NewVMFromName(d.MachineName)
if err != nil {
return "", err
}
err = vm.Info()
if err != nil {
return "", err
}
if ip, ok := vm.XPath("/VM/TEMPLATE/NIC/IP"); ok {
d.IPAddress = ip
}
if d.IPAddress == "" {
return "", fmt.Errorf("IP address is not set")
}
return d.IPAddress, nil
}
func (d *Driver) GetState() (state.State, error) {
d.setClient()
vm, err := goca.NewVMFromName(d.MachineName)
if err != nil {
return state.None, err
}
err = vm.Info()
if err != nil {
return state.None, err
}
vmState, lcmState, err := vm.StateString()
if err != nil {
return state.None, err
}
switch vmState {
case "INIT", "PENDING", "HOLD", "CLONING":
return state.Starting, nil
case "ACTIVE":
switch lcmState {
case "RUNNING",
// migration is considered running
"MIGRATE",
"SAVE_MIGRATE",
"PROLOG_MIGRATE",
"BOOT_MIGRATE",
// recover --recreate is also considered running
"CLEANUP_RESUBMIT",
// operation on the VMs
"HOTPLUG",
"HOTPLUG_SNAPSHOT",
"HOTPLUG_NIC",
"HOTPLUG_SAVEAS",
"DISK_SNAPSHOT",
"DISK_SNAPSHOT_DELETE":
return state.Running, nil
case "LCM_INIT",
"PROLOG",
"BOOT",
"PROLOG_RESUME",
"BOOT_UNKNOWN",
"BOOT_POWEROFF",
"BOOT_SUSPENDED",
"BOOT_STOPPED",
"PROLOG_UNDEPLOY",
"BOOT_UNDEPLOY",
"PROLOG_MIGRATE_UNKNOWN":
return state.Starting, nil
case "HOTPLUG_SAVEAS_POWEROFF",
"HOTPLUG_SAVEAS_SUSPENDED",
"HOTPLUG_PROLOG_POWEROFF",
"HOTPLUG_EPILOG_POWEROFF",
"PROLOG_MIGRATE_POWEROFF",
"PROLOG_MIGRATE_SUSPEND",
"DISK_SNAPSHOT_POWEROFF",
"DISK_SNAPSHOT_REVERT_POWEROFF",
"DISK_SNAPSHOT_DELETE_POWEROFF",
"DISK_SNAPSHOT_SUSPENDED",
"DISK_SNAPSHOT_REVERT_SUSPENDED",
"DISK_SNAPSHOT_DELETE_SUSPENDED":
return state.Stopped, nil
case "SAVE_SUSPEND",
"SAVE_STOP",
"EPILOG_STOP",
"EPILOG",
"SHUTDOWN",
"SHUTDOWN_POWEROFF",
"SHUTDOWN_UNDEPLOY",
"EPILOG_UNDEPLOY":
return state.Stopping, nil
case "UNKNOWN",
"CLEANUP_DELETE",
"BOOT_FAILURE",
"BOOT_MIGRATE_FAILURE",
"PROLOG_MIGRATE_FAILURE",
"PROLOG_FAILURE",
"EPILOG_FAILURE",
"EPILOG_STOP_FAILURE",
"EPILOG_UNDEPLOY_FAILURE",
"PROLOG_MIGRATE_POWEROFF_FAILURE",
"PROLOG_MIGRATE_SUSPEND_FAILURE",
"BOOT_UNDEPLOY_FAILURE",
"BOOT_STOPPED_FAILURE",
"PROLOG_RESUME_FAILURE",
"PROLOG_UNDEPLOY_FAILURE",
"PROLOG_MIGRATE_UNKNOWN_FAILURE":
return state.Error, nil
}
case "POWEROFF", "UNDEPLOYED":
return state.Stopped, nil
case "STOPPED", "SUSPENDED":
return state.Saved, nil
case "DONE", "FAILED", "CLONING_FAILURE":
return state.Error, nil
}
return state.Error, nil
}
func (d *Driver) Start() error {
d.setClient()
vm, err := goca.NewVMFromName(d.MachineName)
if err != nil {
return err
}
vm.Resume()
s := state.None
for retry := 0; retry < 50 && s != state.Running; retry++ {
s, err = d.GetState()
if err != nil {
return err
}
switch s {
case state.Error:
return errors.New("vM in error state")
default:
time.Sleep(2 * time.Second)
}
}
if d.IPAddress == "" {
if d.IPAddress, err = d.GetIP(); err != nil {
return err
}
}
log.Infof("Waiting for SSH..")
// Wait for SSH over NAT to be available before returning to user
return drivers.WaitForSSH(d)
}
func (d *Driver) Stop() error {
d.setClient()
vm, err := goca.NewVMFromName(d.MachineName)
if err != nil {
return err
}
err = vm.Poweroff()
if err != nil {
return err
}
return nil
}
func (d *Driver) Remove() error {
d.setClient()
vm, err := goca.NewVMFromName(d.MachineName)
if err != nil {
return err
}
err = vm.TerminateHard()
if err != nil {
return err
}
return nil
}
func (d *Driver) Restart() error {
d.setClient()
vm, err := goca.NewVMFromName(d.MachineName)
if err != nil {
return err
}
err = vm.Reboot()
if err != nil {
return err
}
return nil
}
func (d *Driver) Kill() error {
d.setClient()
vm, err := goca.NewVMFromName(d.MachineName)
if err != nil {
return err
}
return vm.PoweroffHard()
}
func (d *Driver) publicSSHKeyPath() string {
return d.GetSSHKeyPath() + ".pub"
}

View File

@ -0,0 +1 @@
package opennebula

View File

@ -0,0 +1,18 @@
.PHONY: build test help default
default: test
help:
@echo 'Management commands for goca:'
@echo
@echo 'Usage:'
@echo ' make test Run the tests.'
@echo ' make get-deps runs glide install, mostly used for ci.'
@echo
test:
go test $(glide nv)
golint $(glide nv)
get-deps:
glide install

View File

@ -0,0 +1,38 @@
package goca
// ACLPool represents an OpenNebula ACL list pool
type ACLPool struct {
XMLResource
}
// NewACLPool returns an acl pool. A connection to OpenNebula is
// performed.
func NewACLPool() (*ACLPool, error) {
response, err := client.Call("one.acl.info")
if err != nil {
return nil, err
}
aclpool := &ACLPool{XMLResource{body: response.Body()}}
return aclpool, err
}
// CreateACLRule adds a new ACL rule.
// * user: User component of the new rule. A string containing a hex number.
// * resource: Resource component of the new rule. A string containing a hex number.
// * rights: Rights component of the new rule. A string containing a hex number.
func CreateACLRule(user, resource, rights string) (uint, error) {
response, err := client.Call("one.acl.addrule", user, resource, rights)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// DeleteACLRule deletes an ACL rule.
func DeleteACLRule(aclID uint) error {
_, err := client.Call("one.acl.delrule", int(aclID))
return err
}

View File

@ -0,0 +1,130 @@
package goca
// Cluster represents an OpenNebula Cluster
type Cluster struct {
XMLResource
ID uint
Name string
}
// ClusterPool represents an OpenNebula ClusterPool
type ClusterPool struct {
XMLResource
}
// NewClusterPool returns a cluster pool. A connection to OpenNebula is
// performed.
func NewClusterPool() (*ClusterPool, error) {
response, err := client.Call("one.clusterpool.info")
if err != nil {
return nil, err
}
clusterpool := &ClusterPool{XMLResource{body: response.Body()}}
return clusterpool, err
}
// NewCluster finds a cluster object by ID. No connection to OpenNebula.
func NewCluster(id uint) *Cluster {
return &Cluster{ID: id}
}
// NewClusterFromName finds a cluster object by name. It connects to
// 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) {
clusterPool, err := NewClusterPool()
if err != nil {
return nil, err
}
id, err := clusterPool.GetIDFromName(name, "/CLUSTER_POOL/CLUSTER")
if err != nil {
return nil, err
}
return NewCluster(id), nil
}
// CreateCluster allocates a new cluster. It returns the new cluster ID.
func CreateCluster(name string) (uint, error) {
response, err := client.Call("one.cluster.allocate", name)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Delete deletes the given cluster from the pool.
func (cluster *Cluster) Delete() error {
_, err := client.Call("one.cluster.delete", cluster.ID)
return err
}
// Update replaces the cluster cluster contents.
// * tpl: The new cluster contents. Syntax can be the usual attribute=value or
// XML.
// * appendCluster: Update type: 0: Replace the whole cluster. 1: Merge new
// cluster with the existing one.
func (cluster *Cluster) Update(tpl string, appendCluster int) error {
_, err := client.Call("one.cluster.update", cluster.ID, tpl, appendCluster)
return err
}
// AddHost adds a host to the given cluster.
// * hostID: The host ID.
func (cluster *Cluster) AddHost(hostID uint) error {
_, err := client.Call("one.cluster.addhost", cluster.ID, int(hostID))
return err
}
// DelHost removes a host from the given cluster.
// * hostID: The host ID.
func (cluster *Cluster) DelHost(hostID uint) error {
_, err := client.Call("one.cluster.delhost", cluster.ID, int(hostID))
return err
}
// AddDatastore adds a datastore to the given cluster.
// * dsID: The datastore ID.
func (cluster *Cluster) AddDatastore(dsID uint) error {
_, err := client.Call("one.cluster.adddatastore", cluster.ID, int(dsID))
return err
}
// DelDatastore removes a datastore from the given cluster.
// * dsID: The datastore ID.
func (cluster *Cluster) DelDatastore(dsID uint) error {
_, err := client.Call("one.cluster.deldatastore", cluster.ID, int(dsID))
return err
}
// AddVnet adds a vnet to the given cluster.
// * vnetID: The vnet ID.
func (cluster *Cluster) AddVnet(vnetID uint) error {
_, err := client.Call("one.cluster.addvnet", cluster.ID, int(vnetID))
return err
}
// DelVnet removes a vnet from the given cluster.
// * vnetID: The vnet ID.
func (cluster *Cluster) DelVnet(vnetID uint) error {
_, err := client.Call("one.cluster.delvnet", cluster.ID, int(vnetID))
return err
}
// Rename renames a cluster.
// * newName: The new name.
func (cluster *Cluster) Rename(newName string) error {
_, err := client.Call("one.cluster.rename", cluster.ID, newName)
return err
}
// Info retrieves information for the cluster.
func (cluster *Cluster) Info() error {
_, err := client.Call("one.cluster.info", cluster.ID)
return err
}

View File

@ -0,0 +1,117 @@
package goca
// Datastore represents an OpenNebula Datastore
type Datastore struct {
XMLResource
ID uint
Name string
}
// DatastorePool represents an OpenNebula DatastorePool
type DatastorePool struct {
XMLResource
}
// NewDatastorePool returns a datastore pool. A connection to OpenNebula is
// performed.
func NewDatastorePool() (*DatastorePool, error) {
response, err := client.Call("one.datastorepool.info")
if err != nil {
return nil, err
}
datastorepool := &DatastorePool{XMLResource{body: response.Body()}}
return datastorepool, err
}
// NewDatastore finds a datastore object by ID. No connection to OpenNebula.
func NewDatastore(id uint) *Datastore {
return &Datastore{ID: id}
}
// NewDatastoreFromName finds a datastore object by name. It connects to
// 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) {
datastorePool, err := NewDatastorePool()
if err != nil {
return nil, err
}
id, err := datastorePool.GetIDFromName(name, "/DATASTORE_POOL/DATASTORE")
if err != nil {
return nil, err
}
return NewDatastore(id), nil
}
// CreateDatastore allocates a new datastore. It returns the new datastore ID.
// * tpl: template of the datastore
// * clusterID: The cluster ID. If it is -1, the default one will be used.
func CreateDatastore(tpl string, clusterID int) (uint, error) {
response, err := client.Call("one.datastore.allocate", tpl, clusterID)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Delete deletes the given datastore from the pool.
func (datastore *Datastore) Delete() error {
_, err := client.Call("one.datastore.delete", datastore.ID)
return err
}
// Update replaces the datastore template contents.
// * tpl: The new template contents. Syntax can be the usual attribute=value or XML.
// * appendTemplate: Update type: 0: Replace the whole template. 1: Merge new template with the existing one.
func (datastore *Datastore) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.datastore.update", datastore.ID, tpl, appendTemplate)
return err
}
// Chmod changes the permission bits of a datastore.
// * uu: USER USE bit. If set to -1, it will not change.
// * um: USER MANAGE bit. If set to -1, it will not change.
// * ua: USER ADMIN bit. If set to -1, it will not change.
// * gu: GROUP USE bit. If set to -1, it will not change.
// * gm: GROUP MANAGE bit. If set to -1, it will not change.
// * ga: GROUP ADMIN bit. If set to -1, it will not change.
// * ou: OTHER USE bit. If set to -1, it will not change.
// * om: OTHER MANAGE bit. If set to -1, it will not change.
// * oa: OTHER ADMIN bit. If set to -1, it will not change.
func (datastore *Datastore) Chmod(uu, um, ua, gu, gm, ga, ou, om, oa int) error {
_, err := client.Call("one.datastore.chmod", datastore.ID, uu, um, ua, gu, gm, ga, ou, om, oa)
return err
}
// Chown changes the ownership of a datastore.
// * userID: The User ID of the new owner. If set to -1, it will not change.
// * groupID: The Group ID of the new group. If set to -1, it will not change.
func (datastore *Datastore) Chown(userID, groupID uint) error {
_, err := client.Call("one.datastore.chown", datastore.ID, int(userID), int(groupID))
return err
}
// Rename renames a datastore.
// * newName: The new name.
func (datastore *Datastore) Rename(newName string) error {
_, err := client.Call("one.datastore.rename", datastore.ID, newName)
return err
}
// Enable enables or disables a datastore.
// * enable: True for enabling
func (datastore *Datastore) Enable(enable bool) error {
_, err := client.Call("one.datastore.enable", datastore.ID, enable)
return err
}
// Info retrieves information for the datastore.
func (datastore *Datastore) Info() error {
_, err := client.Call("one.datastore.info", datastore.ID)
return err
}

View File

@ -0,0 +1,144 @@
package goca
import "errors"
// Document represents an OpenNebula Document
type Document struct {
XMLResource
ID uint
Name string
}
// DocumentPool represents an OpenNebula DocumentPool
type DocumentPool struct {
XMLResource
}
// NewDocumentPool returns a document pool. A connection to OpenNebula is
// performed.
func NewDocumentPool(documentType int, args ...int) (*DocumentPool, error) {
var who, start, end int
switch len(args) {
case 0:
who = PoolWhoMine
start = -1
end = -1
case 1:
who = args[0]
start = -1
end = -1
case 3:
who = args[0]
start = args[1]
end = args[2]
default:
return nil, errors.New("Wrong number of arguments")
}
response, err := client.Call("one.documentpool.info", who, start, end, documentType)
if err != nil {
return nil, err
}
documentpool := &DocumentPool{XMLResource{body: response.Body()}}
return documentpool, err
}
// NewDocument finds a document object by ID. No connection to OpenNebula.
func NewDocument(id uint) *Document {
return &Document{ID: id}
}
// NewDocumentFromName finds a document object by name. It connects to
// 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) {
documentPool, err := NewDocumentPool(documentType)
if err != nil {
return nil, err
}
id, err := documentPool.GetIDFromName(name, "/DOCUMENT_POOL/DOCUMENT")
if err != nil {
return nil, err
}
return NewDocument(id), nil
}
// CreateDocument allocates a new document. It returns the new document ID.
func CreateDocument(tpl string, documentType int) (uint, error) {
response, err := client.Call("one.document.allocate", tpl, documentType)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Clone clones an existing document.
// * newName: Name for the new document.
func (document *Document) Clone(newName string) error {
_, err := client.Call("one.document.clone", document.ID, newName)
return err
}
// Delete deletes the given document from the pool.
func (document *Document) Delete() error {
_, err := client.Call("one.document.delete", document.ID)
return err
}
// Update replaces the document template contents.
// * tpl: The new document template contents. Syntax can be the usual attribute=value or XML.
// * appendTemplate: Update type: 0: Replace the whole template. 1: Merge new template with the existing one.
func (document *Document) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.document.update", document.ID, tpl, appendTemplate)
return err
}
// Chmod changes the permission bits of a document.
// * uu: USER USE bit. If set to -1, it will not change.
// * um: USER MANAGE bit. If set to -1, it will not change.
// * ua: USER ADMIN bit. If set to -1, it will not change.
// * gu: GROUP USE bit. If set to -1, it will not change.
// * gm: GROUP MANAGE bit. If set to -1, it will not change.
// * ga: GROUP ADMIN bit. If set to -1, it will not change.
// * ou: OTHER USE bit. If set to -1, it will not change.
// * om: OTHER MANAGE bit. If set to -1, it will not change.
// * oa: OTHER ADMIN bit. If set to -1, it will not change.
func (document *Document) Chmod(uu, um, ua, gu, gm, ga, ou, om, oa int) error {
_, err := client.Call("one.document.chmod", document.ID, uu, um, ua, gu, gm, ga, ou, om, oa)
return err
}
// Chown changes the ownership of a document.
// * userID: The User ID of the new owner. If set to -1, it will not change.
// * groupID: The Group ID of the new group. If set to -1, it will not change.
func (document *Document) Chown(userID, groupID uint) error {
_, err := client.Call("one.document.chown", document.ID, int(userID), int(groupID))
return err
}
// Rename renames a document.
// * newName: The new name.
func (document *Document) Rename(newName string) error {
_, err := client.Call("one.document.rename", document.ID, newName)
return err
}
// Lock locks the document at the api level. The lock automatically expires after 2 minutes.
// * applicationName: String to identify the application requesting the lock.
func (document *Document) Lock(applicationName string) error {
_, err := client.Call("one.document.lock", document.ID, applicationName)
return err
}
// Unlock unlocks the document at the api level.
// * applicationName: String to identify the application requesting the lock.
func (document *Document) Unlock(applicationName string) error {
_, err := client.Call("one.document.unlock", document.ID, applicationName)
return err
}

13
src/oca/go/src/goca/glide.lock generated Normal file
View File

@ -0,0 +1,13 @@
hash: ac9bb59552226f818b59a1f54831140c20901e060de067f144f54d4e100d0e12
updated: 2017-11-28T12:03:57.539724958+01:00
imports:
- name: github.com/kolo/xmlrpc
version: 0826b98aaa29c0766956cb40d45cf7482a597671
- name: golang.org/x/net
version: fc492d2e106922eb3aa4bca863d55e882c087ae3
subpackages:
- html
- html/atom
- name: gopkg.in/xmlpath.v2
version: 860cbeca3ebcc600db0b213c0e83ad6ce91f5739
testImports: []

View File

@ -0,0 +1,4 @@
package: github.com/OpenNebula/goca
import:
- package: github.com/kolo/xmlrpc
- package: gopkg.in/xmlpath.v2

178
src/oca/go/src/goca/goca.go Normal file
View File

@ -0,0 +1,178 @@
package goca
import (
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"github.com/kolo/xmlrpc"
)
var (
client *oneClient
)
// OneConfig contains the information to communicate with OpenNebula
type OneConfig struct {
// Token is the authentication string. In the format of <user>:<password>
Token string
// XmlrpcURL contains OpenNebula's XML-RPC API endpoint. Defaults to
// http://localhost:2633/RPC2
XmlrpcURL string
}
type oneClient struct {
token string
xmlrpcClient *xmlrpc.Client
xmlrpcClientError error
}
type response struct {
status bool
body string
bodyInt int
}
// 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() {
err := SetClient(NewConfig("", "", ""))
if err != nil {
log.Fatal(err)
}
}
// NewConfig returns a new OneConfig object with the specified user, password,
// and xmlrpcURL
func NewConfig(user string, password string, xmlrpcURL string) OneConfig {
var authToken string
var oneAuthPath string
oneXmlrpc := xmlrpcURL
if user == "" && password == "" {
oneAuthPath = os.Getenv("ONE_AUTH")
if oneAuthPath == "" {
oneAuthPath = os.Getenv("HOME") + "/.one/one_auth"
}
token, err := ioutil.ReadFile(oneAuthPath)
if err == nil {
authToken = strings.TrimSpace(string(token))
} else {
authToken = ""
}
} else {
authToken = user + ":" + password
}
if oneXmlrpc == "" {
oneXmlrpc = os.Getenv("ONE_XMLRPC")
if oneXmlrpc == "" {
oneXmlrpc = "http://localhost:2633/RPC2"
}
}
config := OneConfig{
Token: authToken,
XmlrpcURL: oneXmlrpc,
}
return config
}
// SetClient assigns a value to the client variable
func SetClient(conf OneConfig) error {
xmlrpcClient, xmlrpcClientError := xmlrpc.NewClient(conf.XmlrpcURL, nil)
client = &oneClient{
token: conf.Token,
xmlrpcClient: xmlrpcClient,
xmlrpcClientError: xmlrpcClientError,
}
return nil
}
// SystemVersion returns the current OpenNebula Version
func SystemVersion() (string, error) {
response, err := client.Call("one.system.version")
if err != nil {
return "", err
}
return response.Body(), nil
}
// 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) {
var (
ok bool
status bool
body string
bodyInt int64
)
if c.xmlrpcClientError != nil {
return nil, fmt.Errorf("Unitialized client. Token: '%s', xmlrpcClient: '%s'", c.token, c.xmlrpcClientError)
}
result := []interface{}{}
xmlArgs := make([]interface{}, len(args)+1)
xmlArgs[0] = c.token
copy(xmlArgs[1:], args[:])
err := c.xmlrpcClient.Call(method, xmlArgs, &result)
if err != nil {
log.Fatal(err)
}
status, ok = result[0].(bool)
if ok == false {
log.Fatal("Unexpected XML-RPC response. Expected: Index 0 Boolean")
}
body, ok = result[1].(string)
if ok == false {
bodyInt, ok = result[1].(int64)
if ok == false {
log.Fatal("Unexpected XML-RPC response. Expected: Index 0 Int or String")
}
}
// TODO: errCode? result[2]
r := &response{status, body, int(bodyInt)}
if status == false {
err = errors.New(body)
}
return r, err
}
// Body accesses the body of the response
func (r *response) Body() string {
return r.body
}
// BodyInt accesses the body of the response, if it's an int.
func (r *response) BodyInt() int {
return r.bodyInt
}

View File

@ -0,0 +1,99 @@
package goca
// Group represents an OpenNebula Group
type Group struct {
XMLResource
ID uint
Name string
}
// GroupPool represents an OpenNebula GroupPool
type GroupPool struct {
XMLResource
}
// NewGroupPool returns a group pool. A connection to OpenNebula is
// performed.
func NewGroupPool() (*GroupPool, error) {
response, err := client.Call("one.grouppool.info")
if err != nil {
return nil, err
}
grouppool := &GroupPool{XMLResource{body: response.Body()}}
return grouppool, err
}
// NewGroup finds a group object by ID. No connection to OpenNebula.
func NewGroup(id uint) *Group {
return &Group{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) {
groupPool, err := NewGroupPool()
if err != nil {
return nil, err
}
id, err := groupPool.GetIDFromName(name, "/GROUP_POOL/GROUP")
if err != nil {
return nil, err
}
return NewGroup(id), nil
}
// CreateGroup allocates a new group. It returns the new group ID.
func CreateGroup(name string) (uint, error) {
response, err := client.Call("one.group.allocate", name)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Delete deletes the given group from the pool.
func (group *Group) Delete() error {
_, err := client.Call("one.group.delete", group.ID)
return err
}
// Info retrieves information for the group.
func (group *Group) Info() error {
_, err := client.Call("one.group.info", group.ID)
return err
}
// Update replaces the group template contents.
// * tpl: The new template contents. Syntax can be the usual attribute=value or XML.
// * appendTemplate: Update type: 0: Replace the whole template. 1: Merge new template with the existing one.
func (group *Group) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.group.update", group.ID, tpl, appendTemplate)
return err
}
// AddAdmin adds a User to the Group administrators set
// * userID: The user ID.
func (group *Group) AddAdmin(userID uint) error {
_, err := client.Call("one.group.addadmin", group.ID, int(userID))
return err
}
// DelAdmin removes a User from the Group administrators set
// * userID: The user ID.
func (group *Group) DelAdmin(userID uint) error {
_, err := client.Call("one.group.deladmin", group.ID, int(userID))
return err
}
// Quota sets the group quota limits.
// * tpl: The new quota template contents. Syntax can be the usual attribute=value or XML.
func (group *Group) Quota(tpl string) error {
_, err := client.Call("one.group.quota", group.ID, tpl)
return err
}

View File

@ -0,0 +1,45 @@
package goca
import (
"crypto/md5"
"fmt"
"strconv"
"testing"
"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)
d := []byte(t)
h := fmt.Sprintf("%x", md5.Sum(d))[:6]
return name + "-" + h
}
func WaitResource(f func() bool) bool {
for i := 0; i < 20; i++ {
if f() {
return true
}
time.Sleep(2 * time.Second)
}
return false
}

102
src/oca/go/src/goca/host.go Normal file
View File

@ -0,0 +1,102 @@
package goca
// Host represents an OpenNebula Host
type Host struct {
XMLResource
ID uint
Name string
}
// HostPool represents an OpenNebula HostPool
type HostPool struct {
XMLResource
}
// NewHostPool returns a host pool. A connection to OpenNebula is
// performed.
func NewHostPool() (*HostPool, error) {
response, err := client.Call("one.hostpool.info")
if err != nil {
return nil, err
}
hostpool := &HostPool{XMLResource{body: response.Body()}}
return hostpool, err
}
// NewHost finds a host object by ID. No connection to OpenNebula.
func NewHost(id uint) *Host {
return &Host{ID: id}
}
// NewHostFromName finds a host object by name. It connects to
// 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) {
hostPool, err := NewHostPool()
if err != nil {
return nil, err
}
id, err := hostPool.GetIDFromName(name, "/HOST_POOL/HOST")
if err != nil {
return nil, err
}
return NewHost(id), nil
}
// CreateHost allocates a new host. It returns the new host ID.
// * name: name of the host
// * im: information driver for the host
// * vm: virtualization driver for the host
// * clusterID: The cluster ID. If it is -1, the default one will be used.
func CreateHost(name, im, vm string, clusterID int) (uint, error) {
response, err := client.Call("one.host.allocate", name, im, vm, clusterID)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Delete deletes the given host from the pool
func (host *Host) Delete() error {
_, err := client.Call("one.host.delete", host.ID)
return err
}
// Status sets the status of the host
// * status: 0: ENABLED, 1: DISABLED, 2: OFFLINE
func (host *Host) Status(status int) error {
_, err := client.Call("one.host.status", host.ID, status)
return err
}
// Update replaces the hosts template contents.
// * tpl: The new template contents. Syntax can be the usual attribute=value or XML.
// * appendTemplate: Update type: 0: Replace the whole template. 1: Merge new template with the existing one.
func (host *Host) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.host.update", host.ID, tpl, appendTemplate)
return err
}
// Rename renames a host.
// * newName: The new name.
func (host *Host) Rename(newName string) error {
_, err := client.Call("one.host.rename", host.ID, newName)
return err
}
// Info retrieves information for the host.
func (host *Host) Info() error {
_, err := client.Call("one.host.info", host.ID)
return err
}
// Monitoring returns the host monitoring records.
func (host *Host) Monitoring() error {
_, err := client.Call("one.host.monitoring", host.ID)
return err
}

View File

@ -0,0 +1,168 @@
package goca
import (
"errors"
"strconv"
)
// Image represents an OpenNebula Image
type Image struct {
XMLResource
ID uint
Name string
}
// ImagePool represents an OpenNebula Image pool
type ImagePool struct {
XMLResource
}
// ImageState is the state of the Image
type ImageState int
const (
// ImageInit image is being initialized
ImageInit ImageState = iota
// ImageReady image is ready to be used
ImageReady
// ImageUsed image is in use
ImageUsed
// ImageDisabled image is in disabled
ImageDisabled
// ImageLocked image is locked
ImageLocked
// ImageError image is in error state
ImageError
// ImageClone image is in clone state
ImageClone
// ImageDelete image is in delete state
ImageDelete
// ImageLockUsed image is in locked state (non-persistent)
ImageLockUsed
// ImageLockUsedPers image is in locked state (persistent)
ImageLockUsedPers
)
// String returns the string version of the ImageState
func (s ImageState) String() string {
return [...]string{
"INIT",
"READY",
"USED",
"DISABLED",
"LOCKED",
"ERROR",
"CLONE",
"DELETE",
"USED_PERS",
"LOCKED_USED",
"LOCKED_USED_PERS",
}[s]
}
// CreateImage allocates a new image based on the template string provided. It
// returns the image ID.
func CreateImage(template string, dsid uint) (uint, error) {
response, err := client.Call("one.image.allocate", template, dsid)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// NewImagePool returns a new image pool. It accepts the scope of the query. It
// performs an OpenNebula connectio to fetch the information.
func NewImagePool(args ...int) (*ImagePool, error) {
var who, start, end int
switch len(args) {
case 0:
who = PoolWhoMine
start = -1
end = -1
case 3:
who = args[0]
start = args[1]
end = args[2]
default:
return nil, errors.New("Wrong number of arguments")
}
response, err := client.Call("one.imagepool.info", who, start, end)
if err != nil {
return nil, err
}
imagepool := &ImagePool{XMLResource{body: response.Body()}}
return imagepool, err
}
// NewImage finds an image by ID returns a new Image object. At this stage no
// connection to OpenNebula is performed.
func NewImage(id uint) *Image {
return &Image{ID: id}
}
// NewImageFromName finds an image by name and returns Image object. It connects
// 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) {
imagePool, err := NewImagePool()
if err != nil {
return nil, err
}
id, err := imagePool.GetIDFromName(name, "/IMAGE_POOL/IMAGE")
if err != nil {
return nil, err
}
return NewImage(id), nil
}
// Info connects to OpenNebula and fetches the information of the Image
func (image *Image) Info() error {
response, err := client.Call("one.image.info", image.ID)
image.body = response.Body()
return err
}
// 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, _ := strconv.Atoi(stateString)
return ImageState(state), nil
}
// StateString returns the state in string format
func (image *Image) StateString() (string, error) {
state, err := image.State()
if err != nil {
return "", err
}
return ImageState(state).String(), nil
}
// Delete will remove the image from OpenNebula, which will remove it from the
// backend.
func (image *Image) Delete() error {
_, err := client.Call("one.image.delete", image.ID)
return err
}

View File

@ -0,0 +1,136 @@
package goca
import (
"errors"
)
// Template represents an OpenNebula Template
type Template struct {
XMLResource
ID uint
Name string
}
// TemplatePool represents an OpenNebula TemplatePool
type TemplatePool struct {
XMLResource
}
// NewTemplatePool returns a template pool. A connection to OpenNebula is
// performed.
func NewTemplatePool(args ...int) (*TemplatePool, error) {
var who, start, end int
switch len(args) {
case 0:
who = PoolWhoMine
start = -1
end = -1
case 3:
who = args[0]
start = args[1]
end = args[2]
default:
return nil, errors.New("Wrong number of arguments")
}
response, err := client.Call("one.templatepool.info", who, start, end)
if err != nil {
return nil, err
}
templatepool := &TemplatePool{XMLResource{body: response.Body()}}
return templatepool, err
}
// NewTemplate finds a template object by ID. No connection to OpenNebula.
func NewTemplate(id uint) *Template {
return &Template{ID: id}
}
// NewTemplateFromName finds a template object by name. It connects to
// 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) {
templatePool, err := NewTemplatePool()
if err != nil {
return nil, err
}
id, err := templatePool.GetIDFromName(name, "/VMTEMPLATE_POOL/VMTEMPLATE")
if err != nil {
return nil, err
}
return NewTemplate(id), nil
}
// CreateTemplate allocates a new template. It returns the new template ID.
func CreateTemplate(template string) (uint, error) {
response, err := client.Call("one.template.allocate", template)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Info connects to OpenNebula and fetches the information of the Template
func (template *Template) Info() error {
response, err := client.Call("one.template.info", template.ID)
template.body = response.Body()
return err
}
// Update will modify the template. If appendTemplate is 0, it will
// replace the whole template. If its 1, it will merge.
func (template *Template) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.template.update", template.ID, tpl, appendTemplate)
return err
}
// Chown changes the owner/group of a template. If uid or gid is -1 it will not
// change
func (template *Template) Chown(uid, gid int) error {
_, err := client.Call("one.template.chown", template.ID, uid, gid)
return err
}
// Chmod changes the permissions of a template. If any perm is -1 it will not
// change
func (template *Template) Chmod(uu, um, ua, gu, gm, ga, ou, om, oa int) error {
_, err := client.Call("one.template.chmod", template.ID, uu, um, ua, gu, gm, ga, ou, om, oa)
return err
}
// Rename changes the name of template
func (template *Template) Rename(newName string) error {
_, err := client.Call("one.template.rename", template.ID, newName)
return err
}
// Delete will remove the template from OpenNebula.
func (template *Template) Delete() error {
_, err := client.Call("one.template.delete", template.ID)
return err
}
// Instantiate will instantiate the template
func (template *Template) Instantiate(name string, pending bool, extra string) (uint, error) {
response, err := client.Call("one.template.instantiate", template.ID, name, pending, extra)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Clone an existing template. If recursive is true it will clone the template
// plus any image defined in DISK. The new IMAGE_ID is set into each DISK.
func (template *Template) Clone(name string, recursive bool) error {
_, err := client.Call("one.template.clone", template.ID, name, recursive)
return err
}

View File

@ -0,0 +1,117 @@
package goca
import (
"errors"
"fmt"
"strings"
)
// TemplateBuilder represents an OpenNebula syntax template
type TemplateBuilder struct {
elements []TemplateBuilderElement
}
// TemplateBuilderElement is an interface that must implement the String
// function
type TemplateBuilderElement interface {
String() string
}
// TemplateBuilderPair is a key / value pair
type TemplateBuilderPair struct {
key string
value string
}
// TemplateBuilderVector contains an array of keyvalue pairs
type TemplateBuilderVector struct {
key string
pairs []TemplateBuilderPair
}
// NewTemplateBuilder returns a new TemplateBuilder object
func NewTemplateBuilder() *TemplateBuilder {
return &TemplateBuilder{}
}
// NewVector creates a new vector in the template
func (t *TemplateBuilder) NewVector(key string) *TemplateBuilderVector {
vector := &TemplateBuilderVector{key: key}
t.elements = append(t.elements, vector)
return vector
}
// String prints the TemplateBuilder in OpenNebula syntax
func (t *TemplateBuilder) String() string {
s := ""
endToken := "\n"
for i, element := range t.elements {
if i == len(t.elements)-1 {
endToken = ""
}
s += element.String() + endToken
}
return s
}
// String prints a TemplateBuilderPair in OpenNebula syntax
func (t *TemplateBuilderPair) String() string {
return fmt.Sprintf("%s=\"%s\"", t.key, t.value)
}
func (t *TemplateBuilderVector) String() string {
s := fmt.Sprintf("%s=[\n", strings.ToUpper(t.key))
endToken := ",\n"
for i, pair := range t.pairs {
if i == len(t.pairs)-1 {
endToken = ""
}
s += fmt.Sprintf(" %s%s", pair.String(), endToken)
}
s += " ]"
return s
}
// AddValue adds a new pair to a TemplateBuilder objects
func (t *TemplateBuilder) AddValue(key string, v interface{}) error {
var val string
switch v := v.(type) {
default:
return errors.New("Unexpected type")
case int, uint:
val = fmt.Sprintf("%d", v)
case string:
val = v
}
pair := &TemplateBuilderPair{strings.ToUpper(key), val}
t.elements = append(t.elements, pair)
return nil
}
// AddValue adds a new pair to a TemplateBuilderVector
func (t *TemplateBuilderVector) AddValue(key string, v interface{}) error {
var val string
switch v := v.(type) {
default:
return errors.New("Unexpected type")
case int, uint:
val = fmt.Sprintf("%d", v)
case string:
val = v
}
pair := TemplateBuilderPair{strings.ToUpper(key), val}
t.pairs = append(t.pairs, pair)
return nil
}

View File

@ -0,0 +1,36 @@
package goca
import (
"fmt"
)
func Example() {
template := NewTemplateBuilder()
// Main
template.AddValue("cpu", 1)
template.AddValue("memory", "64")
template.AddValue("vcpu", "2")
// Disk
vector := template.NewVector("disk")
vector.AddValue("image_id", "119")
vector.AddValue("dev_prefix", "vd")
// NIC
vector = template.NewVector("nic")
vector.AddValue("network_id", "3")
vector.AddValue("model", "virtio")
fmt.Println(template)
// Output:
// CPU="1"
// MEMORY="64"
// VCPU="2"
// DISK=[
// IMAGE_ID="119",
// DEV_PREFIX="vd" ]
// NIC=[
// NETWORK_ID="3",
// MODEL="virtio" ]
}

View File

@ -0,0 +1,132 @@
package goca
import (
"testing"
)
// Helper to create a template
func createTemplate(t *testing.T) *Template {
templateName := GenName("template")
// Create template
tpl := NewTemplateBuilder()
tpl.AddValue("name", templateName)
tpl.AddValue("cpu", 1)
tpl.AddValue("memory", "64")
id, err := CreateTemplate(tpl.String())
if err != nil {
t.Error(err)
}
// Get template by ID
template := NewTemplate(id)
err = template.Info()
if err != nil {
t.Error(err)
}
return template
}
func TestTemplateCreateAndDelete(t *testing.T) {
template := createTemplate(t)
idParse, err := GetID(t, template, "VMTEMPLATE")
if err != nil {
t.Error(err)
}
if idParse != template.ID {
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)
if err != nil {
t.Error(err)
}
err = template.Info()
if err != nil {
t.Error(err)
}
idParse, err = GetID(t, template, "VMTEMPLATE")
if idParse != template.ID {
t.Errorf("Template ID does not match")
}
// Delete template
err = template.Delete()
if err != nil {
t.Error(err)
}
}
func TestTemplateInstantiate(t *testing.T) {
templateName := GenName("template")
// Create template
tpl := NewTemplateBuilder()
tpl.AddValue("name", templateName)
tpl.AddValue("cpu", 1)
tpl.AddValue("memory", "64")
id, err := CreateTemplate(tpl.String())
if err != nil {
t.Error(err)
}
// Get template by ID
template := NewTemplate(id)
// Instantiate(name string, pending bool, extra string) (uint, error)
vmid, err := template.Instantiate("", false, "")
if err != nil {
t.Error(err)
}
vm := NewVM(vmid)
vm.Terminate()
// Delete template
err = template.Delete()
if err != nil {
t.Error(err)
}
}
func TestTemplateUpdate(t *testing.T) {
template := createTemplate(t)
tpl := NewTemplateBuilder()
tpl.AddValue("A", "B")
// Update
template.Update(tpl.String(), 1)
err := template.Info()
if err != nil {
t.Error(err)
}
if val, ok := template.XPath("/VMTEMPLATE/TEMPLATE/A"); !ok || val != "B" {
t.Errorf("Expecting A=B")
}
// Delete template
err = template.Delete()
if err != nil {
t.Error(err)
}
}

134
src/oca/go/src/goca/user.go Normal file
View File

@ -0,0 +1,134 @@
package goca
// User represents an OpenNebula User
type User struct {
XMLResource
ID uint
Name string
}
// UserPool represents an OpenNebula UserPool
type UserPool struct {
XMLResource
}
// NewUserPool returns a user pool. A connection to OpenNebula is
// performed.
func NewUserPool() (*UserPool, error) {
response, err := client.Call("one.userpool.info")
if err != nil {
return nil, err
}
userpool := &UserPool{XMLResource{body: response.Body()}}
return userpool, err
}
// NewUser finds a user object by ID. No connection to OpenNebula.
func NewUser(id uint) *User {
return &User{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) {
userPool, err := NewUserPool()
if err != nil {
return nil, err
}
id, err := userPool.GetIDFromName(name, "/USER_POOL/USER")
if err != nil {
return nil, err
}
return NewUser(id), nil
}
// CreateUser allocates a new user. It returns the new user ID.
// * name: name of the user
// * password: password of the user
// * authDriver: auth driver
// * groupIDs: array of groupIDs to add to the user
func CreateUser(name, password, authDriver string, groupIDs []uint) (uint, error) {
response, err := client.Call("one.user.allocate", name, password, authDriver, groupIDs)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Delete deletes the given user from the pool.
func (user *User) Delete() error {
_, err := client.Call("one.user.delete", user.ID)
return err
}
// Passwd changes the password for the given user.
// * password: The new password
func (user *User) Passwd(password string) error {
_, err := client.Call("one.user.passwd", user.ID, password)
return err
}
// Login generates or sets a login token.
// * token: The token
// * timeSeconds: Valid period in seconds; 0 reset the token and -1 for a non-expiring token.
// * effectiveGID: Effective GID to use with this token. To use the current GID and user groups set it to -1
func (user *User) Login(token string, timeSeconds int, effectiveGID uint) error {
_, err := client.Call("one.user.login", user.ID, token, timeSeconds, int(effectiveGID))
return err
}
// Update replaces the user template contents.
// * tpl: The new template contents. Syntax can be the usual attribute=value or XML.
// * appendTemplate: Update type: 0: Replace the whole template. 1: Merge new template with the existing one.
func (user *User) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.user.update", user.ID, tpl, appendTemplate)
return err
}
// Chauth changes the authentication driver and the password for the given user.
// * authDriver: The new authentication driver.
// * password: The new password. If it is an empty string
func (user *User) Chauth(authDriver, password string) error {
_, err := client.Call("one.user.chauth", user.ID, authDriver, password)
return err
}
// Quota sets the user quota limits.
// * tpl: The new quota template contents. Syntax can be the usual attribute=value or XML.
func (user *User) Quota(tpl string) error {
_, err := client.Call("one.user.quota", user.ID, tpl)
return err
}
// Chgrp changes the group of the given user.
// * groupID: The Group ID of the new group.
func (user *User) Chgrp(groupID uint) error {
_, err := client.Call("one.user.chgrp", user.ID, int(groupID))
return err
}
// AddGroup adds the User to a secondary group.
// * groupID: The Group ID of the new group.
func (user *User) AddGroup(groupID uint) error {
_, err := client.Call("one.user.addgroup", user.ID, int(groupID))
return err
}
// DelGroup removes the User from a secondary group
// * groupID: The Group ID.
func (user *User) DelGroup(groupID uint) error {
_, err := client.Call("one.user.delgroup", user.ID, int(groupID))
return err
}
// Info retrieves information for the user.
func (user *User) Info() error {
_, err := client.Call("one.user.info", user.ID)
return err
}

167
src/oca/go/src/goca/vdc.go Normal file
View File

@ -0,0 +1,167 @@
package goca
// Vdc represents an OpenNebula Vdc
type Vdc struct {
XMLResource
ID uint
Name string
}
// VdcPool represents an OpenNebula VdcPool
type VdcPool struct {
XMLResource
}
// NewVdcPool returns a vdc pool. A connection to OpenNebula is
// performed.
func NewVdcPool() (*VdcPool, error) {
response, err := client.Call("one.vdcpool.info")
if err != nil {
return nil, err
}
vdcpool := &VdcPool{XMLResource{body: response.Body()}}
return vdcpool, err
}
// NewVdc finds a vdc object by ID. No connection to OpenNebula.
func NewVdc(id uint) *Vdc {
return &Vdc{ID: id}
}
// NewVdcFromName finds a vdc object by name. It connects to
// 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) {
vdcPool, err := NewVdcPool()
if err != nil {
return nil, err
}
id, err := vdcPool.GetIDFromName(name, "/VDC_POOL/VDC")
if err != nil {
return nil, err
}
return NewVdc(id), nil
}
// CreateVdc allocates a new vdc. It returns the new vdc ID.
// * tpl: A string containing the template of the VDC. Syntax can be the usual
// attribute=value or XML.
// * clusterID: The cluster ID. If it is -1, this virtual network wont be added
// to any cluster
func CreateVdc(tpl string, clusterID int) (uint, error) {
response, err := client.Call("one.vdc.allocate", tpl, clusterID)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Delete deletes the given VDC from the pool.
func (vdc *Vdc) Delete() error {
_, err := client.Call("one.vdc.delete", vdc.ID)
return err
}
// Update replaces the VDC template contents.
// * tpl: The new template contents. Syntax can be the usual attribute=value or XML.
// * appendTemplate: Update type: 0: Replace the whole template. 1: Merge new template with the existing one.
func (vdc *Vdc) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.vdc.update", vdc.ID, tpl, appendTemplate)
return err
}
// Rename renames a VDC.
// * newName: The new name.
func (vdc *Vdc) Rename(newName string) error {
_, err := client.Call("one.vdc.rename", vdc.ID, newName)
return err
}
// Info retrieves information for the VDC.
func (vdc *Vdc) Info() error {
_, err := client.Call("one.vdc.info", vdc.ID)
return err
}
// AddGroup adds a group to the VDC
// * groupID: The group ID.
func (vdc *Vdc) AddGroup(groupID uint) error {
_, err := client.Call("one.vdc.addgroup", vdc.ID, int(groupID))
return err
}
// DelGroup deletes a group from the VDC
// * groupID: The group ID.
func (vdc *Vdc) DelGroup(groupID uint) error {
_, err := client.Call("one.vdc.delgroup", vdc.ID, int(groupID))
return err
}
// AddCluster adds a cluster to the VDC
// * zoneID: The Zone ID.
// * clusterID: The Cluster ID.
func (vdc *Vdc) AddCluster(zoneID, clusterID uint) error {
_, err := client.Call("one.vdc.addcluster", vdc.ID, int(zoneID), int(clusterID))
return err
}
// DelCluster deletes a cluster from the VDC
// * zoneID: The Zone ID.
// * clusterID: The Cluster ID.
func (vdc *Vdc) DelCluster(zoneID, clusterID uint) error {
_, err := client.Call("one.vdc.delcluster", vdc.ID, int(zoneID), int(clusterID))
return err
}
// AddHost adds a host to the VDC
// * zoneID: The Zone ID.
// * hostID: The Host ID.
func (vdc *Vdc) AddHost(zoneID, hostID uint) error {
_, err := client.Call("one.vdc.addhost", vdc.ID, int(zoneID), int(hostID))
return err
}
// DelHost deletes a host from the VDC
// * zoneID: The Zone ID.
// * hostID: The Host ID.
func (vdc *Vdc) DelHost(zoneID, hostID uint) error {
_, err := client.Call("one.vdc.delhost", vdc.ID, int(zoneID), int(hostID))
return err
}
// AddDatastore adds a datastore to the VDC
// * zoneID: The Zone ID.
// * dsID: The Datastore ID.
func (vdc *Vdc) AddDatastore(zoneID, dsID uint) error {
_, err := client.Call("one.vdc.adddatastore", vdc.ID, int(zoneID), int(dsID))
return err
}
// DelDatastore deletes a datastore from the VDC
// * zoneID: The Zone ID.
// * dsID: The Datastore ID.
func (vdc *Vdc) DelDatastore(zoneID, dsID uint) error {
_, err := client.Call("one.vdc.deldatastore", vdc.ID, int(zoneID), int(dsID))
return err
}
// AddVnet adds a vnet to the VDC
// * zoneID: The Zone ID.
// * vnetID: The Vnet ID.
func (vdc *Vdc) AddVnet(zoneID, vnetID uint) error {
_, err := client.Call("one.vdc.addvnet", vdc.ID, int(zoneID), int(vnetID))
return err
}
// DelVnet deletes a vnet from the VDC
// * zoneID: The Zone ID.
// * vnetID: The Vnet ID.
func (vdc *Vdc) DelVnet(zoneID, vnetID uint) error {
_, err := client.Call("one.vdc.delvnet", vdc.ID, int(zoneID), int(vnetID))
return err
}

View File

@ -0,0 +1,13 @@
package goca
// GitCommit is the git commit that was compiled. This will be filled in by the
// compiler.
var GitCommit string
// Version is the main version number that is being run at the moment.
const Version = "0.1.0"
// VersionPrerelease is the pre-release marker for the version. If this is ""
// (empty string) then it means that it is a final release. Otherwise, this is a
// pre-release such as "dev" (in development)
var VersionPrerelease = ""

View File

@ -0,0 +1,180 @@
package goca
import "errors"
// VirtualNetwork represents an OpenNebula VirtualNetwork
type VirtualNetwork struct {
XMLResource
ID uint
Name string
}
// VirtualNetworkPool represents an OpenNebula VirtualNetworkPool
type VirtualNetworkPool struct {
XMLResource
}
// NewVirtualNetworkPool returns a virtualnetwork pool. A connection to OpenNebula is
// performed.
func NewVirtualNetworkPool(args ...int) (*VirtualNetworkPool, error) {
var who, start, end int
switch len(args) {
case 0:
who = PoolWhoMine
start = -1
end = -1
case 1:
who = args[0]
start = -1
end = -1
case 3:
who = args[0]
start = args[1]
end = args[2]
default:
return nil, errors.New("Wrong number of arguments")
}
response, err := client.Call("one.vnpool.info", who, start, end)
if err != nil {
return nil, err
}
vnpool := &VirtualNetworkPool{XMLResource{body: response.Body()}}
return vnpool, err
}
// NewVirtualNetwork finds a virtualnetwork object by ID. No connection to OpenNebula.
func NewVirtualNetwork(id uint) *VirtualNetwork {
return &VirtualNetwork{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()
if err != nil {
return nil, err
}
id, err := virtualnetworkPool.GetIDFromName(name, "/VNET_POOL/VNET")
if err != nil {
return nil, err
}
return NewVirtualNetwork(id), nil
}
// CreateVirtualnetwork allocates a new virtualnetwork. It returns the new virtualnetwork ID.
// * tpl: template of the virtualnetwork
// * clusterID: The cluster ID. If it is -1, the default one will be used.
func CreateVirtualnetwork(tpl string, clusterID int) (uint, error) {
response, err := client.Call("one.vn.allocate", tpl, clusterID)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Delete deletes the given virtual network from the pool.
func (vn *VirtualNetwork) Delete() error {
_, err := client.Call("one.vn.delete", vn.ID)
return err
}
// AddAr adds address ranges to a virtual network.
// * tpl: template of the address ranges to add. Syntax can be the usual attribute=value or XML
func (vn *VirtualNetwork) AddAr(tpl string) error {
_, err := client.Call("one.vn.add_ar", vn.ID, tpl)
return err
}
// RmAr removes an address range from a virtual network.
// * arID: ID of the address range to remove.
func (vn *VirtualNetwork) RmAr(arID int) error {
_, err := client.Call("one.vn.rm_ar", vn.ID, arID)
return err
}
// UpdateAr updates the attributes of an address range.
// * tpl: template of the address ranges to update. Syntax can be the usual attribute=value or XML
func (vn *VirtualNetwork) UpdateAr(tpl string) error {
_, err := client.Call("one.vn.update_ar", vn.ID, tpl)
return err
}
// Reserve reserve network addresses.
// * tpl: Template
func (vn *VirtualNetwork) Reserve(tpl string) error {
_, err := client.Call("one.vn.reserve", vn.ID, tpl)
return err
}
// FreeAr frees a reserved address range from a virtual network.
// * arID: ID of the address range to free.
func (vn *VirtualNetwork) FreeAr(arID int) error {
_, err := client.Call("one.vn.free_ar", vn.ID, arID)
return err
}
// Hold holds a virtual network Lease as used.
// * tpl: template of the lease to hold
func (vn *VirtualNetwork) Hold(tpl string) error {
_, err := client.Call("one.vn.hold", vn.ID, tpl)
return err
}
// Release releases a virtual network Lease on hold.
// * tpl: template of the lease to release
func (vn *VirtualNetwork) Release(tpl string) error {
_, err := client.Call("one.vn.release", vn.ID, tpl)
return err
}
// Update replaces the virtual network template contents.
// * tpl: The new template contents. Syntax can be the usual attribute=value or XML.
// * appendTemplate: Update type: 0: Replace the whole template. 1: Merge new template with the existing one.
func (vn *VirtualNetwork) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.vn.update", vn.ID, tpl, appendTemplate)
return err
}
// Chmod changes the permission bits of a virtual network.
// * uu: USER USE bit. If set to -1, it will not change.
// * um: USER MANAGE bit. If set to -1, it will not change.
// * ua: USER ADMIN bit. If set to -1, it will not change.
// * gu: GROUP USE bit. If set to -1, it will not change.
// * gm: GROUP MANAGE bit. If set to -1, it will not change.
// * ga: GROUP ADMIN bit. If set to -1, it will not change.
// * ou: OTHER USE bit. If set to -1, it will not change.
// * om: OTHER MANAGE bit. If set to -1, it will not change.
// * oa: OTHER ADMIN bit. If set to -1, it will not change.
func (vn *VirtualNetwork) Chmod(uu, um, ua, gu, gm, ga, ou, om, oa int) error {
_, err := client.Call("one.vn.chmod", vn.ID, uu, um, ua, gu, gm, ga, ou, om, oa)
return err
}
// Chown changes the ownership of a virtual network.
// * userID: The User ID of the new owner. If set to -1, it will not change.
// * groupID: The Group ID of the new group. If set to -1, it will not change.
func (vn *VirtualNetwork) Chown(userID, groupID uint) error {
_, err := client.Call("one.vn.chown", vn.ID, int(userID), int(groupID))
return err
}
// Rename renames a virtual network.
// * newName: The new name.
func (vn *VirtualNetwork) Rename(newName string) error {
_, err := client.Call("one.vn.rename", vn.ID, newName)
return err
}
// Info retrieves information for the virtual network.
func (vn *VirtualNetwork) Info() error {
_, err := client.Call("one.vn.info", vn.ID)
return err
}

840
src/oca/go/src/goca/vm.go Normal file
View File

@ -0,0 +1,840 @@
package goca
import (
"errors"
"strconv"
)
// 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
}
// VMState is the state of the Virtual Machine
type VMState int
const (
// Init state
Init VMState = 0
// Pending state
Pending VMState = 1
// Hold state
Hold VMState = 2
// Active state
Active VMState = 3
// Stopped state
Stopped VMState = 4
// Suspended state
Suspended VMState = 5
// Done state
Done VMState = 6
// Deprecated
// Failed VMState = 7
// Poweroff state
Poweroff VMState = 8
// Undeployed state
Undeployed VMState = 9
// Cloning state
Cloning VMState = 10
// CloningFailure state
CloningFailure VMState = 11
)
func (s VMState) String() string {
switch s {
case Init:
return "INIT"
case Pending:
return "PENDING"
case Hold:
return "HOLD"
case Active:
return "ACTIVE"
case Stopped:
return "STOPPED"
case Suspended:
return "SUSPENDED"
case Done:
return "DONE"
case Poweroff:
return "POWEROFF"
case Undeployed:
return "UNDEPLOYED"
case Cloning:
return "CLONING"
case CloningFailure:
return "CLONINGFAILURE"
default:
return ""
}
}
// LCMState is the life-cycle manager state of the virtual machine. It is used
// only when the VM's state is active, otherwise it's LcmInit
type LCMState int
const (
// LcmInit lcm state
LcmInit LCMState = 0
// Prolog lcm state
Prolog LCMState = 1
// Boot lcm state
Boot LCMState = 2
// Running lcm state
Running LCMState = 3
// Migrate lcm state
Migrate LCMState = 4
// SaveStop lcm state
SaveStop LCMState = 5
// SaveSuspend lcm state
SaveSuspend LCMState = 6
// SaveMigrate lcm state
SaveMigrate LCMState = 7
// PrologMigrate lcm state
PrologMigrate LCMState = 8
// PrologResume lcm state
PrologResume LCMState = 9
// EpilogStop lcm state
EpilogStop LCMState = 10
// Epilog lcm state
Epilog LCMState = 11
// Shutdown lcm state
Shutdown LCMState = 12
// Deprecated
// Cancel LCMState = 13
// Failure LCMState = 14
// CleanupResubmit lcm state
CleanupResubmit LCMState = 15
// Unknown lcm state
Unknown LCMState = 16
// Hotplug lcm state
Hotplug LCMState = 17
// ShutdownPoweroff lcm state
ShutdownPoweroff LCMState = 18
// BootUnknown lcm state
BootUnknown LCMState = 19
// BootPoweroff lcm state
BootPoweroff LCMState = 20
// BootSuspended lcm state
BootSuspended LCMState = 21
// BootStopped lcm state
BootStopped LCMState = 22
// CleanupDelete lcm state
CleanupDelete LCMState = 23
// HotplugSnapshot lcm state
HotplugSnapshot LCMState = 24
// HotplugNic lcm state
HotplugNic LCMState = 25
// HotplugSaveas lcm state
HotplugSaveas LCMState = 26
// HotplugSaveasPoweroff lcm state
HotplugSaveasPoweroff LCMState = 27
// HotplugSaveasSuspended lcm state
HotplugSaveasSuspended LCMState = 28
// ShutdownUndeploy lcm state
ShutdownUndeploy LCMState = 29
// EpilogUndeploy lcm state
EpilogUndeploy LCMState = 30
// PrologUndeploy lcm state
PrologUndeploy LCMState = 31
// BootUndeploy lcm state
BootUndeploy LCMState = 32
// HotplugPrologPoweroff lcm state
HotplugPrologPoweroff LCMState = 33
// HotplugEpilogPoweroff lcm state
HotplugEpilogPoweroff LCMState = 34
// BootMigrate lcm state
BootMigrate LCMState = 35
// BootFailure lcm state
BootFailure LCMState = 36
// BootMigrateFailure lcm state
BootMigrateFailure LCMState = 37
// PrologMigrateFailure lcm state
PrologMigrateFailure LCMState = 38
// PrologFailure lcm state
PrologFailure LCMState = 39
// EpilogFailure lcm state
EpilogFailure LCMState = 40
// EpilogStopFailure lcm state
EpilogStopFailure LCMState = 41
// EpilogUndeployFailure lcm state
EpilogUndeployFailure LCMState = 42
// PrologMigratePoweroff lcm state
PrologMigratePoweroff LCMState = 43
// PrologMigratePoweroffFailure lcm state
PrologMigratePoweroffFailure LCMState = 44
// PrologMigrateSuspend lcm state
PrologMigrateSuspend LCMState = 45
// PrologMigrateSuspendFailure lcm state
PrologMigrateSuspendFailure LCMState = 46
// BootUndeployFailure lcm state
BootUndeployFailure LCMState = 47
// BootStoppedFailure lcm state
BootStoppedFailure LCMState = 48
// PrologResumeFailure lcm state
PrologResumeFailure LCMState = 49
// PrologUndeployFailure lcm state
PrologUndeployFailure LCMState = 50
// DiskSnapshotPoweroff lcm state
DiskSnapshotPoweroff LCMState = 51
// DiskSnapshotRevertPoweroff lcm state
DiskSnapshotRevertPoweroff LCMState = 52
// DiskSnapshotDeletePoweroff lcm state
DiskSnapshotDeletePoweroff LCMState = 53
// DiskSnapshotSuspended lcm state
DiskSnapshotSuspended LCMState = 54
// DiskSnapshotRevertSuspended lcm state
DiskSnapshotRevertSuspended LCMState = 55
// DiskSnapshotDeleteSuspended lcm state
DiskSnapshotDeleteSuspended LCMState = 56
// DiskSnapshot lcm state
DiskSnapshot LCMState = 57
// Deprecated
// DiskSnapshotRevert LCMState = 58
// DiskSnapshotDelete lcm state
DiskSnapshotDelete LCMState = 59
// PrologMigrateUnknown lcm state
PrologMigrateUnknown LCMState = 60
// PrologMigrateUnknownFailure lcm state
PrologMigrateUnknownFailure LCMState = 61
// DiskResize lcm state
DiskResize LCMState = 62
// DiskResizePoweroff lcm state
DiskResizePoweroff LCMState = 63
// DiskResizeUndeployed lcm state
DiskResizeUndeployed LCMState = 64
)
func (l LCMState) String() string {
switch l {
case LcmInit:
return "LCM_INIT"
case Prolog:
return "PROLOG"
case Boot:
return "BOOT"
case Running:
return "RUNNING"
case Migrate:
return "MIGRATE"
case SaveStop:
return "SAVE_STOP"
case SaveSuspend:
return "SAVESuspend"
case SaveMigrate:
return "SAVE_MIGRATE"
case PrologMigrate:
return "PROLOG_MIGRATE"
case PrologResume:
return "PROLOG_RESUME"
case EpilogStop:
return "EPILOG_STOP"
case Epilog:
return "EPILOG"
case Shutdown:
return "SHUTDOWN"
case CleanupResubmit:
return "CLEANUP_RESUBMIT"
case Unknown:
return "UNKNOWN"
case Hotplug:
return "HOTPLUG"
case ShutdownPoweroff:
return "SHUTDOWN_POWEROFF"
case BootUnknown:
return "BOOT_UNKNOWN"
case BootPoweroff:
return "BOOT_POWEROFF"
case BootSuspended:
return "BOOTSuspendED"
case BootStopped:
return "BOOT_STOPPED"
case CleanupDelete:
return "CLEANUP_DELETE"
case HotplugSnapshot:
return "HOTPLUG_SNAPSHOT"
case HotplugNic:
return "HOTPLUG_NIC"
case HotplugSaveas:
return "HOTPLUG_SAVEAS"
case HotplugSaveasPoweroff:
return "HOTPLUG_SAVEAS_POWEROFF"
case HotplugSaveasSuspended:
return "HOTPLUG_SAVEASSuspendED"
case ShutdownUndeploy:
return "SHUTDOWN_UNDEPLOY"
case EpilogUndeploy:
return "EPILOG_UNDEPLOY"
case PrologUndeploy:
return "PROLOG_UNDEPLOY"
case BootUndeploy:
return "BOOT_UNDEPLOY"
case HotplugPrologPoweroff:
return "HOTPLUG_PROLOG_POWEROFF"
case HotplugEpilogPoweroff:
return "HOTPLUG_EPILOG_POWEROFF"
case BootMigrate:
return "BOOT_MIGRATE"
case BootFailure:
return "BOOT_FAILURE"
case BootMigrateFailure:
return "BOOT_MIGRATE_FAILURE"
case PrologMigrateFailure:
return "PROLOG_MIGRATE_FAILURE"
case PrologFailure:
return "PROLOG_FAILURE"
case EpilogFailure:
return "EPILOG_FAILURE"
case EpilogStopFailure:
return "EPILOG_STOP_FAILURE"
case EpilogUndeployFailure:
return "EPILOG_UNDEPLOY_FAILURE"
case PrologMigratePoweroff:
return "PROLOG_MIGRATE_POWEROFF"
case PrologMigratePoweroffFailure:
return "PROLOG_MIGRATE_POWEROFF_FAILURE"
case PrologMigrateSuspend:
return "PROLOG_MIGRATESuspend"
case PrologMigrateSuspendFailure:
return "PROLOG_MIGRATESuspend_FAILURE"
case BootUndeployFailure:
return "BOOT_UNDEPLOY_FAILURE"
case BootStoppedFailure:
return "BOOT_STOPPED_FAILURE"
case PrologResumeFailure:
return "PROLOG_RESUME_FAILURE"
case PrologUndeployFailure:
return "PROLOG_UNDEPLOY_FAILURE"
case DiskSnapshotPoweroff:
return "DISK_SNAPSHOT_POWEROFF"
case DiskSnapshotRevertPoweroff:
return "DISK_SNAPSHOT_REVERT_POWEROFF"
case DiskSnapshotDeletePoweroff:
return "DISK_SNAPSHOT_DELETE_POWEROFF"
case DiskSnapshotSuspended:
return "DISK_SNAPSHOTSuspendED"
case DiskSnapshotRevertSuspended:
return "DISK_SNAPSHOT_REVERTSuspendED"
case DiskSnapshotDeleteSuspended:
return "DISK_SNAPSHOT_DELETESuspendED"
case DiskSnapshot:
return "DISK_SNAPSHOT"
case DiskSnapshotDelete:
return "DISK_SNAPSHOT_DELETE"
case PrologMigrateUnknown:
return "PROLOG_MIGRATE_UNKNOWN"
case PrologMigrateUnknownFailure:
return "PROLOG_MIGRATE_UNKNOWN_FAILURE"
case DiskResize:
return "DISK_RESIZE"
case DiskResizePoweroff:
return "DISK_RESIZE_POWEROFF"
case DiskResizeUndeployed:
return "DISK_RESIZE_UNDEPLOYED"
default:
return ""
}
}
// NewVMPool returns a new image pool. It accepts the scope of the query.
func NewVMPool(args ...int) (*VMPool, error) {
var who, start, end, state int
switch len(args) {
case 0:
who = PoolWhoMine
start = -1
end = -1
state = -1
case 1:
who = args[0]
start = -1
end = -1
state = -1
case 3:
who = args[0]
start = args[1]
end = args[2]
state = -1
case 4:
who = args[0]
start = args[1]
end = args[2]
state = args[3]
default:
return nil, errors.New("Wrong number of arguments")
}
response, err := client.Call("one.vmpool.info", who, start, end, state)
if err != nil {
return nil, err
}
vmpool := &VMPool{XMLResource{body: response.Body()}}
return vmpool, err
}
// Monitoring returns all the virtual machine monitorin records
// filter flag:
// -4: Resources belonging to the user's primary group
// -3: Resources belonging to the user
// -2: All resources
// -1: Resources belonging to the user and any of his groups
// >= 0: UID User's Resources
func (vmpool *VMPool) Monitoring(filter int) error {
_, err := client.Call("one.vmpool.monitoring", filter)
return err
}
// Accounting returns the virtual machine history records
// filter flag:
// -4: Resources belonging to the user's primary group
// -3: Resources belonging to the user
// -2: All resources
// -1: Resources belonging to the user and any of his groups
// >= 0: UID User's Resources
// if startTime and/or endTime are -1 it means no limit
func (vmpool *VMPool) Accounting(filter, startTime, endTime int) error {
_, err := client.Call("one.vmpool.accounting", filter)
return err
}
// Showback returns the virtual machine showback records
// filter flag
// <= -3: Connected user's resources
// -2: All resources
// -1: Connected user's and his group's resources
// >= 0: UID User's Resources
// firstMonth: January is 1. Can be -1, in which case the time interval won't have
// a left boundary.
// firstYear: Can be -1, in which case the time interval won't have a left
// boundary.
// lastMonth: January is 1. Can be -1, in which case the time interval won't have
// a right boundary.
// lastYear: Can be -1, in which case the time interval won't have a right
// boundary.
func (vmpool *VMPool) Showback(filter, firstMonth, firstYear, lastMonth, lastYear int) error {
_, err := client.Call("one.vmpool.showback", filter, firstMonth, firstYear, lastMonth, lastYear)
return err
}
// CalculateShowback processes all the history records, and stores the monthly cost for each VM
// firstMonth: January is 1. Can be -1, in which case the time interval won't have
// a left boundary.
// firstYear: Can be -1, in which case the time interval won't have a left
// boundary.
// lastMonth: January is 1. Can be -1, in which case the time interval won't have
// a right boundary.
// lastYear: Can be -1, in which case the time interval won't have a right
// boundary.
func (vmpool *VMPool) CalculateShowback(firstMonth, firstYear, lastMonth, lastYear int) error {
_, err := client.Call("one.vmpool.calculateshowback", firstMonth, firstYear, lastMonth, lastYear)
return err
}
// CreateVM allocates a new VM based on the template string provided. It
// returns the image ID
func CreateVM(template string, pending bool) (uint, error) {
response, err := client.Call("one.vm.allocate", template, pending)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// 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}
}
// 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()
if err != nil {
return nil, err
}
id, err := vmpool.GetIDFromName(name, "/VM_POOL/VM")
if err != nil {
return nil, err
}
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")
}
lcmStateString, ok := vm.XPath("/VM/LCM_STATE")
if ok != true {
return -1, -1, errors.New("Unable to parse LCM State")
}
vmState, _ := strconv.Atoi(vmStateString)
lcmState, _ := strconv.Atoi(lcmStateString)
return VMState(vmState), LCMState(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
}
return VMState(vmState).String(), LCMState(lcmState).String(), nil
}
// Action is the generic method to run any action on the VM
func (vm *VM) Action(action string) error {
_, err := client.Call("one.vm.action", action, vm.ID)
return err
}
// Info connects to OpenNebula and fetches the information of the VM
func (vm *VM) Info() error {
response, err := client.Call("one.vm.info", vm.ID)
vm.body = response.Body()
return err
}
// Update will modify the VM's template. If appendTemplate is 0, it will
// replace the whole template. If its 1, it will merge.
func (vm *VM) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.vm.update", vm.ID, tpl, appendTemplate)
return err
}
// UpdateConf updates (appends) a set of supported configuration attributes in
// the VM template
func (vm *VM) UpdateConf(tpl string) error {
_, err := client.Call("one.vm.updateconf", vm.ID, tpl)
return err
}
// Monitoring Returns the virtual machine monitoring records
func (vm *VM) Monitoring() error {
_, err := client.Call("one.vm.monitoring", vm.ID)
return err
}
// Chown changes the owner/group of a VM. If uid or gid is -1 it will not
// change
func (vm *VM) Chown(uid, gid int) error {
_, err := client.Call("one.vm.chown", vm.ID, uid, gid)
return err
}
// Chmod changes the permissions of a VM. If any perm is -1 it will not
// change
func (vm *VM) Chmod(uu, um, ua, gu, gm, ga, ou, om, oa int) error {
_, err := client.Call("one.vm.chmod", vm.ID, uu, um, ua, gu, gm, ga, ou, om, oa)
return err
}
// Rename changes the name of a VM
func (vm *VM) Rename(newName string) error {
_, err := client.Call("one.vm.rename", vm.ID, newName)
return err
}
// Delete will remove the VM from OpenNebula
func (vm *VM) Delete() error {
_, err := client.Call("one.vm.delete", vm.ID)
return err
}
// Deploy in the selected hostID and/or dsID. Enforce to return error in case of
// overcommitment. Enforce is automatically enabled for non-oneadmin users.
func (vm *VM) Deploy(hostID uint, enforce bool, dsID uint) error {
_, err := client.Call("one.vm.deploy", vm.ID, int(hostID), enforce, int(dsID))
return err
}
// Resize changes the capacity of the virtual machine
func (vm *VM) Resize(template string, enforce bool) error {
_, err := client.Call("one.vm.resize", vm.ID, template, enforce)
return err
}
// DiskSaveas exports a disk to an image. If imageType is empty the default one
// will be used. If snapID is -1 the current image state will be exported
func (vm *VM) DiskSaveas(diskID int, imageName, imageType string, snapID int) error {
_, err := client.Call("one.vm.disksaveas", vm.ID, diskID, imageName, imageType, snapID)
return err
}
// DiskSnapshotCreate will create a snapshot of the disk image
func (vm *VM) DiskSnapshotCreate(diskID int, description string) error {
_, err := client.Call("one.vm.disksnapshotcreate", vm.ID, diskID, description)
return err
}
// DiskSnapshotDelete will delete a snapshot
func (vm *VM) DiskSnapshotDelete(diskID, snapID int) error {
_, err := client.Call("one.vm.disksnapshotdelete", vm.ID, diskID, snapID)
return err
}
// DiskSnapshotRevert will revert disk state to a previously taken snapshot
func (vm *VM) DiskSnapshotRevert(diskID, snapID int) error {
_, err := client.Call("one.vm.disksnapshotrevert", vm.ID, diskID, snapID)
return err
}
// SnapshotCreate creates a new virtual machine snapshot. name can be empty
func (vm *VM) SnapshotCreate(name string) error {
_, err := client.Call("one.vm.snapshotcreate", vm.ID, name)
return err
}
// SnapshotDelete deletes a virtual machine snapshot
func (vm *VM) SnapshotDelete(snapID int) error {
_, err := client.Call("one.vm.snapshotdelete", vm.ID, snapID)
return err
}
// SnapshotRevert reverts a virtual machine to a snapshot
func (vm *VM) SnapshotRevert(snapID int) error {
_, err := client.Call("one.vm.snapshotrevert", vm.ID, snapID)
return err
}
// Attach a new disk to the virtual machine. diskTemplate is a string containing
// a single DISK vector attribute. Syntax can be the usual attribute=value or
// XML
func (vm *VM) Attach(diskTemplate string) error {
_, err := client.Call("one.vm.attach", vm.ID, diskTemplate)
return err
}
// Detach a disk from a virtual machine
func (vm *VM) Detach(diskID int) error {
_, err := client.Call("one.vm.detach", vm.ID, diskID)
return err
}
// DiskResize a disk of a virtual machine
func (vm *VM) DiskResize(diskID int, size string) error {
_, err := client.Call("one.vm.diskresize", vm.ID, diskID, size)
return err
}
// Migrate a VM to a target host and/or to another ds
func (vm *VM) Migrate(hostID uint, live, enforce bool, dsID uint) error {
_, err := client.Call("one.vm.migrate", int(hostID), live, enforce, int(dsID))
return err
}
// AttachNic attaches new network interface to the virtual machine
func (vm *VM) AttachNic(tpl string) error {
_, err := client.Call("one.vm.attachnic", vm.ID, tpl)
return err
}
// DetachNic detaches a network interface from the virtual machine
func (vm *VM) DetachNic(nicID string) error {
_, err := client.Call("one.vm.detachnic", vm.ID, nicID)
return err
}
// VM Actions
// TerminateHard action on the VM
func (vm *VM) TerminateHard() error {
return vm.Action("terminate-hard")
}
// Terminate action on the VM
func (vm *VM) Terminate() error {
return vm.Action("terminate")
}
// UndeployHard action on the VM
func (vm *VM) UndeployHard() error {
return vm.Action("undeploy-hard")
}
// Undeploy action on the VM
func (vm *VM) Undeploy() error {
return vm.Action("undeploy")
}
// PoweroffHard action on the VM
func (vm *VM) PoweroffHard() error {
return vm.Action("poweroff-hard")
}
// Poweroff action on the VM
func (vm *VM) Poweroff() error {
return vm.Action("poweroff")
}
// RebootHard action on the VM
func (vm *VM) RebootHard() error {
return vm.Action("reboot-hard")
}
// Reboot action on the VM
func (vm *VM) Reboot() error {
return vm.Action("reboot")
}
// Hold action on the VM
func (vm *VM) Hold() error {
return vm.Action("hold")
}
// Release action on the VM
func (vm *VM) Release() error {
return vm.Action("release")
}
// Stop action on the VM
func (vm *VM) Stop() error {
return vm.Action("stop")
}
// Suspend action on the VM
func (vm *VM) Suspend() error {
return vm.Action("suspend")
}
// Resume action on the VM
func (vm *VM) Resume() error {
return vm.Action("resume")
}
// Resched action on the VM
func (vm *VM) Resched() error {
return vm.Action("resched")
}
// Unresched action on the VM
func (vm *VM) Unresched() error {
return vm.Action("unresched")
}
// End actions
// Recover recovers a stuck VM that is waiting for a driver operation
func (vm *VM) Recover(op int) error {
_, err := client.Call("one.vm.recover", vm.ID, op)
return err
}
// RecoverSuccess forces a success
func (vm *VM) RecoverSuccess() error {
return vm.Recover(1)
}
// RecoverFailure forces a success
func (vm *VM) RecoverFailure() error {
return vm.Recover(0)
}
// RecoverRetry forces a success
func (vm *VM) RecoverRetry() error {
return vm.Recover(2)
}
// RecoverDelete forces a delete
func (vm *VM) RecoverDelete() error {
return vm.Recover(3)
}
// RecoverDeleteRecreate forces a delete
func (vm *VM) RecoverDeleteRecreate() error {
return vm.Recover(4)
}

View File

@ -0,0 +1,204 @@
package goca
import (
"testing"
. "gopkg.in/check.v1"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
type VMSuite struct {
templateID uint
vmID uint
}
var _ = Suite(&VMSuite{})
func (s *VMSuite) SetUpSuite(c *C) {
// Create template
tpl := NewTemplateBuilder()
tpl.AddValue("NAME", GenName("VMSuite-template"))
tpl.AddValue("CPU", 1)
tpl.AddValue("MEMORY", "64")
templateID, err := CreateTemplate(tpl.String())
c.Assert(err, IsNil)
s.templateID = templateID
}
func (s *VMSuite) SetUpTest(c *C) {
template := NewTemplate(s.templateID)
vmID, err := template.Instantiate("", true, "")
c.Assert(err, IsNil)
s.vmID = vmID
}
func (s *VMSuite) TearDownTest(c *C) {
vm := NewVM(s.vmID)
err := vm.TerminateHard()
if err != nil {
err = vm.RecoverDelete()
}
c.Assert(err, IsNil)
}
func (s *VMSuite) TearDownSuite(c *C) {
template := NewTemplate(s.templateID)
template.Delete()
}
////////////////////////////////////////////////////////////////////////////////
func VMExpectState(c *C, vm *VM, state, lcmState string) func() bool {
return func() bool {
vm.Info()
s, l, err := vm.StateString()
if err != nil {
return false
}
if lcmState != "" && l == lcmState {
return true
}
if state != "" && s == state {
return true
}
c.Logf("VM: %d. Expecting: %s/%s, Got: %s/%s", vm.ID, state, lcmState, s, l)
return false
}
}
////////////////////////////////////////////////////////////////////////////////
func (s *VMSuite) TestVMDeploy(c *C) {
vm := NewVM(s.vmID)
err := vm.Release()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "ACTIVE", "RUNNING")), Equals, true)
}
func (s *VMSuite) TestVMHoldRelease(c *C) {
vm := NewVM(s.vmID)
c.Assert(WaitResource(VMExpectState(c, vm, "HOLD", "")), Equals, true)
err := vm.Release()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "PENDING", "")), Equals, true)
}
func (s *VMSuite) TestVMUpdate(c *C) {
vm := NewVM(s.vmID)
err := vm.Update("A=B", 1)
c.Assert(err, IsNil)
err = vm.Info()
c.Assert(err, IsNil)
val, ok := vm.XPath("/VM/USER_TEMPLATE/A")
c.Assert(ok, Equals, true)
c.Assert(val, Equals, "B")
}
// TODO: Hosts
// func (s *VMSuite) TestVMMigrate(c *C) {
// }
// func (s *VMSuite) TestVMLiveMigrate(c *C) {
// }
func (s *VMSuite) TestVMTerminate(c *C) {
vm := NewVM(s.vmID)
err := vm.Release()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "ACTIVE", "RUNNING")), Equals, true)
err = vm.Terminate()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "DONE", "")), Equals, true)
}
func (s *VMSuite) TestVMTerminateHard(c *C) {
vm := NewVM(s.vmID)
err := vm.Release()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "ACTIVE", "RUNNING")), Equals, true)
vm.TerminateHard()
c.Assert(WaitResource(VMExpectState(c, vm, "DONE", "")), Equals, true)
}
func (s *VMSuite) TestVMStop(c *C) {
vm := NewVM(s.vmID)
err := vm.Release()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "ACTIVE", "RUNNING")), Equals, true)
err = vm.Stop()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "STOPPED", "")), Equals, true)
}
func (s *VMSuite) TestVMSuspend(c *C) {
vm := NewVM(s.vmID)
err := vm.Release()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "ACTIVE", "RUNNING")), Equals, true)
err = vm.Suspend()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "SUSPENDED", "")), Equals, true)
}
func (s *VMSuite) TestVMResume(c *C) {
vm := NewVM(s.vmID)
err := vm.Release()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "ACTIVE", "RUNNING")), Equals, true)
err = vm.Suspend()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "SUSPENDED", "")), Equals, true)
err = vm.Resume()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "ACTIVE", "RUNNING")), Equals, true)
}
func (s *VMSuite) TestVMResize(c *C) {
vm := NewVM(s.vmID)
err := vm.Release()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "ACTIVE", "RUNNING")), Equals, true)
err = vm.Poweroff()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "POWEROFF", "")), Equals, true)
err = vm.Resize("CPU=2.5\nMEMORY=512", false)
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "POWEROFF", "")), Equals, true)
err = vm.Resume()
c.Assert(err, IsNil)
c.Assert(WaitResource(VMExpectState(c, vm, "ACTIVE", "RUNNING")), Equals, true)
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")
memory, ok := vm.XPath("/VM/TEMPLATE/MEMORY")
c.Assert(ok, Equals, true)
c.Assert(memory, Equals, "512")
}

View File

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

View File

@ -0,0 +1,88 @@
package goca
// Zone represents an OpenNebula Zone
type Zone struct {
XMLResource
ID uint
Name string
}
// ZonePool represents an OpenNebula ZonePool
type ZonePool struct {
XMLResource
}
// NewZonePool returns a zone pool. A connection to OpenNebula is
// performed.
func NewZonePool() (*ZonePool, error) {
response, err := client.Call("one.zonepool.info")
if err != nil {
return nil, err
}
zonepool := &ZonePool{XMLResource{body: response.Body()}}
return zonepool, err
}
// NewZone finds a zone object by ID. No connection to OpenNebula.
func NewZone(id uint) *Zone {
return &Zone{ID: id}
}
// NewZoneFromName finds a zone object by name. It connects to
// OpenNebula to retrieve the pool, but doesn't perform the Info() call to
// retrieve the attributes of the zone.
func NewZoneFromName(name string) (*Zone, error) {
zonePool, err := NewZonePool()
if err != nil {
return nil, err
}
id, err := zonePool.GetIDFromName(name, "/ZONE_POOL/ZONE")
if err != nil {
return nil, err
}
return NewZone(id), nil
}
// CreateZone allocates a new zone. It returns the new zone ID.
// * tpl: A string containing the template of the ZONE. Syntax can be the usual
// attribute=value or XML.
// * clusterID: The id of the cluster. If -1, the default one will be used
func CreateZone(tpl string, clusterID int) (uint, error) {
response, err := client.Call("one.zone.allocate", tpl, clusterID)
if err != nil {
return 0, err
}
return uint(response.BodyInt()), nil
}
// Delete deletes the given zone from the pool.
func (zone *Zone) Delete() error {
_, err := client.Call("one.zone.delete", zone.ID)
return err
}
// Update replaces the zone template contents.
// * tpl: The new template contents. Syntax can be the usual attribute=value or XML.
// * appendTemplate: Update type: 0: Replace the whole template. 1: Merge new template with the existing one.
func (zone *Zone) Update(tpl string, appendTemplate int) error {
_, err := client.Call("one.zone.update", zone.ID, tpl, appendTemplate)
return err
}
// Rename renames a zone.
// * newName: The new name.
func (zone *Zone) Rename(newName string) error {
_, err := client.Call("one.zone.rename", zone.ID, newName)
return err
}
// Info retrieves information for the zone.
func (zone *Zone) Info() error {
_, err := client.Call("one.zone.info", zone.ID)
return err
}