Add support for oVirt cloud provider
This patch adds the initial support for the oVirt cloud provider. Signed-off-by: Federico Simoncelli <fsimonce@redhat.com>
This commit is contained in:
parent
18bcc5f9a9
commit
320fd528e2
9
cluster/ovirt/ovirt-cloud.conf
Normal file
9
cluster/ovirt/ovirt-cloud.conf
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# ovirt-cloud.conf
|
||||||
|
[connection]
|
||||||
|
uri = https://localhost:8443/ovirt-engine/api
|
||||||
|
username = admin@internal
|
||||||
|
password = admin
|
||||||
|
|
||||||
|
[filters]
|
||||||
|
# Search queries used to find minions
|
||||||
|
vms = tag=kubernetes
|
@ -22,4 +22,5 @@ package main
|
|||||||
import (
|
import (
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
||||||
|
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/ovirt"
|
||||||
)
|
)
|
||||||
|
156
pkg/cloudprovider/ovirt/ovirt.go
Normal file
156
pkg/cloudprovider/ovirt/ovirt.go
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ovirt_cloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.google.com/p/gcfg"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OVirtCloud struct {
|
||||||
|
VmsRequest *url.URL
|
||||||
|
HostsRequest *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
type OVirtApiConfig struct {
|
||||||
|
Connection struct {
|
||||||
|
ApiEntry string `gcfg:"uri"`
|
||||||
|
Username string `gcfg:"username"`
|
||||||
|
Password string `gcfg:"password"`
|
||||||
|
}
|
||||||
|
Filters struct {
|
||||||
|
VmsQuery string `gcfg:"vms"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type XmlVmInfo struct {
|
||||||
|
Hostname string `xml:"guest_info>fqdn"`
|
||||||
|
State string `xml:"status>state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type XmlVmsList struct {
|
||||||
|
XMLName xml.Name `xml:"vms"`
|
||||||
|
Vm []XmlVmInfo `xml:"vm"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cloudprovider.RegisterCloudProvider("ovirt",
|
||||||
|
func(config io.Reader) (cloudprovider.Interface, error) {
|
||||||
|
return newOVirtCloud(config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOVirtCloud(config io.Reader) (*OVirtCloud, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("missing configuration file for ovirt cloud provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
oVirtConfig := OVirtApiConfig{}
|
||||||
|
|
||||||
|
/* defaults */
|
||||||
|
oVirtConfig.Connection.Username = "admin@internal"
|
||||||
|
|
||||||
|
if err := gcfg.ReadInto(&oVirtConfig, config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if oVirtConfig.Connection.ApiEntry == "" {
|
||||||
|
return nil, fmt.Errorf("missing ovirt uri in cloud provider configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := url.Parse(oVirtConfig.Connection.ApiEntry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Path = path.Join(request.Path, "vms")
|
||||||
|
request.User = url.UserPassword(oVirtConfig.Connection.Username, oVirtConfig.Connection.Password)
|
||||||
|
request.RawQuery = url.Values{"search": {oVirtConfig.Filters.VmsQuery}}.Encode()
|
||||||
|
|
||||||
|
return &OVirtCloud{VmsRequest: request}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCPLoadBalancer returns an implementation of TCPLoadBalancer for oVirt cloud
|
||||||
|
func (v *OVirtCloud) TCPLoadBalancer() (cloudprovider.TCPLoadBalancer, bool) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instances returns an implementation of Instances for oVirt cloud
|
||||||
|
func (v *OVirtCloud) Instances() (cloudprovider.Instances, bool) {
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zones returns an implementation of Zones for oVirt cloud
|
||||||
|
func (v *OVirtCloud) Zones() (cloudprovider.Zones, bool) {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddress returns the address of a particular machine instance
|
||||||
|
func (v *OVirtCloud) IPAddress(instance string) (net.IP, error) {
|
||||||
|
// since the instance now is the IP in the ovirt env, this is trivial no-op
|
||||||
|
return net.ParseIP(instance), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInstancesFromXml(body io.Reader) ([]string, error) {
|
||||||
|
if body == nil {
|
||||||
|
return nil, fmt.Errorf("ovirt rest-api response body is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vmlist := XmlVmsList{}
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(content, &vmlist); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var instances []string
|
||||||
|
|
||||||
|
for _, vm := range vmlist.Vm {
|
||||||
|
// Always return only vms that are up and running
|
||||||
|
if vm.Hostname != "" && strings.ToLower(vm.State) == "up" {
|
||||||
|
instances = append(instances, vm.Hostname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instances, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List enumerates the set of minions instances known by the cloud provider
|
||||||
|
func (v *OVirtCloud) List(filter string) ([]string, error) {
|
||||||
|
response, err := http.Get(v.VmsRequest.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
return getInstancesFromXml(response.Body)
|
||||||
|
}
|
124
pkg/cloudprovider/ovirt/ovirt_test.go
Normal file
124
pkg/cloudprovider/ovirt/ovirt_test.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ovirt_cloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOVirtCloudConfiguration(t *testing.T) {
|
||||||
|
config1 := (io.Reader)(nil)
|
||||||
|
|
||||||
|
_, err1 := cloudprovider.GetCloudProvider("ovirt", config1)
|
||||||
|
if err1 == nil {
|
||||||
|
t.Fatalf("An error is expected when the configuration is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
config2 := strings.NewReader("")
|
||||||
|
|
||||||
|
_, err2 := cloudprovider.GetCloudProvider("ovirt", config2)
|
||||||
|
if err2 == nil {
|
||||||
|
t.Fatalf("An error is expected when the configuration is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
config3 := strings.NewReader(`
|
||||||
|
[connection]
|
||||||
|
`)
|
||||||
|
|
||||||
|
_, err3 := cloudprovider.GetCloudProvider("ovirt", config3)
|
||||||
|
if err3 == nil {
|
||||||
|
t.Fatalf("An error is expected when the uri is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
config4 := strings.NewReader(`
|
||||||
|
[connection]
|
||||||
|
uri = https://localhost:8443/ovirt-engine/api
|
||||||
|
`)
|
||||||
|
|
||||||
|
_, err4 := cloudprovider.GetCloudProvider("ovirt", config4)
|
||||||
|
if err4 != nil {
|
||||||
|
t.Fatalf("Unexpected error creating the provider: %s", err4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOVirtCloudXmlParsing(t *testing.T) {
|
||||||
|
body1 := (io.Reader)(nil)
|
||||||
|
|
||||||
|
_, err1 := getInstancesFromXml(body1)
|
||||||
|
if err1 == nil {
|
||||||
|
t.Fatalf("An error is expected when body is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
body2 := strings.NewReader("")
|
||||||
|
|
||||||
|
_, err2 := getInstancesFromXml(body2)
|
||||||
|
if err2 == nil {
|
||||||
|
t.Fatalf("An error is expected when body is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
body3 := strings.NewReader(`
|
||||||
|
<vms>
|
||||||
|
<vm></vm>
|
||||||
|
</vms>
|
||||||
|
`)
|
||||||
|
|
||||||
|
instances3, err3 := getInstancesFromXml(body3)
|
||||||
|
if err3 != nil {
|
||||||
|
t.Fatalf("Unexpected error listing instances: %s", err3)
|
||||||
|
}
|
||||||
|
if len(instances3) > 0 {
|
||||||
|
t.Fatalf("Unexpected number of instance(s): %i", len(instances3))
|
||||||
|
}
|
||||||
|
|
||||||
|
body4 := strings.NewReader(`
|
||||||
|
<vms>
|
||||||
|
<vm>
|
||||||
|
<status><state>Up</state></status>
|
||||||
|
<guest_info><fqdn>host1</fqdn></guest_info>
|
||||||
|
</vm>
|
||||||
|
<vm>
|
||||||
|
<!-- empty -->
|
||||||
|
</vm>
|
||||||
|
<vm>
|
||||||
|
<status><state>Up</state></status>
|
||||||
|
</vm>
|
||||||
|
<vm>
|
||||||
|
<status><state>Down</state></status>
|
||||||
|
<guest_info><fqdn>host2</fqdn></guest_info>
|
||||||
|
</vm>
|
||||||
|
<vm>
|
||||||
|
<status><state>Up</state></status>
|
||||||
|
<guest_info><fqdn>host3</fqdn></guest_info>
|
||||||
|
</vm>
|
||||||
|
</vms>
|
||||||
|
`)
|
||||||
|
|
||||||
|
instances4, err4 := getInstancesFromXml(body4)
|
||||||
|
if err4 != nil {
|
||||||
|
t.Fatalf("Unexpected error listing instances: %s", err4)
|
||||||
|
}
|
||||||
|
if len(instances4) != 2 {
|
||||||
|
t.Fatalf("Unexpected number of instance(s): %i", len(instances4))
|
||||||
|
}
|
||||||
|
if instances4[0] != "host1" || instances4[1] != "host3" {
|
||||||
|
t.Fatalf("Unexpected instance(s): %s", instances4)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user