mirror of https://github.com/k3s-io/k3s
Merge pull request #31005 from simonswine/feature-flocker-dyn-provisioning
Automatic merge from submit-queue Dynamic provisioning for flocker volume plugin Refactor flocker volume plugin * [x] Support provisioning beta (#29006) * [x] Support deletion * [x] Use bind mounts instead of /flocker in containers * [x] support ownership management or SELinux relabeling. * [x] adds volume specification via datasetUUID (this is guranted to be unique) I based my refactor work to replicate pretty much GCE-PD behaviour **Related issues**: #29006 #26908 @jsafrane @mattbates @wallrj @wallnerryanpull/6/head
commit
df064881d2
|
@ -65,10 +65,6 @@
|
|||
"Comment": "v7.0.6-4-g2492d97",
|
||||
"Rev": "2492d97b402e00797833c03ac5fa1c572c7bb29a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ClusterHQ/flocker-go",
|
||||
"Rev": "1c0a791b33bdc01d062b376612aa04e27eed7eb3"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/go-winio",
|
||||
"Comment": "v0.1.0",
|
||||
|
@ -331,6 +327,10 @@
|
|||
"Comment": "1.2.0",
|
||||
"Rev": "db0d0650b6496bfe8061ec56a92edd32d8e75c30"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/clusterhq/flocker-go",
|
||||
"Rev": "2b8b7259d3139c96c4a6871031355808ab3fd3b3"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/negroni",
|
||||
"Comment": "v0.1.0-62-g8d75e11",
|
||||
|
|
|
@ -9231,7 +9231,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
|
||||
================================================================================
|
||||
= vendor/github.com/ClusterHQ/flocker-go licensed under: =
|
||||
= vendor/github.com/clusterhq/flocker-go licensed under: =
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
@ -9424,7 +9424,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
= vendor/github.com/ClusterHQ/flocker-go/LICENSE d8103d9796cd0e951379d0834edad066 -
|
||||
= vendor/github.com/clusterhq/flocker-go/LICENSE d8103d9796cd0e951379d0834edad066 -
|
||||
================================================================================
|
||||
|
||||
|
||||
|
|
|
@ -1813,14 +1813,15 @@
|
|||
},
|
||||
"v1.FlockerVolumeSource": {
|
||||
"id": "v1.FlockerVolumeSource",
|
||||
"description": "Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"required": [
|
||||
"datasetName"
|
||||
],
|
||||
"description": "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"properties": {
|
||||
"datasetName": {
|
||||
"type": "string",
|
||||
"description": "Required: the volume name. This is going to be store on metadata -\u003e name on the payload for Flocker"
|
||||
"description": "Name of the dataset stored as metadata -\u003e name on the dataset for Flocker should be considered as deprecated"
|
||||
},
|
||||
"datasetUUID": {
|
||||
"type": "string",
|
||||
"description": "UUID of the dataset. This is unique identifier of a Flocker dataset"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1818,14 +1818,15 @@
|
|||
},
|
||||
"v1.FlockerVolumeSource": {
|
||||
"id": "v1.FlockerVolumeSource",
|
||||
"description": "Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"required": [
|
||||
"datasetName"
|
||||
],
|
||||
"description": "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"properties": {
|
||||
"datasetName": {
|
||||
"type": "string",
|
||||
"description": "Required: the volume name. This is going to be store on metadata -\u003e name on the payload for Flocker"
|
||||
"description": "Name of the dataset stored as metadata -\u003e name on the dataset for Flocker should be considered as deprecated"
|
||||
},
|
||||
"datasetUUID": {
|
||||
"type": "string",
|
||||
"description": "UUID of the dataset. This is unique identifier of a Flocker dataset"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -8427,14 +8427,15 @@
|
|||
},
|
||||
"v1.FlockerVolumeSource": {
|
||||
"id": "v1.FlockerVolumeSource",
|
||||
"description": "Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"required": [
|
||||
"datasetName"
|
||||
],
|
||||
"description": "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"properties": {
|
||||
"datasetName": {
|
||||
"type": "string",
|
||||
"description": "Required: the volume name. This is going to be store on metadata -\u003e name on the payload for Flocker"
|
||||
"description": "Name of the dataset stored as metadata -\u003e name on the dataset for Flocker should be considered as deprecated"
|
||||
},
|
||||
"datasetUUID": {
|
||||
"type": "string",
|
||||
"description": "UUID of the dataset. This is unique identifier of a Flocker dataset"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -17638,14 +17638,15 @@
|
|||
},
|
||||
"v1.FlockerVolumeSource": {
|
||||
"id": "v1.FlockerVolumeSource",
|
||||
"description": "Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"required": [
|
||||
"datasetName"
|
||||
],
|
||||
"description": "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"properties": {
|
||||
"datasetName": {
|
||||
"type": "string",
|
||||
"description": "Required: the volume name. This is going to be store on metadata -\u003e name on the payload for Flocker"
|
||||
"description": "Name of the dataset stored as metadata -\u003e name on the dataset for Flocker should be considered as deprecated"
|
||||
},
|
||||
"datasetUUID": {
|
||||
"type": "string",
|
||||
"description": "UUID of the dataset. This is unique identifier of a Flocker dataset"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -41,6 +41,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/volume/azure_dd"
|
||||
"k8s.io/kubernetes/pkg/volume/cinder"
|
||||
"k8s.io/kubernetes/pkg/volume/flexvolume"
|
||||
"k8s.io/kubernetes/pkg/volume/flocker"
|
||||
"k8s.io/kubernetes/pkg/volume/gce_pd"
|
||||
"k8s.io/kubernetes/pkg/volume/glusterfs"
|
||||
"k8s.io/kubernetes/pkg/volume/host_path"
|
||||
|
@ -108,6 +109,8 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componen
|
|||
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, quobyte.ProbeVolumePlugins()...)
|
||||
|
||||
allPlugins = append(allPlugins, flocker.ProbeVolumePlugins()...)
|
||||
|
||||
if cloud != nil {
|
||||
switch {
|
||||
case aws.ProviderName == cloud.ProviderName():
|
||||
|
|
|
@ -2353,7 +2353,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
|
|||
<div class="sect2">
|
||||
<h3 id="_v1_flockervolumesource">v1.FlockerVolumeSource</h3>
|
||||
<div class="paragraph">
|
||||
<p>Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.</p>
|
||||
<p>Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all" style="width:100%; ">
|
||||
<colgroup>
|
||||
|
@ -2375,8 +2375,15 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">datasetName</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Required: the volume name. This is going to be store on metadata → name on the payload for Flocker</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Name of the dataset stored as metadata → name on the dataset for Flocker should be considered as deprecated</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">datasetUUID</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">UUID of the dataset. This is unique identifier of a Flocker dataset</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
|
@ -4389,7 +4396,7 @@ The PetSet guarantees that a given network identity will always map to the same
|
|||
</div>
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Last updated 2016-09-22 17:28:35 UTC
|
||||
Last updated 2016-09-24 08:31:55 UTC
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -2284,7 +2284,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
|
|||
<div class="sect2">
|
||||
<h3 id="_v1_flockervolumesource">v1.FlockerVolumeSource</h3>
|
||||
<div class="paragraph">
|
||||
<p>Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.</p>
|
||||
<p>Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all" style="width:100%; ">
|
||||
<colgroup>
|
||||
|
@ -2306,8 +2306,15 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">datasetName</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Required: the volume name. This is going to be store on metadata → name on the payload for Flocker</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Name of the dataset stored as metadata → name on the dataset for Flocker should be considered as deprecated</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">datasetUUID</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">UUID of the dataset. This is unique identifier of a Flocker dataset</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
|
@ -4321,7 +4328,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
|
|||
</div>
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Last updated 2016-09-18 23:34:31 UTC
|
||||
Last updated 2016-09-24 08:32:06 UTC
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -2142,7 +2142,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
|
|||
<div class="sect2">
|
||||
<h3 id="_v1_flockervolumesource">v1.FlockerVolumeSource</h3>
|
||||
<div class="paragraph">
|
||||
<p>Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.</p>
|
||||
<p>Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all" style="width:100%; ">
|
||||
<colgroup>
|
||||
|
@ -2164,8 +2164,15 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">datasetName</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Required: the volume name. This is going to be store on metadata → name on the payload for Flocker</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Name of the dataset stored as metadata → name on the dataset for Flocker should be considered as deprecated</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">datasetUUID</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">UUID of the dataset. This is unique identifier of a Flocker dataset</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
|
@ -6590,7 +6597,7 @@ Both these may change in the future. Incoming requests are matched against the h
|
|||
</div>
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Last updated 2016-09-18 23:34:24 UTC
|
||||
Last updated 2016-09-24 08:32:15 UTC
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -2568,7 +2568,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
|
|||
<div class="sect2">
|
||||
<h3 id="_v1_flockervolumesource">v1.FlockerVolumeSource</h3>
|
||||
<div class="paragraph">
|
||||
<p>Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.</p>
|
||||
<p>Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all" style="width:100%; ">
|
||||
<colgroup>
|
||||
|
@ -2590,8 +2590,15 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">datasetName</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Required: the volume name. This is going to be store on metadata → name on the payload for Flocker</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Name of the dataset stored as metadata → name on the dataset for Flocker should be considered as deprecated</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">datasetUUID</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">UUID of the dataset. This is unique identifier of a Flocker dataset</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
|
||||
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
|
||||
<td class="tableblock halign-left valign-top"></td>
|
||||
</tr>
|
||||
|
@ -8468,7 +8475,7 @@ The resulting set of endpoints can be viewed as:<br>
|
|||
</div>
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Last updated 2016-09-19 23:42:42 UTC
|
||||
Last updated 2016-09-24 08:31:51 UTC
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -741,10 +741,14 @@ type CephFSVolumeSource struct {
|
|||
}
|
||||
|
||||
// Represents a Flocker volume mounted by the Flocker agent.
|
||||
// One and only one of datasetName and datasetUUID should be set.
|
||||
// Flocker volumes do not support ownership management or SELinux relabeling.
|
||||
type FlockerVolumeSource struct {
|
||||
// Required: the volume name. This is going to be store on metadata -> name on the payload for Flocker
|
||||
DatasetName string `json:"datasetName"`
|
||||
// Name of the dataset stored as metadata -> name on the dataset for Flocker
|
||||
// should be considered as deprecated
|
||||
DatasetName string `json:"datasetName,omitempty"`
|
||||
// UUID of the dataset. This is unique identifier of a Flocker dataset
|
||||
DatasetUUID string `json:"datasetUUID,omitempty"`
|
||||
}
|
||||
|
||||
// Represents a volume containing downward API info.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -840,10 +840,15 @@ message FlexVolumeSource {
|
|||
}
|
||||
|
||||
// Represents a Flocker volume mounted by the Flocker agent.
|
||||
// One and only one of datasetName and datasetUUID should be set.
|
||||
// Flocker volumes do not support ownership management or SELinux relabeling.
|
||||
message FlockerVolumeSource {
|
||||
// Required: the volume name. This is going to be store on metadata -> name on the payload for Flocker
|
||||
// Name of the dataset stored as metadata -> name on the dataset for Flocker
|
||||
// should be considered as deprecated
|
||||
optional string datasetName = 1;
|
||||
|
||||
// UUID of the dataset. This is unique identifier of a Flocker dataset
|
||||
optional string datasetUUID = 2;
|
||||
}
|
||||
|
||||
// Represents a Persistent Disk resource in Google Compute Engine.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -648,10 +648,14 @@ type CephFSVolumeSource struct {
|
|||
}
|
||||
|
||||
// Represents a Flocker volume mounted by the Flocker agent.
|
||||
// One and only one of datasetName and datasetUUID should be set.
|
||||
// Flocker volumes do not support ownership management or SELinux relabeling.
|
||||
type FlockerVolumeSource struct {
|
||||
// Required: the volume name. This is going to be store on metadata -> name on the payload for Flocker
|
||||
DatasetName string `json:"datasetName" protobuf:"bytes,1,opt,name=datasetName"`
|
||||
// Name of the dataset stored as metadata -> name on the dataset for Flocker
|
||||
// should be considered as deprecated
|
||||
DatasetName string `json:"datasetName,omitempty" protobuf:"bytes,1,opt,name=datasetName"`
|
||||
// UUID of the dataset. This is unique identifier of a Flocker dataset
|
||||
DatasetUUID string `json:"datasetUUID,omitempty" protobuf:"bytes,2,opt,name=datasetUUID"`
|
||||
}
|
||||
|
||||
// StorageMedium defines ways that storage can be allocated to a volume.
|
||||
|
|
|
@ -529,8 +529,9 @@ func (FlexVolumeSource) SwaggerDoc() map[string]string {
|
|||
}
|
||||
|
||||
var map_FlockerVolumeSource = map[string]string{
|
||||
"": "Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"datasetName": "Required: the volume name. This is going to be store on metadata -> name on the payload for Flocker",
|
||||
"": "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
"datasetName": "Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated",
|
||||
"datasetUUID": "UUID of the dataset. This is unique identifier of a Flocker dataset",
|
||||
}
|
||||
|
||||
func (FlockerVolumeSource) SwaggerDoc() map[string]string {
|
||||
|
|
|
@ -2196,6 +2196,7 @@ func Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in *api.FlexVolumeSourc
|
|||
|
||||
func autoConvert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in *FlockerVolumeSource, out *api.FlockerVolumeSource, s conversion.Scope) error {
|
||||
out.DatasetName = in.DatasetName
|
||||
out.DatasetUUID = in.DatasetUUID
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -2205,6 +2206,7 @@ func Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in *FlockerVolume
|
|||
|
||||
func autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *api.FlockerVolumeSource, out *FlockerVolumeSource, s conversion.Scope) error {
|
||||
out.DatasetName = in.DatasetName
|
||||
out.DatasetUUID = in.DatasetUUID
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1150,6 +1150,7 @@ func DeepCopy_v1_FlockerVolumeSource(in interface{}, out interface{}, c *convers
|
|||
in := in.(*FlockerVolumeSource)
|
||||
out := out.(*FlockerVolumeSource)
|
||||
out.DatasetName = in.DatasetName
|
||||
out.DatasetUUID = in.DatasetUUID
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -855,8 +855,12 @@ func validateGlusterfs(glusterfs *api.GlusterfsVolumeSource, fldPath *field.Path
|
|||
|
||||
func validateFlockerVolumeSource(flocker *api.FlockerVolumeSource, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if len(flocker.DatasetName) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("datasetName"), ""))
|
||||
if len(flocker.DatasetName) == 0 && len(flocker.DatasetUUID) == 0 {
|
||||
//TODO: consider adding a RequiredOneOf() error for this and similar cases
|
||||
allErrs = append(allErrs, field.Required(fldPath, "one of datasetName and datasetUUID is required"))
|
||||
}
|
||||
if len(flocker.DatasetName) != 0 && len(flocker.DatasetUUID) != 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "resource", "datasetName and datasetUUID can not be specified simultaneously"))
|
||||
}
|
||||
if strings.Contains(flocker.DatasetName, "/") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("datasetName"), flocker.DatasetName, "must not contain '/'"))
|
||||
|
|
|
@ -1520,7 +1520,18 @@ func TestValidateVolumes(t *testing.T) {
|
|||
},
|
||||
// Flocker
|
||||
{
|
||||
name: "valid Flocker",
|
||||
name: "valid Flocker -- datasetUUID",
|
||||
vol: api.Volume{
|
||||
Name: "flocker",
|
||||
VolumeSource: api.VolumeSource{
|
||||
Flocker: &api.FlockerVolumeSource{
|
||||
DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid Flocker -- datasetName",
|
||||
vol: api.Volume{
|
||||
Name: "flocker",
|
||||
VolumeSource: api.VolumeSource{
|
||||
|
@ -1531,7 +1542,7 @@ func TestValidateVolumes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "empty flocker datasetName",
|
||||
name: "both empty",
|
||||
vol: api.Volume{
|
||||
Name: "flocker",
|
||||
VolumeSource: api.VolumeSource{
|
||||
|
@ -1541,7 +1552,21 @@ func TestValidateVolumes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
errtype: field.ErrorTypeRequired,
|
||||
errfield: "flocker.datasetName",
|
||||
errfield: "flocker",
|
||||
},
|
||||
{
|
||||
name: "both specified",
|
||||
vol: api.Volume{
|
||||
Name: "flocker",
|
||||
VolumeSource: api.VolumeSource{
|
||||
Flocker: &api.FlockerVolumeSource{
|
||||
DatasetName: "datasetName",
|
||||
DatasetUUID: "d846b09d-223d-43df-ab5b-d6db2206a0e4",
|
||||
},
|
||||
},
|
||||
},
|
||||
errtype: field.ErrorTypeInvalid,
|
||||
errfield: "flocker",
|
||||
},
|
||||
{
|
||||
name: "slash in flocker datasetName",
|
||||
|
|
|
@ -1178,6 +1178,7 @@ func DeepCopy_api_FlockerVolumeSource(in interface{}, out interface{}, c *conver
|
|||
in := in.(*FlockerVolumeSource)
|
||||
out := out.(*FlockerVolumeSource)
|
||||
out.DatasetName = in.DatasetName
|
||||
out.DatasetUUID = in.DatasetUUID
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7896,17 +7896,23 @@ var OpenAPIDefinitions *common.OpenAPIDefinitions = &common.OpenAPIDefinitions{
|
|||
"v1.FlockerVolumeSource": {
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Represents a Flocker volume mounted by the Flocker agent. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
Description: "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.",
|
||||
Properties: map[string]spec.Schema{
|
||||
"datasetName": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Required: the volume name. This is going to be store on metadata -> name on the payload for Flocker",
|
||||
Description: "Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"datasetUUID": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "UUID of the dataset. This is unique identifier of a Flocker dataset",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"datasetName"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{},
|
||||
|
|
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flocker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/env"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/util/strings"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
|
||||
flockerApi "github.com/clusterhq/flocker-go"
|
||||
)
|
||||
|
||||
// This is the primary entrypoint for volume plugins.
|
||||
func ProbeVolumePlugins() []volume.VolumePlugin {
|
||||
return []volume.VolumePlugin{&flockerPlugin{nil}}
|
||||
}
|
||||
|
||||
type flockerPlugin struct {
|
||||
host volume.VolumeHost
|
||||
}
|
||||
|
||||
type flockerVolume struct {
|
||||
volName string
|
||||
podUID types.UID
|
||||
// dataset metadata name deprecated
|
||||
datasetName string
|
||||
// dataset uuid
|
||||
datasetUUID string
|
||||
//pod *api.Pod
|
||||
flockerClient flockerApi.Clientable
|
||||
manager volumeManager
|
||||
plugin *flockerPlugin
|
||||
mounter mount.Interface
|
||||
volume.MetricsProvider
|
||||
}
|
||||
|
||||
var _ volume.VolumePlugin = &flockerPlugin{}
|
||||
var _ volume.PersistentVolumePlugin = &flockerPlugin{}
|
||||
var _ volume.DeletableVolumePlugin = &flockerPlugin{}
|
||||
var _ volume.ProvisionableVolumePlugin = &flockerPlugin{}
|
||||
|
||||
const (
|
||||
flockerPluginName = "kubernetes.io/flocker"
|
||||
|
||||
defaultHost = "localhost"
|
||||
defaultPort = 4523
|
||||
defaultCACertFile = "/etc/flocker/cluster.crt"
|
||||
defaultClientKeyFile = "/etc/flocker/apiuser.key"
|
||||
defaultClientCertFile = "/etc/flocker/apiuser.crt"
|
||||
defaultMountPath = "/flocker"
|
||||
|
||||
timeoutWaitingForVolume = 2 * time.Minute
|
||||
tickerWaitingForVolume = 5 * time.Second
|
||||
)
|
||||
|
||||
func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
|
||||
return host.GetPodVolumeDir(uid, strings.EscapeQualifiedNameForDisk(flockerPluginName), volName)
|
||||
}
|
||||
|
||||
func makeGlobalFlockerPath(datasetUUID string) string {
|
||||
return path.Join(defaultMountPath, datasetUUID)
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) Init(host volume.VolumeHost) error {
|
||||
p.host = host
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) GetPluginName() string {
|
||||
return flockerPluginName
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return volumeSource.DatasetName, nil
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) CanSupport(spec *volume.Spec) bool {
|
||||
return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Flocker != nil) ||
|
||||
(spec.Volume != nil && spec.Volume.Flocker != nil)
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) RequiresRemount() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *flockerPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
|
||||
return []api.PersistentVolumeAccessMode{
|
||||
api.ReadWriteOnce,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) getFlockerVolumeSource(spec *volume.Spec) (*api.FlockerVolumeSource, bool) {
|
||||
// AFAIK this will always be r/w, but perhaps for the future it will be needed
|
||||
readOnly := false
|
||||
|
||||
if spec.Volume != nil && spec.Volume.Flocker != nil {
|
||||
return spec.Volume.Flocker, readOnly
|
||||
}
|
||||
return spec.PersistentVolume.Spec.Flocker, readOnly
|
||||
}
|
||||
|
||||
func (plugin *flockerPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
|
||||
// Inject real implementations here, test through the internal function.
|
||||
return plugin.newMounterInternal(spec, pod.UID, &FlockerUtil{}, plugin.host.GetMounter())
|
||||
}
|
||||
|
||||
func (plugin *flockerPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager volumeManager, mounter mount.Interface) (volume.Mounter, error) {
|
||||
volumeSource, readOnly, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
datasetName := volumeSource.DatasetName
|
||||
datasetUUID := volumeSource.DatasetUUID
|
||||
|
||||
return &flockerVolumeMounter{
|
||||
flockerVolume: &flockerVolume{
|
||||
podUID: podUID,
|
||||
volName: spec.Name(),
|
||||
datasetName: datasetName,
|
||||
datasetUUID: datasetUUID,
|
||||
mounter: mounter,
|
||||
manager: manager,
|
||||
plugin: plugin,
|
||||
MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)),
|
||||
},
|
||||
readOnly: readOnly}, nil
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
|
||||
// Inject real implementations here, test through the internal function.
|
||||
return p.newUnmounterInternal(volName, podUID, &FlockerUtil{}, p.host.GetMounter())
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) newUnmounterInternal(volName string, podUID types.UID, manager volumeManager, mounter mount.Interface) (volume.Unmounter, error) {
|
||||
return &flockerVolumeUnmounter{&flockerVolume{
|
||||
podUID: podUID,
|
||||
volName: volName,
|
||||
manager: manager,
|
||||
mounter: mounter,
|
||||
plugin: p,
|
||||
MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, p.host)),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
|
||||
flockerVolume := &api.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: api.VolumeSource{
|
||||
Flocker: &api.FlockerVolumeSource{
|
||||
DatasetName: volumeName,
|
||||
},
|
||||
},
|
||||
}
|
||||
return volume.NewSpecFromVolume(flockerVolume), nil
|
||||
}
|
||||
|
||||
func (b *flockerVolume) GetDatasetUUID() (datasetUUID string, err error) {
|
||||
|
||||
// return UUID if set
|
||||
if len(b.datasetUUID) > 0 {
|
||||
return b.datasetUUID, nil
|
||||
}
|
||||
|
||||
if b.flockerClient == nil {
|
||||
return "", fmt.Errorf("Flocker client is not initialized")
|
||||
}
|
||||
|
||||
// lookup in flocker API otherwise
|
||||
return b.flockerClient.GetDatasetID(b.datasetName)
|
||||
}
|
||||
|
||||
type flockerVolumeMounter struct {
|
||||
*flockerVolume
|
||||
readOnly bool
|
||||
}
|
||||
|
||||
func (b *flockerVolumeMounter) GetAttributes() volume.Attributes {
|
||||
return volume.Attributes{
|
||||
ReadOnly: b.readOnly,
|
||||
Managed: false,
|
||||
SupportsSELinux: false,
|
||||
}
|
||||
}
|
||||
func (b *flockerVolumeMounter) GetPath() string {
|
||||
return getPath(b.podUID, b.volName, b.plugin.host)
|
||||
}
|
||||
|
||||
// SetUp bind mounts the disk global mount to the volume path.
|
||||
func (b *flockerVolumeMounter) SetUp(fsGroup *int64) error {
|
||||
return b.SetUpAt(b.GetPath(), fsGroup)
|
||||
}
|
||||
|
||||
// newFlockerClient uses environment variables and pod attributes to return a
|
||||
// flocker client capable of talking with the Flocker control service.
|
||||
func (p *flockerPlugin) newFlockerClient(hostIP string) (*flockerApi.Client, error) {
|
||||
host := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_HOST", defaultHost)
|
||||
port, err := env.GetEnvAsIntOrFallback("FLOCKER_CONTROL_SERVICE_PORT", defaultPort)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertPath := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_CA_FILE", defaultCACertFile)
|
||||
keyPath := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_CLIENT_KEY_FILE", defaultClientKeyFile)
|
||||
certPath := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_CLIENT_CERT_FILE", defaultClientCertFile)
|
||||
|
||||
c, err := flockerApi.NewClient(host, port, hostIP, caCertPath, keyPath, certPath)
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (b *flockerVolumeMounter) newFlockerClient() (*flockerApi.Client, error) {
|
||||
|
||||
hostIP, err := b.plugin.host.GetHostIP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.plugin.newFlockerClient(hostIP.String())
|
||||
}
|
||||
|
||||
/*
|
||||
SetUpAt will setup a Flocker volume following this flow of calls to the Flocker
|
||||
control service:
|
||||
|
||||
1. Get the dataset id for the given volume name/dir
|
||||
2. It should already be there, if it's not the user needs to manually create it
|
||||
3. Check the current Primary UUID
|
||||
4. If it doesn't match with the Primary UUID that we got on 2, then we will
|
||||
need to update the Primary UUID for this volume.
|
||||
5. Wait until the Primary UUID was updated or timeout.
|
||||
*/
|
||||
func (b *flockerVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||
var err error
|
||||
if b.flockerClient == nil {
|
||||
b.flockerClient, err = b.newFlockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
datasetUUID, err := b.GetDatasetUUID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("The datasetUUID for volume with datasetName='%s' can not be found using flocker: %s", b.datasetName, err)
|
||||
}
|
||||
|
||||
datasetState, err := b.flockerClient.GetDatasetState(datasetUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The datasetState for volume with datasetUUID='%s' could not determinted uusing flocker: %s", datasetUUID, err)
|
||||
}
|
||||
|
||||
primaryUUID, err := b.flockerClient.GetPrimaryUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if datasetState.Primary != primaryUUID {
|
||||
if err := b.updateDatasetPrimary(datasetUUID, primaryUUID); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := b.flockerClient.GetDatasetState(datasetUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The volume with datasetUUID='%s' migrated unsuccessfully.", datasetUUID)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle failed mounts here.
|
||||
notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
glog.V(4).Infof("flockerVolume set up: %s %v %v, datasetUUID %v readOnly %v", dir, !notMnt, err, datasetUUID, b.readOnly)
|
||||
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
|
||||
}
|
||||
|
||||
// Perform a bind mount to the full path to allow duplicate mounts of the same PD.
|
||||
options := []string{"bind"}
|
||||
if b.readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
|
||||
globalFlockerPath := makeGlobalFlockerPath(datasetUUID)
|
||||
glog.V(4).Infof("attempting to mount %s", dir)
|
||||
|
||||
err = b.mounter.Mount(globalFlockerPath, 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 {
|
||||
// This is very odd, we don't expect it. We'll try again next sync loop.
|
||||
glog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
os.Remove(dir)
|
||||
glog.Errorf("mount of disk %s failed: %v", dir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !b.readOnly {
|
||||
volume.SetVolumeOwnership(b, fsGroup)
|
||||
}
|
||||
|
||||
glog.V(4).Infof("successfully mounted %s", dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateDatasetPrimary will update the primary in Flocker and wait for it to
|
||||
// be ready. If it never gets to ready state it will timeout and error.
|
||||
func (b *flockerVolumeMounter) updateDatasetPrimary(datasetUUID string, primaryUUID string) error {
|
||||
// We need to update the primary and wait for it to be ready
|
||||
_, err := b.flockerClient.UpdatePrimaryForDataset(primaryUUID, datasetUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeoutChan := time.NewTimer(timeoutWaitingForVolume)
|
||||
defer timeoutChan.Stop()
|
||||
tickChan := time.NewTicker(tickerWaitingForVolume)
|
||||
defer tickChan.Stop()
|
||||
|
||||
for {
|
||||
if s, err := b.flockerClient.GetDatasetState(datasetUUID); err == nil && s.Primary == primaryUUID {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-timeoutChan.C:
|
||||
return fmt.Errorf(
|
||||
"Timed out waiting for the datasetUUID: '%s' to be moved to the primary: '%s'\n%v",
|
||||
datasetUUID, primaryUUID, err,
|
||||
)
|
||||
case <-tickChan.C:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getVolumeSource(spec *volume.Spec) (*api.FlockerVolumeSource, bool, error) {
|
||||
if spec.Volume != nil && spec.Volume.Flocker != nil {
|
||||
return spec.Volume.Flocker, spec.ReadOnly, nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.Flocker != nil {
|
||||
return spec.PersistentVolume.Spec.Flocker, spec.ReadOnly, nil
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("Spec does not reference a Flocker volume type")
|
||||
}
|
||||
|
||||
type flockerVolumeUnmounter struct {
|
||||
*flockerVolume
|
||||
}
|
||||
|
||||
var _ volume.Unmounter = &flockerVolumeUnmounter{}
|
||||
|
||||
func (c *flockerVolumeUnmounter) GetPath() string {
|
||||
return getPath(c.podUID, c.volName, c.plugin.host)
|
||||
}
|
||||
|
||||
// Unmounts the bind mount, and detaches the disk only if the PD
|
||||
// resource was the last reference to that disk on the kubelet.
|
||||
func (c *flockerVolumeUnmounter) TearDown() error {
|
||||
return c.TearDownAt(c.GetPath())
|
||||
}
|
||||
|
||||
// TearDownAt unmounts the bind mount
|
||||
func (c *flockerVolumeUnmounter) TearDownAt(dir string) error {
|
||||
notMnt, err := c.mounter.IsLikelyNotMountPoint(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if notMnt {
|
||||
return os.Remove(dir)
|
||||
}
|
||||
if err := c.mounter.Unmount(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
notMnt, mntErr := c.mounter.IsLikelyNotMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("isLikelyNotMountPoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if notMnt {
|
||||
return os.Remove(dir)
|
||||
}
|
||||
return fmt.Errorf("Failed to unmount volume dir")
|
||||
}
|
||||
|
||||
func (plugin *flockerPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
|
||||
return plugin.newDeleterInternal(spec, &FlockerUtil{})
|
||||
}
|
||||
|
||||
func (plugin *flockerPlugin) newDeleterInternal(spec *volume.Spec, manager volumeManager) (volume.Deleter, error) {
|
||||
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Flocker == nil {
|
||||
return nil, fmt.Errorf("spec.PersistentVolumeSource.Flocker is nil")
|
||||
}
|
||||
return &flockerVolumeDeleter{
|
||||
flockerVolume: &flockerVolume{
|
||||
volName: spec.Name(),
|
||||
datasetName: spec.PersistentVolume.Spec.Flocker.DatasetName,
|
||||
datasetUUID: spec.PersistentVolume.Spec.Flocker.DatasetUUID,
|
||||
manager: manager,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (plugin *flockerPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
|
||||
if len(options.AccessModes) == 0 {
|
||||
options.AccessModes = plugin.GetAccessModes()
|
||||
}
|
||||
return plugin.newProvisionerInternal(options, &FlockerUtil{})
|
||||
}
|
||||
|
||||
func (plugin *flockerPlugin) newProvisionerInternal(options volume.VolumeOptions, manager volumeManager) (volume.Provisioner, error) {
|
||||
return &flockerVolumeProvisioner{
|
||||
flockerVolume: &flockerVolume{
|
||||
manager: manager,
|
||||
plugin: plugin,
|
||||
},
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
|
@ -17,19 +17,111 @@ limitations under the License.
|
|||
package flocker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
flockerclient "github.com/ClusterHQ/flocker-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
utiltesting "k8s.io/kubernetes/pkg/util/testing"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
|
||||
flockerApi "github.com/clusterhq/flocker-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const pluginName = "kubernetes.io/flocker"
|
||||
const datasetOneID = "11111111-1111-1111-1111-111111111100"
|
||||
const nodeOneID = "11111111-1111-1111-1111-111111111111"
|
||||
const nodeTwoID = "22222222-2222-2222-2222-222222222222"
|
||||
|
||||
var _ flockerApi.Clientable = &fakeFlockerClient{}
|
||||
|
||||
type fakeFlockerClient struct {
|
||||
DatasetID string
|
||||
Primary string
|
||||
Deleted bool
|
||||
Metadata map[string]string
|
||||
Nodes []flockerApi.NodeState
|
||||
Error error
|
||||
}
|
||||
|
||||
func newFakeFlockerClient() *fakeFlockerClient {
|
||||
return &fakeFlockerClient{
|
||||
DatasetID: datasetOneID,
|
||||
Primary: nodeOneID,
|
||||
Deleted: false,
|
||||
Metadata: map[string]string{"Name": "dataset-one"},
|
||||
Nodes: []flockerApi.NodeState{
|
||||
{
|
||||
Host: "1.2.3.4",
|
||||
UUID: nodeOneID,
|
||||
},
|
||||
{
|
||||
Host: "4.5.6.7",
|
||||
UUID: nodeTwoID,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *fakeFlockerClient) CreateDataset(options *flockerApi.CreateDatasetOptions) (*flockerApi.DatasetState, error) {
|
||||
|
||||
if c.Error != nil {
|
||||
return nil, c.Error
|
||||
}
|
||||
|
||||
return &flockerApi.DatasetState{
|
||||
DatasetID: c.DatasetID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *fakeFlockerClient) DeleteDataset(datasetID string) error {
|
||||
c.DatasetID = datasetID
|
||||
c.Deleted = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeFlockerClient) GetDatasetState(datasetID string) (*flockerApi.DatasetState, error) {
|
||||
return &flockerApi.DatasetState{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeFlockerClient) GetDatasetID(metaName string) (datasetID string, err error) {
|
||||
if val, ok := c.Metadata["Name"]; !ok {
|
||||
return val, nil
|
||||
}
|
||||
return "", fmt.Errorf("No dataset with metadata X found")
|
||||
}
|
||||
|
||||
func (c *fakeFlockerClient) GetPrimaryUUID() (primaryUUID string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *fakeFlockerClient) ListNodes() (nodes []flockerApi.NodeState, err error) {
|
||||
return c.Nodes, nil
|
||||
}
|
||||
|
||||
func (c *fakeFlockerClient) UpdatePrimaryForDataset(primaryUUID, datasetID string) (*flockerApi.DatasetState, error) {
|
||||
return &flockerApi.DatasetState{}, nil
|
||||
}
|
||||
|
||||
type fakeFlockerUtil struct {
|
||||
}
|
||||
|
||||
func (fake *fakeFlockerUtil) CreateVolume(c *flockerVolumeProvisioner) (datasetUUID string, volumeSizeGB int, labels map[string]string, err error) {
|
||||
labels = make(map[string]string)
|
||||
labels["fakeflockerutil"] = "yes"
|
||||
return "test-flocker-volume-uuid", 3, labels, nil
|
||||
}
|
||||
|
||||
func (fake *fakeFlockerUtil) DeleteVolume(cd *flockerVolumeDeleter) error {
|
||||
if cd.datasetUUID != "test-flocker-volume-uuid" {
|
||||
return fmt.Errorf("Deleter got unexpected datasetUUID: %s", cd.datasetUUID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newInitializedVolumePlugMgr(t *testing.T) (*volume.VolumePluginMgr, string) {
|
||||
plugMgr := &volume.VolumePluginMgr{}
|
||||
|
@ -39,6 +131,38 @@ func newInitializedVolumePlugMgr(t *testing.T) (*volume.VolumePluginMgr, string)
|
|||
return plugMgr, dir
|
||||
}
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("flockerTest")
|
||||
if err != nil {
|
||||
t.Fatalf("can't make a temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/flocker")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
spec := &api.Volume{
|
||||
Name: "vol1",
|
||||
VolumeSource: api.VolumeSource{
|
||||
Flocker: &api.FlockerVolumeSource{
|
||||
DatasetUUID: "uuid1",
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeManager := &fakeFlockerUtil{}
|
||||
fakeMounter := &mount.FakeMounter{}
|
||||
mounter, err := plug.(*flockerPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), fakeManager, fakeMounter)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Mounter: %v", err)
|
||||
}
|
||||
if mounter == nil {
|
||||
t.Errorf("Got a nil Mounter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByName(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
plugMgr, _ := newInitializedVolumePlugMgr(t)
|
||||
|
@ -115,7 +239,7 @@ func TestGetFlockerVolumeSource(t *testing.T) {
|
|||
assert.Equal(spec.PersistentVolume.Spec.Flocker, vs)
|
||||
}
|
||||
|
||||
func TestNewMounter(t *testing.T) {
|
||||
func TestNewMounterDatasetName(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
plugMgr, _ := newInitializedVolumePlugMgr(t)
|
||||
|
@ -136,7 +260,31 @@ func TestNewMounter(t *testing.T) {
|
|||
assert.NoError(err)
|
||||
}
|
||||
|
||||
func TestNewMounterDatasetUUID(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
plugMgr, _ := newInitializedVolumePlugMgr(t)
|
||||
plug, err := plugMgr.FindPluginByName(pluginName)
|
||||
assert.NoError(err)
|
||||
|
||||
spec := &volume.Spec{
|
||||
Volume: &api.Volume{
|
||||
VolumeSource: api.VolumeSource{
|
||||
Flocker: &api.FlockerVolumeSource{
|
||||
DatasetUUID: "uuid1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mounter, err := plug.NewMounter(spec, &api.Pod{}, volume.VolumeOptions{})
|
||||
assert.NoError(err)
|
||||
assert.NotNil(mounter, "got a nil mounter")
|
||||
|
||||
}
|
||||
|
||||
func TestNewUnmounter(t *testing.T) {
|
||||
t.Skip("broken")
|
||||
assert := assert.New(t)
|
||||
|
||||
p := flockerPlugin{}
|
||||
|
@ -147,22 +295,13 @@ func TestNewUnmounter(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIsReadOnly(t *testing.T) {
|
||||
b := &flockerMounter{readOnly: true}
|
||||
b := &flockerVolumeMounter{readOnly: true}
|
||||
assert.True(t, b.GetAttributes().ReadOnly)
|
||||
}
|
||||
|
||||
func TestGetPath(t *testing.T) {
|
||||
const expectedPath = "/flocker/expected"
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
b := flockerMounter{flocker: &flocker{path: expectedPath}}
|
||||
assert.Equal(expectedPath, b.GetPath())
|
||||
}
|
||||
|
||||
type mockFlockerClient struct {
|
||||
datasetID, primaryUUID, path string
|
||||
datasetState *flockerclient.DatasetState
|
||||
datasetState *flockerApi.DatasetState
|
||||
}
|
||||
|
||||
func newMockFlockerClient(mockDatasetID, mockPrimaryUUID, mockPath string) *mockFlockerClient {
|
||||
|
@ -170,7 +309,7 @@ func newMockFlockerClient(mockDatasetID, mockPrimaryUUID, mockPath string) *mock
|
|||
datasetID: mockDatasetID,
|
||||
primaryUUID: mockPrimaryUUID,
|
||||
path: mockPath,
|
||||
datasetState: &flockerclient.DatasetState{
|
||||
datasetState: &flockerApi.DatasetState{
|
||||
Path: mockPath,
|
||||
DatasetID: mockDatasetID,
|
||||
Primary: mockPrimaryUUID,
|
||||
|
@ -178,10 +317,10 @@ func newMockFlockerClient(mockDatasetID, mockPrimaryUUID, mockPath string) *mock
|
|||
}
|
||||
}
|
||||
|
||||
func (m mockFlockerClient) CreateDataset(metaName string) (*flockerclient.DatasetState, error) {
|
||||
func (m mockFlockerClient) CreateDataset(metaName string) (*flockerApi.DatasetState, error) {
|
||||
return m.datasetState, nil
|
||||
}
|
||||
func (m mockFlockerClient) GetDatasetState(datasetID string) (*flockerclient.DatasetState, error) {
|
||||
func (m mockFlockerClient) GetDatasetState(datasetID string) (*flockerApi.DatasetState, error) {
|
||||
return m.datasetState, nil
|
||||
}
|
||||
func (m mockFlockerClient) GetDatasetID(metaName string) (string, error) {
|
||||
|
@ -190,10 +329,12 @@ func (m mockFlockerClient) GetDatasetID(metaName string) (string, error) {
|
|||
func (m mockFlockerClient) GetPrimaryUUID() (string, error) {
|
||||
return m.primaryUUID, nil
|
||||
}
|
||||
func (m mockFlockerClient) UpdatePrimaryForDataset(primaryUUID, datasetID string) (*flockerclient.DatasetState, error) {
|
||||
func (m mockFlockerClient) UpdatePrimaryForDataset(primaryUUID, datasetID string) (*flockerApi.DatasetState, error) {
|
||||
return m.datasetState, nil
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: reenable after refactor
|
||||
func TestSetUpAtInternal(t *testing.T) {
|
||||
const dir = "dir"
|
||||
mockPath := "expected-to-be-set-properly" // package var
|
||||
|
@ -209,9 +350,10 @@ func TestSetUpAtInternal(t *testing.T) {
|
|||
assert.NoError(err)
|
||||
|
||||
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
|
||||
b := flockerMounter{flocker: &flocker{pod: pod, plugin: plug.(*flockerPlugin)}}
|
||||
b := flockerVolumeMounter{flockerVolume: &flockerVolume{pod: pod, plugin: plug.(*flockerPlugin)}}
|
||||
b.client = newMockFlockerClient("dataset-id", "primary-uid", mockPath)
|
||||
|
||||
assert.NoError(b.SetUpAt(dir, nil))
|
||||
assert.Equal(expectedPath, b.flocker.path)
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flocker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/rand"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
|
||||
flockerApi "github.com/clusterhq/flocker-go"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type FlockerUtil struct{}
|
||||
|
||||
func (util *FlockerUtil) DeleteVolume(d *flockerVolumeDeleter) error {
|
||||
var err error
|
||||
|
||||
if d.flockerClient == nil {
|
||||
d.flockerClient, err = d.plugin.newFlockerClient("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
datasetUUID, err := d.GetDatasetUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.flockerClient.DeleteDataset(datasetUUID)
|
||||
}
|
||||
|
||||
func (util *FlockerUtil) CreateVolume(c *flockerVolumeProvisioner) (datasetUUID string, volumeSizeGB int, labels map[string]string, err error) {
|
||||
|
||||
if c.flockerClient == nil {
|
||||
c.flockerClient, err = c.plugin.newFlockerClient("")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
nodes, err := c.flockerClient.ListNodes()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(nodes) < 1 {
|
||||
err = fmt.Errorf("no nodes found inside the flocker cluster to provision a dataset")
|
||||
return
|
||||
}
|
||||
|
||||
// select random node
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
node := nodes[rand.Intn(len(nodes))]
|
||||
glog.V(2).Infof("selected flocker node with UUID '%s' to provision dataset", node.UUID)
|
||||
|
||||
requestBytes := c.options.Capacity.Value()
|
||||
volumeSizeGB = int(volume.RoundUpSize(requestBytes, 1024*1024*1024))
|
||||
|
||||
createOptions := &flockerApi.CreateDatasetOptions{
|
||||
MaximumSize: requestBytes,
|
||||
Metadata: map[string]string{
|
||||
"type": "k8s-dynamic-prov",
|
||||
"pvc": c.options.PVCName,
|
||||
},
|
||||
Primary: node.UUID,
|
||||
}
|
||||
|
||||
datasetState, err := c.flockerClient.CreateDataset(createOptions)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
datasetUUID = datasetState.DatasetID
|
||||
|
||||
glog.V(2).Infof("successfully created Flocker dataset with UUID '%s'", datasetUUID)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flocker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFlockerUtil_CreateVolume(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// test CreateVolume happy path
|
||||
options := volume.VolumeOptions{
|
||||
Capacity: resource.MustParse("3Gi"),
|
||||
AccessModes: []api.PersistentVolumeAccessMode{
|
||||
api.ReadWriteOnce,
|
||||
},
|
||||
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
|
||||
}
|
||||
|
||||
fakeFlockerClient := newFakeFlockerClient()
|
||||
provisioner := newTestableProvisioner(assert, options).(*flockerVolumeProvisioner)
|
||||
provisioner.flockerClient = fakeFlockerClient
|
||||
|
||||
flockerUtil := &FlockerUtil{}
|
||||
|
||||
datasetID, size, _, err := flockerUtil.CreateVolume(provisioner)
|
||||
assert.NoError(err)
|
||||
assert.Equal(datasetOneID, datasetID)
|
||||
assert.Equal(3, size)
|
||||
|
||||
// test error during CreateVolume
|
||||
fakeFlockerClient.Error = fmt.Errorf("Do not feel like provisioning")
|
||||
_, _, _, err = flockerUtil.CreateVolume(provisioner)
|
||||
assert.Equal(fakeFlockerClient.Error.Error(), err.Error())
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flocker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
type volumeManager interface {
|
||||
// Creates a volume
|
||||
CreateVolume(provisioner *flockerVolumeProvisioner) (datasetUUID string, volumeSizeGB int, labels map[string]string, err error)
|
||||
// Deletes a volume
|
||||
DeleteVolume(deleter *flockerVolumeDeleter) error
|
||||
}
|
||||
|
||||
type flockerVolumeDeleter struct {
|
||||
*flockerVolume
|
||||
}
|
||||
|
||||
var _ volume.Deleter = &flockerVolumeDeleter{}
|
||||
|
||||
func (b *flockerVolumeDeleter) GetPath() string {
|
||||
return getPath(b.podUID, b.volName, b.plugin.host)
|
||||
}
|
||||
|
||||
func (d *flockerVolumeDeleter) Delete() error {
|
||||
return d.manager.DeleteVolume(d)
|
||||
}
|
||||
|
||||
type flockerVolumeProvisioner struct {
|
||||
*flockerVolume
|
||||
options volume.VolumeOptions
|
||||
}
|
||||
|
||||
var _ volume.Provisioner = &flockerVolumeProvisioner{}
|
||||
|
||||
func (c *flockerVolumeProvisioner) Provision() (*api.PersistentVolume, error) {
|
||||
|
||||
if len(c.options.Parameters) > 0 {
|
||||
return nil, fmt.Errorf("Provisioning failed: Specified at least one unsupported parameter")
|
||||
}
|
||||
|
||||
if c.options.Selector != nil {
|
||||
return nil, fmt.Errorf("Provisioning failed: Specified unsupported selector")
|
||||
}
|
||||
|
||||
datasetUUID, sizeGB, labels, err := c.manager.CreateVolume(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pv := &api.PersistentVolume{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: c.options.PVName,
|
||||
Labels: map[string]string{},
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/createdby": "flocker-dynamic-provisioner",
|
||||
},
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
|
||||
AccessModes: c.options.AccessModes,
|
||||
Capacity: api.ResourceList{
|
||||
api.ResourceName(api.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
|
||||
},
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
Flocker: &api.FlockerVolumeSource{
|
||||
DatasetUUID: datasetUUID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if len(labels) != 0 {
|
||||
if pv.Labels == nil {
|
||||
pv.Labels = make(map[string]string)
|
||||
}
|
||||
for k, v := range labels {
|
||||
pv.Labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return pv, nil
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flocker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
utiltesting "k8s.io/kubernetes/pkg/util/testing"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func newTestableProvisioner(assert *assert.Assertions, options volume.VolumeOptions) volume.Provisioner {
|
||||
tmpDir, err := utiltesting.MkTmpdir("flockervolumeTest")
|
||||
assert.NoError(err, fmt.Sprintf("can't make a temp dir: %v", err))
|
||||
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil, "" /* rootContext */))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName(pluginName)
|
||||
assert.NoError(err, "Can't find the plugin by name")
|
||||
|
||||
provisioner, err := plug.(*flockerPlugin).newProvisionerInternal(options, &fakeFlockerUtil{})
|
||||
|
||||
return provisioner
|
||||
}
|
||||
|
||||
func TestProvision(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cap := resource.MustParse("3Gi")
|
||||
options := volume.VolumeOptions{
|
||||
Capacity: cap,
|
||||
AccessModes: []api.PersistentVolumeAccessMode{
|
||||
api.ReadWriteOnce,
|
||||
},
|
||||
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
|
||||
}
|
||||
|
||||
provisioner := newTestableProvisioner(assert, options)
|
||||
|
||||
persistentSpec, err := provisioner.Provision()
|
||||
assert.NoError(err, "Provision() failed: ", err)
|
||||
|
||||
cap = persistentSpec.Spec.Capacity[api.ResourceStorage]
|
||||
|
||||
assert.Equal(int64(3*1024*1024*1024), cap.Value())
|
||||
|
||||
assert.Equal(
|
||||
"test-flocker-volume-uuid",
|
||||
persistentSpec.Spec.PersistentVolumeSource.Flocker.DatasetUUID,
|
||||
)
|
||||
|
||||
assert.Equal(
|
||||
map[string]string{"fakeflockerutil": "yes"},
|
||||
persistentSpec.Labels,
|
||||
)
|
||||
|
||||
// parameters are not supported
|
||||
options = volume.VolumeOptions{
|
||||
Capacity: cap,
|
||||
AccessModes: []api.PersistentVolumeAccessMode{
|
||||
api.ReadWriteOnce,
|
||||
},
|
||||
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
|
||||
Parameters: map[string]string{
|
||||
"not-supported-params": "test123",
|
||||
},
|
||||
}
|
||||
|
||||
provisioner = newTestableProvisioner(assert, options)
|
||||
persistentSpec, err = provisioner.Provision()
|
||||
assert.Error(err, "Provision() did not fail with Parameters specified")
|
||||
|
||||
// selectors are not supported
|
||||
options = volume.VolumeOptions{
|
||||
Capacity: cap,
|
||||
AccessModes: []api.PersistentVolumeAccessMode{
|
||||
api.ReadWriteOnce,
|
||||
},
|
||||
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
|
||||
Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"key": "value"}},
|
||||
}
|
||||
|
||||
provisioner = newTestableProvisioner(assert, options)
|
||||
persistentSpec, err = provisioner.Provision()
|
||||
assert.Error(err, "Provision() did not fail with Selector specified")
|
||||
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flocker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/env"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
|
||||
flockerclient "github.com/ClusterHQ/flocker-go"
|
||||
)
|
||||
|
||||
const (
|
||||
flockerPluginName = "kubernetes.io/flocker"
|
||||
|
||||
defaultHost = "localhost"
|
||||
defaultPort = 4523
|
||||
defaultCACertFile = "/etc/flocker/cluster.crt"
|
||||
defaultClientKeyFile = "/etc/flocker/apiuser.key"
|
||||
defaultClientCertFile = "/etc/flocker/apiuser.crt"
|
||||
|
||||
timeoutWaitingForVolume = 2 * time.Minute
|
||||
tickerWaitingForVolume = 5 * time.Second
|
||||
)
|
||||
|
||||
func ProbeVolumePlugins() []volume.VolumePlugin {
|
||||
return []volume.VolumePlugin{&flockerPlugin{}}
|
||||
}
|
||||
|
||||
type flockerPlugin struct {
|
||||
host volume.VolumeHost
|
||||
}
|
||||
|
||||
type flocker struct {
|
||||
datasetName string
|
||||
path string
|
||||
pod *api.Pod
|
||||
mounter mount.Interface
|
||||
plugin *flockerPlugin
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) Init(host volume.VolumeHost) error {
|
||||
p.host = host
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) GetPluginName() string {
|
||||
return flockerPluginName
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return volumeSource.DatasetName, nil
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) CanSupport(spec *volume.Spec) bool {
|
||||
return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Flocker != nil) ||
|
||||
(spec.Volume != nil && spec.Volume.Flocker != nil)
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) RequiresRemount() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) getFlockerVolumeSource(spec *volume.Spec) (*api.FlockerVolumeSource, bool) {
|
||||
// AFAIK this will always be r/w, but perhaps for the future it will be needed
|
||||
readOnly := false
|
||||
|
||||
if spec.Volume != nil && spec.Volume.Flocker != nil {
|
||||
return spec.Volume.Flocker, readOnly
|
||||
}
|
||||
return spec.PersistentVolume.Spec.Flocker, readOnly
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
|
||||
source, readOnly := p.getFlockerVolumeSource(spec)
|
||||
mounter := flockerMounter{
|
||||
flocker: &flocker{
|
||||
datasetName: source.DatasetName,
|
||||
pod: pod,
|
||||
mounter: p.host.GetMounter(),
|
||||
plugin: p,
|
||||
},
|
||||
exe: exec.New(),
|
||||
opts: opts,
|
||||
readOnly: readOnly,
|
||||
}
|
||||
return &mounter, nil
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) NewUnmounter(datasetName string, podUID types.UID) (volume.Unmounter, error) {
|
||||
// Flocker agent will take care of this, there is nothing we can do here
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *flockerPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
|
||||
flockerVolume := &api.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: api.VolumeSource{
|
||||
Flocker: &api.FlockerVolumeSource{
|
||||
DatasetName: volumeName,
|
||||
},
|
||||
},
|
||||
}
|
||||
return volume.NewSpecFromVolume(flockerVolume), nil
|
||||
}
|
||||
|
||||
type flockerMounter struct {
|
||||
*flocker
|
||||
client flockerclient.Clientable
|
||||
exe exec.Interface
|
||||
opts volume.VolumeOptions
|
||||
readOnly bool
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (b flockerMounter) GetAttributes() volume.Attributes {
|
||||
return volume.Attributes{
|
||||
ReadOnly: b.readOnly,
|
||||
Managed: false,
|
||||
SupportsSELinux: false,
|
||||
}
|
||||
}
|
||||
func (b flockerMounter) GetPath() string {
|
||||
return b.flocker.path
|
||||
}
|
||||
|
||||
func (b flockerMounter) SetUp(fsGroup *int64) error {
|
||||
return b.SetUpAt(b.flocker.datasetName, fsGroup)
|
||||
}
|
||||
|
||||
// newFlockerClient uses environment variables and pod attributes to return a
|
||||
// flocker client capable of talking with the Flocker control service.
|
||||
func (b flockerMounter) newFlockerClient() (*flockerclient.Client, error) {
|
||||
host := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_HOST", defaultHost)
|
||||
port, err := env.GetEnvAsIntOrFallback("FLOCKER_CONTROL_SERVICE_PORT", defaultPort)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caCertPath := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_CA_FILE", defaultCACertFile)
|
||||
keyPath := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_CLIENT_KEY_FILE", defaultClientKeyFile)
|
||||
certPath := env.GetEnvAsStringOrFallback("FLOCKER_CONTROL_SERVICE_CLIENT_CERT_FILE", defaultClientCertFile)
|
||||
|
||||
hostIP, err := b.plugin.host.GetHostIP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := flockerclient.NewClient(host, port, hostIP.String(), caCertPath, keyPath, certPath)
|
||||
return c, err
|
||||
}
|
||||
|
||||
/*
|
||||
SetUpAt will setup a Flocker volume following this flow of calls to the Flocker
|
||||
control service:
|
||||
|
||||
1. Get the dataset id for the given volume name/dir
|
||||
2. It should already be there, if it's not the user needs to manually create it
|
||||
3. Check the current Primary UUID
|
||||
4. If it doesn't match with the Primary UUID that we got on 2, then we will
|
||||
need to update the Primary UUID for this volume.
|
||||
5. Wait until the Primary UUID was updated or timeout.
|
||||
*/
|
||||
func (b flockerMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||
if b.client == nil {
|
||||
c, err := b.newFlockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.client = c
|
||||
}
|
||||
|
||||
datasetID, err := b.client.GetDatasetID(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := b.client.GetDatasetState(datasetID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The volume '%s' is not available in Flocker. You need to create this manually with Flocker CLI before using it.", dir)
|
||||
}
|
||||
|
||||
primaryUUID, err := b.client.GetPrimaryUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.Primary != primaryUUID {
|
||||
if err := b.updateDatasetPrimary(datasetID, primaryUUID); err != nil {
|
||||
return err
|
||||
}
|
||||
newState, err := b.client.GetDatasetState(datasetID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("The volume '%s' migrated unsuccessfully.", datasetID)
|
||||
}
|
||||
b.flocker.path = newState.Path
|
||||
} else {
|
||||
b.flocker.path = s.Path
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateDatasetPrimary will update the primary in Flocker and wait for it to
|
||||
// be ready. If it never gets to ready state it will timeout and error.
|
||||
func (b flockerMounter) updateDatasetPrimary(datasetID, primaryUUID string) error {
|
||||
// We need to update the primary and wait for it to be ready
|
||||
_, err := b.client.UpdatePrimaryForDataset(primaryUUID, datasetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeoutChan := time.NewTimer(timeoutWaitingForVolume)
|
||||
defer timeoutChan.Stop()
|
||||
tickChan := time.NewTicker(tickerWaitingForVolume)
|
||||
defer tickChan.Stop()
|
||||
|
||||
for {
|
||||
if s, err := b.client.GetDatasetState(datasetID); err == nil && s.Primary == primaryUUID {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-timeoutChan.C:
|
||||
return fmt.Errorf(
|
||||
"Timed out waiting for the dataset_id: '%s' to be moved to the primary: '%s'\n%v",
|
||||
datasetID, primaryUUID, err,
|
||||
)
|
||||
case <-tickChan.C:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getVolumeSource(spec *volume.Spec) (*api.FlockerVolumeSource, bool, error) {
|
||||
if spec.Volume != nil && spec.Volume.Flocker != nil {
|
||||
return spec.Volume.Flocker, spec.ReadOnly, nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.Flocker != nil {
|
||||
return spec.PersistentVolume.Spec.Flocker, spec.ReadOnly, nil
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("Spec does not reference a Flocker volume type")
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
machine:
|
||||
timezone:
|
||||
America/Los_Angeles
|
||||
|
||||
# Output the test output to circle.
|
||||
test:
|
||||
pre:
|
||||
- go get -u github.com/jstemmer/go-junit-report
|
||||
override:
|
||||
- go test -coverprofile=coverage.out -v -race ./... > test.out
|
||||
- cat test.out | go-junit-report > report.xml
|
||||
- go tool cover -func=coverage.out
|
||||
post:
|
||||
- mv test.out $CIRCLE_ARTIFACTS/
|
||||
- mv report.xml $CIRCLE_TEST_REPORTS/
|
||||
|
|
@ -34,12 +34,15 @@ var (
|
|||
|
||||
// Clientable exposes the needed methods to implement your own Flocker Client.
|
||||
type Clientable interface {
|
||||
CreateDataset(metaName string) (*DatasetState, error)
|
||||
CreateDataset(options *CreateDatasetOptions) (*DatasetState, error)
|
||||
DeleteDataset(datasetID string) error
|
||||
|
||||
GetDatasetState(datasetID string) (*DatasetState, error)
|
||||
GetDatasetID(metaName string) (datasetID string, err error)
|
||||
GetPrimaryUUID() (primaryUUID string, err error)
|
||||
|
||||
ListNodes() (nodes []NodeState, err error)
|
||||
|
||||
UpdatePrimaryForDataset(primaryUUID, datasetID string) (*DatasetState, error)
|
||||
}
|
||||
|
||||
|
@ -57,6 +60,8 @@ type Client struct {
|
|||
maximumSize json.Number
|
||||
}
|
||||
|
||||
var _ Clientable = &Client{}
|
||||
|
||||
// NewClient creates a wrapper over http.Client to communicate with the flocker control service.
|
||||
func NewClient(host string, port int, clientIP string, caCertPath, keyPath, certPath string) (*Client, error) {
|
||||
client, err := newTLSClient(caCertPath, keyPath, certPath)
|
||||
|
@ -110,6 +115,11 @@ func (c Client) post(url string, payload interface{}) (*http.Response, error) {
|
|||
return c.request("POST", url, payload)
|
||||
}
|
||||
|
||||
// delete performs a delete request with the indicated payload
|
||||
func (c Client) delete(url string, payload interface{}) (*http.Response, error) {
|
||||
return c.request("DELETE", url, payload)
|
||||
}
|
||||
|
||||
// get performs a get request
|
||||
func (c Client) get(url string) (*http.Response, error) {
|
||||
return c.request("GET", url, nil)
|
||||
|
@ -128,6 +138,13 @@ type configurationPayload struct {
|
|||
Metadata metadataPayload `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type CreateDatasetOptions struct {
|
||||
Primary string `json:"primary"`
|
||||
DatasetID string `json:"dataset_id,omitempty"`
|
||||
MaximumSize int64 `json:"maximum_size,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type metadataPayload struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
@ -143,7 +160,7 @@ type datasetStatePayload struct {
|
|||
*DatasetState
|
||||
}
|
||||
|
||||
type nodeStatePayload struct {
|
||||
type NodeState struct {
|
||||
UUID string `json:"uuid"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
@ -163,25 +180,54 @@ func (c Client) findIDInConfigurationsPayload(body io.ReadCloser, name string) (
|
|||
return "", err
|
||||
}
|
||||
|
||||
// ListNodes returns a list of dataset agent nodes from Flocker Control Service
|
||||
func (c *Client) ListNodes() (nodes []NodeState, err error) {
|
||||
resp, err := c.get(c.getURL("state/nodes"))
|
||||
if err != nil {
|
||||
return []NodeState{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
return []NodeState{}, fmt.Errorf("Expected: {1,2}xx listing nodes, got: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&nodes)
|
||||
if err != nil {
|
||||
return []NodeState{}, err
|
||||
}
|
||||
return nodes, err
|
||||
}
|
||||
|
||||
// GetPrimaryUUID returns the UUID of the primary Flocker Control Service for
|
||||
// the given host.
|
||||
func (c Client) GetPrimaryUUID() (uuid string, err error) {
|
||||
resp, err := c.get(c.getURL("state/nodes"))
|
||||
states, err := c.ListNodes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, s := range states {
|
||||
if s.Host == c.clientIP {
|
||||
return s.UUID, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("No node found with IP '%s', available nodes %+v", c.clientIP, states)
|
||||
}
|
||||
|
||||
// DeleteDataset performs a delete request to the given datasetID
|
||||
func (c *Client) DeleteDataset(datasetID string) error {
|
||||
url := c.getURL(fmt.Sprintf("configuration/datasets/%s", datasetID))
|
||||
resp, err := c.delete(url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var states []nodeStatePayload
|
||||
if err = json.NewDecoder(resp.Body).Decode(&states); err == nil {
|
||||
for _, s := range states {
|
||||
if s.Host == c.clientIP {
|
||||
return s.UUID, nil
|
||||
}
|
||||
}
|
||||
return "", errStateNotFound
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("Expected: {1,2}xx deleting the dataset %s, got: %d", datasetID, resp.StatusCode)
|
||||
}
|
||||
return "", err
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDatasetState performs a get request to get the state of the given datasetID, if
|
||||
|
@ -213,34 +259,30 @@ returns the dataset id.
|
|||
This process is a little bit complex but follows this flow:
|
||||
|
||||
1. Find the Flocker Control Service UUID
|
||||
2. Try to create the dataset
|
||||
3. If it already exists an error is returned
|
||||
4. If it didn't previously exist, wait for it to be ready
|
||||
2. If it already exists an error is returned
|
||||
3. If it didn't previously exist, wait for it to be ready
|
||||
*/
|
||||
func (c Client) CreateDataset(metaName string) (*DatasetState, error) {
|
||||
func (c *Client) CreateDataset(options *CreateDatasetOptions) (datasetState *DatasetState, err error) {
|
||||
// 1) Find the primary Flocker UUID
|
||||
// Note: it could be cached, but doing this query we health check it
|
||||
primary, err := c.GetPrimaryUUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if options.Primary == "" {
|
||||
options.Primary, err = c.GetPrimaryUUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Try to create the dataset in the given Primary
|
||||
payload := configurationPayload{
|
||||
Primary: primary,
|
||||
MaximumSize: json.Number(c.maximumSize),
|
||||
Metadata: metadataPayload{
|
||||
Name: metaName,
|
||||
},
|
||||
if options.MaximumSize == 0 {
|
||||
options.MaximumSize, _ = c.maximumSize.Int64()
|
||||
}
|
||||
|
||||
resp, err := c.post(c.getURL("configuration/datasets"), payload)
|
||||
resp, err := c.post(c.getURL("configuration/datasets"), options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 3) Return if the dataset was previously created
|
||||
// 2) Return if the dataset was previously created
|
||||
if resp.StatusCode == http.StatusConflict {
|
||||
return nil, errVolumeAlreadyExists
|
||||
}
|
||||
|
@ -254,21 +296,31 @@ func (c Client) CreateDataset(metaName string) (*DatasetState, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// 4) Wait until the dataset is ready for usage. In case it never gets
|
||||
// 3) Wait until the dataset is ready for usage. In case it never gets
|
||||
// ready there is a timeoutChan that will return an error
|
||||
timeoutChan := time.NewTimer(timeoutWaitingForVolume).C
|
||||
tickChan := time.NewTicker(tickerWaitingForVolume).C
|
||||
|
||||
for {
|
||||
if s, err := c.GetDatasetState(p.DatasetID); err == nil {
|
||||
var strErrDel string
|
||||
s, err := c.GetDatasetState(p.DatasetID)
|
||||
if err == nil {
|
||||
return s, nil
|
||||
} else if err != errStateNotFound {
|
||||
return nil, err
|
||||
errDel := c.DeleteDataset(p.DatasetID)
|
||||
if errDel != nil {
|
||||
strErrDel = fmt.Sprintf(", deletion of dataset failed with %s", errDel)
|
||||
}
|
||||
return nil, fmt.Errorf("Flocker API error during dataset creation (datasetID %s): %s%s", p.DatasetID, err, strErrDel)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-timeoutChan:
|
||||
return nil, err
|
||||
errDel := c.DeleteDataset(p.DatasetID)
|
||||
if errDel != nil {
|
||||
strErrDel = fmt.Sprintf(", deletion of dataset failed with %s", errDel)
|
||||
}
|
||||
return nil, fmt.Errorf("Flocker API timeout during dataset creation (datasetID %s): %s%s", p.DatasetID, err, strErrDel)
|
||||
case <-tickChan:
|
||||
break
|
||||
}
|
Loading…
Reference in New Issue