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 (
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/gce"
|
||||
_ "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