photon.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. /*
  2. Copyright 2016 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. // This version of Photon cloud provider supports the disk interface
  14. // for Photon persistent disk volume plugin. LoadBalancer, Routes, and
  15. // Zones are currently not supported.
  16. // The use of Photon cloud provider requires to start kubelet, kube-apiserver,
  17. // and kube-controller-manager with config flag: '--cloud-provider=photon
  18. // --cloud-config=[path_to_config_file]'. When running multi-node kubernetes
  19. // using docker, the config file should be located inside /etc/kubernetes.
  20. package photon
  21. import (
  22. "bufio"
  23. "context"
  24. "errors"
  25. "fmt"
  26. "io"
  27. "log"
  28. "net"
  29. "os"
  30. "strings"
  31. "github.com/vmware/photon-controller-go-sdk/photon"
  32. "gopkg.in/gcfg.v1"
  33. "k8s.io/api/core/v1"
  34. k8stypes "k8s.io/apimachinery/pkg/types"
  35. cloudprovider "k8s.io/cloud-provider"
  36. nodehelpers "k8s.io/cloud-provider/node/helpers"
  37. "k8s.io/klog"
  38. )
  39. const (
  40. ProviderName = "photon"
  41. DiskSpecKind = "persistent-disk"
  42. MAC_OUI_VC = "00:50:56"
  43. MAC_OUI_ESX = "00:0c:29"
  44. )
  45. // overrideIP indicates if the hostname is overridden by IP address, such as when
  46. // running multi-node kubernetes using docker. In this case the user should set
  47. // overrideIP = true in cloud config file. Default value is false.
  48. var overrideIP = false
  49. var _ cloudprovider.Interface = (*PCCloud)(nil)
  50. var _ cloudprovider.Instances = (*PCCloud)(nil)
  51. var _ cloudprovider.Zones = (*PCCloud)(nil)
  52. // PCCloud is an implementation of the cloud provider interface for Photon Controller.
  53. type PCCloud struct {
  54. cfg *PCConfig
  55. // InstanceID of the server where this PCCloud object is instantiated.
  56. localInstanceID string
  57. // local $HOSTNAME
  58. localHostname string
  59. // hostname from K8S, could be overridden
  60. localK8sHostname string
  61. // Photon project ID. We assume that there is only one Photon Controller project
  62. // in the environment per current Photon Controller deployment methodology.
  63. projID string
  64. cloudprovider.Zone
  65. photonClient *photon.Client
  66. logger *log.Logger
  67. }
  68. type PCConfig struct {
  69. Global struct {
  70. // the Photon Controller endpoint IP address
  71. CloudTarget string `gcfg:"target"`
  72. // Photon Controller project name
  73. Project string `gcfg:"project"`
  74. // when kubelet is started with '--hostname-override=${IP_ADDRESS}', set to true;
  75. // otherwise, set to false.
  76. OverrideIP bool `gcfg:"overrideIP"`
  77. // VM ID for this node
  78. VMID string `gcfg:"vmID"`
  79. // Authentication enabled or not
  80. AuthEnabled bool `gcfg:"authentication"`
  81. }
  82. }
  83. // Disks is interface for manipulation with PhotonController Persistent Disks.
  84. type Disks interface {
  85. // AttachDisk attaches given disk to given node. Current node
  86. // is used when nodeName is empty string.
  87. AttachDisk(ctx context.Context, pdID string, nodeName k8stypes.NodeName) error
  88. // DetachDisk detaches given disk to given node. Current node
  89. // is used when nodeName is empty string.
  90. DetachDisk(ctx context.Context, pdID string, nodeName k8stypes.NodeName) error
  91. // DiskIsAttached checks if a disk is attached to the given node.
  92. DiskIsAttached(ctx context.Context, pdID string, nodeName k8stypes.NodeName) (bool, error)
  93. // DisksAreAttached is a batch function to check if a list of disks are attached
  94. // to the node with the specified NodeName.
  95. DisksAreAttached(ctx context.Context, pdIDs []string, nodeName k8stypes.NodeName) (map[string]bool, error)
  96. // CreateDisk creates a new PD with given properties.
  97. CreateDisk(volumeOptions *VolumeOptions) (pdID string, err error)
  98. // DeleteDisk deletes PD.
  99. DeleteDisk(pdID string) error
  100. }
  101. // VolumeOptions specifies capacity, tags, name and flavorID for a volume.
  102. type VolumeOptions struct {
  103. CapacityGB int
  104. Tags map[string]string
  105. Name string
  106. Flavor string
  107. }
  108. func readConfig(config io.Reader) (PCConfig, error) {
  109. if config == nil {
  110. err := fmt.Errorf("cloud provider config file is missing. Please restart kubelet with --cloud-provider=photon --cloud-config=[path_to_config_file]")
  111. return PCConfig{}, err
  112. }
  113. var cfg PCConfig
  114. err := gcfg.ReadInto(&cfg, config)
  115. return cfg, err
  116. }
  117. func init() {
  118. cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
  119. cfg, err := readConfig(config)
  120. if err != nil {
  121. klog.Errorf("Photon Cloud Provider: failed to read in cloud provider config file. Error[%v]", err)
  122. return nil, err
  123. }
  124. return newPCCloud(cfg)
  125. })
  126. }
  127. // Retrieve the Photon VM ID from the Photon Controller endpoint based on the node name
  128. func getVMIDbyNodename(pc *PCCloud, nodeName string) (string, error) {
  129. photonClient, err := getPhotonClient(pc)
  130. if err != nil {
  131. klog.Errorf("Photon Cloud Provider: Failed to get photon client for getVMIDbyNodename, error: [%v]", err)
  132. return "", err
  133. }
  134. vmList, err := photonClient.Projects.GetVMs(pc.projID, nil)
  135. if err != nil {
  136. klog.Errorf("Photon Cloud Provider: Failed to GetVMs from project %s with nodeName %s, error: [%v]", pc.projID, nodeName, err)
  137. return "", err
  138. }
  139. for _, vm := range vmList.Items {
  140. if vm.Name == nodeName {
  141. return vm.ID, nil
  142. }
  143. }
  144. return "", fmt.Errorf("No matching started VM is found with name %s", nodeName)
  145. }
  146. // Retrieve the Photon VM ID from the Photon Controller endpoint based on the IP address
  147. func getVMIDbyIP(pc *PCCloud, IPAddress string) (string, error) {
  148. photonClient, err := getPhotonClient(pc)
  149. if err != nil {
  150. klog.Errorf("Photon Cloud Provider: Failed to get photon client for getVMIDbyNodename, error: [%v]", err)
  151. return "", err
  152. }
  153. vmList, err := photonClient.Projects.GetVMs(pc.projID, nil)
  154. if err != nil {
  155. klog.Errorf("Photon Cloud Provider: Failed to GetVMs for project %s. error: [%v]", pc.projID, err)
  156. return "", err
  157. }
  158. for _, vm := range vmList.Items {
  159. task, err := photonClient.VMs.GetNetworks(vm.ID)
  160. if err != nil {
  161. klog.Warningf("Photon Cloud Provider: GetNetworks failed for vm.ID %s, error [%v]", vm.ID, err)
  162. } else {
  163. task, err = photonClient.Tasks.Wait(task.ID)
  164. if err != nil {
  165. klog.Warningf("Photon Cloud Provider: Wait task for GetNetworks failed for vm.ID %s, error [%v]", vm.ID, err)
  166. } else {
  167. networkConnections := task.ResourceProperties.(map[string]interface{})
  168. networks := networkConnections["networkConnections"].([]interface{})
  169. for _, nt := range networks {
  170. network := nt.(map[string]interface{})
  171. if val, ok := network["ipAddress"]; ok && val != nil {
  172. ipAddr := val.(string)
  173. if ipAddr == IPAddress {
  174. return vm.ID, nil
  175. }
  176. }
  177. }
  178. }
  179. }
  180. }
  181. return "", fmt.Errorf("No matching VM is found with IP %s", IPAddress)
  182. }
  183. func getPhotonClient(pc *PCCloud) (*photon.Client, error) {
  184. var err error
  185. if len(pc.cfg.Global.CloudTarget) == 0 {
  186. return nil, fmt.Errorf("Photon Controller endpoint was not specified")
  187. }
  188. options := &photon.ClientOptions{
  189. IgnoreCertificate: true,
  190. }
  191. pc.photonClient = photon.NewClient(pc.cfg.Global.CloudTarget, options, pc.logger)
  192. if pc.cfg.Global.AuthEnabled == true {
  193. // work around before metadata is available
  194. file, err := os.Open("/etc/kubernetes/pc_login_info")
  195. if err != nil {
  196. klog.Errorf("Photon Cloud Provider: Authentication is enabled but found no username/password at /etc/kubernetes/pc_login_info. Error[%v]", err)
  197. return nil, err
  198. }
  199. defer file.Close()
  200. scanner := bufio.NewScanner(file)
  201. if !scanner.Scan() {
  202. klog.Error("Photon Cloud Provider: Empty username inside /etc/kubernetes/pc_login_info.")
  203. return nil, fmt.Errorf("Failed to create authentication enabled client with invalid username")
  204. }
  205. username := scanner.Text()
  206. if !scanner.Scan() {
  207. klog.Error("Photon Cloud Provider: Empty password set inside /etc/kubernetes/pc_login_info.")
  208. return nil, fmt.Errorf("Failed to create authentication enabled client with invalid password")
  209. }
  210. password := scanner.Text()
  211. tokenOptions, err := pc.photonClient.Auth.GetTokensByPassword(username, password)
  212. if err != nil {
  213. klog.Error("Photon Cloud Provider: failed to get tokens by password")
  214. return nil, err
  215. }
  216. options = &photon.ClientOptions{
  217. IgnoreCertificate: true,
  218. TokenOptions: &photon.TokenOptions{
  219. AccessToken: tokenOptions.AccessToken,
  220. },
  221. }
  222. pc.photonClient = photon.NewClient(pc.cfg.Global.CloudTarget, options, pc.logger)
  223. }
  224. status, err := pc.photonClient.Status.Get()
  225. if err != nil {
  226. klog.Errorf("Photon Cloud Provider: new client creation failed. Error[%v]", err)
  227. return nil, err
  228. }
  229. klog.V(2).Infof("Photon Cloud Provider: Status of the new photon controller client: %v", status)
  230. return pc.photonClient, nil
  231. }
  232. func newPCCloud(cfg PCConfig) (*PCCloud, error) {
  233. projID := cfg.Global.Project
  234. vmID := cfg.Global.VMID
  235. // Get local hostname
  236. hostname, err := os.Hostname()
  237. if err != nil {
  238. klog.Errorf("Photon Cloud Provider: get hostname failed. Error[%v]", err)
  239. return nil, err
  240. }
  241. pc := PCCloud{
  242. cfg: &cfg,
  243. localInstanceID: vmID,
  244. localHostname: hostname,
  245. localK8sHostname: "",
  246. projID: projID,
  247. }
  248. overrideIP = cfg.Global.OverrideIP
  249. return &pc, nil
  250. }
  251. // Initialize passes a Kubernetes clientBuilder interface to the cloud provider
  252. func (pc *PCCloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
  253. }
  254. // Instances returns an implementation of Instances for Photon Controller.
  255. func (pc *PCCloud) Instances() (cloudprovider.Instances, bool) {
  256. return pc, true
  257. }
  258. // List is an implementation of Instances.List.
  259. func (pc *PCCloud) List(filter string) ([]k8stypes.NodeName, error) {
  260. return nil, nil
  261. }
  262. // NodeAddresses is an implementation of Instances.NodeAddresses.
  263. func (pc *PCCloud) NodeAddresses(ctx context.Context, nodeName k8stypes.NodeName) ([]v1.NodeAddress, error) {
  264. nodeAddrs := []v1.NodeAddress{}
  265. name := string(nodeName)
  266. if name == pc.localK8sHostname {
  267. ifaces, err := net.Interfaces()
  268. if err != nil {
  269. klog.Errorf("Photon Cloud Provider: net.Interfaces() failed for NodeAddresses. Error[%v]", err)
  270. return nodeAddrs, err
  271. }
  272. for _, i := range ifaces {
  273. addrs, err := i.Addrs()
  274. if err != nil {
  275. klog.Warningf("Photon Cloud Provider: Failed to extract addresses for NodeAddresses. Error[%v]", err)
  276. } else {
  277. for _, addr := range addrs {
  278. if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
  279. if ipnet.IP.To4() != nil {
  280. // Filter external IP by MAC address OUIs from vCenter and from ESX
  281. if strings.HasPrefix(i.HardwareAddr.String(), MAC_OUI_VC) ||
  282. strings.HasPrefix(i.HardwareAddr.String(), MAC_OUI_ESX) {
  283. nodehelpers.AddToNodeAddresses(&nodeAddrs,
  284. v1.NodeAddress{
  285. Type: v1.NodeExternalIP,
  286. Address: ipnet.IP.String(),
  287. },
  288. )
  289. } else {
  290. nodehelpers.AddToNodeAddresses(&nodeAddrs,
  291. v1.NodeAddress{
  292. Type: v1.NodeInternalIP,
  293. Address: ipnet.IP.String(),
  294. },
  295. )
  296. }
  297. }
  298. }
  299. }
  300. }
  301. }
  302. return nodeAddrs, nil
  303. }
  304. // Inquiring IP addresses from photon controller endpoint only for a node other than this node.
  305. // This is assumed to be done by master only.
  306. vmID, err := getInstanceID(pc, name)
  307. if err != nil {
  308. klog.Errorf("Photon Cloud Provider: getInstanceID failed for NodeAddresses. Error[%v]", err)
  309. return nodeAddrs, err
  310. }
  311. photonClient, err := getPhotonClient(pc)
  312. if err != nil {
  313. klog.Errorf("Photon Cloud Provider: Failed to get photon client for NodeAddresses, error: [%v]", err)
  314. return nodeAddrs, err
  315. }
  316. // Retrieve the Photon VM's IP addresses from the Photon Controller endpoint based on the VM ID
  317. vmList, err := photonClient.Projects.GetVMs(pc.projID, nil)
  318. if err != nil {
  319. klog.Errorf("Photon Cloud Provider: Failed to GetVMs for project %s. Error[%v]", pc.projID, err)
  320. return nodeAddrs, err
  321. }
  322. for _, vm := range vmList.Items {
  323. if vm.ID == vmID {
  324. task, err := photonClient.VMs.GetNetworks(vm.ID)
  325. if err != nil {
  326. klog.Errorf("Photon Cloud Provider: GetNetworks failed for node %s with vm.ID %s. Error[%v]", name, vm.ID, err)
  327. return nodeAddrs, err
  328. } else {
  329. task, err = photonClient.Tasks.Wait(task.ID)
  330. if err != nil {
  331. klog.Errorf("Photon Cloud Provider: Wait task for GetNetworks failed for node %s with vm.ID %s. Error[%v]", name, vm.ID, err)
  332. return nodeAddrs, err
  333. } else {
  334. networkConnections := task.ResourceProperties.(map[string]interface{})
  335. networks := networkConnections["networkConnections"].([]interface{})
  336. for _, nt := range networks {
  337. ipAddr := "-"
  338. macAddr := "-"
  339. network := nt.(map[string]interface{})
  340. if val, ok := network["ipAddress"]; ok && val != nil {
  341. ipAddr = val.(string)
  342. }
  343. if val, ok := network["macAddress"]; ok && val != nil {
  344. macAddr = val.(string)
  345. }
  346. if ipAddr != "-" {
  347. if strings.HasPrefix(macAddr, MAC_OUI_VC) ||
  348. strings.HasPrefix(macAddr, MAC_OUI_ESX) {
  349. nodehelpers.AddToNodeAddresses(&nodeAddrs,
  350. v1.NodeAddress{
  351. Type: v1.NodeExternalIP,
  352. Address: ipAddr,
  353. },
  354. )
  355. } else {
  356. nodehelpers.AddToNodeAddresses(&nodeAddrs,
  357. v1.NodeAddress{
  358. Type: v1.NodeInternalIP,
  359. Address: ipAddr,
  360. },
  361. )
  362. }
  363. }
  364. }
  365. return nodeAddrs, nil
  366. }
  367. }
  368. }
  369. }
  370. klog.Errorf("Failed to find the node %s from Photon Controller endpoint", name)
  371. return nodeAddrs, fmt.Errorf("Failed to find the node %s from Photon Controller endpoint", name)
  372. }
  373. // NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
  374. // This method will not be called from the node that is requesting this ID. i.e. metadata service
  375. // and other local methods cannot be used here
  376. func (pc *PCCloud) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
  377. return []v1.NodeAddress{}, cloudprovider.NotImplemented
  378. }
  379. func (pc *PCCloud) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
  380. return cloudprovider.NotImplemented
  381. }
  382. func (pc *PCCloud) CurrentNodeName(ctx context.Context, hostname string) (k8stypes.NodeName, error) {
  383. pc.localK8sHostname = hostname
  384. return k8stypes.NodeName(hostname), nil
  385. }
  386. func getInstanceID(pc *PCCloud, name string) (string, error) {
  387. var vmID string
  388. var err error
  389. if overrideIP == true {
  390. vmID, err = getVMIDbyIP(pc, name)
  391. } else {
  392. vmID, err = getVMIDbyNodename(pc, name)
  393. }
  394. if err != nil {
  395. return "", err
  396. }
  397. if vmID == "" {
  398. err = cloudprovider.InstanceNotFound
  399. }
  400. return vmID, err
  401. }
  402. // InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running.
  403. // If false is returned with no error, the instance will be immediately deleted by the cloud controller manager.
  404. func (pc *PCCloud) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
  405. return false, cloudprovider.NotImplemented
  406. }
  407. // InstanceShutdownByProviderID returns true if the instance is in safe state to detach volumes
  408. func (pc *PCCloud) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) {
  409. return false, cloudprovider.NotImplemented
  410. }
  411. // InstanceID returns the cloud provider ID of the specified instance.
  412. func (pc *PCCloud) InstanceID(ctx context.Context, nodeName k8stypes.NodeName) (string, error) {
  413. name := string(nodeName)
  414. if name == pc.localK8sHostname {
  415. return pc.localInstanceID, nil
  416. }
  417. // We assume only master need to get InstanceID of a node other than itself
  418. id, err := getInstanceID(pc, name)
  419. if err != nil {
  420. klog.Errorf("Photon Cloud Provider: getInstanceID failed for InstanceID. Error[%v]", err)
  421. }
  422. return id, err
  423. }
  424. // InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
  425. // This method will not be called from the node that is requesting this ID. i.e. metadata service
  426. // and other local methods cannot be used here
  427. func (pc *PCCloud) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
  428. return "", cloudprovider.NotImplemented
  429. }
  430. func (pc *PCCloud) InstanceType(ctx context.Context, nodeName k8stypes.NodeName) (string, error) {
  431. return "", nil
  432. }
  433. func (pc *PCCloud) Clusters() (cloudprovider.Clusters, bool) {
  434. return nil, true
  435. }
  436. // ProviderName returns the cloud provider ID.
  437. func (pc *PCCloud) ProviderName() string {
  438. return ProviderName
  439. }
  440. // LoadBalancer returns an implementation of LoadBalancer for Photon Controller.
  441. func (pc *PCCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
  442. return nil, false
  443. }
  444. // Zones returns an implementation of Zones for Photon Controller.
  445. func (pc *PCCloud) Zones() (cloudprovider.Zones, bool) {
  446. return pc, true
  447. }
  448. func (pc *PCCloud) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
  449. return pc.Zone, nil
  450. }
  451. // GetZoneByProviderID implements Zones.GetZoneByProviderID
  452. // This is particularly useful in external cloud providers where the kubelet
  453. // does not initialize node data.
  454. func (pc *PCCloud) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) {
  455. return cloudprovider.Zone{}, errors.New("GetZoneByProviderID not implemented")
  456. }
  457. // GetZoneByNodeName implements Zones.GetZoneByNodeName
  458. // This is particularly useful in external cloud providers where the kubelet
  459. // does not initialize node data.
  460. func (pc *PCCloud) GetZoneByNodeName(ctx context.Context, nodeName k8stypes.NodeName) (cloudprovider.Zone, error) {
  461. return cloudprovider.Zone{}, errors.New("GetZoneByNodeName not imeplemented")
  462. }
  463. // Routes returns a false since the interface is not supported for photon controller.
  464. func (pc *PCCloud) Routes() (cloudprovider.Routes, bool) {
  465. return nil, false
  466. }
  467. // HasClusterID returns true if the cluster has a clusterID
  468. func (pc *PCCloud) HasClusterID() bool {
  469. return true
  470. }
  471. // AttachDisk attaches given virtual disk volume to the compute running kubelet.
  472. func (pc *PCCloud) AttachDisk(ctx context.Context, pdID string, nodeName k8stypes.NodeName) error {
  473. photonClient, err := getPhotonClient(pc)
  474. if err != nil {
  475. klog.Errorf("Photon Cloud Provider: Failed to get photon client for AttachDisk, error: [%v]", err)
  476. return err
  477. }
  478. operation := &photon.VmDiskOperation{
  479. DiskID: pdID,
  480. }
  481. vmID, err := pc.InstanceID(ctx, nodeName)
  482. if err != nil {
  483. klog.Errorf("Photon Cloud Provider: pc.InstanceID failed for AttachDisk. Error[%v]", err)
  484. return err
  485. }
  486. task, err := photonClient.VMs.AttachDisk(vmID, operation)
  487. if err != nil {
  488. klog.Errorf("Photon Cloud Provider: Failed to attach disk with pdID %s. Error[%v]", pdID, err)
  489. return err
  490. }
  491. _, err = photonClient.Tasks.Wait(task.ID)
  492. if err != nil {
  493. klog.Errorf("Photon Cloud Provider: Failed to wait for task to attach disk with pdID %s. Error[%v]", pdID, err)
  494. return err
  495. }
  496. return nil
  497. }
  498. // Detaches given virtual disk volume from the compute running kubelet.
  499. func (pc *PCCloud) DetachDisk(ctx context.Context, pdID string, nodeName k8stypes.NodeName) error {
  500. photonClient, err := getPhotonClient(pc)
  501. if err != nil {
  502. klog.Errorf("Photon Cloud Provider: Failed to get photon client for DetachDisk, error: [%v]", err)
  503. return err
  504. }
  505. operation := &photon.VmDiskOperation{
  506. DiskID: pdID,
  507. }
  508. vmID, err := pc.InstanceID(ctx, nodeName)
  509. if err != nil {
  510. klog.Errorf("Photon Cloud Provider: pc.InstanceID failed for DetachDisk. Error[%v]", err)
  511. return err
  512. }
  513. task, err := photonClient.VMs.DetachDisk(vmID, operation)
  514. if err != nil {
  515. klog.Errorf("Photon Cloud Provider: Failed to detach disk with pdID %s. Error[%v]", pdID, err)
  516. return err
  517. }
  518. _, err = photonClient.Tasks.Wait(task.ID)
  519. if err != nil {
  520. klog.Errorf("Photon Cloud Provider: Failed to wait for task to detach disk with pdID %s. Error[%v]", pdID, err)
  521. return err
  522. }
  523. return nil
  524. }
  525. // DiskIsAttached returns if disk is attached to the VM using controllers supported by the plugin.
  526. func (pc *PCCloud) DiskIsAttached(ctx context.Context, pdID string, nodeName k8stypes.NodeName) (bool, error) {
  527. photonClient, err := getPhotonClient(pc)
  528. if err != nil {
  529. klog.Errorf("Photon Cloud Provider: Failed to get photon client for DiskIsAttached, error: [%v]", err)
  530. return false, err
  531. }
  532. disk, err := photonClient.Disks.Get(pdID)
  533. if err != nil {
  534. klog.Errorf("Photon Cloud Provider: Failed to Get disk with pdID %s. Error[%v]", pdID, err)
  535. return false, err
  536. }
  537. vmID, err := pc.InstanceID(ctx, nodeName)
  538. if err == cloudprovider.InstanceNotFound {
  539. klog.Infof("Instance %q does not exist, disk %s will be detached automatically.", nodeName, pdID)
  540. return false, nil
  541. }
  542. if err != nil {
  543. klog.Errorf("Photon Cloud Provider: pc.InstanceID failed for DiskIsAttached. Error[%v]", err)
  544. return false, err
  545. }
  546. for _, vm := range disk.VMs {
  547. if vm == vmID {
  548. return true, nil
  549. }
  550. }
  551. return false, nil
  552. }
  553. // DisksAreAttached returns if disks are attached to the VM using controllers supported by the plugin.
  554. func (pc *PCCloud) DisksAreAttached(ctx context.Context, pdIDs []string, nodeName k8stypes.NodeName) (map[string]bool, error) {
  555. attached := make(map[string]bool)
  556. photonClient, err := getPhotonClient(pc)
  557. if err != nil {
  558. klog.Errorf("Photon Cloud Provider: Failed to get photon client for DisksAreAttached, error: [%v]", err)
  559. return attached, err
  560. }
  561. for _, pdID := range pdIDs {
  562. attached[pdID] = false
  563. }
  564. vmID, err := pc.InstanceID(ctx, nodeName)
  565. if err == cloudprovider.InstanceNotFound {
  566. klog.Infof("Instance %q does not exist, its disks will be detached automatically.", nodeName)
  567. // make all the disks as detached.
  568. return attached, nil
  569. }
  570. if err != nil {
  571. klog.Errorf("Photon Cloud Provider: pc.InstanceID failed for DiskIsAttached. Error[%v]", err)
  572. return attached, err
  573. }
  574. for _, pdID := range pdIDs {
  575. disk, err := photonClient.Disks.Get(pdID)
  576. if err != nil {
  577. klog.Warningf("Photon Cloud Provider: failed to get VMs for persistent disk %s, err [%v]", pdID, err)
  578. } else {
  579. for _, vm := range disk.VMs {
  580. if vm == vmID {
  581. attached[pdID] = true
  582. }
  583. }
  584. }
  585. }
  586. return attached, nil
  587. }
  588. // Create a volume of given size (in GB).
  589. func (pc *PCCloud) CreateDisk(volumeOptions *VolumeOptions) (pdID string, err error) {
  590. photonClient, err := getPhotonClient(pc)
  591. if err != nil {
  592. klog.Errorf("Photon Cloud Provider: Failed to get photon client for CreateDisk, error: [%v]", err)
  593. return "", err
  594. }
  595. diskSpec := photon.DiskCreateSpec{}
  596. diskSpec.Name = volumeOptions.Name
  597. diskSpec.Flavor = volumeOptions.Flavor
  598. diskSpec.CapacityGB = volumeOptions.CapacityGB
  599. diskSpec.Kind = DiskSpecKind
  600. task, err := photonClient.Projects.CreateDisk(pc.projID, &diskSpec)
  601. if err != nil {
  602. klog.Errorf("Photon Cloud Provider: Failed to CreateDisk. Error[%v]", err)
  603. return "", err
  604. }
  605. waitTask, err := photonClient.Tasks.Wait(task.ID)
  606. if err != nil {
  607. klog.Errorf("Photon Cloud Provider: Failed to wait for task to CreateDisk. Error[%v]", err)
  608. return "", err
  609. }
  610. return waitTask.Entity.ID, nil
  611. }
  612. // DeleteDisk deletes a volume given volume name.
  613. func (pc *PCCloud) DeleteDisk(pdID string) error {
  614. photonClient, err := getPhotonClient(pc)
  615. if err != nil {
  616. klog.Errorf("Photon Cloud Provider: Failed to get photon client for DeleteDisk, error: [%v]", err)
  617. return err
  618. }
  619. task, err := photonClient.Disks.Delete(pdID)
  620. if err != nil {
  621. klog.Errorf("Photon Cloud Provider: Failed to DeleteDisk. Error[%v]", err)
  622. return err
  623. }
  624. _, err = photonClient.Tasks.Wait(task.ID)
  625. if err != nil {
  626. klog.Errorf("Photon Cloud Provider: Failed to wait for task to DeleteDisk. Error[%v]", err)
  627. return err
  628. }
  629. return nil
  630. }