diff --git a/contrib/completions/bash/oadm b/contrib/completions/bash/oadm index 9b11f772e260..1a15618e0c8f 100644 --- a/contrib/completions/bash/oadm +++ b/contrib/completions/bash/oadm @@ -2474,6 +2474,7 @@ _oadm_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") diff --git a/contrib/completions/bash/oc b/contrib/completions/bash/oc index 2978f3954c01..0864ca067642 100644 --- a/contrib/completions/bash/oc +++ b/contrib/completions/bash/oc @@ -1626,6 +1626,7 @@ _oc_get() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -1709,6 +1710,7 @@ _oc_get() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -1975,6 +1977,7 @@ _oc_edit() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -2058,6 +2061,7 @@ _oc_edit() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -2663,6 +2667,7 @@ _oc_label() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -2746,6 +2751,7 @@ _oc_label() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -3071,6 +3077,7 @@ _oc_delete() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -3154,6 +3161,7 @@ _oc_delete() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -6566,6 +6574,7 @@ _oc_adm_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -9438,6 +9447,7 @@ _oc_patch() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -9521,6 +9531,7 @@ _oc_patch() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") diff --git a/contrib/completions/bash/openshift b/contrib/completions/bash/openshift index bc63f7f7efe0..4dbfde840cba 100644 --- a/contrib/completions/bash/openshift +++ b/contrib/completions/bash/openshift @@ -3122,6 +3122,7 @@ _openshift_admin_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -5966,6 +5967,7 @@ _openshift_cli_get() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -6049,6 +6051,7 @@ _openshift_cli_get() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -6317,6 +6320,7 @@ _openshift_cli_edit() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -6400,6 +6404,7 @@ _openshift_cli_edit() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -7013,6 +7018,7 @@ _openshift_cli_label() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -7096,6 +7102,7 @@ _openshift_cli_label() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -7424,6 +7431,7 @@ _openshift_cli_delete() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -7507,6 +7515,7 @@ _openshift_cli_delete() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -10984,6 +10993,7 @@ _openshift_cli_adm_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -13911,6 +13921,7 @@ _openshift_cli_patch() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -13994,6 +14005,7 @@ _openshift_cli_patch() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -15903,6 +15915,7 @@ _openshift_kube_get() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -15986,6 +15999,7 @@ _openshift_kube_get() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -17497,6 +17511,7 @@ _openshift_kube_patch() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -17580,6 +17595,7 @@ _openshift_kube_patch() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -17746,6 +17762,7 @@ _openshift_kube_delete() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -17829,6 +17846,7 @@ _openshift_kube_delete() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -17991,6 +18009,7 @@ _openshift_kube_edit() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -18074,6 +18093,7 @@ _openshift_kube_edit() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -20233,6 +20253,7 @@ _openshift_kube_label() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -20316,6 +20337,7 @@ _openshift_kube_label() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -20625,6 +20647,7 @@ _openshift_kube_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") diff --git a/contrib/completions/zsh/oadm b/contrib/completions/zsh/oadm index 5011577dbeff..dea4f4245d90 100644 --- a/contrib/completions/zsh/oadm +++ b/contrib/completions/zsh/oadm @@ -2635,6 +2635,7 @@ _oadm_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") diff --git a/contrib/completions/zsh/oc b/contrib/completions/zsh/oc index 5fe41012aacf..7de1608f7a96 100644 --- a/contrib/completions/zsh/oc +++ b/contrib/completions/zsh/oc @@ -1787,6 +1787,7 @@ _oc_get() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -1870,6 +1871,7 @@ _oc_get() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -2136,6 +2138,7 @@ _oc_edit() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -2219,6 +2222,7 @@ _oc_edit() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -2824,6 +2828,7 @@ _oc_label() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -2907,6 +2912,7 @@ _oc_label() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -3232,6 +3238,7 @@ _oc_delete() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -3315,6 +3322,7 @@ _oc_delete() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -6727,6 +6735,7 @@ _oc_adm_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -9599,6 +9608,7 @@ _oc_patch() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -9682,6 +9692,7 @@ _oc_patch() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") diff --git a/contrib/completions/zsh/openshift b/contrib/completions/zsh/openshift index 664fa9e3088e..7b73a1410073 100644 --- a/contrib/completions/zsh/openshift +++ b/contrib/completions/zsh/openshift @@ -3283,6 +3283,7 @@ _openshift_admin_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -6127,6 +6128,7 @@ _openshift_cli_get() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -6210,6 +6212,7 @@ _openshift_cli_get() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -6478,6 +6481,7 @@ _openshift_cli_edit() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -6561,6 +6565,7 @@ _openshift_cli_edit() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -7174,6 +7179,7 @@ _openshift_cli_label() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -7257,6 +7263,7 @@ _openshift_cli_label() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -7585,6 +7592,7 @@ _openshift_cli_delete() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -7668,6 +7676,7 @@ _openshift_cli_delete() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -11145,6 +11154,7 @@ _openshift_cli_adm_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -14072,6 +14082,7 @@ _openshift_cli_patch() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -14155,6 +14166,7 @@ _openshift_cli_patch() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -16064,6 +16076,7 @@ _openshift_kube_get() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -16147,6 +16160,7 @@ _openshift_kube_get() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -17658,6 +17672,7 @@ _openshift_kube_patch() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -17741,6 +17756,7 @@ _openshift_kube_patch() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -17907,6 +17923,7 @@ _openshift_kube_delete() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -17990,6 +18007,7 @@ _openshift_kube_delete() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -18152,6 +18170,7 @@ _openshift_kube_edit() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -18235,6 +18254,7 @@ _openshift_kube_edit() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -20394,6 +20414,7 @@ _openshift_kube_label() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") @@ -20477,6 +20498,7 @@ _openshift_kube_label() noun_aliases+=("securitycontextconstraintses") noun_aliases+=("serviceaccounts") noun_aliases+=("services") + noun_aliases+=("storageclasses") noun_aliases+=("svc") noun_aliases+=("templates") noun_aliases+=("thirdpartyresourcedatas") @@ -20786,6 +20808,7 @@ _openshift_kube_taint() must_have_one_noun+=("securitycontextconstraints") must_have_one_noun+=("service") must_have_one_noun+=("serviceaccount") + must_have_one_noun+=("storageclass") must_have_one_noun+=("template") must_have_one_noun+=("thirdpartyresource") must_have_one_noun+=("thirdpartyresourcedata") diff --git a/pkg/cmd/server/bootstrappolicy/infra_sa_policy.go b/pkg/cmd/server/bootstrappolicy/infra_sa_policy.go index 4bbc60d3e68f..f9144220519a 100644 --- a/pkg/cmd/server/bootstrappolicy/infra_sa_policy.go +++ b/pkg/cmd/server/bootstrappolicy/infra_sa_policy.go @@ -506,6 +506,12 @@ func init() { Verbs: sets.NewString("create", "update", "patch"), Resources: sets.NewString("events"), }, + // PersistentVolumeController.syncClaim() -> provisionClaim() + { + APIGroups: []string{extensions.GroupName}, + Verbs: sets.NewString("list", "watch"), + Resources: sets.NewString("storageclasses"), + }, }, }, ) diff --git a/pkg/cmd/server/kubernetes/master.go b/pkg/cmd/server/kubernetes/master.go index b0f8c819a65a..a96165c232c5 100644 --- a/pkg/cmd/server/kubernetes/master.go +++ b/pkg/cmd/server/kubernetes/master.go @@ -154,20 +154,15 @@ func (c *MasterConfig) RunNamespaceController(kubeClient internalclientset.Inter func (c *MasterConfig) RunPersistentVolumeController(client *client.Client, namespace, recyclerImageName, recyclerServiceAccountName string) { s := c.ControllerManager - provisioner, err := kctrlmgr.NewVolumeProvisioner(c.CloudProvider, s.VolumeConfiguration) - if err != nil { - glog.Fatal("A Provisioner could not be created, but one was expected. Provisioning will not work. This functionality is considered an early Alpha version.") - } - volumeController := persistentvolumecontroller.NewPersistentVolumeController( clientadapter.FromUnversionedClient(client), s.PVClaimBinderSyncPeriod.Duration, - provisioner, - probeRecyclableVolumePlugins(s.VolumeConfiguration, namespace, recyclerImageName, recyclerServiceAccountName), + probeControllerVolumePlugins(s.VolumeConfiguration, namespace, recyclerImageName, recyclerServiceAccountName), c.CloudProvider, s.ClusterName, - nil, nil, nil, + nil, nil, nil, nil, s.VolumeConfiguration.EnableDynamicProvisioning, + "", ) volumeController.Run() @@ -187,8 +182,8 @@ func (c *MasterConfig) RunPersistentVolumeController(client *client.Client, name } } -// probeRecyclableVolumePlugins collects all persistent volume plugins into an easy to use list. -func probeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration, namespace, recyclerImageName, recyclerServiceAccountName string) []volume.VolumePlugin { +// probeControllerVolumePlugins collects all persistent volume plugins into an easy to use list. +func probeControllerVolumePlugins(config componentconfig.VolumeConfiguration, namespace, recyclerImageName, recyclerServiceAccountName string) []volume.VolumePlugin { uid := int64(0) defaultScrubPod := volume.NewPersistentVolumeRecyclerPodTemplate() defaultScrubPod.Namespace = namespace @@ -214,6 +209,7 @@ func probeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration, na RecyclerMinimumTimeout: int(config.PersistentVolumeRecyclerConfiguration.MinimumTimeoutHostPath), RecyclerTimeoutIncrement: int(config.PersistentVolumeRecyclerConfiguration.IncrementTimeoutHostPath), RecyclerPodTemplate: defaultScrubPod, + ProvisioningEnabled: config.EnableHostPathProvisioning, } if err := kctrlmgr.AttemptToLoadRecycler(config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathHostPath, &hostPathConfig); err != nil { glog.Fatalf("Could not create hostpath recycler pod from file %s: %+v", config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathHostPath, err) diff --git a/test/testdata/bootstrappolicy/bootstrap_cluster_roles.yaml b/test/testdata/bootstrappolicy/bootstrap_cluster_roles.yaml index 3d6b61a220a7..1bd04377979b 100644 --- a/test/testdata/bootstrappolicy/bootstrap_cluster_roles.yaml +++ b/test/testdata/bootstrappolicy/bootstrap_cluster_roles.yaml @@ -2660,6 +2660,14 @@ items: - create - patch - update + - apiGroups: + - extensions + attributeRestrictions: null + resources: + - storageclasses + verbs: + - list + - watch - apiVersion: v1 kind: ClusterRole metadata: diff --git a/vendor/k8s.io/kubernetes/api/swagger-spec/extensions_v1beta1.json b/vendor/k8s.io/kubernetes/api/swagger-spec/extensions_v1beta1.json index f18cd1f3a064..4f47ec63744c 100644 --- a/vendor/k8s.io/kubernetes/api/swagger-spec/extensions_v1beta1.json +++ b/vendor/k8s.io/kubernetes/api/swagger-spec/extensions_v1beta1.json @@ -6476,6 +6476,561 @@ } ] }, + { + "path": "/apis/extensions/v1beta1/storageclasses", + "description": "API at /apis/extensions/v1beta1", + "operations": [ + { + "type": "v1beta1.StorageClassList", + "method": "GET", + "summary": "list or watch objects of kind StorageClass", + "nickname": "listStorageClass", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.StorageClassList" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.StorageClass", + "method": "POST", + "summary": "create a StorageClass", + "nickname": "createStorageClass", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1beta1.StorageClass", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.StorageClass" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "unversioned.Status", + "method": "DELETE", + "summary": "delete collection of StorageClass", + "nickname": "deletecollectionStorageClass", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "unversioned.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/extensions/v1beta1/watch/storageclasses", + "description": "API at /apis/extensions/v1beta1", + "operations": [ + { + "type": "*versioned.Event", + "method": "GET", + "summary": "watch individual changes to a list of StorageClass", + "nickname": "watchStorageClassList", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "*versioned.Event" + } + ], + "produces": [ + "application/json", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/extensions/v1beta1/storageclasses/{name}", + "description": "API at /apis/extensions/v1beta1", + "operations": [ + { + "type": "v1beta1.StorageClass", + "method": "GET", + "summary": "read the specified StorageClass", + "nickname": "readStorageClass", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "export", + "description": "Should this value be exported. Export strips fields that a user can not specify.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "exact", + "description": "Should the export be exact. Exact export maintains cluster-specific fields like 'Namespace'", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StorageClass", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.StorageClass" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.StorageClass", + "method": "PUT", + "summary": "replace the specified StorageClass", + "nickname": "replaceStorageClass", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1beta1.StorageClass", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StorageClass", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.StorageClass" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta1.StorageClass", + "method": "PATCH", + "summary": "partially update the specified StorageClass", + "nickname": "patchStorageClass", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "unversioned.Patch", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StorageClass", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta1.StorageClass" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "unversioned.Status", + "method": "DELETE", + "summary": "delete a StorageClass", + "nickname": "deleteStorageClass", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "v1.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StorageClass", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "unversioned.Status" + } + ], + "produces": [ + "application/json", + "application/yaml", + "application/vnd.kubernetes.protobuf" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/apis/extensions/v1beta1/watch/storageclasses/{name}", + "description": "API at /apis/extensions/v1beta1", + "operations": [ + { + "type": "*versioned.Event", + "method": "GET", + "summary": "watch changes to an object of kind StorageClass", + "nickname": "watchStorageClass", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "pretty", + "description": "If 'true', then the output is pretty printed.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history.", + "required": false, + "allowMultiple": false + }, + { + "type": "integer", + "paramType": "query", + "name": "timeoutSeconds", + "description": "Timeout for the list/watch call.", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the StorageClass", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "*versioned.Event" + } + ], + "produces": [ + "application/json", + "application/json;stream=watch", + "application/vnd.kubernetes.protobuf", + "application/vnd.kubernetes.protobuf;stream=watch" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/apis/extensions/v1beta1/thirdpartyresources", "description": "API at /apis/extensions/v1beta1", @@ -9588,6 +10143,63 @@ } } }, + "v1beta1.StorageClassList": { + "id": "v1beta1.StorageClassList", + "description": "StorageClassList is a collection of storage classes.", + "required": [ + "items" + ], + "properties": { + "kind": { + "type": "string", + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources" + }, + "metadata": { + "$ref": "unversioned.ListMeta", + "description": "Standard list metadata More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1beta1.StorageClass" + }, + "description": "Items is the list of StorageClasses" + } + } + }, + "v1beta1.StorageClass": { + "id": "v1beta1.StorageClass", + "description": "StorageClass describes the parameters for a class of storage for which PersistentVolumes can be dynamically provisioned.\n\nStorageClasses are non-namespaced; the name of the storage class according to etcd is in ObjectMeta.Name.", + "required": [ + "provisioner" + ], + "properties": { + "kind": { + "type": "string", + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds" + }, + "apiVersion": { + "type": "string", + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources" + }, + "metadata": { + "$ref": "v1.ObjectMeta", + "description": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata" + }, + "provisioner": { + "type": "string", + "description": "Provisioner indicates the type of the provisioner." + }, + "parameters": { + "type": "object", + "description": "Parameters holds the parameters for the provisioner that should create volumes of this storage class." + } + } + }, "v1beta1.ThirdPartyResourceList": { "id": "v1beta1.ThirdPartyResourceList", "description": "ThirdPartyResourceList is a list of ThirdPartyResources.", diff --git a/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/controllermanager.go b/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/controllermanager.go index 9947e453437b..b9dff24297cd 100644 --- a/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/controllermanager.go +++ b/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/controllermanager.go @@ -386,20 +386,17 @@ func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig } } - provisioner, err := NewVolumeProvisioner(cloud, s.VolumeConfiguration) - if err != nil { - glog.Fatal("A Provisioner could not be created, but one was expected. Provisioning will not work. This functionality is considered an early Alpha version.") - } - volumeController := persistentvolumecontroller.NewPersistentVolumeController( clientset.NewForConfigOrDie(restclient.AddUserAgent(kubeconfig, "persistent-volume-binder")), s.PVClaimBinderSyncPeriod.Duration, - provisioner, - ProbeRecyclableVolumePlugins(s.VolumeConfiguration), + ProbeControllerVolumePlugins(cloud, s.VolumeConfiguration), cloud, s.ClusterName, - nil, nil, nil, + // volumeSource, claimSource, classSource, eventRecorder + nil, nil, nil, nil, s.VolumeConfiguration.EnableDynamicProvisioning, + // deault storageClass + "", ) volumeController.Run() time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) diff --git a/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/plugins.go b/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/plugins.go index 6b11dd382265..d55b819a3f61 100644 --- a/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/plugins.go +++ b/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/plugins.go @@ -62,8 +62,9 @@ func ProbeAttachableVolumePlugins(config componentconfig.VolumeConfiguration) [] return allPlugins } -// ProbeRecyclableVolumePlugins collects all persistent volume plugins into an easy to use list. -func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) []volume.VolumePlugin { +// ProbeControllerVolumePlugins collects all persistent volume plugins into an easy to use list. +// Only provisioner/recycler/deleter volume plugins should be returned. +func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componentconfig.VolumeConfiguration) []volume.VolumePlugin { allPlugins := []volume.VolumePlugin{} // The list of plugins to probe is decided by this binary, not @@ -79,6 +80,7 @@ func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) [] RecyclerMinimumTimeout: int(config.PersistentVolumeRecyclerConfiguration.MinimumTimeoutHostPath), RecyclerTimeoutIncrement: int(config.PersistentVolumeRecyclerConfiguration.IncrementTimeoutHostPath), RecyclerPodTemplate: volume.NewPersistentVolumeRecyclerPodTemplate(), + ProvisioningEnabled: config.EnableHostPathProvisioning, } if err := AttemptToLoadRecycler(config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathHostPath, &hostPathConfig); err != nil { glog.Fatalf("Could not create hostpath recycler pod from file %s: %+v", config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathHostPath, err) @@ -95,32 +97,18 @@ func ProbeRecyclableVolumePlugins(config componentconfig.VolumeConfiguration) [] } allPlugins = append(allPlugins, nfs.ProbeVolumePlugins(nfsConfig)...) - allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...) - allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...) - allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...) - allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...) - - return allPlugins -} - -// NewVolumeProvisioner returns a volume provisioner to use when running in a cloud or development environment. -// The beta implementation of provisioning allows 1 implied provisioner per cloud, until we allow configuration of many. -// We explicitly map clouds to volume plugins here which allows us to configure many later without backwards compatibility issues. -// Not all cloudproviders have provisioning capability, which is the reason for the bool in the return to tell the caller to expect one or not. -func NewVolumeProvisioner(cloud cloudprovider.Interface, config componentconfig.VolumeConfiguration) (volume.ProvisionableVolumePlugin, error) { switch { - case cloud == nil && config.EnableHostPathProvisioning: - return getProvisionablePluginFromVolumePlugins(host_path.ProbeVolumePlugins(volume.VolumeConfig{})) case cloud != nil && aws.ProviderName == cloud.ProviderName(): - return getProvisionablePluginFromVolumePlugins(aws_ebs.ProbeVolumePlugins()) + allPlugins = append(allPlugins, aws_ebs.ProbeVolumePlugins()...) case cloud != nil && gce.ProviderName == cloud.ProviderName(): - return getProvisionablePluginFromVolumePlugins(gce_pd.ProbeVolumePlugins()) + allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...) case cloud != nil && openstack.ProviderName == cloud.ProviderName(): - return getProvisionablePluginFromVolumePlugins(cinder.ProbeVolumePlugins()) + allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...) case cloud != nil && vsphere.ProviderName == cloud.ProviderName(): - return getProvisionablePluginFromVolumePlugins(vsphere_volume.ProbeVolumePlugins()) + allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...) } - return nil, nil + + return allPlugins } func getProvisionablePluginFromVolumePlugins(plugins []volume.VolumePlugin) (volume.ProvisionableVolumePlugin, error) { diff --git a/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/README.md b/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/README.md index e7796576967e..8e293488db71 100644 --- a/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/README.md +++ b/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/README.md @@ -14,27 +14,49 @@ scripts that launch kube-controller-manager. ### Admin Configuration -No configuration is required by the admin! 3 cloud providers will be provided in the alpha version -of this feature: EBS, GCE, and Cinder. - -When Kubernetes is running in one of those clouds, there will be an implied provisioner. -There is no provisioner when running outside of any of those 3 cloud providers. +The admin must define `StorageClass` objects that describe named "classes" of storage offered in a cluster. Different classes might map to arbitrary levels or policies determined by the admin. When configuring a `StorageClass` object for persistent volume provisioning, the admin will need to describe the type of provisioner to use and the parameters that will be used by the provisioner when it provisions a `PersistentVolume` belonging to the class. + +The name of a StorageClass object is significant, and is how users can request a particular class, by specifying the name in their `PersistentVolumeClaim`. The `provisioner` field must be specified as it determines what volume plugin is used for provisioning PVs. 2 cloud providers will be provided in the beta version of this feature: EBS and GCE. The `parameters` field contains the parameters that describe volumes belonging to the storage class. Different parameters may be accepted depending on the `provisioner`. For example, the value `io1`, for the parameter `type`, and the parameter `iopsPerGB` are specific to EBS . When a parameter is omitted, some default is used. + +#### AWS + +```yaml +kind: StorageClass +apiVersion: extensions/v1beta1 +metadata: + name: slow +provisioner: kubernetes.io/aws-ebs +parameters: + type: io1 + zone: us-east-1d + iopsPerGB: "10" +``` -A fourth provisioner is included for testing and development only. It creates HostPath volumes, -which will never work outside of a single node cluster. It is not supported in any way except for -local for testing and development. +* `type`: `io1`, `gp2`, `sc1`, `st1`. See AWS docs for details. Default: `gp2`. +* `zone`: AWS zone +* `iopsPerGB`: only for `io1` volumes. I/O operations per second per GiB. AWS volume plugin multiplies this with size of requested volume to compute IOPS of the volume and caps it at 20 000 IOPS (maximum supported by AWS, see AWS docs). + +#### GCE + +```yaml +kind: StorageClass +apiVersion: extensions/v1beta1 +metadata: + name: slow +provisioner: kubernetes.io/gce-pd +parameters: + type: pd-standard + zone: us-central1-a +``` +* `type`: `pd-standard` or `pd-ssd`. Default: `pd-ssd` +* `zone`: GCE zone ### User provisioning requests Users request dynamically provisioned storage by including a storage class in their `PersistentVolumeClaim`. -The annotation `volume.alpha.kubernetes.io/storage-class` is used to access this experimental feature. -In the future, admins will be able to define many storage classes. -The storage class may remain in an annotation or become a field on the claim itself. - -> The value of the storage-class annotation does not matter in the alpha version of this feature. There is -a single implied provisioner per cloud (which creates 1 kind of volume in the provider). The full version of the feature -will require that this value matches what is configured by the administrator. +The annotation `volume.beta.kubernetes.io/storage-class` is used to access this experimental feature. It is required that this value matches the name of a `StorageClass` configured by the administrator. +In the future, the storage class may remain in an annotation or become a field on the claim itself. ``` { @@ -43,7 +65,7 @@ will require that this value matches what is configured by the administrator. "metadata": { "name": "claim1", "annotations": { - "volume.alpha.kubernetes.io/storage-class": "foo" + "volume.beta.kubernetes.io/storage-class": "slow" } }, "spec": { @@ -61,26 +83,28 @@ will require that this value matches what is configured by the administrator. ### Sample output -This example uses HostPath but any provisioner would follow the same flow. +This example uses gce but any provisioner would follow the same flow. -First we note there are no Persistent Volumes in the cluster. After creating a claim, we see a new PV is created +First we note there are no Persistent Volumes in the cluster. After creating a storage class and a claim including that storage class, we see a new PV is created and automatically bound to the claim requesting storage. ``` $ kubectl get pv +$ kubectl create -f examples/experimental/persistent-volume-provisioning/gce-pd.yaml +storageclass "slow" created + $ kubectl create -f examples/experimental/persistent-volume-provisioning/claim1.json -I1012 13:07:57.666759 22875 decoder.go:141] decoding stream as JSON persistentvolumeclaim "claim1" created $ kubectl get pv -NAME LABELS CAPACITY ACCESSMODES STATUS CLAIM REASON AGE -pv-hostpath-r6z5o createdby=hostpath-dynamic-provisioner 3Gi RWO Bound default/claim1 2s +NAME CAPACITY ACCESSMODES STATUS CLAIM REASON AGE +pvc-bb6d2f0c-534c-11e6-9348-42010af00002 3Gi RWO Bound default/claim1 4s $ kubectl get pvc -NAME LABELS STATUS VOLUME CAPACITY ACCESSMODES AGE -claim1 Bound pv-hostpath-r6z5o 3Gi RWO 7s +NAME LABELS STATUS VOLUME CAPACITY ACCESSMODES AGE +claim1 Bound pvc-bb6d2f0c-534c-11e6-9348-42010af00002 3Gi RWO 7s # delete the claim to release the volume $ kubectl delete pvc claim1 diff --git a/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/aws-ebs.yaml b/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/aws-ebs.yaml new file mode 100644 index 000000000000..ee5b1e93a85b --- /dev/null +++ b/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/aws-ebs.yaml @@ -0,0 +1,9 @@ +kind: StorageClass +apiVersion: extensions/v1beta1 +metadata: + name: slow +provisioner: kubernetes.io/aws-ebs +parameters: + type: io1 + zone: us-east-1d + iopsPerGB: "10" \ No newline at end of file diff --git a/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/claim1.json b/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/claim1.json index 48f28b3ca876..6dc3a0d41b14 100644 --- a/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/claim1.json +++ b/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/claim1.json @@ -4,7 +4,7 @@ "metadata": { "name": "claim1", "annotations": { - "volume.alpha.kubernetes.io/storage-class": "foo" + "volume.beta.kubernetes.io/storage-class": "slow" } }, "spec": { diff --git a/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/claim2.json b/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/claim2.json deleted file mode 100644 index 8ffd9c8e8f56..000000000000 --- a/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/claim2.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "kind": "PersistentVolumeClaim", - "apiVersion": "v1", - "metadata": { - "name": "claim2", - "annotations": { - "volume.alpha.kubernetes.io/storage-class": "bar" - } - }, - "spec": { - "accessModes": [ - "ReadWriteOnce" - ], - "resources": { - "requests": { - "storage": "3Gi" - } - } - } -} diff --git a/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/gce-pd.yaml b/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/gce-pd.yaml new file mode 100644 index 000000000000..3afb7d352f95 --- /dev/null +++ b/vendor/k8s.io/kubernetes/examples/experimental/persistent-volume-provisioning/gce-pd.yaml @@ -0,0 +1,8 @@ +kind: StorageClass +apiVersion: extensions/v1beta1 +metadata: + name: slow +provisioner: kubernetes.io/gce-pd +parameters: + type: pd-standard + zone: us-central1-a \ No newline at end of file diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/deep_copy_generated.go index 118d478c28b0..c3c1ad888560 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/deep_copy_generated.go @@ -79,6 +79,8 @@ func init() { DeepCopy_extensions_Scale, DeepCopy_extensions_ScaleSpec, DeepCopy_extensions_ScaleStatus, + DeepCopy_extensions_StorageClass, + DeepCopy_extensions_StorageClassList, DeepCopy_extensions_SupplementalGroupsStrategyOptions, DeepCopy_extensions_ThirdPartyResource, DeepCopy_extensions_ThirdPartyResourceData, @@ -860,6 +862,47 @@ func DeepCopy_extensions_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conver return nil } +func DeepCopy_extensions_StorageClass(in StorageClass, out *StorageClass, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + out.Provisioner = in.Provisioner + if in.Parameters != nil { + in, out := in.Parameters, &out.Parameters + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val + } + } else { + out.Parameters = nil + } + return nil +} + +func DeepCopy_extensions_StorageClassList(in StorageClassList, out *StorageClassList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]StorageClass, len(in)) + for i := range in { + if err := DeepCopy_extensions_StorageClass(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + func DeepCopy_extensions_SupplementalGroupsStrategyOptions(in SupplementalGroupsStrategyOptions, out *SupplementalGroupsStrategyOptions, c *conversion.Cloner) error { out.Rule = in.Rule if in.Ranges != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/install/install.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/install/install.go index d88104eac238..7144673e07f0 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/install/install.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/install/install.go @@ -94,6 +94,7 @@ func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper rootScoped := sets.NewString( "PodSecurityPolicy", "ThirdPartyResource", + "StorageClass", ) ignoredKinds := sets.NewString() diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/register.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/register.go index 1c5f6ba105ab..9d28e5389155 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/register.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/register.go @@ -75,5 +75,7 @@ func addKnownTypes(scheme *runtime.Scheme) { &PodSecurityPolicyList{}, &NetworkPolicy{}, &NetworkPolicyList{}, + &StorageClass{}, + &StorageClassList{}, ) } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.go index 9db03ab7ce8c..e1b792acdff2 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.go @@ -896,3 +896,41 @@ type NetworkPolicyList struct { Items []NetworkPolicy `json:"items"` } + +// +genclient=true +// +nonNamespaced=true + +// StorageClass describes a named "class" of storage offered in a cluster. +// Different classes might map to quality-of-service levels, or to backup policies, +// or to arbitrary policies determined by the cluster administrators. Kubernetes +// itself is unopinionated about what classes represent. This concept is sometimes +// called "profiles" in other storage systems. +// The name of a StorageClass object is significant, and is how users can request a particular class. +type StorageClass struct { + unversioned.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata,omitempty"` + + // provisioner is the driver expected to handle this StorageClass. + // This is an optionally-prefixed name, like a label key. + // For example: "kubernetes.io/gce-pd" or "kubernetes.io/aws-ebs". + // This value may not be empty. + Provisioner string `json:"provisioner"` + + // parameters holds parameters for the provisioner. + // These values are opaque to the system and are passed directly + // to the provisioner. The only validation done on keys is that they are + // not empty. The maximum number of parameters is + // 512, with a cumulative max size of 256K + Parameters map[string]string `json:"parameters,omitempty"` +} + +// StorageClassList is a collection of storage classes. +type StorageClassList struct { + unversioned.TypeMeta `json:",inline"` + // Standard list metadata + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + unversioned.ListMeta `json:"metadata,omitempty"` + + // Items is the list of StorageClasses + Items []StorageClass `json:"items"` +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_generated.go index 445394f16a59..8fa3189c7bc6 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_generated.go @@ -152,6 +152,10 @@ func init() { Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec, Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus, Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus, + Convert_v1beta1_StorageClass_To_extensions_StorageClass, + Convert_extensions_StorageClass_To_v1beta1_StorageClass, + Convert_v1beta1_StorageClassList_To_extensions_StorageClassList, + Convert_extensions_StorageClassList_To_v1beta1_StorageClassList, Convert_v1beta1_SupplementalGroupsStrategyOptions_To_extensions_SupplementalGroupsStrategyOptions, Convert_extensions_SupplementalGroupsStrategyOptions_To_v1beta1_SupplementalGroupsStrategyOptions, Convert_v1beta1_ThirdPartyResource_To_extensions_ThirdPartyResource, @@ -2238,6 +2242,90 @@ func Convert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in *extensions.ScaleSpec, return autoConvert_extensions_ScaleSpec_To_v1beta1_ScaleSpec(in, out, s) } +func autoConvert_v1beta1_StorageClass_To_extensions_StorageClass(in *StorageClass, out *extensions.StorageClass, s conversion.Scope) error { + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil { + return err + } + out.Provisioner = in.Provisioner + out.Parameters = in.Parameters + return nil +} + +func Convert_v1beta1_StorageClass_To_extensions_StorageClass(in *StorageClass, out *extensions.StorageClass, s conversion.Scope) error { + return autoConvert_v1beta1_StorageClass_To_extensions_StorageClass(in, out, s) +} + +func autoConvert_extensions_StorageClass_To_v1beta1_StorageClass(in *extensions.StorageClass, out *StorageClass, s conversion.Scope) error { + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil { + return err + } + out.Provisioner = in.Provisioner + out.Parameters = in.Parameters + return nil +} + +func Convert_extensions_StorageClass_To_v1beta1_StorageClass(in *extensions.StorageClass, out *StorageClass, s conversion.Scope) error { + return autoConvert_extensions_StorageClass_To_v1beta1_StorageClass(in, out, s) +} + +func autoConvert_v1beta1_StorageClassList_To_extensions_StorageClassList(in *StorageClassList, out *extensions.StorageClassList, s conversion.Scope) error { + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]extensions.StorageClass, len(*in)) + for i := range *in { + if err := Convert_v1beta1_StorageClass_To_extensions_StorageClass(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_v1beta1_StorageClassList_To_extensions_StorageClassList(in *StorageClassList, out *extensions.StorageClassList, s conversion.Scope) error { + return autoConvert_v1beta1_StorageClassList_To_extensions_StorageClassList(in, out, s) +} + +func autoConvert_extensions_StorageClassList_To_v1beta1_StorageClassList(in *extensions.StorageClassList, out *StorageClassList, s conversion.Scope) error { + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]StorageClass, len(*in)) + for i := range *in { + if err := Convert_extensions_StorageClass_To_v1beta1_StorageClass(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_extensions_StorageClassList_To_v1beta1_StorageClassList(in *extensions.StorageClassList, out *StorageClassList, s conversion.Scope) error { + return autoConvert_extensions_StorageClassList_To_v1beta1_StorageClassList(in, out, s) +} + func autoConvert_v1beta1_SupplementalGroupsStrategyOptions_To_extensions_SupplementalGroupsStrategyOptions(in *SupplementalGroupsStrategyOptions, out *extensions.SupplementalGroupsStrategyOptions, s conversion.Scope) error { out.Rule = extensions.SupplementalGroupsStrategyType(in.Rule) if in.Ranges != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/deep_copy_generated.go index 32debd197f0a..66003a2fe018 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/deep_copy_generated.go @@ -94,6 +94,8 @@ func init() { DeepCopy_v1beta1_Scale, DeepCopy_v1beta1_ScaleSpec, DeepCopy_v1beta1_ScaleStatus, + DeepCopy_v1beta1_StorageClass, + DeepCopy_v1beta1_StorageClassList, DeepCopy_v1beta1_SubresourceReference, DeepCopy_v1beta1_SupplementalGroupsStrategyOptions, DeepCopy_v1beta1_ThirdPartyResource, @@ -1191,6 +1193,47 @@ func DeepCopy_v1beta1_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conversio return nil } +func DeepCopy_v1beta1_StorageClass(in StorageClass, out *StorageClass, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + out.Provisioner = in.Provisioner + if in.Parameters != nil { + in, out := in.Parameters, &out.Parameters + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val + } + } else { + out.Parameters = nil + } + return nil +} + +func DeepCopy_v1beta1_StorageClassList(in StorageClassList, out *StorageClassList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]StorageClass, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_StorageClass(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + func DeepCopy_v1beta1_SubresourceReference(in SubresourceReference, out *SubresourceReference, c *conversion.Cloner) error { out.Kind = in.Kind out.Name = in.Name diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.pb.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.pb.go index 3120ce17ff31..7f57bc038291 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.pb.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.pb.go @@ -88,6 +88,8 @@ limitations under the License. Scale ScaleSpec ScaleStatus + StorageClass + StorageClassList SubresourceReference SupplementalGroupsStrategyOptions ThirdPartyResource @@ -365,6 +367,14 @@ func (m *ScaleStatus) Reset() { *m = ScaleStatus{} } func (m *ScaleStatus) String() string { return proto.CompactTextString(m) } func (*ScaleStatus) ProtoMessage() {} +func (m *StorageClass) Reset() { *m = StorageClass{} } +func (m *StorageClass) String() string { return proto.CompactTextString(m) } +func (*StorageClass) ProtoMessage() {} + +func (m *StorageClassList) Reset() { *m = StorageClassList{} } +func (m *StorageClassList) String() string { return proto.CompactTextString(m) } +func (*StorageClassList) ProtoMessage() {} + func (m *SubresourceReference) Reset() { *m = SubresourceReference{} } func (m *SubresourceReference) String() string { return proto.CompactTextString(m) } func (*SubresourceReference) ProtoMessage() {} @@ -453,6 +463,8 @@ func init() { proto.RegisterType((*Scale)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.Scale") proto.RegisterType((*ScaleSpec)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.ScaleSpec") proto.RegisterType((*ScaleStatus)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.ScaleStatus") + proto.RegisterType((*StorageClass)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.StorageClass") + proto.RegisterType((*StorageClassList)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.StorageClassList") proto.RegisterType((*SubresourceReference)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.SubresourceReference") proto.RegisterType((*SupplementalGroupsStrategyOptions)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.SupplementalGroupsStrategyOptions") proto.RegisterType((*ThirdPartyResource)(nil), "k8s.io.kubernetes.pkg.apis.extensions.v1beta1.ThirdPartyResource") @@ -2895,6 +2907,91 @@ func (m *ScaleStatus) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *StorageClass) 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 *StorageClass) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + data[i] = 0xa + i++ + i = encodeVarintGenerated(data, i, uint64(m.ObjectMeta.Size())) + n72, err := m.ObjectMeta.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n72 + data[i] = 0x12 + i++ + i = encodeVarintGenerated(data, i, uint64(len(m.Provisioner))) + i += copy(data[i:], m.Provisioner) + if len(m.Parameters) > 0 { + for k := range m.Parameters { + data[i] = 0x1a + i++ + v := m.Parameters[k] + mapSize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + i = encodeVarintGenerated(data, i, uint64(mapSize)) + data[i] = 0xa + i++ + i = encodeVarintGenerated(data, i, uint64(len(k))) + i += copy(data[i:], k) + data[i] = 0x12 + i++ + i = encodeVarintGenerated(data, i, uint64(len(v))) + i += copy(data[i:], v) + } + } + return i, nil +} + +func (m *StorageClassList) 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 *StorageClassList) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + data[i] = 0xa + i++ + i = encodeVarintGenerated(data, i, uint64(m.ListMeta.Size())) + n73, err := m.ListMeta.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n73 + if len(m.Items) > 0 { + for _, msg := range m.Items { + data[i] = 0x12 + i++ + i = encodeVarintGenerated(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *SubresourceReference) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -2981,11 +3078,11 @@ func (m *ThirdPartyResource) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ObjectMeta.Size())) - n72, err := m.ObjectMeta.MarshalTo(data[i:]) + n74, err := m.ObjectMeta.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n72 + i += n74 data[i] = 0x12 i++ i = encodeVarintGenerated(data, i, uint64(len(m.Description))) @@ -3023,11 +3120,11 @@ func (m *ThirdPartyResourceData) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ObjectMeta.Size())) - n73, err := m.ObjectMeta.MarshalTo(data[i:]) + n75, err := m.ObjectMeta.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n73 + i += n75 if m.Data != nil { data[i] = 0x12 i++ @@ -3055,11 +3152,11 @@ func (m *ThirdPartyResourceDataList) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ListMeta.Size())) - n74, err := m.ListMeta.MarshalTo(data[i:]) + n76, err := m.ListMeta.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n74 + i += n76 if len(m.Items) > 0 { for _, msg := range m.Items { data[i] = 0x12 @@ -3093,11 +3190,11 @@ func (m *ThirdPartyResourceList) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintGenerated(data, i, uint64(m.ListMeta.Size())) - n75, err := m.ListMeta.MarshalTo(data[i:]) + n77, err := m.ListMeta.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n75 + i += n77 if len(m.Items) > 0 { for _, msg := range m.Items { data[i] = 0x12 @@ -3998,6 +4095,38 @@ func (m *ScaleStatus) Size() (n int) { return n } +func (m *StorageClass) Size() (n int) { + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Provisioner) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Parameters) > 0 { + for k, v := range m.Parameters { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + return n +} + +func (m *StorageClassList) Size() (n int) { + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + func (m *SubresourceReference) Size() (n int) { var l int _ = l @@ -12150,6 +12279,337 @@ func (m *ScaleStatus) Unmarshal(data []byte) error { } return nil } +func (m *StorageClass) 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: StorageClass: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StorageClass: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", 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 err := m.ObjectMeta.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Provisioner", 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.Provisioner = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Parameters", 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 + } + var keykey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + keykey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLenmapkey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey := string(data[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + var valuekey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + valuekey |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLenmapvalue |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue := string(data[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + if m.Parameters == nil { + m.Parameters = make(map[string]string) + } + m.Parameters[mapkey] = mapvalue + 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 *StorageClassList) 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: StorageClassList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StorageClassList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", 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 err := m.ListMeta.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", 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 + } + m.Items = append(m.Items, StorageClass{}) + if err := m.Items[len(m.Items)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + 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 *SubresourceReference) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.proto b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.proto index bd4da6974f24..29f87f449e23 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.proto +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.proto @@ -942,6 +942,34 @@ message ScaleStatus { optional string targetSelector = 3; } +// StorageClass describes the parameters for a class of storage for +// which PersistentVolumes can be dynamically provisioned. +// +// StorageClasses are non-namespaced; the name of the storage class +// according to etcd is in ObjectMeta.Name. +message StorageClass { + // Standard object's metadata. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + optional k8s.io.kubernetes.pkg.api.v1.ObjectMeta metadata = 1; + + // Provisioner indicates the type of the provisioner. + optional string provisioner = 2; + + // Parameters holds the parameters for the provisioner that should + // create volumes of this storage class. + map parameters = 3; +} + +// StorageClassList is a collection of storage classes. +message StorageClassList { + // Standard list metadata + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + optional k8s.io.kubernetes.pkg.api.unversioned.ListMeta metadata = 1; + + // Items is the list of StorageClasses + repeated StorageClass items = 2; +} + // SubresourceReference contains enough information to let you inspect or modify the referred subresource. message SubresourceReference { // Kind of the referent; More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#types-kinds diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/register.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/register.go index e8bbf28b100f..96e04a9ed392 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/register.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/register.go @@ -63,6 +63,8 @@ func addKnownTypes(scheme *runtime.Scheme) { &PodSecurityPolicyList{}, &NetworkPolicy{}, &NetworkPolicyList{}, + &StorageClass{}, + &StorageClassList{}, ) // Add the watch version that applies versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.go index 7fead65b9512..fd45479ffe53 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.go @@ -1193,3 +1193,36 @@ type NetworkPolicyList struct { // Items is a list of schema objects. Items []NetworkPolicy `json:"items" protobuf:"bytes,2,rep,name=items"` } + +// +genclient=true +// +nonNamespaced=true + +// StorageClass describes the parameters for a class of storage for +// which PersistentVolumes can be dynamically provisioned. +// +// StorageClasses are non-namespaced; the name of the storage class +// according to etcd is in ObjectMeta.Name. +type StorageClass struct { + unversioned.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Provisioner indicates the type of the provisioner. + Provisioner string `json:"provisioner" protobuf:"bytes,2,opt,name=provisioner"` + + // Parameters holds the parameters for the provisioner that should + // create volumes of this storage class. + Parameters map[string]string `json:"parameters,omitempty" protobuf:"bytes,3,rep,name=parameters"` +} + +// StorageClassList is a collection of storage classes. +type StorageClassList struct { + unversioned.TypeMeta `json:",inline"` + // Standard list metadata + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + unversioned.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Items is the list of StorageClasses + Items []StorageClass `json:"items" protobuf:"bytes,2,rep,name=items"` +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go index 1864a965828d..87aa68872ba7 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go @@ -674,6 +674,27 @@ func (ScaleStatus) SwaggerDoc() map[string]string { return map_ScaleStatus } +var map_StorageClass = map[string]string{ + "": "StorageClass describes the parameters for a class of storage for which PersistentVolumes can be dynamically provisioned.\n\nStorageClasses are non-namespaced; the name of the storage class according to etcd is in ObjectMeta.Name.", + "metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", + "provisioner": "Provisioner indicates the type of the provisioner.", + "parameters": "Parameters holds the parameters for the provisioner that should create volumes of this storage class.", +} + +func (StorageClass) SwaggerDoc() map[string]string { + return map_StorageClass +} + +var map_StorageClassList = map[string]string{ + "": "StorageClassList is a collection of storage classes.", + "metadata": "Standard list metadata More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", + "items": "Items is the list of StorageClasses", +} + +func (StorageClassList) SwaggerDoc() map[string]string { + return map_StorageClassList +} + var map_SubresourceReference = map[string]string{ "": "SubresourceReference contains enough information to let you inspect or modify the referred subresource.", "kind": "Kind of the referent; More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#types-kinds", diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation.go index d0405c3770f5..629f0b7590a6 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation.go @@ -744,3 +744,65 @@ func ValidateNetworkPolicyUpdate(update, old *extensions.NetworkPolicy) field.Er } return allErrs } + +// ValidateStorageClass validates a StorageClass. +func ValidateStorageClass(storageClass *extensions.StorageClass) field.ErrorList { + allErrs := apivalidation.ValidateObjectMeta(&storageClass.ObjectMeta, false, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata")) + allErrs = append(allErrs, validateProvisioner(storageClass.Provisioner, field.NewPath("provisioner"))...) + allErrs = append(allErrs, validateParameters(storageClass.Parameters, field.NewPath("parameters"))...) + + return allErrs +} + +// ValidateStorageClassUpdate tests if an update to StorageClass is valid. +func ValidateStorageClassUpdate(storageClass, oldStorageClass *extensions.StorageClass) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&storageClass.ObjectMeta, &oldStorageClass.ObjectMeta, field.NewPath("metadata")) + if !reflect.DeepEqual(oldStorageClass.Parameters, storageClass.Parameters) { + allErrs = append(allErrs, field.Forbidden(field.NewPath("parameters"), "updates to parameters are forbidden.")) + } + + if strings.Compare(storageClass.Provisioner, oldStorageClass.Provisioner) != 0 { + allErrs = append(allErrs, field.Forbidden(field.NewPath("provisioner"), "updates to provisioner are forbidden.")) + } + return allErrs +} + +// validateProvisioner tests if provisioner is a valid qualified name. +func validateProvisioner(provisioner string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if len(provisioner) == 0 { + allErrs = append(allErrs, field.Required(fldPath, provisioner)) + } + if len(provisioner) > 0 { + for _, msg := range validation.IsQualifiedName(strings.ToLower(provisioner)) { + allErrs = append(allErrs, field.Invalid(fldPath, provisioner, msg)) + } + } + return allErrs +} + +const maxProvisionerParameterSize = 256 * (1 << 10) // 256 kB +const maxProvisionerParameterLen = 512 + +// validateParameters tests that keys are qualified names and that provisionerParameter are < 256kB. +func validateParameters(params map[string]string, fldPath *field.Path) field.ErrorList { + var totalSize int64 + allErrs := field.ErrorList{} + + if len(params) > maxProvisionerParameterLen { + allErrs = append(allErrs, field.TooLong(fldPath, "Provisioner Parameters exceeded max allowed", maxProvisionerParameterLen)) + return allErrs + } + + for k, v := range params { + if len(k) < 1 { + allErrs = append(allErrs, field.Invalid(fldPath, k, "field can not be empty.")) + } + totalSize += (int64)(len(k)) + (int64)(len(v)) + } + + if totalSize > maxProvisionerParameterSize { + allErrs = append(allErrs, field.TooLong(fldPath, "", maxProvisionerParameterSize)) + } + return allErrs +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation_test.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation_test.go index 7c7fa3c31fd6..87c50e641043 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation_test.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation_test.go @@ -1929,3 +1929,80 @@ func newBool(val bool) *bool { *p = val return p } + +func TestValidateStorageClass(t *testing.T) { + successCases := []extensions.StorageClass{ + { + // empty parameters + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/foo-provisioner", + Parameters: map[string]string{}, + }, + { + // nil parameters + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/foo-provisioner", + }, + { + // some parameters + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/foo-provisioner", + Parameters: map[string]string{ + "kubernetes.io/foo-parameter": "free/form/string", + "foo-parameter": "free-form-string", + "foo-parameter2": "{\"embedded\": \"json\", \"with\": {\"structures\":\"inside\"}}", + }, + }, + } + + // Success cases are expected to pass validation. + for k, v := range successCases { + if errs := ValidateStorageClass(&v); len(errs) != 0 { + t.Errorf("Expected success for %d, got %v", k, errs) + } + } + + // generate a map longer than maxProvisionerParameterSize + longParameters := make(map[string]string) + totalSize := 0 + for totalSize < maxProvisionerParameterSize { + k := fmt.Sprintf("param/%d", totalSize) + v := fmt.Sprintf("value-%d", totalSize) + longParameters[k] = v + totalSize = totalSize + len(k) + len(v) + } + + errorCases := map[string]extensions.StorageClass{ + "namespace is present": { + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Provisioner: "kubernetes.io/foo-provisioner", + }, + "invalid provisioner": { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/invalid/provisioner", + }, + "invalid empty parameter name": { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/foo", + Parameters: map[string]string{ + "": "value", + }, + }, + "provisioner: Required value": { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Provisioner: "", + }, + "too long parameters": { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Provisioner: "kubernetes.io/foo", + Parameters: longParameters, + }, + } + + // Error cases are not expected to pass validation. + for testName, storageClass := range errorCases { + if errs := ValidateStorageClass(&storageClass); len(errs) == 0 { + t.Errorf("Expected failure for test: %s", testName) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go index 9b9f4749a64d..5d86dd8bc553 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go @@ -30,6 +30,7 @@ type ExtensionsInterface interface { PodSecurityPoliciesGetter ReplicaSetsGetter ScalesGetter + StorageClassesGetter ThirdPartyResourcesGetter } @@ -62,6 +63,10 @@ func (c *ExtensionsClient) Scales(namespace string) ScaleInterface { return newScales(c, namespace) } +func (c *ExtensionsClient) StorageClasses() StorageClassInterface { + return newStorageClasses(c) +} + func (c *ExtensionsClient) ThirdPartyResources() ThirdPartyResourceInterface { return newThirdPartyResources(c) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go index 7c2fa08518a2..1ba1fd127ea0 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go @@ -50,6 +50,10 @@ func (c *FakeExtensions) Scales(namespace string) unversioned.ScaleInterface { return &FakeScales{c, namespace} } +func (c *FakeExtensions) StorageClasses() unversioned.StorageClassInterface { + return &FakeStorageClasses{c} +} + func (c *FakeExtensions) ThirdPartyResources() unversioned.ThirdPartyResourceInterface { return &FakeThirdPartyResources{c} } diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_storageclass.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_storageclass.go new file mode 100644 index 000000000000..80f6de6ab90c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_storageclass.go @@ -0,0 +1,99 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeStorageClasses implements StorageClassInterface +type FakeStorageClasses struct { + Fake *FakeExtensions +} + +var storageclassesResource = unversioned.GroupVersionResource{Group: "extensions", Version: "", Resource: "storageclasses"} + +func (c *FakeStorageClasses) Create(storageClass *extensions.StorageClass) (result *extensions.StorageClass, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction(storageclassesResource, storageClass), &extensions.StorageClass{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.StorageClass), err +} + +func (c *FakeStorageClasses) Update(storageClass *extensions.StorageClass) (result *extensions.StorageClass, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction(storageclassesResource, storageClass), &extensions.StorageClass{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.StorageClass), err +} + +func (c *FakeStorageClasses) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction(storageclassesResource, name), &extensions.StorageClass{}) + return err +} + +func (c *FakeStorageClasses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction(storageclassesResource, listOptions) + + _, err := c.Fake.Invokes(action, &extensions.StorageClassList{}) + return err +} + +func (c *FakeStorageClasses) Get(name string) (result *extensions.StorageClass, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction(storageclassesResource, name), &extensions.StorageClass{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.StorageClass), err +} + +func (c *FakeStorageClasses) List(opts api.ListOptions) (result *extensions.StorageClassList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction(storageclassesResource, opts), &extensions.StorageClassList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.StorageClassList{} + for _, item := range obj.(*extensions.StorageClassList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested storageClasses. +func (c *FakeStorageClasses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction(storageclassesResource, opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go index 7a1999454e6e..15b48ec7de00 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go @@ -29,3 +29,5 @@ type PodSecurityPolicyExpansion interface{} type ThirdPartyResourceExpansion interface{} type ReplicaSetExpansion interface{} + +type StorageClassExpansion interface{} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/storageclass.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/storageclass.go new file mode 100644 index 000000000000..d918c8a41687 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/storageclass.go @@ -0,0 +1,127 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + watch "k8s.io/kubernetes/pkg/watch" +) + +// StorageClassesGetter has a method to return a StorageClassInterface. +// A group's client should implement this interface. +type StorageClassesGetter interface { + StorageClasses() StorageClassInterface +} + +// StorageClassInterface has methods to work with StorageClass resources. +type StorageClassInterface interface { + Create(*extensions.StorageClass) (*extensions.StorageClass, error) + Update(*extensions.StorageClass) (*extensions.StorageClass, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*extensions.StorageClass, error) + List(opts api.ListOptions) (*extensions.StorageClassList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + StorageClassExpansion +} + +// storageClasses implements StorageClassInterface +type storageClasses struct { + client *ExtensionsClient +} + +// newStorageClasses returns a StorageClasses +func newStorageClasses(c *ExtensionsClient) *storageClasses { + return &storageClasses{ + client: c, + } +} + +// Create takes the representation of a storageClass and creates it. Returns the server's representation of the storageClass, and an error, if there is any. +func (c *storageClasses) Create(storageClass *extensions.StorageClass) (result *extensions.StorageClass, err error) { + result = &extensions.StorageClass{} + err = c.client.Post(). + Resource("storageclasses"). + Body(storageClass). + Do(). + Into(result) + return +} + +// Update takes the representation of a storageClass and updates it. Returns the server's representation of the storageClass, and an error, if there is any. +func (c *storageClasses) Update(storageClass *extensions.StorageClass) (result *extensions.StorageClass, err error) { + result = &extensions.StorageClass{} + err = c.client.Put(). + Resource("storageclasses"). + Name(storageClass.Name). + Body(storageClass). + Do(). + Into(result) + return +} + +// Delete takes name of the storageClass and deletes it. Returns an error if one occurs. +func (c *storageClasses) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("storageclasses"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *storageClasses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("storageclasses"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the storageClass, and returns the corresponding storageClass object, and an error if there is any. +func (c *storageClasses) Get(name string) (result *extensions.StorageClass, err error) { + result = &extensions.StorageClass{} + err = c.client.Get(). + Resource("storageclasses"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of StorageClasses that match those selectors. +func (c *storageClasses) List(opts api.ListOptions) (result *extensions.StorageClassList, err error) { + result = &extensions.StorageClassList{} + err = c.client.Get(). + Resource("storageclasses"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested storageClasses. +func (c *storageClasses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("storageclasses"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/extensions_client.go index 23aa5b219ed8..65a3c5900ea1 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/extensions_client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/extensions_client.go @@ -33,6 +33,7 @@ type ExtensionsInterface interface { PodSecurityPoliciesGetter ReplicaSetsGetter ScalesGetter + StorageClassesGetter ThirdPartyResourcesGetter } @@ -73,6 +74,10 @@ func (c *ExtensionsClient) Scales(namespace string) ScaleInterface { return newScales(c, namespace) } +func (c *ExtensionsClient) StorageClasses() StorageClassInterface { + return newStorageClasses(c) +} + func (c *ExtensionsClient) ThirdPartyResources() ThirdPartyResourceInterface { return newThirdPartyResources(c) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/fake/fake_extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/fake/fake_extensions_client.go index ac0f33da68aa..a1ee4fdfa8d6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/fake/fake_extensions_client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/fake/fake_extensions_client.go @@ -58,6 +58,10 @@ func (c *FakeExtensions) Scales(namespace string) v1beta1.ScaleInterface { return &FakeScales{c, namespace} } +func (c *FakeExtensions) StorageClasses() v1beta1.StorageClassInterface { + return &FakeStorageClasses{c} +} + func (c *FakeExtensions) ThirdPartyResources() v1beta1.ThirdPartyResourceInterface { return &FakeThirdPartyResources{c} } diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/fake/fake_storageclass.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/fake/fake_storageclass.go new file mode 100644 index 000000000000..1b94ecf39dbc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/fake/fake_storageclass.go @@ -0,0 +1,99 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeStorageClasses implements StorageClassInterface +type FakeStorageClasses struct { + Fake *FakeExtensions +} + +var storageclassesResource = unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "storageclasses"} + +func (c *FakeStorageClasses) Create(storageClass *v1beta1.StorageClass) (result *v1beta1.StorageClass, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction(storageclassesResource, storageClass), &v1beta1.StorageClass{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.StorageClass), err +} + +func (c *FakeStorageClasses) Update(storageClass *v1beta1.StorageClass) (result *v1beta1.StorageClass, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction(storageclassesResource, storageClass), &v1beta1.StorageClass{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.StorageClass), err +} + +func (c *FakeStorageClasses) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction(storageclassesResource, name), &v1beta1.StorageClass{}) + return err +} + +func (c *FakeStorageClasses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction(storageclassesResource, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.StorageClassList{}) + return err +} + +func (c *FakeStorageClasses) Get(name string) (result *v1beta1.StorageClass, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction(storageclassesResource, name), &v1beta1.StorageClass{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.StorageClass), err +} + +func (c *FakeStorageClasses) List(opts api.ListOptions) (result *v1beta1.StorageClassList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction(storageclassesResource, opts), &v1beta1.StorageClassList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.StorageClassList{} + for _, item := range obj.(*v1beta1.StorageClassList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested storageClasses. +func (c *FakeStorageClasses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction(storageclassesResource, opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/generated_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/generated_expansion.go index 7477a5711336..7d6b898083da 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/generated_expansion.go +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/generated_expansion.go @@ -29,3 +29,5 @@ type ThirdPartyResourceExpansion interface{} type ReplicaSetExpansion interface{} type PodSecurityPolicyExpansion interface{} + +type StorageClassExpansion interface{} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/storageclass.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/storageclass.go new file mode 100644 index 000000000000..12843b6f1dac --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_3/typed/extensions/v1beta1/storageclass.go @@ -0,0 +1,127 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// StorageClassesGetter has a method to return a StorageClassInterface. +// A group's client should implement this interface. +type StorageClassesGetter interface { + StorageClasses() StorageClassInterface +} + +// StorageClassInterface has methods to work with StorageClass resources. +type StorageClassInterface interface { + Create(*v1beta1.StorageClass) (*v1beta1.StorageClass, error) + Update(*v1beta1.StorageClass) (*v1beta1.StorageClass, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.StorageClass, error) + List(opts api.ListOptions) (*v1beta1.StorageClassList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + StorageClassExpansion +} + +// storageClasses implements StorageClassInterface +type storageClasses struct { + client *ExtensionsClient +} + +// newStorageClasses returns a StorageClasses +func newStorageClasses(c *ExtensionsClient) *storageClasses { + return &storageClasses{ + client: c, + } +} + +// Create takes the representation of a storageClass and creates it. Returns the server's representation of the storageClass, and an error, if there is any. +func (c *storageClasses) Create(storageClass *v1beta1.StorageClass) (result *v1beta1.StorageClass, err error) { + result = &v1beta1.StorageClass{} + err = c.client.Post(). + Resource("storageclasses"). + Body(storageClass). + Do(). + Into(result) + return +} + +// Update takes the representation of a storageClass and updates it. Returns the server's representation of the storageClass, and an error, if there is any. +func (c *storageClasses) Update(storageClass *v1beta1.StorageClass) (result *v1beta1.StorageClass, err error) { + result = &v1beta1.StorageClass{} + err = c.client.Put(). + Resource("storageclasses"). + Name(storageClass.Name). + Body(storageClass). + Do(). + Into(result) + return +} + +// Delete takes name of the storageClass and deletes it. Returns an error if one occurs. +func (c *storageClasses) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("storageclasses"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *storageClasses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("storageclasses"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the storageClass, and returns the corresponding storageClass object, and an error if there is any. +func (c *storageClasses) Get(name string) (result *v1beta1.StorageClass, err error) { + result = &v1beta1.StorageClass{} + err = c.client.Get(). + Resource("storageclasses"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of StorageClasses that match those selectors. +func (c *storageClasses) List(opts api.ListOptions) (result *v1beta1.StorageClassList, err error) { + result = &v1beta1.StorageClassList{} + err = c.client.Get(). + Resource("storageclasses"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested storageClasses. +func (c *storageClasses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("storageclasses"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/extensions.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/extensions.go index 3c9114d9a88c..5545bf58991f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/extensions.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/extensions.go @@ -37,6 +37,7 @@ type ExtensionsInterface interface { ThirdPartyResourceNamespacer ReplicaSetsNamespacer PodSecurityPoliciesInterface + StorageClassesInterface } // ExtensionsClient is used to interact with experimental Kubernetes features. @@ -82,6 +83,10 @@ func (c *ExtensionsClient) ReplicaSets(namespace string) ReplicaSetInterface { return newReplicaSets(c, namespace) } +func (c *ExtensionsClient) StorageClasses() StorageClassInterface { + return newStorageClasses(c) +} + // NewExtensions creates a new ExtensionsClient for the given config. This client // provides access to experimental Kubernetes features. // Features of Extensions group are not supported and may be changed or removed in diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/storageclasses.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/storageclasses.go new file mode 100644 index 000000000000..7c5c0b4e72dd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/storageclasses.go @@ -0,0 +1,87 @@ +/* +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 unversioned + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/watch" +) + +type StorageClassesInterface interface { + StorageClasses() StorageClassInterface +} + +// StorageClassInterface has methods to work with StorageClass resources. +type StorageClassInterface interface { + List(opts api.ListOptions) (*extensions.StorageClassList, error) + Get(name string) (*extensions.StorageClass, error) + Create(storageClass *extensions.StorageClass) (*extensions.StorageClass, error) + Update(storageClass *extensions.StorageClass) (*extensions.StorageClass, error) + Delete(name string) error + Watch(opts api.ListOptions) (watch.Interface, error) +} + +// storageClasses implements StorageClassInterface +type storageClasses struct { + client *ExtensionsClient +} + +func newStorageClasses(c *ExtensionsClient) *storageClasses { + return &storageClasses{c} +} + +func (c *storageClasses) List(opts api.ListOptions) (result *extensions.StorageClassList, err error) { + result = &extensions.StorageClassList{} + err = c.client.Get(). + Resource("storageclasses"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + + return result, err +} + +func (c *storageClasses) Get(name string) (result *extensions.StorageClass, err error) { + result = &extensions.StorageClass{} + err = c.client.Get().Resource("storageClasses").Name(name).Do().Into(result) + return +} + +func (c *storageClasses) Create(storageClass *extensions.StorageClass) (result *extensions.StorageClass, err error) { + result = &extensions.StorageClass{} + err = c.client.Post().Resource("storageClasses").Body(storageClass).Do().Into(result) + return +} + +func (c *storageClasses) Update(storageClass *extensions.StorageClass) (result *extensions.StorageClass, err error) { + result = &extensions.StorageClass{} + err = c.client.Put().Resource("storageClasses").Name(storageClass.Name).Body(storageClass).Do().Into(result) + return +} + +func (c *storageClasses) Delete(name string) error { + return c.client.Delete().Resource("storageClasses").Name(name).Do().Error() +} + +func (c *storageClasses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("storageClasses"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/storageclasses_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/storageclasses_test.go new file mode 100644 index 000000000000..955de0999082 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/storageclasses_test.go @@ -0,0 +1,147 @@ +/* +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 unversioned_test + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" +) + +func getStorageClassResourceName() string { + return "storageclasses" +} + +func TestListStorageClasses(t *testing.T) { + c := &simple.Client{ + Request: simple.Request{ + Method: "GET", + Path: testapi.Extensions.ResourcePath(getStorageClassResourceName(), "", ""), + }, + Response: simple.Response{StatusCode: 200, + Body: &extensions.StorageClassList{ + Items: []extensions.StorageClass{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + "name": "baz", + }, + }, + Provisioner: "aaa", + }, + }, + }, + }, + } + receivedSCList, err := c.Setup(t).Extensions().StorageClasses().List(api.ListOptions{}) + c.Validate(t, receivedSCList, err) +} + +func TestGetStorageClass(t *testing.T) { + c := &simple.Client{ + Request: simple.Request{Method: "GET", Path: testapi.Extensions.ResourcePath(getStorageClassResourceName(), "", "foo"), Query: simple.BuildQueryValues(nil)}, + Response: simple.Response{ + StatusCode: 200, + Body: &extensions.StorageClass{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + "name": "baz", + }, + }, + Provisioner: "aaa", + }, + }, + } + receivedSC, err := c.Setup(t).Extensions().StorageClasses().Get("foo") + c.Validate(t, receivedSC, err) +} + +func TestGetStorageClassWithNoName(t *testing.T) { + c := &simple.Client{Error: true} + receivedSC, err := c.Setup(t).Extensions().StorageClasses().Get("") + if (err != nil) && (err.Error() != simple.NameRequiredError) { + t.Errorf("Expected error: %v, but got %v", simple.NameRequiredError, err) + } + + c.Validate(t, receivedSC, err) +} + +func TestUpdateStorageClass(t *testing.T) { + requestSC := &extensions.StorageClass{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + Provisioner: "aaa", + } + c := &simple.Client{ + Request: simple.Request{Method: "PUT", Path: testapi.Extensions.ResourcePath(getStorageClassResourceName(), "", "foo"), Query: simple.BuildQueryValues(nil)}, + Response: simple.Response{ + StatusCode: 200, + Body: &extensions.StorageClass{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + "name": "baz", + }, + }, + Provisioner: "aaa", + }, + }, + } + receivedSC, err := c.Setup(t).Extensions().StorageClasses().Update(requestSC) + c.Validate(t, receivedSC, err) +} + +func TestDeleteStorageClass(t *testing.T) { + c := &simple.Client{ + Request: simple.Request{Method: "DELETE", Path: testapi.Extensions.ResourcePath(getStorageClassResourceName(), "", "foo"), Query: simple.BuildQueryValues(nil)}, + Response: simple.Response{StatusCode: 200}, + } + err := c.Setup(t).Extensions().StorageClasses().Delete("foo") + c.Validate(t, nil, err) +} + +func TestCreateStorageClass(t *testing.T) { + requestSC := &extensions.StorageClass{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Provisioner: "aaa", + } + c := &simple.Client{ + Request: simple.Request{Method: "POST", Path: testapi.Extensions.ResourcePath(getStorageClassResourceName(), "", ""), Body: requestSC, Query: simple.BuildQueryValues(nil)}, + Response: simple.Response{ + StatusCode: 200, + Body: &extensions.StorageClass{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + "name": "baz", + }, + }, + Provisioner: "aaa", + }, + }, + } + receivedSC, err := c.Setup(t).Extensions().StorageClasses().Create(requestSC) + c.Validate(t, receivedSC, err) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_storage_classes.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_storage_classes.go new file mode 100644 index 000000000000..0a6bb65f4b43 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_storage_classes.go @@ -0,0 +1,74 @@ +/* +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 testclient + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + kclientlib "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/watch" +) + +// FakeStorageClasses implements StorageClassInterface. Meant to be embedded into a struct to get a default +// implementation. This makes faking out just the method you want to test easier. +type FakeStorageClasses struct { + Fake *FakeExperimental +} + +// Ensure statically that FakeStorageClasses implements StorageClassInterface. +var _ kclientlib.StorageClassInterface = &FakeStorageClasses{} + +func (c *FakeStorageClasses) Get(name string) (*extensions.StorageClass, error) { + obj, err := c.Fake.Invokes(NewGetAction("storageclasses", "", name), &extensions.StorageClass{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.StorageClass), err +} + +func (c *FakeStorageClasses) List(opts api.ListOptions) (*extensions.StorageClassList, error) { + obj, err := c.Fake.Invokes(NewListAction("storageclasses", "", opts), &extensions.StorageClassList{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.StorageClassList), err +} + +func (c *FakeStorageClasses) Create(np *extensions.StorageClass) (*extensions.StorageClass, error) { + obj, err := c.Fake.Invokes(NewCreateAction("storageclasses", "", np), &extensions.StorageClass{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.StorageClass), err +} + +func (c *FakeStorageClasses) Update(np *extensions.StorageClass) (*extensions.StorageClass, error) { + obj, err := c.Fake.Invokes(NewUpdateAction("storageclasses", "", np), &extensions.StorageClass{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.StorageClass), err +} + +func (c *FakeStorageClasses) Delete(name string) error { + _, err := c.Fake.Invokes(NewDeleteAction("storageclasses", "", name), &extensions.StorageClass{}) + return err +} + +func (c *FakeStorageClasses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake.InvokesWatch(NewWatchAction("storageclasses", "", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/testclient.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/testclient.go index 2277961c33d0..509972150e6c 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/testclient.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/testclient.go @@ -410,6 +410,10 @@ func (c *FakeExperimental) NetworkPolicies(namespace string) client.NetworkPolic return &FakeNetworkPolicies{Fake: c, Namespace: namespace} } +func (c *FakeExperimental) StorageClasses() client.StorageClassInterface { + return &FakeStorageClasses{Fake: c} +} + func NewSimpleFakeRbac(objects ...runtime.Object) *FakeRbac { return &FakeRbac{Fake: NewSimpleFake(objects...)} } diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws.go index a2f9836f302d..98a3adc3f7bc 100644 --- a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws.go +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws.go @@ -28,6 +28,8 @@ import ( "sync" "time" + "gopkg.in/gcfg.v1" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" @@ -39,7 +41,6 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elb" "github.com/golang/glog" - "gopkg.in/gcfg.v1" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/service" @@ -208,11 +209,29 @@ type EC2Metadata interface { GetMetadata(path string) (string, error) } +// AWS volume types +const ( + // Provisioned IOPS SSD + VolumeTypeIO1 = "io1" + // General Purpose SSD + VolumeTypeGP2 = "gp2" + // Cold HDD (sc1) + VolumeTypeSC1 = "sc1" + // Throughput Optimized HDD + VolumeTypeST1 = "st1" +) + // VolumeOptions specifies capacity and tags for a volume. type VolumeOptions struct { - CapacityGB int - Tags map[string]string - PVCName string + CapacityGB int + Tags map[string]string + PVCName string + VolumeType string + AvailabilityZone string + // IOPSPerGB x CapacityGB will give total IOPS of the volume to create. + // IPSPerGB must be bigger than zero and smaller or equal to 30. + // Calculated total IOPS will be capped at 20000 IOPS. + IOPSPerGB int } // Volumes is an interface for managing cloud-provisioned volumes @@ -1459,14 +1478,47 @@ func (c *Cloud) CreateDisk(volumeOptions *VolumeOptions) (string, error) { return "", fmt.Errorf("error querying for all zones: %v", err) } - createAZ := volume.ChooseZoneForVolume(allZones, volumeOptions.PVCName) + createAZ := volumeOptions.AvailabilityZone + if createAZ == "" { + createAZ = volume.ChooseZoneForVolume(allZones, volumeOptions.PVCName) + } + + var createType string + var iops int64 + switch volumeOptions.VolumeType { + case VolumeTypeGP2, VolumeTypeSC1, VolumeTypeST1: + createType = volumeOptions.VolumeType + + case VolumeTypeIO1: + // See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateVolume.html for IOPS constraints + if volumeOptions.IOPSPerGB <= 0 || volumeOptions.IOPSPerGB > 30 { + return "", fmt.Errorf("invalid iopsPerGB value %d, must be 0 < IOPSPerGB <= 30", volumeOptions.IOPSPerGB) + } + createType = volumeOptions.VolumeType + iops = int64(volumeOptions.CapacityGB * volumeOptions.IOPSPerGB) + if iops < 100 { + iops = 100 + } + if iops > 20000 { + iops = 20000 + } + + case "": + createType = DefaultVolumeType + + default: + return "", fmt.Errorf("invalid AWS VolumeType %q", volumeOptions.VolumeType) + } // TODO: Should we tag this with the cluster id (so it gets deleted when the cluster does?) request := &ec2.CreateVolumeInput{} request.AvailabilityZone = &createAZ volSize := int64(volumeOptions.CapacityGB) request.Size = &volSize - request.VolumeType = aws.String(DefaultVolumeType) + request.VolumeType = &createType + if iops > 0 { + request.Iops = &iops + } response, err := c.ec2.CreateVolume(request) if err != nil { return "", err diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce.go index 40f247451e93..607ac78e182b 100644 --- a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce.go +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce.go @@ -101,6 +101,16 @@ type Config struct { } } +type DiskType string + +const ( + DiskTypeSSD = "pd-ssd" + DiskTypeStandard = "pd-standard" + + diskTypeDefault = DiskTypeStandard + diskTypeUriTemplate = "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/diskTypes/%s" +) + // Disks is interface for manipulation with GCE PDs. type Disks interface { // AttachDisk attaches given disk to given instance. Current instance @@ -116,7 +126,7 @@ type Disks interface { // CreateDisk creates a new PD with given properties. Tags are serialized // as JSON into Description field. - CreateDisk(name string, zone string, sizeGb int64, tags map[string]string) error + CreateDisk(name string, diskType string, zone string, sizeGb int64, tags map[string]string) error // DeleteDisk deletes PD. DeleteDisk(diskToDelete string) error @@ -2226,18 +2236,29 @@ func (gce *GCECloud) encodeDiskTags(tags map[string]string) (string, error) { } // CreateDisk creates a new Persistent Disk, with the specified name & size, in -// the specified zone. It stores specified tags endoced in JSON in Description +// the specified zone. It stores specified tags encoded in JSON in Description // field. -func (gce *GCECloud) CreateDisk(name string, zone string, sizeGb int64, tags map[string]string) error { +func (gce *GCECloud) CreateDisk(name string, diskType string, zone string, sizeGb int64, tags map[string]string) error { tagsStr, err := gce.encodeDiskTags(tags) if err != nil { return err } + switch diskType { + case DiskTypeSSD, DiskTypeStandard: + // noop + case "": + diskType = diskTypeDefault + default: + return fmt.Errorf("invalid GCE disk type %q", diskType) + } + diskTypeUri := fmt.Sprintf(diskTypeUriTemplate, gce.projectID, zone, diskType) + diskToCreate := &compute.Disk{ Name: name, SizeGb: sizeGb, Description: tagsStr, + Type: diskTypeUri, } createOp, err := gce.service.Disks.Insert(gce.projectID, zone, diskToCreate).Do() diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/binder_test.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/binder_test.go index 86ed41e3a72a..fd2e8614268e 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/binder_test.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/binder_test.go @@ -20,6 +20,7 @@ import ( "testing" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" ) // Test single call to syncClaim and syncVolume methods. @@ -422,7 +423,7 @@ func TestSync(t *testing.T) { noevents, noerrors, testSyncVolume, }, } - runSyncTests(t, tests) + runSyncTests(t, tests, []*extensions.StorageClass{}, "") } // Test multiple calls to syncClaim/syncVolume and periodic sync of all @@ -469,5 +470,5 @@ func TestMultiSync(t *testing.T) { }, } - runMultisyncTests(t, tests) + runMultisyncTests(t, tests, []*extensions.StorageClass{}, "") } diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller.go index 87dc695c275c..b4f0b52489d5 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller.go @@ -21,6 +21,7 @@ import ( "time" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/cache" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/record" @@ -72,10 +73,19 @@ const annBindCompleted = "pv.kubernetes.io/bind-completed" // pre-bound). Value of this annotation does not matter. const annBoundByController = "pv.kubernetes.io/bound-by-controller" -// annClass annotation represents a new field which instructs dynamic -// provisioning to choose a particular storage class (aka profile). -// Value of this annotation should be empty. -const annClass = "volume.alpha.kubernetes.io/storage-class" +// annClass annotation represents the storage class associated with a resource: +// - in PersistentVolumeClaim it represents required class to match. +// Only PersistentVolumes with the same class (i.e. annotation with the same +// value) can be bound to the claim. In case no such volume exists, the +// controller will provision a new one using StorageClass instance with +// the same name as the annotation value. +// - in PersistentVolume it represents storage class to which the persistent +// volume belongs. +const annClass = "volume.beta.kubernetes.io/storage-class" + +// alphaAnnClass annotation represents the previous alpha storage class +// annotation. it's no longer used and held here for posterity. +const alphaAnnClass = "volume.alpha.kubernetes.io/storage-class" // This annotation is added to a PV that has been dynamically provisioned by // Kubernetes. Its value is name of volume plugin that created the volume. @@ -112,13 +122,16 @@ type PersistentVolumeController struct { claimController *framework.Controller claimControllerStopCh chan struct{} claimSource cache.ListerWatcher + classReflector *cache.Reflector + classReflectorStopCh chan struct{} + classSource cache.ListerWatcher kubeClient clientset.Interface eventRecorder record.EventRecorder cloud cloudprovider.Interface - recyclePluginMgr vol.VolumePluginMgr - provisioner vol.ProvisionableVolumePlugin + volumePluginMgr vol.VolumePluginMgr enableDynamicProvisioning bool clusterName string + defaultStorageClass string // Cache of the last known version of volumes and claims. This cache is // thread safe as long as the volumes/claims there are not modified, they @@ -127,6 +140,7 @@ type PersistentVolumeController struct { // it saves newer version to etcd. volumes persistentVolumeOrderedIndex claims cache.Store + classes cache.Store // Map of scheduled/running operations. runningOperations goroutinemap.GoRoutineMap @@ -885,7 +899,7 @@ func (ctrl *PersistentVolumeController) recycleVolumeOperation(arg interface{}) // Find a plugin. spec := vol.NewSpecFromPersistentVolume(volume, false) - plugin, err := ctrl.recyclePluginMgr.FindRecyclablePluginBySpec(spec) + plugin, err := ctrl.volumePluginMgr.FindRecyclablePluginBySpec(spec) if err != nil { // No recycler found. Emit an event and mark the volume Failed. if _, err = ctrl.updateVolumePhaseWithEvent(volume, api.VolumeFailed, api.EventTypeWarning, "VolumeFailedRecycle", "No recycler plugin found for the volume!"); err != nil { @@ -1039,13 +1053,32 @@ func (ctrl *PersistentVolumeController) isVolumeReleased(volume *api.PersistentV // (it will be re-used in future provisioner error cases). func (ctrl *PersistentVolumeController) doDeleteVolume(volume *api.PersistentVolume) error { glog.V(4).Infof("doDeleteVolume [%s]", volume.Name) - // Find a plugin. + var err error + + // Find a plugin. Try to find the same plugin that provisioned the volume + var plugin vol.DeletableVolumePlugin + if hasAnnotation(volume.ObjectMeta, annDynamicallyProvisioned) { + provisionPluginName := volume.Annotations[annDynamicallyProvisioned] + if provisionPluginName != "" { + plugin, err = ctrl.volumePluginMgr.FindDeletablePluginByName(provisionPluginName) + if err != nil { + glog.V(3).Infof("did not find a deleter plugin %q for volume %q: %v, will try to find a generic one", + provisionPluginName, volume.Name, err) + } + } + } + spec := vol.NewSpecFromPersistentVolume(volume, false) - plugin, err := ctrl.recyclePluginMgr.FindDeletablePluginBySpec(spec) - if err != nil { - // No deleter found. Emit an event and mark the volume Failed. - return fmt.Errorf("Error getting deleter volume plugin for volume %q: %v", volume.Name, err) + if plugin == nil { + // The plugin that provisioned the volume was not found or the volume + // was not dynamically provisioned. Try a generic plugin. + plugin, err = ctrl.volumePluginMgr.FindDeletablePluginBySpec(spec) + if err != nil { + // No deleter found. Emit an event and mark the volume Failed. + return fmt.Errorf("Error getting deleter volume plugin for volume %q: %v", volume.Name, err) + } } + glog.V(5).Infof("found a deleter plugin %q for volume %q", plugin.GetPluginName(), volume.Name) // Plugin found deleter, err := plugin.NewDeleter(spec) @@ -1107,12 +1140,10 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa return } - // TODO: find provisionable plugin based on a class/profile - plugin := ctrl.provisioner - if plugin == nil { - // No provisioner found. Emit an event. - ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", "No provisioner plugin found for the claim!") - glog.V(2).Infof("no provisioner plugin found for claim %s!", claimToClaimKey(claim)) + plugin, storageClass, err := ctrl.findProvisionablePlugin(claim) + if err != nil { + ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", err.Error()) + glog.V(2).Infof("error finding provisioning plugin for claim %s: %v", claimToClaimKey(claim), err) // The controller will retry provisioning the volume in every // syncVolume() call. return @@ -1132,21 +1163,23 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa ClusterName: ctrl.clusterName, PVName: pvName, PVCName: claim.Name, + Parameters: storageClass.Parameters, + ProvisionerSelector: claim.Spec.Selector, } // Provision the volume provisioner, err := plugin.NewProvisioner(options) if err != nil { strerr := fmt.Sprintf("Failed to create provisioner: %v", err) - glog.V(2).Infof("failed to create provisioner for claim %q: %v", claimToClaimKey(claim), err) + glog.V(2).Infof("failed to create provisioner for claim %q with StorageClass %q: %v", claimToClaimKey(claim), storageClass.Name, err) ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", strerr) return } volume, err = provisioner.Provision() if err != nil { - strerr := fmt.Sprintf("Failed to provision volume: %v", err) - glog.V(2).Infof("failed to provision volume for claim %q: %v", claimToClaimKey(claim), err) + strerr := fmt.Sprintf("Failed to provision volume with StorageClass %q: %v", storageClass.Name, err) + glog.V(2).Infof("failed to provision volume for claim %q with StorageClass %q: %v", claimToClaimKey(claim), storageClass.Name, err) ctrl.eventRecorder.Event(claim, api.EventTypeWarning, "ProvisioningFailed", strerr) return } @@ -1162,6 +1195,7 @@ func (ctrl *PersistentVolumeController) provisionClaimOperation(claimObj interfa // Add annBoundByController (used in deleting the volume) setAnnotation(&volume.ObjectMeta, annBoundByController, "yes") setAnnotation(&volume.ObjectMeta, annDynamicallyProvisioned, plugin.GetPluginName()) + setAnnotation(&volume.ObjectMeta, annClass, getClaimClass(claim)) // Try to create the PV object several times for i := 0; i < ctrl.createProvisionedPVRetryCount; i++ { @@ -1240,3 +1274,40 @@ func (ctrl *PersistentVolumeController) scheduleOperation(operationName string, } } } + +func (ctrl *PersistentVolumeController) findProvisionablePlugin(claim *api.PersistentVolumeClaim) (vol.ProvisionableVolumePlugin, *extensions.StorageClass, error) { + storageClass, err := ctrl.findStorageClass(claim) + if err != nil { + return nil, nil, err + } + + // Find a plugin for the class + plugin, err := ctrl.volumePluginMgr.FindProvisionablePluginByName(string(storageClass.Provisioner)) + if err != nil { + return nil, nil, err + } + return plugin, storageClass, nil +} + +func (ctrl *PersistentVolumeController) findStorageClass(claim *api.PersistentVolumeClaim) (*extensions.StorageClass, error) { + className := getClaimClass(claim) + if className == "" { + className = ctrl.defaultStorageClass + } + if className == "" { + return nil, fmt.Errorf("No default StorageClass configured") + } + + classObj, found, err := ctrl.classes.GetByKey(className) + if err != nil { + return nil, err + } + if !found { + return nil, fmt.Errorf("StorageClass %q not found", className) + } + class, ok := classObj.(*extensions.StorageClass) + if !ok { + return nil, fmt.Errorf("Cannot convert object to StorageClass: %+v", classObj) + } + return class, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller_base.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller_base.go index 67a70b2f39e8..0f65e58acc14 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller_base.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller_base.go @@ -24,6 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/cache" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" unversioned_core "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" @@ -47,13 +48,13 @@ import ( func NewPersistentVolumeController( kubeClient clientset.Interface, syncPeriod time.Duration, - provisioner vol.ProvisionableVolumePlugin, - recyclers []vol.VolumePlugin, + volumePlugins []vol.VolumePlugin, cloud cloudprovider.Interface, clusterName string, - volumeSource, claimSource cache.ListerWatcher, + volumeSource, claimSource, classSource cache.ListerWatcher, eventRecorder record.EventRecorder, enableDynamicProvisioning bool, + defaultStorageClass string, ) *PersistentVolumeController { if eventRecorder == nil { @@ -63,25 +64,20 @@ func NewPersistentVolumeController( } controller := &PersistentVolumeController{ - volumes: newPersistentVolumeOrderedIndex(), - claims: cache.NewStore(framework.DeletionHandlingMetaNamespaceKeyFunc), - kubeClient: kubeClient, - eventRecorder: eventRecorder, - runningOperations: goroutinemap.NewGoRoutineMap(false /* exponentialBackOffOnError */), - cloud: cloud, - provisioner: provisioner, + volumes: newPersistentVolumeOrderedIndex(), + claims: cache.NewStore(framework.DeletionHandlingMetaNamespaceKeyFunc), + kubeClient: kubeClient, + eventRecorder: eventRecorder, + runningOperations: goroutinemap.NewGoRoutineMap(false /* exponentialBackOffOnError */), + cloud: cloud, enableDynamicProvisioning: enableDynamicProvisioning, clusterName: clusterName, createProvisionedPVRetryCount: createProvisionedPVRetryCount, createProvisionedPVInterval: createProvisionedPVInterval, + defaultStorageClass: defaultStorageClass, } - controller.recyclePluginMgr.InitPlugins(recyclers, controller) - if controller.provisioner != nil { - if err := controller.provisioner.Init(controller); err != nil { - glog.Errorf("PersistentVolumeController: error initializing provisioner plugin: %v", err) - } - } + controller.volumePluginMgr.InitPlugins(volumePlugins, controller) if volumeSource == nil { volumeSource = &cache.ListWatch{ @@ -107,6 +103,18 @@ func NewPersistentVolumeController( } controller.claimSource = claimSource + if classSource == nil { + classSource = &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return kubeClient.Extensions().StorageClasses().List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return kubeClient.Extensions().StorageClasses().Watch(options) + }, + } + } + controller.classSource = classSource + _, controller.volumeController = framework.NewIndexerInformer( volumeSource, &api.PersistentVolume{}, @@ -128,6 +136,16 @@ func NewPersistentVolumeController( DeleteFunc: controller.deleteClaim, }, ) + + // This is just a cache of StorageClass instances, no special actions are + // needed when a class is created/deleted/updated. + controller.classes = cache.NewStore(framework.DeletionHandlingMetaNamespaceKeyFunc) + controller.classReflector = cache.NewReflector( + classSource, + &extensions.StorageClass{}, + controller.classes, + syncPeriod, + ) return controller } @@ -433,6 +451,11 @@ func (ctrl *PersistentVolumeController) Run() { ctrl.claimControllerStopCh = make(chan struct{}) go ctrl.claimController.Run(ctrl.claimControllerStopCh) } + + if ctrl.classReflectorStopCh == nil { + ctrl.classReflectorStopCh = make(chan struct{}) + go ctrl.classReflector.RunUntil(ctrl.classReflectorStopCh) + } } // Stop gracefully shuts down this controller @@ -440,6 +463,7 @@ func (ctrl *PersistentVolumeController) Stop() { glog.V(4).Infof("stopping PersistentVolumeController") close(ctrl.volumeControllerStopCh) close(ctrl.claimControllerStopCh) + close(ctrl.classReflectorStopCh) } const ( @@ -578,3 +602,25 @@ func storeObjectUpdate(store cache.Store, obj interface{}, className string) (bo } return true, nil } + +// getVolumeClass returns value of annClass annotation or empty string in case +// the annotation does not exist. +// TODO: change to PersistentVolume.Spec.Class value when this attribute is +// introduced. +func getVolumeClass(volume *api.PersistentVolume) string { + if class, found := volume.Annotations[annClass]; found { + return class + } + return "" +} + +// getClaimClass returns value of annClass annotation or empty string in case +// the annotation does not exist. +// TODO: change to PersistentVolumeClaim.Spec.Class value when this attribute is +// introduced. +func getClaimClass(claim *api.PersistentVolumeClaim) string { + if class, found := claim.Annotations[annClass]; found { + return class + } + return "" +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller_test.go index 4233e1b6bd10..b9169127c98e 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller_test.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/controller_test.go @@ -164,7 +164,7 @@ func TestControllerSync(t *testing.T) { client := &fake.Clientset{} volumeSource := framework.NewFakePVControllerSource() claimSource := framework.NewFakePVCControllerSource() - ctrl := newTestController(client, volumeSource, claimSource, true) + ctrl := newTestController(client, volumeSource, claimSource, nil, true, "") reactor := newVolumeReactor(client, ctrl, volumeSource, claimSource, test.errors) for _, claim := range test.initialClaims { claimSource.Add(claim) diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/delete_test.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/delete_test.go index c486502b8bda..44d170038523 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/delete_test.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/delete_test.go @@ -21,6 +21,7 @@ import ( "testing" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" ) // Test single call to syncVolume, expecting recycling to happen. @@ -39,7 +40,7 @@ func TestDeleteSync(t *testing.T) { noevents, noerrors, // Inject deleter into the controller and call syncVolume. The // deleter simulates one delete() call that succeeds. - wrapTestWithControllerConfig(operationDelete, []error{nil}, testSyncVolume), + wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume), }, { // delete volume bound by user @@ -51,7 +52,7 @@ func TestDeleteSync(t *testing.T) { noevents, noerrors, // Inject deleter into the controller and call syncVolume. The // deleter simulates one delete() call that succeeds. - wrapTestWithControllerConfig(operationDelete, []error{nil}, testSyncVolume), + wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume), }, { // delete failure - plugin not found @@ -70,7 +71,7 @@ func TestDeleteSync(t *testing.T) { noclaims, noclaims, []string{"Warning VolumeFailedDelete"}, noerrors, - wrapTestWithControllerConfig(operationDelete, []error{}, testSyncVolume), + wrapTestWithReclaimCalls(operationDelete, []error{}, testSyncVolume), }, { // delete failure - delete() returns error @@ -80,7 +81,7 @@ func TestDeleteSync(t *testing.T) { noclaims, noclaims, []string{"Warning VolumeFailedDelete"}, noerrors, - wrapTestWithControllerConfig(operationDelete, []error{errors.New("Mock delete error")}, testSyncVolume), + wrapTestWithReclaimCalls(operationDelete, []error{errors.New("Mock delete error")}, testSyncVolume), }, { // delete success(?) - volume is deleted before doDelete() starts @@ -90,7 +91,7 @@ func TestDeleteSync(t *testing.T) { noclaims, noclaims, noevents, noerrors, - wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { + wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { // Delete the volume before delete operation starts reactor.lock.Lock() delete(reactor.volumes, "volume8-6") @@ -107,7 +108,7 @@ func TestDeleteSync(t *testing.T) { noclaims, newClaimArray("claim8-7", "uid8-7", "10Gi", "volume8-7", api.ClaimBound), noevents, noerrors, - wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { + wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationDelete, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { reactor.lock.Lock() defer reactor.lock.Unlock() // Bind the volume to resurrected claim (this should never @@ -130,10 +131,10 @@ func TestDeleteSync(t *testing.T) { noevents, noerrors, // Inject deleter into the controller and call syncVolume. The // deleter simulates one delete() call that succeeds. - wrapTestWithControllerConfig(operationDelete, []error{nil}, testSyncVolume), + wrapTestWithReclaimCalls(operationDelete, []error{nil}, testSyncVolume), }, } - runSyncTests(t, tests) + runSyncTests(t, tests, []*extensions.StorageClass{}, "") } // Test multiple calls to syncClaim/syncVolume and periodic sync of all @@ -161,9 +162,9 @@ func TestDeleteMultiSync(t *testing.T) { noclaims, noclaims, []string{"Warning VolumeFailedDelete"}, noerrors, - wrapTestWithControllerConfig(operationDelete, []error{errors.New("Mock delete error"), nil}, testSyncVolume), + wrapTestWithReclaimCalls(operationDelete, []error{errors.New("Mock delete error"), nil}, testSyncVolume), }, } - runMultisyncTests(t, tests) + runMultisyncTests(t, tests, []*extensions.StorageClass{}, "") } diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/framework_test.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/framework_test.go index c4b5752560cd..53ff11f6f306 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/framework_test.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/framework_test.go @@ -33,6 +33,7 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/cache" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" @@ -555,7 +556,7 @@ func newVolumeReactor(client *fake.Clientset, ctrl *PersistentVolumeController, return reactor } -func newTestController(kubeClient clientset.Interface, volumeSource, claimSource cache.ListerWatcher, enableDynamicProvisioning bool) *PersistentVolumeController { +func newTestController(kubeClient clientset.Interface, volumeSource, claimSource, classSource cache.ListerWatcher, enableDynamicProvisioning bool, defaultStorageClass string) *PersistentVolumeController { if volumeSource == nil { volumeSource = framework.NewFakePVControllerSource() } @@ -565,14 +566,15 @@ func newTestController(kubeClient clientset.Interface, volumeSource, claimSource ctrl := NewPersistentVolumeController( kubeClient, 5*time.Second, // sync period - nil, // provisioner []vol.VolumePlugin{}, // recyclers nil, // cloud "", volumeSource, claimSource, + classSource, record.NewFakeRecorder(1000), // event recorder enableDynamicProvisioning, + defaultStorageClass, ) // Speed up the test @@ -580,27 +582,6 @@ func newTestController(kubeClient clientset.Interface, volumeSource, claimSource return ctrl } -func addRecyclePlugin(ctrl *PersistentVolumeController, expectedRecycleCalls []error) { - plugin := &mockVolumePlugin{ - recycleCalls: expectedRecycleCalls, - } - ctrl.recyclePluginMgr.InitPlugins([]vol.VolumePlugin{plugin}, ctrl) -} - -func addDeletePlugin(ctrl *PersistentVolumeController, expectedDeleteCalls []error) { - plugin := &mockVolumePlugin{ - deleteCalls: expectedDeleteCalls, - } - ctrl.recyclePluginMgr.InitPlugins([]vol.VolumePlugin{plugin}, ctrl) -} - -func addProvisionPlugin(ctrl *PersistentVolumeController, expectedDeleteCalls []error) { - plugin := &mockVolumePlugin{ - provisionCalls: expectedDeleteCalls, - } - ctrl.provisioner = plugin -} - // newVolume returns a new volume with given attributes func newVolume(name, capacity, boundToClaimUID, boundToClaimName string, phase api.PersistentVolumePhase, reclaimPolicy api.PersistentVolumeReclaimPolicy, annotations ...string) *api.PersistentVolume { volume := api.PersistentVolume{ @@ -636,10 +617,13 @@ func newVolume(name, capacity, boundToClaimUID, boundToClaimName string, phase a if len(annotations) > 0 { volume.Annotations = make(map[string]string) for _, a := range annotations { - if a != annDynamicallyProvisioned { - volume.Annotations[a] = "yes" - } else { + switch a { + case annDynamicallyProvisioned: volume.Annotations[a] = mockPluginName + case annClass: + volume.Annotations[a] = "gold" + default: + volume.Annotations[a] = "yes" } } } @@ -674,6 +658,17 @@ func withMessage(message string, volumes []*api.PersistentVolume) []*api.Persist return volumes } +// volumeWithClass saves given class into annClass annotation. +// Meant to be used to compose claims specified inline in a test. +func volumeWithClass(className string, volumes []*api.PersistentVolume) []*api.PersistentVolume { + if volumes[0].Annotations == nil { + volumes[0].Annotations = map[string]string{annClass: className} + } else { + volumes[0].Annotations[annClass] = className + } + return volumes +} + // newVolumeArray returns array with a single volume that would be returned by // newVolume() with the same parameters. func newVolumeArray(name, capacity, boundToClaimUID, boundToClaimName string, phase api.PersistentVolumePhase, reclaimPolicy api.PersistentVolumeReclaimPolicy, annotations ...string) []*api.PersistentVolume { @@ -710,7 +705,12 @@ func newClaim(name, claimUID, capacity, boundToVolume string, phase api.Persiste if len(annotations) > 0 { claim.Annotations = make(map[string]string) for _, a := range annotations { - claim.Annotations[a] = "yes" + switch a { + case annClass: + claim.Annotations[a] = "gold" + default: + claim.Annotations[a] = "yes" + } } } return &claim @@ -724,6 +724,17 @@ func newClaimArray(name, claimUID, capacity, boundToVolume string, phase api.Per } } +// claimWithClass saves given class into annClass annotation. +// Meant to be used to compose claims specified inline in a test. +func claimWithClass(className string, claims []*api.PersistentVolumeClaim) []*api.PersistentVolumeClaim { + if claims[0].Annotations == nil { + claims[0].Annotations = map[string]string{annClass: className} + } else { + claims[0].Annotations[annClass] = className + } + return claims +} + func testSyncClaim(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error { return ctrl.syncClaim(test.initialClaims[0]) } @@ -745,29 +756,45 @@ type operationType string const operationDelete = "Delete" const operationRecycle = "Recycle" -const operationProvision = "Provision" -// wrapTestWithControllerConfig returns a testCall that: -// - configures controller with recycler, deleter or provisioner which will -// return provided errors when a volume is deleted, recycled or provisioned +// wrapTestWithReclaimCalls returns a testCall that: +// - configures controller with a volume plugin that implements recycler, +// deleter and provisioner. The plugin retunrs provided errors when a volume +// is deleted, recycled or provisioned. // - calls given testCall -func wrapTestWithControllerConfig(operation operationType, expectedOperationCalls []error, toWrap testCall) testCall { - expected := expectedOperationCalls - +func wrapTestWithPluginCalls(expectedRecycleCalls, expectedDeleteCalls []error, expectedProvisionCalls []provisionCall, toWrap testCall) testCall { return func(ctrl *PersistentVolumeController, reactor *volumeReactor, test controllerTest) error { - switch operation { - case operationDelete: - addDeletePlugin(ctrl, expected) - case operationRecycle: - addRecyclePlugin(ctrl, expected) - case operationProvision: - addProvisionPlugin(ctrl, expected) + plugin := &mockVolumePlugin{ + recycleCalls: expectedRecycleCalls, + deleteCalls: expectedDeleteCalls, + provisionCalls: expectedProvisionCalls, } + ctrl.volumePluginMgr.InitPlugins([]vol.VolumePlugin{plugin}, ctrl) return toWrap(ctrl, reactor, test) } } +// wrapTestWithReclaimCalls returns a testCall that: +// - configures controller with recycler or deleter which will return provided +// errors when a volume is deleted or recycled +// - calls given testCall +func wrapTestWithReclaimCalls(operation operationType, expectedOperationCalls []error, toWrap testCall) testCall { + if operation == operationDelete { + return wrapTestWithPluginCalls(nil, expectedOperationCalls, nil, toWrap) + } else { + return wrapTestWithPluginCalls(expectedOperationCalls, nil, nil, toWrap) + } +} + +// wrapTestWithProvisionCalls returns a testCall that: +// - configures controller with a provisioner which will return provided errors +// when a claim is provisioned +// - calls given testCall +func wrapTestWithProvisionCalls(expectedProvisionCalls []provisionCall, toWrap testCall) testCall { + return wrapTestWithPluginCalls(nil, nil, expectedProvisionCalls, toWrap) +} + // wrapTestWithInjectedOperation returns a testCall that: // - starts the controller and lets it run original testCall until // scheduleOperation() call. It blocks the controller there and calls the @@ -825,13 +852,13 @@ func evaluateTestResults(ctrl *PersistentVolumeController, reactor *volumeReacto // 2. Call the tested function (syncClaim/syncVolume) via // controllerTest.testCall *once*. // 3. Compare resulting volumes and claims with expected volumes and claims. -func runSyncTests(t *testing.T, tests []controllerTest) { +func runSyncTests(t *testing.T, tests []controllerTest, storageClasses []*extensions.StorageClass, defaultStorageClass string) { for _, test := range tests { glog.V(4).Infof("starting test %q", test.name) // Initialize the controller client := &fake.Clientset{} - ctrl := newTestController(client, nil, nil, true) + ctrl := newTestController(client, nil, nil, nil, true, defaultStorageClass) reactor := newVolumeReactor(client, ctrl, nil, nil, test.errors) for _, claim := range test.initialClaims { ctrl.claims.Add(claim) @@ -842,6 +869,15 @@ func runSyncTests(t *testing.T, tests []controllerTest) { reactor.volumes[volume.Name] = volume } + // Convert classes to []interface{} and forcefully inject them into + // controller. + storageClassPtrs := make([]interface{}, len(storageClasses)) + for i, s := range storageClasses { + storageClassPtrs[i] = s + } + // 1 is the resource version + ctrl.classes.Replace(storageClassPtrs, "1") + // Run the tested functions err := test.test(ctrl, reactor, test) if err != nil { @@ -869,13 +905,22 @@ func runSyncTests(t *testing.T, tests []controllerTest) { // 5. When 3. does not do any changes, finish the tests and compare final set // of volumes/claims with expected claims/volumes and report differences. // Some limit of calls in enforced to prevent endless loops. -func runMultisyncTests(t *testing.T, tests []controllerTest) { +func runMultisyncTests(t *testing.T, tests []controllerTest, storageClasses []*extensions.StorageClass, defaultStorageClass string) { for _, test := range tests { glog.V(4).Infof("starting multisync test %q", test.name) // Initialize the controller client := &fake.Clientset{} - ctrl := newTestController(client, nil, nil, true) + ctrl := newTestController(client, nil, nil, nil, true, defaultStorageClass) + + // Convert classes to []interface{} and forcefully inject them into + // controller. + storageClassPtrs := make([]interface{}, len(storageClasses)) + for i, s := range storageClasses { + storageClassPtrs[i] = s + } + ctrl.classes.Replace(storageClassPtrs, "1") + reactor := newVolumeReactor(client, ctrl, nil, nil, test.errors) for _, claim := range test.initialClaims { ctrl.claims.Add(claim) @@ -971,7 +1016,7 @@ func runMultisyncTests(t *testing.T, tests []controllerTest) { // Dummy volume plugin for provisioning, deletion and recycling. It contains // lists of expected return values to simulate errors. type mockVolumePlugin struct { - provisionCalls []error + provisionCalls []provisionCall provisionCallCounter int deleteCalls []error deleteCallCounter int @@ -980,6 +1025,11 @@ type mockVolumePlugin struct { provisionOptions vol.VolumeOptions } +type provisionCall struct { + expectedParameters map[string]string + ret error +} + var _ vol.VolumePlugin = &mockVolumePlugin{} var _ vol.RecyclableVolumePlugin = &mockVolumePlugin{} var _ vol.DeletableVolumePlugin = &mockVolumePlugin{} @@ -1032,8 +1082,12 @@ func (plugin *mockVolumePlugin) Provision() (*api.PersistentVolume, error) { } var pv *api.PersistentVolume - err := plugin.provisionCalls[plugin.provisionCallCounter] - if err == nil { + call := plugin.provisionCalls[plugin.provisionCallCounter] + if !reflect.DeepEqual(call.expectedParameters, plugin.provisionOptions.Parameters) { + glog.Errorf("invalid provisioner call, expected options: %+v, got: %+v", call.expectedParameters, plugin.provisionOptions.Parameters) + return nil, fmt.Errorf("Mock plugin error: invalid provisioner call") + } + if call.ret == nil { // Create a fake PV with known GCE volume (to match expected volume) pv = &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ @@ -1053,8 +1107,8 @@ func (plugin *mockVolumePlugin) Provision() (*api.PersistentVolume, error) { } plugin.provisionCallCounter++ - glog.V(4).Infof("mock plugin Provision call nr. %d, returning %v: %v", plugin.provisionCallCounter, pv, err) - return pv, err + glog.V(4).Infof("mock plugin Provision call nr. %d, returning %v: %v", plugin.provisionCallCounter, pv, call.ret) + return pv, call.ret } // Deleter interfaces diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index.go index 1d4df66e4f30..fe9617de6bcf 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index.go @@ -18,12 +18,11 @@ package persistentvolume import ( "fmt" - "sort" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/labels" + "sort" ) // persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes @@ -126,11 +125,16 @@ func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *api.PersistentVo // filter out: // - volumes bound to another claim // - volumes whose labels don't match the claim's selector, if specified + // - volumes in Class that is not requested if volume.Spec.ClaimRef != nil { continue } else if selector != nil && !selector.Matches(labels.Set(volume.Labels)) { continue } + claimClass := getClaimClass(claim) + if claimClass != "" && claimClass != getVolumeClass(volume) { + continue + } volumeQty := volume.Spec.Capacity[api.ResourceStorage] volumeSize := volumeQty.Value() @@ -142,17 +146,6 @@ func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *api.PersistentVo } } - // We want to provision volumes if the annotation is set even if there - // is matching PV. Therefore, do not look for available PV and let - // a new volume to be provisioned. - // - // When provisioner creates a new PV to this claim, an exact match - // pre-bound to the claim will be found by the checks above during - // subsequent claim sync. - if hasAnnotation(claim.ObjectMeta, annClass) { - return nil, nil - } - if smallestVolume != nil { // Found a matching volume return smallestVolume, nil diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index_test.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index_test.go index 4fdb9c15ae5d..acaabf314149 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index_test.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/index_test.go @@ -164,7 +164,52 @@ func TestMatchVolume(t *testing.T) { AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, Resources: api.ResourceRequirements{ Requests: api.ResourceList{ - api.ResourceName(api.ResourceStorage): resource.MustParse("10000G"), + api.ResourceName(api.ResourceStorage): resource.MustParse("20000G"), + }, + }, + }, + }, + }, + "successful-match-with-class": { + expectedMatch: "gce-pd-silver1", + claim: &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + Annotations: map[string]string{ + annClass: "silver", + }, + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{ + "should-exist": "true", + }, + }, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), + }, + }, + }, + }, + }, + "successful-match-with-class-and-labels": { + expectedMatch: "gce-pd-silver2", + claim: &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + Annotations: map[string]string{ + annClass: "silver", + }, + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), }, }, }, @@ -561,6 +606,29 @@ func createTestVolumes() []*api.PersistentVolume { "should-exist": "true", }, }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("20000G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "gce-pd-silver1", + Name: "gce0023", + Labels: map[string]string{ + "should-exist": "true", + }, + Annotations: map[string]string{ + annClass: "silver", + }, + }, Spec: api.PersistentVolumeSpec{ Capacity: api.ResourceList{ api.ResourceName(api.ResourceStorage): resource.MustParse("10000G"), @@ -573,6 +641,46 @@ func createTestVolumes() []*api.PersistentVolume { }, }, }, + { + ObjectMeta: api.ObjectMeta{ + UID: "gce-pd-silver2", + Name: "gce0024", + Annotations: map[string]string{ + annClass: "silver", + }, + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("100G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "gce-pd-gold", + Name: "gce0025", + Annotations: map[string]string{ + annClass: "gold", + }, + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("50G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + }, + }, + }, } } diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/provision_test.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/provision_test.go index dc9c59ae05c4..14a5a6d294a1 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/provision_test.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/provision_test.go @@ -21,8 +21,59 @@ import ( "testing" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" ) +var class1Parameters = map[string]string{ + "param1": "value1", +} +var class2Parameters = map[string]string{ + "param2": "value2", +} +var storageClasses = []*extensions.StorageClass{ + { + TypeMeta: unversioned.TypeMeta{ + Kind: "StorageClass", + }, + + ObjectMeta: api.ObjectMeta{ + Name: "gold", + }, + + Provisioner: mockPluginName, + Parameters: class1Parameters, + }, + { + TypeMeta: unversioned.TypeMeta{ + Kind: "StorageClass", + }, + ObjectMeta: api.ObjectMeta{ + Name: "silver", + }, + Provisioner: mockPluginName, + Parameters: class2Parameters, + }, +} + +// call to storageClass 1, returning an error +var provision1Error = provisionCall{ + ret: errors.New("Moc provisioner error"), + expectedParameters: class1Parameters, +} + +// call to storageClass 1, returning a valid PV +var provision1Success = provisionCall{ + ret: nil, + expectedParameters: class1Parameters, +} + +// call to storageClass 2, returning a valid PV +var provision2Success = provisionCall{ + ret: nil, + expectedParameters: class2Parameters, +} + // Test single call to syncVolume, expecting provisioning to happen. // 1. Fill in the controller with initial data // 2. Call the syncVolume *once*. @@ -30,14 +81,14 @@ import ( func TestProvisionSync(t *testing.T) { tests := []controllerTest{ { - // Provision a volume - "11-1 - successful provision", + // Provision a volume (with the default class) + "11-1 - successful provision with storage class 1", novolumes, - newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned), + newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass), newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass), // Binding will be completed in the next syncClaim newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass), - noevents, noerrors, wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim), + noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim), }, { // Provision failure - plugin not found @@ -57,7 +108,7 @@ func TestProvisionSync(t *testing.T) { newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass), newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass), []string{"Warning ProvisioningFailed"}, noerrors, - wrapTestWithControllerConfig(operationProvision, []error{}, testSyncClaim), + wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), }, { // Provision failure - Provision returns error @@ -67,40 +118,35 @@ func TestProvisionSync(t *testing.T) { newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass), newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass), []string{"Warning ProvisioningFailed"}, noerrors, - wrapTestWithControllerConfig(operationProvision, []error{errors.New("Moc provisioner error")}, testSyncClaim), + wrapTestWithProvisionCalls([]provisionCall{provision1Error}, testSyncClaim), }, { - // Provision success - there is already a volume available, still - // we provision a new one when requested. + // No provisioning if there is a matching volume available "11-6 - provisioning when there is a volume available", - newVolumeArray("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain), - []*api.PersistentVolume{ - newVolume("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain), - newVolume("pvc-uid11-6", "1Gi", "uid11-6", "claim11-6", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned), - }, - newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, annClass), - // Binding will be completed in the next syncClaim + newVolumeArray("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, annClass), + newVolumeArray("volume11-6", "1Gi", "uid11-6", "claim11-6", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController, annClass), newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, annClass), + newClaimArray("claim11-6", "uid11-6", "1Gi", "volume11-6", api.ClaimBound, annClass, annBoundByController, annBindCompleted), noevents, noerrors, // No provisioning plugin confingure - makes the test fail when // the controller errorneously tries to provision something - wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim), + wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim), }, { // Provision success? - claim is bound before provisioner creates // a volume. "11-7 - claim is bound before provisioning", novolumes, - newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned), + newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass), newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass), // The claim would be bound in next syncClaim newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass), noevents, noerrors, - wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationProvision, []error{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { + wrapTestWithInjectedOperation(wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { // Create a volume before provisionClaimOperation starts. // This similates a parallel controller provisioning the volume. reactor.lock.Lock() - volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned) + volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass) reactor.volumes[volume.Name] = volume reactor.lock.Unlock() }), @@ -110,7 +156,7 @@ func TestProvisionSync(t *testing.T) { // second retry succeeds "11-8 - cannot save provisioned volume", novolumes, - newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned), + newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass), newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass), // Binding will be completed in the next syncClaim newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass), @@ -121,7 +167,7 @@ func TestProvisionSync(t *testing.T) { // will succeed. {"create", "persistentvolumes", errors.New("Mock creation error")}, }, - wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim), + wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim), }, { // Provision success? - cannot save provisioned PV five times, @@ -141,8 +187,12 @@ func TestProvisionSync(t *testing.T) { {"create", "persistentvolumes", errors.New("Mock creation error4")}, {"create", "persistentvolumes", errors.New("Mock creation error5")}, }, - wrapTestWithControllerConfig(operationDelete, []error{nil}, - wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim)), + wrapTestWithPluginCalls( + nil, // recycle calls + []error{nil}, // delete calls + []provisionCall{provision1Success}, // provision calls + testSyncClaim, + ), }, { // Provision failure - cannot save provisioned PV five times, @@ -163,7 +213,7 @@ func TestProvisionSync(t *testing.T) { {"create", "persistentvolumes", errors.New("Mock creation error5")}, }, // No deleteCalls are configured, which results into no deleter plugin available for the volume - wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim), + wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim), }, { // Provision failure - cannot save provisioned PV five times, @@ -183,16 +233,17 @@ func TestProvisionSync(t *testing.T) { {"create", "persistentvolumes", errors.New("Mock creation error4")}, {"create", "persistentvolumes", errors.New("Mock creation error5")}, }, - wrapTestWithControllerConfig( - operationDelete, []error{ + wrapTestWithPluginCalls( + nil, // recycle calls + []error{ // delete calls errors.New("Mock deletion error1"), errors.New("Mock deletion error2"), errors.New("Mock deletion error3"), errors.New("Mock deletion error4"), errors.New("Mock deletion error5"), }, - wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim), - ), + []provisionCall{provision1Success}, // provision calls + testSyncClaim), }, { // Provision failure - cannot save provisioned PV five times, @@ -212,16 +263,37 @@ func TestProvisionSync(t *testing.T) { {"create", "persistentvolumes", errors.New("Mock creation error4")}, {"create", "persistentvolumes", errors.New("Mock creation error5")}, }, - wrapTestWithControllerConfig( - operationDelete, []error{ + wrapTestWithPluginCalls( + nil, // recycle calls + []error{ // delete calls errors.New("Mock deletion error1"), nil, - }, - wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim), + }, // provison calls + []provisionCall{provision1Success}, + testSyncClaim, ), }, + { + // Provision a volume (with non-default class) + "11-13 - successful provision with storage class 2", + novolumes, + volumeWithClass("silver", newVolumeArray("pvc-uid11-13", "1Gi", "uid11-13", "claim11-13", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned)), + claimWithClass("silver", newClaimArray("claim11-13", "uid11-13", "1Gi", "", api.ClaimPending)), + // Binding will be completed in the next syncClaim + claimWithClass("silver", newClaimArray("claim11-13", "uid11-13", "1Gi", "", api.ClaimPending)), + noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision2Success}, testSyncClaim), + }, + { + // Provision error - non existing class + "11-14 - fail due to non-existing class", + novolumes, + novolumes, + claimWithClass("non-existing", newClaimArray("claim11-14", "uid11-14", "1Gi", "", api.ClaimPending)), + claimWithClass("non-existing", newClaimArray("claim11-14", "uid11-14", "1Gi", "", api.ClaimPending)), + noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), + }, } - runSyncTests(t, tests) + runSyncTests(t, tests, storageClasses, storageClasses[0].Name) } // Test multiple calls to syncClaim/syncVolume and periodic sync of all @@ -244,20 +316,20 @@ func TestProvisionMultiSync(t *testing.T) { // Provision a volume with binding "12-1 - successful provision", novolumes, - newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned), + newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass), newClaimArray("claim12-1", "uid12-1", "1Gi", "", api.ClaimPending, annClass), // Binding will be completed in the next syncClaim newClaimArray("claim12-1", "uid12-1", "1Gi", "pvc-uid12-1", api.ClaimBound, annClass, annBoundByController, annBindCompleted), - noevents, noerrors, wrapTestWithControllerConfig(operationProvision, []error{nil}, testSyncClaim), + noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim), }, } - runMultisyncTests(t, tests) + runMultisyncTests(t, tests, storageClasses, storageClasses[0].Name) } // When provisioning is disabled, provisioning a claim should instantly return nil func TestDisablingDynamicProvisioner(t *testing.T) { - ctrl := newTestController(nil, nil, nil, false) + ctrl := newTestController(nil, nil, nil, nil, false, "") retVal := ctrl.provisionClaim(nil) if retVal != nil { t.Errorf("Expected nil return but got %v", retVal) diff --git a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/recycle_test.go b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/recycle_test.go index 7832bf048656..03832a941ef2 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/recycle_test.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/volume/persistentvolume/recycle_test.go @@ -21,6 +21,7 @@ import ( "testing" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" ) // Test single call to syncVolume, expecting recycling to happen. @@ -39,7 +40,7 @@ func TestRecycleSync(t *testing.T) { noevents, noerrors, // Inject recycler into the controller and call syncVolume. The // recycler simulates one recycle() call that succeeds. - wrapTestWithControllerConfig(operationRecycle, []error{nil}, testSyncVolume), + wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume), }, { // recycle volume bound by user @@ -51,7 +52,7 @@ func TestRecycleSync(t *testing.T) { noevents, noerrors, // Inject recycler into the controller and call syncVolume. The // recycler simulates one recycle() call that succeeds. - wrapTestWithControllerConfig(operationRecycle, []error{nil}, testSyncVolume), + wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume), }, { // recycle failure - plugin not found @@ -70,7 +71,7 @@ func TestRecycleSync(t *testing.T) { noclaims, noclaims, []string{"Warning VolumeFailedRecycle"}, noerrors, - wrapTestWithControllerConfig(operationRecycle, []error{}, testSyncVolume), + wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), }, { // recycle failure - recycle returns error @@ -80,7 +81,7 @@ func TestRecycleSync(t *testing.T) { noclaims, noclaims, []string{"Warning VolumeFailedRecycle"}, noerrors, - wrapTestWithControllerConfig(operationRecycle, []error{errors.New("Mock recycle error")}, testSyncVolume), + wrapTestWithReclaimCalls(operationRecycle, []error{errors.New("Mock recycle error")}, testSyncVolume), }, { // recycle success(?) - volume is deleted before doRecycle() starts @@ -90,7 +91,7 @@ func TestRecycleSync(t *testing.T) { noclaims, noclaims, noevents, noerrors, - wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { + wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { // Delete the volume before recycle operation starts reactor.lock.Lock() delete(reactor.volumes, "volume6-6") @@ -107,7 +108,7 @@ func TestRecycleSync(t *testing.T) { noclaims, noclaims, noevents, noerrors, - wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { + wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { // Mark the volume as Available before the recycler starts reactor.lock.Lock() volume := reactor.volumes["volume6-7"] @@ -128,7 +129,7 @@ func TestRecycleSync(t *testing.T) { noclaims, noclaims, noevents, noerrors, - wrapTestWithInjectedOperation(wrapTestWithControllerConfig(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { + wrapTestWithInjectedOperation(wrapTestWithReclaimCalls(operationRecycle, []error{}, testSyncVolume), func(ctrl *PersistentVolumeController, reactor *volumeReactor) { // Mark the volume as Available before the recycler starts reactor.lock.Lock() volume := reactor.volumes["volume6-8"] @@ -148,7 +149,7 @@ func TestRecycleSync(t *testing.T) { noevents, noerrors, // Inject recycler into the controller and call syncVolume. The // recycler simulates one recycle() call that succeeds. - wrapTestWithControllerConfig(operationRecycle, []error{nil}, testSyncVolume), + wrapTestWithReclaimCalls(operationRecycle, []error{nil}, testSyncVolume), }, { // volume has unknown reclaim policy - failure expected @@ -160,7 +161,7 @@ func TestRecycleSync(t *testing.T) { []string{"Warning VolumeUnknownReclaimPolicy"}, noerrors, testSyncVolume, }, } - runSyncTests(t, tests) + runSyncTests(t, tests, []*extensions.StorageClass{}, "") } // Test multiple calls to syncClaim/syncVolume and periodic sync of all @@ -188,9 +189,9 @@ func TestRecycleMultiSync(t *testing.T) { noclaims, noclaims, []string{"Warning VolumeFailedRecycle"}, noerrors, - wrapTestWithControllerConfig(operationRecycle, []error{errors.New("Mock recycle error"), nil}, testSyncVolume), + wrapTestWithReclaimCalls(operationRecycle, []error{errors.New("Mock recycle error"), nil}, testSyncVolume), }, } - runMultisyncTests(t, tests) + runMultisyncTests(t, tests, []*extensions.StorageClass{}, "") } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer.go index e2ae42d8049c..c86e64436e35 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer.go @@ -450,6 +450,7 @@ var roleColumns = []string{"NAME", "AGE"} var roleBindingColumns = []string{"NAME", "AGE"} var clusterRoleColumns = []string{"NAME", "AGE"} var clusterRoleBindingColumns = []string{"NAME", "AGE"} +var storageClassColumns = []string{"NAME", "TYPE"} // TODO: consider having 'KIND' for third party resource data var thirdPartyResourceDataColumns = []string{"NAME", "LABELS", "DATA"} @@ -530,6 +531,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() { h.Handler(clusterRoleBindingColumns, printClusterRoleBindingList) h.Handler(securityContextConstraintsColumns, printSecurityContextConstraints) h.Handler(securityContextConstraintsColumns, printSecurityContextConstraintsList) + h.Handler(storageClassColumns, printStorageClass) + h.Handler(storageClassColumns, printStorageClassList) } func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error { @@ -1884,6 +1887,32 @@ func printNetworkPolicyList(list *extensions.NetworkPolicyList, w io.Writer, opt return nil } +func printStorageClass(sc *extensions.StorageClass, w io.Writer, options PrintOptions) error { + name := sc.Name + provtype := sc.Provisioner + + if _, err := fmt.Fprintf(w, "%s\t%s\t", name, provtype); err != nil { + return err + } + if _, err := fmt.Fprint(w, AppendLabels(sc.Labels, options.ColumnLabels)); err != nil { + return err + } + if _, err := fmt.Fprint(w, AppendAllLabels(options.ShowLabels, sc.Labels)); err != nil { + return err + } + + return nil +} + +func printStorageClassList(scList *extensions.StorageClassList, w io.Writer, options PrintOptions) error { + for _, sc := range scList.Items { + if err := printStorageClass(&sc, w, options); err != nil { + return err + } + } + return nil +} + func AppendLabels(itemLabels map[string]string, columnLabels []string) string { var buffer bytes.Buffer diff --git a/vendor/k8s.io/kubernetes/pkg/master/master.go b/vendor/k8s.io/kubernetes/pkg/master/master.go index 4867c6acf0c1..a5dfbb3ee076 100644 --- a/vendor/k8s.io/kubernetes/pkg/master/master.go +++ b/vendor/k8s.io/kubernetes/pkg/master/master.go @@ -99,6 +99,7 @@ import ( serviceetcd "k8s.io/kubernetes/pkg/registry/service/etcd" ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator" serviceaccountetcd "k8s.io/kubernetes/pkg/registry/serviceaccount/etcd" + storageclassetcd "k8s.io/kubernetes/pkg/registry/storageclass/etcd" thirdpartyresourceetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd" "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata" thirdpartyresourcedataetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd" @@ -947,6 +948,10 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage { if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("networkpolicies")) { storage["networkpolicies"] = networkPolicyStorage } + storageClassStorage := storageclassetcd.NewREST(restOptions("storageclasses")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("storageclasses")) { + storage["storageclasses"] = storageClassStorage + } return storage } @@ -1117,6 +1122,7 @@ func DefaultAPIResourceConfigSource() *genericapiserver.ResourceConfig { extensionsapiv1beta1.SchemeGroupVersion.WithResource("jobs"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("replicasets"), extensionsapiv1beta1.SchemeGroupVersion.WithResource("thirdpartyresources"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("storageclasses"), ) return ret diff --git a/vendor/k8s.io/kubernetes/pkg/registry/storageclass/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/doc.go new file mode 100644 index 000000000000..8cb043231773 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/doc.go @@ -0,0 +1,19 @@ +/* +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 storageClass provides Registry interface and its REST +// implementation for storing storageclass api objects. +package storageclass diff --git a/vendor/k8s.io/kubernetes/pkg/registry/storageclass/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/etcd/etcd.go new file mode 100644 index 000000000000..65e69890358f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/etcd/etcd.go @@ -0,0 +1,71 @@ +/* +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/registry/storageclass" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *registry.Store +} + +// NewREST returns a RESTStorage object that will work against persistent volumes. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/storageclasses" + + newListFunc := func() runtime.Object { return &extensions.StorageClassList{} } + storageInterface := opts.Decorator( + opts.Storage, + 100, + &extensions.StorageClass{}, + prefix, + storageclass.Strategy, + newListFunc, + ) + + store := ®istry.Store{ + NewFunc: func() runtime.Object { return &extensions.StorageClass{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return prefix + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return registry.NoNamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.StorageClass).Name, nil + }, + PredicateFunc: storageclass.MatchStorageClasses, + QualifiedResource: api.Resource("storageclasses"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: storageclass.Strategy, + UpdateStrategy: storageclass.Strategy, + DeleteStrategy: storageclass.Strategy, + ReturnDeletedObject: true, + + Storage: storageInterface, + } + + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/storageclass/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/etcd/etcd_test.go new file mode 100644 index 000000000000..4c1a5d43733e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/etcd/etcd_test.go @@ -0,0 +1,136 @@ +/* +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName) + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + storageClassStorage := NewREST(restOptions) + return storageClassStorage, server +} + +func validNewStorageClass(name string) *extensions.StorageClass { + return &extensions.StorageClass{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Provisioner: "kubernetes.io/aws-ebs", + Parameters: map[string]string{ + "foo": "bar", + }, + } +} + +func validChangedStorageClass() *extensions.StorageClass { + return validNewStorageClass("foo") +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store).ClusterScope() + storageClass := validNewStorageClass("foo") + storageClass.ObjectMeta = api.ObjectMeta{GenerateName: "foo"} + test.TestCreate( + // valid + storageClass, + // invalid + &extensions.StorageClass{ + ObjectMeta: api.ObjectMeta{Name: "*BadName!"}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store).ClusterScope() + test.TestUpdate( + // valid + validNewStorageClass("foo"), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.StorageClass) + object.Parameters = map[string]string{"foo": "bar"} + return object + }, + //invalid update + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.StorageClass) + object.Parameters = map[string]string{"faz": "bar"} + return object + }, + ) + +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store).ClusterScope().ReturnDeletedObject() + test.TestDelete(validNewStorageClass("foo")) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store).ClusterScope() + test.TestGet(validNewStorageClass("foo")) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store).ClusterScope() + test.TestList(validNewStorageClass("foo")) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store).ClusterScope() + test.TestWatch( + validNewStorageClass("foo"), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/storageclass/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/strategy.go new file mode 100644 index 000000000000..a8ebac5d0a47 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/strategy.go @@ -0,0 +1,98 @@ +/* +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 storageclass + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// storageClassStrategy implements behavior for StorageClass objects +type storageClassStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating +// StorageClass objects via the REST API. +var Strategy = storageClassStrategy{api.Scheme, api.SimpleNameGenerator} + +func (storageClassStrategy) NamespaceScoped() bool { + return false +} + +// ResetBeforeCreate clears the Status field which is not allowed to be set by end users on creation. +func (storageClassStrategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*extensions.StorageClass) +} + +func (storageClassStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + storageClass := obj.(*extensions.StorageClass) + return validation.ValidateStorageClass(storageClass) +} + +// Canonicalize normalizes the object after validation. +func (storageClassStrategy) Canonicalize(obj runtime.Object) { +} + +func (storageClassStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate sets the Status fields which is not allowed to be set by an end user updating a PV +func (storageClassStrategy) PrepareForUpdate(obj, old runtime.Object) { + _ = obj.(*extensions.StorageClass) + _ = old.(*extensions.StorageClass) +} + +func (storageClassStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + errorList := validation.ValidateStorageClass(obj.(*extensions.StorageClass)) + return append(errorList, validation.ValidateStorageClassUpdate(obj.(*extensions.StorageClass), old.(*extensions.StorageClass))...) +} + +func (storageClassStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// MatchStorageClass returns a generic matcher for a given label and field selector. +func MatchStorageClasses(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + cls, ok := obj.(*extensions.StorageClass) + if !ok { + return nil, nil, fmt.Errorf("given object is not of type StorageClass") + } + + return labels.Set(cls.ObjectMeta.Labels), StorageClassToSelectableFields(cls), nil + }, + } +} + +// StorageClassToSelectableFields returns a label set that represents the object +func StorageClassToSelectableFields(storageClass *extensions.StorageClass) fields.Set { + return generic.ObjectMetaFieldsSet(storageClass.ObjectMeta, false) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/storageclass/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/strategy_test.go new file mode 100644 index 000000000000..73884c43de09 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/storageclass/strategy_test.go @@ -0,0 +1,69 @@ +/* +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 storageclass + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestStorageClassStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if Strategy.NamespaceScoped() { + t.Errorf("StorageClass must not be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("StorageClass should not allow create on update") + } + + storageClass := &extensions.StorageClass{ + ObjectMeta: api.ObjectMeta{ + Name: "valid-class", + }, + Provisioner: "kubernetes.io/aws-ebs", + Parameters: map[string]string{ + "foo": "bar", + }, + } + + Strategy.PrepareForCreate(storageClass) + + errs := Strategy.Validate(ctx, storageClass) + if len(errs) != 0 { + t.Errorf("unexpected error validating %v", errs) + } + + newStorageClass := &extensions.StorageClass{ + ObjectMeta: api.ObjectMeta{ + Name: "valid-class-2", + ResourceVersion: "4", + }, + Provisioner: "kubernetes.io/aws-ebs", + Parameters: map[string]string{ + "foo": "bar", + }, + } + + Strategy.PrepareForUpdate(newStorageClass, storageClass) + + errs = Strategy.ValidateUpdate(ctx, newStorageClass, storageClass) + if len(errs) == 0 { + t.Errorf("Expected a validation error") + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/volume/aws_ebs/aws_util.go b/vendor/k8s.io/kubernetes/pkg/volume/aws_ebs/aws_util.go index 76ebe72adc5a..96730fdd57ab 100644 --- a/vendor/k8s.io/kubernetes/pkg/volume/aws_ebs/aws_util.go +++ b/vendor/k8s.io/kubernetes/pkg/volume/aws_ebs/aws_util.go @@ -19,6 +19,8 @@ package aws_ebs import ( "fmt" "os" + "strconv" + "strings" "time" "github.com/golang/glog" @@ -84,6 +86,28 @@ func (util *AWSDiskUtil) CreateVolume(c *awsElasticBlockStoreProvisioner) (strin Tags: tags, PVCName: c.options.PVCName, } + // Apply Parameters (case-insensitive). We leave validation of + // the values to the cloud provider. + for k, v := range c.options.Parameters { + switch strings.ToLower(k) { + case "type": + volumeOptions.VolumeType = v + case "zone": + volumeOptions.AvailabilityZone = v + case "iopspergb": + volumeOptions.IOPSPerGB, err = strconv.Atoi(v) + if err != nil { + return "", 0, nil, fmt.Errorf("invalid iopsPerGB value %q, must be integer between 1 and 30: %v", v, err) + } + default: + return "", 0, nil, fmt.Errorf("invalid option %q for volume plugin %s", k, c.plugin.GetPluginName()) + } + } + + // TODO: implement c.options.ProvisionerSelector parsing + if c.options.ProvisionerSelector != nil { + return "", 0, nil, fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on AWS") + } name, err := cloud.CreateDisk(volumeOptions) if err != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/volume/gce_pd/attacher_test.go b/vendor/k8s.io/kubernetes/pkg/volume/gce_pd/attacher_test.go index c3c4b596b4f2..8e7597c27af8 100644 --- a/vendor/k8s.io/kubernetes/pkg/volume/gce_pd/attacher_test.go +++ b/vendor/k8s.io/kubernetes/pkg/volume/gce_pd/attacher_test.go @@ -351,7 +351,7 @@ func (testcase *testcase) DiskIsAttached(diskName, instanceID string) (bool, err return expected.isAttached, expected.ret } -func (testcase *testcase) CreateDisk(name string, zone string, sizeGb int64, tags map[string]string) error { +func (testcase *testcase) CreateDisk(name string, diskType string, zone string, sizeGb int64, tags map[string]string) error { return errors.New("Not implemented") } diff --git a/vendor/k8s.io/kubernetes/pkg/volume/gce_pd/gce_util.go b/vendor/k8s.io/kubernetes/pkg/volume/gce_pd/gce_util.go index 717b9b0df100..d07d6a08be3b 100644 --- a/vendor/k8s.io/kubernetes/pkg/volume/gce_pd/gce_util.go +++ b/vendor/k8s.io/kubernetes/pkg/volume/gce_pd/gce_util.go @@ -80,17 +80,37 @@ func (gceutil *GCEDiskUtil) CreateVolume(c *gcePersistentDiskProvisioner) (strin // GCE works with gigabytes, convert to GiB with rounding up requestGB := volume.RoundUpSize(requestBytes, 1024*1024*1024) - // The disk will be created in the zone in which this code is currently running - // TODO: We should support auto-provisioning volumes in multiple/specified zones - zones, err := cloud.GetAllZones() - if err != nil { - glog.V(2).Infof("error getting zone information from GCE: %v", err) - return "", 0, nil, err + // Apply Parameters (case-insensitive). We leave validation of + // the values to the cloud provider. + diskType := "" + zone := "" + for k, v := range c.options.Parameters { + switch strings.ToLower(k) { + case "type": + diskType = v + case "zone": + zone = v + default: + return "", 0, nil, fmt.Errorf("invalid option %q for volume plugin %s", k, c.plugin.GetPluginName()) + } } - zone := volume.ChooseZoneForVolume(zones, c.options.PVCName) + if c.options.ProvisionerSelector != nil { + return "", 0, nil, fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on GCE") + } + + if zone == "" { + // No zone specified, choose one randomly in the same region as the + // node is running. + zones, err := cloud.GetAllZones() + if err != nil { + glog.V(2).Infof("error getting zone information from GCE: %v", err) + return "", 0, nil, err + } + zone = volume.ChooseZoneForVolume(zones, c.options.PVCName) + } - err = cloud.CreateDisk(name, zone, int64(requestGB), *c.options.CloudTags) + err = cloud.CreateDisk(name, diskType, zone, int64(requestGB), *c.options.CloudTags) if err != nil { glog.V(2).Infof("Error creating GCE PD volume: %v", err) return "", 0, nil, err diff --git a/vendor/k8s.io/kubernetes/pkg/volume/host_path/host_path.go b/vendor/k8s.io/kubernetes/pkg/volume/host_path/host_path.go index 7f0b26a4c298..c681e6743c25 100644 --- a/vendor/k8s.io/kubernetes/pkg/volume/host_path/host_path.go +++ b/vendor/k8s.io/kubernetes/pkg/volume/host_path/host_path.go @@ -43,17 +43,6 @@ func ProbeVolumePlugins(volumeConfig volume.VolumeConfig) []volume.VolumePlugin } } -func ProbeRecyclableVolumePlugins(recyclerFunc func(pvName string, spec *volume.Spec, host volume.VolumeHost, volumeConfig volume.VolumeConfig) (volume.Recycler, error), volumeConfig volume.VolumeConfig) []volume.VolumePlugin { - return []volume.VolumePlugin{ - &hostPathPlugin{ - host: nil, - newRecyclerFunc: recyclerFunc, - newProvisionerFunc: newProvisioner, - config: volumeConfig, - }, - } -} - type hostPathPlugin struct { host volume.VolumeHost // decouple creating Recyclers/Deleters/Provisioners by deferring to a function. Allows for easier testing. @@ -132,6 +121,9 @@ func (plugin *hostPathPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, err } func (plugin *hostPathPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) { + if !plugin.config.ProvisioningEnabled { + return nil, fmt.Errorf("Provisioning in volume plugin %q is disabled", plugin.GetPluginName()) + } if len(options.AccessModes) == 0 { options.AccessModes = plugin.GetAccessModes() } diff --git a/vendor/k8s.io/kubernetes/pkg/volume/host_path/host_path_test.go b/vendor/k8s.io/kubernetes/pkg/volume/host_path/host_path_test.go index 77946b02010d..2be5a5f0629f 100644 --- a/vendor/k8s.io/kubernetes/pkg/volume/host_path/host_path_test.go +++ b/vendor/k8s.io/kubernetes/pkg/volume/host_path/host_path_test.go @@ -153,7 +153,8 @@ func TestProvisioner(t *testing.T) { err := os.MkdirAll(tempPath, 0750) plugMgr := volume.VolumePluginMgr{} - plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */)) + plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{ProvisioningEnabled: true}), + volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil, "" /* rootContext */)) spec := &volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{Path: tempPath}}}}} plug, err := plugMgr.FindCreatablePluginBySpec(spec) if err != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/volume/plugins.go b/vendor/k8s.io/kubernetes/pkg/volume/plugins.go index 6a58b68812de..87816cf95703 100644 --- a/vendor/k8s.io/kubernetes/pkg/volume/plugins.go +++ b/vendor/k8s.io/kubernetes/pkg/volume/plugins.go @@ -25,6 +25,7 @@ import ( "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/cloudprovider" "k8s.io/kubernetes/pkg/types" @@ -55,6 +56,10 @@ type VolumeOptions struct { ClusterName string // Tags to attach to the real volume in the cloud provider - e.g. AWS EBS CloudTags *map[string]string + // Volume provisioning parameters from StorageClass + Parameters map[string]string + // Volume selector from PersistentVolumeClaim + ProvisionerSelector *unversioned.LabelSelector } // VolumePlugin is an interface to volume plugins that can be used on a @@ -283,6 +288,10 @@ type VolumeConfig struct { // the system and only understood by the binary hosting the plugin and the // plugin itself. OtherAttributes map[string]string + + // ProvisioningEnabled configures whether provisioning of this plugin is + // enabled or not. Currently used only in host_path plugin. + ProvisioningEnabled bool } // NewSpecFromVolume creates an Spec from an api.Volume @@ -419,7 +428,20 @@ func (pm *VolumePluginMgr) FindRecyclablePluginBySpec(spec *Spec) (RecyclableVol return nil, fmt.Errorf("no recyclable volume plugin matched") } -// FindDeletablePluginByName fetches a persistent volume plugin by name. If +// FindProvisionablePluginByName fetches a persistent volume plugin by name. If +// no plugin is found, returns error. +func (pm *VolumePluginMgr) FindProvisionablePluginByName(name string) (ProvisionableVolumePlugin, error) { + volumePlugin, err := pm.FindPluginByName(name) + if err != nil { + return nil, err + } + if provisionableVolumePlugin, ok := volumePlugin.(ProvisionableVolumePlugin); ok { + return provisionableVolumePlugin, nil + } + return nil, fmt.Errorf("no provisionable volume plugin matched") +} + +// FindDeletablePluginBySppec fetches a persistent volume plugin by spec. If // no plugin is found, returns error. func (pm *VolumePluginMgr) FindDeletablePluginBySpec(spec *Spec) (DeletableVolumePlugin, error) { volumePlugin, err := pm.FindPluginBySpec(spec) @@ -432,6 +454,19 @@ func (pm *VolumePluginMgr) FindDeletablePluginBySpec(spec *Spec) (DeletableVolum return nil, fmt.Errorf("no deletable volume plugin matched") } +// FindDeletablePluginByName fetches a persistent volume plugin by name. If +// no plugin is found, returns error. +func (pm *VolumePluginMgr) FindDeletablePluginByName(name string) (DeletableVolumePlugin, error) { + volumePlugin, err := pm.FindPluginByName(name) + if err != nil { + return nil, err + } + if deletableVolumePlugin, ok := volumePlugin.(DeletableVolumePlugin); ok { + return deletableVolumePlugin, nil + } + return nil, fmt.Errorf("no deletable volume plugin matched") +} + // FindCreatablePluginBySpec fetches a persistent volume plugin by name. If // no plugin is found, returns error. func (pm *VolumePluginMgr) FindCreatablePluginBySpec(spec *Spec) (ProvisionableVolumePlugin, error) { diff --git a/vendor/k8s.io/kubernetes/test/e2e/pd.go b/vendor/k8s.io/kubernetes/test/e2e/pd.go index 28613fa30cfb..d74198d1529c 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/pd.go +++ b/vendor/k8s.io/kubernetes/test/e2e/pd.go @@ -469,7 +469,7 @@ func createPD() (string, error) { } tags := map[string]string{} - err = gceCloud.CreateDisk(pdName, framework.TestContext.CloudConfig.Zone, 10 /* sizeGb */, tags) + err = gceCloud.CreateDisk(pdName, gcecloud.DiskTypeSSD, framework.TestContext.CloudConfig.Zone, 10 /* sizeGb */, tags) if err != nil { return "", err } diff --git a/vendor/k8s.io/kubernetes/test/e2e/volume_provisioning.go b/vendor/k8s.io/kubernetes/test/e2e/volume_provisioning.go index 30bfa7bf465d..a10c602e16ed 100644 --- a/vendor/k8s.io/kubernetes/test/e2e/volume_provisioning.go +++ b/vendor/k8s.io/kubernetes/test/e2e/volume_provisioning.go @@ -22,6 +22,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/test/e2e/framework" @@ -52,6 +53,12 @@ var _ = framework.KubeDescribe("Dynamic provisioning", func() { framework.KubeDescribe("DynamicProvisioner", func() { It("should create and delete persistent volumes", func() { framework.SkipUnlessProviderIs("openstack", "gce", "aws", "gke") + + By("creating a claim with a dynamic provisioning annotation") + class := createStorageClass() + c.Extensions().StorageClasses().Create(class) + defer c.Extensions().StorageClasses().Delete(class.Name) + By("creating a claim with a dynamic provisioning annotation") claim := createClaim(ns) defer func() { @@ -130,7 +137,7 @@ func createClaim(ns string) *api.PersistentVolumeClaim { GenerateName: "pvc-", Namespace: ns, Annotations: map[string]string{ - "volume.alpha.kubernetes.io/storage-class": "", + "volume.beta.kubernetes.io/storage-class": "fast", }, }, Spec: api.PersistentVolumeClaimSpec{ @@ -192,3 +199,26 @@ func runInPodWithVolume(c *client.Client, ns, claimName, command string) { framework.ExpectNoError(err, "Failed to create pod: %v", err) framework.ExpectNoError(framework.WaitForPodSuccessInNamespaceSlow(c, pod.Name, pod.Spec.Containers[0].Name, pod.Namespace)) } + +func createStorageClass() *extensions.StorageClass { + var pluginName string + + switch { + case framework.ProviderIs("gke"), framework.ProviderIs("gce"): + pluginName = "kubernetes.io/gce-pd" + case framework.ProviderIs("aws"): + pluginName = "kubernetes.io/aws-ebs" + case framework.ProviderIs("openstack"): + pluginName = "kubernetes.io/cinder" + } + + return &extensions.StorageClass{ + TypeMeta: unversioned.TypeMeta{ + Kind: "StorageClass", + }, + ObjectMeta: api.ObjectMeta{ + Name: "fast", + }, + Provisioner: pluginName, + } +} diff --git a/vendor/k8s.io/kubernetes/test/integration/storageclasses/storage_classes_test.go b/vendor/k8s.io/kubernetes/test/integration/storageclasses/storage_classes_test.go new file mode 100644 index 000000000000..fd7f783c44a5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/test/integration/storageclasses/storage_classes_test.go @@ -0,0 +1,101 @@ +// +build integration,!no-etcd + +/* +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 storageclasses + +// This file contains tests for the storage classes API resource. + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/restclient" + client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/test/integration/framework" +) + +const provisionerPluginName = "kubernetes.io/mock-provisioner" + +// TestStorageClasses tests apiserver-side behavior of creation of storage class objects and their use by pvcs. +func TestStorageClasses(t *testing.T) { + _, s := framework.RunAMaster(nil) + defer s.Close() + + client := client.NewOrDie(&restclient.Config{Host: s.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + + ns := framework.CreateTestingNamespace("storageclass", s, t) + defer framework.DeleteTestingNamespace(ns, s, t) + + DoTestStorageClasses(t, client, ns) +} + +// DoTestStorageClasses tests storage classes for one api version. +func DoTestStorageClasses(t *testing.T, client *client.Client, ns *api.Namespace) { + // Make a storage class object. + s := extensions.StorageClass{ + TypeMeta: unversioned.TypeMeta{ + Kind: "StorageClass", + }, + ObjectMeta: api.ObjectMeta{ + Name: "gold", + }, + Provisioner: provisionerPluginName, + } + + if _, err := client.Extensions().StorageClasses().Create(&s); err != nil { + t.Errorf("unable to create test storage class: %v", err) + } + defer deleteStorageClassOrErrorf(t, client, s.Namespace, s.Name) + + // Template for pvcs that specify a storage class + pvc := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "XXX", + Namespace: ns.Name, + Annotations: map[string]string{ + "volume.beta.kubernetes.io/storage-class": "gold", + }, + }, + Spec: api.PersistentVolumeClaimSpec{ + Resources: api.ResourceRequirements{Requests: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("1G")}}, + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + }, + } + + pvc.ObjectMeta.Name = "uses-storageclass" + if _, err := client.PersistentVolumeClaims(ns.Name).Create(pvc); err != nil { + t.Errorf("Failed to create pvc: %v", err) + } + defer deletePersistentVolumeClaimOrErrorf(t, client, ns.Name, pvc.Name) +} + +func deleteStorageClassOrErrorf(t *testing.T, c *client.Client, ns, name string) { + if err := c.Extensions().StorageClasses().Delete(name); err != nil { + t.Errorf("unable to delete storage class %v: %v", name, err) + } +} + +func deletePersistentVolumeClaimOrErrorf(t *testing.T, c *client.Client, ns, name string) { + if err := c.PersistentVolumeClaims(ns).Delete(name); err != nil { + t.Errorf("unable to delete persistent volume claim %v: %v", name, err) + } +}