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

tokers 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 f613f36  feat: add ApisixClusterConfig controller loop (#416)
f613f36 is described below

commit f613f36bab842089ab54f681ed1b4bf126e4c162
Author: Alex Zhang <[email protected]>
AuthorDate: Fri May 7 10:06:13 2021 +0800

    feat: add ApisixClusterConfig controller loop (#416)
---
 Makefile                                           |   4 +-
 docs/en/latest/concepts/apisix_cluster_config.md   |  81 +++++++
 docs/en/latest/references/apisix_cluster_config.md |  38 ++++
 pkg/apisix/apisix.go                               |   2 +
 pkg/apisix/cluster.go                              |  37 +--
 pkg/apisix/nonexistentclient.go                    |   4 +
 pkg/config/config.go                               |   2 +-
 pkg/ingress/apisix_cluster_config.go               | 250 +++++++++++++++++++++
 pkg/ingress/controller.go                          |  75 ++++---
 pkg/kube/apisix/apis/config/v2alpha1/register.go   |   2 +
 pkg/kube/apisix/apis/config/v2alpha1/types.go      |   8 +-
 pkg/kube/translation/global_rule.go                |  47 ++++
 pkg/kube/translation/global_rule_test.go           |  53 +++++
 pkg/kube/translation/translator.go                 |   3 +
 pkg/types/apisix/v1/types.go                       |   2 +-
 samples/deploy/rbac/apisix_view_clusterrole.yaml   |   1 +
 test/e2e/features/global_rule.go                   |  73 ++++++
 test/e2e/scaffold/ingress.go                       |   5 +-
 test/e2e/scaffold/k8s.go                           |  20 ++
 19 files changed, 650 insertions(+), 57 deletions(-)

diff --git a/Makefile b/Makefile
index 2ec758f..f74ae3b 100644
--- a/Makefile
+++ b/Makefile
@@ -68,9 +68,7 @@ unit-test:
 ### e2e-test:             Run e2e test cases (kind is required)
 .PHONY: e2e-test
 e2e-test: ginkgo-check push-images-to-kind
-       kubectl apply -f $(PWD)/samples/deploy/crd/v1beta1/ApisixRoute.yaml
-       kubectl apply -f $(PWD)/samples/deploy/crd/v1beta1/ApisixUpstream.yaml
-       kubectl apply -f $(PWD)/samples/deploy/crd/v1beta1/ApisixTls.yaml
+       kubectl apply -k $(PWD)/samples/deploy/crd/v1beta1
        cd test/e2e && ginkgo -cover -coverprofile=coverage.txt -r 
--randomizeSuites --randomizeAllSpecs --trace -p --nodes=$(E2E_CONCURRENCY)
 
 .PHONY: ginkgo-check
diff --git a/docs/en/latest/concepts/apisix_cluster_config.md 
b/docs/en/latest/concepts/apisix_cluster_config.md
new file mode 100644
index 0000000..1c98edb
--- /dev/null
+++ b/docs/en/latest/concepts/apisix_cluster_config.md
@@ -0,0 +1,81 @@
+---
+title: ApisixClusterConfig
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+`ApisixClusterConfig` is a CRD resource which used to describe an APISIX 
cluster, currently it's not a required
+resource but its existence can enrich an APISIX cluster, for instance, 
enabling cluster-wide monitoring, rate limiting and so on.
+
+monitoring features like collecting [Prometheus](https://prometheus.io/) 
metrics
+and [skywalking](https://skywalking.apache.org/) spans
+
+Monitoring
+----------
+
+By default, monitoring are not enabled for the APISIX cluster, this is not 
favorable
+if you'd like to learn the real running status of your cluster. In such a 
case, you
+could create a `ApisixClusterConfig` to enable these features explicitly.
+
+```yaml
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixClusterConfig
+metadata:
+  name: default
+spec:
+  monitoring:
+    prometheus:
+      enable: true
+    skywalking:
+      enable: true
+      sampleRatio: 0.5
+```
+
+The above example enables both the Prometheus and Skywalking for the APISIX 
cluster which name is "default".
+Please see [Prometheus in 
APISIX](http://apisix.apache.org/docs/apisix/plugins/prometheus) and 
[Skywalking in APISIX](http://apisix.apache.org/docs/apisix/plugins/skywalking) 
for the details.
+
+Admin Config
+------------
+
+The default APISIX cluster is configured through command line options like 
`--default-apisix-cluster-xxx`. They are constant in 
apisix-ingress-controller's lifecycle, you have to change the definition
+of Deployment or Pod template. Now with the help of `ApisixClusterConfig`, you 
can change some administrative fields on it.
+
+```yaml
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixClusterConfig
+metadata:
+  name: default
+spec:
+  admin:
+    baseURL: http://apisix-gw.default.svc.cluster.local:9180/apisix/admin
+    adminKey: "123456"
+```
+
+The above `ApisixClusterConfig` sets the base url and admin key for the APISIX 
cluster `"default"`. Once this
+resource is processed, resources like Route, Upstream and others will be 
pushed to the new address with the new admin key (for authentication).
+
+Multiple Clusters Management
+----------------------------
+
+`ApisixClusterConfig` is also designed for supporting multiple clusters 
management, but currently this function IS NOT ENABLED YET.
+Only the `ApisixClusterConfig` with the same named configured in 
`--default-apisix-cluster-name` option will be handled by 
apisix-ingress-controller, other instances will be neglected.
+
+The current delete event for `ApisixClusterConfig` doesn't mean the 
apisix-ingress-controller will lose the view of the corresponding APISIX 
cluster but
+resetting all the features on it, so the running of APISIX cluster is not 
influenced by this event.
diff --git a/docs/en/latest/references/apisix_cluster_config.md 
b/docs/en/latest/references/apisix_cluster_config.md
new file mode 100644
index 0000000..d4e3850
--- /dev/null
+++ b/docs/en/latest/references/apisix_cluster_config.md
@@ -0,0 +1,38 @@
+---
+title: ApisixRoute/v2alpha1 Reference
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Spec
+
+Spec describes the desired state of an ApisixClusterConfig object.
+
+|     Field     |  Type    |     Description       |
+|---------------|----------|-----------------------|
+| monitoring | object | Monitoring settings. |
+| monitoring.prometheus | object | Prometheus settings. |
+| monitoring.prometheus.enable | boolean | Whether to enable Prometheus or 
not. |
+| monitoring.skywalking | object | Skywalking settings. |
+| monitoring.skywalking.enable | boolean | Whether to enable Skywalking or 
not. |
+| monitoring.skywalking.sampleRatio | number | The sample ratio for spans, 
value should be in `[0, 1]`.|
+| admin | object | Administrative settings. |
+| admin.baseURL | string | the base url for APISIX cluster. |
+| admin.AdminKey | string | admin key used for authentication with APISIX 
cluster. |
diff --git a/pkg/apisix/apisix.go b/pkg/apisix/apisix.go
index e6c346c..a33c716 100644
--- a/pkg/apisix/apisix.go
+++ b/pkg/apisix/apisix.go
@@ -44,6 +44,8 @@ type Cluster interface {
        SSL() SSL
        // StreamRoute returns a StreamRoute interface that can operate 
StreamRoute resources.
        StreamRoute() StreamRoute
+       // GlobalRule returns a GlobalRule interface that can operate 
GlobalRule resources.
+       GlobalRule() GlobalRule
        // String exposes the client information in human readable format.
        String() string
        // HasSynced checks whether all resources in APISIX cluster is synced 
to cache.
diff --git a/pkg/apisix/cluster.go b/pkg/apisix/cluster.go
index bc31ee8..8ca93c7 100644
--- a/pkg/apisix/cluster.go
+++ b/pkg/apisix/cluster.go
@@ -280,23 +280,28 @@ func (c *cluster) StreamRoute() StreamRoute {
        return c.streamRoute
 }
 
-func (s *cluster) applyAuth(req *http.Request) {
-       if s.adminKey != "" {
-               req.Header.Set("X-API-Key", s.adminKey)
+// GlobalRule implements Cluster.GlobalRule method.
+func (c *cluster) GlobalRule() GlobalRule {
+       return c.globalRules
+}
+
+func (c *cluster) applyAuth(req *http.Request) {
+       if c.adminKey != "" {
+               req.Header.Set("X-API-Key", c.adminKey)
        }
 }
 
-func (s *cluster) do(req *http.Request) (*http.Response, error) {
-       s.applyAuth(req)
-       return s.cli.Do(req)
+func (c *cluster) do(req *http.Request) (*http.Response, error) {
+       c.applyAuth(req)
+       return c.cli.Do(req)
 }
 
-func (s *cluster) getResource(ctx context.Context, url string) (*getResponse, 
error) {
+func (c *cluster) getResource(ctx context.Context, url string) (*getResponse, 
error) {
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
        if err != nil {
                return nil, err
        }
-       resp, err := s.do(req)
+       resp, err := c.do(req)
        if err != nil {
                return nil, err
        }
@@ -320,12 +325,12 @@ func (s *cluster) getResource(ctx context.Context, url 
string) (*getResponse, er
        return &res, nil
 }
 
-func (s *cluster) listResource(ctx context.Context, url string) 
(*listResponse, error) {
+func (c *cluster) listResource(ctx context.Context, url string) 
(*listResponse, error) {
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
        if err != nil {
                return nil, err
        }
-       resp, err := s.do(req)
+       resp, err := c.do(req)
        if err != nil {
                return nil, err
        }
@@ -345,12 +350,12 @@ func (s *cluster) listResource(ctx context.Context, url 
string) (*listResponse,
        return &list, nil
 }
 
-func (s *cluster) createResource(ctx context.Context, url string, body 
io.Reader) (*createResponse, error) {
+func (c *cluster) createResource(ctx context.Context, url string, body 
io.Reader) (*createResponse, error) {
        req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, body)
        if err != nil {
                return nil, err
        }
-       resp, err := s.do(req)
+       resp, err := c.do(req)
        if err != nil {
                return nil, err
        }
@@ -371,12 +376,12 @@ func (s *cluster) createResource(ctx context.Context, url 
string, body io.Reader
        return &cr, nil
 }
 
-func (s *cluster) updateResource(ctx context.Context, url string, body 
io.Reader) (*updateResponse, error) {
+func (c *cluster) updateResource(ctx context.Context, url string, body 
io.Reader) (*updateResponse, error) {
        req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, body)
        if err != nil {
                return nil, err
        }
-       resp, err := s.do(req)
+       resp, err := c.do(req)
        if err != nil {
                return nil, err
        }
@@ -395,12 +400,12 @@ func (s *cluster) updateResource(ctx context.Context, url 
string, body io.Reader
        return &ur, nil
 }
 
-func (s *cluster) deleteResource(ctx context.Context, url string) error {
+func (c *cluster) deleteResource(ctx context.Context, url string) error {
        req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
        if err != nil {
                return err
        }
-       resp, err := s.do(req)
+       resp, err := c.do(req)
        if err != nil {
                return err
        }
diff --git a/pkg/apisix/nonexistentclient.go b/pkg/apisix/nonexistentclient.go
index d965cda..d1a2c45 100644
--- a/pkg/apisix/nonexistentclient.go
+++ b/pkg/apisix/nonexistentclient.go
@@ -172,6 +172,10 @@ func (nc *nonExistentCluster) StreamRoute() StreamRoute {
        return nc.streamRoute
 }
 
+func (nc *nonExistentCluster) GlobalRule() GlobalRule {
+       return nc.globalRule
+}
+
 func (nc *nonExistentCluster) HasSynced(_ context.Context) error {
        return nil
 }
diff --git a/pkg/config/config.go b/pkg/config/config.go
index aa7f681..86ba8dc 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -91,7 +91,7 @@ type APISIXConfig struct {
        BaseURL string `json:"base_url" yaml:"base_url"`
        // AdminKey is same to DefaultClusterAdminKey.
        // Deprecated: use DefaultClusterAdminKey instead. AdminKey will be 
removed
-       //      // once v1.0.0 is released.
+       // once v1.0.0 is released.
        AdminKey string `json:"admin_key" yaml:"admin_key"`
 }
 
diff --git a/pkg/ingress/apisix_cluster_config.go 
b/pkg/ingress/apisix_cluster_config.go
new file mode 100644
index 0000000..c275a3c
--- /dev/null
+++ b/pkg/ingress/apisix_cluster_config.go
@@ -0,0 +1,250 @@
+// 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"
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/client-go/tools/cache"
+       "k8s.io/client-go/util/workqueue"
+
+       "github.com/apache/apisix-ingress-controller/pkg/apisix"
+       configv2alpha1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
+       "github.com/apache/apisix-ingress-controller/pkg/log"
+       "github.com/apache/apisix-ingress-controller/pkg/types"
+)
+
+type apisixClusterConfigController struct {
+       controller *Controller
+       workqueue  workqueue.RateLimitingInterface
+       workers    int
+}
+
+func (c *Controller) newApisixClusterConfigController() 
*apisixClusterConfigController {
+       ctl := &apisixClusterConfigController{
+               controller: c,
+               workqueue:  
workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(time.Second,
 60*time.Second, 5), "ApisixClusterConfig"),
+               workers:    1,
+       }
+       c.apisixClusterConfigInformer.AddEventHandler(
+               cache.ResourceEventHandlerFuncs{
+                       AddFunc:    ctl.onAdd,
+                       UpdateFunc: ctl.onUpdate,
+                       DeleteFunc: ctl.onDelete,
+               },
+       )
+       return ctl
+}
+
+func (c *apisixClusterConfigController) run(ctx context.Context) {
+       log.Info("ApisixClusterConfig controller started")
+       defer log.Info("ApisixClusterConfig controller exited")
+       if ok := cache.WaitForCacheSync(ctx.Done(), 
c.controller.apisixClusterConfigInformer.HasSynced); !ok {
+               log.Error("cache sync failed")
+               return
+       }
+       for i := 0; i < c.workers; i++ {
+               go c.runWorker(ctx)
+       }
+       <-ctx.Done()
+       c.workqueue.ShutDown()
+}
+
+func (c *apisixClusterConfigController) 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 *apisixClusterConfigController) sync(ctx context.Context, ev 
*types.Event) error {
+       key := ev.Object.(string)
+       _, name, err := cache.SplitMetaNamespaceKey(key)
+       if err != nil {
+               log.Errorf("found ApisixClusterConfig resource with invalid 
meta key %s: %s", key, err)
+               return err
+       }
+       acc, err := c.controller.apisixClusterConfigLister.Get(name)
+       if err != nil {
+               if !k8serrors.IsNotFound(err) {
+                       log.Errorf("failed to get ApisixClusterConfig %s: %s", 
key, err)
+                       return err
+               }
+               if ev.Type != types.EventDelete {
+                       log.Warnf("ApisixClusterConfig %s was deleted before it 
can be delivered", key)
+                       return nil
+               }
+       }
+       if ev.Type == types.EventDelete {
+               if acc != 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.Warnf("discard the stale ApisixClusterConfig delete 
event since the %s exists", key)
+                       return nil
+               }
+               acc = ev.Tombstone.(*configv2alpha1.ApisixClusterConfig)
+       }
+
+       // Currently we don't handle multiple cluster, so only process
+       // the default apisix cluster.
+       if acc.Name != c.controller.cfg.APISIX.DefaultClusterName {
+               log.Infow("ignore non-default apisix cluster config",
+                       zap.String("default_cluster_name", 
c.controller.cfg.APISIX.DefaultClusterName),
+                       zap.Any("ApisixClusterConfig", acc),
+               )
+               return nil
+       }
+       // Cluster delete is dangerous.
+       // TODO handle delete?
+       if ev.Type == types.EventDelete {
+               log.Error("ApisixClusterConfig delete event for default apisix 
cluster will be ignored")
+               return nil
+       }
+
+       if acc.Spec.Admin != nil {
+               clusterOpts := &apisix.ClusterOptions{
+                       Name:     acc.Name,
+                       BaseURL:  acc.Spec.Admin.BaseURL,
+                       AdminKey: acc.Spec.Admin.AdminKey,
+               }
+               log.Infow("updating cluster",
+                       zap.Any("opts", clusterOpts),
+               )
+               // TODO we may first call AddCluster.
+               // Since now we already have the default cluster, we just call 
UpdateCluster.
+               if err := c.controller.apisix.UpdateCluster(clusterOpts); err 
!= nil {
+                       log.Errorw("failed to update cluster",
+                               zap.String("cluster_name", acc.Name),
+                               zap.Error(err),
+                               zap.Any("opts", clusterOpts),
+                       )
+                       return err
+               }
+       }
+
+       globalRule, err := c.controller.translator.TranslateClusterConfig(acc)
+       if err != nil {
+               // TODO add status
+               log.Errorw("failed to translate ApisixClusterConfig",
+                       zap.Error(err),
+                       zap.String("key", key),
+                       zap.Any("object", acc),
+               )
+               return err
+       }
+       log.Debugw("translated global_rule",
+               zap.Any("object", globalRule),
+       )
+
+       // TODO multiple cluster support
+       if ev.Type == types.EventAdd {
+               _, err = 
c.controller.apisix.Cluster(acc.Name).GlobalRule().Create(ctx, globalRule)
+       } else {
+               _, err = 
c.controller.apisix.Cluster(acc.Name).GlobalRule().Update(ctx, globalRule)
+       }
+       if err != nil {
+               log.Errorw("failed to reflect global_rule changes to apisix 
cluster",
+                       zap.Any("global_rule", globalRule),
+                       zap.Any("cluster", acc.Name),
+               )
+               return err
+       }
+       return nil
+}
+
+func (c *apisixClusterConfigController) handleSyncErr(obj interface{}, err 
error) {
+       if err == nil {
+               c.workqueue.Forget(obj)
+               return
+       }
+       log.Warnw("sync ApisixClusterConfig failed, will retry",
+               zap.Any("object", obj),
+               zap.Error(err),
+       )
+       c.workqueue.AddRateLimited(obj)
+}
+
+func (c *apisixClusterConfigController) onAdd(obj interface{}) {
+       key, err := cache.MetaNamespaceKeyFunc(obj)
+       if err != nil {
+               log.Errorf("found ApisixClusterConfig resource with bad meta 
key: %s", err.Error())
+               return
+       }
+       log.Debugw("ApisixClusterConfig add event arrived",
+               zap.String("key", key),
+               zap.Any("object", obj),
+       )
+
+       c.workqueue.AddRateLimited(&types.Event{
+               Type:   types.EventAdd,
+               Object: key,
+       })
+}
+
+func (c *apisixClusterConfigController) onUpdate(oldObj, newObj interface{}) {
+       prev := oldObj.(*configv2alpha1.ApisixClusterConfig)
+       curr := newObj.(*configv2alpha1.ApisixClusterConfig)
+       if prev.ResourceVersion >= curr.ResourceVersion {
+               return
+       }
+       key, err := cache.MetaNamespaceKeyFunc(newObj)
+       if err != nil {
+               log.Errorf("found ApisixClusterConfig with bad meta key: %s", 
err)
+               return
+       }
+       log.Debugw("ApisixClusterConfig update event arrived",
+               zap.Any("new object", curr),
+               zap.Any("old object", prev),
+       )
+
+       c.workqueue.AddRateLimited(&types.Event{
+               Type:   types.EventUpdate,
+               Object: key,
+       })
+}
+
+func (c *apisixClusterConfigController) onDelete(obj interface{}) {
+       acc, ok := obj.(*configv2alpha1.ApisixClusterConfig)
+       if !ok {
+               tombstone, ok := obj.(*cache.DeletedFinalStateUnknown)
+               if !ok {
+                       return
+               }
+               acc = tombstone.Obj.(*configv2alpha1.ApisixClusterConfig)
+       }
+
+       key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
+       if err != nil {
+               log.Errorf("found ApisixClusterConfig resource with bad meta 
key: %s", err)
+               return
+       }
+       log.Debugw("ApisixClusterConfig delete event arrived",
+               zap.Any("final state", acc),
+       )
+       c.workqueue.AddRateLimited(&types.Event{
+               Type:      types.EventDelete,
+               Object:    key,
+               Tombstone: acc,
+       })
+}
diff --git a/pkg/ingress/controller.go b/pkg/ingress/controller.go
index 7b2c9f9..a388e11 100644
--- a/pkg/ingress/controller.go
+++ b/pkg/ingress/controller.go
@@ -41,6 +41,7 @@ import (
        crdclientset 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned"
        
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions"
        listersv1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/listers/config/v1"
+       listersv2alpha1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/listers/config/v2alpha1"
        "github.com/apache/apisix-ingress-controller/pkg/kube/translation"
        "github.com/apache/apisix-ingress-controller/pkg/log"
        "github.com/apache/apisix-ingress-controller/pkg/metrics"
@@ -82,29 +83,32 @@ type Controller struct {
        secretSSLMap *sync.Map
 
        // common informers and listers
-       epInformer             cache.SharedIndexInformer
-       epLister               listerscorev1.EndpointsLister
-       svcInformer            cache.SharedIndexInformer
-       svcLister              listerscorev1.ServiceLister
-       ingressLister          kube.IngressLister
-       ingressInformer        cache.SharedIndexInformer
-       secretInformer         cache.SharedIndexInformer
-       secretLister           listerscorev1.SecretLister
-       apisixUpstreamInformer cache.SharedIndexInformer
-       apisixUpstreamLister   listersv1.ApisixUpstreamLister
-       apisixRouteLister      kube.ApisixRouteLister
-       apisixRouteInformer    cache.SharedIndexInformer
-       apisixTlsLister        listersv1.ApisixTlsLister
-       apisixTlsInformer      cache.SharedIndexInformer
+       epInformer                  cache.SharedIndexInformer
+       epLister                    listerscorev1.EndpointsLister
+       svcInformer                 cache.SharedIndexInformer
+       svcLister                   listerscorev1.ServiceLister
+       ingressLister               kube.IngressLister
+       ingressInformer             cache.SharedIndexInformer
+       secretInformer              cache.SharedIndexInformer
+       secretLister                listerscorev1.SecretLister
+       apisixUpstreamInformer      cache.SharedIndexInformer
+       apisixUpstreamLister        listersv1.ApisixUpstreamLister
+       apisixRouteLister           kube.ApisixRouteLister
+       apisixRouteInformer         cache.SharedIndexInformer
+       apisixTlsLister             listersv1.ApisixTlsLister
+       apisixTlsInformer           cache.SharedIndexInformer
+       apisixClusterConfigLister   listersv2alpha1.ApisixClusterConfigLister
+       apisixClusterConfigInformer cache.SharedIndexInformer
 
        // resource controllers
        endpointsController *endpointsController
        ingressController   *ingressController
        secretController    *secretController
 
-       apisixUpstreamController *apisixUpstreamController
-       apisixRouteController    *apisixRouteController
-       apisixTlsController      *apisixTlsController
+       apisixUpstreamController      *apisixUpstreamController
+       apisixRouteController         *apisixRouteController
+       apisixTlsController           *apisixTlsController
+       apisixClusterConfigController *apisixClusterConfigController
 }
 
 // NewController creates an ingress apisix controller object.
@@ -183,20 +187,22 @@ func NewController(cfg *config.Config) (*Controller, 
error) {
                secretSSLMap:       new(sync.Map),
                recorder:           eventBroadcaster.NewRecorder(scheme.Scheme, 
v1.EventSource{Component: _component}),
 
-               epInformer:             
kube.CoreSharedInformerFactory.Core().V1().Endpoints().Informer(),
-               epLister:               
kube.CoreSharedInformerFactory.Core().V1().Endpoints().Lister(),
-               svcInformer:            
kube.CoreSharedInformerFactory.Core().V1().Services().Informer(),
-               svcLister:              
kube.CoreSharedInformerFactory.Core().V1().Services().Lister(),
-               ingressLister:          ingressLister,
-               ingressInformer:        ingressInformer,
-               secretInformer:         
kube.CoreSharedInformerFactory.Core().V1().Secrets().Informer(),
-               secretLister:           
kube.CoreSharedInformerFactory.Core().V1().Secrets().Lister(),
-               apisixRouteInformer:    apisixRouteInformer,
-               apisixRouteLister:      apisixRouteLister,
-               apisixUpstreamInformer: 
sharedInformerFactory.Apisix().V1().ApisixUpstreams().Informer(),
-               apisixUpstreamLister:   
sharedInformerFactory.Apisix().V1().ApisixUpstreams().Lister(),
-               apisixTlsInformer:      
sharedInformerFactory.Apisix().V1().ApisixTlses().Informer(),
-               apisixTlsLister:        
sharedInformerFactory.Apisix().V1().ApisixTlses().Lister(),
+               epInformer:                  
kube.CoreSharedInformerFactory.Core().V1().Endpoints().Informer(),
+               epLister:                    
kube.CoreSharedInformerFactory.Core().V1().Endpoints().Lister(),
+               svcInformer:                 
kube.CoreSharedInformerFactory.Core().V1().Services().Informer(),
+               svcLister:                   
kube.CoreSharedInformerFactory.Core().V1().Services().Lister(),
+               ingressLister:               ingressLister,
+               ingressInformer:             ingressInformer,
+               secretInformer:              
kube.CoreSharedInformerFactory.Core().V1().Secrets().Informer(),
+               secretLister:                
kube.CoreSharedInformerFactory.Core().V1().Secrets().Lister(),
+               apisixRouteInformer:         apisixRouteInformer,
+               apisixRouteLister:           apisixRouteLister,
+               apisixUpstreamInformer:      
sharedInformerFactory.Apisix().V1().ApisixUpstreams().Informer(),
+               apisixUpstreamLister:        
sharedInformerFactory.Apisix().V1().ApisixUpstreams().Lister(),
+               apisixTlsInformer:           
sharedInformerFactory.Apisix().V1().ApisixTlses().Informer(),
+               apisixTlsLister:             
sharedInformerFactory.Apisix().V1().ApisixTlses().Lister(),
+               apisixClusterConfigInformer: 
sharedInformerFactory.Apisix().V2alpha1().ApisixClusterConfigs().Informer(),
+               apisixClusterConfigLister:   
sharedInformerFactory.Apisix().V2alpha1().ApisixClusterConfigs().Lister(),
        }
        c.translator = translation.NewTranslator(&translation.TranslatorOptions{
                EndpointsLister:      c.epLister,
@@ -208,6 +214,7 @@ func NewController(cfg *config.Config) (*Controller, error) 
{
        c.endpointsController = c.newEndpointsController()
        c.apisixUpstreamController = c.newApisixUpstreamController()
        c.apisixRouteController = c.newApisixRouteController()
+       c.apisixClusterConfigController = c.newApisixClusterConfigController()
        c.apisixTlsController = c.newApisixTlsController()
        c.ingressController = c.newIngressController()
        c.secretController = c.newSecretController()
@@ -350,6 +357,9 @@ func (c *Controller) run(ctx context.Context) {
                c.apisixUpstreamInformer.Run(ctx.Done())
        })
        c.goAttach(func() {
+               c.apisixClusterConfigInformer.Run(ctx.Done())
+       })
+       c.goAttach(func() {
                c.secretInformer.Run(ctx.Done())
        })
        c.goAttach(func() {
@@ -368,6 +378,9 @@ func (c *Controller) run(ctx context.Context) {
                c.apisixRouteController.run(ctx)
        })
        c.goAttach(func() {
+               c.apisixClusterConfigController.run(ctx)
+       })
+       c.goAttach(func() {
                c.apisixTlsController.run(ctx)
        })
        c.goAttach(func() {
diff --git a/pkg/kube/apisix/apis/config/v2alpha1/register.go 
b/pkg/kube/apisix/apis/config/v2alpha1/register.go
index 5af6a1b..779ef2f 100644
--- a/pkg/kube/apisix/apis/config/v2alpha1/register.go
+++ b/pkg/kube/apisix/apis/config/v2alpha1/register.go
@@ -42,6 +42,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
        scheme.AddKnownTypes(SchemeGroupVersion,
                &ApisixRoute{},
                &ApisixRouteList{},
+               &ApisixClusterConfig{},
+               &ApisixClusterConfigList{},
        )
 
        // register the type in the scheme
diff --git a/pkg/kube/apisix/apis/config/v2alpha1/types.go 
b/pkg/kube/apisix/apis/config/v2alpha1/types.go
index 2c01b6f..c96448d 100644
--- a/pkg/kube/apisix/apis/config/v2alpha1/types.go
+++ b/pkg/kube/apisix/apis/config/v2alpha1/types.go
@@ -271,10 +271,10 @@ type ApisixClusterConfigSpec struct {
 type ApisixClusterMonitoringConfig struct {
        // Prometheus is the config for using Prometheus in APISIX Cluster.
        // +optional
-       Prometheus ApisixClusterPrometheusConfig
+       Prometheus ApisixClusterPrometheusConfig `json:"prometheus" 
yaml:"prometheus"`
        // Skywalking is the config for using Skywalking in APISIX Cluster.
        // +optional
-       Skywalking ApisixClusterSkywalkingConfig
+       Skywalking ApisixClusterSkywalkingConfig `json:"skywalking" 
yaml:"skywalking"`
 }
 
 // ApisixClusterPrometheusConfig is the config for using Prometheus in APISIX 
Cluster.
@@ -295,9 +295,9 @@ type ApisixClusterSkywalkingConfig struct {
 type ApisixClusterAdminConfig struct {
        // BaseURL is the base URL for the APISIX Admin API.
        // It looks like 
"http://apisix-admin.default.svc.cluster.local:9080/apisix/admin";
-       BaseURL string
+       BaseURL string `json:"baseURL" yaml:"baseURL"`
        // AdminKey is used to verify the admin API user.
-       AdminKey string
+       AdminKey string `json:"adminKey" yaml:"adminKey"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
diff --git a/pkg/kube/translation/global_rule.go 
b/pkg/kube/translation/global_rule.go
new file mode 100644
index 0000000..d830e04
--- /dev/null
+++ b/pkg/kube/translation/global_rule.go
@@ -0,0 +1,47 @@
+// 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 (
+       "github.com/apache/apisix-ingress-controller/pkg/id"
+       configv2alpha1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
+       apisixv1 
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+type prometheusPluginConfig struct{}
+
+type skywalkingPluginConfig struct {
+       SampleRatio float64 `json:"sample_ratio,omitempty"`
+}
+
+func (t *translator) TranslateClusterConfig(acc 
*configv2alpha1.ApisixClusterConfig) (*apisixv1.GlobalRule, error) {
+       globalRule := &apisixv1.GlobalRule{
+               ID:      id.GenID(acc.Name),
+               Plugins: make(apisixv1.Plugins),
+       }
+
+       if acc.Spec.Monitoring != nil {
+               if acc.Spec.Monitoring.Prometheus.Enable {
+                       globalRule.Plugins["prometheus"] = 
&prometheusPluginConfig{}
+               }
+               if acc.Spec.Monitoring.Skywalking.Enable {
+                       globalRule.Plugins["skywalking"] = 
&skywalkingPluginConfig{
+                               SampleRatio: 
acc.Spec.Monitoring.Skywalking.SampleRatio,
+                       }
+               }
+       }
+
+       return globalRule, nil
+}
diff --git a/pkg/kube/translation/global_rule_test.go 
b/pkg/kube/translation/global_rule_test.go
new file mode 100644
index 0000000..313e649
--- /dev/null
+++ b/pkg/kube/translation/global_rule_test.go
@@ -0,0 +1,53 @@
+// 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"
+
+       "github.com/apache/apisix-ingress-controller/pkg/id"
+       configv2alpha1 
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
+)
+
+func TestTranslateClusterConfig(t *testing.T) {
+       tr := &translator{}
+
+       acc := &configv2alpha1.ApisixClusterConfig{
+               TypeMeta: metav1.TypeMeta{},
+               ObjectMeta: metav1.ObjectMeta{
+                       Name: "qa-apisix",
+               },
+               Spec: configv2alpha1.ApisixClusterConfigSpec{
+                       Monitoring: 
&configv2alpha1.ApisixClusterMonitoringConfig{
+                               Prometheus: 
configv2alpha1.ApisixClusterPrometheusConfig{
+                                       Enable: true,
+                               },
+                               Skywalking: 
configv2alpha1.ApisixClusterSkywalkingConfig{
+                                       Enable:      true,
+                                       SampleRatio: 0.5,
+                               },
+                       },
+               },
+       }
+       gr, err := tr.TranslateClusterConfig(acc)
+       assert.Nil(t, err, "translating ApisixClusterConfig")
+       assert.Equal(t, gr.ID, id.GenID("qa-apisix"), "checking global_rule id")
+       assert.Len(t, gr.Plugins, 2)
+       assert.Equal(t, gr.Plugins["prometheus"], &prometheusPluginConfig{})
+       assert.Equal(t, gr.Plugins["skywalking"], 
&skywalkingPluginConfig{SampleRatio: 0.5})
+}
diff --git a/pkg/kube/translation/translator.go 
b/pkg/kube/translation/translator.go
index 701d7dc..9742d2a 100644
--- a/pkg/kube/translation/translator.go
+++ b/pkg/kube/translation/translator.go
@@ -66,6 +66,9 @@ type Translator interface {
        TranslateRouteV2alpha1(*configv2alpha1.ApisixRoute) (*TranslateContext, 
error)
        // TranslateSSL translates the configv2alpha1.ApisixTls object into the 
APISIX SSL resource.
        TranslateSSL(*configv1.ApisixTls) (*apisixv1.Ssl, error)
+       // TranslateClusterConfig translates the 
configv2alpha1.ApisixClusterConfig object into the APISIX
+       // Global Rule resource.
+       TranslateClusterConfig(config *configv2alpha1.ApisixClusterConfig) 
(*apisixv1.GlobalRule, error)
 }
 
 // TranslatorOptions contains options to help Translator
diff --git a/pkg/types/apisix/v1/types.go b/pkg/types/apisix/v1/types.go
index 7acbf92..846182b 100644
--- a/pkg/types/apisix/v1/types.go
+++ b/pkg/types/apisix/v1/types.go
@@ -25,7 +25,7 @@ import (
 const (
        // HashOnVars means the hash scope is variable.
        HashOnVars = "vars"
-       // HashVarsCombination means the hash scope is the
+       // HashOnVarsCombination means the hash scope is the
        // variable combination.
        HashOnVarsCombination = "vars_combinations"
        // HashOnHeader means the hash scope is HTTP request
diff --git a/samples/deploy/rbac/apisix_view_clusterrole.yaml 
b/samples/deploy/rbac/apisix_view_clusterrole.yaml
index 572b063..a5f75be 100644
--- a/samples/deploy/rbac/apisix_view_clusterrole.yaml
+++ b/samples/deploy/rbac/apisix_view_clusterrole.yaml
@@ -138,6 +138,7 @@ rules:
   - apisixroutes
   - apisixupstreams
   - apisixtlses
+  - apisixclusterconfigs
   verbs:
   - get
   - list
diff --git a/test/e2e/features/global_rule.go b/test/e2e/features/global_rule.go
new file mode 100644
index 0000000..02b4440
--- /dev/null
+++ b/test/e2e/features/global_rule.go
@@ -0,0 +1,73 @@
+// 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 features
+
+import (
+       "time"
+
+       "github.com/apache/apisix-ingress-controller/pkg/id"
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+       "github.com/onsi/ginkgo"
+       "github.com/stretchr/testify/assert"
+)
+
+var _ = ginkgo.Describe("ApisixClusterConfig", func() {
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
+       ginkgo.It("enable prometheus", func() {
+               acc := `
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixClusterConfig
+metadata:
+  name: default
+spec:
+  monitoring:
+    prometheus:
+      enable: true
+`
+               err := s.CreateResourceFromString(acc)
+               assert.Nil(ginkgo.GinkgoT(), err, "creating 
ApisixClusterConfig")
+
+               defer func() {
+                       err := s.RemoveResourceByString(acc)
+                       assert.Nil(ginkgo.GinkgoT(), err)
+               }()
+
+               // Wait until the ApisixClusterConfig create event was 
delivered.
+               time.Sleep(3 * time.Second)
+
+               grs, err := s.ListApisixGlobalRules()
+               assert.Nil(ginkgo.GinkgoT(), err, "listing global_rules")
+               assert.Len(ginkgo.GinkgoT(), grs, 1)
+               assert.Equal(ginkgo.GinkgoT(), grs[0].ID, id.GenID("default"))
+               assert.Len(ginkgo.GinkgoT(), grs[0].Plugins, 1)
+               _, ok := grs[0].Plugins["prometheus"]
+               assert.Equal(ginkgo.GinkgoT(), ok, true)
+
+               resp := 
s.NewAPISIXClient().GET("/apisix/prometheus/metrics").Expect()
+               resp.Status(200)
+               resp.Body().Contains("# HELP apisix_etcd_modify_indexes Etcd 
modify index for APISIX keys")
+               resp.Body().Contains("# HELP apisix_etcd_reachable Config 
server etcd reachable from APISIX, 0 is unreachable")
+               resp.Body().Contains("# HELP apisix_node_info Info of APISIX 
node")
+       })
+})
diff --git a/test/e2e/scaffold/ingress.go b/test/e2e/scaffold/ingress.go
index 67f9343..3fdf36d 100644
--- a/test/e2e/scaffold/ingress.go
+++ b/test/e2e/scaffold/ingress.go
@@ -155,6 +155,7 @@ rules:
       - apisixupstreams
       - apisixservices
       - apisixtlses
+      - apisixclusterconfigs
     verbs:
       - get
       - list
@@ -243,7 +244,9 @@ spec:
             - stdout
             - --http-listen
             - :8080
-            - --apisix-base-url
+            - --default-apisix-cluster-name
+            - default
+            - --default-apisix-cluster-base-url
             - http://apisix-service-e2e-test:9180/apisix/admin
             - --app-namespace
             - %s
diff --git a/test/e2e/scaffold/k8s.go b/test/e2e/scaffold/k8s.go
index 37f8868..f165d8a 100644
--- a/test/e2e/scaffold/k8s.go
+++ b/test/e2e/scaffold/k8s.go
@@ -193,6 +193,26 @@ func (s *Scaffold) ListApisixUpstreams() ([]*v1.Upstream, 
error) {
        return cli.Cluster("").Upstream().List(context.TODO())
 }
 
+// ListApisixGlobalRules list all global_rules from APISIX
+func (s *Scaffold) ListApisixGlobalRules() ([]*v1.GlobalRule, 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(&apisix.ClusterOptions{
+               BaseURL: u.String(),
+       })
+       if err != nil {
+               return nil, err
+       }
+       return cli.Cluster("").GlobalRule().List(context.TODO())
+}
+
 // ListApisixRoutes list all routes from APISIX.
 func (s *Scaffold) ListApisixRoutes() ([]*v1.Route, error) {
        u := url.URL{

Reply via email to