This is an automated email from the ASF dual-hosted git repository.

zhangjintao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new a1ef639  feat: add ApisixPluginConfig controller loop and e2e test 
case (#815)
a1ef639 is described below

commit a1ef63963c5ee9f4b225e5e663ec060afd4da2f8
Author: nevercase <[email protected]>
AuthorDate: Wed Dec 29 22:20:27 2021 +0800

    feat: add ApisixPluginConfig controller loop and e2e test case (#815)
    
    Co-authored-by: Jintao Zhang <[email protected]>
---
 pkg/apisix/apisix.go                               |   2 +
 pkg/apisix/cache/memdb.go                          |  17 +
 pkg/apisix/cache/memdb_test.go                     |  20 +-
 pkg/apisix/cache/schema.go                         |   6 +
 pkg/apisix/cluster.go                              |  20 +
 pkg/apisix/cluster_test.go                         |   9 +
 pkg/apisix/nonexistentclient.go                    |   4 +
 pkg/ingress/apisix_pluginconfig.go                 | 329 +++++++++++++
 pkg/ingress/apisix_route.go                        |  53 +-
 pkg/ingress/compare.go                             |  71 ++-
 pkg/ingress/controller.go                          |  15 +-
 pkg/ingress/ingress.go                             |  17 +-
 pkg/ingress/manifest.go                            |  91 +++-
 pkg/ingress/manifest_test.go                       |  83 ++++
 pkg/ingress/status.go                              |  17 +
 pkg/kube/apisix/apis/config/v2beta3/types.go       |  15 +-
 .../apis/config/v2beta3/zz_generated.deepcopy.go   |   2 +-
 pkg/kube/apisix_plugin_config.go                   | 132 +++++
 pkg/kube/apisix_route.go                           |   6 +-
 pkg/kube/translation/apisix_pluginconfig.go        |  64 +++
 pkg/kube/translation/apisix_pluginconfig_test.go   | 114 +++++
 pkg/kube/translation/apisix_route.go               |  76 ++-
 pkg/kube/translation/apisix_route_test.go          |  14 +-
 pkg/kube/translation/context.go                    |  21 +-
 pkg/kube/translation/context_test.go               |  22 +-
 pkg/kube/translation/ingress.go                    |  65 ++-
 pkg/kube/translation/ingress_test.go               |  44 +-
 pkg/kube/translation/translator.go                 |  22 +-
 pkg/types/apisix/v1/types.go                       |  31 +-
 samples/deploy/crd/v1/ApisixPluginConfig.yaml      |   1 +
 samples/deploy/crd/v1/ApisixRoute.yaml             |   3 +
 samples/deploy/rbac/apisix_view_clusterrole.yaml   |   4 +-
 test/e2e/ingress/resourcepushing.go                |  45 +-
 test/e2e/plugins/plugin_config.go                  | 546 +++++++++++++++++++++
 test/e2e/scaffold/ingress.go                       |   4 +-
 test/e2e/scaffold/k8s.go                           |  33 ++
 36 files changed, 1887 insertions(+), 131 deletions(-)

diff --git a/pkg/apisix/apisix.go b/pkg/apisix/apisix.go
index 838b286..d8cfb2d 100644
--- a/pkg/apisix/apisix.go
+++ b/pkg/apisix/apisix.go
@@ -59,6 +59,8 @@ type Cluster interface {
        HealthCheck(context.Context) error
        // Plugin returns a Plugin interface that can operate Plugin resources.
        Plugin() Plugin
+       // PluginConfig returns a PluginConfig interface that can operate 
PluginConfig resources.
+       PluginConfig() PluginConfig
        // Schema returns a Schema interface that can fetch schema of APISIX 
objects.
        Schema() Schema
 }
diff --git a/pkg/apisix/cache/memdb.go b/pkg/apisix/cache/memdb.go
index 4557d13..bf21818 100644
--- a/pkg/apisix/cache/memdb.go
+++ b/pkg/apisix/cache/memdb.go
@@ -310,6 +310,9 @@ func (c *dbCache) DeleteSchema(schema *v1.Schema) error {
 }
 
 func (c *dbCache) DeletePluginConfig(pc *v1.PluginConfig) error {
+       if err := c.checkPluginConfigReference(pc); err != nil {
+               return err
+       }
        return c.delete("plugin_config", pc)
 }
 
@@ -347,3 +350,17 @@ func (c *dbCache) checkUpstreamReference(u *v1.Upstream) 
error {
        }
        return nil
 }
+
+func (c *dbCache) checkPluginConfigReference(u *v1.PluginConfig) error {
+       // PluginConfig is referenced by Route.
+       txn := c.db.Txn(false)
+       defer txn.Abort()
+       obj, err := txn.First("route", "plugin_config_id", u.ID)
+       if err != nil && err != memdb.ErrNotFound {
+               return err
+       }
+       if obj != nil {
+               return ErrStillInUse
+       }
+       return nil
+}
diff --git a/pkg/apisix/cache/memdb_test.go b/pkg/apisix/cache/memdb_test.go
index 25b731f..b6ee313 100644
--- a/pkg/apisix/cache/memdb_test.go
+++ b/pkg/apisix/cache/memdb_test.go
@@ -18,6 +18,7 @@ package cache
 import (
        "testing"
 
+       "github.com/hashicorp/go-memdb"
        "github.com/stretchr/testify/assert"
 
        v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
@@ -183,7 +184,8 @@ func TestMemDBCacheReference(t *testing.T) {
                        Name: "route",
                        ID:   "1",
                },
-               UpstreamId: "1",
+               UpstreamId:     "1",
+               PluginConfigId: "1",
        }
        u := &v1.Upstream{
                Metadata: v1.Metadata{
@@ -201,6 +203,18 @@ func TestMemDBCacheReference(t *testing.T) {
                ID:         "1",
                UpstreamId: "2",
        }
+       pc := &v1.PluginConfig{
+               Metadata: v1.Metadata{
+                       ID:   "1",
+                       Name: "pluginConfig",
+               },
+       }
+       pc2 := &v1.PluginConfig{
+               Metadata: v1.Metadata{
+                       ID:   "2",
+                       Name: "pluginConfig",
+               },
+       }
 
        db, err := NewMemDBCache()
        assert.Nil(t, err, "NewMemDBCache")
@@ -208,13 +222,17 @@ func TestMemDBCacheReference(t *testing.T) {
        assert.Nil(t, db.InsertUpstream(u))
        assert.Nil(t, db.InsertStreamRoute(sr))
        assert.Nil(t, db.InsertUpstream(u2))
+       assert.Nil(t, db.InsertPluginConfig(pc))
 
        assert.Error(t, ErrStillInUse, db.DeleteUpstream(u))
        assert.Error(t, ErrStillInUse, db.DeleteUpstream(u2))
+       assert.Error(t, ErrStillInUse, db.DeletePluginConfig(pc))
+       assert.Equal(t, memdb.ErrNotFound, db.DeletePluginConfig(pc2))
        assert.Nil(t, db.DeleteRoute(r))
        assert.Nil(t, db.DeleteUpstream(u))
        assert.Nil(t, db.DeleteStreamRoute(sr))
        assert.Nil(t, db.DeleteUpstream(u2))
+       assert.Nil(t, db.DeletePluginConfig(pc))
 }
 
 func TestMemDBCacheStreamRoute(t *testing.T) {
diff --git a/pkg/apisix/cache/schema.go b/pkg/apisix/cache/schema.go
index 66c3588..e4ef348 100644
--- a/pkg/apisix/cache/schema.go
+++ b/pkg/apisix/cache/schema.go
@@ -42,6 +42,12 @@ var (
                                                Indexer:      
&memdb.StringFieldIndex{Field: "UpstreamId"},
                                                AllowMissing: true,
                                        },
+                                       "plugin_config_id": {
+                                               Name:         
"plugin_config_id",
+                                               Unique:       false,
+                                               Indexer:      
&memdb.StringFieldIndex{Field: "PluginConfigId"},
+                                               AllowMissing: true,
+                                       },
                                },
                        },
                        "upstream": {
diff --git a/pkg/apisix/cluster.go b/pkg/apisix/cluster.go
index 0106dec..4a5dbd7 100644
--- a/pkg/apisix/cluster.go
+++ b/pkg/apisix/cluster.go
@@ -233,6 +233,11 @@ func (c *cluster) syncCacheOnce(ctx context.Context) 
(bool, error) {
                log.Errorf("failed to list consumers in APISIX: %s", err)
                return false, err
        }
+       pluginConfigs, err := c.pluginConfig.List(ctx)
+       if err != nil {
+               log.Errorf("failed to list plugin_configs in APISIX: %s", err)
+               return false, err
+       }
 
        for _, r := range routes {
                if err := c.cache.InsertRoute(r); err != nil {
@@ -293,6 +298,16 @@ func (c *cluster) syncCacheOnce(ctx context.Context) 
(bool, error) {
                        )
                }
        }
+       for _, u := range pluginConfigs {
+               if err := c.cache.InsertPluginConfig(u); err != nil {
+                       log.Errorw("failed to insert pluginConfig to cache",
+                               zap.String("pluginConfig", u.ID),
+                               zap.String("cluster", c.name),
+                               zap.String("error", err.Error()),
+                       )
+                       return false, err
+               }
+       }
        return true, nil
 }
 
@@ -433,6 +448,11 @@ func (c *cluster) Plugin() Plugin {
        return c.plugin
 }
 
+// PluginConfig implements Cluster.PluginConfig method.
+func (c *cluster) PluginConfig() PluginConfig {
+       return c.pluginConfig
+}
+
 // Schema implements Cluster.Schema method.
 func (c *cluster) Schema() Schema {
        return c.schema
diff --git a/pkg/apisix/cluster_test.go b/pkg/apisix/cluster_test.go
index cd6f323..51d970f 100644
--- a/pkg/apisix/cluster_test.go
+++ b/pkg/apisix/cluster_test.go
@@ -92,4 +92,13 @@ func TestNonExistentCluster(t *testing.T) {
        assert.Equal(t, ErrClusterNotExist, err)
        err = 
apisix.Cluster("non-existent-cluster").SSL().Delete(context.Background(), 
&v1.Ssl{})
        assert.Equal(t, ErrClusterNotExist, err)
+
+       _, err = 
apisix.Cluster("non-existent-cluster").PluginConfig().List(context.Background())
+       assert.Equal(t, ErrClusterNotExist, err)
+       _, err = 
apisix.Cluster("non-existent-cluster").PluginConfig().Create(context.Background(),
 &v1.PluginConfig{})
+       assert.Equal(t, ErrClusterNotExist, err)
+       _, err = 
apisix.Cluster("non-existent-cluster").PluginConfig().Update(context.Background(),
 &v1.PluginConfig{})
+       assert.Equal(t, ErrClusterNotExist, err)
+       err = 
apisix.Cluster("non-existent-cluster").PluginConfig().Delete(context.Background(),
 &v1.PluginConfig{})
+       assert.Equal(t, ErrClusterNotExist, err)
 }
diff --git a/pkg/apisix/nonexistentclient.go b/pkg/apisix/nonexistentclient.go
index 445ff62..c844480 100644
--- a/pkg/apisix/nonexistentclient.go
+++ b/pkg/apisix/nonexistentclient.go
@@ -268,6 +268,10 @@ func (nc *nonExistentCluster) Plugin() Plugin {
        return nc.plugin
 }
 
+func (nc *nonExistentCluster) PluginConfig() PluginConfig {
+       return nc.pluginConfig
+}
+
 func (nc *nonExistentCluster) Schema() Schema {
        return nc.schema
 }
diff --git a/pkg/ingress/apisix_pluginconfig.go 
b/pkg/ingress/apisix_pluginconfig.go
new file mode 100644
index 0000000..6b79299
--- /dev/null
+++ b/pkg/ingress/apisix_pluginconfig.go
@@ -0,0 +1,329 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 ingress
+
+import (
+       "context"
+       "time"
+
+       "go.uber.org/zap"
+       v1 "k8s.io/api/core/v1"
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/client-go/tools/cache"
+       "k8s.io/client-go/util/workqueue"
+
+       "github.com/apache/apisix-ingress-controller/pkg/kube"
+       "github.com/apache/apisix-ingress-controller/pkg/kube/translation"
+       "github.com/apache/apisix-ingress-controller/pkg/log"
+       "github.com/apache/apisix-ingress-controller/pkg/types"
+)
+
+type apisixPluginConfigController struct {
+       controller *Controller
+       workqueue  workqueue.RateLimitingInterface
+       workers    int
+}
+
+func (c *Controller) newApisixPluginConfigController() 
*apisixPluginConfigController {
+       ctl := &apisixPluginConfigController{
+               controller: c,
+               workqueue:  
workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second,
 60*time.Second, 5), "ApisixPluginConfig"),
+               workers:    1,
+       }
+       c.apisixPluginConfigInformer.AddEventHandler(
+               cache.ResourceEventHandlerFuncs{
+                       AddFunc:    ctl.onAdd,
+                       UpdateFunc: ctl.onUpdate,
+                       DeleteFunc: ctl.onDelete,
+               },
+       )
+       return ctl
+}
+
+func (c *apisixPluginConfigController) run(ctx context.Context) {
+       log.Info("ApisixPluginConfig controller started")
+       defer log.Info("ApisixPluginConfig controller exited")
+       defer c.workqueue.ShutDown()
+
+       ok := cache.WaitForCacheSync(ctx.Done(), 
c.controller.apisixPluginConfigInformer.HasSynced)
+       if !ok {
+               log.Error("cache sync failed")
+               return
+       }
+
+       for i := 0; i < c.workers; i++ {
+               go c.runWorker(ctx)
+       }
+       <-ctx.Done()
+}
+
+func (c *apisixPluginConfigController) runWorker(ctx context.Context) {
+       for {
+               obj, quit := c.workqueue.Get()
+               if quit {
+                       return
+               }
+               err := c.sync(ctx, obj.(*types.Event))
+               c.workqueue.Done(obj)
+               c.handleSyncErr(obj, err)
+       }
+}
+
+func (c *apisixPluginConfigController) sync(ctx context.Context, ev 
*types.Event) error {
+       obj := ev.Object.(kube.ApisixPluginConfigEvent)
+       namespace, name, err := cache.SplitMetaNamespaceKey(obj.Key)
+       if err != nil {
+               log.Errorf("invalid resource key: %s", obj.Key)
+               return err
+       }
+       var (
+               apc  kube.ApisixPluginConfig
+               tctx *translation.TranslateContext
+       )
+       switch obj.GroupVersion {
+       case kube.ApisixPluginConfigV2beta3:
+               apc, err = 
c.controller.apisixPluginConfigLister.V2beta3(namespace, name)
+       }
+       if err != nil {
+               if !k8serrors.IsNotFound(err) {
+                       log.Errorw("failed to get ApisixPluginConfig",
+                               zap.String("version", obj.GroupVersion),
+                               zap.String("key", obj.Key),
+                               zap.Error(err),
+                       )
+                       return err
+               }
+
+               if ev.Type != types.EventDelete {
+                       log.Warnw("ApisixPluginConfig was deleted before it can 
be delivered",
+                               zap.String("key", obj.Key),
+                               zap.String("version", obj.GroupVersion),
+                       )
+                       return nil
+               }
+       }
+       if ev.Type == types.EventDelete {
+               if apc != nil {
+                       // We still find the resource while we are processing 
the DELETE event,
+                       // that means object with same namespace and name was 
created, discarding
+                       // this stale DELETE event.
+                       log.Warnw("discard the stale ApisixPluginConfig delete 
event since the resource still exists",
+                               zap.String("key", obj.Key),
+                       )
+                       return nil
+               }
+               apc = ev.Tombstone.(kube.ApisixPluginConfig)
+       }
+
+       switch obj.GroupVersion {
+       case kube.ApisixPluginConfigV2beta3:
+               if ev.Type != types.EventDelete {
+                       tctx, err = 
c.controller.translator.TranslatePluginConfigV2beta3(apc.V2beta3())
+               } else {
+                       tctx, err = 
c.controller.translator.TranslatePluginConfigV2beta3NotStrictly(apc.V2beta3())
+               }
+               if err != nil {
+                       log.Errorw("failed to translate ApisixPluginConfig 
v2beta3",
+                               zap.Error(err),
+                               zap.Any("object", apc),
+                       )
+                       return err
+               }
+       }
+
+       log.Debugw("translated ApisixPluginConfig",
+               zap.Any("pluginConfigs", tctx.PluginConfigs),
+       )
+
+       m := &manifest{
+               pluginConfigs: tctx.PluginConfigs,
+       }
+
+       var (
+               added   *manifest
+               updated *manifest
+               deleted *manifest
+       )
+
+       if ev.Type == types.EventDelete {
+               deleted = m
+       } else if ev.Type == types.EventAdd {
+               added = m
+       } else {
+               var oldCtx *translation.TranslateContext
+               switch obj.GroupVersion {
+               case kube.ApisixPluginConfigV2beta3:
+                       oldCtx, err = 
c.controller.translator.TranslatePluginConfigV2beta3(obj.OldObject.V2beta3())
+               }
+               if err != nil {
+                       log.Errorw("failed to translate old ApisixPluginConfig",
+                               zap.String("version", obj.GroupVersion),
+                               zap.String("event", "update"),
+                               zap.Error(err),
+                               zap.Any("ApisixPluginConfig", apc),
+                       )
+                       return err
+               }
+
+               om := &manifest{
+                       pluginConfigs: oldCtx.PluginConfigs,
+               }
+               added, updated, deleted = m.diff(om)
+       }
+
+       return c.controller.syncManifests(ctx, added, updated, deleted)
+}
+
+func (c *apisixPluginConfigController) handleSyncErr(obj interface{}, 
errOrigin error) {
+       ev := obj.(*types.Event)
+       event := ev.Object.(kube.ApisixPluginConfigEvent)
+       namespace, name, errLocal := cache.SplitMetaNamespaceKey(event.Key)
+       if errLocal != nil {
+               log.Errorf("invalid resource key: %s", event.Key)
+               c.controller.MetricsCollector.IncrSyncOperation("PluginConfig", 
"failure")
+               return
+       }
+       var apc kube.ApisixPluginConfig
+       switch event.GroupVersion {
+       case kube.ApisixPluginConfigV2beta3:
+               apc, errLocal = 
c.controller.apisixPluginConfigLister.V2beta3(namespace, name)
+       }
+       if errOrigin == nil {
+               if ev.Type != types.EventDelete {
+                       if errLocal == nil {
+                               switch apc.GroupVersion() {
+                               case kube.ApisixPluginConfigV2beta3:
+                                       
c.controller.recorderEvent(apc.V2beta3(), v1.EventTypeNormal, _resourceSynced, 
nil)
+                                       
c.controller.recordStatus(apc.V2beta3(), _resourceSynced, nil, 
metav1.ConditionTrue, apc.V2beta3().GetGeneration())
+                               }
+                       } else {
+                               log.Errorw("failed list ApisixPluginConfig",
+                                       zap.Error(errLocal),
+                                       zap.String("name", name),
+                                       zap.String("namespace", namespace),
+                               )
+                       }
+               }
+               c.workqueue.Forget(obj)
+               c.controller.MetricsCollector.IncrSyncOperation("PluginConfig", 
"success")
+               return
+       }
+       log.Warnw("sync ApisixPluginConfig failed, will retry",
+               zap.Any("object", obj),
+               zap.Error(errOrigin),
+       )
+       if errLocal == nil {
+               switch apc.GroupVersion() {
+               case kube.ApisixPluginConfigV2beta3:
+                       c.controller.recorderEvent(apc.V2beta3(), 
v1.EventTypeWarning, _resourceSyncAborted, errOrigin)
+                       c.controller.recordStatus(apc.V2beta3(), 
_resourceSyncAborted, errOrigin, metav1.ConditionFalse, 
apc.V2beta3().GetGeneration())
+               }
+       } else {
+               log.Errorw("failed list ApisixPluginConfig",
+                       zap.Error(errLocal),
+                       zap.String("name", name),
+                       zap.String("namespace", namespace),
+               )
+       }
+       c.workqueue.AddRateLimited(obj)
+       c.controller.MetricsCollector.IncrSyncOperation("PluginConfig", 
"failure")
+}
+
+func (c *apisixPluginConfigController) onAdd(obj interface{}) {
+       key, err := cache.MetaNamespaceKeyFunc(obj)
+       if err != nil {
+               log.Errorf("found ApisixPluginConfig resource with bad meta 
namespace key: %s", err)
+               return
+       }
+       if !c.controller.namespaceWatching(key) {
+               return
+       }
+       log.Debugw("ApisixPluginConfig add event arrived",
+               zap.Any("object", obj))
+
+       apc := kube.MustNewApisixPluginConfig(obj)
+       c.workqueue.Add(&types.Event{
+               Type: types.EventAdd,
+               Object: kube.ApisixPluginConfigEvent{
+                       Key:          key,
+                       GroupVersion: apc.GroupVersion(),
+               },
+       })
+
+       c.controller.MetricsCollector.IncrEvents("PluginConfig", "add")
+}
+
+func (c *apisixPluginConfigController) onUpdate(oldObj, newObj interface{}) {
+       prev := kube.MustNewApisixPluginConfig(oldObj)
+       curr := kube.MustNewApisixPluginConfig(newObj)
+       if prev.ResourceVersion() >= curr.ResourceVersion() {
+               return
+       }
+       key, err := cache.MetaNamespaceKeyFunc(newObj)
+       if err != nil {
+               log.Errorf("found ApisixPluginConfig resource with bad meta 
namespace key: %s", err)
+               return
+       }
+       if !c.controller.namespaceWatching(key) {
+               return
+       }
+       log.Debugw("ApisixPluginConfig update event arrived",
+               zap.Any("new object", curr),
+               zap.Any("old object", prev),
+       )
+       c.workqueue.Add(&types.Event{
+               Type: types.EventUpdate,
+               Object: kube.ApisixPluginConfigEvent{
+                       Key:          key,
+                       GroupVersion: curr.GroupVersion(),
+                       OldObject:    prev,
+               },
+       })
+
+       c.controller.MetricsCollector.IncrEvents("PluginConfig", "update")
+}
+
+func (c *apisixPluginConfigController) onDelete(obj interface{}) {
+       apc, err := kube.NewApisixPluginConfig(obj)
+       if err != nil {
+               tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+               if !ok {
+                       return
+               }
+               apc = kube.MustNewApisixPluginConfig(tombstone)
+       }
+       key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
+       if err != nil {
+               log.Errorf("found ApisixPluginConfig resource with bad meta 
namesapce key: %s", err)
+               return
+       }
+       if !c.controller.namespaceWatching(key) {
+               return
+       }
+       log.Debugw("ApisixPluginConfig delete event arrived",
+               zap.Any("final state", apc),
+       )
+       c.workqueue.Add(&types.Event{
+               Type: types.EventDelete,
+               Object: kube.ApisixPluginConfigEvent{
+                       Key:          key,
+                       GroupVersion: apc.GroupVersion(),
+               },
+               Tombstone: apc,
+       })
+
+       c.controller.MetricsCollector.IncrEvents("PluginConfig", "delete")
+}
diff --git a/pkg/ingress/apisix_route.go b/pkg/ingress/apisix_route.go
index b46c99d..8d601a4 100644
--- a/pkg/ingress/apisix_route.go
+++ b/pkg/ingress/apisix_route.go
@@ -26,9 +26,11 @@ import (
        "k8s.io/client-go/util/workqueue"
 
        "github.com/apache/apisix-ingress-controller/pkg/kube"
+       
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
        "github.com/apache/apisix-ingress-controller/pkg/kube/translation"
        "github.com/apache/apisix-ingress-controller/pkg/log"
        "github.com/apache/apisix-ingress-controller/pkg/types"
+       apisixv1 
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 type apisixRouteController struct {
@@ -90,8 +92,9 @@ func (c *apisixRouteController) sync(ctx context.Context, ev 
*types.Event) error
                return err
        }
        var (
-               ar   kube.ApisixRoute
-               tctx *translation.TranslateContext
+               ar       kube.ApisixRoute
+               replaced *v2beta3.ApisixRoute
+               tctx     *translation.TranslateContext
        )
        switch obj.GroupVersion {
        case kube.ApisixRouteV2beta1:
@@ -161,7 +164,9 @@ func (c *apisixRouteController) sync(ctx context.Context, 
ev *types.Event) error
                }
        case kube.ApisixRouteV2beta3:
                if ev.Type != types.EventDelete {
-                       tctx, err = 
c.controller.translator.TranslateRouteV2beta3(ar.V2beta3())
+                       if replaced, err = 
c.replacePluginNameWithIdIfNotEmptyV2beta3(ctx, ar.V2beta3()); err == nil {
+                               tctx, err = 
c.controller.translator.TranslateRouteV2beta3(replaced)
+                       }
                } else {
                        tctx, err = 
c.controller.translator.TranslateRouteV2beta3NotStrictly(ar.V2beta3())
                }
@@ -178,12 +183,14 @@ func (c *apisixRouteController) sync(ctx context.Context, 
ev *types.Event) error
                zap.Any("routes", tctx.Routes),
                zap.Any("upstreams", tctx.Upstreams),
                zap.Any("apisix_route", ar),
+               zap.Any("pluginConfigs", tctx.PluginConfigs),
        )
 
        m := &manifest{
-               routes:       tctx.Routes,
-               upstreams:    tctx.Upstreams,
-               streamRoutes: tctx.StreamRoutes,
+               routes:        tctx.Routes,
+               upstreams:     tctx.Upstreams,
+               streamRoutes:  tctx.StreamRoutes,
+               pluginConfigs: tctx.PluginConfigs,
        }
 
        var (
@@ -204,7 +211,9 @@ func (c *apisixRouteController) sync(ctx context.Context, 
ev *types.Event) error
                case kube.ApisixRouteV2beta2:
                        oldCtx, err = 
c.controller.translator.TranslateRouteV2beta2(obj.OldObject.V2beta2())
                case kube.ApisixRouteV2beta3:
-                       oldCtx, err = 
c.controller.translator.TranslateRouteV2beta3(obj.OldObject.V2beta3())
+                       if replaced, err = 
c.replacePluginNameWithIdIfNotEmptyV2beta3(ctx, obj.OldObject.V2beta3()); err 
== nil {
+                               oldCtx, err = 
c.controller.translator.TranslateRouteV2beta3(replaced)
+                       }
                }
                if err != nil {
                        log.Errorw("failed to translate old ApisixRoute",
@@ -217,9 +226,10 @@ func (c *apisixRouteController) sync(ctx context.Context, 
ev *types.Event) error
                }
 
                om := &manifest{
-                       routes:       oldCtx.Routes,
-                       upstreams:    oldCtx.Upstreams,
-                       streamRoutes: oldCtx.StreamRoutes,
+                       routes:        oldCtx.Routes,
+                       upstreams:     oldCtx.Upstreams,
+                       streamRoutes:  oldCtx.StreamRoutes,
+                       pluginConfigs: oldCtx.PluginConfigs,
                }
                added, updated, deleted = m.diff(om)
        }
@@ -227,6 +237,29 @@ func (c *apisixRouteController) sync(ctx context.Context, 
ev *types.Event) error
        return c.controller.syncManifests(ctx, added, updated, deleted)
 }
 
+func (c *apisixRouteController) replacePluginNameWithIdIfNotEmptyV2beta3(ctx 
context.Context, in *v2beta3.ApisixRoute) (*v2beta3.ApisixRoute, error) {
+       clusterName := c.controller.cfg.APISIX.DefaultClusterName
+       news := make([]v2beta3.ApisixRouteHTTP, 0)
+       for _, v := range in.Spec.HTTP {
+               pluginConfigId := ""
+               if v.PluginConfigName != "" {
+                       pc, err := 
c.controller.apisix.Cluster(clusterName).PluginConfig().Get(ctx, 
apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName))
+                       if err != nil {
+                               
log.Errorw("replacePluginNameWithIdIfNotEmptyV2beta3 error:  plugin_config not 
found",
+                                       zap.String("name", 
apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName)),
+                                       zap.Any("obj", in),
+                                       zap.Error(err))
+                       } else {
+                               pluginConfigId = pc.ID
+                       }
+               }
+               v.PluginConfigName = pluginConfigId
+               news = append(news, v)
+       }
+       in.Spec.HTTP = news
+       return in, nil
+}
+
 func (c *apisixRouteController) handleSyncErr(obj interface{}, errOrigin 
error) {
        ev := obj.(*types.Event)
        event := ev.Object.(kube.ApisixRouteEvent)
diff --git a/pkg/ingress/compare.go b/pkg/ingress/compare.go
index 227d284..fa870b2 100644
--- a/pkg/ingress/compare.go
+++ b/pkg/ingress/compare.go
@@ -31,18 +31,20 @@ import (
 // cc 
https://github.com/apache/apisix-ingress-controller/pull/742#discussion_r757197791
 func (c *Controller) CompareResources(ctx context.Context) error {
        var (
-               wg                sync.WaitGroup
-               routeMapK8S       = new(sync.Map)
-               streamRouteMapK8S = new(sync.Map)
-               upstreamMapK8S    = new(sync.Map)
-               sslMapK8S         = new(sync.Map)
-               consumerMapK8S    = new(sync.Map)
-
-               routeMapA6       = make(map[string]string)
-               streamRouteMapA6 = make(map[string]string)
-               upstreamMapA6    = make(map[string]string)
-               sslMapA6         = make(map[string]string)
-               consumerMapA6    = make(map[string]string)
+               wg                 sync.WaitGroup
+               routeMapK8S        = new(sync.Map)
+               streamRouteMapK8S  = new(sync.Map)
+               upstreamMapK8S     = new(sync.Map)
+               sslMapK8S          = new(sync.Map)
+               consumerMapK8S     = new(sync.Map)
+               pluginConfigMapK8S = new(sync.Map)
+
+               routeMapA6        = make(map[string]string)
+               streamRouteMapA6  = make(map[string]string)
+               upstreamMapA6     = make(map[string]string)
+               sslMapA6          = make(map[string]string)
+               consumerMapA6     = make(map[string]string)
+               pluginConfigMapA6 = make(map[string]string)
        )
        // watchingNamespace == nil means to monitor all namespaces
        if !validation.HasValueInSyncMap(c.watchingNamespace) {
@@ -94,11 +96,15 @@ func (c *Controller) CompareResources(ctx context.Context) 
error {
                                                for _, ssl := range tc.SSL {
                                                        sslMapK8S.Store(ssl.ID, 
ssl.ID)
                                                }
+                                               // pluginConfigs
+                                               for _, pluginConfig := range 
tc.PluginConfigs {
+                                                       
pluginConfigMapK8S.Store(pluginConfig.ID, pluginConfig.ID)
+                                               }
                                        }
                                }
                        }
-                       // todo ApisixUpstream
-                       // ApisixUpstream should be synced with ApisixRoute 
resource
+                       // todo ApisixUpstream and ApisixPluginConfig
+                       // ApisixUpstream and ApisixPluginConfig should be 
synced with ApisixRoute resource
 
                        // ApisixSSL
                        retSSL, err := 
c.kubeClient.APISIXClient.ApisixV2beta3().ApisixTlses(ns).List(ctx, opts)
@@ -153,18 +159,23 @@ func (c *Controller) CompareResources(ctx 
context.Context) error {
        if err := c.listConsumerCache(ctx, consumerMapA6); err != nil {
                return err
        }
+       if err := c.listPluginConfigCache(ctx, pluginConfigMapA6); err != nil {
+               return err
+       }
        // 3.compare
-       routeReult := findRedundant(routeMapA6, routeMapK8S)
-       streamRouteReult := findRedundant(streamRouteMapA6, streamRouteMapK8S)
-       upstreamReult := findRedundant(upstreamMapA6, upstreamMapK8S)
-       sslReult := findRedundant(sslMapA6, sslMapK8S)
-       consuemrReult := findRedundant(consumerMapA6, consumerMapK8S)
+       routeResult := findRedundant(routeMapA6, routeMapK8S)
+       streamRouteResult := findRedundant(streamRouteMapA6, streamRouteMapK8S)
+       upstreamResult := findRedundant(upstreamMapA6, upstreamMapK8S)
+       sslResult := findRedundant(sslMapA6, sslMapK8S)
+       consumerResult := findRedundant(consumerMapA6, consumerMapK8S)
+       pluginConfigResult := findRedundant(pluginConfigMapA6, 
pluginConfigMapK8S)
        // 4.warn
-       warnRedundantResources(routeReult, "route")
-       warnRedundantResources(streamRouteReult, "streamRoute")
-       warnRedundantResources(upstreamReult, "upstream")
-       warnRedundantResources(sslReult, "ssl")
-       warnRedundantResources(consuemrReult, "consumer")
+       warnRedundantResources(routeResult, "route")
+       warnRedundantResources(streamRouteResult, "streamRoute")
+       warnRedundantResources(upstreamResult, "upstream")
+       warnRedundantResources(sslResult, "ssl")
+       warnRedundantResources(consumerResult, "consumer")
+       warnRedundantResources(pluginConfigResult, "pluginConfig")
 
        return nil
 }
@@ -247,3 +258,15 @@ func (c *Controller) listConsumerCache(ctx 
context.Context, consumerMapA6 map[st
        }
        return nil
 }
+
+func (c *Controller) listPluginConfigCache(ctx context.Context, 
pluginConfigMapA6 map[string]string) error {
+       pluginConfigInA6, err := 
c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).PluginConfig().List(ctx)
+       if err != nil {
+               return err
+       } else {
+               for _, ra := range pluginConfigInA6 {
+                       pluginConfigMapA6[ra.ID] = ra.ID
+               }
+       }
+       return nil
+}
diff --git a/pkg/ingress/controller.go b/pkg/ingress/controller.go
index b7686ee..b519b75 100644
--- a/pkg/ingress/controller.go
+++ b/pkg/ingress/controller.go
@@ -115,6 +115,8 @@ type Controller struct {
        apisixClusterConfigInformer cache.SharedIndexInformer
        apisixConsumerInformer      cache.SharedIndexInformer
        apisixConsumerLister        listersv2beta3.ApisixConsumerLister
+       apisixPluginConfigInformer  cache.SharedIndexInformer
+       apisixPluginConfigLister    kube.ApisixPluginConfigLister
        gatewayInformer             cache.SharedIndexInformer
        gatewayLister               gatewaylistersv1alpha2.GatewayLister
 
@@ -132,6 +134,7 @@ type Controller struct {
        apisixTlsController           *apisixTlsController
        apisixClusterConfigController *apisixClusterConfigController
        apisixConsumerController      *apisixConsumerController
+       apisixPluginConfigController  *apisixPluginConfigController
 }
 
 // NewController creates an ingress apisix controller object.
@@ -224,6 +227,9 @@ func (c *Controller) initWhenStartLeading() {
        c.apisixTlsLister = 
apisixFactory.Apisix().V2beta3().ApisixTlses().Lister()
        c.apisixClusterConfigLister = 
apisixFactory.Apisix().V2beta3().ApisixClusterConfigs().Lister()
        c.apisixConsumerLister = 
apisixFactory.Apisix().V2beta3().ApisixConsumers().Lister()
+       c.apisixPluginConfigLister = kube.NewApisixPluginConfigLister(
+               apisixFactory.Apisix().V2beta3().ApisixPluginConfigs().Lister(),
+       )
 
        c.translator = translation.NewTranslator(&translation.TranslatorOptions{
                PodCache:             c.podCache,
@@ -265,6 +271,7 @@ func (c *Controller) initWhenStartLeading() {
        c.secretInformer = kubeFactory.Core().V1().Secrets().Informer()
        c.apisixTlsInformer = 
apisixFactory.Apisix().V2beta3().ApisixTlses().Informer()
        c.apisixConsumerInformer = 
apisixFactory.Apisix().V2beta3().ApisixConsumers().Informer()
+       c.apisixPluginConfigInformer = 
apisixFactory.Apisix().V2beta3().ApisixPluginConfigs().Informer()
 
        if c.cfg.Kubernetes.WatchEndpointSlices {
                c.endpointSliceController = c.newEndpointSliceController()
@@ -280,6 +287,7 @@ func (c *Controller) initWhenStartLeading() {
        c.apisixTlsController = c.newApisixTlsController()
        c.secretController = c.newSecretController()
        c.apisixConsumerController = c.newApisixConsumerController()
+       c.apisixPluginConfigController = c.newApisixPluginConfigController()
        c.gatewayController = c.newGatewayController()
 }
 
@@ -467,7 +475,6 @@ func (c *Controller) run(ctx context.Context) {
                c.ingressInformer.Run(ctx.Done())
        })
        c.goAttach(func() {
-
                c.apisixRouteInformer.Run(ctx.Done())
        })
        c.goAttach(func() {
@@ -486,6 +493,9 @@ func (c *Controller) run(ctx context.Context) {
                c.apisixConsumerInformer.Run(ctx.Done())
        })
        c.goAttach(func() {
+               c.apisixPluginConfigInformer.Run(ctx.Done())
+       })
+       c.goAttach(func() {
                c.namespaceController.run(ctx)
        })
        c.goAttach(func() {
@@ -530,6 +540,9 @@ func (c *Controller) run(ctx context.Context) {
        c.goAttach(func() {
                c.apisixConsumerController.run(ctx)
        })
+       c.goAttach(func() {
+               c.apisixPluginConfigController.run(ctx)
+       })
 
        c.MetricsCollector.ResetLeader(true)
 
diff --git a/pkg/ingress/ingress.go b/pkg/ingress/ingress.go
index b4d69a8..b5ffbb6 100644
--- a/pkg/ingress/ingress.go
+++ b/pkg/ingress/ingress.go
@@ -135,17 +135,19 @@ func (c *ingressController) sync(ctx context.Context, ev 
*types.Event) error {
                return err
        }
 
-       log.Debugw("translated ingress resource to a couple of routes and 
upstreams",
+       log.Debugw("translated ingress resource to a couple of routes, 
upstreams and pluginConfigs",
                zap.Any("ingress", ing),
                zap.Any("routes", tctx.Routes),
                zap.Any("upstreams", tctx.Upstreams),
                zap.Any("ssl", tctx.SSL),
+               zap.Any("pluginConfigs", tctx.PluginConfigs),
        )
 
        m := &manifest{
-               ssl:       tctx.SSL,
-               routes:    tctx.Routes,
-               upstreams: tctx.Upstreams,
+               ssl:           tctx.SSL,
+               routes:        tctx.Routes,
+               upstreams:     tctx.Upstreams,
+               pluginConfigs: tctx.PluginConfigs,
        }
 
        var (
@@ -169,9 +171,10 @@ func (c *ingressController) sync(ctx context.Context, ev 
*types.Event) error {
                        return err
                }
                om := &manifest{
-                       routes:    oldCtx.Routes,
-                       upstreams: oldCtx.Upstreams,
-                       ssl:       oldCtx.SSL,
+                       routes:        oldCtx.Routes,
+                       upstreams:     oldCtx.Upstreams,
+                       ssl:           oldCtx.SSL,
+                       pluginConfigs: oldCtx.PluginConfigs,
                }
                added, updated, deleted = m.diff(om)
        }
diff --git a/pkg/ingress/manifest.go b/pkg/ingress/manifest.go
index 2d82a30..bf4edfa 100644
--- a/pkg/ingress/manifest.go
+++ b/pkg/ingress/manifest.go
@@ -140,11 +140,37 @@ func diffStreamRoutes(olds, news []*apisixv1.StreamRoute) 
(added, updated, delet
        return
 }
 
+func diffPluginConfigs(olds, news []*apisixv1.PluginConfig) (added, updated, 
deleted []*apisixv1.PluginConfig) {
+       oldMap := make(map[string]*apisixv1.PluginConfig, len(olds))
+       newMap := make(map[string]*apisixv1.PluginConfig, len(news))
+       for _, sr := range olds {
+               oldMap[sr.ID] = sr
+       }
+       for _, sr := range news {
+               newMap[sr.ID] = sr
+       }
+
+       for _, sr := range news {
+               if ou, ok := oldMap[sr.ID]; !ok {
+                       added = append(added, sr)
+               } else if !reflect.DeepEqual(ou, sr) {
+                       updated = append(updated, sr)
+               }
+       }
+       for _, sr := range olds {
+               if _, ok := newMap[sr.ID]; !ok {
+                       deleted = append(deleted, sr)
+               }
+       }
+       return
+}
+
 type manifest struct {
-       routes       []*apisixv1.Route
-       upstreams    []*apisixv1.Upstream
-       streamRoutes []*apisixv1.StreamRoute
-       ssl          []*apisixv1.Ssl
+       routes        []*apisixv1.Route
+       upstreams     []*apisixv1.Upstream
+       streamRoutes  []*apisixv1.StreamRoute
+       ssl           []*apisixv1.Ssl
+       pluginConfigs []*apisixv1.PluginConfig
 }
 
 func (m *manifest) diff(om *manifest) (added, updated, deleted *manifest) {
@@ -152,29 +178,33 @@ func (m *manifest) diff(om *manifest) (added, updated, 
deleted *manifest) {
        ar, ur, dr := diffRoutes(om.routes, m.routes)
        au, uu, du := diffUpstreams(om.upstreams, m.upstreams)
        asr, usr, dsr := diffStreamRoutes(om.streamRoutes, m.streamRoutes)
+       apc, upc, dpc := diffPluginConfigs(om.pluginConfigs, m.pluginConfigs)
 
-       if ar != nil || au != nil || asr != nil || sa != nil {
+       if ar != nil || au != nil || asr != nil || sa != nil || apc != nil {
                added = &manifest{
-                       routes:       ar,
-                       upstreams:    au,
-                       streamRoutes: asr,
-                       ssl:          sa,
+                       routes:        ar,
+                       upstreams:     au,
+                       streamRoutes:  asr,
+                       ssl:           sa,
+                       pluginConfigs: apc,
                }
        }
-       if ur != nil || uu != nil || usr != nil || su != nil {
+       if ur != nil || uu != nil || usr != nil || su != nil || upc != nil {
                updated = &manifest{
-                       routes:       ur,
-                       upstreams:    uu,
-                       streamRoutes: usr,
-                       ssl:          su,
+                       routes:        ur,
+                       upstreams:     uu,
+                       streamRoutes:  usr,
+                       ssl:           su,
+                       pluginConfigs: upc,
                }
        }
-       if dr != nil || du != nil || dsr != nil || sd != nil {
+       if dr != nil || du != nil || dsr != nil || sd != nil || dpc != nil {
                deleted = &manifest{
-                       routes:       dr,
-                       upstreams:    du,
-                       streamRoutes: dsr,
-                       ssl:          sd,
+                       routes:        dr,
+                       upstreams:     du,
+                       streamRoutes:  dsr,
+                       ssl:           sd,
+                       pluginConfigs: dpc,
                }
        }
        return
@@ -213,6 +243,19 @@ func (c *Controller) syncManifests(ctx context.Context, 
added, updated, deleted
                                }
                        }
                }
+               for _, pc := range deleted.pluginConfigs {
+                       if err := 
c.apisix.Cluster(clusterName).PluginConfig().Delete(ctx, pc); err != nil {
+                               // pluginConfig might be referenced by other 
routes.
+                               if err != cache.ErrStillInUse {
+                                       merr = multierror.Append(merr, err)
+                               } else {
+                                       log.Infow("plugin_config was referenced 
by other routes",
+                                               zap.String("plugin_config_id", 
pc.ID),
+                                               
zap.String("plugin_config_name", pc.Name),
+                                       )
+                               }
+                       }
+               }
        }
        if added != nil {
                // Should create upstreams firstly due to the dependencies.
@@ -226,6 +269,11 @@ func (c *Controller) syncManifests(ctx context.Context, 
added, updated, deleted
                                merr = multierror.Append(merr, err)
                        }
                }
+               for _, pc := range added.pluginConfigs {
+                       if _, err := 
c.apisix.Cluster(clusterName).PluginConfig().Create(ctx, pc); err != nil {
+                               merr = multierror.Append(merr, err)
+                       }
+               }
                for _, r := range added.routes {
                        if _, err := 
c.apisix.Cluster(clusterName).Route().Create(ctx, r); err != nil {
                                merr = multierror.Append(merr, err)
@@ -248,6 +296,11 @@ func (c *Controller) syncManifests(ctx context.Context, 
added, updated, deleted
                                merr = multierror.Append(merr, err)
                        }
                }
+               for _, pc := range updated.pluginConfigs {
+                       if _, err := 
c.apisix.Cluster(clusterName).PluginConfig().Update(ctx, pc); err != nil {
+                               merr = multierror.Append(merr, err)
+                       }
+               }
                for _, r := range updated.routes {
                        if _, err := 
c.apisix.Cluster(clusterName).Route().Update(ctx, r); err != nil {
                                merr = multierror.Append(merr, err)
diff --git a/pkg/ingress/manifest_test.go b/pkg/ingress/manifest_test.go
index 847e179..9bad334 100644
--- a/pkg/ingress/manifest_test.go
+++ b/pkg/ingress/manifest_test.go
@@ -180,6 +180,69 @@ func TestDiffUpstreams(t *testing.T) {
        assert.Equal(t, "2", deleted[0].ID)
 }
 
+func TestDiffPluginConfigs(t *testing.T) {
+       news := []*apisixv1.PluginConfig{
+               {
+                       Metadata: apisixv1.Metadata{
+                               ID: "1",
+                       },
+               },
+               {
+                       Metadata: apisixv1.Metadata{
+                               ID: "3",
+                       },
+                       Plugins: map[string]interface{}{
+                               "key-1": 123456,
+                       },
+               },
+       }
+       added, updated, deleted := diffPluginConfigs(nil, news)
+       assert.Nil(t, updated)
+       assert.Nil(t, deleted)
+       assert.Len(t, added, 2)
+       assert.Equal(t, "1", added[0].ID)
+       assert.Equal(t, "3", added[1].ID)
+       assert.Equal(t, news[1].Plugins, added[1].Plugins)
+
+       olds := []*apisixv1.PluginConfig{
+               {
+                       Metadata: apisixv1.Metadata{
+                               ID: "2",
+                       },
+               },
+               {
+                       Metadata: apisixv1.Metadata{
+                               ID: "3",
+                       },
+                       Plugins: map[string]interface{}{
+                               "key-1": 123456789,
+                               "key-2": map[string][]string{
+                                       "whitelist": {
+                                               "127.0.0.0/24",
+                                               "113.74.26.106",
+                                       },
+                               },
+                       },
+               },
+       }
+       added, updated, deleted = diffPluginConfigs(olds, nil)
+       assert.Nil(t, updated)
+       assert.Nil(t, added)
+       assert.Len(t, deleted, 2)
+       assert.Equal(t, "2", deleted[0].ID)
+       assert.Equal(t, "3", deleted[1].ID)
+       assert.Equal(t, olds[1].Plugins, deleted[1].Plugins)
+
+       added, updated, deleted = diffPluginConfigs(olds, news)
+       assert.Len(t, added, 1)
+       assert.Equal(t, "1", added[0].ID)
+       assert.Len(t, updated, 1)
+       assert.Equal(t, "3", updated[0].ID)
+       assert.Len(t, updated[0].Plugins, 1)
+       assert.Len(t, deleted, 1)
+       assert.Equal(t, "2", deleted[0].ID)
+}
+
 func TestManifestDiff(t *testing.T) {
        retries := 2
        m := &manifest{
@@ -204,6 +267,22 @@ func TestManifestDiff(t *testing.T) {
                                Retries: &retries,
                        },
                },
+               pluginConfigs: []*apisixv1.PluginConfig{
+                       {
+                               Metadata: apisixv1.Metadata{
+                                       ID: "5",
+                               },
+                               Plugins: map[string]interface{}{
+                                       "key-1": 123456789,
+                                       "key-2": map[string][]string{
+                                               "whitelist": {
+                                                       "127.0.0.0/24",
+                                                       "113.74.26.106",
+                                               },
+                                       },
+                               },
+                       },
+               },
        }
        om := &manifest{
                routes: []*apisixv1.Route{
@@ -226,13 +305,17 @@ func TestManifestDiff(t *testing.T) {
        assert.Equal(t, "1", added.routes[0].ID)
        assert.Len(t, added.upstreams, 1)
        assert.Equal(t, "4", added.upstreams[0].ID)
+       assert.Len(t, added.pluginConfigs, 1)
+       assert.Equal(t, "5", added.pluginConfigs[0].ID)
 
        assert.Len(t, updated.routes, 1)
        assert.Equal(t, "3", updated.routes[0].ID)
        assert.Equal(t, []string{"GET"}, updated.routes[0].Methods)
        assert.Nil(t, updated.upstreams)
+       assert.Nil(t, updated.pluginConfigs)
 
        assert.Len(t, deleted.routes, 1)
        assert.Equal(t, "2", deleted.routes[0].ID)
        assert.Nil(t, updated.upstreams)
+       assert.Nil(t, updated.pluginConfigs)
 }
diff --git a/pkg/ingress/status.go b/pkg/ingress/status.go
index 09f3125..cda58a5 100644
--- a/pkg/ingress/status.go
+++ b/pkg/ingress/status.go
@@ -173,6 +173,23 @@ func (c *Controller) recordStatus(at interface{}, reason 
string, err error, stat
                                )
                        }
                }
+       case *configv2beta3.ApisixPluginConfig:
+               // set to status
+               if v.Status.Conditions == nil {
+                       conditions := make([]metav1.Condition, 0)
+                       v.Status.Conditions = conditions
+               }
+               if c.verifyGeneration(&v.Status.Conditions, condition) {
+                       meta.SetStatusCondition(&v.Status.Conditions, condition)
+                       if _, errRecord := 
client.ApisixV2beta3().ApisixPluginConfigs(v.Namespace).
+                               UpdateStatus(context.TODO(), v, 
metav1.UpdateOptions{}); errRecord != nil {
+                               log.Errorw("failed to record status change for 
ApisixPluginConfig",
+                                       zap.Error(errRecord),
+                                       zap.String("name", v.Name),
+                                       zap.String("namespace", v.Namespace),
+                               )
+                       }
+               }
        case *networkingv1.Ingress:
                // set to status
                lbips, err := c.ingressLBStatusIPs()
diff --git a/pkg/kube/apisix/apis/config/v2beta3/types.go 
b/pkg/kube/apisix/apis/config/v2beta3/types.go
index bdf948f..58c4c14 100644
--- a/pkg/kube/apisix/apis/config/v2beta3/types.go
+++ b/pkg/kube/apisix/apis/config/v2beta3/types.go
@@ -67,10 +67,11 @@ type ApisixRouteHTTP struct {
        // Backends represents potential backends to proxy after the route
        // rule matched. When number of backends are more than one, 
traffic-split
        // plugin in APISIX will be used to split traffic based on the backend 
weight.
-       Backends       []ApisixRouteHTTPBackend  `json:"backends,omitempty" 
yaml:"backends,omitempty"`
-       Websocket      bool                      `json:"websocket" 
yaml:"websocket"`
-       Plugins        []ApisixRouteHTTPPlugin   `json:"plugins,omitempty" 
yaml:"plugins,omitempty"`
-       Authentication ApisixRouteAuthentication 
`json:"authentication,omitempty" yaml:"authentication,omitempty"`
+       Backends         []ApisixRouteHTTPBackend  `json:"backends,omitempty" 
yaml:"backends,omitempty"`
+       Websocket        bool                      `json:"websocket" 
yaml:"websocket"`
+       PluginConfigName string                    
`json:"plugin_config_name,omitempty" yaml:"plugin_config_name,omitempty"`
+       Plugins          []ApisixRouteHTTPPlugin   `json:"plugins,omitempty" 
yaml:"plugins,omitempty"`
+       Authentication   ApisixRouteAuthentication 
`json:"authentication,omitempty" yaml:"authentication,omitempty"`
 }
 
 // ApisixRouteHTTPBackend represents a HTTP backend (a Kuberentes Service).
@@ -629,11 +630,9 @@ type ApisixPluginConfig struct {
 
 // ApisixPluginConfigSpec defines the desired state of ApisixPluginConfigSpec.
 type ApisixPluginConfigSpec struct {
-       // Plugins contains a list of ApisixRouteHTTPPluginConfig
+       // Plugins contains a list of ApisixRouteHTTPPlugin
        // +required
-       // +kubebuilder:validation:Required
-       // +kubebuilder:validation:MinItems=1
-       Plugins []ApisixRouteHTTPPluginConfig `json:"plugins" yaml:"plugins"`
+       Plugins []ApisixRouteHTTPPlugin `json:"plugins" yaml:"plugins"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
diff --git a/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go 
b/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
index 3498257..27ba4f5 100644
--- a/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
@@ -522,7 +522,7 @@ func (in *ApisixPluginConfigSpec) DeepCopyInto(out 
*ApisixPluginConfigSpec) {
        *out = *in
        if in.Plugins != nil {
                in, out := &in.Plugins, &out.Plugins
-               *out = make([]ApisixRouteHTTPPluginConfig, len(*in))
+               *out = make([]ApisixRouteHTTPPlugin, len(*in))
                for i := range *in {
                        (*in)[i].DeepCopyInto(&(*out)[i])
                }
diff --git a/pkg/kube/apisix_plugin_config.go b/pkg/kube/apisix_plugin_config.go
new file mode 100644
index 0000000..a3147d2
--- /dev/null
+++ b/pkg/kube/apisix_plugin_config.go
@@ -0,0 +1,132 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 kube
+
+import (
+       "errors"
+
+       configv2beta3 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+       listersv2beta3 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/listers/config/v2beta3"
+)
+
+const (
+       // ApisixPluginConfigV2beta3 represents the ApisixPluginConfig in 
apisix.apache.org/v2beta3 group version
+       ApisixPluginConfigV2beta3 = "apisix.apache.org/v2beta3"
+)
+
+// ApisixPluginConfigLister is an encapsulation for the lister of 
ApisixPluginConfig,
+// it aims at to be compatible with different ApisixPluginConfig versions.
+type ApisixPluginConfigLister interface {
+       // V2beta3 gets the ApisixPluginConfig in apisix.apache.org/v2beta3.
+       V2beta3(string, string) (ApisixPluginConfig, error)
+}
+
+// ApisixPluginConfigInformer is an encapsulation for the informer of 
ApisixPluginConfig,
+// it aims at to be compatible with different ApisixPluginConfig versions.
+type ApisixPluginConfigInformer interface {
+       Run(chan struct{})
+}
+
+// ApisixPluginConfig is an encapsulation for ApisixPluginConfig resource with 
different
+// versions, for now, they are apisix.apache.org/v1 and 
apisix.apache.org/v2alpha1
+type ApisixPluginConfig interface {
+       // GroupVersion returns the api group version of the
+       // real ApisixPluginConfig.
+       GroupVersion() string
+       // V2beta3 returns the ApisixPluginConfig in apisix.apache.org/v2beta3, 
the real
+       // ApisixPluginConfig must be in this group version, otherwise will 
panic.
+       V2beta3() *configv2beta3.ApisixPluginConfig
+       // ResourceVersion returns the the resource version field inside
+       // the real ApisixPluginConfig.
+       ResourceVersion() string
+}
+
+// ApisixPluginConfigEvent contains the ApisixPluginConfig key (namespace/name)
+// and the group version message.
+type ApisixPluginConfigEvent struct {
+       Key          string
+       OldObject    ApisixPluginConfig
+       GroupVersion string
+}
+
+type apisixPluginConfig struct {
+       groupVersion string
+       v2beta3      *configv2beta3.ApisixPluginConfig
+}
+
+func (apc *apisixPluginConfig) V2beta3() *configv2beta3.ApisixPluginConfig {
+       if apc.groupVersion != ApisixPluginConfigV2beta3 {
+               panic("not a apisix.apache.org/v2beta3 pluginConfig")
+       }
+       return apc.v2beta3
+}
+
+func (apc *apisixPluginConfig) GroupVersion() string {
+       return apc.groupVersion
+}
+
+func (apc *apisixPluginConfig) ResourceVersion() string {
+       return apc.V2beta3().ResourceVersion
+}
+
+type apisixPluginConfigLister struct {
+       v2beta3Lister listersv2beta3.ApisixPluginConfigLister
+}
+
+func (l *apisixPluginConfigLister) V2beta3(namespace, name string) 
(ApisixPluginConfig, error) {
+       apc, err := l.v2beta3Lister.ApisixPluginConfigs(namespace).Get(name)
+       if err != nil {
+               return nil, err
+       }
+       return &apisixPluginConfig{
+               groupVersion: ApisixPluginConfigV2beta3,
+               v2beta3:      apc,
+       }, nil
+}
+
+// MustNewApisixPluginConfig creates a kube.ApisixPluginConfig object 
according to the
+// type of obj.
+func MustNewApisixPluginConfig(obj interface{}) ApisixPluginConfig {
+       switch apc := obj.(type) {
+       case *configv2beta3.ApisixPluginConfig:
+               return &apisixPluginConfig{
+                       groupVersion: ApisixPluginConfigV2beta3,
+                       v2beta3:      apc,
+               }
+       default:
+               panic("invalid ApisixPluginConfig type")
+       }
+}
+
+// NewApisixPluginConfig creates a kube.ApisixPluginConfig object according to 
the
+// type of obj. It returns nil and the error reason when the
+// type assertion fails.
+func NewApisixPluginConfig(obj interface{}) (ApisixPluginConfig, error) {
+       switch apc := obj.(type) {
+       case *configv2beta3.ApisixPluginConfig:
+               return &apisixPluginConfig{
+                       groupVersion: ApisixPluginConfigV2beta3,
+                       v2beta3:      apc,
+               }, nil
+       default:
+               return nil, errors.New("invalid ApisixPluginConfig type")
+       }
+}
+
+func NewApisixPluginConfigLister(v2beta3 
listersv2beta3.ApisixPluginConfigLister) ApisixPluginConfigLister {
+       return &apisixPluginConfigLister{
+               v2beta3Lister: v2beta3,
+       }
+}
diff --git a/pkg/kube/apisix_route.go b/pkg/kube/apisix_route.go
index 7ecf025..54e4a78 100644
--- a/pkg/kube/apisix_route.go
+++ b/pkg/kube/apisix_route.go
@@ -88,20 +88,20 @@ type apisixRoute struct {
 
 func (ar *apisixRoute) V2beta1() *configv2beta1.ApisixRoute {
        if ar.groupVersion != ApisixRouteV2beta1 {
-               panic("not a apisix.apache.org/v2beta1 ingress")
+               panic("not a apisix.apache.org/v2beta1 route")
        }
        return ar.v2beta1
 }
 func (ar *apisixRoute) V2beta2() *configv2beta2.ApisixRoute {
        if ar.groupVersion != ApisixRouteV2beta2 {
-               panic("not a apisix.apache.org/v2beta3 ingress")
+               panic("not a apisix.apache.org/v2beta3 route")
        }
        return ar.v2beta2
 }
 
 func (ar *apisixRoute) V2beta3() *configv2beta3.ApisixRoute {
        if ar.groupVersion != ApisixRouteV2beta3 {
-               panic("not a apisix.apache.org/v2beta3 ingress")
+               panic("not a apisix.apache.org/v2beta3 route")
        }
        return ar.v2beta3
 }
diff --git a/pkg/kube/translation/apisix_pluginconfig.go 
b/pkg/kube/translation/apisix_pluginconfig.go
new file mode 100644
index 0000000..44a6216
--- /dev/null
+++ b/pkg/kube/translation/apisix_pluginconfig.go
@@ -0,0 +1,64 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 translation
+
+import (
+       "go.uber.org/zap"
+
+       "github.com/apache/apisix-ingress-controller/pkg/id"
+       configv2beta3 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+       "github.com/apache/apisix-ingress-controller/pkg/log"
+       apisixv1 
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+func (t *translator) TranslatePluginConfigV2beta3(config 
*configv2beta3.ApisixPluginConfig) (*TranslateContext, error) {
+       ctx := defaultEmptyTranslateContext()
+       pluginMap := make(apisixv1.Plugins)
+       if len(config.Spec.Plugins) > 0 {
+               for _, plugin := range config.Spec.Plugins {
+                       if !plugin.Enable {
+                               continue
+                       }
+                       if plugin.Config != nil {
+                               // Here, it will override same key.
+                               if t, ok := pluginMap[plugin.Name]; ok {
+                                       log.Infow("TranslatePluginConfigV2beta3 
override same plugin key",
+                                               zap.String("key", plugin.Name),
+                                               zap.Any("old", t),
+                                               zap.Any("new", plugin.Config),
+                                       )
+                               }
+                               pluginMap[plugin.Name] = plugin.Config
+                       } else {
+                               pluginMap[plugin.Name] = 
make(map[string]interface{})
+                       }
+               }
+       }
+       pc := apisixv1.NewDefaultPluginConfig()
+       pc.Name = apisixv1.ComposePluginConfigName(config.Namespace, 
config.Name)
+       pc.ID = id.GenID(pc.Name)
+       pc.Plugins = pluginMap
+       ctx.addPluginConfig(pc)
+       return ctx, nil
+}
+
+func (t *translator) TranslatePluginConfigV2beta3NotStrictly(config 
*configv2beta3.ApisixPluginConfig) (*TranslateContext, error) {
+       ctx := defaultEmptyTranslateContext()
+       pc := apisixv1.NewDefaultPluginConfig()
+       pc.Name = apisixv1.ComposePluginConfigName(config.Namespace, 
config.Name)
+       pc.ID = id.GenID(pc.Name)
+       ctx.addPluginConfig(pc)
+       return ctx, nil
+}
diff --git a/pkg/kube/translation/apisix_pluginconfig_test.go 
b/pkg/kube/translation/apisix_pluginconfig_test.go
new file mode 100644
index 0000000..4d20570
--- /dev/null
+++ b/pkg/kube/translation/apisix_pluginconfig_test.go
@@ -0,0 +1,114 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 translation
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+       configv2beta3 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+)
+
+func TestTranslatePluginConfigV2beta3(t *testing.T) {
+       apc := &configv2beta3.ApisixPluginConfig{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "apc",
+                       Namespace: "test-ns",
+               },
+               Spec: configv2beta3.ApisixPluginConfigSpec{
+                       Plugins: []configv2beta3.ApisixRouteHTTPPlugin{
+                               {
+                                       Name:   "case1",
+                                       Enable: true,
+                                       Config: map[string]interface{}{
+                                               "key-1": 1,
+                                               "key-2": 2,
+                                       },
+                               },
+                               {
+                                       Name:   "case2",
+                                       Enable: false,
+                                       Config: map[string]interface{}{
+                                               "key-3": 3,
+                                               "key-4": 4,
+                                               "key-5": 5,
+                                       },
+                               },
+                               {
+                                       Name:   "case3",
+                                       Enable: true,
+                                       Config: map[string]interface{}{
+                                               "key-6": 6,
+                                               "key-7": 7,
+                                               "key-8": 8,
+                                               "key-9": 9,
+                                       },
+                               },
+                       },
+               },
+       }
+       trans := &translator{}
+       ctx, err := trans.TranslatePluginConfigV2beta3(apc)
+       assert.NoError(t, err)
+       assert.Len(t, ctx.PluginConfigs, 1)
+       assert.Len(t, ctx.PluginConfigs[0].Plugins, 2)
+}
+
+func TestTranslatePluginConfigV2beta3NotStrictly(t *testing.T) {
+       apc := &configv2beta3.ApisixPluginConfig{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "apc",
+                       Namespace: "test-ns",
+               },
+               Spec: configv2beta3.ApisixPluginConfigSpec{
+                       Plugins: []configv2beta3.ApisixRouteHTTPPlugin{
+                               {
+                                       Name:   "case1",
+                                       Enable: true,
+                                       Config: map[string]interface{}{
+                                               "key-1": 1,
+                                               "key-2": 2,
+                                       },
+                               },
+                               {
+                                       Name:   "case2",
+                                       Enable: false,
+                                       Config: map[string]interface{}{
+                                               "key-3": 3,
+                                               "key-4": 4,
+                                               "key-5": 5,
+                                       },
+                               },
+                               {
+                                       Name:   "case3",
+                                       Enable: true,
+                                       Config: map[string]interface{}{
+                                               "key-6": 6,
+                                               "key-7": 7,
+                                               "key-8": 8,
+                                               "key-9": 9,
+                                       },
+                               },
+                       },
+               },
+       }
+       trans := &translator{}
+       ctx, err := trans.TranslatePluginConfigV2beta3NotStrictly(apc)
+       assert.NoError(t, err)
+       assert.Len(t, ctx.PluginConfigs, 1)
+       assert.Len(t, ctx.PluginConfigs[0].Plugins, 0)
+}
diff --git a/pkg/kube/translation/apisix_route.go 
b/pkg/kube/translation/apisix_route.go
index edaa8ae..8e92485 100644
--- a/pkg/kube/translation/apisix_route.go
+++ b/pkg/kube/translation/apisix_route.go
@@ -135,9 +135,7 @@ import (
 //}
 
 func (t *translator) TranslateRouteV2beta1(ar *configv2beta1.ApisixRoute) 
(*TranslateContext, error) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
 
        if err := t.translateHTTPRouteV2beta1(ctx, ar); err != nil {
                return nil, err
@@ -149,9 +147,7 @@ func (t *translator) TranslateRouteV2beta1(ar 
*configv2beta1.ApisixRoute) (*Tran
 }
 
 func (t *translator) TranslateRouteV2beta1NotStrictly(ar 
*configv2beta1.ApisixRoute) (*TranslateContext, error) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
 
        if err := t.translateHTTPRouteV2beta1NotStrictly(ctx, ar); err != nil {
                return nil, err
@@ -277,9 +273,7 @@ func (t *translator) translateHTTPRouteV2beta1(ctx 
*TranslateContext, ar *config
 }
 
 func (t *translator) TranslateRouteV2beta2(ar *configv2beta2.ApisixRoute) 
(*TranslateContext, error) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
 
        if err := t.translateHTTPRouteV2beta2(ctx, ar); err != nil {
                return nil, err
@@ -291,9 +285,7 @@ func (t *translator) TranslateRouteV2beta2(ar 
*configv2beta2.ApisixRoute) (*Tran
 }
 
 func (t *translator) TranslateRouteV2beta2NotStrictly(ar 
*configv2beta2.ApisixRoute) (*TranslateContext, error) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
 
        if err := t.translateHTTPRouteV2beta2NotStrictly(ctx, ar); err != nil {
                return nil, err
@@ -305,9 +297,7 @@ func (t *translator) TranslateRouteV2beta2NotStrictly(ar 
*configv2beta2.ApisixRo
 }
 
 func (t *translator) TranslateRouteV2beta3(ar *configv2beta3.ApisixRoute) 
(*TranslateContext, error) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
 
        if err := t.translateHTTPRouteV2beta3(ctx, ar); err != nil {
                return nil, err
@@ -319,9 +309,7 @@ func (t *translator) TranslateRouteV2beta3(ar 
*configv2beta3.ApisixRoute) (*Tran
 }
 
 func (t *translator) TranslateRouteV2beta3NotStrictly(ar 
*configv2beta3.ApisixRoute) (*TranslateContext, error) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
 
        if err := t.translateHTTPRouteV2beta3NotStrictly(ctx, ar); err != nil {
                return nil, err
@@ -542,6 +530,18 @@ func (t *translator) translateHTTPRouteV2beta3(ctx 
*TranslateContext, ar *config
                route.EnableWebsocket = part.Websocket
                route.Plugins = pluginMap
                route.Timeout = timeout
+               pluginConfig := apisixv1.NewDefaultPluginConfig()
+               if len(pluginMap) > 0 {
+                       pluginConfigName := 
apisixv1.ComposePluginConfigName(ar.Namespace, backend.ServiceName)
+                       route.PluginConfigId = id.GenID(pluginConfigName)
+                       pluginConfig.ID = route.PluginConfigId
+                       pluginConfig.Name = pluginConfigName
+                       pluginConfig.Plugins = pluginMap
+               } else {
+                       if part.PluginConfigName != "" {
+                               route.PluginConfigId = part.PluginConfigName
+                       }
+               }
 
                if len(backends) > 0 {
                        weight := _defaultWeight
@@ -566,6 +566,9 @@ func (t *translator) translateHTTPRouteV2beta3(ctx 
*TranslateContext, ar *config
                        }
                        ctx.addUpstream(ups)
                }
+               if len(pluginMap) > 0 {
+                       ctx.addPluginConfig(pluginConfig)
+               }
        }
        return nil
 }
@@ -944,10 +947,44 @@ func (t *translator) 
translateHTTPRouteV2beta3NotStrictly(ctx *TranslateContext,
                // Use the first backend as the default backend in Route,
                // others will be configured in traffic-split plugin.
                backend := backends[0]
+
+               pluginMap := make(apisixv1.Plugins)
+               // add route plugins
+               for _, plugin := range part.Plugins {
+                       if !plugin.Enable {
+                               continue
+                       }
+                       if plugin.Config != nil {
+                               pluginMap[plugin.Name] = plugin.Config
+                       } else {
+                               pluginMap[plugin.Name] = 
make(map[string]interface{})
+                       }
+               }
+
+               // add KeyAuth and basicAuth plugin
+               if part.Authentication.Enable {
+                       switch part.Authentication.Type {
+                       case "keyAuth":
+                               pluginMap["key-auth"] = 
part.Authentication.KeyAuth
+                       case "basicAuth":
+                               pluginMap["basic-auth"] = 
make(map[string]interface{})
+                       default:
+                               pluginMap["basic-auth"] = 
make(map[string]interface{})
+                       }
+               }
+
                upstreamName := apisixv1.ComposeUpstreamName(ar.Namespace, 
backend.ServiceName, backend.Subset, backend.ServicePort.IntVal)
                route := apisixv1.NewDefaultRoute()
                route.Name = apisixv1.ComposeRouteName(ar.Namespace, ar.Name, 
part.Name)
                route.ID = id.GenID(route.Name)
+               pluginConfig := apisixv1.NewDefaultPluginConfig()
+               if len(pluginMap) > 0 {
+                       pluginConfigName := 
apisixv1.ComposePluginConfigName(ar.Namespace, backend.ServiceName)
+                       route.PluginConfigId = id.GenID(pluginConfigName)
+                       pluginConfig.ID = route.PluginConfigId
+                       pluginConfig.Name = pluginConfigName
+               }
+
                ctx.addRoute(route)
                if !ctx.checkUpstreamExist(upstreamName) {
                        ups, err := 
t.translateUpstreamNotStrictly(ar.Namespace, backend.ServiceName, 
backend.Subset, backend.ServicePort.IntVal)
@@ -956,6 +993,9 @@ func (t *translator) 
translateHTTPRouteV2beta3NotStrictly(ctx *TranslateContext,
                        }
                        ctx.addUpstream(ups)
                }
+               if len(pluginMap) > 0 {
+                       ctx.addPluginConfig(pluginConfig)
+               }
        }
        return nil
 }
diff --git a/pkg/kube/translation/apisix_route_test.go 
b/pkg/kube/translation/apisix_route_test.go
index 59cb23c..ed15ea8 100644
--- a/pkg/kube/translation/apisix_route_test.go
+++ b/pkg/kube/translation/apisix_route_test.go
@@ -355,6 +355,16 @@ func TestTranslateApisixRouteV2alpha1NotStrictly(t 
*testing.T) {
                                                        },
                                                },
                                        },
+                                       Plugins: 
[]configv2beta3.ApisixRouteHTTPPlugin{
+                                               {
+                                                       Name:   "plugin-1",
+                                                       Enable: true,
+                                                       Config: 
map[string]interface{}{
+                                                               "key-1": 123456,
+                                                               "key-2": 
"2121331",
+                                                       },
+                                               },
+                                       },
                                },
                                {
                                        Name: "rule2",
@@ -381,14 +391,16 @@ func TestTranslateApisixRouteV2alpha1NotStrictly(t 
*testing.T) {
        assert.NoError(t, err, "translateRoute not strictly should be no error")
        assert.Equal(t, 2, len(tx.Routes), "There should be 2 routes")
        assert.Equal(t, 2, len(tx.Upstreams), "There should be 2 upstreams")
+       assert.Equal(t, 1, len(tx.PluginConfigs), "There should be 1 
pluginConfigs")
        assert.Equal(t, "test_ar_rule1", tx.Routes[0].Name, "route1 name error")
        assert.Equal(t, "test_ar_rule2", tx.Routes[1].Name, "route2 name error")
        assert.Equal(t, "test_svc1_81", tx.Upstreams[0].Name, "upstream1 name 
error")
        assert.Equal(t, "test_svc2_82", tx.Upstreams[1].Name, "upstream2 name 
error")
+       assert.Equal(t, "test_svc1", tx.PluginConfigs[0].Name, "pluginConfig1 
name error")
 
        assert.Equal(t, id.GenID("test_ar_rule1"), tx.Routes[0].ID, "route1 id 
error")
        assert.Equal(t, id.GenID("test_ar_rule2"), tx.Routes[1].ID, "route2 id 
error")
        assert.Equal(t, id.GenID("test_svc1_81"), tx.Upstreams[0].ID, 
"upstream1 id error")
        assert.Equal(t, id.GenID("test_svc2_82"), tx.Upstreams[1].ID, 
"upstream2 id error")
-
+       assert.Equal(t, id.GenID("test_svc1"), tx.PluginConfigs[0].ID, 
"pluginConfig1 id error")
 }
diff --git a/pkg/kube/translation/context.go b/pkg/kube/translation/context.go
index 3f21830..dc67ef7 100644
--- a/pkg/kube/translation/context.go
+++ b/pkg/kube/translation/context.go
@@ -18,11 +18,18 @@ import apisix 
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 
 // TranslateContext contains APISIX resources generated by the translator.
 type TranslateContext struct {
-       Routes       []*apisix.Route
-       StreamRoutes []*apisix.StreamRoute
-       Upstreams    []*apisix.Upstream
-       upstreamMap  map[string]struct{}
-       SSL          []*apisix.Ssl
+       Routes        []*apisix.Route
+       StreamRoutes  []*apisix.StreamRoute
+       Upstreams     []*apisix.Upstream
+       upstreamMap   map[string]struct{}
+       SSL           []*apisix.Ssl
+       PluginConfigs []*apisix.PluginConfig
+}
+
+func defaultEmptyTranslateContext() *TranslateContext {
+       return &TranslateContext{
+               upstreamMap: make(map[string]struct{}),
+       }
 }
 
 func (tc *TranslateContext) addRoute(r *apisix.Route) {
@@ -49,3 +56,7 @@ func (tc *TranslateContext) checkUpstreamExist(name string) 
(ok bool) {
        _, ok = tc.upstreamMap[name]
        return
 }
+
+func (tc *TranslateContext) addPluginConfig(pc *apisix.PluginConfig) {
+       tc.PluginConfigs = append(tc.PluginConfigs, pc)
+}
diff --git a/pkg/kube/translation/context_test.go 
b/pkg/kube/translation/context_test.go
index 20f55a8..78cabc5 100644
--- a/pkg/kube/translation/context_test.go
+++ b/pkg/kube/translation/context_test.go
@@ -23,9 +23,8 @@ import (
 )
 
 func TestTranslateContext(t *testing.T) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
+
        r1 := &apisix.Route{
                Metadata: apisix.Metadata{
                        ID: "1",
@@ -54,22 +53,39 @@ func TestTranslateContext(t *testing.T) {
                        Name: "aaa",
                },
        }
+       pc1 := &apisix.PluginConfig{
+               Metadata: apisix.Metadata{
+                       ID:   "1",
+                       Name: "aaa",
+               },
+       }
+       pc2 := &apisix.PluginConfig{
+               Metadata: apisix.Metadata{
+                       ID:   "2",
+                       Name: "aaa",
+               },
+       }
        ctx.addRoute(r1)
        ctx.addRoute(r2)
        ctx.addStreamRoute(sr1)
        ctx.addStreamRoute(sr2)
        ctx.addUpstream(u1)
        ctx.addUpstream(u2)
+       ctx.addPluginConfig(pc1)
+       ctx.addPluginConfig(pc2)
 
        assert.Len(t, ctx.Routes, 2)
        assert.Len(t, ctx.StreamRoutes, 2)
        assert.Len(t, ctx.Upstreams, 1)
+       assert.Len(t, ctx.PluginConfigs, 2)
 
        assert.Equal(t, r1, ctx.Routes[0])
        assert.Equal(t, r2, ctx.Routes[1])
        assert.Equal(t, sr1, ctx.StreamRoutes[0])
        assert.Equal(t, sr2, ctx.StreamRoutes[1])
        assert.Equal(t, u1, ctx.Upstreams[0])
+       assert.Equal(t, pc1, ctx.PluginConfigs[0])
+       assert.Equal(t, pc2, ctx.PluginConfigs[1])
 
        assert.Equal(t, true, ctx.checkUpstreamExist("aaa"))
        assert.Equal(t, false, ctx.checkUpstreamExist("bbb"))
diff --git a/pkg/kube/translation/ingress.go b/pkg/kube/translation/ingress.go
index d7c5779..48fe18d 100644
--- a/pkg/kube/translation/ingress.go
+++ b/pkg/kube/translation/ingress.go
@@ -39,9 +39,7 @@ const (
 )
 
 func (t *translator) translateIngressV1(ing *networkingv1.Ingress) 
(*TranslateContext, error) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
        plugins := t.translateAnnotations(ing.Annotations)
        annoExtractor := annotations.NewExtractor(ing.Annotations)
        useRegex := 
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
@@ -78,8 +76,9 @@ func (t *translator) translateIngressV1(ing 
*networkingv1.Ingress) (*TranslateCo
        for _, rule := range ing.Spec.Rules {
                for _, pathRule := range rule.HTTP.Paths {
                        var (
-                               ups *apisixv1.Upstream
-                               err error
+                               ups          *apisixv1.Upstream
+                               pluginConfig *apisixv1.PluginConfig
+                               err          error
                        )
                        if pathRule.Backend.Service != nil {
                                ups, err = 
t.translateUpstreamFromIngressV1(ing.Namespace, pathRule.Backend.Service)
@@ -138,6 +137,14 @@ func (t *translator) translateIngressV1(ing 
*networkingv1.Ingress) (*TranslateCo
                        }
                        if len(plugins) > 0 {
                                route.Plugins = *(plugins.DeepCopy())
+
+                               pluginConfig = apisixv1.NewDefaultPluginConfig()
+                               pluginConfig.Name = 
composeIngressPluginName(ing.Namespace, pathRule.Backend.Service.Name)
+                               pluginConfig.ID = id.GenID(route.Name)
+                               pluginConfig.Plugins = *(plugins.DeepCopy())
+                               ctx.addPluginConfig(pluginConfig)
+
+                               route.PluginConfigId = pluginConfig.ID
                        }
                        if ups != nil {
                                route.UpstreamId = ups.ID
@@ -149,9 +156,7 @@ func (t *translator) translateIngressV1(ing 
*networkingv1.Ingress) (*TranslateCo
 }
 
 func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress) 
(*TranslateContext, error) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
        plugins := t.translateAnnotations(ing.Annotations)
        annoExtractor := annotations.NewExtractor(ing.Annotations)
        useRegex := 
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
@@ -188,8 +193,9 @@ func (t *translator) translateIngressV1beta1(ing 
*networkingv1beta1.Ingress) (*T
        for _, rule := range ing.Spec.Rules {
                for _, pathRule := range rule.HTTP.Paths {
                        var (
-                               ups *apisixv1.Upstream
-                               err error
+                               ups          *apisixv1.Upstream
+                               pluginConfig *apisixv1.PluginConfig
+                               err          error
                        )
                        if pathRule.Backend.ServiceName != "" {
                                ups, err = 
t.translateUpstreamFromIngressV1beta1(ing.Namespace, 
pathRule.Backend.ServiceName, pathRule.Backend.ServicePort)
@@ -248,6 +254,14 @@ func (t *translator) translateIngressV1beta1(ing 
*networkingv1beta1.Ingress) (*T
                        }
                        if len(plugins) > 0 {
                                route.Plugins = *(plugins.DeepCopy())
+
+                               pluginConfig = apisixv1.NewDefaultPluginConfig()
+                               pluginConfig.Name = 
composeIngressPluginName(ing.Namespace, pathRule.Backend.ServiceName)
+                               pluginConfig.ID = id.GenID(route.Name)
+                               pluginConfig.Plugins = *(plugins.DeepCopy())
+                               ctx.addPluginConfig(pluginConfig)
+
+                               route.PluginConfigId = pluginConfig.ID
                        }
                        if ups != nil {
                                route.UpstreamId = ups.ID
@@ -290,9 +304,7 @@ func (t *translator) 
translateUpstreamFromIngressV1(namespace string, backend *n
 }
 
 func (t *translator) translateIngressExtensionsV1beta1(ing 
*extensionsv1beta1.Ingress) (*TranslateContext, error) {
-       ctx := &TranslateContext{
-               upstreamMap: make(map[string]struct{}),
-       }
+       ctx := defaultEmptyTranslateContext()
        plugins := t.translateAnnotations(ing.Annotations)
        annoExtractor := annotations.NewExtractor(ing.Annotations)
        useRegex := 
annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
@@ -300,8 +312,9 @@ func (t *translator) translateIngressExtensionsV1beta1(ing 
*extensionsv1beta1.In
        for _, rule := range ing.Spec.Rules {
                for _, pathRule := range rule.HTTP.Paths {
                        var (
-                               ups *apisixv1.Upstream
-                               err error
+                               ups          *apisixv1.Upstream
+                               pluginConfig *apisixv1.PluginConfig
+                               err          error
                        )
                        if pathRule.Backend.ServiceName != "" {
                                // Structure here is same to 
ingress.extensions/v1beta1, so just use this method.
@@ -361,6 +374,14 @@ func (t *translator) translateIngressExtensionsV1beta1(ing 
*extensionsv1beta1.In
                        }
                        if len(plugins) > 0 {
                                route.Plugins = *(plugins.DeepCopy())
+
+                               pluginConfig = apisixv1.NewDefaultPluginConfig()
+                               pluginConfig.Name = 
composeIngressPluginName(ing.Namespace, pathRule.Backend.ServiceName)
+                               pluginConfig.ID = id.GenID(route.Name)
+                               pluginConfig.Plugins = *(plugins.DeepCopy())
+                               ctx.addPluginConfig(pluginConfig)
+
+                               route.PluginConfigId = pluginConfig.ID
                        }
                        if ups != nil {
                                route.UpstreamId = ups.ID
@@ -415,3 +436,17 @@ func composeIngressRouteName(host, path string) string {
        return buf.String()
 
 }
+
+func composeIngressPluginName(svc, name string) string {
+       p := make([]byte, 0, len(svc)+len(name)+len("ingress")+2)
+       buf := bytes.NewBuffer(p)
+
+       buf.WriteString("ingress")
+       buf.WriteByte('_')
+       buf.WriteString(svc)
+       buf.WriteByte('_')
+       buf.WriteString(name)
+
+       return buf.String()
+
+}
diff --git a/pkg/kube/translation/ingress_test.go 
b/pkg/kube/translation/ingress_test.go
index 98b953e..f72ea25 100644
--- a/pkg/kube/translation/ingress_test.go
+++ b/pkg/kube/translation/ingress_test.go
@@ -16,6 +16,7 @@ package translation
 
 import (
        "context"
+       "path"
        "testing"
 
        "github.com/stretchr/testify/assert"
@@ -34,6 +35,7 @@ import (
        fakeapisix 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/fake"
        apisixinformers 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions"
        apisixconst 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
+       
"github.com/apache/apisix-ingress-controller/pkg/kube/translation/annotations"
        v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
@@ -123,7 +125,9 @@ func TestTranslateIngressV1NoBackend(t *testing.T) {
        assert.Nil(t, err)
        assert.Len(t, ctx.Routes, 1)
        assert.Len(t, ctx.Upstreams, 0)
+       assert.Len(t, ctx.PluginConfigs, 0)
        assert.Equal(t, "", ctx.Routes[0].UpstreamId)
+       assert.Equal(t, "", ctx.Routes[0].PluginConfigId)
        assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
 }
 
@@ -282,6 +286,8 @@ func TestTranslateIngressV1WithRegex(t *testing.T) {
        assert.Nil(t, err)
        assert.Len(t, ctx.Routes, 1)
        assert.Len(t, ctx.Upstreams, 1)
+       // the number of the PluginConfigs should be zero, cause there no 
available Annotations matched te rule
+       assert.Len(t, ctx.PluginConfigs, 0)
        routeVars, err := 
tr.translateRouteMatchExprs([]configv2beta3.ApisixRouteHTTPMatchExpr{{
                Subject: configv2beta3.ApisixRouteHTTPMatchExprSubject{
                        Scope: apisixconst.ScopePath,
@@ -304,6 +310,11 @@ func TestTranslateIngressV1(t *testing.T) {
                ObjectMeta: metav1.ObjectMeta{
                        Name:      "test",
                        Namespace: "default",
+                       Annotations: map[string]string{
+                               "k8s.apisix.apache.org/use-regex":              
                    "true",
+                               path.Join(annotations.AnnotationsPrefix, 
"enable-cors"):            "true",
+                               path.Join(annotations.AnnotationsPrefix, 
"allowlist-source-range"): "127.0.0.1",
+                       },
                },
                Spec: networkingv1.IngressSpec{
                        Rules: []networkingv1.IngressRule{
@@ -386,12 +397,15 @@ func TestTranslateIngressV1(t *testing.T) {
        assert.Nil(t, err)
        assert.Len(t, ctx.Routes, 2)
        assert.Len(t, ctx.Upstreams, 2)
+       assert.Len(t, ctx.PluginConfigs, 2)
 
        assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
        assert.Equal(t, ctx.Upstreams[0].ID, ctx.Routes[0].UpstreamId)
+       assert.Equal(t, ctx.PluginConfigs[0].ID, ctx.Routes[0].PluginConfigId)
        assert.Equal(t, "apisix.apache.org", ctx.Routes[0].Host)
        assert.Equal(t, []string{"/bar"}, ctx.Routes[1].Uris)
        assert.Equal(t, ctx.Upstreams[1].ID, ctx.Routes[1].UpstreamId)
+       assert.Equal(t, ctx.PluginConfigs[1].ID, ctx.Routes[1].PluginConfigId)
        assert.Equal(t, "apisix.apache.org", ctx.Routes[1].Host)
 
        assert.Equal(t, "roundrobin", ctx.Upstreams[0].Type)
@@ -409,6 +423,9 @@ func TestTranslateIngressV1(t *testing.T) {
        assert.Equal(t, "192.168.1.1", ctx.Upstreams[1].Nodes[0].Host)
        assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[1].Port)
        assert.Equal(t, "192.168.1.2", ctx.Upstreams[1].Nodes[1].Host)
+
+       assert.Len(t, ctx.PluginConfigs[0].Plugins, 2)
+       assert.Len(t, ctx.PluginConfigs[1].Plugins, 2)
 }
 
 func TestTranslateIngressV1beta1NoBackend(t *testing.T) {
@@ -442,7 +459,9 @@ func TestTranslateIngressV1beta1NoBackend(t *testing.T) {
        assert.Nil(t, err)
        assert.Len(t, ctx.Routes, 1)
        assert.Len(t, ctx.Upstreams, 0)
+       assert.Len(t, ctx.PluginConfigs, 0)
        assert.Equal(t, "", ctx.Routes[0].UpstreamId)
+       assert.Equal(t, "", ctx.Routes[0].PluginConfigId)
        assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
 }
 
@@ -600,6 +619,8 @@ func TestTranslateIngressV1beta1WithRegex(t *testing.T) {
        assert.Nil(t, err)
        assert.Len(t, ctx.Routes, 1)
        assert.Len(t, ctx.Upstreams, 1)
+       // the number of the PluginConfigs should be zero, cause there no 
available Annotations matched te rule
+       assert.Len(t, ctx.PluginConfigs, 0)
 
        routeVars, err := 
tr.translateRouteMatchExprs([]configv2beta3.ApisixRouteHTTPMatchExpr{{
                Subject: configv2beta3.ApisixRouteHTTPMatchExprSubject{
@@ -621,6 +642,12 @@ func TestTranslateIngressV1beta1(t *testing.T) {
                ObjectMeta: metav1.ObjectMeta{
                        Name:      "test",
                        Namespace: "default",
+                       Annotations: map[string]string{
+                               "k8s.apisix.apache.org/use-regex":              
                    "true",
+                               path.Join(annotations.AnnotationsPrefix, 
"enable-cors"):            "true",
+                               path.Join(annotations.AnnotationsPrefix, 
"allowlist-source-range"): "127.0.0.1",
+                               path.Join(annotations.AnnotationsPrefix, 
"enable-cors222"):         "true",
+                       },
                },
                Spec: networkingv1beta1.IngressSpec{
                        Rules: []networkingv1beta1.IngressRule{
@@ -701,6 +728,7 @@ func TestTranslateIngressV1beta1(t *testing.T) {
        assert.Nil(t, err)
        assert.Len(t, ctx.Routes, 2)
        assert.Len(t, ctx.Upstreams, 2)
+       assert.Len(t, ctx.PluginConfigs, 2)
 
        assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
        assert.Equal(t, ctx.Upstreams[0].ID, ctx.Routes[0].UpstreamId)
@@ -724,6 +752,9 @@ func TestTranslateIngressV1beta1(t *testing.T) {
        assert.Equal(t, "192.168.1.1", ctx.Upstreams[1].Nodes[0].Host)
        assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[1].Port)
        assert.Equal(t, "192.168.1.2", ctx.Upstreams[1].Nodes[1].Host)
+
+       assert.Len(t, ctx.PluginConfigs[0].Plugins, 2)
+       assert.Len(t, ctx.PluginConfigs[1].Plugins, 2)
 }
 
 func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
@@ -733,6 +764,12 @@ func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
                ObjectMeta: metav1.ObjectMeta{
                        Name:      "test",
                        Namespace: "default",
+                       Annotations: map[string]string{
+                               "k8s.apisix.apache.org/use-regex":              
                    "true",
+                               path.Join(annotations.AnnotationsPrefix, 
"enable-cors"):            "true",
+                               path.Join(annotations.AnnotationsPrefix, 
"allowlist-source-range"): "127.0.0.1",
+                               path.Join(annotations.AnnotationsPrefix, 
"enable-cors222"):         "true",
+                       },
                },
                Spec: extensionsv1beta1.IngressSpec{
                        Rules: []extensionsv1beta1.IngressRule{
@@ -813,6 +850,7 @@ func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
        assert.Nil(t, err)
        assert.Len(t, ctx.Routes, 2)
        assert.Len(t, ctx.Upstreams, 2)
+       assert.Len(t, ctx.PluginConfigs, 2)
 
        assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
        assert.Equal(t, ctx.Upstreams[0].ID, ctx.Routes[0].UpstreamId)
@@ -836,6 +874,9 @@ func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
        assert.Equal(t, "192.168.1.1", ctx.Upstreams[1].Nodes[0].Host)
        assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[1].Port)
        assert.Equal(t, "192.168.1.2", ctx.Upstreams[1].Nodes[1].Host)
+
+       assert.Len(t, ctx.PluginConfigs[0].Plugins, 2)
+       assert.Len(t, ctx.PluginConfigs[1].Plugins, 2)
 }
 
 func TestTranslateIngressExtensionsV1beta1BackendWithInvalidService(t 
*testing.T) {
@@ -991,6 +1032,8 @@ func TestTranslateIngressExtensionsV1beta1WithRegex(t 
*testing.T) {
        assert.Nil(t, err)
        assert.Len(t, ctx.Routes, 1)
        assert.Len(t, ctx.Upstreams, 1)
+       // the number of the PluginConfigs should be zero, cause there no 
available Annotations matched te rule
+       assert.Len(t, ctx.PluginConfigs, 0)
        routeVars, err := 
tr.translateRouteMatchExprs([]configv2beta3.ApisixRouteHTTPMatchExpr{{
                Subject: configv2beta3.ApisixRouteHTTPMatchExprSubject{
                        Scope: apisixconst.ScopePath,
@@ -1004,5 +1047,4 @@ func TestTranslateIngressExtensionsV1beta1WithRegex(t 
*testing.T) {
 
        assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris)
        assert.Equal(t, expectedVars, ctx.Routes[0].Vars)
-
 }
diff --git a/pkg/kube/translation/translator.go 
b/pkg/kube/translation/translator.go
index a4d2ad5..538e7a6 100644
--- a/pkg/kube/translation/translator.go
+++ b/pkg/kube/translation/translator.go
@@ -68,20 +68,20 @@ type Translator interface {
        // TranslateRouteV2beta1 translates the configv2beta1.ApisixRoute 
object into several Route
        // and Upstream resources.
        TranslateRouteV2beta1(*configv2beta1.ApisixRoute) (*TranslateContext, 
error)
-       // TranslateRouteV2beta1NotStrictly translates the 
configv2beta1.ApisixRoute object into several Route
+       // TranslateRouteV2beta1NotStrictly translates the 
configv2beta1.ApisixRoute object into several Route,
        // and Upstream resources not strictly, only used for delete event.
        TranslateRouteV2beta1NotStrictly(*configv2beta1.ApisixRoute) 
(*TranslateContext, error)
-       // TranslateRouteV2beta2 translates the configv2beta2.ApisixRoute 
object into several Route
+       // TranslateRouteV2beta2 translates the configv2beta2.ApisixRoute 
object into several Route,
        // and Upstream resources.
        TranslateRouteV2beta2(*configv2beta2.ApisixRoute) (*TranslateContext, 
error)
-       // TranslateRouteV2beta2NotStrictly translates the 
configv2beta2.ApisixRoute object into several Route
-       // and Upstream resources not strictly, only used for delete event.
+       // TranslateRouteV2beta2NotStrictly translates the 
configv2beta2.ApisixRoute object into several Route,
+       // and Upstream  resources not strictly, only used for delete event.
        TranslateRouteV2beta2NotStrictly(*configv2beta2.ApisixRoute) 
(*TranslateContext, error)
-       // TranslateRouteV2beta3 translates the configv2beta3.ApisixRoute 
object into several Route
-       // and Upstream resources.
+       // TranslateRouteV2beta3 translates the configv2beta3.ApisixRoute 
object into several Route,
+       // Upstream and PluginConfig resources.
        TranslateRouteV2beta3(*configv2beta3.ApisixRoute) (*TranslateContext, 
error)
-       // TranslateRouteV2beta3NotStrictly translates the 
configv2beta3.ApisixRoute object into several Route
-       // and Upstream resources not strictly, only used for delete event.
+       // TranslateRouteV2beta3NotStrictly translates the 
configv2beta3.ApisixRoute object into several Route,
+       // Upstream and PluginConfig resources not strictly, only used for 
delete event.
        TranslateRouteV2beta3NotStrictly(*configv2beta3.ApisixRoute) 
(*TranslateContext, error)
        // TranslateSSL translates the configv2beta3.ApisixTls object into the 
APISIX SSL resource.
        TranslateSSL(*configv2beta3.ApisixTls) (*apisixv1.Ssl, error)
@@ -91,6 +91,12 @@ type Translator interface {
        // TranslateApisixConsumer translates the configv2beta3.APisixConsumer 
object into the APISIX Consumer
        // resource.
        TranslateApisixConsumer(*configv2beta3.ApisixConsumer) 
(*apisixv1.Consumer, error)
+       // TranslatePluginConfigV2beta3 translates the 
configv2beta3.ApisixPluginConfig object into several PluginConfig
+       // resources.
+       TranslatePluginConfigV2beta3(*configv2beta3.ApisixPluginConfig) 
(*TranslateContext, error)
+       // TranslatePluginConfigV2beta3NotStrictly translates the 
configv2beta3.ApisixPluginConfig object into several PluginConfig
+       // resources not strictly, only used for delete event.
+       
TranslatePluginConfigV2beta3NotStrictly(*configv2beta3.ApisixPluginConfig) 
(*TranslateContext, error)
        // ExtractKeyPair extracts certificate and private key pair from secret
        // Supports APISIX style ("cert" and "key") and Kube style ("tls.crt" 
and "tls.key)
        ExtractKeyPair(s *corev1.Secret, hasPrivateKey bool) ([]byte, []byte, 
error)
diff --git a/pkg/types/apisix/v1/types.go b/pkg/types/apisix/v1/types.go
index 99c99a9..0080584 100644
--- a/pkg/types/apisix/v1/types.go
+++ b/pkg/types/apisix/v1/types.go
@@ -100,6 +100,7 @@ type Route struct {
        RemoteAddrs     []string         `json:"remote_addrs,omitempty" 
yaml:"remote_addrs,omitempty"`
        UpstreamId      string           `json:"upstream_id,omitempty" 
yaml:"upstream_id,omitempty"`
        Plugins         Plugins          `json:"plugins,omitempty" 
yaml:"plugins,omitempty"`
+       PluginConfigId  string           `json:"plugin_config_id,omitempty" 
yaml:"plugin_config_id,omitempty"`
 }
 
 // Vars represents the route match expressions of APISIX.
@@ -357,7 +358,7 @@ type Consumer struct {
 // +k8s:deepcopy-gen=true
 type PluginConfig struct {
        Metadata `json:",inline" yaml:",inline"`
-       Plugins  Plugins `json:"plugins,omitempty" yaml:"plugins,omitempty"`
+       Plugins  Plugins `json:"plugins" yaml:"plugins"`
 }
 
 // NewDefaultUpstream returns an empty Upstream with default values.
@@ -408,6 +409,19 @@ func NewDefaultConsumer() *Consumer {
        }
 }
 
+// NewDefaultPluginConfig returns an empty PluginConfig with default values.
+func NewDefaultPluginConfig() *PluginConfig {
+       return &PluginConfig{
+               Metadata: Metadata{
+                       Desc: "Created by apisix-ingress-controller, DO NOT 
modify it manually",
+                       Labels: map[string]string{
+                               "managed-by": "apisix-ingress-controller",
+                       },
+               },
+               Plugins: make(Plugins),
+       }
+}
+
 // ComposeUpstreamName uses namespace, name, subset (optional) and port info 
to compose
 // the upstream name.
 func ComposeUpstreamName(namespace, name, subset string, port int32) string {
@@ -484,6 +498,21 @@ func ComposeConsumerName(namespace, name string) string {
        return buf.String()
 }
 
+// ComposePluginConfigName uses namespace, name to compose
+// the route name.
+func ComposePluginConfigName(namespace, name string) string {
+       // FIXME Use sync.Pool to reuse this buffer if the upstream
+       // name composing code path is hot.
+       p := make([]byte, 0, len(namespace)+len(name)+1)
+       buf := bytes.NewBuffer(p)
+
+       buf.WriteString(namespace)
+       buf.WriteByte('_')
+       buf.WriteString(name)
+
+       return buf.String()
+}
+
 // Schema represents the schema of APISIX objects.
 type Schema struct {
        Name    string `json:"name,omitempty" yaml:"name,omitempty"`
diff --git a/samples/deploy/crd/v1/ApisixPluginConfig.yaml 
b/samples/deploy/crd/v1/ApisixPluginConfig.yaml
index a3f6a20..e329c56 100644
--- a/samples/deploy/crd/v1/ApisixPluginConfig.yaml
+++ b/samples/deploy/crd/v1/ApisixPluginConfig.yaml
@@ -60,6 +60,7 @@ spec:
                         type: boolean
                       config:
                         type: object
+                        x-kubernetes-preserve-unknown-fields: true # we have 
to enable it since plugin config
                   required:
                     - name
                     - enable
diff --git a/samples/deploy/crd/v1/ApisixRoute.yaml 
b/samples/deploy/crd/v1/ApisixRoute.yaml
index bf01d35..0622a33 100644
--- a/samples/deploy/crd/v1/ApisixRoute.yaml
+++ b/samples/deploy/crd/v1/ApisixRoute.yaml
@@ -707,6 +707,9 @@ spec:
                                 - required: [ "subject", "op", "set" ]
                       websocket:
                         type: boolean
+                      plugin_config_name:
+                        type: string
+                        minLength: 1
                       backends:
                         type: array
                         minItems: 1
diff --git a/samples/deploy/rbac/apisix_view_clusterrole.yaml 
b/samples/deploy/rbac/apisix_view_clusterrole.yaml
index 6394903..db2878d 100644
--- a/samples/deploy/rbac/apisix_view_clusterrole.yaml
+++ b/samples/deploy/rbac/apisix_view_clusterrole.yaml
@@ -149,8 +149,8 @@ rules:
   - apisixclusterconfigs/status
   - apisixconsumers
   - apisixconsumers/status
-  - apisixpluginconfig
-  - apisixpluginconfig/status
+  - apisixpluginconfigs
+  - apisixpluginconfigs/status
   verbs:
   - '*'
 - apiGroups:
diff --git a/test/e2e/ingress/resourcepushing.go 
b/test/e2e/ingress/resourcepushing.go
index 8d1abc1..66f8069 100644
--- a/test/e2e/ingress/resourcepushing.go
+++ b/test/e2e/ingress/resourcepushing.go
@@ -96,6 +96,8 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
                err = s.EnsureNumApisixUpstreamsCreated(1)
                assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(0)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
 
                s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect().Status(http.StatusOK)
 
@@ -133,6 +135,8 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
                err = s.EnsureNumApisixUpstreamsCreated(1)
                assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(0)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
 
                s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect().Status(http.StatusNotFound)
                s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").WithHeader("X-Foo", "barbaz").Expect().Status(http.StatusOK)
@@ -145,6 +149,9 @@ spec:
                ups, err := s.ListApisixUpstreams()
                assert.Nil(ginkgo.GinkgoT(), err, "list upstreams error")
                assert.Len(ginkgo.GinkgoT(), ups, 0, "upstreams nodes not 
expect")
+               pluginConfigs, err := s.ListApisixPluginConfig()
+               assert.Nil(ginkgo.GinkgoT(), err, "list pluginConfigs error")
+               assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0, "pluginConfigs 
nodes not expect")
 
                body := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect().Status(http.StatusNotFound).Body().Raw()
                assert.Contains(ginkgo.GinkgoT(), body, "404 Route Not Found")
@@ -175,6 +182,8 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
                err = s.EnsureNumApisixUpstreamsCreated(1)
                assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(0)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
 
                s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect().Status(http.StatusOK)
 
@@ -212,6 +221,8 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
                err = s.EnsureNumApisixUpstreamsCreated(1)
                assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(0)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
 
                s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect().Status(http.StatusNotFound)
                s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").WithHeader("X-Foo", "barbaz").Expect().Status(http.StatusOK)
@@ -225,6 +236,9 @@ spec:
                ups, err := s.ListApisixUpstreams()
                assert.Nil(ginkgo.GinkgoT(), err, "list upstreams error")
                assert.Len(ginkgo.GinkgoT(), ups, 0, "upstreams nodes not 
expect")
+               pluginConfigs, err := s.ListApisixPluginConfig()
+               assert.Nil(ginkgo.GinkgoT(), err, "list pluginConfigs error")
+               assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0, "pluginConfigs 
nodes not expect")
 
                body := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect().Status(http.StatusNotFound).Body().Raw()
                assert.Contains(ginkgo.GinkgoT(), body, "404 Route Not Found")
@@ -253,6 +267,7 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
                assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1))
                assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixUpstreamsCreated(1))
+               assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixPluginConfigCreated(0))
 
                routes, err := s.ListApisixRoutes()
                assert.Nil(ginkgo.GinkgoT(), err, "listing routes in APISIX")
@@ -262,6 +277,10 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), err, "listing upstreams in APISIX")
                assert.Len(ginkgo.GinkgoT(), upstreams, 1)
 
+               pluginConfigs, err := s.ListApisixPluginConfig()
+               assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs in 
APISIX")
+               assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
                s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect().Status(http.StatusOK)
 
                apisixRoute = fmt.Sprintf(`
@@ -285,6 +304,7 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
                assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1))
                assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixUpstreamsCreated(1))
+               assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixPluginConfigCreated(0))
 
                newRoutes, err := s.ListApisixRoutes()
                assert.Nil(ginkgo.GinkgoT(), err, "listing routes in APISIX")
@@ -292,6 +312,9 @@ spec:
                newUpstreams, err := s.ListApisixUpstreams()
                assert.Nil(ginkgo.GinkgoT(), err, "listing upstreams in APISIX")
                assert.Len(ginkgo.GinkgoT(), newUpstreams, 1)
+               newPluginConfigs, err := s.ListApisixPluginConfig()
+               assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs in 
APISIX")
+               assert.Len(ginkgo.GinkgoT(), newPluginConfigs, 0)
 
                // Upstream doesn't change.
                assert.Equal(ginkgo.GinkgoT(), newUpstreams[0].ID, 
upstreams[0].ID)
@@ -344,6 +367,7 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
                assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(2))
                assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixUpstreamsCreated(1))
+               assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixPluginConfigCreated(0))
 
                s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect().
                        Status(http.StatusOK).
@@ -399,6 +423,7 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
                assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(2))
                assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixUpstreamsCreated(1))
+               assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixPluginConfigCreated(1))
 
                // Hit rule1
                resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect()
@@ -413,7 +438,7 @@ spec:
                resp.Header("X-Request-Id").NotEmpty()
        })
 
-       ginkgo.It("verify route/upstream items", func() {
+       ginkgo.It("verify route/upstream/pluginConfig items", func() {
                backendSvc, backendSvcPort := s.DefaultHTTPBackend()
                apisixRoute := fmt.Sprintf(`
 apiVersion: apisix.apache.org/v2beta3
@@ -437,6 +462,7 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(apisixRoute), "creating ApisixRoute")
                assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1))
                assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixUpstreamsCreated(1))
+               assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixPluginConfigCreated(0))
 
                routes, err := s.ListApisixRoutes()
                assert.Nil(ginkgo.GinkgoT(), err, "listing routes")
@@ -460,6 +486,10 @@ spec:
                        "managed-by": "apisix-ingress-controller",
                })
 
+               pluginConfigs, err := s.ListApisixPluginConfig()
+               assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs")
+               assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
                resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect()
                resp.Status(http.StatusOK)
                resp.Body().Contains("origin")
@@ -525,6 +555,10 @@ spec:
                assert.Equal(ginkgo.GinkgoT(), ups[0].ID, routes[0].UpstreamId)
                assert.Equal(ginkgo.GinkgoT(), ups[0].ID, routes[1].UpstreamId)
 
+               pluginConfigs, err := s.ListApisixPluginConfig()
+               assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs")
+               assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
                resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect()
                resp.Status(http.StatusOK)
                resp.Body().Contains("origin")
@@ -549,6 +583,11 @@ spec:
                assert.Len(ginkgo.GinkgoT(), ups, 1)
                assert.Equal(ginkgo.GinkgoT(), ups[0].ID, routes[0].UpstreamId)
 
+               // As httpbin service is referenced by ar2, the corresponding 
PluginConfig still doesn't exist.
+               pluginConfigs, err = s.ListApisixPluginConfig()
+               assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs")
+               assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
                resp = s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.com").Expect()
                resp.Status(http.StatusNotFound)
                resp = 
s.NewAPISIXClient().GET("/status/200").WithHeader("Host", 
"httpbin.com").Expect()
@@ -567,6 +606,10 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), err, "listing upstreams")
                assert.Len(ginkgo.GinkgoT(), ups, 0)
 
+               pluginConfigs, err = s.ListApisixPluginConfig()
+               assert.Nil(ginkgo.GinkgoT(), err, "listing pluginConfigs")
+               assert.Len(ginkgo.GinkgoT(), pluginConfigs, 0)
+
                resp = 
s.NewAPISIXClient().GET("/status/200").WithHeader("Host", 
"httpbin.com").Expect()
                resp.Status(http.StatusNotFound)
        })
diff --git a/test/e2e/plugins/plugin_config.go 
b/test/e2e/plugins/plugin_config.go
new file mode 100644
index 0000000..9b4ae47
--- /dev/null
+++ b/test/e2e/plugins/plugin_config.go
@@ -0,0 +1,546 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 plugins
+
+import (
+       "fmt"
+       "net/http"
+       "time"
+
+       "github.com/onsi/ginkgo"
+       "github.com/stretchr/testify/assert"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = ginkgo.Describe("ApisixPluginConfig", func() {
+       opts := &scaffold.Options{
+               Name:                  "default",
+               Kubeconfig:            scaffold.GetKubeconfig(),
+               APISIXConfigPath:      "testdata/apisix-gw-config.yaml",
+               IngressAPISIXReplicas: 1,
+               HTTPBinServicePort:    80,
+               APISIXRouteVersion:    "apisix.apache.org/v2beta3",
+       }
+       s := scaffold.NewScaffold(opts)
+       ginkgo.It("add crd from definition", func() {
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: echo-and-cors-apc
+spec:
+ plugins:
+ - name: echo
+   enable: true
+   config:
+    before_body: "This is the preface"
+    after_body: "This is the epilogue"
+    headers:
+     X-Foo: v1
+     X-Foo2: v2
+ - name: cors
+   enable: true
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+               err := s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+
+               time.Sleep(time.Second * 3)
+
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.org
+      paths:
+      - /ip
+    backends:
+    - serviceName: %s
+      servicePort: %d
+      weight: 10
+    plugin_config_name: echo-and-cors-apc
+`, backendSvc, backendPorts[0])
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               time.Sleep(3 * time.Second)
+               pcs, err := s.ListApisixPluginConfig()
+               assert.Nil(ginkgo.GinkgoT(), err, nil, "listing pluginConfigs")
+               assert.Len(ginkgo.GinkgoT(), pcs, 1)
+               assert.Len(ginkgo.GinkgoT(), pcs[0].Plugins, 2)
+
+               resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.org").Expect()
+               resp.Status(http.StatusOK)
+               resp.Header("X-Foo").Equal("v1")
+               resp.Header("X-Foo2").Equal("v2")
+               resp.Header("Access-Control-Allow-Origin").Equal("*")
+               resp.Header("Access-Control-Allow-Methods").Equal("*")
+               resp.Header("Access-Control-Allow-Headers").Equal("*")
+               resp.Header("Access-Control-Expose-Headers").Equal("*")
+               resp.Header("Access-Control-Max-Age").Equal("5")
+               resp.Body().Contains("This is the preface")
+               resp.Body().Contains("origin")
+               resp.Body().Contains("This is the epilogue")
+       })
+
+       ginkgo.It("ApisixPluginConfig replace body", func() {
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: echo
+   enable: true
+   config:
+    body: "my custom body"
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+   match:
+     hosts:
+     - httpbin.org
+     paths:
+       - /ip
+   backends:
+   - serviceName: %s
+     servicePort: %d
+     weight: 10
+   plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+
+               err := s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.org").Expect()
+               resp.Status(http.StatusOK)
+               resp.Body().Equal("my custom body")
+       })
+
+       ginkgo.It("disable plugin", func() {
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: echo
+   enable: false
+   config:
+    body: "my custom body"
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+   match:
+     hosts:
+     - httpbin.org
+     paths:
+       - /ip
+   backends:
+   - serviceName: %s
+     servicePort: %d
+     weight: 10
+   plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+
+               time.Sleep(6 * time.Second)
+               err := s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.org").Expect()
+               resp.Status(http.StatusOK)
+               resp.Body().Contains("origin")
+               resp.Body().NotContains("my custom body")
+       })
+
+       ginkgo.It("enable plugin and then delete it", func() {
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: echo
+   enable: true
+   config:
+    body: "my custom body"
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+   match:
+     hosts:
+     - httpbin.org
+     paths:
+       - /ip
+   backends:
+   - serviceName: %s
+     servicePort: %d
+     weight: 10
+   plugin_config_name: test-apc-1 
+`, backendSvc, backendPorts[0])
+
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+
+               err := s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.org").Expect()
+               resp.Status(http.StatusOK)
+               resp.Body().Equal("my custom body")
+
+               apc = fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: echo
+   enable: false
+   config:
+    body: "my custom body"
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+               err = s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               resp = s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.org").Expect()
+               resp.Status(http.StatusOK)
+               resp.Body().NotContains("my custom body")
+               resp.Body().Contains("origin")
+       })
+
+       ginkgo.It("empty config", func() {
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+ name: test-apc-1
+spec:
+ plugins:
+ - name: cors
+   enable: true
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+   match:
+     hosts:
+     - httpbin.org
+     paths:
+       - /ip
+   backends:
+   - serviceName: %s
+     servicePort: %d
+     weight: 10
+   plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+               err := s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.org").Expect()
+               resp.Status(http.StatusOK)
+               resp.Header("Access-Control-Allow-Origin").Equal("*")
+               resp.Header("Access-Control-Allow-Methods").Equal("*")
+               resp.Header("Access-Control-Allow-Headers").Equal("*")
+               resp.Header("Access-Control-Expose-Headers").Equal("*")
+               resp.Header("Access-Control-Max-Age").Equal("5")
+               resp.Body().Contains("origin")
+       })
+
+       ginkgo.It("finer granularity config", func() {
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+  name: test-apc-1
+spec:
+  plugins:
+    - name: cors
+      enable: true
+      config:
+        allow_origins: http://foo.bar.org
+        allow_methods: "GET,POST"
+        max_age: 3600
+        expose_headers: x-foo,x-baz
+        allow_headers: x-from-ingress
+        allow_credential: true
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+   match:
+     hosts:
+     - httpbin.org
+     paths:
+       - /ip
+   backends:
+   - serviceName: %s
+     servicePort: %d
+     weight: 10
+   plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+               err := s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               resp := s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("Origin", "http://foo.bar.org";).
+                       Expect()
+               resp.Status(http.StatusOK)
+               
resp.Header("Access-Control-Allow-Origin").Equal("http://foo.bar.org";)
+               resp.Header("Access-Control-Allow-Methods").Equal("GET,POST")
+               
resp.Header("Access-Control-Allow-Headers").Equal("x-from-ingress")
+               
resp.Header("Access-Control-Expose-Headers").Equal("x-foo,x-baz")
+               resp.Header("Access-Control-Max-Age").Equal("3600")
+               resp.Header("Access-Control-Allow-Credentials").Equal("true")
+               resp.Body().Contains("origin")
+
+               resp = s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("Origin", "http://foo.bar2.org";).
+                       Expect()
+               resp.Header("Access-Control-Allow-Methods").Empty()
+               resp.Header("Access-Control-Allow-Headers").Empty()
+               resp.Header("Access-Control-Expose-Headers").Empty()
+               resp.Header("Access-Control-Max-Age").Empty()
+               // httpbin set it by itself.
+               //resp.Header("Access-Control-Allow-Credentials").Empty()
+               resp.Body().Contains("origin")
+       })
+
+       ginkgo.It("disable plugin", func() {
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+  name: test-apc-1
+spec:
+  plugins:
+    - name: cors
+      enable: false
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+   match:
+     hosts:
+     - httpbin.org
+     paths:
+       - /ip
+   backends:
+   - serviceName: %s
+     servicePort: %d
+     weight: 10
+   plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+               err := s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.org").Expect()
+               resp.Status(http.StatusOK)
+               // httpbin sets this header by itself.
+               //resp.Header("Access-Control-Allow-Origin").Empty()
+               resp.Header("Access-Control-Allow-Methods").Empty()
+               resp.Header("Access-Control-Allow-Headers").Empty()
+               resp.Header("Access-Control-Expose-Headers").Empty()
+               resp.Header("Access-Control-Max-Age").Empty()
+               resp.Body().Contains("origin")
+       })
+
+       ginkgo.It("enable plugin and then delete it", func() {
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               apc := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+  name: test-apc-1
+spec:
+  plugins:
+  - name: cors
+    enable: true
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+   match:
+     hosts:
+     - httpbin.org
+     paths:
+       - /ip
+   backends:
+   - serviceName: %s
+     servicePort: %d
+     weight: 10
+   plugin_config_name: test-apc-1
+`, backendSvc, backendPorts[0])
+
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+               err := s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.org").Expect()
+               resp.Status(http.StatusOK)
+
+               resp.Header("Access-Control-Allow-Origin").Equal("*")
+               resp.Header("Access-Control-Allow-Methods").Equal("*")
+               resp.Header("Access-Control-Allow-Headers").Equal("*")
+               resp.Header("Access-Control-Expose-Headers").Equal("*")
+               resp.Header("Access-Control-Max-Age").Equal("5")
+               resp.Body().Contains("origin")
+
+               apc = fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixPluginConfig
+metadata:
+  name: test-apc-1
+spec:
+  plugins:
+  - name: cors
+    enable: false
+`)
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(apc))
+
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+               err = s.EnsureNumApisixUpstreamsCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
upstreams")
+               err = s.EnsureNumApisixPluginConfigCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of 
pluginConfigs")
+               err = s.EnsureNumApisixRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               resp = s.NewAPISIXClient().GET("/ip").WithHeader("Host", 
"httpbin.org").Expect()
+               resp.Status(http.StatusOK)
+
+               // httpbin sets this header by itself.
+               //resp.Header("Access-Control-Allow-Origin").Empty()
+               resp.Header("Access-Control-Allow-Methods").Empty()
+               resp.Header("Access-Control-Allow-Headers").Empty()
+               resp.Header("Access-Control-Expose-Headers").Empty()
+               resp.Header("Access-Control-Max-Age").Empty()
+               resp.Body().Contains("origin")
+       })
+})
diff --git a/test/e2e/scaffold/ingress.go b/test/e2e/scaffold/ingress.go
index 99b9ec3..6c5181c 100644
--- a/test/e2e/scaffold/ingress.go
+++ b/test/e2e/scaffold/ingress.go
@@ -168,8 +168,8 @@ rules:
       - apisixclusterconfigs/status
       - apisixconsumers
       - apisixconsumers/status
-      - apisixpluginconfig
-      - apisixpluginconfig/status
+      - apisixpluginconfigs
+      - apisixpluginconfigs/status
     verbs:
       - '*'
   - apiGroups:
diff --git a/test/e2e/scaffold/k8s.go b/test/e2e/scaffold/k8s.go
index 8362144..d6fde3d 100644
--- a/test/e2e/scaffold/k8s.go
+++ b/test/e2e/scaffold/k8s.go
@@ -240,6 +240,17 @@ func (s *Scaffold) EnsureNumApisixUpstreamsCreated(desired 
int) error {
        return s.ensureNumApisixCRDsCreated(u.String(), desired)
 }
 
+// EnsureNumApisixPluginConfigCreated waits until desired number of 
PluginConfig are created in
+// APISIX cluster.
+func (s *Scaffold) EnsureNumApisixPluginConfigCreated(desired int) error {
+       u := url.URL{
+               Scheme: "http",
+               Host:   s.apisixAdminTunnel.Endpoint(),
+               Path:   "/apisix/admin/plugin_configs",
+       }
+       return s.ensureNumApisixCRDsCreated(u.String(), desired)
+}
+
 // GetServerInfo collect server info from "/v1/server_info" (Control API) 
exposed by server-info plugin
 func (s *Scaffold) GetServerInfo() (map[string]interface{}, error) {
        u := url.URL{
@@ -397,6 +408,28 @@ func (s *Scaffold) ListApisixSsl() ([]*v1.Ssl, error) {
        return cli.Cluster("").SSL().List(context.TODO())
 }
 
+// ListApisixRoutes list all pluginConfigs from APISIX.
+func (s *Scaffold) ListApisixPluginConfig() ([]*v1.PluginConfig, error) {
+       u := url.URL{
+               Scheme: "http",
+               Host:   s.apisixAdminTunnel.Endpoint(),
+               Path:   "/apisix/admin",
+       }
+       cli, err := apisix.NewClient()
+       if err != nil {
+               return nil, err
+       }
+       err = cli.AddCluster(context.Background(), &apisix.ClusterOptions{
+               BaseURL:          u.String(),
+               AdminKey:         s.opts.APISIXAdminAPIKey,
+               MetricsCollector: metrics.NewPrometheusCollector(),
+       })
+       if err != nil {
+               return nil, err
+       }
+       return cli.Cluster("").PluginConfig().List(context.TODO())
+}
+
 func (s *Scaffold) newAPISIXTunnels() error {
        var (
                adminNodePort   int

Reply via email to