diff -uNr --no-dereference kubernetes-original/api/swagger-spec/apps_v1alpha1.json kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/apps_v1alpha1.json --- kubernetes-original/api/swagger-spec/apps_v1alpha1.json 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/apps_v1alpha1.json 2018-04-30 21:11:51.000000000 +0000 @@ -1459,6 +1459,10 @@ "photonPersistentDisk": { "$ref": "v1.PhotonPersistentDiskVolumeSource", "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" + }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" } } }, @@ -2105,6 +2109,23 @@ }, "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.CascadeDiskVolumeSource": { + "id": "v1.CascadeDiskVolumeSource", + "description": "Represents a Cascade persistent disk resource.", + "required": [ + "diskID" + ], + "properties": { + "diskID": { + "type": "string", + "description": "ID that identifies Cascade persistent disk" + }, + "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." } } diff -uNr --no-dereference kubernetes-original/api/swagger-spec/apps_v1beta1.json kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/apps_v1beta1.json --- kubernetes-original/api/swagger-spec/apps_v1beta1.json 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/apps_v1beta1.json 2018-04-30 21:11:51.000000000 +0000 @@ -4479,6 +4479,10 @@ "$ref": "v1.PhotonPersistentDiskVolumeSource", "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" + }, "projected": { "$ref": "v1.ProjectedVolumeSource", "description": "Items for all in one resources secrets, configmaps, and downward API" @@ -5202,6 +5206,23 @@ }, "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.CascadeDiskVolumeSource": { + "id": "v1.CascadeDiskVolumeSource", + "description": "Represents a Cascade persistent disk resource.", + "required": [ + "diskID" + ], + "properties": { + "diskID": { + "type": "string", + "description": "ID that identifies Cascade persistent disk" + }, + "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." } } diff -uNr --no-dereference kubernetes-original/api/swagger-spec/apps_v1beta2.json kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/apps_v1beta2.json --- kubernetes-original/api/swagger-spec/apps_v1beta2.json 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/apps_v1beta2.json 2018-04-30 21:11:51.000000000 +0000 @@ -6845,6 +6845,10 @@ "$ref": "v1.PhotonPersistentDiskVolumeSource", "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" + }, "projected": { "$ref": "v1.ProjectedVolumeSource", "description": "Items for all in one resources secrets, configmaps, and downward API" @@ -7568,6 +7572,23 @@ }, "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.CascadeDiskVolumeSource": { + "id": "v1.CascadeDiskVolumeSource", + "description": "Represents a Cascade persistent disk resource.", + "required": [ + "diskID" + ], + "properties": { + "diskID": { + "type": "string", + "description": "ID that identifies Cascade persistent disk" + }, + "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." } } diff -uNr --no-dereference kubernetes-original/api/swagger-spec/batch_v1beta1.json kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/batch_v1beta1.json --- kubernetes-original/api/swagger-spec/batch_v1beta1.json 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/batch_v1beta1.json 2018-04-30 21:11:51.000000000 +0000 @@ -1874,6 +1874,10 @@ "$ref": "v1.PhotonPersistentDiskVolumeSource", "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" + }, "projected": { "$ref": "v1.ProjectedVolumeSource", "description": "Items for all in one resources secrets, configmaps, and downward API" @@ -2597,6 +2601,23 @@ }, "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.CascadeDiskVolumeSource": { + "id": "v1.CascadeDiskVolumeSource", + "description": "Represents a Cascade persistent disk resource.", + "required": [ + "diskID" + ], + "properties": { + "diskID": { + "type": "string", + "description": "ID that identifies Cascade persistent disk" + }, + "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." } } diff -uNr --no-dereference kubernetes-original/api/swagger-spec/batch_v1.json kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/batch_v1.json --- kubernetes-original/api/swagger-spec/batch_v1.json 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/batch_v1.json 2018-04-30 21:11:51.000000000 +0000 @@ -1819,6 +1819,10 @@ "$ref": "v1.PhotonPersistentDiskVolumeSource", "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" + }, "projected": { "$ref": "v1.ProjectedVolumeSource", "description": "Items for all in one resources secrets, configmaps, and downward API" @@ -2542,6 +2546,23 @@ }, "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.CascadeDiskVolumeSource": { + "id": "v1.CascadeDiskVolumeSource", + "description": "Represents a Cascade persistent disk resource.", + "required": [ + "diskID" + ], + "properties": { + "diskID": { + "type": "string", + "description": "ID that identifies Cascade persistent disk" + }, + "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." } } diff -uNr --no-dereference kubernetes-original/api/swagger-spec/batch_v2alpha1.json kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/batch_v2alpha1.json --- kubernetes-original/api/swagger-spec/batch_v2alpha1.json 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/batch_v2alpha1.json 2018-04-30 21:11:51.000000000 +0000 @@ -1889,6 +1889,10 @@ "storageos": { "$ref": "v1.StorageOSVolumeSource", "description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes." + }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" } } }, @@ -2793,6 +2797,23 @@ } } }, + "v1.CascadeDiskVolumeSource": { + "id": "v1.CascadeDiskVolumeSource", + "description": "Represents a Cascade persistent disk resource.", + "required": [ + "diskID" + ], + "properties": { + "diskID": { + "type": "string", + "description": "ID that identifies Cascade persistent disk" + }, + "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": { "id": "v1.Container", "description": "A single application container that you want to run within a pod.", diff -uNr --no-dereference kubernetes-original/api/swagger-spec/extensions_v1beta1.json kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/extensions_v1beta1.json --- kubernetes-original/api/swagger-spec/extensions_v1beta1.json 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/extensions_v1beta1.json 2018-04-30 21:11:51.000000000 +0000 @@ -7502,6 +7502,10 @@ "storageos": { "$ref": "v1.StorageOSVolumeSource", "description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes." + }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" } } }, @@ -8210,6 +8214,23 @@ }, "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.CascadeDiskVolumeSource": { + "id": "v1.CascadeDiskVolumeSource", + "description": "Represents a Cascade persistent disk resource.", + "required": [ + "diskID" + ], + "properties": { + "diskID": { + "type": "string", + "description": "ID that identifies Cascade persistent disk" + }, + "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." } } diff -uNr --no-dereference kubernetes-original/api/swagger-spec/settings.k8s.io_v1alpha1.json kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/settings.k8s.io_v1alpha1.json --- kubernetes-original/api/swagger-spec/settings.k8s.io_v1alpha1.json 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/settings.k8s.io_v1alpha1.json 2018-04-30 21:11:51.000000000 +0000 @@ -1676,6 +1676,10 @@ "storageos": { "$ref": "v1.StorageOSVolumeSource", "description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes." + }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" } } }, @@ -2346,6 +2350,23 @@ }, "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.CascadeDiskVolumeSource": { + "id": "v1.CascadeDiskVolumeSource", + "description": "Represents a Cascade persistent disk resource.", + "required": [ + "diskID" + ], + "properties": { + "diskID": { + "type": "string", + "description": "ID that identifies Cascade persistent disk" + }, + "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." } } diff -uNr --no-dereference kubernetes-original/api/swagger-spec/v1.json kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/v1.json --- kubernetes-original/api/swagger-spec/v1.json 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/api/swagger-spec/v1.json 2018-04-30 21:11:51.000000000 +0000 @@ -20629,6 +20629,10 @@ "$ref": "v1.PhotonPersistentDiskVolumeSource", "description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" + }, "portworxVolume": { "$ref": "v1.PortworxVolumeSource", "description": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine" @@ -21200,6 +21204,23 @@ } } }, + "v1.CascadeDiskVolumeSource": { + "id": "v1.CascadeDiskVolumeSource", + "description": "Represents a Cascade persistent disk resource.", + "required": [ + "diskID" + ], + "properties": { + "diskID": { + "type": "string", + "description": "ID that identifies Cascade persistent disk" + }, + "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.PortworxVolumeSource": { "id": "v1.PortworxVolumeSource", "description": "PortworxVolumeSource represents a Portworx volume resource.", @@ -21657,6 +21678,10 @@ "storageos": { "$ref": "v1.StorageOSVolumeSource", "description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes." + }, + "cascadeDisk": { + "$ref": "v1.CascadeDiskVolumeSource", + "description": "CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine" } } }, diff -uNr --no-dereference kubernetes-original/cmd/kube-controller-manager/app/BUILD kubernetes-modified/src/k8s.io/kubernetes/cmd/kube-controller-manager/app/BUILD --- kubernetes-original/cmd/kube-controller-manager/app/BUILD 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/cmd/kube-controller-manager/app/BUILD 2018-04-30 21:11:51.000000000 +0000 @@ -86,6 +86,7 @@ "//pkg/volume/aws_ebs:go_default_library", "//pkg/volume/azure_dd:go_default_library", "//pkg/volume/azure_file:go_default_library", + "//pkg/volume/cascade_disk:go_default_library", "//pkg/volume/cinder:go_default_library", "//pkg/volume/csi:go_default_library", "//pkg/volume/fc:go_default_library", diff -uNr --no-dereference kubernetes-original/cmd/kube-controller-manager/app/plugins.go kubernetes-modified/src/k8s.io/kubernetes/cmd/kube-controller-manager/app/plugins.go --- kubernetes-original/cmd/kube-controller-manager/app/plugins.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/cmd/kube-controller-manager/app/plugins.go 2018-04-30 21:11:51.000000000 +0000 @@ -34,6 +34,7 @@ "k8s.io/kubernetes/pkg/volume/aws_ebs" "k8s.io/kubernetes/pkg/volume/azure_dd" "k8s.io/kubernetes/pkg/volume/azure_file" + "k8s.io/kubernetes/pkg/volume/cascade_disk" "k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/csi" "k8s.io/kubernetes/pkg/volume/fc" @@ -77,6 +78,7 @@ allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...) allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...) allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, cascade_disk.ProbeVolumePlugins()...) if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...) } @@ -106,6 +108,7 @@ allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...) allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, cascade_disk.ProbeVolumePlugins()...) if !utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...) } @@ -165,6 +168,7 @@ allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...) allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...) allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, cascade_disk.ProbeVolumePlugins()...) return allPlugins } diff -uNr --no-dereference kubernetes-original/cmd/kubelet/app/BUILD kubernetes-modified/src/k8s.io/kubernetes/cmd/kubelet/app/BUILD --- kubernetes-original/cmd/kubelet/app/BUILD 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/cmd/kubelet/app/BUILD 2018-04-30 21:11:51.000000000 +0000 @@ -74,6 +74,7 @@ "//pkg/volume/aws_ebs:go_default_library", "//pkg/volume/azure_dd:go_default_library", "//pkg/volume/azure_file:go_default_library", + "//pkg/volume/cascade_disk:go_default_library", "//pkg/volume/cephfs:go_default_library", "//pkg/volume/cinder:go_default_library", "//pkg/volume/configmap:go_default_library", diff -uNr --no-dereference kubernetes-original/cmd/kubelet/app/plugins.go kubernetes-modified/src/k8s.io/kubernetes/cmd/kubelet/app/plugins.go --- kubernetes-original/cmd/kubelet/app/plugins.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/cmd/kubelet/app/plugins.go 2018-04-30 21:11:51.000000000 +0000 @@ -32,6 +32,7 @@ "k8s.io/kubernetes/pkg/volume/aws_ebs" "k8s.io/kubernetes/pkg/volume/azure_dd" "k8s.io/kubernetes/pkg/volume/azure_file" + "k8s.io/kubernetes/pkg/volume/cascade_disk" "k8s.io/kubernetes/pkg/volume/cephfs" "k8s.io/kubernetes/pkg/volume/cinder" "k8s.io/kubernetes/pkg/volume/configmap" @@ -100,6 +101,7 @@ allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...) allPlugins = append(allPlugins, local.ProbeVolumePlugins()...) allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...) + allPlugins = append(allPlugins, cascade_disk.ProbeVolumePlugins()...) if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...) } diff -uNr --no-dereference kubernetes-original/.idea/kubernetes.iml kubernetes-modified/src/k8s.io/kubernetes/.idea/kubernetes.iml --- kubernetes-original/.idea/kubernetes.iml 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/.idea/kubernetes.iml 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$" /> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff -uNr --no-dereference kubernetes-original/.idea/modules.xml kubernetes-modified/src/k8s.io/kubernetes/.idea/modules.xml --- kubernetes-original/.idea/modules.xml 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/.idea/modules.xml 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/kubernetes.iml" filepath="$PROJECT_DIR$/.idea/kubernetes.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff -uNr --no-dereference kubernetes-original/.idea/workspace.xml kubernetes-modified/src/k8s.io/kubernetes/.idea/workspace.xml --- kubernetes-original/.idea/workspace.xml 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/.idea/workspace.xml 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,217 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ChangeListManager"> + <list default="true" id="76c7d3c7-31f8-4430-8717-7c15514d1ce9" name="Default" comment="" /> + <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" /> + <option name="TRACKING_ENABLED" value="true" /> + <option name="SHOW_DIALOG" value="false" /> + <option name="HIGHLIGHT_CONFLICTS" value="true" /> + <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> + <option name="LAST_RESOLUTION" value="IGNORE" /> + </component> + <component name="ExecutionTargetManager" SELECTED_TARGET="default_target" /> + <component name="GradleLocalSettings"> + <option name="externalProjectsViewState"> + <projects_view /> + </option> + </component> + <component name="ProjectFrameBounds"> + <option name="x" value="1680" /> + <option name="y" value="-235" /> + <option name="width" value="2560" /> + <option name="height" value="1417" /> + </component> + <component name="ProjectView"> + <navigator currentView="ProjectPane" proportions="" version="1"> + <flattenPackages /> + <showMembers /> + <showModules /> + <showLibraryContents /> + <hideEmptyPackages /> + <abbreviatePackageNames /> + <autoscrollToSource /> + <autoscrollFromSource /> + <sortByType /> + <manualOrder /> + <foldersAlwaysOnTop value="true" /> + </navigator> + <panes> + <pane id="Scope" /> + <pane id="Scratches" /> + <pane id="PackagesPane" /> + <pane id="ProjectPane"> + <subPane> + <PATH> + <PATH_ELEMENT> + <option name="myItemId" value="kubernetes" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" /> + </PATH_ELEMENT> + <PATH_ELEMENT> + <option name="myItemId" value="kubernetes" /> + <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" /> + </PATH_ELEMENT> + </PATH> + </subPane> + </pane> + </panes> + </component> + <component name="PropertiesComponent"> + <property name="settings.editor.selected.configurable" value="com.goide.configuration.GoLibrariesConfigurableProvider$1" /> + <property name="configurable.Global.libraries.is.expanded" value="true" /> + <property name="last_opened_file_path" value="$PROJECT_DIR$" /> + </component> + <component name="RunDashboard"> + <option name="ruleStates"> + <list> + <RuleState> + <option name="name" value="ConfigurationTypeDashboardGroupingRule" /> + </RuleState> + <RuleState> + <option name="name" value="StatusDashboardGroupingRule" /> + </RuleState> + </list> + </option> + </component> + <component name="RunManager"> + <configuration default="true" type="#org.jetbrains.idea.devkit.run.PluginConfigurationType" factoryName="Plugin"> + <module name="" /> + <option name="VM_PARAMETERS" value="-Xmx512m -Xms256m -XX:MaxPermSize=250m -ea" /> + <option name="PROGRAM_PARAMETERS" /> + <predefined_log_file id="idea.log" enabled="true" /> + <method /> + </configuration> + <configuration default="true" type="Applet" factoryName="Applet"> + <option name="HTML_USED" value="false" /> + <option name="WIDTH" value="400" /> + <option name="HEIGHT" value="300" /> + <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" /> + <module /> + <method /> + </configuration> + <configuration default="true" type="Application" factoryName="Application"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <option name="MAIN_CLASS_NAME" /> + <option name="VM_PARAMETERS" /> + <option name="PROGRAM_PARAMETERS" /> + <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" /> + <option name="ENABLE_SWING_INSPECTOR" value="false" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <module name="" /> + <envs /> + <method /> + </configuration> + <configuration default="true" type="JUnit" factoryName="JUnit"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <module name="" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" /> + <option name="PACKAGE_NAME" /> + <option name="MAIN_CLASS_NAME" /> + <option name="METHOD_NAME" /> + <option name="TEST_OBJECT" value="class" /> + <option name="VM_PARAMETERS" value="-ea" /> + <option name="PARAMETERS" /> + <option name="WORKING_DIRECTORY" value="$MODULE_DIR$" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <option name="TEST_SEARCH_SCOPE"> + <value defaultName="singleModule" /> + </option> + <envs /> + <patterns /> + <method /> + </configuration> + <configuration default="true" type="Remote" factoryName="Remote"> + <option name="USE_SOCKET_TRANSPORT" value="true" /> + <option name="SERVER_MODE" value="false" /> + <option name="SHMEM_ADDRESS" value="javadebug" /> + <option name="HOST" value="localhost" /> + <option name="PORT" value="5005" /> + <method /> + </configuration> + <configuration default="true" type="TestNG" factoryName="TestNG"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <module name="" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" /> + <option name="SUITE_NAME" /> + <option name="PACKAGE_NAME" /> + <option name="MAIN_CLASS_NAME" /> + <option name="METHOD_NAME" /> + <option name="GROUP_NAME" /> + <option name="TEST_OBJECT" value="CLASS" /> + <option name="VM_PARAMETERS" value="-ea" /> + <option name="PARAMETERS" /> + <option name="WORKING_DIRECTORY" value="$MODULE_DIR$" /> + <option name="OUTPUT_DIRECTORY" /> + <option name="ANNOTATION_TYPE" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <option name="TEST_SEARCH_SCOPE"> + <value defaultName="singleModule" /> + </option> + <option name="USE_DEFAULT_REPORTERS" value="false" /> + <option name="PROPERTIES_FILE" /> + <envs /> + <properties /> + <listeners /> + <method /> + </configuration> + </component> + <component name="ShelveChangesManager" show_recycled="false"> + <option name="remove_strategy" value="false" /> + </component> + <component name="TaskManager"> + <task active="true" id="Default" summary="Default task"> + <changelist id="76c7d3c7-31f8-4430-8717-7c15514d1ce9" name="Default" comment="" /> + <created>1510772775522</created> + <option name="number" value="Default" /> + <option name="presentableId" value="Default" /> + <updated>1510772775522</updated> + </task> + <servers /> + </component> + <component name="ToolWindowManager"> + <frame x="1680" y="-235" width="2560" height="1417" extended-state="6" /> + <layout> + <window_info id="Palette" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" /> + <window_info id="Nl-Palette" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Palette	" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Image Layers" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Capture Analysis" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" /> + <window_info id="Maven Projects" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> + <window_info id="Properties" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" /> + <window_info id="Capture Tool" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Designer" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25178713" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" /> + <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> + <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> + <window_info id="UI Designer" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" /> + <window_info id="Theme Preview" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" /> + <window_info id="Data View" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" /> + <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" /> + <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" /> + <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" /> + <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" /> + <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" /> + </layout> + </component> + <component name="VcsContentAnnotationSettings"> + <option name="myLimit" value="2678400000" /> + </component> + <component name="XDebuggerManager"> + <breakpoint-manager /> + <watches-manager /> + </component> +</project> \ No newline at end of file diff -uNr --no-dereference kubernetes-original/pkg/apis/core/types.go kubernetes-modified/src/k8s.io/kubernetes/pkg/apis/core/types.go --- kubernetes-original/pkg/apis/core/types.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/apis/core/types.go 2018-04-30 21:11:51.000000000 +0000 @@ -316,6 +316,8 @@ // StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod // +optional StorageOS *StorageOSVolumeSource + // CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine + CascadeDisk *CascadeDiskVolumeSource } // Similar to VolumeSource but meant for the administrator who creates PVs. @@ -394,6 +396,8 @@ // CSI (Container Storage Interface) represents storage that handled by an external CSI driver // +optional CSI *CSIPersistentVolumeSource + // CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine + CascadeDisk *CascadeDiskVolumeSource } type PersistentVolumeClaimVolumeSource struct { @@ -1471,6 +1475,16 @@ SecretRef *ObjectReference } +// Represents a Cascade persistent disk resource. +type CascadeDiskVolumeSource struct { + // ID that identifies Cascade persistent disk + DiskID string + // 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 +} + // Adapts a ConfigMap into a volume. // // The contents of the target ConfigMap's Data field will be presented in a diff -uNr --no-dereference kubernetes-original/pkg/apis/core/validation/validation.go kubernetes-modified/src/k8s.io/kubernetes/pkg/apis/core/validation/validation.go --- kubernetes-original/pkg/apis/core/validation/validation.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/apis/core/validation/validation.go 2018-04-30 21:11:51.000000000 +0000 @@ -681,6 +681,14 @@ allErrs = append(allErrs, validateScaleIOVolumeSource(source.ScaleIO, fldPath.Child("scaleIO"))...) } } + if source.CascadeDisk != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("cascadeDisk"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateCascadeDiskVolumeSource(source.CascadeDisk, fldPath.Child("cascadeDisk"))...) + } + } if numVolumes == 0 { allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type")) @@ -1440,6 +1448,14 @@ return allErrs } +func validateCascadeDiskVolumeSource(cd *core.CascadeDiskVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(cd.DiskID) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("diskID"), "")) + } + return allErrs +} + // ValidatePersistentVolumeName checks that a name is appropriate for a // PersistentVolumeName object. var ValidatePersistentVolumeName = NameIsDNSSubdomain @@ -1674,6 +1690,15 @@ } } + if pv.Spec.CascadeDisk != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("cascadeDisk"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateCascadeDiskVolumeSource(pv.Spec.CascadeDisk, specPath.Child("cascadeDisk"))...) + } + } + if numVolumes == 0 { allErrs = append(allErrs, field.Required(specPath, "must specify a volume type")) } diff -uNr --no-dereference kubernetes-original/pkg/apis/extensions/types.go kubernetes-modified/src/k8s.io/kubernetes/pkg/apis/extensions/types.go --- kubernetes-original/pkg/apis/extensions/types.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/apis/extensions/types.go 2018-04-30 21:11:51.000000000 +0000 @@ -925,6 +925,7 @@ PortworxVolume FSType = "portworxVolume" ScaleIO FSType = "scaleIO" CSI FSType = "csi" + CascadeDisk FSType = "cascadeDisk" All FSType = "*" ) diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/BUILD kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/BUILD --- kubernetes-original/pkg/cloudprovider/providers/BUILD 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/BUILD 2018-04-30 21:11:51.000000000 +0000 @@ -12,6 +12,7 @@ deps = [ "//pkg/cloudprovider/providers/aws:go_default_library", "//pkg/cloudprovider/providers/azure:go_default_library", + "//pkg/cloudprovider/providers/cascade:go_default_library", "//pkg/cloudprovider/providers/cloudstack:go_default_library", "//pkg/cloudprovider/providers/gce:go_default_library", "//pkg/cloudprovider/providers/openstack:go_default_library", @@ -34,6 +35,7 @@ ":package-srcs", "//pkg/cloudprovider/providers/aws:all-srcs", "//pkg/cloudprovider/providers/azure:all-srcs", + "//pkg/cloudprovider/providers/cascade:all-srcs", "//pkg/cloudprovider/providers/cloudstack:all-srcs", "//pkg/cloudprovider/providers/fake:all-srcs", "//pkg/cloudprovider/providers/gce:all-srcs", diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/apitypes.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/apitypes.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/apitypes.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/apitypes.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,227 @@ +package cascade + +import "fmt" + +const ( + NotFoundError = 1408 + VMNotFoundError = 2006 + DiskNotFoundError = 3011 + DiskInUseError = 3012 +) + +// Represents APIError returned by the API in case of an error. +type APIError struct { + Code *string `json:"code"` + Data map[string]string `json:"data"` + ErrorCode int32 `json:"errorCode,omitempty"` + Message *string `json:"message"` + HttpStatusCode int `json:"-"` // Not part of API contract +} + +// Implement Go error interface for ApiError. +func (e APIError) Error() string { + return fmt.Sprintf( + "Cascade: { HTTP status: '%d', code: '%s', message: '%s', data: '%v', errorcode: '%d' }", + e.HttpStatusCode, StringVal(e.Code), StringVal(e.Message), e.Data, e.ErrorCode) +} + +// Used to represent a generic HTTP error, i.e. an unexpected HTTP 500. +type HttpError struct { + StatusCode int + Message string +} + +// Implementation of error interface for HttpError. +func (e HttpError) Error() string { + return fmt.Sprintf("Cascade: HTTP %d: %v", e.StatusCode, e.Message) +} + +// Represents a task which gets returned for long running API calls. +type Task struct { + EndTime int64 `json:"endTime,omitempty"` + Entity *Entity `json:"entity,omitempty"` + ID *string `json:"id"` + Operation string `json:"operation,omitempty"` + QueuedTime *int64 `json:"queuedTime"` + ResourceProperties interface{} `json:"resourceProperties,omitempty"` + SelfLink string `json:"selfLink,omitempty"` + StartedTime *int64 `json:"startedTime"` + State *string `json:"state"` + Steps []*Step `json:"steps"` +} + +// Represents the entity associated with the task. +type Entity struct { + ID *string `json:"id"` + Kind *string `json:"kind"` +} + +// Represents a task that has entered into an error state. Task errors can be caught and type-checked against with the +// usual Go idiom. +type TaskError struct { + ID string `json:"id"` + Step Step `json:"step,omitempty"` +} + +// Implement Go error interface for TaskError. +func (e TaskError) Error() string { + return fmt.Sprintf("Cascade: Task '%s' is in error state: {@step==%s}", e.ID, GetStep(e.Step)) +} + +// An error representing a timeout while waiting for a task to complete. +type TaskTimeoutError struct { + ID string +} + +// Implement Go error interface for TaskTimeoutError. +func (e TaskTimeoutError) Error() string { + return fmt.Sprintf("Cascade: Timed out waiting for task '%s'. "+ + "Task may not be in error state, examine task for full details.", e.ID) +} + +// Represents a step in a task. +type Step struct { + EndTime int64 `json:"endTime,omitempty"` + Errors []*APIError `json:"errors"` + Operation string `json:"operation,omitempty"` + Options map[string]string `json:"options,omitempty"` + QueuedTime *int64 `json:"queuedTime"` + Sequence int32 `json:"sequence,omitempty"` + StartedTime *int64 `json:"startedTime"` + State *string `json:"state"` + Warnings []*APIError `json:"warnings"` +} + +// Implement Go error interface for Step. +func GetStep(s Step) string { + return fmt.Sprintf("{\"operation\"=>\"%s\",\"state\"=>\"%s}", s.Operation, StringVal(s.State)) +} + +// Represents the VM response returned by the API. +type VM struct { + AttachedDisks []*AttachedDisk `json:"attachedDisks"` + Cost []*QuotaLineItem `json:"cost"` + Flavor *string `json:"flavor"` + FloatingIP string `json:"floatingIp,omitempty"` + HighAvailableVMGroupID string `json:"highAvailableVMGroupID,omitempty"` + ID *string `json:"id"` + Kind string `json:"kind"` + Name *string `json:"name"` + SelfLink string `json:"selfLink,omitempty"` + SourceImageID string `json:"sourceImageId,omitempty"` + State *string `json:"state"` + Subnets []string `json:"subnets"` + Tags []string `json:"tags"` +} + +// Represents the listVMs response returned by the API. +type VMList struct { + Items []*VM `json:"items"` + NextPageLink string `json:"nextPageLink,omitempty"` + PreviousPageLink string `json:"previousPageLink,omitempty"` +} + +// Represents multiple VMs returned by the API. +type VMs struct { + Items []VM `json:"items"` +} + +// Represents the disks attached to the VMs. +type AttachedDisk struct { + BootDisk *bool `json:"bootDisk"` + CapacityGb *int32 `json:"capacityGb"` + Flavor *string `json:"flavor"` + ID *string `json:"id"` + Kind *string `json:"kind"` + Name *string `json:"name"` + State *string `json:"state"` +} + +// Represents an attach disk operation request. +type VMDiskOperation struct { + Arguments map[string]string `json:"arguments,omitempty"` + DiskID *string `json:"diskId"` +} + +// Represents the quota line items for the VM. +type QuotaLineItem struct { + Key *string `json:"key"` + Unit *string `json:"unit"` + Value *float64 `json:"value"` +} + +// Represents a persistent disk +type PersistentDisk struct { + CapacityGB int32 `json:"capacityGb,omitempty"` + Cost []*QuotaLineItem `json:"cost"` + Datastore string `json:"datastore,omitempty"` + Flavor *string `json:"flavor"` + ID *string `json:"id"` + Kind string `json:"kind"` + Name *string `json:"name"` + SelfLink string `json:"selfLink,omitempty"` + State *string `json:"state"` + Tags []string `json:"tags"` + VM string `json:"vm"` + MountDevice string `json:"mountDevice,omitempty"` + Zone *string `json:"zone"` +} + +// Represents the spec for creating a disk. +type DiskCreateSpec struct { + Affinities []*LocalitySpec `json:"affinities"` + CapacityGB *int32 `json:"capacityGb"` + Flavor *string `json:"flavor"` + Kind *string `json:"kind"` + Name *string `json:"name"` + Tags []string `json:"tags"` + Zone *string `json:"zone"` +} + +// Represents the spec for specifying affinity for a disk with another entity. +type LocalitySpec struct { + ID *string `json:"id"` + Kind *string `json:"kind"` +} + +// Represens the LoadBalancer response returned by the API. +type LoadBalancer struct { + Endpoint *string `json:"endpoint"` +} + +// Represents the spec for creating a LoadBalancer. +type LoadBalancerCreateSpec struct { + HealthCheck *LoadBalancerHealthCheck `json:"healthCheck"` + Name *string `json:"name"` + PortMaps []*LoadBalancerPortMap `json:"portMaps"` + Type *string `json:"type"` + SubDomain *string `json:"subDomain"` +} + +// Represents the health check spec for a load balancer. +type LoadBalancerHealthCheck struct { + HealthyThreshold int64 `json:"healthyThreshold,omitempty"` + IntervalInSeconds int64 `json:"intervalInSeconds,omitempty"` + Path *string `json:"path,omitempty"` + Port *int64 `json:"port"` + Protocol *string `json:"protocol"` +} + +// Represents a port mapping spec for a load balancer. +type LoadBalancerPortMap struct { + AllowedCidrs []*string `json:"allowedCidrs"` + InstancePort *int64 `json:"instancePort"` + InstanceProtocol *string `json:"instanceProtocol"` + LoadBalancerPort *int64 `json:"loadBalancerPort"` + LoadBalancerProtocol *string `json:"loadBalancerProtocol"` +} + +// Represents a VM to be registered with or deregistered from the load balancer. +type LoadBalancerVM struct { + ID *string `json:"id"` +} + +// Represents a list of VMs to be registered with or deregistered from the load balancer. +type LoadBalancerVMUpdate struct { + VMIds []*LoadBalancerVM `json:"vmIds"` +} diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/auth.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/auth.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/auth.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/auth.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,145 @@ +package cascade + +import ( + "fmt" + "strings" + "github.com/golang/glog" + "os/exec" +) + +const ( + tScope = "openid offline_access rs_admin_server at_groups rs_vmdir" + + afdCli = "/opt/vmware/bin/vmafd-cli" + afdCliMachineAccountCmd = "get-machine-account-info" + afdCliPasswordPrefix = "Password: " + afdCliSeparator = "\n" +) + +// AuthConfig contains configuration information for the authentication client. +type AuthConfig struct { + tenantName string + authEndpoint string + machineAccountName string +} + +// AuthClient defines functions related to authentication. +type AuthClient struct { + cfg *AuthConfig +} + +// NewAuthClient creates a new authentication client +func NewAuthClient(cascadeCfg *CascadeConfig) (*AuthClient, error) { + return &AuthClient{ + cfg: &AuthConfig{ + tenantName: cascadeCfg.Global.TenantName, + authEndpoint: cascadeCfg.Global.AuthEndpoint, + machineAccountName: fmt.Sprintf("%s@%s", cascadeCfg.Global.DNSName, cascadeCfg.Global.DomainName), + }, + }, nil +} + +func (c *AuthClient) GetTokensByMachineAccount() (*TokenOptions, error) { + // Use the VMAFD CLI to get the machine account password + cmd := exec.Command(afdCli, afdCliMachineAccountCmd) + output, err := cmd.Output() + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to get machine account credentials. Cannot create Client.") + return nil, fmt.Errorf("Failed to get machine account credentials, err: %v", err) + } + + password, err := parseMachineAccountInfo(output) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to parse machine account credentials. Cannot create Client.") + return nil, fmt.Errorf("Failed to parse machine account credentials, err: %v", err) + } + + return c.GetTokensByCredentials(c.cfg.machineAccountName, password) +} + +// GetTokensByPassword gets tokens using username and password +func (c *AuthClient) GetTokensByCredentials(username, password string) (*TokenOptions, error) { + // Parse tenant part from username + parts := strings.Split(username, "@") + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid full user name '%s': expected user@tenant", username) + } + tenant := parts[1] + + oidcClient, err := buildOIDCClient(c.cfg.authEndpoint) + if err != nil { + return nil, err + } + + tokenResponse, err := oidcClient.GetTokenByPasswordGrant(tenant, username, password) + if err != nil { + return nil, err + } + + return toTokenOptions(tokenResponse), nil +} + +// GetTokensByRefreshToken gets tokens using refresh token +func (c *AuthClient) GetTokensByRefreshToken(refreshtoken string) (*TokenOptions, error) { + oidcClient, err := buildOIDCClient(c.cfg.authEndpoint) + if err != nil { + return nil, err + } + + tokenResponse, err := oidcClient.GetTokenByRefreshTokenGrant(c.cfg.tenantName, refreshtoken) + if err != nil { + return nil, err + } + + return toTokenOptions(tokenResponse), nil +} + +func buildOIDCClient(authEndpoint string) (*OIDCClient, error) { + options := &OIDCClientOptions{ + IgnoreCertificate: false, + RootCAs: nil, + TokenScope: tScope, + } + + return NewOIDCClient(authEndpoint, options, nil), nil +} + +func toTokenOptions(response *OIDCTokenResponse) *TokenOptions { + return &TokenOptions{ + AccessToken: response.AccessToken, + ExpiresIn: response.ExpiresIn, + RefreshToken: response.RefreshToken, + IDToken: response.IDToken, + TokenType: response.TokenType, + } +} + +// parseMachineAccountInfo parses the machine account password from the machine-account-info output which looks like +// this: +//MachineAccount: photon-8rwdscr1.lw-testdom.com +//Password: FT`])}]d/3\EPwRpz9k1 +func parseMachineAccountInfo(output []byte) (string, error) { + if len(output) <= 0 { + return "", fmt.Errorf("account info is not specified") + } + + strOut := string(output) + strOutLen := len(strOut) + + pwdStart := strings.Index(strOut, afdCliPasswordPrefix) + if pwdStart < 0 { + return "", fmt.Errorf("account info is not in expected format") + } + pwdStart = pwdStart + len(afdCliPasswordPrefix) + if pwdStart >= strOutLen { + return "", fmt.Errorf("account info is not in expected format") + } + pwdEnd := strings.LastIndex(strOut, afdCliSeparator) + if pwdEnd < 0 || pwdEnd <= pwdStart || pwdEnd >= strOutLen { + return "", fmt.Errorf("account info is not in expected format") + } + + pwd := strOut[pwdStart:pwdEnd] + + return pwd, nil +} \ No newline at end of file diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/BUILD kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/BUILD --- kubernetes-original/pkg/cloudprovider/providers/cascade/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/BUILD 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,44 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "apitypes.go", + "auth.go", + "cascade.go", + "cascade_disks.go", + "cascade_instances.go", + "cascade_loadbalancer.go", + "client.go", + "oidcclient.go", + "restclient.go", + "utils.go" + ], + deps = [ + "//pkg/api/v1/helper:go_default_library", + "//pkg/cloudprovider:go_default_library", + "//pkg/controller:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/gopkg.in/gcfg.v1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/cascade_disks.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/cascade_disks.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/cascade_disks.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/cascade_disks.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,225 @@ +package cascade + +import ( + "github.com/golang/glog" + k8stypes "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubernetes/pkg/kubelet/apis" + "k8s.io/kubernetes/pkg/volume" +) + +// Attaches given virtual disk volume to the node running kubelet. +func (cc *CascadeCloud) AttachDisk(diskID string, nodeName k8stypes.NodeName) (string, error) { + // Check if disk is already attached to that node. + attached, err := cc.DiskIsAttached(diskID, nodeName) + if err != nil { + glog.Errorf("Cascade Cloud Provider: cc.DiskIsAttached failed during AttachDisk. Error[%v]", err) + return "", err + } + + // If not already attached, attach the disk. + if !attached { + operation := &VMDiskOperation{ + DiskID: StringPtr(diskID), + } + + vmID, err := cc.InstanceID(nodeName) + if err != nil { + glog.Errorf("Cascade Cloud Provider: cc.InstanceID failed for AttachDisk. Error[%v]", err) + return "", err + } + + task, err := cc.apiClient.AttachDisk(vmID, operation) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to attach disk with ID %s. Error[%v]", diskID, err) + return "", err + } + + _, err = cc.apiClient.WaitForTask(StringVal(task.ID)) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to wait for task to attach disk with ID %s. Error[%v]", + diskID, err) + return "", err + } + } + + // Get mount device of the attached disk. + disk, err := cc.apiClient.GetDisk(diskID) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to Get disk with diskID %s. Error[%v]", diskID, err) + return "", err + } + + return disk.MountDevice, nil +} + +// Detaches given virtual disk volume from the node running kubelet. +func (cc *CascadeCloud) DetachDisk(diskID string, nodeName k8stypes.NodeName) error { + operation := &VMDiskOperation{ + DiskID: StringPtr(diskID), + } + + vmID, err := cc.InstanceID(nodeName) + if err != nil { + glog.Errorf("Cascade Cloud Provider: cc.InstanceID failed for DetachDisk. Error[%v]", err) + return err + } + + task, err := cc.apiClient.DetachDisk(vmID, operation) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to detach disk with pdID %s. Error[%v]", diskID, err) + return err + } + + _, err = cc.apiClient.WaitForTask(StringVal(task.ID)) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to wait for task to detach disk with pdID %s. Error[%v]", + diskID, err) + return err + } + + return nil +} + +// DiskIsAttached returns if disk is attached to the VM using controllers supported by the plugin. +func (cc *CascadeCloud) DiskIsAttached(diskID string, nodeName k8stypes.NodeName) (bool, error) { + vmID, err := cc.InstanceID(nodeName) + if err != nil { + glog.Errorf("Cascade Cloud Provider: cc.InstanceID failed for DiskIsAttached. Error[%v]", err) + return false, err + } + + _, err = cc.apiClient.GetVM(vmID) + if err != nil { + switch err.(type) { + case APIError: + if err.(APIError).ErrorCode == VMNotFoundError { + // If instance no longer exists, we will assume that the volume is not attached. + glog.Warningf("Cascade Cloud Provider: Instance %s does not exist. DiskIsAttached will assume"+ + " disk %s is not attached to it.", nodeName, diskID) + return false, nil + } + } + return false, err + } + + disk, err := cc.apiClient.GetDisk(diskID) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to Get disk with diskID %s. Error[%v]", diskID, err) + return false, err + } + + if disk.VM == vmID { + return true, nil + } + + return false, nil +} + +// DisksAreAttached returns if disks are attached to the VM using controllers supported by the plugin. +func (cc *CascadeCloud) DisksAreAttached(diskIDs []string, nodeName k8stypes.NodeName) (map[string]bool, error) { + attached := make(map[string]bool) + for _, diskID := range diskIDs { + attached[diskID] = false + } + + vmID, err := cc.InstanceID(nodeName) + if err != nil { + glog.Errorf("Cascade Cloud Provider: cc.InstanceID failed for DiskIsAttached. Error[%v]", err) + return attached, err + } + + for _, diskID := range diskIDs { + disk, err := cc.apiClient.GetDisk(diskID) + if err != nil { + glog.Warningf("Cascade Cloud Provider: failed to get VMs for persistent disk %s, err [%v]", + diskID, err) + } else { + if disk.VM == vmID { + attached[diskID] = true + } + } + } + + return attached, nil +} + +// Create a volume of given size (in GB). +func (cc *CascadeCloud) CreateDisk(volumeOptions *VolumeOptions) (diskID string, err error) { + // Get Zones for the cluster + zones, err := cc.apiClient.GetZones() + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to Get zones for the cluster. Error[%v]", err) + return "", err + } + + // Pick a zone to place the disk in. + zoneSet := sets.NewString() + for _, zone := range zones { + zoneSet.Insert(zone) + } + zone := volume.ChooseZoneForVolume(zoneSet, volumeOptions.Name) + + diskSpec := DiskCreateSpec{} + diskSpec.Name = StringPtr(volumeOptions.Name) + diskSpec.Flavor = StringPtr(volumeOptions.Flavor) + diskSpec.CapacityGB = Int32Ptr(int32(volumeOptions.CapacityGB)) + diskSpec.Kind = StringPtr(DiskSpecKind) + diskSpec.Zone = StringPtr(zone) + + task, err := cc.apiClient.CreateDisk(&diskSpec) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to CreateDisk. Error[%v]", err) + return "", err + } + + waitTask, err := cc.apiClient.WaitForTask(StringVal(task.ID)) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to wait for task to CreateDisk. Error[%v]", err) + return "", err + } + + return StringVal(waitTask.Entity.ID), nil +} + +// Deletes a volume given volume name. +func (cc *CascadeCloud) DeleteDisk(diskID string) error { + task, err := cc.apiClient.DeleteDisk(diskID) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to DeleteDisk. Error[%v]", err) + // If we get a DiskNotFound error, we assume that the disk is already deleted. So we don't return an error here. + switch err.(type) { + case APIError: + if err.(APIError).ErrorCode == DiskNotFoundError { + return nil + } + if err.(APIError).ErrorCode == DiskInUseError { + return volume.NewDeletedVolumeInUseError(err.Error()) + } + } + return err + } + + _, err = cc.apiClient.WaitForTask(StringVal(task.ID)) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to wait for task to DeleteDisk. Error[%v]", err) + return err + } + + return nil +} + +// Gets the zone and region for the volume. +func (cc *CascadeCloud) GetVolumeLabels(diskID string) (map[string]string, error) { + disk, err := cc.apiClient.GetDisk(diskID) + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to GetDisk for GetVolumeLabels. Error[%v]", err) + return nil, err + } + + labels := make(map[string]string) + labels[apis.LabelZoneFailureDomain] = StringVal(disk.Zone) + labels[apis.LabelZoneRegion] = cc.cfg.Global.Region + + return labels, nil +} diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/cascade.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/cascade.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/cascade.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/cascade.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,216 @@ +// The use of Cascade cloud provider requires the kubelet, kube-apiserver, and kube-controller-manager to be started +// with config flag: '--cloud-provider=cascade --cloud-config=[path_to_config_file]'. +package cascade + +import ( + "errors" + "fmt" + "io" + "os" + "github.com/golang/glog" + "gopkg.in/gcfg.v1" + k8stypes "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" + "strings" +) + +const ( + ProviderName = "cascade" + DiskSpecKind = "persistent-disk" + MasterPrefix = "master" +) + +// CascadeCloud is an implementation of the cloud provider interface for Cascade Controller. +type CascadeCloud struct { + cfg *CascadeConfig + // Authentication client to get token for Cascade API calls + authClient *AuthClient + // API Client to make Cascade API calls + apiClient *Client + // local $HOSTNAME + localHostname string + // hostname from K8S, could be overridden + localK8sHostname string +} + +// CascadeCloud represents Cascade cloud provider's configuration. +type CascadeConfig struct { + Global struct { + // the Cascade Controller endpoint + CloudTarget string `gcfg:"target"` + // Cascade Controller tenantName name + TenantName string `gcfg:"tenantName"` + // Cascade Controller cluster ID + ClusterID string `gcfg:"clusterID"` + // Authentication server endpoint for Cascade Controller + AuthEndpoint string `gcfg:"authEndpoint"` + // Lightwave domain name for the node + DomainName string `gcfg:"domainName"` + // DNS name of the node. + DNSName string `gcfg:"dnsName"` + // Region in which the cluster is in + Region string `gcfg:"region"` + // Availability zone in which the cluster is in + Zone string `gcfg:"zone"` + } +} + +// Disks is interface for manipulation with Cascade Controller Persistent Disks. +type Disks interface { + // AttachDisk attaches given disk to given node. Current node + // is used when nodeName is empty string. + AttachDisk(diskID string, nodeName k8stypes.NodeName) (string, error) + + // DetachDisk detaches given disk to given node. Current node + // is used when nodeName is empty string. + DetachDisk(diskID string, nodeName k8stypes.NodeName) error + + // DiskIsAttached checks if a disk is attached to the given node. + DiskIsAttached(diskID string, nodeName k8stypes.NodeName) (bool, error) + + // DisksAreAttached is a batch function to check if a list of disks are attached + // to the node with the specified NodeName. + DisksAreAttached(diskID []string, nodeName k8stypes.NodeName) (map[string]bool, error) + + // CreateDisk creates a new PD with given properties. + CreateDisk(volumeOptions *VolumeOptions) (diskID string, err error) + + // DeleteDisk deletes PD. + DeleteDisk(diskID string) error + + // Get labels to apply to volume on creation. + GetVolumeLabels(diskID string) (map[string]string, error) +} + +// VolumeOptions specifies capacity, tags, name and flavorID for a volume. +type VolumeOptions struct { + CapacityGB int + Tags map[string]string + Name string + Flavor string +} + +func readConfig(config io.Reader) (*CascadeConfig, error) { + if config == nil { + err := fmt.Errorf("Cascade Cloud Provider: config file is missing. Please restart with " + + "--cloud-provider=cascade --cloud-config=[path_to_config_file]") + return nil, err + } + + var cfg CascadeConfig + err := gcfg.ReadInto(&cfg, config) + return &cfg, err +} + +func init() { + cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { + cfg, err := readConfig(config) + if err != nil { + glog.Errorf("Cascade Cloud Provider: failed to read in cloud provider config file. Error[%v]", err) + return nil, err + } + return newCascadeCloud(cfg) + }) +} + +func newCascadeCloud(cfg *CascadeConfig) (*CascadeCloud, error) { + if len(cfg.Global.CloudTarget) == 0 { + return nil, fmt.Errorf("Cascade Controller endpoint was not specified.") + } + + // Get local hostname + hostname, err := os.Hostname() + if err != nil { + glog.Errorf("Cascade Cloud Provider: get hostname failed. Error[%v]", err) + return nil, err + } + + cc := CascadeCloud{ + cfg: cfg, + localHostname: hostname, + localK8sHostname: "", + } + + // Instantiate the auth and API clients only on the master nodes. Kubelets running on the workers don't need them as + // they are used primarily for making API calls to Cascade. + if strings.HasPrefix(hostname, MasterPrefix) { + if cc.authClient, err = NewAuthClient(cfg); err != nil { + return nil, err + } + + if cc.apiClient, err = NewClient(cfg, cc.authClient); err != nil { + return nil, err + } + } + + return &cc, nil +} + +// Initialize passes a Kubernetes clientBuilder interface to the cloud provider +func (cc *CascadeCloud) Initialize(clientBuilder controller.ControllerClientBuilder) {} + +// Instances returns an implementation of Instances for Cascade Controller. +func (cc *CascadeCloud) Instances() (cloudprovider.Instances, bool) { + return cc, true +} + +// List is an implementation of Instances.List. +func (cc *CascadeCloud) List(filter string) ([]k8stypes.NodeName, error) { + return nil, errors.New("unimplemented") +} + +func (cc *CascadeCloud) Clusters() (cloudprovider.Clusters, bool) { + return nil, true +} + +// ProviderName returns the cloud provider ID. +func (cc *CascadeCloud) ProviderName() string { + return ProviderName +} + +// LoadBalancer returns an implementation of LoadBalancer for Cascade Controller. +func (cc *CascadeCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { + return cc, true +} + +// Zones returns an implementation of Zones for Cascade Controller. +func (cc *CascadeCloud) Zones() (cloudprovider.Zones, bool) { + return cc, true +} + +func (cc *CascadeCloud) GetZone() (cloudprovider.Zone, error) { + return cloudprovider.Zone{ + Region: cc.cfg.Global.Region, + FailureDomain: cc.cfg.Global.Zone, + }, nil +} + +// GetZoneByProviderID implements Zones.GetZoneByProviderID +// This is particularly useful in external cloud providers where the kubelet +// does not initialize node data. +func (cc *CascadeCloud) GetZoneByProviderID(providerID string) (cloudprovider.Zone, error) { + return cloudprovider.Zone{}, errors.New("unimplemented") +} + +// GetZoneByNodeName implements Zones.GetZoneByNodeName +// This is particularly useful in external cloud providers where the kubelet +// does not initialize node data. +func (cc *CascadeCloud) GetZoneByNodeName(nodeName k8stypes.NodeName) (cloudprovider.Zone, error) { + return cloudprovider.Zone{}, errors.New("unimeplemented") +} + +// Routes returns a false since the interface is not supported for Cascade controller. +func (cc *CascadeCloud) Routes() (cloudprovider.Routes, bool) { + return nil, false +} + +// ScrubDNS filters DNS settings for pods. +func (cc *CascadeCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { + return nameservers, searches +} + +// HasClusterID returns true if the cluster has a clusterID +func (cc *CascadeCloud) HasClusterID() bool { + return true +} \ No newline at end of file diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/cascade_instances.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/cascade_instances.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/cascade_instances.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/cascade_instances.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,90 @@ +package cascade + +import ( + "k8s.io/api/core/v1" + k8stypes "k8s.io/apimachinery/pkg/types" + "errors" + "strings" +) + +// NodeAddresses is an implementation of Instances.NodeAddresses. In the future, private IP address, external IP, etc. +// will be added based on need. +func (cc *CascadeCloud) NodeAddresses(nodeName k8stypes.NodeName) ([]v1.NodeAddress, error) { + addresses := []v1.NodeAddress{} + addresses = append(addresses, v1.NodeAddress{Type: v1.NodeInternalDNS, Address: cc.cfg.Global.DNSName}) + return addresses, nil +} + +// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID +// This method will not be called from the node that is requesting this ID. i.e. metadata service +// and other local methods cannot be used here +func (cc *CascadeCloud) NodeAddressesByProviderID(providerID string) ([]v1.NodeAddress, error) { + // Get the name of the VM using the ID and generate the DNS name based on the VM name. + vm, err := cc.apiClient.GetVM(providerID) + if err != nil { + return nil, err + } + // Get the DNS name for the master VM and replace the VM name portion with the requested VM name. + dnsNameParts := strings.SplitN(cc.cfg.Global.DNSName, ".", 2) + if len(dnsNameParts) != 2 { + return nil, errors.New("Cascade cloud provider: Invalid DNS name specified in the configuation. " + + "Cannot get NodeAddressByProviderID.") + } + dnsAddress := StringVal(vm.Name) + dnsNameParts[1] + addresses := []v1.NodeAddress{} + addresses = append(addresses, v1.NodeAddress{Type: v1.NodeInternalDNS, Address: dnsAddress}) + return addresses, nil +} + +func (cc *CascadeCloud) AddSSHKeyToAllInstances(user string, keyData []byte) error { + return errors.New("unimplemented") +} + +// Current node name returns node name based on host name. For Cascade Kubernetes nodes, we will use host name as the +// node name. +func (cc *CascadeCloud) CurrentNodeName(hostname string) (k8stypes.NodeName, error) { + cc.localK8sHostname = hostname + return k8stypes.NodeName(hostname), nil +} + +// ExternalID returns the cloud provider ID of the specified instance (deprecated). +// Note: We do not call Cascade Controller here to check if the instance is alive or not because that requires the +// worker nodes to also login to Cascade Controller. That check is used by Kubernetes to proactively remove nodes that +// the cloud provider believes is no longer available. Even otherwise, Kubernetes will remove those nodes eventually. +// So we are not losing much by not doing that check. +func (cc *CascadeCloud) ExternalID(nodeName k8stypes.NodeName) (string, error) { + return getInstanceIDFromNodeName(nodeName) +} + +// InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running. +// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager. +func (cc *CascadeCloud) InstanceExistsByProviderID(providerID string) (bool, error) { + return false, errors.New("unimplemented") +} + +// InstanceID returns the cloud provider ID of the specified instance. +func (cc *CascadeCloud) InstanceID(nodeName k8stypes.NodeName) (string, error) { + return getInstanceIDFromNodeName(nodeName) +} + +// This gets the Cascade VM ID from the Kubernetes node name. +func getInstanceIDFromNodeName(nodeName k8stypes.NodeName) (string, error) { + // nodeName is of the format master-instance-id or worker-instance-id. To compute the instance ID, we need to just + // get the portion after master- or worker-. That is what we do below. + nodeParts := strings.SplitN(string(nodeName), "-", 2) + if len(nodeParts) != 2 { + return "", errors.New("Cascade cloud provider: Invalid node name. Cannot fetch instance ID.") + } + return nodeParts[1], nil +} + +// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID +// This method will not be called from the node that is requesting this ID. i.e. metadata service +// and other local methods cannot be used here +func (cc *CascadeCloud) InstanceTypeByProviderID(providerID string) (string, error) { + return "", errors.New("unimplemented") +} + +func (cc *CascadeCloud) InstanceType(nodeName k8stypes.NodeName) (string, error) { + return "", nil +} diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/cascade_loadbalancer.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/cascade_loadbalancer.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/cascade_loadbalancer.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/cascade_loadbalancer.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,284 @@ +package cascade + +import ( + "fmt" + "github.com/golang/glog" + "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/api/v1/service" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/apimachinery/pkg/types" +) + +const TCP_PROTOCOL = "TCP" + +const HTTP_PROTOCOL = "HTTP" + +// EnsureLoadBalancer creates or updates a Cascade load balancer +func (cc *CascadeCloud) EnsureLoadBalancer(clusterName string, k8sService *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) { + logger := newLoadBalancerLogger(clusterName, k8sService, "EnsureLoadBalancer") + + loadBalancerName := cloudprovider.GetLoadBalancerName(k8sService) + logger.Infof("Load balancer name: %s", loadBalancerName) + + // Sanity checks + if k8sService.Spec.SessionAffinity != v1.ServiceAffinityNone { + logger.Errorf("Unsupported load balancer session affinity: %+v", k8sService.Spec.SessionAffinity) + return nil, fmt.Errorf("Unsupported load balancer session affinity: %+v", k8sService.Spec.SessionAffinity) + } + + if len(k8sService.Spec.Ports) == 0 { + logger.Errorf("No port mapping is specified") + return nil, fmt.Errorf("No port mapping is specified") + } + + // Create load balancer port maps + portMaps := []*LoadBalancerPortMap{} + for _, port := range k8sService.Spec.Ports { + if port.Protocol != v1.ProtocolTCP { + logger.Warningf("Ignoring port that does not use TCP protocol: %+v", port) + continue + } + + if port.NodePort == 0 { + logger.Warningf("Ignoring port without node port defined: %+v", port) + continue + } + + // TODO: For now we only support SSL pass through. All port mappings are using TCP protocol. + // Also note that we allow all external traffic to access the ports. + portMap := &LoadBalancerPortMap{ + InstancePort: Int64Ptr(int64(port.NodePort)), + InstanceProtocol: StringPtr(TCP_PROTOCOL), + LoadBalancerPort: Int64Ptr(int64(port.Port)), + LoadBalancerProtocol: StringPtr(TCP_PROTOCOL), + } + portMaps = append(portMaps, portMap) + } + + // Create load balancer health check + healthCheck := &LoadBalancerHealthCheck{ + HealthyThreshold: 5, + IntervalInSeconds: 10, + } + if healthCheckPath, healthCheckNodePort := service.GetServiceHealthCheckPathPort(k8sService); healthCheckPath != "" { + logger.Infof("HTTP health checks on: %s:%d", healthCheckPath, healthCheckNodePort) + healthCheck.Path = StringPtr(healthCheckPath) + healthCheck.Port = Int64Ptr(int64(healthCheckNodePort)) + healthCheck.Protocol = StringPtr(HTTP_PROTOCOL) + } else { + logger.Infof("TCP health check on port: %d", Int64Val(portMaps[0].InstancePort)) + healthCheck.Port = portMaps[0].InstancePort + healthCheck.Protocol = StringPtr(TCP_PROTOCOL) + } + + // Create load balancer + createSpec := &LoadBalancerCreateSpec{ + Name: StringPtr(loadBalancerName), + Type: StringPtr("PUBLIC"), + PortMaps: portMaps, + HealthCheck: healthCheck, + SubDomain: StringPtr(k8sService.Name), + } + logger.Infof("Load balancer create spec: %+v", *createSpec) + + task, err := cc.apiClient.CreateOrUpdateLoadBalancer(createSpec) + if err != nil { + logger.Errorf("Failed to create or update load balancer. Error: [%v]", err) + return nil, err + } + + _, err = cc.apiClient.WaitForTask(StringVal(task.ID)) + if err != nil { + logger.Errorf("Failed to poll task status of creating or updating load balancer. Error: [%v]", err) + return nil, err + } + + // Apply VM update to load balancer + err = cc.updateLoadBalancerVMs(nodes, loadBalancerName, logger) + if err != nil { + // The private function already did logging. No need to log again. + return nil, err + } + + // Get load balancer + loadBalancer, err := cc.apiClient.GetLoadBalancer(StringPtr(loadBalancerName)) + if err != nil { + glog.Errorf("Failed to get load balancer. Error: [%v]", err) + return nil, err + } + + return toLoadBalancerStatus(loadBalancer), nil +} + +// GetLoadBalancer returns the information about a Cascade load balancer +func (cc *CascadeCloud) GetLoadBalancer(clusterName string, k8sService *v1.Service) (*v1.LoadBalancerStatus, bool, error) { + logger := newLoadBalancerLogger(clusterName, k8sService, "GetLoadBalancer") + + loadBalancerName := cloudprovider.GetLoadBalancerName(k8sService) + logger.Infof("Load balancer name: %s", loadBalancerName) + + // Get load balancer + loadBalancer, err := cc.apiClient.GetLoadBalancer(StringPtr(loadBalancerName)) + if err != nil { + logger.Errorf("Failed to get load balancer. Error: [%v]", err) + // Do not return error here because we want the caller of this function to determine + // what to do with the not-found situation. + switch err.(type) { + case APIError: + if err.(APIError).ErrorCode == NotFoundError { + return nil, false, nil + } + } + return nil, false, err + } + + return toLoadBalancerStatus(loadBalancer), true, nil +} + +// UpdateLoadBalancer updates the node information of a Cascade load balancer +func (cc *CascadeCloud) UpdateLoadBalancer(clusterName string, k8sService *v1.Service, nodes []*v1.Node) error { + logger := newLoadBalancerLogger(clusterName, k8sService, "UpdateLoadBalancer") + + loadBalancerName := cloudprovider.GetLoadBalancerName(k8sService) + logger.Infof("Load balancer name: %s", loadBalancerName) + + err := cc.updateLoadBalancerVMs(nodes, loadBalancerName, logger) + if err != nil { + // The private function already did logging. No need to log again. + return err + } + + return nil +} + +// EnsureLoadBalancerDeleted deletes a Cascade load balancer +func (cc *CascadeCloud) EnsureLoadBalancerDeleted(clusterName string, k8sService *v1.Service) error { + logger := newLoadBalancerLogger(clusterName, k8sService, "EnsureLoadBalancerDeleted") + + loadBalancerName := cloudprovider.GetLoadBalancerName(k8sService) + logger.Infof("Load balancer name: %s", loadBalancerName) + + task, err := cc.apiClient.DeleteLoadBalancer(StringPtr(loadBalancerName)) + if err != nil { + logger.Errorf("Failed to delete load balancer. Error: [%v]", err) + // If we get a NotFound error, we assume that the load balancer is already deleted. So we don't return an error + // here. + switch err.(type) { + case APIError: + if err.(APIError).ErrorCode == NotFoundError { + return nil + } + } + return err + } + + _, err = cc.apiClient.WaitForTask(StringVal(task.ID)) + if err != nil { + logger.Errorf("Failed to poll task status of deleting load balancer. Error: [%v]", err) + return err + } + + return nil +} + +func (cc *CascadeCloud) updateLoadBalancerVMs( + nodes []*v1.Node, loadBalancerName string, logger *loadBalancerLogger) error { + + // Apply VM update to the load balancer + loadBalancerVMs := make([]*LoadBalancerVM, 0) + + for _, node := range(nodes) { + // If the node does not have a name, we cannot derive its instance ID. Therefore we skip this node. + if len(node.Name) == 0 { + logger.Warningf("Node %s does not have a name. Skip updating this VM for load balancer", node.UID) + continue + } + + // If we cannot get the instance ID, something is wrong on the Cascade Controller side. + // However, we should tolerate such failure and continue the load balancer VM update + // by skipping this VM. + instanceID, err := cc.InstanceID(types.NodeName(node.Name)) + if err != nil { + logger.Warningf("Unable to get instance ID for node %s, skip updating this VM for load balancer. Error [%v]", node.Name, err) + continue + } + + loadBalancerVMs = append(loadBalancerVMs, &LoadBalancerVM{ + ID: StringPtr(instanceID), + }) + } + + if len(loadBalancerVMs) == 0 { + logger.Infof("No nodes to be added to the load balancer. Skip updating load balancer VMs") + return nil + } + + vmUpdate := &LoadBalancerVMUpdate{ + VMIds: loadBalancerVMs, + } + logger.Infof("Load balancer VM update spec: %+v", vmUpdate.VMIds) + + task, err := cc.apiClient.ApplyVMsToLoadBalancer(StringPtr(loadBalancerName), vmUpdate) + if err != nil { + logger.Errorf("Failed to update load balancer VMs. Error: [%v]", err) + return err + } + + _, err = cc.apiClient.WaitForTask(StringVal(task.ID)) + if err != nil { + logger.Errorf("Failed to poll task status of updating load balancer VMs. Error: [%v]", err) + return err + } + + return nil +} + +func toLoadBalancerStatus(lb *LoadBalancer) *v1.LoadBalancerStatus { + var endpoint string + if lb != nil && lb.Endpoint != nil { + endpoint = StringVal(lb.Endpoint) + } + + return &v1.LoadBalancerStatus{ + Ingress: []v1.LoadBalancerIngress{ + { + Hostname: endpoint, + }, + }, + } +} + +type loadBalancerLogger struct { + clusterName string + k8sService *v1.Service + callingFunc string +} + +func newLoadBalancerLogger(clusterName string, k8sService *v1.Service, callingFunc string) *loadBalancerLogger { + return &loadBalancerLogger{ + clusterName: clusterName, + k8sService: k8sService, + callingFunc: callingFunc, + } +} + +func (l *loadBalancerLogger) getLogMsg( + msgTemplate string, args ...interface{}) string { + + errorMsg := fmt.Sprintf("Cascade Cloud Provider::%s::Cluster [%s] Service [%s]: %s", + l.callingFunc, l.clusterName, l.k8sService.Name, + msgTemplate) + return fmt.Sprintf(errorMsg, args) +} + +func (l *loadBalancerLogger) Errorf(msgTemplate string, args ...interface{}) { + glog.Errorln(l.getLogMsg(msgTemplate, args)) +} + +func (l *loadBalancerLogger) Warningf(msgTemplate string, args ...interface{}) { + glog.Warningln(l.getLogMsg(msgTemplate, args)) +} + +func (l *loadBalancerLogger) Infof(msgTemplate string, args ...interface{}) { + glog.Infoln(l.getLogMsg(msgTemplate, args)) +} \ No newline at end of file diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/client.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/client.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/client.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/client.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,394 @@ +package cascade + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "github.com/golang/glog" + "net/http" + "strings" + "time" +) + +// Represents stateless context needed to call Cascade APIs. +// Note that we are implementing the Cascade APIs manually instead of using the swagger generated code +// because swagger uses a different version of openapi library than kubernetes. It is difficult to +// address the version conflict to make it compile. +type Client struct { + cfg *ClientConfig + options ClientOptions + restClient *restClient +} + +type ClientConfig struct { + tenantName string + clusterID string + region string + endpoint string +} + +// Represents Tokens +type TokenOptions struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token,omitempty"` + IDToken string `json:"id_token"` + TokenType string `json:"token_type"` +} + +type TokenCallback func(string) + +// Options for Client +type ClientOptions struct { + // When using the Tasks.Wait APIs, defines the duration of how long + // we should continue to poll the server. Default is 30 minutes. + // TasksAPI.WaitTimeout() can be used to specify timeout on + // individual calls. + TaskPollTimeout time.Duration + + // Whether or not to ignore any TLS errors when talking to Cascade, + // false by default. + IgnoreCertificate bool + + // List of root CA's to use for server validation + // nil by default. + RootCAs *x509.CertPool + + // For tasks APIs, defines the number of retries to make in the event + // of an error. Default is 3. + TaskRetryCount int + + // Tokens for user authentication. Default is empty. + TokenOptions *TokenOptions +} + +const minimumTaskPollDelay = 500 * time.Millisecond + +// Creates a new Cascade client which can be used to make API calls to Cascade. +func NewClient(cfg *CascadeConfig, authClient *AuthClient) (c *Client, err error) { + tokenOptions, err := authClient.GetTokensByMachineAccount() + if err != nil { + glog.Errorf("Cascade Cloud Provider: Failed to create new client due to error: %+v", err) + return + } + + options := &ClientOptions{ + TaskPollTimeout: 30 * time.Minute, + TaskRetryCount: 3, + TokenOptions: tokenOptions, + IgnoreCertificate: false, + RootCAs: nil, + } + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: options.IgnoreCertificate, + RootCAs: options.RootCAs}, + } + + tokenCallback := func(newToken string) { + c.options.TokenOptions.AccessToken = newToken + } + + restClient := &restClient{ + authClient: authClient, + httpClient: &http.Client{Transport: tr}, + UpdateAccessTokenCallback: tokenCallback, + } + + clientConfig := &ClientConfig{ + tenantName: cfg.Global.TenantName, + clusterID: cfg.Global.ClusterID, + region: cfg.Global.Region, + endpoint: strings.TrimRight(cfg.Global.CloudTarget, "/"), + } + + c = &Client{ + cfg: clientConfig, + restClient: restClient, + // Ensure a copy of options is made, rather than using a pointer + // which may change out from underneath if misused by the caller. + options: *options, + } + + return +} + +// Gets VM with the specified ID. +func (api *Client) GetVM(vmID string) (vm *VM, err error) { + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/vms/%s", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID, vmID) + res, err := api.restClient.Get(uri, api.options.TokenOptions) + if err != nil { + return + } + defer res.Body.Close() + res, err = getError(res) + if err != nil { + return + } + vm = &VM{} + err = json.NewDecoder(res.Body).Decode(vm) + return +} + +// Gets disk with the specified ID. +func (api *Client) GetDisk(diskID string) (disk *PersistentDisk, err error) { + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/disks/%s", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID, diskID) + res, err := api.restClient.Get(uri, api.options.TokenOptions) + if err != nil { + return + } + defer res.Body.Close() + res, err = getError(res) + if err != nil { + return + } + disk = &PersistentDisk{} + err = json.NewDecoder(res.Body).Decode(disk) + return +} + +// Creates a disk under the cluster. +func (api *Client) CreateDisk(spec *DiskCreateSpec) (task *Task, err error) { + body, err := json.Marshal(spec) + if err != nil { + return + } + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/disks", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID) + res, err := api.restClient.Post(uri, "application/json", bytes.NewReader(body), api.options.TokenOptions) + if err != nil { + return + } + defer res.Body.Close() + task, err = getTask(getError(res)) + return +} + +// Deletes a disk with the specified ID. +func (api *Client) DeleteDisk(diskID string) (task *Task, err error) { + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/disks/%s", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID, diskID) + res, err := api.restClient.Delete(uri, api.options.TokenOptions) + if err != nil { + return + } + defer res.Body.Close() + task, err = getTask(getError(res)) + return +} + +// Attaches a disk to the specified VM. +func (api *Client) AttachDisk(vmID string, op *VMDiskOperation) (task *Task, err error) { + body, err := json.Marshal(op) + if err != nil { + return + } + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/vms/%s/attach_disk", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID, vmID) + res, err := api.restClient.Post(uri, "application/json", bytes.NewReader(body), api.options.TokenOptions) + if err != nil { + return + } + defer res.Body.Close() + task, err = getTask(getError(res)) + return +} + +// Detaches a disk from the specified VM. +func (api *Client) DetachDisk(vmID string, op *VMDiskOperation) (task *Task, err error) { + body, err := json.Marshal(op) + if err != nil { + return + } + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/vms/%s/detach_disk", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID, vmID) + res, err := api.restClient.Post(uri, "application/json", bytes.NewReader(body), api.options.TokenOptions) + if err != nil { + return + } + defer res.Body.Close() + task, err = getTask(getError(res)) + return +} + +// Gets a task by ID. +func (api *Client) GetTask(taskID string) (task *Task, err error) { + uri := fmt.Sprintf("%s/v1/tenants/%s/tasks/%s?region=%s", api.cfg.endpoint, api.cfg.tenantName, + taskID, api.cfg.region) + res, err := api.restClient.Get(uri, api.options.TokenOptions) + if err != nil { + return + } + defer res.Body.Close() + result, err := getTask(getError(res)) + return result, err +} + +// Waits for a task to complete by polling the tasks API until a task returns with the state COMPLETED or ERROR. +func (api *Client) WaitForTask(taskID string) (task *Task, err error) { + start := time.Now() + numErrors := 0 + maxErrors := api.options.TaskRetryCount + backoffMultiplier := 1 + + for time.Since(start) < api.options.TaskPollTimeout { + task, err = api.GetTask(taskID) + if err != nil { + switch err.(type) { + // If an ApiError comes back, something is wrong, return the error to the caller + case APIError: + return + // For other errors, retry before giving up + default: + numErrors++ + if numErrors > maxErrors { + return + } + } + } else { + // Reset the error count any time a successful call is made + numErrors = 0 + if StringVal(task.State) == "COMPLETED" { + return + } + if StringVal(task.State) == "ERROR" { + err = TaskError{StringVal(task.ID), getFailedStep(task)} + return + } + } + + // Perform backoff based on how long it has been since we started polling. The logic is as follows: + // For the first 10 seconds, poll every 500 milliseconds. + // From there till the first 1 minute, poll every 1 second. + // From there till the first 10 minutes, poll every 5 seconds. + // From there till the timeout (30 minutes), poll every 10 seconds. + elapsedTime := time.Since(start) + if elapsedTime > 10*time.Second && elapsedTime <= 60*time.Second { + backoffMultiplier = 2 + } else if elapsedTime > 60*time.Second && elapsedTime <= 600*time.Second { + backoffMultiplier = 10 + } else if elapsedTime > 600*time.Second && elapsedTime <= api.options.TaskPollTimeout { + backoffMultiplier = 20 + } + time.Sleep(time.Duration(backoffMultiplier) * minimumTaskPollDelay) + } + err = TaskTimeoutError{taskID} + return +} + +// CreateOrUpdateLoadBalancer creates a load balancer if not existed, or update one otherwise +func (api *Client) CreateOrUpdateLoadBalancer(spec *LoadBalancerCreateSpec) (*Task, error) { + body, err := json.Marshal(spec) + if err != nil { + return nil, err + } + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/loadbalancers", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID) + res, err := api.restClient.Post(uri, "application/json", bytes.NewReader(body), api.options.TokenOptions) + if err != nil { + return nil, err + } + defer res.Body.Close() + return getTask(getError(res)) +} + +// GetLoadBalancer returns a load balancer by name +func (api *Client) GetLoadBalancer(loadBalancerName *string) (*LoadBalancer, error) { + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/loadbalancers/%s", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID, StringVal(loadBalancerName)) + res, err := api.restClient.Get(uri, api.options.TokenOptions) + if err != nil { + return nil, err + } + defer res.Body.Close() + res, err = getError(res) + if err != nil { + return nil, err + } + loadBalancer := &LoadBalancer{} + err = json.NewDecoder(res.Body).Decode(loadBalancer) + return loadBalancer, err +} + +// DeleteLoadBalancer deletes a load balancer by name +func (api *Client) DeleteLoadBalancer(loadBalancerName *string) (*Task, error) { + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/loadbalancers/%s", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID, StringVal(loadBalancerName)) + res, err := api.restClient.Delete(uri, api.options.TokenOptions) + if err != nil { + return nil, err + } + return getTask(getError(res)) +} + +// ApplyVMsToLoadBalancer updates the instances that are registered with the load balancer +func (api *Client) ApplyVMsToLoadBalancer(loadBalancerName *string, update *LoadBalancerVMUpdate) (*Task, error) { + body, err := json.Marshal(update) + if err != nil { + return nil, err + } + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/loadbalancers/%s/update_vms", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID, StringVal(loadBalancerName)) + res, err := api.restClient.Post(uri, "application/json", bytes.NewReader(body), api.options.TokenOptions) + if err != nil { + return nil, err + } + defer res.Body.Close() + return getTask(getError(res)) +} + +// Gets all the zones in which the cluster has the VMs in. +func (api *Client) GetZones() (zones []string, err error) { + uri := fmt.Sprintf("%s/v1/tenants/%s/clusters/%s/zones", api.cfg.endpoint, api.cfg.tenantName, + api.cfg.clusterID) + res, err := api.restClient.Get(uri, api.options.TokenOptions) + if err != nil { + return + } + defer res.Body.Close() + res, err = getError(res) + if err != nil { + return + } + err = json.NewDecoder(res.Body).Decode(&zones) + return +} + +// Reads a task object out of the HTTP response. Takes an error argument +// so that GetTask can easily wrap GetError. This function will do nothing +// if e is not nil. +// e.g. res, err := getTask(getError(someApi.Get())) +func getTask(res *http.Response, e error) (*Task, error) { + if e != nil { + return nil, e + } + var task Task + err := json.NewDecoder(res.Body).Decode(&task) + if err != nil { + return nil, err + } + if StringVal(task.State) == "ERROR" { + // Critical: return task as well, so that it can be examined + // for error details. + return &task, TaskError{StringVal(task.ID), getFailedStep(&task)} + } + return &task, nil +} + +// Gets the failed step in the task to get error details for failed task. +func getFailedStep(task *Task) (step Step) { + var errorStep Step + for _, s := range task.Steps { + if StringVal(s.State) == "ERROR" { + errorStep = *s + break + } + } + + return errorStep +} diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/oidcclient.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/oidcclient.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/oidcclient.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/oidcclient.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,297 @@ +package cascade + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" +) + +const tokenScope string = "openid offline_access" + +// OIDCClient is client for OIDC +type OIDCClient struct { + httpClient *http.Client + logger *log.Logger + + Endpoint string + Options *OIDCClientOptions +} + +// OIDCClientOptions is OIDC client options +type OIDCClientOptions struct { + // Whether or not to ignore any TLS errors when talking to Cascade, + // false by default. + IgnoreCertificate bool + + // List of root CA's to use for server validation + // nil by default. + RootCAs *x509.CertPool + + // The scope values to use when requesting tokens + TokenScope string +} + +// NewOIDCClient creates an instance of OIDCClient +func NewOIDCClient(endpoint string, options *OIDCClientOptions, logger *log.Logger) (c *OIDCClient) { + if logger == nil { + logger = log.New(ioutil.Discard, "", log.LstdFlags) + } + + options = buildOptions(options) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: options.IgnoreCertificate, + RootCAs: options.RootCAs}, + } + + c = &OIDCClient{ + httpClient: &http.Client{Transport: tr}, + logger: logger, + Endpoint: strings.TrimRight(endpoint, "/"), + Options: options, + } + return +} + +func buildOptions(options *OIDCClientOptions) (result *OIDCClientOptions) { + result = &OIDCClientOptions{ + TokenScope: tokenScope, + } + + if options == nil { + return + } + + result.IgnoreCertificate = options.IgnoreCertificate + + if options.RootCAs != nil { + result.RootCAs = options.RootCAs + } + + if options.TokenScope != "" { + result.TokenScope = options.TokenScope + } + + return +} + +func (client *OIDCClient) buildURL(path string) (url string) { + return fmt.Sprintf("%s%s", client.Endpoint, path) +} + +// Cert download helper + +const certDownloadPath string = "/afd/vecs/ssl" + +type lightWaveCert struct { + Value string `json:"encoded"` +} + +// GetRootCerts gets root certs +func (client *OIDCClient) GetRootCerts() (certList []*x509.Certificate, err error) { + // turn TLS verification off for + originalTr := client.httpClient.Transport + defer client.setTransport(originalTr) + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: false, + }, + } + client.setTransport(tr) + + // get the certs + resp, err := client.httpClient.Get(client.buildURL(certDownloadPath)) + if err != nil { + return + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + err = fmt.Errorf("Unexpected error retrieving auth server certs: %v %s", resp.StatusCode, resp.Status) + return + } + + // parse the certs + certsData := &[]lightWaveCert{} + err = json.NewDecoder(resp.Body).Decode(certsData) + if err != nil { + return + } + + certList = make([]*x509.Certificate, len(*certsData)) + for idx, cert := range *certsData { + block, _ := pem.Decode([]byte(cert.Value)) + if block == nil { + err = fmt.Errorf("Unexpected response format: %v", certsData) + return nil, err + } + + decodedCert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + + certList[idx] = decodedCert + } + + return +} + +func (client *OIDCClient) setTransport(tr http.RoundTripper) { + client.httpClient.Transport = tr +} + +// Metadata request helpers +const metadataPathFormat string = "/openidconnect/%s/.well-known/openid-configuration" + +// OIDCMetadataResponse is the response for Metadata request +type OIDCMetadataResponse struct { + TokenEndpoint string `json:"token_endpoint"` + AuthorizationEndpoint string `json:"authorization_endpoint"` + EndSessionEndpoint string `json:"end_session_endpoint"` +} + +func (client *OIDCClient) getMetadata(domain string) (metadata *OIDCMetadataResponse, err error) { + metadataPath := fmt.Sprintf(metadataPathFormat, domain) + request, err := http.NewRequest("GET", client.buildURL(metadataPath), nil) + if err != nil { + return nil, err + } + + resp, err := client.httpClient.Do(request) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + err = client.checkResponse(resp) + if err != nil { + return nil, err + } + + metadata = &OIDCMetadataResponse{} + err = json.NewDecoder(resp.Body).Decode(metadata) + if err != nil { + return nil, err + } + + return +} + +// Token request helpers + +const passwordGrantFormatString = "grant_type=password&username=%s&password=%s&scope=%s" +const refreshTokenGrantFormatString = "grant_type=refresh_token&refresh_token=%s" +const clientGrantFormatString = "grant_type=password&username=%s&password=%s&scope=%s&client_id=%s" + +// OIDCTokenResponse is the response for OIDC request +type OIDCTokenResponse struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token,omitempty"` + IDToken string `json:"id_token"` + TokenType string `json:"token_type"` +} + +// GetTokenByPasswordGrant gets OIDC tokens by password +func (client *OIDCClient) GetTokenByPasswordGrant(domain, username, password string) (tokens *OIDCTokenResponse, err error) { + metadata, err := client.getMetadata(domain) + if err != nil { + return nil, err + } + + username = url.QueryEscape(username) + password = url.QueryEscape(password) + body := fmt.Sprintf(passwordGrantFormatString, username, password, client.Options.TokenScope) + return client.getToken(metadata.TokenEndpoint, body) +} + +// GetClientTokenByPasswordGrant gets OIDC tokens by password +func (client *OIDCClient) GetClientTokenByPasswordGrant(domain, username, password, clientID string) (tokens *OIDCTokenResponse, err error) { + metadata, err := client.getMetadata(domain) + if err != nil { + return nil, err + } + + username = url.QueryEscape(username) + password = url.QueryEscape(password) + clientID = url.QueryEscape(clientID) + body := fmt.Sprintf(clientGrantFormatString, username, password, client.Options.TokenScope, clientID) + return client.getToken(metadata.TokenEndpoint, body) +} + +// GetTokenByRefreshTokenGrant gets OIDC tokens by refresh token +func (client *OIDCClient) GetTokenByRefreshTokenGrant(domain, refreshToken string) (tokens *OIDCTokenResponse, err error) { + metadata, err := client.getMetadata(domain) + if err != nil { + return nil, err + } + + body := fmt.Sprintf(refreshTokenGrantFormatString, refreshToken) + return client.getToken(metadata.TokenEndpoint, body) +} + +func (client *OIDCClient) getToken(tokenEndpoint, body string) (tokens *OIDCTokenResponse, err error) { + request, err := http.NewRequest("POST", tokenEndpoint, strings.NewReader(body)) + if err != nil { + return nil, err + } + request.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := client.httpClient.Do(request) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + err = client.checkResponse(resp) + if err != nil { + return nil, err + } + + tokens = &OIDCTokenResponse{} + err = json.NewDecoder(resp.Body).Decode(tokens) + if err != nil { + return nil, err + } + + return +} + +// OIDCError is OIDC error +type OIDCError struct { + Code string `json:"error"` + Message string `json:"error_description"` +} + +func (e OIDCError) Error() string { + return fmt.Sprintf("%v: %v", e.Code, e.Message) +} + +func (client *OIDCClient) checkResponse(response *http.Response) (err error) { + if response.StatusCode/100 == 2 { + return + } + + respBody, readErr := ioutil.ReadAll(response.Body) + if readErr != nil { + return fmt.Errorf( + "Status: %v, Body: %v [%v]", response.Status, string(respBody[:]), readErr) + } + + var oidcErr OIDCError + err = json.Unmarshal(respBody, &oidcErr) + if err != nil || oidcErr.Code == "" { + return fmt.Errorf( + "Status: %v, Body: %v [%v]", response.Status, string(respBody[:]), readErr) + } + + return oidcErr +} diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/OWNERS kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/OWNERS --- kubernetes-original/pkg/cloudprovider/providers/cascade/OWNERS 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/OWNERS 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,3 @@ +maintainers: +- ashokc +- ysheng diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/restclient.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/restclient.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/restclient.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/restclient.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,262 @@ +package cascade + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" +) + +type restClient struct { + httpClient *http.Client + authClient *AuthClient + UpdateAccessTokenCallback TokenCallback +} + +type request struct { + Method string + URL string + ContentType string + Body io.Reader + Tokens *TokenOptions +} + +type page struct { + Items []interface{} `json:"items"` + NextPageLink string `json:"nextPageLink"` + PreviousPageLink string `json:"previousPageLink"` +} + +type documentList struct { + Items []interface{} +} + +type bodyRewinder func() io.Reader + +const appJson string = "application/json" +const expiredAuthToken int32 = 1904 + +func (client *restClient) AppendSlice(origSlice []interface{}, dataToAppend []interface{}) []interface{} { + origLen := len(origSlice) + newLen := origLen + len(dataToAppend) + + if newLen > cap(origSlice) { + newSlice := make([]interface{}, (newLen+1)*2) + copy(newSlice, origSlice) + origSlice = newSlice + } + + origSlice = origSlice[0:newLen] + copy(origSlice[origLen:newLen], dataToAppend) + + return origSlice +} + +func (client *restClient) Get(url string, tokens *TokenOptions) (res *http.Response, err error) { + req := request{"GET", url, "", nil, tokens} + res, err = client.SendRequest(&req, nil) + return +} + +func (client *restClient) GetList(endpoint string, url string, tokens *TokenOptions) (result []byte, err error) { + req := request{"GET", url, "", nil, tokens} + res, err := client.SendRequest(&req, nil) + if err != nil { + return + } + res, err = getError(res) + if err != nil { + return + } + + decoder := json.NewDecoder(res.Body) + decoder.UseNumber() + + page := &page{} + err = decoder.Decode(page) + if err != nil { + return + } + + documentList := &documentList{} + documentList.Items = client.AppendSlice(documentList.Items, page.Items) + + for page.NextPageLink != "" { + req = request{"GET", endpoint + page.NextPageLink, "", nil, tokens} + res, err = client.SendRequest(&req, nil) + if err != nil { + return + } + res, err = getError(res) + if err != nil { + return + } + + decoder = json.NewDecoder(res.Body) + decoder.UseNumber() + + page.NextPageLink = "" + page.PreviousPageLink = "" + + err = decoder.Decode(page) + if err != nil { + return + } + + documentList.Items = client.AppendSlice(documentList.Items, page.Items) + } + + result, err = json.Marshal(documentList) + + return +} + +func (client *restClient) Post(url string, contentType string, body io.ReadSeeker, tokens *TokenOptions) (res *http.Response, err error) { + if contentType == "" { + contentType = appJson + } + + req := request{"POST", url, contentType, body, tokens} + rewinder := func() io.Reader { + body.Seek(0, 0) + return body + } + res, err = client.SendRequest(&req, rewinder) + return +} + +func (client *restClient) Patch(url string, contentType string, body io.ReadSeeker, tokens *TokenOptions) (res *http.Response, err error) { + if contentType == "" { + contentType = appJson + } + + req := request{"PATCH", url, contentType, body, tokens} + rewinder := func() io.Reader { + body.Seek(0, 0) + return body + } + res, err = client.SendRequest(&req, rewinder) + return +} + +func (client *restClient) Put(url string, contentType string, body io.ReadSeeker, tokens *TokenOptions) (res *http.Response, err error) { + if contentType == "" { + contentType = appJson + } + + req := request{"PUT", url, contentType, body, tokens} + rewinder := func() io.Reader { + body.Seek(0, 0) + return body + } + res, err = client.SendRequest(&req, rewinder) + return +} + +func (client *restClient) Delete(url string, tokens *TokenOptions) (res *http.Response, err error) { + req := request{"DELETE", url, "", nil, tokens} + res, err = client.SendRequest(&req, nil) + return +} + +func (client *restClient) SendRequest(req *request, bodyRewinder bodyRewinder) (res *http.Response, err error) { + res, err = client.sendRequestHelper(req) + // In most cases, we'll return immediately + // If the operation succeeded, but we got a 401 response and if we're using + // authentication, then we'll look into the body to see if the token expired + if err != nil { + return res, err + } + if res.StatusCode != 401 { + // It's not a 401, so the token didn't expire + return res, err + } + if req.Tokens == nil || req.Tokens.AccessToken == "" { + // We don't have a token, so we can't renew the token, no need to proceed + return res, err + } + + // We're going to look in the body to see if it failed because the token expired + // This means we need to read the body, but the functions that call us also + // expect to read the body. So we read the body, then create a new reader + // so they can read the body as normal. + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return res, err + } + res.Body = ioutil.NopCloser(bytes.NewReader(body)) + + // Now see if we had an expired token or not + var apiError APIError + err = json.Unmarshal(body, &apiError) + if err != nil { + return res, err + } + if apiError.ErrorCode != expiredAuthToken { + return res, nil + } + + // We were told that the access token expired, so we acquire a new token using the refresh token. + newTokens, err := client.authClient.GetTokensByRefreshToken(req.Tokens.RefreshToken) + // If there is an error during token refresh, we assume that the refresh token also expired. So we login again using + // the machine account. + if err != nil { + newTokens, err = client.authClient.GetTokensByMachineAccount() + if err != nil { + return res, err + } + } + req.Tokens.AccessToken = newTokens.AccessToken + if client.UpdateAccessTokenCallback != nil { + client.UpdateAccessTokenCallback(newTokens.AccessToken) + } + if req.Body != nil && bodyRewinder != nil { + req.Body = bodyRewinder() + } + res, err = client.sendRequestHelper(req) + return res, nil +} + +func (client *restClient) sendRequestHelper(req *request) (res *http.Response, err error) { + r, err := http.NewRequest(req.Method, req.URL, req.Body) + if err != nil { + return + } + if req.ContentType != "" { + r.Header.Add("Content-Type", req.ContentType) + } + if req.Tokens != nil && req.Tokens.AccessToken != "" { + r.Header.Add("Authorization", "Bearer "+req.Tokens.AccessToken) + } + res, err = client.httpClient.Do(r) + if err != nil { + return + } + + return +} + +// Reads an error out of the HTTP response, or does nothing if +// no error occured. +func getError(res *http.Response) (*http.Response, error) { + // Do nothing if the response is a successful 2xx + if res.StatusCode/100 == 2 { + return res, nil + } + var apiError APIError + // ReadAll is usually a bad practice, but here we need to read the response all + // at once because we may attempt to use the data twice. It's preferable to use + // methods that take io.Reader, e.g. json.NewDecoder + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + err = json.Unmarshal(body, &apiError) + if err != nil { + // If deserializing into ApiError fails, return a generic HttpError instead + return nil, HttpError{res.StatusCode, string(body[:])} + } + apiError.HttpStatusCode = res.StatusCode + return nil, apiError +} diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/tests_owed kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/tests_owed --- kubernetes-original/pkg/cloudprovider/providers/cascade/tests_owed 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/tests_owed 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,5 @@ + +Yu Sheng +Change-Id: Ifc11818f65a3e018aeea6988d9e2c0719b592920 + + diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/cascade/utils.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/utils.go --- kubernetes-original/pkg/cloudprovider/providers/cascade/utils.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/cascade/utils.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,25 @@ +package cascade + +func StringPtr(s string) *string { + return &s +} + +// StringVal returns string from string pointer, nil returns "" +func StringVal(p *string) (s string) { + if p != nil { + s = *p + } + return +} + +func Int64Ptr(s int64) *int64 { + return &s +} + +func Int64Val(s *int64) int64 { + return *s +} + +func Int32Ptr(s int32) *int32 { + return &s +} \ No newline at end of file diff -uNr --no-dereference kubernetes-original/pkg/cloudprovider/providers/providers.go kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/providers.go --- kubernetes-original/pkg/cloudprovider/providers/providers.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/cloudprovider/providers/providers.go 2018-04-30 21:11:51.000000000 +0000 @@ -20,6 +20,7 @@ // Cloud providers _ "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" _ "k8s.io/kubernetes/pkg/cloudprovider/providers/azure" + _ "k8s.io/kubernetes/pkg/cloudprovider/providers/cascade" _ "k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack" _ "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" _ "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack" diff -uNr --no-dereference kubernetes-original/pkg/printers/internalversion/describe.go kubernetes-modified/src/k8s.io/kubernetes/pkg/printers/internalversion/describe.go --- kubernetes-original/pkg/printers/internalversion/describe.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/printers/internalversion/describe.go 2018-04-30 21:11:51.000000000 +0000 @@ -751,6 +751,8 @@ printFlexVolumeSource(volume.VolumeSource.FlexVolume, w) case volume.VolumeSource.Flocker != nil: printFlockerVolumeSource(volume.VolumeSource.Flocker, w) + case volume.VolumeSource.CascadeDisk != nil: + printCascadeDiskVolumeSource(volume.VolumeSource.CascadeDisk, w) default: w.Write(LEVEL_1, "<unknown>\n") } @@ -1101,6 +1103,13 @@ csi.Driver, csi.VolumeHandle, csi.ReadOnly) } +func printCascadeDiskVolumeSource(cascade *api.CascadeDiskVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCascadeDisk (a Persistent Disk resource in Cascade)\n"+ + " DiskID:\t%v\n"+ + " FSType:\t%v\n", + cascade.DiskID, cascade.FSType) +} + type PersistentVolumeDescriber struct { clientset.Interface } @@ -1189,6 +1198,8 @@ printFlockerVolumeSource(pv.Spec.Flocker, w) case pv.Spec.CSI != nil: printCSIPersistentVolumeSource(pv.Spec.CSI, w) + case pv.Spec.CascadeDisk != nil: + printCascadeDiskVolumeSource(pv.Spec.CascadeDisk, w) default: w.Write(LEVEL_1, "<unknown>\n") } diff -uNr --no-dereference kubernetes-original/pkg/security/podsecuritypolicy/util/util.go kubernetes-modified/src/k8s.io/kubernetes/pkg/security/podsecuritypolicy/util/util.go --- kubernetes-original/pkg/security/podsecuritypolicy/util/util.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/security/podsecuritypolicy/util/util.go 2018-04-30 21:11:51.000000000 +0000 @@ -68,6 +68,7 @@ string(extensions.PortworxVolume), string(extensions.ScaleIO), string(extensions.CSI), + string(extensions.CascadeDisk), ) return fstypes } @@ -129,6 +130,8 @@ return extensions.PortworxVolume, nil case v.ScaleIO != nil: return extensions.ScaleIO, nil + case v.CascadeDisk != nil: + return extensions.CascadeDisk, nil } return "", fmt.Errorf("unknown volume type for volume: %#v", v) diff -uNr --no-dereference kubernetes-original/pkg/volume/cascade_disk/attacher.go kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/attacher.go --- kubernetes-original/pkg/volume/cascade_disk/attacher.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/attacher.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,269 @@ +package cascade_disk + +import ( + "fmt" + "os" + "path" + "time" + + "github.com/golang/glog" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/cloudprovider/providers/cascade" + "k8s.io/kubernetes/pkg/util/mount" + "k8s.io/kubernetes/pkg/volume" + volumeutil "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/kubernetes/pkg/volume/util/volumehelper" + "strings" +) + +type cascadeDiskAttacher struct { + host volume.VolumeHost + cascadeDisks cascade.Disks +} + +var _ volume.Attacher = &cascadeDiskAttacher{} +var _ volume.AttachableVolumePlugin = &cascadeDiskPlugin{} + +func (plugin *cascadeDiskPlugin) NewAttacher() (volume.Attacher, error) { + cascadeCloud, err := getCloudProvider(plugin.host.GetCloudProvider()) + if err != nil { + glog.Errorf("Cascade attacher: NewAttacher failed to get cloud provider") + return nil, err + } + + return &cascadeDiskAttacher{ + host: plugin.host, + cascadeDisks: cascadeCloud, + }, nil +} + +// Attach attaches the volume specified by the given spec to the given host. On success, returns the device path where +// the device was attached on the node. +func (attacher *cascadeDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { + hostName := string(nodeName) + volumeSource, _, err := getVolumeSource(spec) + if err != nil { + glog.Errorf("Cascade attacher: Attach failed to get volume source") + return "", err + } + + // cascadeDisks.AttachDisk checks if disk is already attached to the node. So we don't have to do that separately + // here. + glog.V(4).Infof("Cascade: Attach disk called for host %s", hostName) + devicePath, err := attacher.cascadeDisks.AttachDisk(volumeSource.DiskID, nodeName) + if err != nil { + glog.Errorf("Error attaching volume %q to node %q: %+v", volumeSource.DiskID, nodeName, err) + return "", err + } + + // Cacsade uses device names of the format /dev/sdX, but newer Linux Kernels mount them under /dev/xvdX + // (source: AWS console). So we have to rename the first occurrence of sd to xvd. + devicePath = strings.Replace(devicePath, "sd", "xvd", 1) + return devicePath, nil +} + +// VolumesAreAttached verifies whether the volumes specified in the spec are attached to the specified node. +func (attacher *cascadeDiskAttacher) VolumesAreAttached(specs []*volume.Spec, + nodeName types.NodeName) (map[*volume.Spec]bool, error) { + volumesAttachedCheck := make(map[*volume.Spec]bool) + volumeSpecMap := make(map[string]*volume.Spec) + diskIDList := []string{} + for _, spec := range specs { + volumeSource, _, err := getVolumeSource(spec) + if err != nil { + glog.Errorf("Error getting volume (%q) source : %v", spec.Name(), err) + continue + } + + diskIDList = append(diskIDList, volumeSource.DiskID) + volumesAttachedCheck[spec] = true + volumeSpecMap[volumeSource.DiskID] = spec + } + attachedResult, err := attacher.cascadeDisks.DisksAreAttached(diskIDList, nodeName) + if err != nil { + glog.Errorf( + "Error checking if volumes (%v) are attached to current node (%q). err=%v", + diskIDList, nodeName, err) + return volumesAttachedCheck, err + } + + for diskID, attached := range attachedResult { + if !attached { + spec := volumeSpecMap[diskID] + volumesAttachedCheck[spec] = false + glog.V(2).Infof("VolumesAreAttached: check volume %q (specName: %q) is no longer attached", + diskID, spec.Name()) + } + } + return volumesAttachedCheck, nil +} + +// WaitForAttach waits until the devicePath returned by the Attach call is available. +func (attacher *cascadeDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, + timeout time.Duration) (string, error) { + volumeSource, _, err := getVolumeSource(spec) + if err != nil { + glog.Errorf("Cascade attacher: WaitForAttach failed to get volume source") + return "", err + } + + if devicePath == "" { + return "", fmt.Errorf("WaitForAttach failed for disk %s: devicePath is empty.", volumeSource.DiskID) + } + + ticker := time.NewTicker(checkSleepDuration) + defer ticker.Stop() + + timer := time.NewTimer(timeout) + defer timer.Stop() + + for { + select { + case <-ticker.C: + glog.V(4).Infof("Checking disk %s is attached", volumeSource.DiskID) + checkPath, err := verifyDevicePath(devicePath) + if err != nil { + // Log error, if any, and continue checking periodically. See issue #11321 + glog.Warningf("Cascade attacher: WaitForAttach with devicePath %s Checking PD %s Error verify "+ + "path", devicePath, volumeSource.DiskID) + } else if checkPath != "" { + // A device path has successfully been created for the disk + glog.V(4).Infof("Successfully found attached disk %s.", volumeSource.DiskID) + return devicePath, nil + } + case <-timer.C: + return "", fmt.Errorf("Could not find attached disk %s. Timeout waiting for mount paths to be "+ + "created.", volumeSource.DiskID) + } + } +} + +// GetDeviceMountPath returns a path where the device should point which should be bind mounted for individual volumes. +func (attacher *cascadeDiskAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) { + volumeSource, _, err := getVolumeSource(spec) + if err != nil { + glog.Errorf("Cascade attacher: GetDeviceMountPath failed to get volume source") + return "", err + } + + return makeGlobalPDPath(attacher.host, volumeSource.DiskID), nil +} + +// GetMountDeviceRefs finds all other references to the device referenced by deviceMountPath; returns a list of paths. +func (plugin *cascadeDiskPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { + mounter := plugin.host.GetMounter(plugin.GetPluginName()) + return mount.GetMountRefs(mounter, deviceMountPath) +} + +// MountDevice mounts device to global mount point. +func (attacher *cascadeDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { + mounter := attacher.host.GetMounter(cascadeDiskPluginName) + notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) + if err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(deviceMountPath, 0750); err != nil { + glog.Errorf("Failed to create directory at %#v. err: %s", deviceMountPath, err) + return err + } + notMnt = true + } else { + return err + } + } + + volumeSource, _, err := getVolumeSource(spec) + if err != nil { + glog.Errorf("Cascade attacher: MountDevice failed to get volume source. err: %s", err) + return err + } + + options := []string{} + + if notMnt { + diskMounter := volumehelper.NewSafeFormatAndMountFromHost(cascadeDiskPluginName, attacher.host) + mountOptions := volume.MountOptionFromSpec(spec) + err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions) + if err != nil { + os.Remove(deviceMountPath) + return err + } + glog.V(4).Infof("formatting spec %v devicePath %v deviceMountPath %v fs %v with options %+v", + spec.Name(), devicePath, deviceMountPath, volumeSource.FSType, options) + } + return nil +} + +type cascadeDiskDetacher struct { + mounter mount.Interface + cascadeDisks cascade.Disks +} + +var _ volume.Detacher = &cascadeDiskDetacher{} + +// NewDetacher returns the detacher associated with the Cascade volume plugin. +func (plugin *cascadeDiskPlugin) NewDetacher() (volume.Detacher, error) { + cascadeCloud, err := getCloudProvider(plugin.host.GetCloudProvider()) + if err != nil { + glog.Errorf("Cascade attacher: NewDetacher failed to get cloud provider. err: %s", err) + return nil, err + } + + return &cascadeDiskDetacher{ + mounter: plugin.host.GetMounter(plugin.GetPluginName()), + cascadeDisks: cascadeCloud, + }, nil +} + +// Detach detaches the given device from the given host. +func (detacher *cascadeDiskDetacher) Detach(deviceMountPath string, nodeName types.NodeName) error { + hostName := string(nodeName) + diskID := path.Base(deviceMountPath) + attached, err := detacher.cascadeDisks.DiskIsAttached(diskID, nodeName) + if err != nil { + // Log error and continue with detach + glog.Errorf( + "Error checking if persistent disk (%q) is already attached to current node (%q). "+ + "Will continue and try detach anyway. err=%v", diskID, hostName, err) + } + + if err == nil && !attached { + // Volume is already detached from node. + glog.V(4).Infof("detach operation was successful. persistent disk %q is already detached "+ + "from node %q.", diskID, hostName) + return nil + } + + if err := detacher.cascadeDisks.DetachDisk(diskID, nodeName); err != nil { + glog.Errorf("Error detaching volume %q: %v", diskID, err) + return err + } + return nil +} + +// WaitForDetach waits for the devicePath to become unavailable. +func (detacher *cascadeDiskDetacher) WaitForDetach(devicePath string, timeout time.Duration) error { + ticker := time.NewTicker(checkSleepDuration) + defer ticker.Stop() + timer := time.NewTimer(timeout) + defer timer.Stop() + + for { + select { + case <-ticker.C: + glog.V(4).Infof("Checking device %q is detached.", devicePath) + if pathExists, err := volumeutil.PathExists(devicePath); err != nil { + return fmt.Errorf("Error checking if device path exists: %v", err) + } else if !pathExists { + return nil + } + case <-timer.C: + return fmt.Errorf("Timeout reached; Device %v is still attached", devicePath) + } + } +} + +// UnmountDevice unmounts the disk specified by the device mount path. +func (detacher *cascadeDiskDetacher) UnmountDevice(deviceMountPath string) error { + return volumeutil.UnmountPath(deviceMountPath, detacher.mounter) +} diff -uNr --no-dereference kubernetes-original/pkg/volume/cascade_disk/BUILD kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/BUILD --- kubernetes-original/pkg/volume/cascade_disk/BUILD 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/BUILD 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,43 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = [ + "attacher.go", + "cascade_disk.go", + "cascade_util.go", + ], + deps = [ + "//pkg/cloudprovider:go_default_library", + "//pkg/cloudprovider/providers/cascade:go_default_library", + "//pkg/util/mount:go_default_library", + "//pkg/util/strings:go_default_library", + "//pkg/volume:go_default_library", + "//pkg/volume/util:go_default_library", + "//pkg/volume/util/volumehelper:go_default_library", + "//vendor/github.com/golang/glog:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff -uNr --no-dereference kubernetes-original/pkg/volume/cascade_disk/cascade_disk.go kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/cascade_disk.go --- kubernetes-original/pkg/volume/cascade_disk/cascade_disk.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/cascade_disk.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,391 @@ +package cascade_disk + +import ( + "fmt" + "os" + "path" + + "github.com/golang/glog" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubernetes/pkg/util/mount" + utilstrings "k8s.io/kubernetes/pkg/util/strings" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/util" + "k8s.io/kubernetes/pkg/volume/util/volumehelper" +) + +// This is the primary entrypoint for volume plugins. +func ProbeVolumePlugins() []volume.VolumePlugin { + return []volume.VolumePlugin{&cascadeDiskPlugin{}} +} + +type cascadeDiskPlugin struct { + host volume.VolumeHost +} + +var _ volume.VolumePlugin = &cascadeDiskPlugin{} +var _ volume.PersistentVolumePlugin = &cascadeDiskPlugin{} +var _ volume.DeletableVolumePlugin = &cascadeDiskPlugin{} +var _ volume.ProvisionableVolumePlugin = &cascadeDiskPlugin{} + +const ( + cascadeDiskPluginName = "kubernetes.io/cascade-disk" +) + +// Init initializes the Cascade volume plugin. +func (plugin *cascadeDiskPlugin) Init(host volume.VolumeHost) error { + plugin.host = host + return nil +} + +// GetPluginName returns the name of the Cascade volume plugin. +func (plugin *cascadeDiskPlugin) GetPluginName() string { + return cascadeDiskPluginName +} + +// GetVolumeName returns the name of the volume which is the diskID in our case. +func (plugin *cascadeDiskPlugin) GetVolumeName(spec *volume.Spec) (string, error) { + volumeSource, _, err := getVolumeSource(spec) + if err != nil { + glog.Errorf("Cascade volume plugin: GetVolumeName failed to get volume source") + return "", err + } + + return volumeSource.DiskID, nil +} + +// CanSupport specifies whether the Cascade volume plguin can support the specific resource type. +// Cascade plugin only supports the persistent volume and volume resource which has the Cascade disk annotation. +func (plugin *cascadeDiskPlugin) CanSupport(spec *volume.Spec) bool { + return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CascadeDisk != nil) || + (spec.Volume != nil && spec.Volume.CascadeDisk != nil) +} + +// RequiresRemount specifies whether remount is required for the disk. +func (plugin *cascadeDiskPlugin) RequiresRemount() bool { + return false +} + +// SupportsMountOption specifies whether the Cascade volume plugin supports the mount operation. +func (plugin *cascadeDiskPlugin) SupportsMountOption() bool { + return true +} + +// SupportsBulkVolumeVerification specifies whether bulk volume verification is supported. +func (plugin *cascadeDiskPlugin) SupportsBulkVolumeVerification() bool { + return false +} + +// NewMounter returns the mounter associated with the Cascade volume plugin. +func (plugin *cascadeDiskPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, + _ volume.VolumeOptions) (volume.Mounter, error) { + return plugin.newMounterInternal(spec, pod.UID, &CascadeDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName())) +} + +// NewUnmounter returns the unmounter associated with the Cascade volume plugin. +func (plugin *cascadeDiskPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { + return plugin.newUnmounterInternal(volName, podUID, &CascadeDiskUtil{}, + plugin.host.GetMounter(plugin.GetPluginName())) +} + +func (plugin *cascadeDiskPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, + mounter mount.Interface) (volume.Mounter, error) { + volumeSource, _, err := getVolumeSource(spec) + if err != nil { + glog.Errorf("Cascade volume plugin: newMounterInternal failed to get volume source") + return nil, err + } + + diskID := volumeSource.DiskID + fsType := volumeSource.FSType + + return &cascadeDiskMounter{ + cascadeDisk: &cascadeDisk{ + podUID: podUID, + volName: spec.Name(), + diskID: diskID, + manager: manager, + mounter: mounter, + plugin: plugin, + }, + fsType: fsType, + diskMounter: volumehelper.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host)}, nil +} + +func (plugin *cascadeDiskPlugin) newUnmounterInternal(volName string, podUID types.UID, manager diskManager, + mounter mount.Interface) (volume.Unmounter, error) { + return &cascadeDiskUnmounter{ + &cascadeDisk{ + podUID: podUID, + volName: volName, + manager: manager, + mounter: mounter, + plugin: plugin, + }}, nil +} + +// ConstructVolumeSpec constructs a Cascade volume spec based on the name and mount path. +func (plugin *cascadeDiskPlugin) ConstructVolumeSpec(volumeSpecName, mountPath string) (*volume.Spec, error) { + mounter := plugin.host.GetMounter(plugin.GetPluginName()) + pluginDir := plugin.host.GetPluginDir(plugin.GetPluginName()) + diskID, err := mounter.GetDeviceNameFromMount(mountPath, pluginDir) + if err != nil { + return nil, err + } + + cascadeDisk := &v1.Volume{ + Name: volumeSpecName, + VolumeSource: v1.VolumeSource{ + CascadeDisk: &v1.CascadeDiskVolumeSource{ + DiskID: diskID, + }, + }, + } + return volume.NewSpecFromVolume(cascadeDisk), nil +} + +// Abstract interface to disk operations. +type diskManager interface { + // Creates a volume + CreateVolume(provisioner *cascadeDiskProvisioner) (diskID string, volumeSizeGB int, fstype string, err error) + // Deletes a volume + DeleteVolume(deleter *cascadeDiskDeleter) error +} + +// cascadeDisk volumes are disk resources attached to the kubelet's host machine and exposed to the pod. +type cascadeDisk struct { + volName string + podUID types.UID + diskID string + fsType string + manager diskManager + mounter mount.Interface + plugin *cascadeDiskPlugin + volume.MetricsNil +} + +var _ volume.Mounter = &cascadeDiskMounter{} + +type cascadeDiskMounter struct { + *cascadeDisk + fsType string + diskMounter *mount.SafeFormatAndMount +} + +// GetAttributes returns the attributes associated with a Cascade disk. +func (b *cascadeDiskMounter) GetAttributes() volume.Attributes { + return volume.Attributes{ + SupportsSELinux: true, + } +} + +// CanMount checks prior to mount operations to verify that the required components (binaries, etc.) to mount the +// volume are available on the underlying node. If not, it returns an error. +func (b *cascadeDiskMounter) CanMount() error { + return nil +} + +// SetUp attaches the disk and bind mounts to the volume path. +func (b *cascadeDiskMounter) SetUp(fsGroup *int64) error { + return b.SetUpAt(b.GetPath(), fsGroup) +} + +// SetUpAt attaches the disk and bind mounts to the volume path. +func (b *cascadeDiskMounter) SetUpAt(dir string, fsGroup *int64) error { + glog.V(4).Infof("Cascade Persistent Disk setup %s to %s", b.diskID, dir) + + // TODO: handle failed mounts here. + notmnt, err := b.mounter.IsLikelyNotMountPoint(dir) + if err != nil && !os.IsNotExist(err) { + glog.Errorf("cannot validate mount point: %s %v", dir, err) + return err + } + if !notmnt { + return nil + } + + if err := os.MkdirAll(dir, 0750); err != nil { + glog.Errorf("mkdir failed on disk %s (%v)", dir, err) + return err + } + + options := []string{"bind"} + + // Perform a bind mount to the full path to allow duplicate mounts of the same PD. + globalPDPath := makeGlobalPDPath(b.plugin.host, b.diskID) + glog.V(4).Infof("attempting to mount %s", dir) + + err = b.mounter.Mount(globalPDPath, dir, "", options) + if err != nil { + notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) + if mntErr != nil { + glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) + return err + } + if !notmnt { + if mntErr = b.mounter.Unmount(dir); mntErr != nil { + glog.Errorf("Failed to unmount: %v", mntErr) + return err + } + notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) + if mntErr != nil { + glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr) + return err + } + if !notmnt { + glog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", + b.GetPath()) + return err + } + } + os.Remove(dir) + glog.Errorf("Mount of disk %s failed: %v", dir, err) + return err + } + volume.SetVolumeOwnership(b, fsGroup) + + return nil +} + +var _ volume.Unmounter = &cascadeDiskUnmounter{} + +type cascadeDiskUnmounter struct { + *cascadeDisk +} + +// TearDown unmounts the bind mount, and detaches the disk only if the disk resource was the last reference to that +// disk on the kubelet. +func (c *cascadeDiskUnmounter) TearDown() error { + return c.TearDownAt(c.GetPath()) +} + +// TearDownAt unmounts the bind mount, and detaches the disk only if the disk resource was the last reference to that +// disk on the kubelet. +func (c *cascadeDiskUnmounter) TearDownAt(dir string) error { + return util.UnmountPath(dir, c.mounter) +} + +func makeGlobalPDPath(host volume.VolumeHost, diskID string) string { + return path.Join(host.GetPluginDir(cascadeDiskPluginName), mount.MountsInGlobalPDPath, diskID) +} + +func (cd *cascadeDisk) GetPath() string { + name := cascadeDiskPluginName + return cd.plugin.host.GetPodVolumeDir(cd.podUID, utilstrings.EscapeQualifiedNameForDisk(name), cd.volName) +} + +func (plugin *cascadeDiskPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { + return []v1.PersistentVolumeAccessMode{ + v1.ReadWriteOnce, + } +} + +type cascadeDiskDeleter struct { + *cascadeDisk +} + +var _ volume.Deleter = &cascadeDiskDeleter{} + +// NewDeleter returns the deleter associated with the Cascade volume plugin. +func (plugin *cascadeDiskPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { + return plugin.newDeleterInternal(spec, &CascadeDiskUtil{}) +} + +func (plugin *cascadeDiskPlugin) newDeleterInternal(spec *volume.Spec, manager diskManager) (volume.Deleter, error) { + if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CascadeDisk == nil { + return nil, fmt.Errorf("spec.PersistentVolumeSource.CascadeDisk is nil") + } + return &cascadeDiskDeleter{ + &cascadeDisk{ + volName: spec.Name(), + diskID: spec.PersistentVolume.Spec.CascadeDisk.DiskID, + manager: manager, + plugin: plugin, + }}, nil +} + +func (r *cascadeDiskDeleter) Delete() error { + return r.manager.DeleteVolume(r) +} + +type cascadeDiskProvisioner struct { + *cascadeDisk + options volume.VolumeOptions +} + +var _ volume.Provisioner = &cascadeDiskProvisioner{} + +// NewProvisioner returns the provisioner associated with the Cascade volume plugin. +func (plugin *cascadeDiskPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) { + return plugin.newProvisionerInternal(options, &CascadeDiskUtil{}) +} + +func (plugin *cascadeDiskPlugin) newProvisionerInternal(options volume.VolumeOptions, + manager diskManager) (volume.Provisioner, error) { + return &cascadeDiskProvisioner{ + cascadeDisk: &cascadeDisk{ + manager: manager, + plugin: plugin, + }, + options: options, + }, nil +} + +// Provision provisions the persistent volume by making a CreateDisk call to Cascade Controller. +func (p *cascadeDiskProvisioner) Provision() (*v1.PersistentVolume, error) { + if !volume.AccessModesContainedInAll(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) { + return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", + p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes()) + } + + diskID, sizeGB, fstype, err := p.manager.CreateVolume(p) + if err != nil { + return nil, err + } + + if fstype == "" { + fstype = "ext4" + } + + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: p.options.PVName, + Labels: map[string]string{}, + Annotations: map[string]string{ + volumehelper.VolumeDynamicallyCreatedByKey: "cascade-volume-dynamic-provisioner", + }, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeReclaimPolicy: p.options.PersistentVolumeReclaimPolicy, + AccessModes: p.options.PVC.Spec.AccessModes, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)), + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + CascadeDisk: &v1.CascadeDiskVolumeSource{ + DiskID: diskID, + FSType: fstype, + }, + }, + MountOptions: p.options.MountOptions, + }, + } + if len(p.options.PVC.Spec.AccessModes) == 0 { + pv.Spec.AccessModes = p.plugin.GetAccessModes() + } + + return pv, nil +} + +func getVolumeSource(spec *volume.Spec) (*v1.CascadeDiskVolumeSource, bool, error) { + if spec.Volume != nil && spec.Volume.CascadeDisk != nil { + return spec.Volume.CascadeDisk, spec.ReadOnly, nil + } else if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CascadeDisk != nil { + return spec.PersistentVolume.Spec.CascadeDisk, spec.ReadOnly, nil + } + + return nil, false, fmt.Errorf("Spec does not reference a Cascade disk type") +} \ No newline at end of file diff -uNr --no-dereference kubernetes-original/pkg/volume/cascade_disk/cascade_util.go kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/cascade_util.go --- kubernetes-original/pkg/volume/cascade_disk/cascade_util.go 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/cascade_util.go 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,107 @@ +package cascade_disk + +import ( + "fmt" + "strings" + "time" + + "github.com/golang/glog" + "k8s.io/api/core/v1" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/cloudprovider/providers/cascade" + "k8s.io/kubernetes/pkg/volume" + volumeutil "k8s.io/kubernetes/pkg/volume/util" +) + +const ( + checkSleepDuration = time.Second +) + +type CascadeDiskUtil struct{} + +func verifyDevicePath(path string) (string, error) { + if pathExists, err := volumeutil.PathExists(path); err != nil { + return "", fmt.Errorf("Error checking if path exists: %v", err) + } else if pathExists { + return path, nil + } + + glog.V(4).Infof("verifyDevicePath: path does not exist yet") + return "", nil +} + +// CreateVolume creates a Cascade persistent disk. +func (util *CascadeDiskUtil) CreateVolume(p *cascadeDiskProvisioner) (diskID string, capacityGB int, fstype string, + err error) { + cloud, err := getCloudProvider(p.plugin.host.GetCloudProvider()) + if err != nil { + glog.Errorf("Cascade Util: CreateVolume failed to get cloud provider. Error [%v]", err) + return "", 0, "", err + } + + capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] + volSizeBytes := capacity.Value() + // Cascade works with GB, convert to GB with rounding up + volSizeGB := int(volume.RoundUpSize(volSizeBytes, 1024*1024*1024)) + name := volume.GenerateVolumeName(p.options.ClusterName, p.options.PVName, 255) + volumeOptions := &cascade.VolumeOptions{ + CapacityGB: volSizeGB, + Tags: *p.options.CloudTags, + Name: name, + } + + for parameter, value := range p.options.Parameters { + switch strings.ToLower(parameter) { + case "flavor": + volumeOptions.Flavor = value + case volume.VolumeParameterFSType: + fstype = value + glog.V(4).Infof("Cascade Util: Setting fstype to %s", fstype) + default: + glog.Errorf("Cascade Util: invalid option %s for volume plugin %s.", parameter, + p.plugin.GetPluginName()) + return "", 0, "", fmt.Errorf("Cascade Util: invalid option %s for volume plugin %s.", parameter, + p.plugin.GetPluginName()) + } + } + + diskID, err = cloud.CreateDisk(volumeOptions) + if err != nil { + glog.Errorf("Cascade Util: failed to CreateDisk. Error [%v]", err) + return "", 0, "", err + } + + glog.V(4).Infof("Successfully created Cascade persistent disk %s", name) + return diskID, volSizeGB, "", nil +} + +// DeleteVolume deletes a Cascade volume. +func (util *CascadeDiskUtil) DeleteVolume(disk *cascadeDiskDeleter) error { + cloud, err := getCloudProvider(disk.plugin.host.GetCloudProvider()) + if err != nil { + glog.Errorf("Cascade Util: DeleteVolume failed to get cloud provider. Error [%v]", err) + return err + } + + if err = cloud.DeleteDisk(disk.diskID); err != nil { + glog.Errorf("Cascade Util: failed to DeleteDisk for diskID %s. Error [%v]", disk.diskID, err) + return err + } + + glog.V(4).Infof("Successfully deleted Cascade persistent disk %s", disk.diskID) + return nil +} + +func getCloudProvider(cloud cloudprovider.Interface) (*cascade.CascadeCloud, error) { + if cloud == nil { + glog.Errorf("Cascade Util: Cloud provider not initialized properly") + return nil, fmt.Errorf("Cascade Util: Cloud provider not initialized properly") + } + + cc := cloud.(*cascade.CascadeCloud) + if cc == nil { + glog.Errorf("Invalid cloud provider: expected Cascade") + return nil, fmt.Errorf("Invalid cloud provider: expected Cascade") + } + return cc, nil +} diff -uNr --no-dereference kubernetes-original/pkg/volume/cascade_disk/OWNERS kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/OWNERS --- kubernetes-original/pkg/volume/cascade_disk/OWNERS 1970-01-01 00:00:00.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/pkg/volume/cascade_disk/OWNERS 2018-04-30 21:11:51.000000000 +0000 @@ -0,0 +1,2 @@ +maintainers: +- ashokc diff -uNr --no-dereference kubernetes-original/plugin/pkg/admission/persistentvolume/label/admission.go kubernetes-modified/src/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission.go --- kubernetes-original/plugin/pkg/admission/persistentvolume/label/admission.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission.go 2018-04-30 21:11:51.000000000 +0000 @@ -27,6 +27,7 @@ api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" + "k8s.io/kubernetes/pkg/cloudprovider/providers/cascade" "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" @@ -50,6 +51,7 @@ ebsVolumes aws.Volumes cloudConfig []byte gceCloudProvider *gce.GCECloud + cascadeDisks cascade.Disks } var _ admission.MutationInterface = &persistentVolumeLabel{} @@ -102,6 +104,13 @@ } volumeLabels = labels } + if volume.Spec.CascadeDisk != nil { + labels, err := l.findCascadeDiskLabels(volume) + if err != nil { + return admission.NewForbidden(a, fmt.Errorf("error querying Cascade volume %s: %v", volume.Spec.CascadeDisk.DiskID, err)) + } + volumeLabels = labels + } if len(volumeLabels) != 0 { if volume.Labels == nil { @@ -214,3 +223,48 @@ } return l.gceCloudProvider, nil } + +func (l *persistentVolumeLabel) findCascadeDiskLabels(volume *api.PersistentVolume) (map[string]string, error) { + // Ignore any volumes that are being provisioned + if volume.Spec.CascadeDisk.DiskID == vol.ProvisionedVolumeName { + return nil, nil + } + cascadeDisks, err := l.getCascadeDisks() + if err != nil { + return nil, err + } + if cascadeDisks == nil { + return nil, fmt.Errorf("unable to build Cascade cloud provider for volumes") + } + + labels, err := cascadeDisks.GetVolumeLabels(volume.Spec.CascadeDisk.DiskID) + if err != nil { + return nil, err + } + + return labels, nil +} + +// getCascadeDisks returns the Cascade Disks interface +func (l *persistentVolumeLabel) getCascadeDisks() (cascade.Disks, error) { + l.mutex.Lock() + defer l.mutex.Unlock() + + if l.cascadeDisks == nil { + var cloudConfigReader io.Reader + if len(l.cloudConfig) > 0 { + cloudConfigReader = bytes.NewReader(l.cloudConfig) + } + cloudProvider, err := cloudprovider.GetCloudProvider("cascade", cloudConfigReader) + if err != nil || cloudProvider == nil { + return nil, err + } + provider, ok := cloudProvider.(*cascade.CascadeCloud) + if !ok { + // GetCloudProvider has gone very wrong + return nil, fmt.Errorf("error retrieving Cascade cloud provider") + } + l.cascadeDisks = provider + } + return l.cascadeDisks, nil +} diff -uNr --no-dereference kubernetes-original/staging/src/k8s.io/api/core/v1/generated.pb.go kubernetes-modified/src/k8s.io/kubernetes/staging/src/k8s.io/api/core/v1/generated.pb.go --- kubernetes-original/staging/src/k8s.io/api/core/v1/generated.pb.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/staging/src/k8s.io/api/core/v1/generated.pb.go 2018-04-30 21:11:51.000000000 +0000 @@ -35,6 +35,7 @@ Binding CSIPersistentVolumeSource Capabilities + CascadeDiskVolumeSource CephFSPersistentVolumeSource CephFSVolumeSource CinderVolumeSource @@ -260,9 +261,11 @@ func (*AvoidPods) ProtoMessage() {} func (*AvoidPods) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } -func (m *AzureDiskVolumeSource) Reset() { *m = AzureDiskVolumeSource{} } -func (*AzureDiskVolumeSource) ProtoMessage() {} -func (*AzureDiskVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } +func (m *CascadeDiskVolumeSource) Reset() { *m = CascadeDiskVolumeSource{} } +func (*CascadeDiskVolumeSource) ProtoMessage() {} +func (*CascadeDiskVolumeSource) Descriptor() ([]byte, []int) { + return fileDescriptorGenerated, []int{4} +} func (m *AzureFilePersistentVolumeSource) Reset() { *m = AzureFilePersistentVolumeSource{} } func (*AzureFilePersistentVolumeSource) ProtoMessage() {} @@ -1040,6 +1043,11 @@ return fileDescriptorGenerated, []int{185} } +func (m *AzureDiskVolumeSource) Reset() { *m = AzureDiskVolumeSource{} } +func (*AzureDiskVolumeSource) ProtoMessage() {} +func (*AzureDiskVolumeSource) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{186} } + + func init() { proto.RegisterType((*AWSElasticBlockStoreVolumeSource)(nil), "k8s.io.api.core.v1.AWSElasticBlockStoreVolumeSource") proto.RegisterType((*Affinity)(nil), "k8s.io.api.core.v1.Affinity") @@ -1051,6 +1059,7 @@ proto.RegisterType((*Binding)(nil), "k8s.io.api.core.v1.Binding") proto.RegisterType((*CSIPersistentVolumeSource)(nil), "k8s.io.api.core.v1.CSIPersistentVolumeSource") proto.RegisterType((*Capabilities)(nil), "k8s.io.api.core.v1.Capabilities") + proto.RegisterType((*CascadeDiskVolumeSource)(nil), "k8s.io.api.core.v1.CascadeDiskVolumeSource") proto.RegisterType((*CephFSPersistentVolumeSource)(nil), "k8s.io.api.core.v1.CephFSPersistentVolumeSource") proto.RegisterType((*CephFSVolumeSource)(nil), "k8s.io.api.core.v1.CephFSVolumeSource") proto.RegisterType((*CinderVolumeSource)(nil), "k8s.io.api.core.v1.CinderVolumeSource") @@ -1613,6 +1622,32 @@ return i, nil } +func (m *CascadeDiskVolumeSource) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CascadeDiskVolumeSource) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DiskID))) + i += copy(dAtA[i:], m.DiskID) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.FSType))) + i += copy(dAtA[i:], m.FSType) + return i, nil +} + func (m *CephFSPersistentVolumeSource) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -6283,13 +6318,13 @@ } i += n120 } - if m.AzureDisk != nil { + if m.CascadeDisk != nil { dAtA[i] = 0x82 i++ dAtA[i] = 0x1 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.AzureDisk.Size())) - n121, err := m.AzureDisk.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.CascadeDisk.Size())) + n121, err := m.CascadeDisk.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -6367,6 +6402,18 @@ } i += n127 } + if m.AzureDisk != nil { + dAtA[i] = 0xba + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.AzureDisk.Size())) + n128, err := m.AzureDisk.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n128 + } return i, nil } @@ -10316,13 +10363,13 @@ } i += n223 } - if m.AzureDisk != nil { + if m.CascadeDisk != nil { dAtA[i] = 0xb2 i++ dAtA[i] = 0x1 i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.AzureDisk.Size())) - n224, err := m.AzureDisk.MarshalTo(dAtA[i:]) + i = encodeVarintGenerated(dAtA, i, uint64(m.CascadeDisk.Size())) + n224, err := m.CascadeDisk.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -10388,6 +10435,18 @@ } i += n229 } + if m.AzureDisk != nil { + dAtA[i] = 0xe2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.AzureDisk.Size())) + n230, err := m.AzureDisk.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n230 + } return i, nil } @@ -10623,6 +10682,16 @@ return n } +func (m *CascadeDiskVolumeSource) Size() (n int) { + var l int + _ = l + l = len(m.DiskID) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.FSType) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *CephFSPersistentVolumeSource) Size() (n int) { var l int _ = l @@ -12331,8 +12400,8 @@ l = m.Quobyte.Size() n += 1 + l + sovGenerated(uint64(l)) } - if m.AzureDisk != nil { - l = m.AzureDisk.Size() + if m.CascadeDisk != nil { + l = m.CascadeDisk.Size() n += 2 + l + sovGenerated(uint64(l)) } if m.PhotonPersistentDisk != nil { @@ -12359,6 +12428,10 @@ l = m.CSI.Size() n += 2 + l + sovGenerated(uint64(l)) } + if m.AzureDisk != nil { + l = m.AzureDisk.Size() + n += 2 + l + sovGenerated(uint64(l)) + } return n } @@ -13788,8 +13861,8 @@ l = m.Quobyte.Size() n += 2 + l + sovGenerated(uint64(l)) } - if m.AzureDisk != nil { - l = m.AzureDisk.Size() + if m.CascadeDisk != nil { + l = m.CascadeDisk.Size() n += 2 + l + sovGenerated(uint64(l)) } if m.PhotonPersistentDisk != nil { @@ -13812,6 +13885,10 @@ l = m.StorageOS.Size() n += 2 + l + sovGenerated(uint64(l)) } + if m.AzureDisk != nil { + l = m.AzureDisk.Size() + n += 2 + l + sovGenerated(uint64(l)) + } return n } @@ -13971,6 +14048,17 @@ }, "") return s } +func (this *CascadeDiskVolumeSource) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&CascadeDiskVolumeSource{`, + `DiskID:` + fmt.Sprintf("%v", this.DiskID) + `,`, + `FSType:` + fmt.Sprintf("%v", this.FSType) + `,`, + `}`, + }, "") + return s +} func (this *CephFSPersistentVolumeSource) String() string { if this == nil { return "nil" @@ -15335,13 +15423,14 @@ `AzureFile:` + strings.Replace(fmt.Sprintf("%v", this.AzureFile), "AzureFilePersistentVolumeSource", "AzureFilePersistentVolumeSource", 1) + `,`, `VsphereVolume:` + strings.Replace(fmt.Sprintf("%v", this.VsphereVolume), "VsphereVirtualDiskVolumeSource", "VsphereVirtualDiskVolumeSource", 1) + `,`, `Quobyte:` + strings.Replace(fmt.Sprintf("%v", this.Quobyte), "QuobyteVolumeSource", "QuobyteVolumeSource", 1) + `,`, - `AzureDisk:` + strings.Replace(fmt.Sprintf("%v", this.AzureDisk), "AzureDiskVolumeSource", "AzureDiskVolumeSource", 1) + `,`, + `CascadeDisk:` + strings.Replace(fmt.Sprintf("%v", this.CascadeDisk), "CascadeDiskVolumeSource", "CascadeDiskVolumeSource", 1) + `,`, `PhotonPersistentDisk:` + strings.Replace(fmt.Sprintf("%v", this.PhotonPersistentDisk), "PhotonPersistentDiskVolumeSource", "PhotonPersistentDiskVolumeSource", 1) + `,`, `PortworxVolume:` + strings.Replace(fmt.Sprintf("%v", this.PortworxVolume), "PortworxVolumeSource", "PortworxVolumeSource", 1) + `,`, `ScaleIO:` + strings.Replace(fmt.Sprintf("%v", this.ScaleIO), "ScaleIOPersistentVolumeSource", "ScaleIOPersistentVolumeSource", 1) + `,`, `Local:` + strings.Replace(fmt.Sprintf("%v", this.Local), "LocalVolumeSource", "LocalVolumeSource", 1) + `,`, `StorageOS:` + strings.Replace(fmt.Sprintf("%v", this.StorageOS), "StorageOSPersistentVolumeSource", "StorageOSPersistentVolumeSource", 1) + `,`, `CSI:` + strings.Replace(fmt.Sprintf("%v", this.CSI), "CSIPersistentVolumeSource", "CSIPersistentVolumeSource", 1) + `,`, + `AzureDisk:` + strings.Replace(fmt.Sprintf("%v", this.AzureDisk), "AzureDiskVolumeSource", "AzureDiskVolumeSource", 1) + `,`, `}`, }, "") return s @@ -16468,12 +16557,13 @@ `ConfigMap:` + strings.Replace(fmt.Sprintf("%v", this.ConfigMap), "ConfigMapVolumeSource", "ConfigMapVolumeSource", 1) + `,`, `VsphereVolume:` + strings.Replace(fmt.Sprintf("%v", this.VsphereVolume), "VsphereVirtualDiskVolumeSource", "VsphereVirtualDiskVolumeSource", 1) + `,`, `Quobyte:` + strings.Replace(fmt.Sprintf("%v", this.Quobyte), "QuobyteVolumeSource", "QuobyteVolumeSource", 1) + `,`, - `AzureDisk:` + strings.Replace(fmt.Sprintf("%v", this.AzureDisk), "AzureDiskVolumeSource", "AzureDiskVolumeSource", 1) + `,`, + `CascadeDisk:` + strings.Replace(fmt.Sprintf("%v", this.CascadeDisk), "CascadeDiskVolumeSource", "CascadeDiskVolumeSource", 1) + `,`, `PhotonPersistentDisk:` + strings.Replace(fmt.Sprintf("%v", this.PhotonPersistentDisk), "PhotonPersistentDiskVolumeSource", "PhotonPersistentDiskVolumeSource", 1) + `,`, `PortworxVolume:` + strings.Replace(fmt.Sprintf("%v", this.PortworxVolume), "PortworxVolumeSource", "PortworxVolumeSource", 1) + `,`, `ScaleIO:` + strings.Replace(fmt.Sprintf("%v", this.ScaleIO), "ScaleIOVolumeSource", "ScaleIOVolumeSource", 1) + `,`, `Projected:` + strings.Replace(fmt.Sprintf("%v", this.Projected), "ProjectedVolumeSource", "ProjectedVolumeSource", 1) + `,`, `StorageOS:` + strings.Replace(fmt.Sprintf("%v", this.StorageOS), "StorageOSVolumeSource", "StorageOSVolumeSource", 1) + `,`, + `AzureDisk:` + strings.Replace(fmt.Sprintf("%v", this.AzureDisk), "AzureDiskVolumeSource", "AzureDiskVolumeSource", 1) + `,`, `}`, }, "") return s @@ -34322,7 +34412,7 @@ iNdEx = postIndex case 16: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AzureDisk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CascadeDisk", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -34346,10 +34436,10 @@ if postIndex > l { return io.ErrUnexpectedEOF } - if m.AzureDisk == nil { - m.AzureDisk = &AzureDiskVolumeSource{} + if m.CascadeDisk == nil { + m.CascadeDisk = &CascadeDiskVolumeSource{} } - if err := m.AzureDisk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.CascadeDisk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -34551,6 +34641,39 @@ return err } iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AzureDisk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AzureDisk == nil { + m.AzureDisk = &AzureDiskVolumeSource{} + } + if err := m.AzureDisk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -35089,6 +35212,114 @@ } return nil } +func (m *CascadeDiskVolumeSource) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CascadeDiskVolumeSource: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CascadeDiskVolumeSource: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DiskID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DiskID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FSType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FSType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *PhotonPersistentDiskVolumeSource) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -48522,7 +48753,7 @@ iNdEx = postIndex case 22: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AzureDisk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CascadeDisk", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -48546,10 +48777,10 @@ if postIndex > l { return io.ErrUnexpectedEOF } - if m.AzureDisk == nil { - m.AzureDisk = &AzureDiskVolumeSource{} + if m.CascadeDisk == nil { + m.CascadeDisk = &CascadeDiskVolumeSource{} } - if err := m.AzureDisk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.CascadeDisk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -48718,6 +48949,39 @@ return err } iNdEx = postIndex + case 28: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AzureDisk", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AzureDisk == nil { + m.AzureDisk = &AzureDiskVolumeSource{} + } + if err := m.AzureDisk.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff -uNr --no-dereference kubernetes-original/staging/src/k8s.io/api/core/v1/types.go kubernetes-modified/src/k8s.io/kubernetes/staging/src/k8s.io/api/core/v1/types.go --- kubernetes-original/staging/src/k8s.io/api/core/v1/types.go 2018-03-20 19:21:10.000000000 +0000 +++ kubernetes-modified/src/k8s.io/kubernetes/staging/src/k8s.io/api/core/v1/types.go 2018-04-30 21:11:51.000000000 +0000 @@ -333,9 +333,8 @@ // Quobyte represents a Quobyte mount on the host that shares a pod's lifetime // +optional Quobyte *QuobyteVolumeSource `json:"quobyte,omitempty" protobuf:"bytes,21,opt,name=quobyte"` - // AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. - // +optional - AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty" protobuf:"bytes,22,opt,name=azureDisk"` + // CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine + CascadeDisk *CascadeDiskVolumeSource `json:"cascadeDisk,omitempty" protobuf:"bytes,22,opt,name=cascadeDisk"` // PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine PhotonPersistentDisk *PhotonPersistentDiskVolumeSource `json:"photonPersistentDisk,omitempty" protobuf:"bytes,23,opt,name=photonPersistentDisk"` // Items for all in one resources secrets, configmaps, and downward API @@ -349,6 +348,9 @@ // StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. // +optional StorageOS *StorageOSVolumeSource `json:"storageos,omitempty" protobuf:"bytes,27,opt,name=storageos"` + // AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + // +optional + AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty" protobuf:"bytes,28,opt,name=azureDisk"` } // PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. @@ -428,9 +430,8 @@ // Quobyte represents a Quobyte mount on the host that shares a pod's lifetime // +optional Quobyte *QuobyteVolumeSource `json:"quobyte,omitempty" protobuf:"bytes,15,opt,name=quobyte"` - // AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. - // +optional - AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty" protobuf:"bytes,16,opt,name=azureDisk"` + // CascadeDisk represents a Cascade persistent disk attached and mounted on kubelets host machine + CascadeDisk *CascadeDiskVolumeSource `json:"cascadeDisk,omitempty" protobuf:"bytes,16,opt,name=cascadeDisk"` // PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine PhotonPersistentDisk *PhotonPersistentDiskVolumeSource `json:"photonPersistentDisk,omitempty" protobuf:"bytes,17,opt,name=photonPersistentDisk"` // PortworxVolume represents a portworx volume attached and mounted on kubelets host machine @@ -449,6 +450,9 @@ // CSI represents storage that handled by an external CSI driver // +optional CSI *CSIPersistentVolumeSource `json:"csi,omitempty" protobuf:"bytes,22,opt,name=csi"` + // AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + // +optional + AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty" protobuf:"bytes,23,opt,name=azureDisk"` } const ( @@ -1578,6 +1582,16 @@ SecretRef *ObjectReference `json:"secretRef,omitempty" protobuf:"bytes,5,opt,name=secretRef"` } +// Represents a Photon Controller persistent disk resource. +type CascadeDiskVolumeSource struct { + // ID that identifies Cascade persistent disk + DiskID string `json:"diskID" protobuf:"bytes,1,opt,name=diskID"` + // 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. // // The contents of the target ConfigMap's Data field will be presented in a