Commit 8b0e9c57 authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #24947 from hpcloud/hpe/vsphere-volume

Automatic merge from submit-queue vSphere Volume Plugin Implementation This PR implements vSphere Volume plugin support in Kubernetes (ref. issue #23932).
parents 180ab70d 11397654
...@@ -1864,78 +1864,78 @@ ...@@ -1864,78 +1864,78 @@
}, },
{ {
"ImportPath": "github.com/vmware/govmomi", "ImportPath": "github.com/vmware/govmomi",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/find", "ImportPath": "github.com/vmware/govmomi/find",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/list", "ImportPath": "github.com/vmware/govmomi/list",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/object", "ImportPath": "github.com/vmware/govmomi/object",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/property", "ImportPath": "github.com/vmware/govmomi/property",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/session", "ImportPath": "github.com/vmware/govmomi/session",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/task", "ImportPath": "github.com/vmware/govmomi/task",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25", "ImportPath": "github.com/vmware/govmomi/vim25",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/debug", "ImportPath": "github.com/vmware/govmomi/vim25/debug",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/methods", "ImportPath": "github.com/vmware/govmomi/vim25/methods",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/mo", "ImportPath": "github.com/vmware/govmomi/vim25/mo",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/progress", "ImportPath": "github.com/vmware/govmomi/vim25/progress",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/soap", "ImportPath": "github.com/vmware/govmomi/vim25/soap",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/types", "ImportPath": "github.com/vmware/govmomi/vim25/types",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/xml", "ImportPath": "github.com/vmware/govmomi/vim25/xml",
"Comment": "v0.5.0", "Comment": "v0.6.2",
"Rev": "c1b29993f383c32fc3fadb90892909668699810a" "Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
}, },
{ {
"ImportPath": "github.com/xiang90/probing", "ImportPath": "github.com/xiang90/probing",
......
...@@ -1299,6 +1299,10 @@ ...@@ -1299,6 +1299,10 @@
"configMap": { "configMap": {
"$ref": "v1.ConfigMapVolumeSource", "$ref": "v1.ConfigMapVolumeSource",
"description": "ConfigMap represents a configMap that should populate this volume" "description": "ConfigMap represents a configMap that should populate this volume"
},
"vsphereVolume": {
"$ref": "v1.VsphereVirtualDiskVolumeSource",
"description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine"
} }
} }
}, },
...@@ -1805,6 +1809,23 @@ ...@@ -1805,6 +1809,23 @@
} }
} }
}, },
"v1.VsphereVirtualDiskVolumeSource": {
"id": "v1.VsphereVirtualDiskVolumeSource",
"description": "Represents a vSphere volume resource.",
"required": [
"volumePath"
],
"properties": {
"volumePath": {
"type": "string",
"description": "Path that identifies vSphere volume vmdk"
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
}
}
},
"v1.Container": { "v1.Container": {
"id": "v1.Container", "id": "v1.Container",
"description": "A single application container that you want to run within a pod.", "description": "A single application container that you want to run within a pod.",
......
...@@ -1304,6 +1304,10 @@ ...@@ -1304,6 +1304,10 @@
"configMap": { "configMap": {
"$ref": "v1.ConfigMapVolumeSource", "$ref": "v1.ConfigMapVolumeSource",
"description": "ConfigMap represents a configMap that should populate this volume" "description": "ConfigMap represents a configMap that should populate this volume"
},
"vsphereVolume": {
"$ref": "v1.VsphereVirtualDiskVolumeSource",
"description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine"
} }
} }
}, },
...@@ -1810,6 +1814,23 @@ ...@@ -1810,6 +1814,23 @@
} }
} }
}, },
"v1.VsphereVirtualDiskVolumeSource": {
"id": "v1.VsphereVirtualDiskVolumeSource",
"description": "Represents a vSphere volume resource.",
"required": [
"volumePath"
],
"properties": {
"volumePath": {
"type": "string",
"description": "Path that identifies vSphere volume vmdk"
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
}
}
},
"v1.Container": { "v1.Container": {
"id": "v1.Container", "id": "v1.Container",
"description": "A single application container that you want to run within a pod.", "description": "A single application container that you want to run within a pod.",
......
...@@ -6611,6 +6611,10 @@ ...@@ -6611,6 +6611,10 @@
"configMap": { "configMap": {
"$ref": "v1.ConfigMapVolumeSource", "$ref": "v1.ConfigMapVolumeSource",
"description": "ConfigMap represents a configMap that should populate this volume" "description": "ConfigMap represents a configMap that should populate this volume"
},
"vsphereVolume": {
"$ref": "v1.VsphereVirtualDiskVolumeSource",
"description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine"
} }
} }
}, },
...@@ -7117,6 +7121,23 @@ ...@@ -7117,6 +7121,23 @@
} }
} }
}, },
"v1.VsphereVirtualDiskVolumeSource": {
"id": "v1.VsphereVirtualDiskVolumeSource",
"description": "Represents a vSphere volume resource.",
"required": [
"volumePath"
],
"properties": {
"volumePath": {
"type": "string",
"description": "Path that identifies vSphere volume vmdk"
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
}
}
},
"v1.Container": { "v1.Container": {
"id": "v1.Container", "id": "v1.Container",
"description": "A single application container that you want to run within a pod.", "description": "A single application container that you want to run within a pod.",
......
...@@ -16359,6 +16359,10 @@ ...@@ -16359,6 +16359,10 @@
"$ref": "v1.AzureFileVolumeSource", "$ref": "v1.AzureFileVolumeSource",
"description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod." "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod."
}, },
"vsphereVolume": {
"$ref": "v1.VsphereVirtualDiskVolumeSource",
"description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine"
},
"accessModes": { "accessModes": {
"type": "array", "type": "array",
"items": { "items": {
...@@ -16731,6 +16735,23 @@ ...@@ -16731,6 +16735,23 @@
} }
} }
}, },
"v1.VsphereVirtualDiskVolumeSource": {
"id": "v1.VsphereVirtualDiskVolumeSource",
"description": "Represents a vSphere volume resource.",
"required": [
"volumePath"
],
"properties": {
"volumePath": {
"type": "string",
"description": "Path that identifies vSphere volume vmdk"
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
}
}
},
"v1.PersistentVolumeStatus": { "v1.PersistentVolumeStatus": {
"id": "v1.PersistentVolumeStatus", "id": "v1.PersistentVolumeStatus",
"description": "PersistentVolumeStatus is the current status of a persistent volume.", "description": "PersistentVolumeStatus is the current status of a persistent volume.",
...@@ -16977,6 +16998,10 @@ ...@@ -16977,6 +16998,10 @@
"configMap": { "configMap": {
"$ref": "v1.ConfigMapVolumeSource", "$ref": "v1.ConfigMapVolumeSource",
"description": "ConfigMap represents a configMap that should populate this volume" "description": "ConfigMap represents a configMap that should populate this volume"
},
"vsphereVolume": {
"$ref": "v1.VsphereVirtualDiskVolumeSource",
"description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine"
} }
} }
}, },
......
...@@ -32,6 +32,7 @@ import ( ...@@ -32,6 +32,7 @@ import (
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws" "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce" "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
"k8s.io/kubernetes/pkg/cloudprovider/providers/openstack" "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack"
"k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere"
"k8s.io/kubernetes/pkg/util/io" "k8s.io/kubernetes/pkg/util/io"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/aws_ebs" "k8s.io/kubernetes/pkg/volume/aws_ebs"
...@@ -39,6 +40,7 @@ import ( ...@@ -39,6 +40,7 @@ import (
"k8s.io/kubernetes/pkg/volume/gce_pd" "k8s.io/kubernetes/pkg/volume/gce_pd"
"k8s.io/kubernetes/pkg/volume/host_path" "k8s.io/kubernetes/pkg/volume/host_path"
"k8s.io/kubernetes/pkg/volume/nfs" "k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/vsphere_volume"
"github.com/golang/glog" "github.com/golang/glog"
) )
...@@ -79,6 +81,7 @@ func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) [] ...@@ -79,6 +81,7 @@ func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) []
allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...) allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...) allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...) allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
return allPlugins return allPlugins
} }
...@@ -97,6 +100,8 @@ func NewVolumeProvisioner(cloud cloudprovider.Interface, config componentconfig. ...@@ -97,6 +100,8 @@ func NewVolumeProvisioner(cloud cloudprovider.Interface, config componentconfig.
return getProvisionablePluginFromVolumePlugins(gce_pd.ProbeVolumePlugins()) return getProvisionablePluginFromVolumePlugins(gce_pd.ProbeVolumePlugins())
case cloud != nil && openstack.ProviderName == cloud.ProviderName(): case cloud != nil && openstack.ProviderName == cloud.ProviderName():
return getProvisionablePluginFromVolumePlugins(cinder.ProbeVolumePlugins()) return getProvisionablePluginFromVolumePlugins(cinder.ProbeVolumePlugins())
case cloud != nil && vsphere.ProviderName == cloud.ProviderName():
return getProvisionablePluginFromVolumePlugins(vsphere_volume.ProbeVolumePlugins())
} }
return nil, nil return nil, nil
} }
......
...@@ -46,6 +46,7 @@ import ( ...@@ -46,6 +46,7 @@ import (
"k8s.io/kubernetes/pkg/volume/nfs" "k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/rbd" "k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/secret" "k8s.io/kubernetes/pkg/volume/secret"
"k8s.io/kubernetes/pkg/volume/vsphere_volume"
// Cloud providers // Cloud providers
_ "k8s.io/kubernetes/pkg/cloudprovider/providers" _ "k8s.io/kubernetes/pkg/cloudprovider/providers"
) )
...@@ -80,6 +81,7 @@ func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin { ...@@ -80,6 +81,7 @@ func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
allPlugins = append(allPlugins, flexvolume.ProbeVolumePlugins(pluginDir)...) allPlugins = append(allPlugins, flexvolume.ProbeVolumePlugins(pluginDir)...)
allPlugins = append(allPlugins, azure_file.ProbeVolumePlugins()...) allPlugins = append(allPlugins, azure_file.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, configmap.ProbeVolumePlugins()...) allPlugins = append(allPlugins, configmap.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
return allPlugins return allPlugins
} }
......
...@@ -2854,6 +2854,47 @@ Populated by the system when a graceful deletion is requested. Read-only. More i ...@@ -2854,6 +2854,47 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_vspherevirtualdiskvolumesource">v1.VsphereVirtualDiskVolumeSource</h3>
<div class="paragraph">
<p>Represents a vSphere volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumePath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Path that identifies vSphere volume vmdk</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_jobspec">v1.JobSpec</h3> <h3 id="_v1_jobspec">v1.JobSpec</h3>
<div class="paragraph"> <div class="paragraph">
<p>JobSpec describes how the job execution will look like.</p> <p>JobSpec describes how the job execution will look like.</p>
...@@ -3147,6 +3188,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i ...@@ -3147,6 +3188,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_configmapvolumesource">v1.ConfigMapVolumeSource</a></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_configmapvolumesource">v1.ConfigMapVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">vsphereVolume</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_vspherevirtualdiskvolumesource">v1.VsphereVirtualDiskVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -3986,7 +4034,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i ...@@ -3986,7 +4034,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-05-18 12:33:08 UTC Last updated 2016-05-21 03:08:23 UTC
</div> </div>
</div> </div>
</body> </body>
......
...@@ -2610,6 +2610,47 @@ Populated by the system when a graceful deletion is requested. Read-only. More i ...@@ -2610,6 +2610,47 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_vspherevirtualdiskvolumesource">v1.VsphereVirtualDiskVolumeSource</h3>
<div class="paragraph">
<p>Represents a vSphere volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumePath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Path that identifies vSphere volume vmdk</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_deleteoptions">v1.DeleteOptions</h3> <h3 id="_v1_deleteoptions">v1.DeleteOptions</h3>
<div class="paragraph"> <div class="paragraph">
<p>DeleteOptions may be provided when deleting an API object</p> <p>DeleteOptions may be provided when deleting an API object</p>
...@@ -2834,6 +2875,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i ...@@ -2834,6 +2875,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_configmapvolumesource">v1.ConfigMapVolumeSource</a></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_configmapvolumesource">v1.ConfigMapVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">vsphereVolume</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_vspherevirtualdiskvolumesource">v1.VsphereVirtualDiskVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -5976,7 +6024,7 @@ Both these may change in the future. Incoming requests are matched against the h ...@@ -5976,7 +6024,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-05-18 12:32:45 UTC Last updated 2016-05-21 03:08:18 UTC
</div> </div>
</div> </div>
</body> </body>
......
...@@ -3176,6 +3176,47 @@ The resulting set of endpoints can be viewed as:<br> ...@@ -3176,6 +3176,47 @@ The resulting set of endpoints can be viewed as:<br>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_vspherevirtualdiskvolumesource">v1.VsphereVirtualDiskVolumeSource</h3>
<div class="paragraph">
<p>Represents a vSphere volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumePath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Path that identifies vSphere volume vmdk</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_serviceaccountlist">v1.ServiceAccountList</h3> <h3 id="_v1_serviceaccountlist">v1.ServiceAccountList</h3>
<div class="paragraph"> <div class="paragraph">
<p>ServiceAccountList is a list of ServiceAccount objects</p> <p>ServiceAccountList is a list of ServiceAccount objects</p>
...@@ -3576,6 +3617,13 @@ The resulting set of endpoints can be viewed as:<br> ...@@ -3576,6 +3617,13 @@ The resulting set of endpoints can be viewed as:<br>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_configmapvolumesource">v1.ConfigMapVolumeSource</a></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_configmapvolumesource">v1.ConfigMapVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">vsphereVolume</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_vspherevirtualdiskvolumesource">v1.VsphereVirtualDiskVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody> </tbody>
</table> </table>
...@@ -4467,6 +4515,54 @@ The resulting set of endpoints can be viewed as:<br> ...@@ -4467,6 +4515,54 @@ The resulting set of endpoints can be viewed as:<br>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_handler">v1.Handler</h3>
<div class="paragraph">
<p>Handler defines a specific action that should be taken</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">exec</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">One and only one of the following should be specified. Exec specifies the action to take.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_execaction">v1.ExecAction</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">httpGet</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">HTTPGet specifies the http request to perform.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_httpgetaction">v1.HTTPGetAction</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">tcpSocket</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_tcpsocketaction">v1.TCPSocketAction</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_nodestatus">v1.NodeStatus</h3> <h3 id="_v1_nodestatus">v1.NodeStatus</h3>
<div class="paragraph"> <div class="paragraph">
<p>NodeStatus is information about the current status of a node.</p> <p>NodeStatus is information about the current status of a node.</p>
...@@ -4598,54 +4694,6 @@ The resulting set of endpoints can be viewed as:<br> ...@@ -4598,54 +4694,6 @@ The resulting set of endpoints can be viewed as:<br>
</div> </div>
<div class="sect2"> <div class="sect2">
<h3 id="_v1_handler">v1.Handler</h3>
<div class="paragraph">
<p>Handler defines a specific action that should be taken</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">exec</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">One and only one of the following should be specified. Exec specifies the action to take.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_execaction">v1.ExecAction</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">httpGet</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">HTTPGet specifies the http request to perform.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_httpgetaction">v1.HTTPGetAction</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">tcpSocket</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_tcpsocketaction">v1.TCPSocketAction</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_eventsource">v1.EventSource</h3> <h3 id="_v1_eventsource">v1.EventSource</h3>
<div class="paragraph"> <div class="paragraph">
<p>EventSource contains information for an event.</p> <p>EventSource contains information for an event.</p>
...@@ -5705,6 +5753,13 @@ The resulting set of endpoints can be viewed as:<br> ...@@ -5705,6 +5753,13 @@ The resulting set of endpoints can be viewed as:<br>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">vsphereVolume</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_vspherevirtualdiskvolumesource">v1.VsphereVirtualDiskVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">accessModes</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">accessModes</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">AccessModes contains all ways the volume can be mounted. More info: <a href="http://releases.k8s.io/HEAD/docs/user-guide/persistent-volumes.md#access-modes">http://releases.k8s.io/HEAD/docs/user-guide/persistent-volumes.md#access-modes</a></p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">AccessModes contains all ways the volume can be mounted. More info: <a href="http://releases.k8s.io/HEAD/docs/user-guide/persistent-volumes.md#access-modes">http://releases.k8s.io/HEAD/docs/user-guide/persistent-volumes.md#access-modes</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td> <td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
...@@ -7867,7 +7922,7 @@ The resulting set of endpoints can be viewed as:<br> ...@@ -7867,7 +7922,7 @@ The resulting set of endpoints can be viewed as:<br>
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-05-18 12:32:13 UTC Last updated 2016-05-21 03:08:11 UTC
</div> </div>
</div> </div>
</body> </body>
......
...@@ -180,6 +180,7 @@ func init() { ...@@ -180,6 +180,7 @@ func init() {
DeepCopy_api_Volume, DeepCopy_api_Volume,
DeepCopy_api_VolumeMount, DeepCopy_api_VolumeMount,
DeepCopy_api_VolumeSource, DeepCopy_api_VolumeSource,
DeepCopy_api_VsphereVirtualDiskVolumeSource,
DeepCopy_api_WeightedPodAffinityTerm, DeepCopy_api_WeightedPodAffinityTerm,
); err != nil { ); err != nil {
// if one of the deep copy functions is malformed, detect it immediately. // if one of the deep copy functions is malformed, detect it immediately.
...@@ -1925,6 +1926,15 @@ func DeepCopy_api_PersistentVolumeSource(in PersistentVolumeSource, out *Persist ...@@ -1925,6 +1926,15 @@ func DeepCopy_api_PersistentVolumeSource(in PersistentVolumeSource, out *Persist
} else { } else {
out.AzureFile = nil out.AzureFile = nil
} }
if in.VsphereVolume != nil {
in, out := in.VsphereVolume, &out.VsphereVolume
*out = new(VsphereVirtualDiskVolumeSource)
if err := DeepCopy_api_VsphereVirtualDiskVolumeSource(*in, *out, c); err != nil {
return err
}
} else {
out.VsphereVolume = nil
}
return nil return nil
} }
...@@ -3183,6 +3193,21 @@ func DeepCopy_api_VolumeSource(in VolumeSource, out *VolumeSource, c *conversion ...@@ -3183,6 +3193,21 @@ func DeepCopy_api_VolumeSource(in VolumeSource, out *VolumeSource, c *conversion
} else { } else {
out.ConfigMap = nil out.ConfigMap = nil
} }
if in.VsphereVolume != nil {
in, out := in.VsphereVolume, &out.VsphereVolume
*out = new(VsphereVirtualDiskVolumeSource)
if err := DeepCopy_api_VsphereVirtualDiskVolumeSource(*in, *out, c); err != nil {
return err
}
} else {
out.VsphereVolume = nil
}
return nil
}
func DeepCopy_api_VsphereVirtualDiskVolumeSource(in VsphereVirtualDiskVolumeSource, out *VsphereVirtualDiskVolumeSource, c *conversion.Cloner) error {
out.VolumePath = in.VolumePath
out.FSType = in.FSType
return nil return nil
} }
......
...@@ -229,6 +229,8 @@ type VolumeSource struct { ...@@ -229,6 +229,8 @@ type VolumeSource struct {
AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty"` AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty"`
// ConfigMap represents a configMap that should populate this volume // ConfigMap represents a configMap that should populate this volume
ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"` ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"`
// VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine
VsphereVolume *VsphereVirtualDiskVolumeSource `json:"vsphereVolume,omitempty"`
} }
// Similar to VolumeSource but meant for the administrator who creates PVs. // Similar to VolumeSource but meant for the administrator who creates PVs.
...@@ -267,6 +269,8 @@ type PersistentVolumeSource struct { ...@@ -267,6 +269,8 @@ type PersistentVolumeSource struct {
Flocker *FlockerVolumeSource `json:"flocker,omitempty"` Flocker *FlockerVolumeSource `json:"flocker,omitempty"`
// AzureFile represents an Azure File Service mount on the host and bind mount to the pod. // AzureFile represents an Azure File Service mount on the host and bind mount to the pod.
AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty"` AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty"`
// VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine
VsphereVolume *VsphereVirtualDiskVolumeSource `json:"vsphereVolume,omitempty"`
} }
type PersistentVolumeClaimVolumeSource struct { type PersistentVolumeClaimVolumeSource struct {
...@@ -716,6 +720,16 @@ type AzureFileVolumeSource struct { ...@@ -716,6 +720,16 @@ type AzureFileVolumeSource struct {
ReadOnly bool `json:"readOnly,omitempty"` ReadOnly bool `json:"readOnly,omitempty"`
} }
// Represents a vSphere volume resource.
type VsphereVirtualDiskVolumeSource struct {
// Path that identifies vSphere volume vmdk
VolumePath string `json:"volumePath"`
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
FSType string `json:"fsType,omitempty"`
}
// Adapts a ConfigMap into a volume. // Adapts a ConfigMap into a volume.
// //
// The contents of the target ConfigMap's Data field will be presented in a // The contents of the target ConfigMap's Data field will be presented in a
......
...@@ -321,6 +321,8 @@ func init() { ...@@ -321,6 +321,8 @@ func init() {
Convert_api_VolumeMount_To_v1_VolumeMount, Convert_api_VolumeMount_To_v1_VolumeMount,
Convert_v1_VolumeSource_To_api_VolumeSource, Convert_v1_VolumeSource_To_api_VolumeSource,
Convert_api_VolumeSource_To_v1_VolumeSource, Convert_api_VolumeSource_To_v1_VolumeSource,
Convert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource,
Convert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource,
Convert_v1_WeightedPodAffinityTerm_To_api_WeightedPodAffinityTerm, Convert_v1_WeightedPodAffinityTerm_To_api_WeightedPodAffinityTerm,
Convert_api_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm, Convert_api_WeightedPodAffinityTerm_To_v1_WeightedPodAffinityTerm,
); err != nil { ); err != nil {
...@@ -4248,6 +4250,15 @@ func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *Per ...@@ -4248,6 +4250,15 @@ func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *Per
} else { } else {
out.AzureFile = nil out.AzureFile = nil
} }
if in.VsphereVolume != nil {
in, out := &in.VsphereVolume, &out.VsphereVolume
*out = new(api.VsphereVirtualDiskVolumeSource)
if err := Convert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource(*in, *out, s); err != nil {
return err
}
} else {
out.VsphereVolume = nil
}
return nil return nil
} }
...@@ -4373,6 +4384,15 @@ func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api ...@@ -4373,6 +4384,15 @@ func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api
} else { } else {
out.AzureFile = nil out.AzureFile = nil
} }
if in.VsphereVolume != nil {
in, out := &in.VsphereVolume, &out.VsphereVolume
*out = new(VsphereVirtualDiskVolumeSource)
if err := Convert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(*in, *out, s); err != nil {
return err
}
} else {
out.VsphereVolume = nil
}
return nil return nil
} }
...@@ -6841,6 +6861,15 @@ func autoConvert_v1_VolumeSource_To_api_VolumeSource(in *VolumeSource, out *api. ...@@ -6841,6 +6861,15 @@ func autoConvert_v1_VolumeSource_To_api_VolumeSource(in *VolumeSource, out *api.
} else { } else {
out.ConfigMap = nil out.ConfigMap = nil
} }
if in.VsphereVolume != nil {
in, out := &in.VsphereVolume, &out.VsphereVolume
*out = new(api.VsphereVirtualDiskVolumeSource)
if err := Convert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource(*in, *out, s); err != nil {
return err
}
} else {
out.VsphereVolume = nil
}
return nil return nil
} }
...@@ -7020,6 +7049,15 @@ func autoConvert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out * ...@@ -7020,6 +7049,15 @@ func autoConvert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *
} else { } else {
out.ConfigMap = nil out.ConfigMap = nil
} }
if in.VsphereVolume != nil {
in, out := &in.VsphereVolume, &out.VsphereVolume
*out = new(VsphereVirtualDiskVolumeSource)
if err := Convert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(*in, *out, s); err != nil {
return err
}
} else {
out.VsphereVolume = nil
}
return nil return nil
} }
...@@ -7027,6 +7065,26 @@ func Convert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *Volu ...@@ -7027,6 +7065,26 @@ func Convert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *Volu
return autoConvert_api_VolumeSource_To_v1_VolumeSource(in, out, s) return autoConvert_api_VolumeSource_To_v1_VolumeSource(in, out, s)
} }
func autoConvert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource(in *VsphereVirtualDiskVolumeSource, out *api.VsphereVirtualDiskVolumeSource, s conversion.Scope) error {
out.VolumePath = in.VolumePath
out.FSType = in.FSType
return nil
}
func Convert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource(in *VsphereVirtualDiskVolumeSource, out *api.VsphereVirtualDiskVolumeSource, s conversion.Scope) error {
return autoConvert_v1_VsphereVirtualDiskVolumeSource_To_api_VsphereVirtualDiskVolumeSource(in, out, s)
}
func autoConvert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(in *api.VsphereVirtualDiskVolumeSource, out *VsphereVirtualDiskVolumeSource, s conversion.Scope) error {
out.VolumePath = in.VolumePath
out.FSType = in.FSType
return nil
}
func Convert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(in *api.VsphereVirtualDiskVolumeSource, out *VsphereVirtualDiskVolumeSource, s conversion.Scope) error {
return autoConvert_api_VsphereVirtualDiskVolumeSource_To_v1_VsphereVirtualDiskVolumeSource(in, out, s)
}
func autoConvert_v1_WeightedPodAffinityTerm_To_api_WeightedPodAffinityTerm(in *WeightedPodAffinityTerm, out *api.WeightedPodAffinityTerm, s conversion.Scope) error { func autoConvert_v1_WeightedPodAffinityTerm_To_api_WeightedPodAffinityTerm(in *WeightedPodAffinityTerm, out *api.WeightedPodAffinityTerm, s conversion.Scope) error {
out.Weight = int(in.Weight) out.Weight = int(in.Weight)
if err := Convert_v1_PodAffinityTerm_To_api_PodAffinityTerm(&in.PodAffinityTerm, &out.PodAffinityTerm, s); err != nil { if err := Convert_v1_PodAffinityTerm_To_api_PodAffinityTerm(&in.PodAffinityTerm, &out.PodAffinityTerm, s); err != nil {
......
...@@ -177,6 +177,7 @@ func init() { ...@@ -177,6 +177,7 @@ func init() {
DeepCopy_v1_Volume, DeepCopy_v1_Volume,
DeepCopy_v1_VolumeMount, DeepCopy_v1_VolumeMount,
DeepCopy_v1_VolumeSource, DeepCopy_v1_VolumeSource,
DeepCopy_v1_VsphereVirtualDiskVolumeSource,
DeepCopy_v1_WeightedPodAffinityTerm, DeepCopy_v1_WeightedPodAffinityTerm,
); err != nil { ); err != nil {
// if one of the deep copy functions is malformed, detect it immediately. // if one of the deep copy functions is malformed, detect it immediately.
...@@ -1872,6 +1873,15 @@ func DeepCopy_v1_PersistentVolumeSource(in PersistentVolumeSource, out *Persiste ...@@ -1872,6 +1873,15 @@ func DeepCopy_v1_PersistentVolumeSource(in PersistentVolumeSource, out *Persiste
} else { } else {
out.AzureFile = nil out.AzureFile = nil
} }
if in.VsphereVolume != nil {
in, out := in.VsphereVolume, &out.VsphereVolume
*out = new(VsphereVirtualDiskVolumeSource)
if err := DeepCopy_v1_VsphereVirtualDiskVolumeSource(*in, *out, c); err != nil {
return err
}
} else {
out.VsphereVolume = nil
}
return nil return nil
} }
...@@ -3144,6 +3154,21 @@ func DeepCopy_v1_VolumeSource(in VolumeSource, out *VolumeSource, c *conversion. ...@@ -3144,6 +3154,21 @@ func DeepCopy_v1_VolumeSource(in VolumeSource, out *VolumeSource, c *conversion.
} else { } else {
out.ConfigMap = nil out.ConfigMap = nil
} }
if in.VsphereVolume != nil {
in, out := in.VsphereVolume, &out.VsphereVolume
*out = new(VsphereVirtualDiskVolumeSource)
if err := DeepCopy_v1_VsphereVirtualDiskVolumeSource(*in, *out, c); err != nil {
return err
}
} else {
out.VsphereVolume = nil
}
return nil
}
func DeepCopy_v1_VsphereVirtualDiskVolumeSource(in VsphereVirtualDiskVolumeSource, out *VsphereVirtualDiskVolumeSource, c *conversion.Cloner) error {
out.VolumePath = in.VolumePath
out.FSType = in.FSType
return nil return nil
} }
......
...@@ -1676,6 +1676,9 @@ message PersistentVolumeSource { ...@@ -1676,6 +1676,9 @@ message PersistentVolumeSource {
// AzureFile represents an Azure File Service mount on the host and bind mount to the pod. // AzureFile represents an Azure File Service mount on the host and bind mount to the pod.
optional AzureFileVolumeSource azureFile = 13; optional AzureFileVolumeSource azureFile = 13;
// VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine
optional VsphereVirtualDiskVolumeSource vsphereVolume = 14;
} }
// PersistentVolumeSpec is the specification of a persistent volume. // PersistentVolumeSpec is the specification of a persistent volume.
...@@ -2844,6 +2847,20 @@ message VolumeSource { ...@@ -2844,6 +2847,20 @@ message VolumeSource {
// ConfigMap represents a configMap that should populate this volume // ConfigMap represents a configMap that should populate this volume
optional ConfigMapVolumeSource configMap = 19; optional ConfigMapVolumeSource configMap = 19;
// VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine
optional VsphereVirtualDiskVolumeSource vsphereVolume = 20;
}
// Represents a vSphere volume resource.
message VsphereVirtualDiskVolumeSource {
// Path that identifies vSphere volume vmdk
optional string volumePath = 1;
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
optional string fsType = 2;
} }
// The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) // The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)
......
...@@ -273,6 +273,8 @@ type VolumeSource struct { ...@@ -273,6 +273,8 @@ type VolumeSource struct {
AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty" protobuf:"bytes,18,opt,name=azureFile"` AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty" protobuf:"bytes,18,opt,name=azureFile"`
// ConfigMap represents a configMap that should populate this volume // ConfigMap represents a configMap that should populate this volume
ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty" protobuf:"bytes,19,opt,name=configMap"` ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty" protobuf:"bytes,19,opt,name=configMap"`
// VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine
VsphereVolume *VsphereVirtualDiskVolumeSource `json:"vsphereVolume,omitempty" protobuf:"bytes,20,opt,name=vsphereVolume"`
} }
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. // PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
...@@ -333,6 +335,8 @@ type PersistentVolumeSource struct { ...@@ -333,6 +335,8 @@ type PersistentVolumeSource struct {
FlexVolume *FlexVolumeSource `json:"flexVolume,omitempty" protobuf:"bytes,12,opt,name=flexVolume"` FlexVolume *FlexVolumeSource `json:"flexVolume,omitempty" protobuf:"bytes,12,opt,name=flexVolume"`
// AzureFile represents an Azure File Service mount on the host and bind mount to the pod. // AzureFile represents an Azure File Service mount on the host and bind mount to the pod.
AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty" protobuf:"bytes,13,opt,name=azureFile"` AzureFile *AzureFileVolumeSource `json:"azureFile,omitempty" protobuf:"bytes,13,opt,name=azureFile"`
// VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine
VsphereVolume *VsphereVirtualDiskVolumeSource `json:"vsphereVolume,omitempty" protobuf:"bytes,14,opt,name=vsphereVolume"`
} }
// +genclient=true,nonNamespaced=true // +genclient=true,nonNamespaced=true
...@@ -834,6 +838,16 @@ type AzureFileVolumeSource struct { ...@@ -834,6 +838,16 @@ type AzureFileVolumeSource struct {
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,3,opt,name=readOnly"` ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,3,opt,name=readOnly"`
} }
// Represents a vSphere volume resource.
type VsphereVirtualDiskVolumeSource struct {
// Path that identifies vSphere volume vmdk
VolumePath string `json:"volumePath" protobuf:"bytes,1,opt,name=volumePath"`
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
FSType string `json:"fsType,omitempty" protobuf:"bytes,2,opt,name=fsType"`
}
// Adapts a ConfigMap into a volume. // Adapts a ConfigMap into a volume.
// //
// The contents of the target ConfigMap's Data field will be presented in a // The contents of the target ConfigMap's Data field will be presented in a
......
...@@ -1050,6 +1050,7 @@ var map_PersistentVolumeSource = map[string]string{ ...@@ -1050,6 +1050,7 @@ var map_PersistentVolumeSource = map[string]string{
"flocker": "Flocker represents a Flocker volume attached to a kubelet's host machine and exposed to the pod for its usage. This depends on the Flocker control service being running", "flocker": "Flocker represents a Flocker volume attached to a kubelet's host machine and exposed to the pod for its usage. This depends on the Flocker control service being running",
"flexVolume": "FlexVolume represents a generic volume resource that is provisioned/attached using a exec based plugin. This is an alpha feature and may change in future.", "flexVolume": "FlexVolume represents a generic volume resource that is provisioned/attached using a exec based plugin. This is an alpha feature and may change in future.",
"azureFile": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", "azureFile": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.",
"vsphereVolume": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine",
} }
func (PersistentVolumeSource) SwaggerDoc() map[string]string { func (PersistentVolumeSource) SwaggerDoc() map[string]string {
...@@ -1673,21 +1674,32 @@ var map_VolumeSource = map[string]string{ ...@@ -1673,21 +1674,32 @@ var map_VolumeSource = map[string]string{
"iscsi": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: http://releases.k8s.io/HEAD/examples/iscsi/README.md", "iscsi": "ISCSI represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: http://releases.k8s.io/HEAD/examples/iscsi/README.md",
"glusterfs": "Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. More info: http://releases.k8s.io/HEAD/examples/glusterfs/README.md", "glusterfs": "Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. More info: http://releases.k8s.io/HEAD/examples/glusterfs/README.md",
"persistentVolumeClaim": "PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: http://releases.k8s.io/HEAD/docs/user-guide/persistent-volumes.md#persistentvolumeclaims", "persistentVolumeClaim": "PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: http://releases.k8s.io/HEAD/docs/user-guide/persistent-volumes.md#persistentvolumeclaims",
"rbd": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: http://releases.k8s.io/HEAD/examples/rbd/README.md", "rbd": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime. More info: http://releases.k8s.io/HEAD/examples/rbd/README.md",
"flexVolume": "FlexVolume represents a generic volume resource that is provisioned/attached using a exec based plugin. This is an alpha feature and may change in future.", "flexVolume": "FlexVolume represents a generic volume resource that is provisioned/attached using a exec based plugin. This is an alpha feature and may change in future.",
"cinder": "Cinder represents a cinder volume attached and mounted on kubelets host machine More info: http://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md", "cinder": "Cinder represents a cinder volume attached and mounted on kubelets host machine More info: http://releases.k8s.io/HEAD/examples/mysql-cinder-pd/README.md",
"cephfs": "CephFS represents a Ceph FS mount on the host that shares a pod's lifetime", "cephfs": "CephFS represents a Ceph FS mount on the host that shares a pod's lifetime",
"flocker": "Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running", "flocker": "Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running",
"downwardAPI": "DownwardAPI represents downward API about the pod that should populate this volume", "downwardAPI": "DownwardAPI represents downward API about the pod that should populate this volume",
"fc": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.", "fc": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.",
"azureFile": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", "azureFile": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.",
"configMap": "ConfigMap represents a configMap that should populate this volume", "configMap": "ConfigMap represents a configMap that should populate this volume",
"vsphereVolume": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine",
} }
func (VolumeSource) SwaggerDoc() map[string]string { func (VolumeSource) SwaggerDoc() map[string]string {
return map_VolumeSource return map_VolumeSource
} }
var map_VsphereVirtualDiskVolumeSource = map[string]string{
"": "Represents a vSphere volume resource.",
"volumePath": "Path that identifies vSphere volume vmdk",
"fsType": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.",
}
func (VsphereVirtualDiskVolumeSource) SwaggerDoc() map[string]string {
return map_VsphereVirtualDiskVolumeSource
}
var map_WeightedPodAffinityTerm = map[string]string{ var map_WeightedPodAffinityTerm = map[string]string{
"": "The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)", "": "The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)",
"weight": "weight associated with matching the corresponding podAffinityTerm, in the range 1-100.", "weight": "weight associated with matching the corresponding podAffinityTerm, in the range 1-100.",
......
...@@ -710,6 +710,7 @@ var ( ...@@ -710,6 +710,7 @@ var (
DownwardAPI FSType = "downwardAPI" DownwardAPI FSType = "downwardAPI"
FC FSType = "fc" FC FSType = "fc"
ConfigMap FSType = "configMap" ConfigMap FSType = "configMap"
VsphereVolume FSType = "vsphereVolume"
All FSType = "*" All FSType = "*"
) )
......
...@@ -41,6 +41,11 @@ import ( ...@@ -41,6 +41,11 @@ import (
const ProviderName = "vsphere" const ProviderName = "vsphere"
const ActivePowerState = "poweredOn" const ActivePowerState = "poweredOn"
const DefaultDiskController = "scsi"
const DefaultSCSIControllerType = "lsilogic"
var ErrNoDiskUUIDFound = errors.New("no disk UUID found")
var ErrNoDevicesFound = errors.New("No devices found")
// VSphere is an implementation of cloud provider Interface for VSphere. // VSphere is an implementation of cloud provider Interface for VSphere.
type VSphere struct { type VSphere struct {
...@@ -63,6 +68,10 @@ type VSphereConfig struct { ...@@ -63,6 +68,10 @@ type VSphereConfig struct {
Network struct { Network struct {
PublicNetwork string `gcfg:"public-network"` PublicNetwork string `gcfg:"public-network"`
} }
Disk struct {
DiskController string `dcfg:"diskcontroller"`
SCSIControllerType string `dcfg:"scsicontrollertype"`
}
} }
func readConfig(config io.Reader) (VSphereConfig, error) { func readConfig(config io.Reader) (VSphereConfig, error) {
...@@ -136,6 +145,12 @@ func newVSphere(cfg VSphereConfig) (*VSphere, error) { ...@@ -136,6 +145,12 @@ func newVSphere(cfg VSphereConfig) (*VSphere, error) {
return nil, err return nil, err
} }
if cfg.Disk.DiskController == "" {
cfg.Disk.DiskController = DefaultDiskController
}
if cfg.Disk.SCSIControllerType == "" {
cfg.Disk.SCSIControllerType = DefaultSCSIControllerType
}
vs := VSphere{ vs := VSphere{
cfg: &cfg, cfg: &cfg,
localInstanceID: id, localInstanceID: id,
...@@ -429,3 +444,292 @@ func (vs *VSphere) Routes() (cloudprovider.Routes, bool) { ...@@ -429,3 +444,292 @@ func (vs *VSphere) Routes() (cloudprovider.Routes, bool) {
func (vs *VSphere) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { func (vs *VSphere) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
return nameservers, searches return nameservers, searches
} }
func getVirtualMachineDevices(cfg *VSphereConfig, ctx context.Context, c *govmomi.Client, name string) (*object.VirtualMachine, object.VirtualDeviceList, *object.Datastore, error) {
// Create a new finder
f := find.NewFinder(c.Client, true)
// Fetch and set data center
dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
if err != nil {
return nil, nil, nil, err
}
f.SetDatacenter(dc)
// Find datastores
ds, err := f.Datastore(ctx, cfg.Global.Datastore)
if err != nil {
return nil, nil, nil, err
}
vm, err := f.VirtualMachine(ctx, name)
if err != nil {
return nil, nil, nil, err
}
// Get devices from VM
vmDevices, err := vm.Device(ctx)
if err != nil {
return nil, nil, nil, err
}
return vm, vmDevices, ds, nil
}
//cleaning up the controller
func cleanUpController(newSCSIController types.BaseVirtualDevice, vmDevices object.VirtualDeviceList, vm *object.VirtualMachine, ctx context.Context) error {
ctls := vmDevices.SelectByType(newSCSIController)
if len(ctls) < 1 {
return ErrNoDevicesFound
}
newScsi := ctls[len(ctls)-1]
err := vm.RemoveDevice(ctx, true, newScsi)
if err != nil {
return err
}
return nil
}
// Attaches given virtual disk volume to the compute running kubelet.
func (vs *VSphere) AttachDisk(vmDiskPath string, nodeName string) (diskID string, diskUUID string, err error) {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(vs.cfg, ctx)
if err != nil {
return "", "", err
}
defer c.Logout(ctx)
// Find virtual machine to attach disk to
var vSphereInstance string
if nodeName == "" {
vSphereInstance = vs.localInstanceID
} else {
vSphereInstance = nodeName
}
// Get VM device list
vm, vmDevices, ds, err := getVirtualMachineDevices(vs.cfg, ctx, c, vSphereInstance)
if err != nil {
return "", "", err
}
// find SCSI controller to attach the disk
var newSCSICreated bool = false
var newSCSIController types.BaseVirtualDevice
diskController, err := vmDevices.FindDiskController(vs.cfg.Disk.DiskController)
if err != nil {
// create a scsi controller if there is not one
newSCSIController, err := vmDevices.CreateSCSIController(vs.cfg.Disk.SCSIControllerType)
if err != nil {
glog.V(3).Infof("Cannot create new SCSI controller - %v", err)
return "", "", err
}
configNewSCSIController := newSCSIController.(types.BaseVirtualSCSIController).GetVirtualSCSIController()
hotAndRemove := true
configNewSCSIController.HotAddRemove = &hotAndRemove
configNewSCSIController.SharedBus = types.VirtualSCSISharing(types.VirtualSCSISharingNoSharing)
// add the scsi controller to virtual machine
err = vm.AddDevice(context.TODO(), newSCSIController)
if err != nil {
glog.V(3).Infof("Cannot add SCSI controller to vm - %v", err)
// attempt clean up of scsi controller
if vmDevices, err := vm.Device(ctx); err == nil {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
return "", "", err
}
// verify scsi controller in virtual machine
vmDevices, err = vm.Device(ctx)
if err != nil {
//cannot cleanup if there is no device list
return "", "", err
}
if diskController, err = vmDevices.FindDiskController(vs.cfg.Disk.DiskController); err != nil {
glog.V(3).Infof("Cannot find disk controller - %v", err)
// attempt clean up of scsi controller
cleanUpController(newSCSIController, vmDevices, vm, ctx)
return "", "", err
}
newSCSICreated = true
}
disk := vmDevices.CreateDisk(diskController, ds.Reference(), vmDiskPath)
backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
backing.DiskMode = string(types.VirtualDiskModeIndependent_persistent)
disk = vmDevices.ChildDisk(disk)
// Attach disk to the VM
err = vm.AddDevice(ctx, disk)
if err != nil {
glog.V(3).Infof("Cannot add disk to the vm - %v", err)
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
return "", "", err
}
vmDevices, err = vm.Device(ctx)
if err != nil {
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
return "", "", err
}
devices := vmDevices.SelectByType(disk)
if len(devices) < 1 {
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
return "", "", ErrNoDevicesFound
}
// get new disk id
newDevice := devices[len(devices)-1]
deviceName := devices.Name(newDevice)
// get device uuid
diskUUID, err = getVirtualDiskUUID(newDevice)
if err != nil {
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
vs.DetachDisk(deviceName, vSphereInstance)
return "", "", err
}
return deviceName, diskUUID, nil
}
func getVirtualDiskUUID(newDevice types.BaseVirtualDevice) (string, error) {
vd := newDevice.GetVirtualDevice()
if b, ok := vd.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
uuidWithNoHypens := strings.Replace(b.Uuid, "-", "", -1)
return uuidWithNoHypens, nil
}
return "", ErrNoDiskUUIDFound
}
// Detaches given virtual disk volume from the compute running kubelet.
func (vs *VSphere) DetachDisk(diskID string, nodeName string) error {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(vs.cfg, ctx)
if err != nil {
return err
}
defer c.Logout(ctx)
// Find VM to detach disk from
var vSphereInstance string
if nodeName == "" {
vSphereInstance = vs.localInstanceID
} else {
vSphereInstance = nodeName
}
vm, vmDevices, _, err := getVirtualMachineDevices(vs.cfg, ctx, c, vSphereInstance)
if err != nil {
return err
}
// Remove disk from VM
device := vmDevices.Find(diskID)
if device == nil {
return fmt.Errorf("device '%s' not found", diskID)
}
err = vm.RemoveDevice(ctx, false, device)
if err != nil {
return err
}
return nil
}
// Create a volume of given size (in KiB).
func (vs *VSphere) CreateVolume(name string, size int, tags *map[string]string) (volumePath string, err error) {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(vs.cfg, ctx)
if err != nil {
return "", err
}
defer c.Logout(ctx)
// Create a new finder
f := find.NewFinder(c.Client, true)
// Fetch and set data center
dc, err := f.Datacenter(ctx, vs.cfg.Global.Datacenter)
f.SetDatacenter(dc)
// Create a virtual disk manager
vmDiskPath := "[" + vs.cfg.Global.Datastore + "] " + name + ".vmdk"
virtualDiskManager := object.NewVirtualDiskManager(c.Client)
// Create specification for new virtual disk
vmDiskSpec := &types.FileBackedVirtualDiskSpec{
VirtualDiskSpec: types.VirtualDiskSpec{
AdapterType: (*tags)["adapterType"],
DiskType: (*tags)["diskType"],
},
CapacityKb: int64(size),
}
// Create virtual disk
task, err := virtualDiskManager.CreateVirtualDisk(ctx, vmDiskPath, dc, vmDiskSpec)
if err != nil {
return "", err
}
err = task.Wait(ctx)
if err != nil {
return "", err
}
return vmDiskPath, nil
}
// Deletes a volume given volume name.
func (vs *VSphere) DeleteVolume(vmDiskPath string) error {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(vs.cfg, ctx)
if err != nil {
return err
}
defer c.Logout(ctx)
// Create a new finder
f := find.NewFinder(c.Client, true)
// Fetch and set data center
dc, err := f.Datacenter(ctx, vs.cfg.Global.Datacenter)
f.SetDatacenter(dc)
// Create a virtual disk manager
virtualDiskManager := object.NewVirtualDiskManager(c.Client)
// Delete virtual disk
task, err := virtualDiskManager.DeleteVirtualDisk(ctx, vmDiskPath, dc)
if err != nil {
return err
}
return task.Wait(ctx)
}
...@@ -24,6 +24,7 @@ import ( ...@@ -24,6 +24,7 @@ import (
"testing" "testing"
"golang.org/x/net/context" "golang.org/x/net/context"
"k8s.io/kubernetes/pkg/util/rand"
) )
func configFromEnv() (cfg VSphereConfig, ok bool) { func configFromEnv() (cfg VSphereConfig, ok bool) {
...@@ -35,6 +36,7 @@ func configFromEnv() (cfg VSphereConfig, ok bool) { ...@@ -35,6 +36,7 @@ func configFromEnv() (cfg VSphereConfig, ok bool) {
cfg.Global.Password = os.Getenv("VSPHERE_PASSWORD") cfg.Global.Password = os.Getenv("VSPHERE_PASSWORD")
cfg.Global.Datacenter = os.Getenv("VSPHERE_DATACENTER") cfg.Global.Datacenter = os.Getenv("VSPHERE_DATACENTER")
cfg.Network.PublicNetwork = os.Getenv("VSPHERE_PUBLIC_NETWORK") cfg.Network.PublicNetwork = os.Getenv("VSPHERE_PUBLIC_NETWORK")
cfg.Global.Datastore = os.Getenv("VSPHERE_DATASTORE")
if os.Getenv("VSPHERE_INSECURE") != "" { if os.Getenv("VSPHERE_INSECURE") != "" {
InsecureFlag, err = strconv.ParseBool(os.Getenv("VSPHERE_INSECURE")) InsecureFlag, err = strconv.ParseBool(os.Getenv("VSPHERE_INSECURE"))
} else { } else {
...@@ -187,3 +189,54 @@ func TestInstances(t *testing.T) { ...@@ -187,3 +189,54 @@ func TestInstances(t *testing.T) {
} }
t.Logf("Found NodeAddresses(%s) = %s\n", srvs[0], addrs) t.Logf("Found NodeAddresses(%s) = %s\n", srvs[0], addrs)
} }
func TestVolumes(t *testing.T) {
cfg, ok := configFromEnv()
if !ok {
t.Skipf("No config found in environment")
}
vs, err := newVSphere(cfg)
if err != nil {
t.Fatalf("Failed to construct/authenticate vSphere: %s", err)
}
i, ok := vs.Instances()
if !ok {
t.Fatalf("Instances() returned false")
}
srvs, err := i.List("*")
if err != nil {
t.Fatalf("Instances.List() failed: %s", err)
}
if len(srvs) == 0 {
t.Fatalf("Instances.List() returned zero servers")
}
tags := map[string]string{
"adapterType": "lsiLogic",
"diskType": "thin",
}
volPath, err := vs.CreateVolume("kubernetes-test-volume-"+rand.String(10), 1*1024*1024, &tags)
if err != nil {
t.Fatalf("Cannot create a new VMDK volume: %v", err)
}
diskID, _, err := vs.AttachDisk(volPath, "")
if err != nil {
t.Fatalf("Cannot attach volume(%s) to VM(%s): %v", volPath, srvs[0], err)
}
err = vs.DetachDisk(diskID, "")
if err != nil {
t.Fatalf("Cannot detach disk(%s) from VM(%s): %v", diskID, srvs[0], err)
}
// todo: Deleting a volume after detach currently not working through API or UI (vSphere)
// err = vs.DeleteVolume(volPath)
// if err != nil {
// t.Fatalf("Cannot delete VMDK volume %s: %v", volPath, err)
// }
}
...@@ -57,7 +57,8 @@ func GetAllFSTypesAsSet() sets.String { ...@@ -57,7 +57,8 @@ func GetAllFSTypesAsSet() sets.String {
string(extensions.CephFS), string(extensions.CephFS),
string(extensions.DownwardAPI), string(extensions.DownwardAPI),
string(extensions.FC), string(extensions.FC),
string(extensions.ConfigMap)) string(extensions.ConfigMap),
string(extensions.VsphereVolume))
return fstypes return fstypes
} }
...@@ -102,6 +103,8 @@ func GetVolumeFSType(v api.Volume) (extensions.FSType, error) { ...@@ -102,6 +103,8 @@ func GetVolumeFSType(v api.Volume) (extensions.FSType, error) {
return extensions.AzureFile, nil return extensions.AzureFile, nil
case v.ConfigMap != nil: case v.ConfigMap != nil:
return extensions.ConfigMap, nil return extensions.ConfigMap, nil
case v.VsphereVolume != nil:
return extensions.VsphereVolume, nil
} }
return "", fmt.Errorf("unknown volume type for volume: %#v", v) return "", fmt.Errorf("unknown volume type for volume: %#v", v)
......
/*
Copyright 2016 The Kubernetes Authors 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 vsphere_volume
import (
"fmt"
"os"
"path"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/mount"
utiltesting "k8s.io/kubernetes/pkg/util/testing"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
)
func TestCanSupport(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("vsphereVolumeTest")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPluginByName("kubernetes.io/vsphere-volume")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.Name() != "kubernetes.io/vsphere-volume" {
t.Errorf("Wrong name: %s", plug.Name())
}
if !plug.CanSupport(&volume.Spec{Volume: &api.Volume{VolumeSource: api.VolumeSource{VsphereVolume: &api.VsphereVirtualDiskVolumeSource{}}}}) {
t.Errorf("Expected true")
}
if !plug.CanSupport(&volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{VsphereVolume: &api.VsphereVirtualDiskVolumeSource{}}}}}) {
t.Errorf("Expected true")
}
}
type fakePDManager struct {
attachCalled bool
detachCalled bool
}
func getFakeDeviceName(host volume.VolumeHost, volPath string) string {
return path.Join(host.GetPluginDir(vsphereVolumePluginName), "device", volPath)
}
func (fake *fakePDManager) AttachDisk(b *vsphereVolumeMounter, globalPDPath string) error {
fakeDeviceName := getFakeDeviceName(b.plugin.host, b.volPath)
err := os.MkdirAll(fakeDeviceName, 0750)
if err != nil {
return err
}
fake.attachCalled = true
// Simulate the global mount so that the fakeMounter returns the
// expected number of mounts for the attached disk.
err = b.mounter.Mount(fakeDeviceName, globalPDPath, "", []string{"bind"})
if err != nil {
return err
}
return nil
}
func (fake *fakePDManager) DetachDisk(v *vsphereVolumeUnmounter) error {
globalPath := makeGlobalPDPath(v.plugin.host, v.volPath)
fakeDeviceName := getFakeDeviceName(v.plugin.host, v.volPath)
err := v.mounter.Unmount(globalPath)
if err != nil {
return err
}
// "Detach" the fake "device"
err = os.RemoveAll(fakeDeviceName)
if err != nil {
return err
}
fake.detachCalled = true
return nil
}
func (fake *fakePDManager) CreateVolume(v *vsphereVolumeProvisioner) (vmDiskPath string, volumeSizeKB int, err error) {
return "[local] test-volume-name.vmdk", 100, nil
}
func (fake *fakePDManager) DeleteVolume(vd *vsphereVolumeDeleter) error {
if vd.volPath != "[local] test-volume-name.vmdk" {
return fmt.Errorf("Deleter got unexpected volume path: %s", vd.volPath)
}
return nil
}
func TestPlugin(t *testing.T) {
// Initial setup to test volume plugin
tmpDir, err := utiltesting.MkTmpdir("vsphereVolumeTest")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPluginByName("kubernetes.io/vsphere-volume")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
spec := &api.Volume{
Name: "vol1",
VolumeSource: api.VolumeSource{
VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
VolumePath: "[local] test-volume-name.vmdk",
FSType: "ext4",
},
},
}
// Test Mounter
fakeManager := &fakePDManager{}
fakeMounter := &mount.FakeMounter{}
mounter, err := plug.(*vsphereVolumePlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), fakeManager, fakeMounter)
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Errorf("Got a nil Mounter")
}
mntPath := path.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~vsphere-volume/vol1")
path := mounter.GetPath()
if path != mntPath {
t.Errorf("Got unexpected path: %s", path)
}
if err := mounter.SetUp(nil); err != nil {
t.Errorf("Expected success, got: %v", err)
}
if !fakeManager.attachCalled {
t.Errorf("Attach watch not called")
}
// Test Unmounter
fakeManager = &fakePDManager{}
unmounter, err := plug.(*vsphereVolumePlugin).newUnmounterInternal("vol1", types.UID("poduid"), fakeManager, fakeMounter)
if err != nil {
t.Errorf("Failed to make a new Unmounter: %v", err)
}
if unmounter == nil {
t.Errorf("Got a nil Unmounter")
}
if err := unmounter.TearDown(); err != nil {
t.Errorf("Expected success, got: %v", err)
}
if _, err := os.Stat(path); err == nil {
t.Errorf("TearDown() failed, volume path still exists: %s", path)
} else if !os.IsNotExist(err) {
t.Errorf("SetUp() failed: %v", err)
}
if !fakeManager.detachCalled {
t.Errorf("Detach watch not called")
}
// Test Provisioner
cap := resource.MustParse("100Mi")
options := volume.VolumeOptions{
Capacity: cap,
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
},
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
}
provisioner, err := plug.(*vsphereVolumePlugin).newProvisionerInternal(options, &fakePDManager{})
persistentSpec, err := provisioner.Provision()
if err != nil {
t.Errorf("Provision() failed: %v", err)
}
if persistentSpec.Spec.PersistentVolumeSource.VsphereVolume.VolumePath != "[local] test-volume-name.vmdk" {
t.Errorf("Provision() returned unexpected path %s", persistentSpec.Spec.PersistentVolumeSource.VsphereVolume.VolumePath)
}
cap = persistentSpec.Spec.Capacity[api.ResourceStorage]
size := cap.Value()
if size != 100*1024 {
t.Errorf("Provision() returned unexpected volume size: %v", size)
}
// Test Deleter
volSpec := &volume.Spec{
PersistentVolume: persistentSpec,
}
deleter, err := plug.(*vsphereVolumePlugin).newDeleterInternal(volSpec, &fakePDManager{})
err = deleter.Delete()
if err != nil {
t.Errorf("Deleter() failed: %v", err)
}
}
/*
Copyright 2016 The Kubernetes Authors 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 vsphere_volume
import (
"errors"
"io/ioutil"
"os"
"path"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/keymutex"
"k8s.io/kubernetes/pkg/volume"
)
const (
maxRetries = 10
)
// Singleton key mutex for keeping attach/detach operations for the same PD atomic
var attachDetachMutex = keymutex.NewKeyMutex()
type VsphereDiskUtil struct{}
// Attaches a disk to the current kubelet.
// Mounts the disk to it's global path.
func (util *VsphereDiskUtil) AttachDisk(vm *vsphereVolumeMounter, globalPDPath string) error {
options := []string{}
// Block execution until any pending attach/detach operations for this PD have completed
attachDetachMutex.LockKey(vm.volPath)
defer attachDetachMutex.UnlockKey(vm.volPath)
cloud, err := vm.plugin.getCloudProvider()
if err != nil {
return err
}
diskID, diskUUID, attachError := cloud.AttachDisk(vm.volPath, "")
if attachError != nil {
return err
} else if diskUUID == "" {
return errors.New("Disk UUID has no value")
}
// diskID for detach Disk
vm.diskID = diskID
var devicePath string
numTries := 0
for {
devicePath = verifyDevicePath(diskUUID)
// probe the attached vol so that symlink in /dev/disk/by-id is created
probeAttachedVolume()
_, err := os.Stat(devicePath)
if err == nil {
break
}
if err != nil && !os.IsNotExist(err) {
return err
}
numTries++
if numTries == maxRetries {
return errors.New("Could not attach disk: Timeout after 60s")
}
time.Sleep(time.Second * 60)
}
notMnt, err := vm.mounter.IsLikelyNotMountPoint(globalPDPath)
if err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(globalPDPath, 0750); err != nil {
return err
}
notMnt = true
} else {
return err
}
}
if notMnt {
err = vm.diskMounter.FormatAndMount(devicePath, globalPDPath, vm.fsType, options)
if err != nil {
os.Remove(globalPDPath)
return err
}
glog.V(2).Infof("Safe mount successful: %q\n", devicePath)
}
return nil
}
func verifyDevicePath(diskUUID string) string {
files, _ := ioutil.ReadDir("/dev/disk/by-id/")
for _, f := range files {
// TODO: should support other controllers
if strings.Contains(f.Name(), "scsi-") {
devID := f.Name()[len("scsi-"):len(f.Name())]
if strings.Contains(diskUUID, devID) {
glog.V(4).Infof("Found disk attached as %q; full devicepath: %s\n", f.Name(), path.Join("/dev/disk/by-id/", f.Name()))
return path.Join("/dev/disk/by-id/", f.Name())
}
}
}
glog.Warningf("Failed to find device for the diskid: %q\n", diskUUID)
return ""
}
func probeAttachedVolume() error {
executor := exec.New()
args := []string{"trigger"}
cmd := executor.Command("/usr/bin/udevadm", args...)
_, err := cmd.CombinedOutput()
if err != nil {
glog.Errorf("error running udevadm trigger %v\n", err)
return err
}
glog.V(4).Infof("Successfully probed all attachments")
return nil
}
// Unmounts the device and detaches the disk from the kubelet's host machine.
func (util *VsphereDiskUtil) DetachDisk(vu *vsphereVolumeUnmounter) error {
// Block execution until any pending attach/detach operations for this PD have completed
attachDetachMutex.LockKey(vu.volPath)
defer attachDetachMutex.UnlockKey(vu.volPath)
globalPDPath := makeGlobalPDPath(vu.plugin.host, vu.volPath)
if err := vu.mounter.Unmount(globalPDPath); err != nil {
return err
}
if err := os.Remove(globalPDPath); err != nil {
return err
}
glog.V(2).Infof("Successfully unmounted main device: %s\n", globalPDPath)
cloud, err := vu.plugin.getCloudProvider()
if err != nil {
return err
}
if err = cloud.DetachDisk(vu.diskID, ""); err != nil {
return err
}
glog.V(2).Infof("Successfully detached vSphere volume %s", vu.volPath)
return nil
}
// CreateVolume creates a vSphere volume.
func (util *VsphereDiskUtil) CreateVolume(v *vsphereVolumeProvisioner) (vmDiskPath string, volumeSizeKB int, err error) {
cloud, err := v.plugin.getCloudProvider()
if err != nil {
return "", 0, err
}
volSizeBytes := v.options.Capacity.Value()
// vSphere works with kilobytes, convert to KiB with rounding up
volSizeKB := int(volume.RoundUpSize(volSizeBytes, 1024))
name := volume.GenerateVolumeName(v.options.ClusterName, v.options.PVName, 255)
vmDiskPath, err = cloud.CreateVolume(name, volSizeKB, v.options.CloudTags)
if err != nil {
glog.V(2).Infof("Error creating vsphere volume: %v", err)
return "", 0, err
}
glog.V(2).Infof("Successfully created vsphere volume %s", name)
return vmDiskPath, volSizeKB, nil
}
// DeleteVolume deletes a vSphere volume.
func (util *VsphereDiskUtil) DeleteVolume(vd *vsphereVolumeDeleter) error {
cloud, err := vd.plugin.getCloudProvider()
if err != nil {
return err
}
if err = cloud.DeleteVolume(vd.volPath); err != nil {
glog.V(2).Infof("Error deleting vsphere volume %s: %v", vd.volPath, err)
return err
}
glog.V(2).Infof("Successfully deleted vsphere volume %s", vd.volPath)
return nil
}
...@@ -9,15 +9,9 @@ build: ...@@ -9,15 +9,9 @@ build:
- GOVC_INSECURE=1 - GOVC_INSECURE=1
- VCA=1 - VCA=1
commands: commands:
- go get golang.org/x/tools/cmd/vet - make all install
- go get golang.org/x/tools/cmd/goimports
- go get github.com/davecgh/go-spew/spew
- go get
- make all
- make install
- git clone https://github.com/sstephenson/bats.git /tmp/bats - git clone https://github.com/sstephenson/bats.git /tmp/bats
- /tmp/bats/install.sh /usr/local - /tmp/bats/install.sh /usr/local
- apt-get -qq update && apt-get install -yqq uuid-runtime bsdmainutils jq - apt-get -qq update && apt-get install -yqq uuid-runtime bsdmainutils jq
- govc/test/images/update.sh - govc/test/images/update.sh
- bats govc/test - bats govc/test
- govc/test/clean.sh
sudo: false sudo: false
language: go language: go
go: 1.4
go:
- 1.6
before_install: before_install:
- go get golang.org/x/tools/cmd/vet - make vendor
- go get golang.org/x/tools/cmd/goimports
- go get github.com/davecgh/go-spew/spew
script: script:
- make check test - make check test
# changelog # changelog
### 0.6.2 (2016-05-11)
* Get complete file details in Datastore.Stat
* SOAP decoding fixes
* Add VirtualMachine.RemoveAllSnapshot
### 0.6.1 (2016-04-30)
* Fix mo.Entity interface
### 0.6.0 (2016-04-29)
* Add Common.Rename method
* Add mo.Entity interface
* Add OptionManager
* Add Finder.FolderList method
* Add VirtualMachine.WaitForNetIP method
* Add VirtualMachine.RevertToSnapshot method
* Add Datastore.Download method
### 0.5.0 (2016-03-30) ### 0.5.0 (2016-03-30)
Generated fields using xsd type 'int' change to Go type 'int32' Generated fields using xsd type 'int' change to Go type 'int32'
......
...@@ -7,11 +7,13 @@ Alvaro Miranda <kikitux@gmail.com> ...@@ -7,11 +7,13 @@ Alvaro Miranda <kikitux@gmail.com>
Amit Bathla <abathla@.vmware.com> Amit Bathla <abathla@.vmware.com>
Andrew Chin <andrew@andrewtchin.com> Andrew Chin <andrew@andrewtchin.com>
Arran Walker <arran.walker@zopa.com> Arran Walker <arran.walker@zopa.com>
Austin Parker <aparker@apprenda.com>
Bob Killen <killen.bob@gmail.com> Bob Killen <killen.bob@gmail.com>
Bruce Downs <bdowns@vmware.com> Bruce Downs <bdowns@vmware.com>
Clint Greenwood <cgreenwood@vmware.com> <clint.greenwood@gmail.com> Clint Greenwood <cgreenwood@vmware.com> <clint.greenwood@gmail.com>
Cédric Blomart <cblomart@gmail.com> Cédric Blomart <cblomart@gmail.com>
Danny Lockard <danny.lockard@banno.com> Danny Lockard <danny.lockard@banno.com>
Dave Tucker <dave@dtucker.co.uk>
Doug MacEachern <dougm@vmware.com> Doug MacEachern <dougm@vmware.com>
Eloy Coto <eloy.coto@gmail.com> Eloy Coto <eloy.coto@gmail.com>
Eric Yutao <eric.yutao@gmail.com> Eric Yutao <eric.yutao@gmail.com>
...@@ -28,6 +30,7 @@ Mevan Samaratunga <mevansam@gmail.com> ...@@ -28,6 +30,7 @@ Mevan Samaratunga <mevansam@gmail.com>
Pieter Noordhuis <pnoordhuis@vmware.com> <pcnoordhuis@gmail.com> Pieter Noordhuis <pnoordhuis@vmware.com> <pcnoordhuis@gmail.com>
runner.mei <runner.mei@gmail.com> runner.mei <runner.mei@gmail.com>
S.Çağlar Onur <conur@vmware.com> S.Çağlar Onur <conur@vmware.com>
Sergey Ignatov <sergey.ignatov@jetbrains.com>
Takaaki Furukawa <takaaki.frkw@gmail.com> <takaaki.furukawa@mail.rakuten.com> Takaaki Furukawa <takaaki.frkw@gmail.com> <takaaki.furukawa@mail.rakuten.com>
Steve Purcell <steve@sanityinc.com> Steve Purcell <steve@sanityinc.com>
Yang Yang <yangy@vmware.com> Yang Yang <yangy@vmware.com>
......
...@@ -4,7 +4,12 @@ all: check test ...@@ -4,7 +4,12 @@ all: check test
check: goimports govet check: goimports govet
goimports: vendor:
go get golang.org/x/tools/cmd/goimports
go get github.com/davecgh/go-spew/spew
go get golang.org/x/net/context
goimports: vendor
@echo checking go imports... @echo checking go imports...
@! goimports -d . 2>&1 | egrep -v '^$$' @! goimports -d . 2>&1 | egrep -v '^$$'
...@@ -12,9 +17,8 @@ govet: ...@@ -12,9 +17,8 @@ govet:
@echo checking go vet... @echo checking go vet...
@go tool vet -structtags=false -methods=false . @go tool vet -structtags=false -methods=false .
test: test: vendor
go get
go test -v $(TEST_OPTS) ./... go test -v $(TEST_OPTS) ./...
install: install: vendor
go install github.com/vmware/govmomi/govc go install github.com/vmware/govmomi/govc
...@@ -762,28 +762,42 @@ func (f *Finder) VirtualApp(ctx context.Context, path string) (*object.VirtualAp ...@@ -762,28 +762,42 @@ func (f *Finder) VirtualApp(ctx context.Context, path string) (*object.VirtualAp
return apps[0], nil return apps[0], nil
} }
func (f *Finder) Folder(ctx context.Context, path string) (*object.Folder, error) { func (f *Finder) FolderList(ctx context.Context, path string) ([]*object.Folder, error) {
mo, err := f.ManagedObjectList(ctx, path) es, err := f.ManagedObjectList(ctx, path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(mo) == 0 { var folders []*object.Folder
return nil, &NotFoundError{"folder", path}
}
if len(mo) > 1 { for _, e := range es {
return nil, &MultipleFoundError{"folder", path} switch o := e.Object.(type) {
case mo.Folder:
folder := object.NewFolder(f.client, o.Reference())
folder.InventoryPath = e.Path
folders = append(folders, folder)
case *object.Folder:
// RootFolder
folders = append(folders, o)
}
} }
ref := mo[0].Object.Reference() if len(folders) == 0 {
if ref.Type != "Folder" {
return nil, &NotFoundError{"folder", path} return nil, &NotFoundError{"folder", path}
} }
folder := object.NewFolder(f.client, ref) return folders, nil
}
func (f *Finder) Folder(ctx context.Context, path string) (*object.Folder, error) {
folders, err := f.FolderList(ctx, path)
if err != nil {
return nil, err
}
folder.InventoryPath = mo[0].Path if len(folders) > 1 {
return nil, &MultipleFoundError{"folder", path}
}
return folder, nil return folders[0], nil
} }
...@@ -69,3 +69,17 @@ func (c Common) Destroy(ctx context.Context) (*Task, error) { ...@@ -69,3 +69,17 @@ func (c Common) Destroy(ctx context.Context) (*Task, error) {
return NewTask(c.c, res.Returnval), nil return NewTask(c.c, res.Returnval), nil
} }
func (c Common) Rename(ctx context.Context, name string) (*Task, error) {
req := types.Rename_Task{
This: c.Reference(),
NewName: name,
}
res, err := methods.Rename_Task(ctx, c.c, &req)
if err != nil {
return nil, err
}
return NewTask(c.c, res.Returnval), nil
}
...@@ -220,7 +220,16 @@ func (d Datastore) UploadFile(ctx context.Context, file string, path string, par ...@@ -220,7 +220,16 @@ func (d Datastore) UploadFile(ctx context.Context, file string, path string, par
return d.Client().UploadFile(file, u, p) return d.Client().UploadFile(file, u, p)
} }
// DownloadFile via soap.Upload with an http service ticket // Download via soap.Download with an http service ticket
func (d Datastore) Download(ctx context.Context, path string, param *soap.Download) (io.ReadCloser, int64, error) {
u, p, err := d.downloadTicket(ctx, path, param)
if err != nil {
return nil, 0, err
}
return d.Client().Download(u, p)
}
// DownloadFile via soap.Download with an http service ticket
func (d Datastore) DownloadFile(ctx context.Context, path string, file string, param *soap.Download) error { func (d Datastore) DownloadFile(ctx context.Context, path string, file string, param *soap.Download) error {
u, p, err := d.downloadTicket(ctx, path, param) u, p, err := d.downloadTicket(ctx, path, param)
if err != nil { if err != nil {
...@@ -305,8 +314,10 @@ func (d Datastore) Stat(ctx context.Context, file string) (types.BaseFileInfo, e ...@@ -305,8 +314,10 @@ func (d Datastore) Stat(ctx context.Context, file string) (types.BaseFileInfo, e
spec := types.HostDatastoreBrowserSearchSpec{ spec := types.HostDatastoreBrowserSearchSpec{
Details: &types.FileQueryFlags{ Details: &types.FileQueryFlags{
FileType: true, FileType: true,
FileOwner: types.NewBool(true), // TODO: omitempty is generated, but seems to be required FileSize: true,
Modification: true,
FileOwner: types.NewBool(true),
}, },
MatchPattern: []string{path.Base(file)}, MatchPattern: []string{path.Base(file)},
} }
......
...@@ -37,7 +37,9 @@ func NewFolder(c *vim25.Client, ref types.ManagedObjectReference) *Folder { ...@@ -37,7 +37,9 @@ func NewFolder(c *vim25.Client, ref types.ManagedObjectReference) *Folder {
} }
func NewRootFolder(c *vim25.Client) *Folder { func NewRootFolder(c *vim25.Client) *Folder {
return NewFolder(c, c.ServiceContent.RootFolder) f := NewFolder(c, c.ServiceContent.RootFolder)
f.InventoryPath = "/"
return f
} }
func (f Folder) Children(ctx context.Context) ([]Reference, error) { func (f Folder) Children(ctx context.Context) ([]Reference, error) {
...@@ -196,3 +198,17 @@ func (f Folder) CreateDVS(ctx context.Context, spec types.DVSCreateSpec) (*Task, ...@@ -196,3 +198,17 @@ func (f Folder) CreateDVS(ctx context.Context, spec types.DVSCreateSpec) (*Task,
return NewTask(f.c, res.Returnval), nil return NewTask(f.c, res.Returnval), nil
} }
func (f Folder) MoveInto(ctx context.Context, list []types.ManagedObjectReference) (*Task, error) {
req := types.MoveIntoFolder_Task{
This: f.Reference(),
List: list,
}
res, err := methods.MoveIntoFolder_Task(ctx, f.c, &req)
if err != nil {
return nil, err
}
return NewTask(f.c, res.Returnval), nil
}
/*
Copyright (c) 2016 VMware, 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 object
import (
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostAccountManager struct {
Common
}
func NewHostAccountManager(c *vim25.Client, ref types.ManagedObjectReference) *HostAccountManager {
return &HostAccountManager{
Common: NewCommon(c, ref),
}
}
func (m HostAccountManager) Create(ctx context.Context, user *types.HostAccountSpec) error {
req := types.CreateUser{
This: m.Reference(),
User: user,
}
_, err := methods.CreateUser(ctx, m.Client(), &req)
return err
}
func (m HostAccountManager) Update(ctx context.Context, user *types.HostAccountSpec) error {
req := types.UpdateUser{
This: m.Reference(),
User: user,
}
_, err := methods.UpdateUser(ctx, m.Client(), &req)
return err
}
func (m HostAccountManager) Remove(ctx context.Context, userName string) error {
req := types.RemoveUser{
This: m.Reference(),
UserName: userName,
}
_, err := methods.RemoveUser(ctx, m.Client(), &req)
return err
}
...@@ -98,3 +98,25 @@ func (m HostConfigManager) VsanSystem(ctx context.Context) (*HostVsanSystem, err ...@@ -98,3 +98,25 @@ func (m HostConfigManager) VsanSystem(ctx context.Context) (*HostVsanSystem, err
return NewHostVsanSystem(m.c, *h.ConfigManager.VsanSystem), nil return NewHostVsanSystem(m.c, *h.ConfigManager.VsanSystem), nil
} }
func (m HostConfigManager) AccountManager(ctx context.Context) (*HostAccountManager, error) {
var h mo.HostSystem
err := m.Properties(ctx, m.Reference(), []string{"configManager.accountManager"}, &h)
if err != nil {
return nil, err
}
return NewHostAccountManager(m.c, *h.ConfigManager.AccountManager), nil
}
func (m HostConfigManager) OptionManager(ctx context.Context) (*OptionManager, error) {
var h mo.HostSystem
err := m.Properties(ctx, m.Reference(), []string{"configManager.advancedOption"}, &h)
if err != nil {
return nil, err
}
return NewOptionManager(m.c, *h.ConfigManager.AdvancedOption), nil
}
/*
Copyright (c) 2016 VMware, 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 object
import (
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type OptionManager struct {
Common
}
func NewOptionManager(c *vim25.Client, ref types.ManagedObjectReference) *OptionManager {
return &OptionManager{
Common: NewCommon(c, ref),
}
}
func (m OptionManager) Query(ctx context.Context, name string) ([]types.BaseOptionValue, error) {
req := types.QueryOptions{
This: m.Reference(),
Name: name,
}
res, err := methods.QueryOptions(ctx, m.Client(), &req)
if err != nil {
return nil, err
}
return res.Returnval, nil
}
func (m OptionManager) Update(ctx context.Context, value []types.BaseOptionValue) error {
req := types.UpdateOptions{
This: m.Reference(),
ChangedValue: value,
}
_, err := methods.UpdateOptions(ctx, m.Client(), &req)
return err
}
...@@ -63,7 +63,6 @@ func EthernetCardTypes() VirtualDeviceList { ...@@ -63,7 +63,6 @@ func EthernetCardTypes() VirtualDeviceList {
&types.VirtualVmxnet3{}, &types.VirtualVmxnet3{},
}).Select(func(device types.BaseVirtualDevice) bool { }).Select(func(device types.BaseVirtualDevice) bool {
c := device.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard() c := device.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard()
c.AddressType = string(types.VirtualEthernetCardMacTypeGenerated)
c.GetVirtualDevice().Key = -1 c.GetVirtualDevice().Key = -1
return true return true
}) })
......
...@@ -19,6 +19,7 @@ package object ...@@ -19,6 +19,7 @@ package object
import ( import (
"errors" "errors"
"fmt" "fmt"
"net"
"github.com/vmware/govmomi/property" "github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25"
...@@ -245,6 +246,77 @@ func (v VirtualMachine) WaitForIP(ctx context.Context) (string, error) { ...@@ -245,6 +246,77 @@ func (v VirtualMachine) WaitForIP(ctx context.Context) (string, error) {
return ip, nil return ip, nil
} }
// WaitForNetIP waits for the VM guest.net property to report an IP address for all VM NICs.
// Only consider IPv4 addresses if the v4 param is true.
// Returns a map with MAC address as the key and IP address list as the value.
func (v VirtualMachine) WaitForNetIP(ctx context.Context, v4 bool) (map[string][]string, error) {
macs := make(map[string][]string)
p := property.DefaultCollector(v.c)
// Wait for all NICs to have a MacAddress, which may not be generated yet.
err := property.Wait(ctx, p, v.Reference(), []string{"config.hardware.device"}, func(pc []types.PropertyChange) bool {
for _, c := range pc {
if c.Op != types.PropertyChangeOpAssign {
continue
}
devices := c.Val.(types.ArrayOfVirtualDevice).VirtualDevice
for _, device := range devices {
if nic, ok := device.(types.BaseVirtualEthernetCard); ok {
mac := nic.GetVirtualEthernetCard().MacAddress
if mac == "" {
return false
}
macs[mac] = nil
}
}
}
return true
})
err = property.Wait(ctx, p, v.Reference(), []string{"guest.net"}, func(pc []types.PropertyChange) bool {
for _, c := range pc {
if c.Op != types.PropertyChangeOpAssign {
continue
}
nics := c.Val.(types.ArrayOfGuestNicInfo).GuestNicInfo
for _, nic := range nics {
mac := nic.MacAddress
if mac == "" || nic.IpConfig == nil {
continue
}
for _, ip := range nic.IpConfig.IpAddress {
if _, ok := macs[mac]; !ok {
continue // Ignore any that don't correspond to a VM device
}
if v4 && net.ParseIP(ip.IpAddress).To4() == nil {
continue // Ignore non IPv4 address
}
macs[mac] = append(macs[mac], ip.IpAddress)
}
}
}
for _, ips := range macs {
if len(ips) == 0 {
return false
}
}
return true
})
if err != nil {
return nil, err
}
return macs, nil
}
// Device returns the VirtualMachine's config.hardware.device property. // Device returns the VirtualMachine's config.hardware.device property.
func (v VirtualMachine) Device(ctx context.Context) (VirtualDeviceList, error) { func (v VirtualMachine) Device(ctx context.Context) (VirtualDeviceList, error) {
var o mo.VirtualMachine var o mo.VirtualMachine
...@@ -336,8 +408,12 @@ func (v VirtualMachine) EditDevice(ctx context.Context, device ...types.BaseVirt ...@@ -336,8 +408,12 @@ func (v VirtualMachine) EditDevice(ctx context.Context, device ...types.BaseVirt
} }
// RemoveDevice removes the given devices on the VirtualMachine // RemoveDevice removes the given devices on the VirtualMachine
func (v VirtualMachine) RemoveDevice(ctx context.Context, device ...types.BaseVirtualDevice) error { func (v VirtualMachine) RemoveDevice(ctx context.Context, keepFiles bool, device ...types.BaseVirtualDevice) error {
return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationRemove, types.VirtualDeviceConfigSpecFileOperationDestroy, device...) fop := types.VirtualDeviceConfigSpecFileOperationDestroy
if keepFiles {
fop = ""
}
return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationRemove, fop, device...)
} }
// BootOptions returns the VirtualMachine's config.bootOptions property. // BootOptions returns the VirtualMachine's config.bootOptions property.
...@@ -400,6 +476,76 @@ func (v VirtualMachine) CreateSnapshot(ctx context.Context, name string, descrip ...@@ -400,6 +476,76 @@ func (v VirtualMachine) CreateSnapshot(ctx context.Context, name string, descrip
return NewTask(v.c, res.Returnval), nil return NewTask(v.c, res.Returnval), nil
} }
// RemoveAllSnapshot removes all snapshots of a virtual machine
func (v VirtualMachine) RemoveAllSnapshot(ctx context.Context, consolidate *bool) (*Task, error) {
req := types.RemoveAllSnapshots_Task{
This: v.Reference(),
Consolidate: consolidate,
}
res, err := methods.RemoveAllSnapshots_Task(ctx, v.c, &req)
if err != nil {
return nil, err
}
return NewTask(v.c, res.Returnval), nil
}
// RevertToSnapshot reverts to a named snapshot
func (v VirtualMachine) RevertToSnapshot(ctx context.Context, name string, suppressPowerOn bool) (*Task, error) {
var o mo.VirtualMachine
err := v.Properties(ctx, v.Reference(), []string{"snapshot"}, &o)
snapshotTree := o.Snapshot.RootSnapshotList
if len(snapshotTree) < 1 {
return nil, errors.New("No snapshots for this VM")
}
snapshot, err := traverseSnapshotInTree(snapshotTree, name)
if err != nil {
return nil, err
}
req := types.RevertToSnapshot_Task{
This: snapshot,
SuppressPowerOn: types.NewBool(suppressPowerOn),
}
res, err := methods.RevertToSnapshot_Task(ctx, v.c, &req)
if err != nil {
return nil, err
}
return NewTask(v.c, res.Returnval), nil
}
// traverseSnapshotInTree is a recursive function that will traverse a snapshot tree to find a given snapshot
func traverseSnapshotInTree(tree []types.VirtualMachineSnapshotTree, name string) (types.ManagedObjectReference, error) {
var o types.ManagedObjectReference
if tree == nil {
return o, errors.New("Snapshot tree is empty")
}
for _, s := range tree {
if s.Name == name {
o = s.Snapshot
break
} else {
childTree := s.ChildSnapshotList
var err error
o, err = traverseSnapshotInTree(childTree, name)
if err != nil {
return o, err
}
}
}
if o.Value == "" {
return o, errors.New("Snapshot not found")
}
return o, nil
}
// IsToolsRunning returns true if VMware Tools is currently running in the guest OS, and false otherwise. // IsToolsRunning returns true if VMware Tools is currently running in the guest OS, and false otherwise.
func (v VirtualMachine) IsToolsRunning(ctx context.Context) (bool, error) { func (v VirtualMachine) IsToolsRunning(ctx context.Context) (bool, error) {
var o mo.VirtualMachine var o mo.VirtualMachine
......
/*
Copyright (c) 2016 VMware, 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 mo
// Entity is the interface that is implemented by all managed objects
// that extend ManagedEntity.
type Entity interface {
Reference
Entity() *ManagedEntity
}
...@@ -130,6 +130,10 @@ type ComputeResource struct { ...@@ -130,6 +130,10 @@ type ComputeResource struct {
ConfigurationEx types.BaseComputeResourceConfigInfo `mo:"configurationEx"` ConfigurationEx types.BaseComputeResourceConfigInfo `mo:"configurationEx"`
} }
func (m *ComputeResource) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() { func init() {
t["ComputeResource"] = reflect.TypeOf((*ComputeResource)(nil)).Elem() t["ComputeResource"] = reflect.TypeOf((*ComputeResource)(nil)).Elem()
} }
...@@ -187,6 +191,10 @@ type Datacenter struct { ...@@ -187,6 +191,10 @@ type Datacenter struct {
Configuration types.DatacenterConfigInfo `mo:"configuration"` Configuration types.DatacenterConfigInfo `mo:"configuration"`
} }
func (m *Datacenter) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() { func init() {
t["Datacenter"] = reflect.TypeOf((*Datacenter)(nil)).Elem() t["Datacenter"] = reflect.TypeOf((*Datacenter)(nil)).Elem()
} }
...@@ -203,6 +211,10 @@ type Datastore struct { ...@@ -203,6 +211,10 @@ type Datastore struct {
IormConfiguration *types.StorageIORMInfo `mo:"iormConfiguration"` IormConfiguration *types.StorageIORMInfo `mo:"iormConfiguration"`
} }
func (m *Datastore) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() { func init() {
t["Datastore"] = reflect.TypeOf((*Datastore)(nil)).Elem() t["Datastore"] = reflect.TypeOf((*Datastore)(nil)).Elem()
} }
...@@ -255,6 +267,10 @@ type DistributedVirtualSwitch struct { ...@@ -255,6 +267,10 @@ type DistributedVirtualSwitch struct {
Runtime *types.DVSRuntimeInfo `mo:"runtime"` Runtime *types.DVSRuntimeInfo `mo:"runtime"`
} }
func (m *DistributedVirtualSwitch) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() { func init() {
t["DistributedVirtualSwitch"] = reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem() t["DistributedVirtualSwitch"] = reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem()
} }
...@@ -359,6 +375,10 @@ type Folder struct { ...@@ -359,6 +375,10 @@ type Folder struct {
ChildEntity []types.ManagedObjectReference `mo:"childEntity"` ChildEntity []types.ManagedObjectReference `mo:"childEntity"`
} }
func (m *Folder) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() { func init() {
t["Folder"] = reflect.TypeOf((*Folder)(nil)).Elem() t["Folder"] = reflect.TypeOf((*Folder)(nil)).Elem()
} }
...@@ -878,6 +898,10 @@ type HostSystem struct { ...@@ -878,6 +898,10 @@ type HostSystem struct {
SystemResources *types.HostSystemResourceInfo `mo:"systemResources"` SystemResources *types.HostSystemResourceInfo `mo:"systemResources"`
} }
func (m *HostSystem) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() { func init() {
t["HostSystem"] = reflect.TypeOf((*HostSystem)(nil)).Elem() t["HostSystem"] = reflect.TypeOf((*HostSystem)(nil)).Elem()
} }
...@@ -1117,6 +1141,10 @@ type Network struct { ...@@ -1117,6 +1141,10 @@ type Network struct {
Vm []types.ManagedObjectReference `mo:"vm"` Vm []types.ManagedObjectReference `mo:"vm"`
} }
func (m *Network) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() { func init() {
t["Network"] = reflect.TypeOf((*Network)(nil)).Elem() t["Network"] = reflect.TypeOf((*Network)(nil)).Elem()
} }
...@@ -1286,6 +1314,10 @@ type ResourcePool struct { ...@@ -1286,6 +1314,10 @@ type ResourcePool struct {
ChildConfiguration []types.ResourceConfigSpec `mo:"childConfiguration"` ChildConfiguration []types.ResourceConfigSpec `mo:"childConfiguration"`
} }
func (m *ResourcePool) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() { func init() {
t["ResourcePool"] = reflect.TypeOf((*ResourcePool)(nil)).Elem() t["ResourcePool"] = reflect.TypeOf((*ResourcePool)(nil)).Elem()
} }
...@@ -1551,6 +1583,10 @@ type VirtualMachine struct { ...@@ -1551,6 +1583,10 @@ type VirtualMachine struct {
GuestHeartbeatStatus types.ManagedEntityStatus `mo:"guestHeartbeatStatus"` GuestHeartbeatStatus types.ManagedEntityStatus `mo:"guestHeartbeatStatus"`
} }
func (m *VirtualMachine) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() { func init() {
t["VirtualMachine"] = reflect.TypeOf((*VirtualMachine)(nil)).Elem() t["VirtualMachine"] = reflect.TypeOf((*VirtualMachine)(nil)).Elem()
} }
......
...@@ -189,12 +189,20 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) { ...@@ -189,12 +189,20 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
npv := reflect.New(pt) npv := reflect.New(pt)
npv.Elem().Set(pv) npv.Elem().Set(pv)
pv = npv pv = npv
pt = pv.Type()
} else { } else {
panic(fmt.Sprintf("type %s doesn't implement %s", pt.Name(), rt.Name())) panic(fmt.Sprintf("type %s doesn't implement %s", pt.Name(), rt.Name()))
} }
} }
rv.Set(pv) if pt.AssignableTo(rt) {
rv.Set(pv)
} else if rt.ConvertibleTo(pt) {
rv.Set(pv.Convert(rt))
} else {
panic(fmt.Sprintf("cannot assign %s (%s) to %s (%s)", rt.Name(), rt.Kind(), pt.Name(), pt.Kind()))
}
return return
} }
......
...@@ -428,23 +428,12 @@ var DefaultDownload = Download{ ...@@ -428,23 +428,12 @@ var DefaultDownload = Download{
Method: "GET", Method: "GET",
} }
// DownloadFile GETs the given URL to a local file // Download GETs the remote file from the given URL
func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error { func (c *Client) Download(u *url.URL, param *Download) (io.ReadCloser, int64, error) {
var err error
if param == nil {
param = &DefaultDownload
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
req, err := http.NewRequest(param.Method, u.String(), nil) req, err := http.NewRequest(param.Method, u.String(), nil)
if err != nil { if err != nil {
return err return nil, 0, err
} }
if param.Ticket != nil { if param.Ticket != nil {
...@@ -453,11 +442,9 @@ func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error { ...@@ -453,11 +442,9 @@ func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error {
res, err := c.Client.Do(req) res, err := c.Client.Do(req)
if err != nil { if err != nil {
return err return nil, 0, err
} }
defer res.Body.Close()
switch res.StatusCode { switch res.StatusCode {
case http.StatusOK: case http.StatusOK:
default: default:
...@@ -465,12 +452,37 @@ func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error { ...@@ -465,12 +452,37 @@ func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error {
} }
if err != nil { if err != nil {
return nil, 0, err
}
var r io.ReadCloser = res.Body
return r, res.ContentLength, nil
}
// DownloadFile GETs the given URL to a local file
func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error {
var err error
if param == nil {
param = &DefaultDownload
}
rc, contentLength, err := c.Download(u, param)
if err != nil {
return err return err
} }
defer rc.Close()
var r io.Reader = rc
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
var r io.Reader = res.Body
if param.Progress != nil { if param.Progress != nil {
pr := progress.NewReader(param.Progress, res.Body, res.ContentLength) pr := progress.NewReader(param.Progress, r, contentLength)
r = pr r = pr
// Mark progress reader as done when returning from this function. // Mark progress reader as done when returning from this function.
......
...@@ -271,9 +271,19 @@ var ( ...@@ -271,9 +271,19 @@ var (
// Find reflect.Type for an element's type attribute. // Find reflect.Type for an element's type attribute.
func (p *Decoder) typeForElement(val reflect.Value, start *StartElement) reflect.Type { func (p *Decoder) typeForElement(val reflect.Value, start *StartElement) reflect.Type {
t := "" t := ""
for _, a := range start.Attr { for i, a := range start.Attr {
if a.Name == xmlSchemaInstance { if a.Name == xmlSchemaInstance {
t = a.Value t = a.Value
// HACK: ensure xsi:type is last in the list to avoid using that value for
// a "type" attribute, such as ManagedObjectReference.Type for example.
// Note that xsi:type is already the last attribute in VC/ESX responses.
// This is only an issue with govmomi simulator generated responses.
// Proper fix will require finding a few needles in this xml package haystack.
x := len(start.Attr) - 1
if i != x {
start.Attr[i] = start.Attr[x]
start.Attr[x] = a
}
break break
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment