ovirt.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package ovirt
  14. import (
  15. "context"
  16. "encoding/xml"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "net"
  21. "net/http"
  22. "net/url"
  23. "path"
  24. "sort"
  25. "strings"
  26. "gopkg.in/gcfg.v1"
  27. "k8s.io/api/core/v1"
  28. "k8s.io/apimachinery/pkg/types"
  29. cloudprovider "k8s.io/cloud-provider"
  30. )
  31. // ProviderName is the name of this cloud provider.
  32. const ProviderName = "ovirt"
  33. // Instance specifies UUID, name and IP address of the instance.
  34. type Instance struct {
  35. UUID string
  36. Name string
  37. IPAddress string
  38. }
  39. // InstanceMap provides the map of Ovirt instances.
  40. type InstanceMap map[string]Instance
  41. var _ cloudprovider.Interface = (*Cloud)(nil)
  42. var _ cloudprovider.Instances = (*Cloud)(nil)
  43. // Cloud is an implementation of the cloud provider interface for Ovirt.
  44. type Cloud struct {
  45. VmsRequest *url.URL
  46. HostsRequest *url.URL
  47. }
  48. // APIConfig wraps the api settings for the Ovirt.
  49. type APIConfig struct {
  50. Connection struct {
  51. APIEntry string `gcfg:"uri"`
  52. Username string `gcfg:"username"`
  53. Password string `gcfg:"password"`
  54. }
  55. Filters struct {
  56. VmsQuery string `gcfg:"vms"`
  57. }
  58. }
  59. // XMLVMAddress is an implementation for the Ovirt instance IP address in xml.
  60. type XMLVMAddress struct {
  61. Address string `xml:"address,attr"`
  62. }
  63. // XMLVMInfo is an implementation for the Ovirt instance details in xml.
  64. type XMLVMInfo struct {
  65. UUID string `xml:"id,attr"`
  66. Name string `xml:"name"`
  67. Hostname string `xml:"guest_info>fqdn"`
  68. Addresses []XMLVMAddress `xml:"guest_info>ips>ip"`
  69. State string `xml:"status>state"`
  70. }
  71. // XMLVmsList is an implementation to provide the list of Ovirt instances.
  72. type XMLVmsList struct {
  73. XMLName xml.Name `xml:"vms"`
  74. VM []XMLVMInfo `xml:"vm"`
  75. }
  76. func init() {
  77. cloudprovider.RegisterCloudProvider(ProviderName,
  78. func(config io.Reader) (cloudprovider.Interface, error) {
  79. return newOVirtCloud(config)
  80. })
  81. }
  82. func newOVirtCloud(config io.Reader) (*Cloud, error) {
  83. if config == nil {
  84. return nil, fmt.Errorf("missing configuration file for ovirt cloud provider")
  85. }
  86. oVirtConfig := APIConfig{}
  87. /* defaults */
  88. oVirtConfig.Connection.Username = "admin@internal"
  89. if err := gcfg.ReadInto(&oVirtConfig, config); err != nil {
  90. return nil, err
  91. }
  92. if oVirtConfig.Connection.APIEntry == "" {
  93. return nil, fmt.Errorf("missing ovirt uri in cloud provider configuration")
  94. }
  95. request, err := url.Parse(oVirtConfig.Connection.APIEntry)
  96. if err != nil {
  97. return nil, err
  98. }
  99. request.Path = path.Join(request.Path, "vms")
  100. request.User = url.UserPassword(oVirtConfig.Connection.Username, oVirtConfig.Connection.Password)
  101. request.RawQuery = url.Values{"search": {oVirtConfig.Filters.VmsQuery}}.Encode()
  102. return &Cloud{VmsRequest: request}, nil
  103. }
  104. // Initialize passes a Kubernetes clientBuilder interface to the cloud provider
  105. func (v *Cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
  106. }
  107. // Clusters returns the list of clusters.
  108. func (v *Cloud) Clusters() (cloudprovider.Clusters, bool) {
  109. return nil, false
  110. }
  111. // ProviderName returns the cloud provider ID.
  112. func (v *Cloud) ProviderName() string {
  113. return ProviderName
  114. }
  115. // HasClusterID returns true if the cluster has a clusterID
  116. func (v *Cloud) HasClusterID() bool {
  117. return true
  118. }
  119. // LoadBalancer returns an implementation of LoadBalancer for oVirt cloud
  120. func (v *Cloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
  121. return nil, false
  122. }
  123. // Instances returns an implementation of Instances for oVirt cloud
  124. func (v *Cloud) Instances() (cloudprovider.Instances, bool) {
  125. return v, true
  126. }
  127. // Zones returns an implementation of Zones for oVirt cloud
  128. func (v *Cloud) Zones() (cloudprovider.Zones, bool) {
  129. return nil, false
  130. }
  131. // Routes returns an implementation of Routes for oVirt cloud
  132. func (v *Cloud) Routes() (cloudprovider.Routes, bool) {
  133. return nil, false
  134. }
  135. // NodeAddresses returns the NodeAddresses of the instance with the specified nodeName.
  136. func (v *Cloud) NodeAddresses(ctx context.Context, nodeName types.NodeName) ([]v1.NodeAddress, error) {
  137. name := mapNodeNameToInstanceName(nodeName)
  138. instance, err := v.fetchInstance(name)
  139. if err != nil {
  140. return nil, err
  141. }
  142. var address net.IP
  143. if instance.IPAddress != "" {
  144. address = net.ParseIP(instance.IPAddress)
  145. if address == nil {
  146. return nil, fmt.Errorf("couldn't parse address: %s", instance.IPAddress)
  147. }
  148. } else {
  149. resolved, err := net.LookupIP(name)
  150. if err != nil || len(resolved) < 1 {
  151. return nil, fmt.Errorf("couldn't lookup address: %s", name)
  152. }
  153. address = resolved[0]
  154. }
  155. return []v1.NodeAddress{
  156. {Type: v1.NodeInternalIP, Address: address.String()},
  157. {Type: v1.NodeExternalIP, Address: address.String()},
  158. }, nil
  159. }
  160. // NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
  161. // This method will not be called from the node that is requesting this ID. i.e. metadata service
  162. // and other local methods cannot be used here
  163. func (v *Cloud) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
  164. return []v1.NodeAddress{}, cloudprovider.NotImplemented
  165. }
  166. // mapNodeNameToInstanceName maps from a k8s NodeName to an ovirt instance name (the hostname)
  167. // This is a simple string cast
  168. func mapNodeNameToInstanceName(nodeName types.NodeName) string {
  169. return string(nodeName)
  170. }
  171. // InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running.
  172. // If false is returned with no error, the instance will be immediately deleted by the cloud controller manager.
  173. func (v *Cloud) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
  174. return false, cloudprovider.NotImplemented
  175. }
  176. // InstanceShutdownByProviderID returns true if the instance is in safe state to detach volumes
  177. func (v *Cloud) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) {
  178. return false, cloudprovider.NotImplemented
  179. }
  180. // InstanceID returns the cloud provider ID of the node with the specified NodeName.
  181. func (v *Cloud) InstanceID(ctx context.Context, nodeName types.NodeName) (string, error) {
  182. name := mapNodeNameToInstanceName(nodeName)
  183. instance, err := v.fetchInstance(name)
  184. if err != nil {
  185. return "", err
  186. }
  187. // TODO: define a way to identify the provider instance to complete
  188. // the format <provider_instance_id>/<instance_id>.
  189. return "/" + instance.UUID, err
  190. }
  191. // InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
  192. // This method will not be called from the node that is requesting this ID. i.e. metadata service
  193. // and other local methods cannot be used here
  194. func (v *Cloud) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
  195. return "", cloudprovider.NotImplemented
  196. }
  197. // InstanceType returns the type of the specified instance.
  198. func (v *Cloud) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
  199. return "", nil
  200. }
  201. func getInstancesFromXML(body io.Reader) (InstanceMap, error) {
  202. if body == nil {
  203. return nil, fmt.Errorf("ovirt rest-api response body is missing")
  204. }
  205. content, err := ioutil.ReadAll(body)
  206. if err != nil {
  207. return nil, err
  208. }
  209. vmlist := XMLVmsList{}
  210. if err := xml.Unmarshal(content, &vmlist); err != nil {
  211. return nil, err
  212. }
  213. instances := make(InstanceMap)
  214. for _, vm := range vmlist.VM {
  215. // Always return only vms that are up and running
  216. if vm.Hostname != "" && strings.ToLower(vm.State) == "up" {
  217. address := ""
  218. if len(vm.Addresses) > 0 {
  219. address = vm.Addresses[0].Address
  220. }
  221. instances[vm.Hostname] = Instance{
  222. UUID: vm.UUID,
  223. Name: vm.Name,
  224. IPAddress: address,
  225. }
  226. }
  227. }
  228. return instances, nil
  229. }
  230. func (v *Cloud) fetchAllInstances() (InstanceMap, error) {
  231. response, err := http.Get(v.VmsRequest.String())
  232. if err != nil {
  233. return nil, err
  234. }
  235. defer response.Body.Close()
  236. return getInstancesFromXML(response.Body)
  237. }
  238. func (v *Cloud) fetchInstance(name string) (*Instance, error) {
  239. allInstances, err := v.fetchAllInstances()
  240. if err != nil {
  241. return nil, err
  242. }
  243. instance, found := allInstances[name]
  244. if !found {
  245. return nil, fmt.Errorf("cannot find instance: %s", name)
  246. }
  247. return &instance, nil
  248. }
  249. // ListSortedNames returns the list of sorted Ovirt instances name.
  250. func (m *InstanceMap) ListSortedNames() []string {
  251. var names []string
  252. for k := range *m {
  253. names = append(names, k)
  254. }
  255. sort.Strings(names)
  256. return names
  257. }
  258. // CurrentNodeName is implementation of Instances.CurrentNodeName.
  259. func (v *Cloud) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
  260. return types.NodeName(hostname), nil
  261. }
  262. // AddSSHKeyToAllInstances is currently not implemented.
  263. func (v *Cloud) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
  264. return cloudprovider.NotImplemented
  265. }