未验证 提交 1464ca19 编写于 作者: K KubeSphere CI Bot 提交者: GitHub

Merge pull request #2057 from min-zh/snapshot

fix bug of pvc api
......@@ -28,6 +28,36 @@ func New(factory informers.InformerFactory) *Handler {
}
}
func (h *Handler) handleGetResources(request *restful.Request, response *restful.Response) {
namespace := request.PathParameter("namespace")
resourceType := request.PathParameter("resources")
name := request.PathParameter("name")
result, err := h.resourceGetterV1alpha3.Get(resourceType, namespace, name)
if err == nil {
response.WriteEntity(result)
return
}
if err != resource.ErrResourceNotSupported {
klog.Error(err)
api.HandleInternalError(response, nil, err)
return
}
// fallback to v1alpha2
resultV1alpha2, err := h.resourcesGetterV1alpha2.GetResource(namespace, resourceType, name)
if err != nil {
klog.Error(err)
api.HandleInternalError(response, nil, err)
return
}
response.WriteEntity(resultV1alpha2)
}
// handleListResources retrieves resources
func (h *Handler) handleListResources(request *restful.Request, response *restful.Response) {
query := query.ParseQueryParameter(request)
......
......@@ -70,6 +70,15 @@ func AddToContainer(c *restful.Container, informerFactory informers.InformerFact
Param(webservice.QueryParameter(query.ParameterOrderBy, "sort parameters, e.g. orderBy=createTime")).
Returns(http.StatusOK, ok, api.ListResult{}))
webservice.Route(webservice.GET("/namespaces/{namespace}/{resources}/{name}").
To(handler.handleGetResources).
Metadata(restfulspec.KeyOpenAPITags, []string{tagNamespacedResource}).
Doc("Namespace level get resource query").
Param(webservice.PathParameter("namespace", "the name of the project")).
Param(webservice.PathParameter("resources", "namespace level resource type, e.g. pods,jobs,configmaps,services.")).
Param(webservice.PathParameter("name", "the name of resource")).
Returns(http.StatusOK, ok, api.ListResult{}))
webservice.Route(webservice.GET("/components").
To(handler.handleGetComponents).
Metadata(restfulspec.KeyOpenAPITags, []string{tagComponentStatus}).
......
/*
Copyright 2019 The KubeSphere 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 persistentvolumeclaim
import (
snapshotinformers "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/informers/externalversions"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
"strconv"
"strings"
)
const (
storageClassName = "storageClassName"
annotationInUse = "kubesphere.io/in-use"
annotationAllowSnapshot = "kubesphere.io/allow-snapshot"
annotationStorageProvisioner = "volume.beta.kubernetes.io/storage-provisioner"
)
type persistentVolumeClaimGetter struct {
informers informers.SharedInformerFactory
snapshotInformers snapshotinformers.SharedInformerFactory
}
func New(informer informers.SharedInformerFactory, snapshotInformer snapshotinformers.SharedInformerFactory) v1alpha3.Interface {
return &persistentVolumeClaimGetter{informers: informer, snapshotInformers: snapshotInformer}
}
func (p *persistentVolumeClaimGetter) Get(namespace, name string) (runtime.Object, error) {
pvc, err := p.informers.Core().V1().PersistentVolumeClaims().Lister().PersistentVolumeClaims(namespace).Get(name)
if err != nil {
return pvc, err
}
p.annotatePVC(pvc)
return pvc, nil
}
func (p *persistentVolumeClaimGetter) List(namespace string, query *query.Query) (*api.ListResult, error) {
all, err := p.informers.Core().V1().PersistentVolumeClaims().Lister().PersistentVolumeClaims(namespace).List(query.Selector())
if err != nil {
return nil, err
}
var result []runtime.Object
for _, pvc := range all {
p.annotatePVC(pvc)
result = append(result, pvc)
}
return v1alpha3.DefaultList(result, query, p.compare, p.filter), nil
}
func (p *persistentVolumeClaimGetter) compare(left, right runtime.Object, field query.Field) bool {
leftSnapshot, ok := left.(*v1.PersistentVolumeClaim)
if !ok {
return false
}
rightSnapshot, ok := right.(*v1.PersistentVolumeClaim)
if !ok {
return false
}
return v1alpha3.DefaultObjectMetaCompare(leftSnapshot.ObjectMeta, rightSnapshot.ObjectMeta, field)
}
func (p *persistentVolumeClaimGetter) filter(object runtime.Object, filter query.Filter) bool {
pvc, ok := object.(*v1.PersistentVolumeClaim)
if !ok {
return false
}
switch filter.Field {
case query.FieldStatus:
return strings.EqualFold(string(pvc.Status.Phase), string(filter.Value))
case storageClassName:
return pvc.Spec.StorageClassName != nil && *pvc.Spec.StorageClassName == string(filter.Value)
default:
return v1alpha3.DefaultObjectMetaFilter(pvc.ObjectMeta, filter)
}
}
func (p *persistentVolumeClaimGetter) annotatePVC(pvc *v1.PersistentVolumeClaim) {
inUse := p.countPods(pvc.Name, pvc.Namespace)
isSnapshotAllow := p.isSnapshotAllowed(pvc.GetAnnotations()[annotationStorageProvisioner])
if pvc.Annotations == nil {
pvc.Annotations = make(map[string]string)
}
pvc.Annotations[annotationInUse] = strconv.FormatBool(inUse)
pvc.Annotations[annotationAllowSnapshot] = strconv.FormatBool(isSnapshotAllow)
}
func (p *persistentVolumeClaimGetter) countPods(name, namespace string) bool {
pods, err := p.informers.Core().V1().Pods().Lister().Pods(namespace).List(labels.Everything())
if err != nil {
return false
}
for _, pod := range pods {
for _, pvc := range pod.Spec.Volumes {
if pvc.PersistentVolumeClaim != nil && pvc.PersistentVolumeClaim.ClaimName == name {
return true
}
}
}
return false
}
func (p *persistentVolumeClaimGetter) isSnapshotAllowed(provisioner string) bool {
if len(provisioner) == 0 {
return false
}
volumeSnapshotClasses, err := p.snapshotInformers.Snapshot().V1beta1().VolumeSnapshotClasses().Lister().List(labels.Everything())
if err != nil {
return false
}
for _, volumeSnapshotClass := range volumeSnapshotClasses {
if volumeSnapshotClass.Driver == provisioner {
return true
}
}
return false
}
/*
Copyright 2019 The KubeSphere 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 persistentvolumeclaim
import (
"github.com/google/go-cmp/cmp"
snapshot "github.com/kubernetes-csi/external-snapshotter/v2/pkg/apis/volumesnapshot/v1beta1"
snapshotefakeclient "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/clientset/versioned/fake"
snapshotinformers "github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/informers/externalversions"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
"testing"
)
var (
testStorageClassName = "test-csi"
)
func TestListPods(t *testing.T) {
tests := []struct {
description string
namespace string
query *query.Query
expected *api.ListResult
expectedErr error
}{
{
"test name filter",
"default",
&query.Query{
Pagination: &query.Pagination{
Limit: 10,
Offset: 0,
},
SortBy: query.FieldName,
Ascending: false,
Filters: map[query.Field]query.Value{query.FieldNamespace: query.Value("default")},
},
&api.ListResult{
Items: []interface{}{pvc3, pvc2, pvc1},
TotalItems: len(persistentVolumeClaims),
},
nil,
},
{
"test status filter",
"default",
&query.Query{
Pagination: &query.Pagination{
Limit: 10,
Offset: 0,
},
SortBy: query.FieldName,
Ascending: false,
Filters: map[query.Field]query.Value{
query.FieldNamespace: query.Value("default"),
query.FieldStatus: query.Value(pvc1.Status.Phase),
},
},
&api.ListResult{
Items: []interface{}{pvc1},
TotalItems: 1,
},
nil,
},
{
"test StorageClass filter and allow snapshot",
"default",
&query.Query{
Pagination: &query.Pagination{
Limit: 10,
Offset: 0,
},
SortBy: query.FieldName,
Ascending: false,
Filters: map[query.Field]query.Value{
query.FieldNamespace: query.Value("default"),
query.Field(storageClassName): query.Value(*pvc2.Spec.StorageClassName),
},
},
&api.ListResult{
Items: []interface{}{pvcGet2},
TotalItems: 1,
},
nil,
},
{
"test pvc in use",
"default",
&query.Query{
Pagination: &query.Pagination{
Limit: 10,
Offset: 0,
},
SortBy: query.FieldName,
Ascending: false,
Filters: map[query.Field]query.Value{
query.FieldNamespace: query.Value("default"),
query.FieldName: query.Value(pvc3.Name),
},
},
&api.ListResult{
Items: []interface{}{pvcGet3},
TotalItems: 1,
},
nil,
},
}
getter := prepare()
for _, test := range tests {
got, err := getter.List(test.namespace, test.query)
if test.expectedErr != nil && err != test.expectedErr {
t.Errorf("expected error, got nothing")
} else if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(got, test.expected); diff != "" {
t.Errorf("[%s] %T differ (-got, +want): %s", test.description, test.expected, diff)
}
}
}
var (
pvc1 = &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-1",
Namespace: "default",
},
Status: corev1.PersistentVolumeClaimStatus{
Phase: corev1.ClaimPending,
},
}
pvc2 = &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-2",
Namespace: "default",
Annotations: map[string]string{
annotationStorageProvisioner: testStorageClassName,
},
},
Spec: corev1.PersistentVolumeClaimSpec{
StorageClassName: &testStorageClassName,
},
}
pvcGet2 = &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-2",
Namespace: "default",
Annotations: map[string]string{
annotationInUse: "false",
annotationAllowSnapshot: "true",
annotationStorageProvisioner: testStorageClassName,
},
},
Spec: corev1.PersistentVolumeClaimSpec{
StorageClassName: &testStorageClassName,
},
}
pvc3 = &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-3",
Namespace: "default",
},
}
pvcGet3 = &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-3",
Namespace: "default",
Annotations: map[string]string{
annotationInUse: "true",
annotationAllowSnapshot: "false",
},
},
}
pod1 = &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: "default",
},
Spec: corev1.PodSpec{
Volumes: []corev1.Volume{
{
Name: "data",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: pvc3.Name,
},
},
},
},
},
}
volumeSnapshotClass1 = &snapshot.VolumeSnapshotClass{
ObjectMeta: metav1.ObjectMeta{
Name: "VolumeSnapshotClass-1",
Namespace: "default",
},
Driver: testStorageClassName,
}
persistentVolumeClaims = []interface{}{pvc1, pvc2, pvc3}
pods = []interface{}{pod1}
volumeSnapshotClasses = []interface{}{volumeSnapshotClass1}
)
func prepare() v1alpha3.Interface {
client := fake.NewSimpleClientset()
informer := informers.NewSharedInformerFactory(client, 0)
snapshotClient := snapshotefakeclient.NewSimpleClientset()
snapshotInformers := snapshotinformers.NewSharedInformerFactory(snapshotClient, 0)
for _, pvc := range persistentVolumeClaims {
_ = informer.Core().V1().PersistentVolumeClaims().Informer().GetIndexer().Add(pvc)
}
for _, pod := range pods {
_ = informer.Core().V1().Pods().Informer().GetIndexer().Add(pod)
}
for _, volumeSnapshotClass := range volumeSnapshotClasses {
_ = snapshotInformers.Snapshot().V1beta1().VolumeSnapshotClasses().Informer().GetIndexer().Add(volumeSnapshotClass)
}
return New(informer, snapshotInformers)
}
......@@ -27,6 +27,12 @@ import (
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
)
const (
filedNameName = "nodeName"
filedPVCName = "pvcName"
filedServiceName = "serviceName"
)
type podsGetter struct {
informer informers.SharedInformerFactory
}
......@@ -35,13 +41,13 @@ func New(sharedInformers informers.SharedInformerFactory) v1alpha3.Interface {
return &podsGetter{informer: sharedInformers}
}
func (d *podsGetter) Get(namespace, name string) (runtime.Object, error) {
return d.informer.Core().V1().Pods().Lister().Pods(namespace).Get(name)
func (p *podsGetter) Get(namespace, name string) (runtime.Object, error) {
return p.informer.Core().V1().Pods().Lister().Pods(namespace).Get(name)
}
func (d *podsGetter) List(namespace string, query *query.Query) (*api.ListResult, error) {
func (p *podsGetter) List(namespace string, query *query.Query) (*api.ListResult, error) {
all, err := d.informer.Core().V1().Pods().Lister().Pods(namespace).List(query.Selector())
all, err := p.informer.Core().V1().Pods().Lister().Pods(namespace).List(query.Selector())
if err != nil {
return nil, err
}
......@@ -51,10 +57,10 @@ func (d *podsGetter) List(namespace string, query *query.Query) (*api.ListResult
result = append(result, app)
}
return v1alpha3.DefaultList(result, query, d.compare, d.filter), nil
return v1alpha3.DefaultList(result, query, p.compare, p.filter), nil
}
func (d *podsGetter) compare(left runtime.Object, right runtime.Object, field query.Field) bool {
func (p *podsGetter) compare(left runtime.Object, right runtime.Object, field query.Field) bool {
leftPod, ok := left.(*corev1.Pod)
if !ok {
......@@ -69,30 +75,25 @@ func (d *podsGetter) compare(left runtime.Object, right runtime.Object, field qu
return v1alpha3.DefaultObjectMetaCompare(leftPod.ObjectMeta, rightPod.ObjectMeta, field)
}
func (d *podsGetter) filter(object runtime.Object, filter query.Filter) bool {
func (p *podsGetter) filter(object runtime.Object, filter query.Filter) bool {
pod, ok := object.(*corev1.Pod)
if !ok {
return false
}
switch filter.Field {
case "nodeName":
if pod.Spec.NodeName != string(filter.Value) {
return false
}
case "pvcName":
if !d.podBindPVC(pod, string(filter.Value)) {
return false
}
case "serviceName":
if !d.podBelongToService(pod, string(filter.Value)) {
return false
}
case filedNameName:
return pod.Spec.NodeName == string(filter.Value)
case filedPVCName:
return p.podBindPVC(pod, string(filter.Value))
case filedServiceName:
return p.podBelongToService(pod, string(filter.Value))
default:
return v1alpha3.DefaultObjectMetaFilter(pod.ObjectMeta, filter)
}
return v1alpha3.DefaultObjectMetaFilter(pod.ObjectMeta, filter)
}
func (s *podsGetter) podBindPVC(item *corev1.Pod, pvcName string) bool {
func (p *podsGetter) podBindPVC(item *corev1.Pod, pvcName string) bool {
for _, v := range item.Spec.Volumes {
if v.VolumeSource.PersistentVolumeClaim != nil &&
v.VolumeSource.PersistentVolumeClaim.ClaimName == pvcName {
......@@ -102,8 +103,8 @@ func (s *podsGetter) podBindPVC(item *corev1.Pod, pvcName string) bool {
return false
}
func (s *podsGetter) podBelongToService(item *corev1.Pod, serviceName string) bool {
service, err := s.informer.Core().V1().Services().Lister().Services(item.Namespace).Get(serviceName)
func (p *podsGetter) podBelongToService(item *corev1.Pod, serviceName string) bool {
service, err := p.informer.Core().V1().Services().Lister().Services(item.Namespace).Get(serviceName)
if err != nil {
return false
}
......
......@@ -33,11 +33,32 @@ func TestListPods(t *testing.T) {
Filters: map[query.Field]query.Value{query.FieldNamespace: query.Value("default")},
},
&api.ListResult{
Items: []interface{}{foo3, foo2, foo1},
Items: []interface{}{foo4, foo3, foo2, foo1},
TotalItems: len(pods),
},
nil,
},
{
"test pvcName filter",
"default",
&query.Query{
Pagination: &query.Pagination{
Limit: 10,
Offset: 0,
},
SortBy: query.FieldName,
Ascending: false,
Filters: map[query.Field]query.Value{
query.FieldNamespace: query.Value("default"),
filedPVCName: query.Value(foo4.Spec.Volumes[0].PersistentVolumeClaim.ClaimName),
},
},
&api.ListResult{
Items: []interface{}{foo4},
TotalItems: 1,
},
nil,
},
}
getter := prepare()
......@@ -75,7 +96,26 @@ var (
Namespace: "default",
},
}
pods = []interface{}{foo1, foo2, foo3}
foo4 = &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo4",
Namespace: "default",
},
Spec: corev1.PodSpec{
Volumes: []corev1.Volume{
{
Name: "data",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: "pvc-1",
ReadOnly: false,
},
},
},
},
},
}
pods = []interface{}{foo1, foo2, foo3, foo4}
)
func prepare() v1alpha3.Interface {
......@@ -84,7 +124,7 @@ func prepare() v1alpha3.Interface {
informer := informers.NewSharedInformerFactory(client, 0)
for _, pod := range pods {
informer.Core().V1().Pods().Informer().GetIndexer().Add(pod)
_ = informer.Core().V1().Pods().Informer().GetIndexer().Add(pod)
}
return New(informer)
......
......@@ -39,6 +39,7 @@ import (
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/globalrole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/namespace"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/networkpolicy"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/persistentvolumeclaim"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/pod"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/role"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/user"
......@@ -68,6 +69,7 @@ func NewResourceGetter(factory informers.InformerFactory) *ResourceGetter {
getters[iamv1alpha2.SchemeGroupVersion.WithResource(iamv1alpha2.ResourcesPluralUser)] = user.New(factory.KubeSphereSharedInformerFactory())
getters[rbacv1.SchemeGroupVersion.WithResource("roles")] = role.New(factory.KubernetesSharedInformerFactory())
getters[rbacv1.SchemeGroupVersion.WithResource("clusterroles")] = clusterrole.New(factory.KubernetesSharedInformerFactory())
getters[schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaims"}] = persistentvolumeclaim.New(factory.KubernetesSharedInformerFactory(), factory.SnapshotSharedInformerFactory())
getters[snapshotv1beta1.SchemeGroupVersion.WithResource("volumesnapshots")] = volumesnapshot.New(factory.SnapshotSharedInformerFactory())
getters[schema.GroupVersionResource{Group: "cluster.kubesphere.io", Version: "v1alpha1", Resource: "clusters"}] = cluster.New(factory.KubeSphereSharedInformerFactory())
getters[schema.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1", Resource: "customresourcedefinitions"}] = customresourcedefinition.New(factory.ApiExtensionSharedInformerFactory())
......
......@@ -20,7 +20,6 @@ package volumesnapshot
import (
"github.com/kubernetes-csi/external-snapshotter/v2/pkg/apis/volumesnapshot/v1beta1"
"github.com/kubernetes-csi/external-snapshotter/v2/pkg/client/informers/externalversions"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
......@@ -36,26 +35,26 @@ const (
)
type volumeSnapshotGetter struct {
informer externalversions.SharedInformerFactory
informers externalversions.SharedInformerFactory
}
func New(informer externalversions.SharedInformerFactory) v1alpha3.Interface {
return &volumeSnapshotGetter{informer: informer}
return &volumeSnapshotGetter{informers: informer}
}
func (v *volumeSnapshotGetter) Get(namespace, name string) (runtime.Object, error) {
return v.informer.Snapshot().V1beta1().VolumeSnapshots().Lister().VolumeSnapshots(namespace).Get(name)
return v.informers.Snapshot().V1beta1().VolumeSnapshots().Lister().VolumeSnapshots(namespace).Get(name)
}
func (v *volumeSnapshotGetter) List(namespace string, query *query.Query) (*api.ListResult, error) {
all, err := v.listVolumeSnapshots(namespace, query.Selector())
all, err := v.informers.Snapshot().V1beta1().VolumeSnapshots().Lister().VolumeSnapshots(namespace).List(query.Selector())
if err != nil {
return nil, err
}
var result []runtime.Object
for _, app := range all {
result = append(result, app)
for _, snapshot := range all {
result = append(result, snapshot)
}
return v1alpha3.DefaultList(result, query, v.compare, v.filter), nil
......@@ -93,10 +92,6 @@ func (v *volumeSnapshotGetter) filter(object runtime.Object, filter query.Filter
}
}
func (v *volumeSnapshotGetter) listVolumeSnapshots(namespace string, selector labels.Selector) (ret []*v1beta1.VolumeSnapshot, err error) {
return v.informer.Snapshot().V1beta1().VolumeSnapshots().Lister().VolumeSnapshots(namespace).List(selector)
}
func snapshotStatus(item *v1beta1.VolumeSnapshot) string {
status := statusCreating
if *item.Status.ReadyToUse {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册