Extract common functions and implement mwan3rule 90/3490/3
authorchengli3 <cheng1.li@intel.com>
Thu, 14 May 2020 03:18:36 +0000 (03:18 +0000)
committerchengli3 <cheng1.li@intel.com>
Thu, 21 May 2020 02:30:31 +0000 (02:30 +0000)
As we will have several crd/controllers, they have almost the same
reconcile logic. So we extract the common logic and make them as
functions. Controllers call these functions instead of code duplication.

This patch extracts common functions and implements the mwan3rule
crd/controller.

Signed-off-by: chengli3 <cheng1.li@intel.com>
Change-Id: Ie9fe7ddcac6700605dbcb48ed9d88f96981b898a

33 files changed:
platform/crd-ctrlr/README.md
platform/crd-ctrlr/examples/sdewan-controller.yaml
platform/crd-ctrlr/src/Dockerfile
platform/crd-ctrlr/src/Makefile
platform/crd-ctrlr/src/PROJECT
platform/crd-ctrlr/src/api/v1alpha1/common_types.go
platform/crd-ctrlr/src/api/v1alpha1/mwan3rule_types.go [new file with mode: 0644]
platform/crd-ctrlr/src/api/v1alpha1/zz_generated.deepcopy.go
platform/crd-ctrlr/src/basehandler/isdewanhandler.go [new file with mode: 0644]
platform/crd-ctrlr/src/cnfprovider/cnfprovider.go [deleted file]
platform/crd-ctrlr/src/cnfprovider/openprovider.go [new file with mode: 0644]
platform/crd-ctrlr/src/cnfprovider/wrtprovider.go [deleted file]
platform/crd-ctrlr/src/config/crd/bases/batch.sdewan.akraino.org_mwan3policies.yaml
platform/crd-ctrlr/src/config/crd/bases/batch.sdewan.akraino.org_mwan3rules.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/crd/kustomization.yaml
platform/crd-ctrlr/src/config/crd/patches/cainjection_in_mwan3rules.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/crd/patches/webhook_in_mwan3rules.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/rbac/mwan3policy_editor_role.yaml [deleted file]
platform/crd-ctrlr/src/config/rbac/mwan3policy_viewer_role.yaml [deleted file]
platform/crd-ctrlr/src/config/rbac/role.yaml
platform/crd-ctrlr/src/config/samples/batch_v1alpha1_mwan3policy.yaml
platform/crd-ctrlr/src/config/samples/batch_v1alpha1_mwan3rule.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/controllers/base_controller.go [new file with mode: 0644]
platform/crd-ctrlr/src/controllers/mwan3policy_controller.go
platform/crd-ctrlr/src/controllers/mwan3rule_controller.go [new file with mode: 0644]
platform/crd-ctrlr/src/controllers/suite_test.go
platform/crd-ctrlr/src/go.mod
platform/crd-ctrlr/src/go.sum
platform/crd-ctrlr/src/main.go
platform/crd-ctrlr/src/openwrt/firewall.go
platform/crd-ctrlr/src/openwrt/ipsec.go
platform/crd-ctrlr/src/openwrt/mwan3.go
platform/crd-ctrlr/src/openwrt/openwrtclient.go

index eef9ed1..825248d 100644 (file)
@@ -87,6 +87,7 @@ make gen-yaml IMG="integratedcloudnative/sdewan-controller:dev"
 - CNF image built from HuiFeng's script. I have uploaded the image at `integratedcloudnative/openwrt:dev`
 - The CNF sample deployment yaml file under sample directory (together with configmap and ovn network yaml files)
 - A runable framework with Mwan3Policy CRD and controller implemented. It means we can run the controller and add/update/delete mwan3policy rules.
+- We have extracted the common logics of controllers, and implemeted the second crd/controller with it
 
 ### What we don't have yet
 
@@ -95,6 +96,10 @@ make gen-yaml IMG="integratedcloudnative/sdewan-controller:dev"
 - Add raw webhook to implemente the label based permission system
 - Add validation webhook to validate CR
 
+### NOTEs
+
+- We need controller-runtime version at least v0.6.0 to support `GenerationChangedPredicate` which is used to prevent CR status update trigering reconcile
+
 ## References
 
 - https://book.kubebuilder.io/
index 3baf375..7f6ded9 100644 (file)
@@ -9,7 +9,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.2.4
+    controller-gen.kubebuilder.io/version: v0.2.5
   creationTimestamp: null
   name: mwan3policies.batch.sdewan.akraino.org
 spec:
@@ -65,20 +65,112 @@ spec:
         status:
           description: status subsource used for Sdewan rule CRDs
           properties:
+            appliedGeneration:
+              format: int64
+              type: integer
             appliedTime:
               format: date-time
               type: string
-            appliedVersion:
-              description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
-                of cluster Important: Run "make" to regenerate code after modifying
-                this file'
+            message:
+              type: string
+            state:
               type: string
-            inSync:
-              type: boolean
           required:
-          - appliedTime
-          - appliedVersion
-          - inSync
+          - state
+          type: object
+      type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.2.5
+  creationTimestamp: null
+  name: mwan3rules.batch.sdewan.akraino.org
+spec:
+  group: batch.sdewan.akraino.org
+  names:
+    kind: Mwan3Rule
+    listKind: Mwan3RuleList
+    plural: mwan3rules
+    singular: mwan3rule
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: Mwan3Rule is the Schema for the mwan3rules API
+      properties:
+        apiVersion:
+          description: 'APIVersion defines the versioned schema of this representation
+            of an object. Servers should convert recognized schemas to the latest
+            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+          type: string
+        kind:
+          description: 'Kind is a string value representing the REST resource this
+            object represents. Servers may infer this from the endpoint the client
+            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+          type: string
+        metadata:
+          type: object
+        spec:
+          properties:
+            dest_ip:
+              type: string
+            dest_port:
+              type: string
+            family:
+              type: string
+            policy:
+              description: Members []Mwan3Rule `json:"members"`
+              type: string
+            proto:
+              type: string
+            src_ip:
+              type: string
+            src_port:
+              type: string
+            sticky:
+              type: string
+            timeout:
+              type: string
+          required:
+          - dest_ip
+          - dest_port
+          - family
+          - policy
+          - proto
+          - src_ip
+          - src_port
+          - sticky
+          - timeout
+          type: object
+        status:
+          description: status subsource used for Sdewan rule CRDs
+          properties:
+            appliedGeneration:
+              format: int64
+              type: integer
+            appliedTime:
+              format: date-time
+              type: string
+            message:
+              type: string
+            state:
+              type: string
+          required:
+          - state
           type: object
       type: object
   version: v1alpha1
@@ -132,6 +224,22 @@ metadata:
   creationTimestamp: null
   name: sdewan-manager-role
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - apps
+  resources:
+  - deployments/status
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - batch.sdewan.akraino.org
   resources:
@@ -153,13 +261,25 @@ rules:
   - patch
   - update
 - apiGroups:
-  - extensions
+  - batch.sdewan.akraino.org
   resources:
-  - deployments
+  - mwan3rules
   verbs:
+  - create
+  - delete
   - get
   - list
+  - patch
+  - update
   - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3rules/status
+  verbs:
+  - get
+  - patch
+  - update
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
index 3d648aa..2775e84 100644 (file)
@@ -13,6 +13,7 @@ RUN go mod download
 COPY main.go main.go
 COPY api/ api/
 COPY controllers/ controllers/
+COPY basehandler/ basehandler/
 COPY cnfprovider/ cnfprovider/
 COPY openwrt/ openwrt/
 
index 527388e..d4b86fb 100644 (file)
@@ -61,7 +61,7 @@ generate: controller-gen
 
 # Build the docker image
 docker-build: test
-       docker build . -t ${IMG}
+       docker build --network=host . -t ${IMG}
 
 # Push the docker image
 docker-push:
@@ -76,7 +76,7 @@ ifeq (, $(shell which controller-gen))
        CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
        cd $$CONTROLLER_GEN_TMP_DIR ;\
        go mod init tmp ;\
-       go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.4 ;\
+       go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 ;\
        rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
        }
 CONTROLLER_GEN=$(GOBIN)/controller-gen
index dbe80e8..63278f3 100644 (file)
@@ -4,4 +4,7 @@ resources:
 - group: batch
   kind: Mwan3Policy
   version: v1alpha1
+- group: batch
+  kind: Mwan3Rule
+  version: v1alpha1
 version: "2"
index 8d59e86..7c646a2 100644 (file)
@@ -19,11 +19,23 @@ import (
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
+type SdewanState string
+
+const (
+       InSync   SdewanState = "In Sync"
+       Idle                 = "Idle"
+       Applying             = "Trying to apply"
+       Deleting             = "Being delete"
+       Unknown              = "Unknown status"
+)
+
 // status subsource used for Sdewan rule CRDs
 type SdewanStatus struct {
-       // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
-       // Important: Run "make" to regenerate code after modifying this file
-       AppliedVersion string       `json:"appliedVersion"`
-       AppliedTime    *metav1.Time `json:"appliedTime"`
-       InSync         bool         `json:"inSync"`
+       // +optional
+       AppliedGeneration int64 `json:"appliedGeneration,omitempty"`
+       // +optional
+       AppliedTime *metav1.Time `json:"appliedTime,omitempty"`
+       State       SdewanState  `json:"state"`
+       // +optional
+       Message string `json:"message,omitempty"`
 }
diff --git a/platform/crd-ctrlr/src/api/v1alpha1/mwan3rule_types.go b/platform/crd-ctrlr/src/api/v1alpha1/mwan3rule_types.go
new file mode 100644 (file)
index 0000000..327d925
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
+
+// Mwan3RuleSpec defines the desired state of Mwan3Rule
+
+type Mwan3RuleSpec struct {
+       Policy   string `json:"policy"`
+       SrcIp    string `json:"src_ip"`
+       SrcPort  string `json:"src_port"`
+       DestIp   string `json:"dest_ip"`
+       DestPort string `json:"dest_port"`
+       Proto    string `json:"proto"`
+       Family   string `json:"family"`
+       Sticky   string `json:"sticky"`
+       Timeout  string `json:"timeout"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+
+// Mwan3Rule is the Schema for the mwan3rules API
+type Mwan3Rule struct {
+       metav1.TypeMeta   `json:",inline"`
+       metav1.ObjectMeta `json:"metadata,omitempty"`
+
+       Spec   Mwan3RuleSpec `json:"spec,omitempty"`
+       Status SdewanStatus  `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// Mwan3RuleList contains a list of Mwan3Rule
+type Mwan3RuleList struct {
+       metav1.TypeMeta `json:",inline"`
+       metav1.ListMeta `json:"metadata,omitempty"`
+       Items           []Mwan3Rule `json:"items"`
+}
+
+func init() {
+       SchemeBuilder.Register(&Mwan3Rule{}, &Mwan3RuleList{})
+}
index 1e42bbb..9335503 100644 (file)
@@ -117,6 +117,80 @@ func (in *Mwan3PolicySpec) DeepCopy() *Mwan3PolicySpec {
        return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Mwan3Rule) DeepCopyInto(out *Mwan3Rule) {
+       *out = *in
+       out.TypeMeta = in.TypeMeta
+       in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+       out.Spec = in.Spec
+       in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mwan3Rule.
+func (in *Mwan3Rule) DeepCopy() *Mwan3Rule {
+       if in == nil {
+               return nil
+       }
+       out := new(Mwan3Rule)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Mwan3Rule) DeepCopyObject() runtime.Object {
+       if c := in.DeepCopy(); c != nil {
+               return c
+       }
+       return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Mwan3RuleList) DeepCopyInto(out *Mwan3RuleList) {
+       *out = *in
+       out.TypeMeta = in.TypeMeta
+       in.ListMeta.DeepCopyInto(&out.ListMeta)
+       if in.Items != nil {
+               in, out := &in.Items, &out.Items
+               *out = make([]Mwan3Rule, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mwan3RuleList.
+func (in *Mwan3RuleList) DeepCopy() *Mwan3RuleList {
+       if in == nil {
+               return nil
+       }
+       out := new(Mwan3RuleList)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Mwan3RuleList) DeepCopyObject() runtime.Object {
+       if c := in.DeepCopy(); c != nil {
+               return c
+       }
+       return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Mwan3RuleSpec) DeepCopyInto(out *Mwan3RuleSpec) {
+       *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mwan3RuleSpec.
+func (in *Mwan3RuleSpec) DeepCopy() *Mwan3RuleSpec {
+       if in == nil {
+               return nil
+       }
+       out := new(Mwan3RuleSpec)
+       in.DeepCopyInto(out)
+       return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SdewanStatus) DeepCopyInto(out *SdewanStatus) {
        *out = *in
diff --git a/platform/crd-ctrlr/src/basehandler/isdewanhandler.go b/platform/crd-ctrlr/src/basehandler/isdewanhandler.go
new file mode 100644 (file)
index 0000000..fda9ccd
--- /dev/null
@@ -0,0 +1,25 @@
+package basehandler
+
+import (
+       "context"
+       appsv1 "k8s.io/api/apps/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+
+       "sdewan.akraino.org/sdewan/openwrt"
+)
+
+type ISdewanHandler interface {
+       GetType() string
+       GetName(instance runtime.Object) string
+       GetFinalizer() string
+       GetInstance(r client.Client, ctx context.Context, req ctrl.Request) (runtime.Object, error)
+       Convert(o runtime.Object, deployment appsv1.Deployment) (openwrt.IOpenWrtObject, error)
+       IsEqual(instance1 openwrt.IOpenWrtObject, instance2 openwrt.IOpenWrtObject) bool
+       GetObject(clientInfo *openwrt.OpenwrtClientInfo, name string) (openwrt.IOpenWrtObject, error)
+       CreateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error)
+       UpdateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error)
+       DeleteObject(clientInfo *openwrt.OpenwrtClientInfo, name string) error
+       Restart(clientInfo *openwrt.OpenwrtClientInfo) (bool, error)
+}
diff --git a/platform/crd-ctrlr/src/cnfprovider/cnfprovider.go b/platform/crd-ctrlr/src/cnfprovider/cnfprovider.go
deleted file mode 100644 (file)
index b4ffd97..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package cnfprovider
-
-import (
-       sdewanv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
-)
-
-type CnfProvider interface {
-       AddUpdateMwan3Policy(*sdewanv1alpha1.Mwan3Policy) error
-       DeleteMwan3Policy(*sdewanv1alpha1.Mwan3Policy) error
-       // TODO: Add more Interfaces here
-       IsCnfReady() (bool, error)
-}
diff --git a/platform/crd-ctrlr/src/cnfprovider/openprovider.go b/platform/crd-ctrlr/src/cnfprovider/openprovider.go
new file mode 100644 (file)
index 0000000..be75c57
--- /dev/null
@@ -0,0 +1,114 @@
+package cnfprovider
+
+import (
+       "context"
+       "errors"
+       appsv1 "k8s.io/api/apps/v1"
+       corev1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       basehandler "sdewan.akraino.org/sdewan/basehandler"
+       "sdewan.akraino.org/sdewan/openwrt"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       logf "sigs.k8s.io/controller-runtime/pkg/log"
+)
+
+var log = logf.Log.WithName("OpenWrtProvider")
+
+type OpenWrtProvider struct {
+       Namespace     string
+       SdewanPurpose string
+       Deployment    appsv1.Deployment
+       K8sClient     client.Client
+}
+
+func NewOpenWrt(namespace string, sdewanPurpose string, k8sClient client.Client) (*OpenWrtProvider, error) {
+       ctx := context.Background()
+       deployments := &appsv1.DeploymentList{}
+       err := k8sClient.List(ctx, deployments, client.MatchingLabels{"sdewanPurpose": sdewanPurpose})
+       if err != nil {
+               return nil, client.IgnoreNotFound(err)
+       }
+       if len(deployments.Items) > 1 {
+               return nil, errors.New("More than one deployment exists")
+       }
+       if len(deployments.Items) < 1 {
+               // return (nil, nil) to indicate that no cnf exists
+               return nil, nil
+       }
+       return &OpenWrtProvider{namespace, sdewanPurpose, deployments.Items[0], k8sClient}, nil
+}
+
+func (p *OpenWrtProvider) AddOrUpdateObject(handler basehandler.ISdewanHandler, instance runtime.Object) (bool, error) {
+       reqLogger := log.WithValues(handler.GetType(), handler.GetName(instance), "cnf", p.Deployment.Name)
+       ctx := context.Background()
+       podList := &corev1.PodList{}
+       err := p.K8sClient.List(ctx, podList, client.MatchingLabels{"sdewanPurpose": p.SdewanPurpose})
+       if err != nil {
+               return false, err
+       }
+       new_instance, err := handler.Convert(instance, p.Deployment)
+       if err != nil {
+               return false, err
+       }
+       cnfChanged := false
+       for _, pod := range podList.Items {
+               clientInfo := &openwrt.OpenwrtClientInfo{Ip: pod.Status.PodIP, User: "root", Password: ""}
+               runtime_instance, err := handler.GetObject(clientInfo, new_instance.GetName())
+               changed := false
+
+               if err != nil {
+                       _, err := handler.CreateObject(clientInfo, new_instance)
+                       if err != nil {
+                               return false, err
+                       }
+                       changed = true
+               } else if handler.IsEqual(runtime_instance, new_instance) {
+                       reqLogger.Info("Equal to the runtime instance, so no update")
+               } else {
+                       _, err := handler.UpdateObject(clientInfo, new_instance)
+                       if err != nil {
+                               return false, err
+                       }
+                       changed = true
+               }
+               if changed {
+                       _, err = handler.Restart(clientInfo)
+                       if err != nil {
+                               return changed, err
+                       }
+                       cnfChanged = true
+               }
+       }
+       // We say the AddUpdate succeed only when the add/update for all pods succeed
+       return cnfChanged, nil
+}
+
+func (p *OpenWrtProvider) DeleteObject(handler basehandler.ISdewanHandler, instance runtime.Object) (bool, error) {
+       reqLogger := log.WithValues(handler.GetType(), handler.GetName(instance), "cnf", p.Deployment.Name)
+       ctx := context.Background()
+       podList := &corev1.PodList{}
+       err := p.K8sClient.List(ctx, podList, client.MatchingLabels{"sdewanPurpose": p.SdewanPurpose})
+       if err != nil {
+               return false, err
+       }
+       cnfChanged := false
+       for _, pod := range podList.Items {
+               clientInfo := &openwrt.OpenwrtClientInfo{Ip: pod.Status.PodIP, User: "root", Password: ""}
+               runtime_instance, _ := handler.GetObject(clientInfo, handler.GetName(instance))
+               if runtime_instance == nil {
+                       reqLogger.Info("Runtime instance doesn't exist, so don't have to delete")
+               } else {
+                       err = handler.DeleteObject(clientInfo, handler.GetName(instance))
+                       if err != nil {
+                               return false, err
+                       }
+                       _, err = handler.Restart(clientInfo)
+                       if err != nil {
+                               return false, err
+                       }
+                       cnfChanged = true
+               }
+       }
+       // We say the deletioni succeed only when the deletion for all pods succeed
+       return cnfChanged, nil
+}
diff --git a/platform/crd-ctrlr/src/cnfprovider/wrtprovider.go b/platform/crd-ctrlr/src/cnfprovider/wrtprovider.go
deleted file mode 100644 (file)
index 63e514f..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-package cnfprovider
-
-import (
-       "context"
-       "encoding/json"
-       "errors"
-       "fmt"
-       appsv1 "k8s.io/api/apps/v1"
-       corev1 "k8s.io/api/core/v1"
-       "reflect"
-       sdewanv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
-       "sdewan.akraino.org/sdewan/openwrt"
-       "sigs.k8s.io/controller-runtime/pkg/client"
-       logf "sigs.k8s.io/controller-runtime/pkg/log"
-       "strconv"
-)
-
-var log = logf.Log.WithName("wrt_provider")
-
-type WrtProvider struct {
-       Namespace     string
-       SdewanPurpose string
-       Deployment    appsv1.Deployment
-       K8sClient     client.Client
-}
-
-func NewWrt(namespace string, sdewanPurpose string, k8sClient client.Client) (*WrtProvider, error) {
-       reqLogger := log.WithValues("namespace", namespace, "sdewanPurpose", sdewanPurpose)
-       ctx := context.Background()
-       deployments := &appsv1.DeploymentList{}
-       err := k8sClient.List(ctx, deployments, client.MatchingLabels{"sdewanPurpose": sdewanPurpose})
-       if err != nil {
-               reqLogger.Error(err, "Failed to get cnf deployment")
-               return nil, client.IgnoreNotFound(err)
-       }
-       if len(deployments.Items) <= 0 {
-               reqLogger.Info("No deployment exists")
-               return nil, nil
-       }
-       if len(deployments.Items) > 1 {
-               reqLogger.Error(nil, "More than one deployment exists")
-               return nil, errors.New("More than one deployment exists")
-       }
-
-       return &WrtProvider{namespace, sdewanPurpose, deployments.Items[0], k8sClient}, nil
-}
-
-func (p *WrtProvider) net2iface(net string) (string, error) {
-       type Iface struct {
-               DefaultGateway bool `json:"defaultGateway,string"`
-               Interface      string
-               Name           string
-       }
-       type NfnNet struct {
-               Type      string
-               Interface []Iface
-       }
-       ann := p.Deployment.Spec.Template.Annotations
-       nfnNet := NfnNet{}
-       err := json.Unmarshal([]byte(ann["k8s.plugin.opnfv.org/nfn-network"]), &nfnNet)
-       if err != nil {
-               return "", err
-       }
-       for _, iface := range nfnNet.Interface {
-               if iface.Name == net {
-                       return iface.Interface, nil
-               }
-       }
-       return "", errors.New(fmt.Sprintf("No matched network in annotation: %s", net))
-
-}
-
-func (p *WrtProvider) convertCrd(mwan3Policy *sdewanv1alpha1.Mwan3Policy) (*openwrt.SdewanPolicy, error) {
-       members := make([]openwrt.SdewanMember, len(mwan3Policy.Spec.Members))
-       for i, membercr := range mwan3Policy.Spec.Members {
-               iface, err := p.net2iface(membercr.Network)
-               if err != nil {
-                       return nil, err
-               }
-               members[i] = openwrt.SdewanMember{
-                       Interface: iface,
-                       Metric:    strconv.Itoa(membercr.Metric),
-                       Weight:    strconv.Itoa(membercr.Weight),
-               }
-       }
-       return &openwrt.SdewanPolicy{Name: mwan3Policy.Name, Members: members}, nil
-
-}
-
-func (p *WrtProvider) AddUpdateMwan3Policy(mwan3Policy *sdewanv1alpha1.Mwan3Policy) (bool, error) {
-       reqLogger := log.WithValues("Mwan3Policy", mwan3Policy.Name, "cnf", p.Deployment.Name)
-       ctx := context.Background()
-       podList := &corev1.PodList{}
-       err := p.K8sClient.List(ctx, podList, client.MatchingLabels{"sdewanPurpose": p.SdewanPurpose})
-       if err != nil {
-               reqLogger.Error(err, "Failed to get cnf pod list")
-               return false, err
-       }
-       policy, err := p.convertCrd(mwan3Policy)
-       if err != nil {
-               reqLogger.Error(err, "Failed to convert mwan3Policy CR")
-               return false, err
-       }
-       cnfChanged := false
-       for _, pod := range podList.Items {
-               openwrtClient := openwrt.NewOpenwrtClient(pod.Status.PodIP, "root", "")
-               mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
-               service := openwrt.ServiceClient{OpenwrtClient: openwrtClient}
-               runtimePolicy, _ := mwan3.GetPolicy(policy.Name)
-               changed := false
-               if runtimePolicy == nil {
-                       _, err := mwan3.CreatePolicy(*policy)
-                       if err != nil {
-                               reqLogger.Error(err, "Failed to create policy")
-                               return false, err
-                       }
-                       changed = true
-               } else if reflect.DeepEqual(*runtimePolicy, *policy) {
-                       reqLogger.Info("Equal to the runtime policy, so no update")
-               } else {
-                       _, err := mwan3.UpdatePolicy(*policy)
-                       if err != nil {
-                               reqLogger.Error(err, "Failed to update policy")
-                               return false, err
-                       }
-                       changed = true
-               }
-               if changed {
-                       _, err = service.ExecuteService("mwan3", "restart")
-                       if err != nil {
-                               reqLogger.Error(err, "Failed to restart mwan3 service")
-                               return changed, err
-                       }
-                       cnfChanged = true
-               }
-       }
-       // We say the AddUpdate succeed only when the add/update for all pods succeed
-       return cnfChanged, nil
-}
-
-func (p *WrtProvider) DeleteMwan3Policy(mwan3Policy *sdewanv1alpha1.Mwan3Policy) (bool, error) {
-       reqLogger := log.WithValues("Mwan3Policy", mwan3Policy.Name, "cnf", p.Deployment.Name)
-       ctx := context.Background()
-       podList := &corev1.PodList{}
-       err := p.K8sClient.List(ctx, podList, client.MatchingLabels{"sdewanPurpose": p.SdewanPurpose})
-       if err != nil {
-               reqLogger.Error(err, "Failed to get pod list")
-               return false, err
-       }
-       cnfChanged := false
-       for _, pod := range podList.Items {
-               openwrtClient := openwrt.NewOpenwrtClient(pod.Status.PodIP, "root", "")
-               mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
-               service := openwrt.ServiceClient{OpenwrtClient: openwrtClient}
-               runtimePolicy, _ := mwan3.GetPolicy(mwan3Policy.Name)
-               if runtimePolicy == nil {
-                       reqLogger.Info("Runtime policy doesn't exist, so don't have to delete")
-               } else {
-                       err = mwan3.DeletePolicy(mwan3Policy.Name)
-                       if err != nil {
-                               reqLogger.Error(err, "Failed to delete policy")
-                               return false, err
-                       }
-                       _, err = service.ExecuteService("mwan3", "restart")
-                       if err != nil {
-                               reqLogger.Error(err, "Failed to restart mwan3 service")
-                               return false, err
-                       }
-                       cnfChanged = true
-               }
-       }
-       // We say the deletioni succeed only when the deletion for all pods succeed
-       return cnfChanged, nil
-}
index 09b4f5e..27b7c9f 100644 (file)
@@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
 kind: CustomResourceDefinition
 metadata:
   annotations:
-    controller-gen.kubebuilder.io/version: v0.2.4
+    controller-gen.kubebuilder.io/version: v0.2.5
   creationTimestamp: null
   name: mwan3policies.batch.sdewan.akraino.org
 spec:
@@ -60,20 +60,18 @@ spec:
         status:
           description: status subsource used for Sdewan rule CRDs
           properties:
+            appliedGeneration:
+              format: int64
+              type: integer
             appliedTime:
               format: date-time
               type: string
-            appliedVersion:
-              description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
-                of cluster Important: Run "make" to regenerate code after modifying
-                this file'
+            message:
+              type: string
+            state:
               type: string
-            inSync:
-              type: boolean
           required:
-          - appliedTime
-          - appliedVersion
-          - inSync
+          - state
           type: object
       type: object
   version: v1alpha1
diff --git a/platform/crd-ctrlr/src/config/crd/bases/batch.sdewan.akraino.org_mwan3rules.yaml b/platform/crd-ctrlr/src/config/crd/bases/batch.sdewan.akraino.org_mwan3rules.yaml
new file mode 100644 (file)
index 0000000..c66780c
--- /dev/null
@@ -0,0 +1,95 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.2.5
+  creationTimestamp: null
+  name: mwan3rules.batch.sdewan.akraino.org
+spec:
+  group: batch.sdewan.akraino.org
+  names:
+    kind: Mwan3Rule
+    listKind: Mwan3RuleList
+    plural: mwan3rules
+    singular: mwan3rule
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: Mwan3Rule is the Schema for the mwan3rules API
+      properties:
+        apiVersion:
+          description: 'APIVersion defines the versioned schema of this representation
+            of an object. Servers should convert recognized schemas to the latest
+            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+          type: string
+        kind:
+          description: 'Kind is a string value representing the REST resource this
+            object represents. Servers may infer this from the endpoint the client
+            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+          type: string
+        metadata:
+          type: object
+        spec:
+          properties:
+            dest_ip:
+              type: string
+            dest_port:
+              type: string
+            family:
+              type: string
+            policy:
+              description: Members []Mwan3Rule `json:"members"`
+              type: string
+            proto:
+              type: string
+            src_ip:
+              type: string
+            src_port:
+              type: string
+            sticky:
+              type: string
+            timeout:
+              type: string
+          required:
+          - dest_ip
+          - dest_port
+          - family
+          - policy
+          - proto
+          - src_ip
+          - src_port
+          - sticky
+          - timeout
+          type: object
+        status:
+          description: status subsource used for Sdewan rule CRDs
+          properties:
+            appliedGeneration:
+              format: int64
+              type: integer
+            appliedTime:
+              format: date-time
+              type: string
+            message:
+              type: string
+            state:
+              type: string
+          required:
+          - state
+          type: object
+      type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
index ead3f7d..3535427 100644 (file)
@@ -3,17 +3,20 @@
 # It should be run by config/default
 resources:
 - bases/batch.sdewan.akraino.org_mwan3policies.yaml
+- bases/batch.sdewan.akraino.org_mwan3rules.yaml
 # +kubebuilder:scaffold:crdkustomizeresource
 
 patchesStrategicMerge:
 # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
 # patches here are for enabling the conversion webhook for each CRD
 #- patches/webhook_in_mwan3policies.yaml
+#- patches/webhook_in_mwan3rules.yaml
 # +kubebuilder:scaffold:crdkustomizewebhookpatch
 
 # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
 # patches here are for enabling the CA injection for each CRD
 #- patches/cainjection_in_mwan3policies.yaml
+#- patches/cainjection_in_mwan3rules.yaml
 # +kubebuilder:scaffold:crdkustomizecainjectionpatch
 
 # the following config is for teaching kustomize how to do kustomization for CRDs.
diff --git a/platform/crd-ctrlr/src/config/crd/patches/cainjection_in_mwan3rules.yaml b/platform/crd-ctrlr/src/config/crd/patches/cainjection_in_mwan3rules.yaml
new file mode 100644 (file)
index 0000000..d55b6dd
--- /dev/null
@@ -0,0 +1,8 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+  name: mwan3rules.batch.sdewan.akraino.org
diff --git a/platform/crd-ctrlr/src/config/crd/patches/webhook_in_mwan3rules.yaml b/platform/crd-ctrlr/src/config/crd/patches/webhook_in_mwan3rules.yaml
new file mode 100644 (file)
index 0000000..ba59503
--- /dev/null
@@ -0,0 +1,17 @@
+# The following patch enables conversion webhook for CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: mwan3rules.batch.sdewan.akraino.org
+spec:
+  conversion:
+    strategy: Webhook
+    webhookClientConfig:
+      # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
+      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
+      caBundle: Cg==
+      service:
+        namespace: system
+        name: webhook-service
+        path: /convert
diff --git a/platform/crd-ctrlr/src/config/rbac/mwan3policy_editor_role.yaml b/platform/crd-ctrlr/src/config/rbac/mwan3policy_editor_role.yaml
deleted file mode 100644 (file)
index 6a4fa45..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-# permissions to do edit mwan3policies.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: mwan3policy-editor-role
-rules:
-- apiGroups:
-  - batch.sdewan.akraino.org
-  resources:
-  - mwan3policies
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - batch.sdewan.akraino.org
-  resources:
-  - mwan3policies/status
-  verbs:
-  - get
-  - patch
-  - update
diff --git a/platform/crd-ctrlr/src/config/rbac/mwan3policy_viewer_role.yaml b/platform/crd-ctrlr/src/config/rbac/mwan3policy_viewer_role.yaml
deleted file mode 100644 (file)
index 98c70b6..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-# permissions to do viewer mwan3policies.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: mwan3policy-viewer-role
-rules:
-- apiGroups:
-  - batch.sdewan.akraino.org
-  resources:
-  - mwan3policies
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - batch.sdewan.akraino.org
-  resources:
-  - mwan3policies/status
-  verbs:
-  - get
index d5f67ee..0874466 100644 (file)
@@ -6,6 +6,22 @@ metadata:
   creationTimestamp: null
   name: manager-role
 rules:
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - apps
+  resources:
+  - deployments/status
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - batch.sdewan.akraino.org
   resources:
@@ -27,10 +43,22 @@ rules:
   - patch
   - update
 - apiGroups:
-  - extensions
+  - batch.sdewan.akraino.org
   resources:
-  - deployments
+  - mwan3rules
   verbs:
+  - create
+  - delete
   - get
   - list
+  - patch
+  - update
   - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3rules/status
+  verbs:
+  - get
+  - patch
+  - update
index fd6cb16..604ee43 100644 (file)
@@ -8,7 +8,7 @@ metadata:
 spec: 
   members:
     - network: ovn-net1
-      weight: 4
+      weight: 2
       metric: 2
     - network: ovn-net2
       weight: 3
diff --git a/platform/crd-ctrlr/src/config/samples/batch_v1alpha1_mwan3rule.yaml b/platform/crd-ctrlr/src/config/samples/batch_v1alpha1_mwan3rule.yaml
new file mode 100644 (file)
index 0000000..55b0e6f
--- /dev/null
@@ -0,0 +1,19 @@
+apiVersion: batch.sdewan.akraino.org/v1alpha1
+kind: Mwan3Rule
+metadata:
+  name: sample
+  namespace: default
+  labels:
+    sdewanPurpose: cnf1
+spec:
+  # Add fields here
+  dest_ip: "10.10.10.1"
+  dest_port: "1000"
+  family: ipv4
+  policy: balance1
+  proto: udp
+  src_ip: "10.10.10.10"
+  src_port: "22"
+  sticky: "1"
+  timeout: "200"
+
diff --git a/platform/crd-ctrlr/src/controllers/base_controller.go b/platform/crd-ctrlr/src/controllers/base_controller.go
new file mode 100644 (file)
index 0000000..b21aa60
--- /dev/null
@@ -0,0 +1,223 @@
+package controllers
+
+import (
+       "context"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "github.com/go-logr/logr"
+       errs "k8s.io/apimachinery/pkg/api/errors"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       "reflect"
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       "time"
+
+       appsv1 "k8s.io/api/apps/v1"
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+       "sdewan.akraino.org/sdewan/basehandler"
+       "sdewan.akraino.org/sdewan/cnfprovider"
+)
+
+// Helper functions to check and remove string from a slice of strings.
+func containsString(slice []string, s string) bool {
+       for _, item := range slice {
+               if item == s {
+                       return true
+               }
+       }
+       return false
+}
+
+func removeString(slice []string, s string) (result []string) {
+       for _, item := range slice {
+               if item == s {
+                       continue
+               }
+               result = append(result, item)
+       }
+       return
+}
+
+func getPurpose(instance runtime.Object) string {
+       value := reflect.ValueOf(instance)
+       field := reflect.Indirect(value).FieldByName("Labels")
+       labels := field.Interface().(map[string]string)
+       return labels["sdewanPurpose"]
+}
+
+func getDeletionTempstamp(instance runtime.Object) *metav1.Time {
+       value := reflect.ValueOf(instance)
+       field := reflect.Indirect(value).FieldByName("DeletionTimestamp")
+       return field.Interface().(*metav1.Time)
+}
+
+func getFinalizers(instance runtime.Object) []string {
+       value := reflect.ValueOf(instance)
+       field := reflect.Indirect(value).FieldByName("Finalizers")
+       return field.Interface().([]string)
+}
+
+func setStatus(instance runtime.Object, status batchv1alpha1.SdewanStatus) {
+       value := reflect.ValueOf(instance)
+       field_status := reflect.Indirect(value).FieldByName("Status")
+       if status.State == batchv1alpha1.InSync {
+               field_gv := reflect.Indirect(value).FieldByName("Generation")
+               status.AppliedGeneration = field_gv.Interface().(int64)
+               status.AppliedTime = &metav1.Time{Time: time.Now()}
+               status.Message = ""
+       } else {
+               status.AppliedGeneration = 0
+               status.AppliedTime = nil
+       }
+       field_status.Set(reflect.ValueOf(status))
+}
+
+func appendFinalizer(instance runtime.Object, item string) {
+       value := reflect.ValueOf(instance)
+       field := reflect.Indirect(value).FieldByName("ObjectMeta")
+       base_obj := field.Interface().(metav1.ObjectMeta)
+       base_obj.Finalizers = append(base_obj.Finalizers, item)
+       field.Set(reflect.ValueOf(base_obj))
+}
+
+func removeFinalizer(instance runtime.Object, item string) {
+       value := reflect.ValueOf(instance)
+       field := reflect.Indirect(value).FieldByName("ObjectMeta")
+       base_obj := field.Interface().(metav1.ObjectMeta)
+       base_obj.Finalizers = removeString(base_obj.Finalizers, item)
+       field.Set(reflect.ValueOf(base_obj))
+}
+
+func net2iface(net string, deployment appsv1.Deployment) (string, error) {
+       type Iface struct {
+               DefaultGateway bool `json:"defaultGateway,string"`
+               Interface      string
+               Name           string
+       }
+       type NfnNet struct {
+               Type      string
+               Interface []Iface
+       }
+       ann := deployment.Spec.Template.Annotations
+       nfnNet := NfnNet{}
+       err := json.Unmarshal([]byte(ann["k8s.plugin.opnfv.org/nfn-network"]), &nfnNet)
+       if err != nil {
+               return "", err
+       }
+       for _, iface := range nfnNet.Interface {
+               if iface.Name == net {
+                       return iface.Interface, nil
+               }
+       }
+       return "", errors.New(fmt.Sprintf("No matched network in annotation: %s", net))
+}
+
+// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch
+// +kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;list;watch
+
+// Common Reconcile Processing
+func ProcessReconcile(r client.Client, logger logr.Logger, req ctrl.Request, handler basehandler.ISdewanHandler) (ctrl.Result, error) {
+       ctx := context.Background()
+       log := logger.WithValues(handler.GetType(), req.NamespacedName)
+       during, _ := time.ParseDuration("5s")
+
+       instance, err := handler.GetInstance(r, ctx, req)
+       if err != nil {
+               if errs.IsNotFound(err) {
+                       // No instance
+                       return ctrl.Result{}, nil
+               }
+               // Error reading the object - requeue the request.
+               return ctrl.Result{RequeueAfter: during}, nil
+       }
+       purpose := getPurpose(instance)
+       cnf, err := cnfprovider.NewOpenWrt(req.NamespacedName.Namespace, purpose, r)
+       if err != nil {
+               log.Error(err, "Failed to get cnf")
+               setStatus(instance, batchv1alpha1.SdewanStatus{State: batchv1alpha1.Unknown, Message: err.Error()})
+               err = r.Status().Update(ctx, instance)
+               if err != nil {
+                       log.Error(err, "Failed to update status for "+handler.GetType())
+                       return ctrl.Result{}, err
+               }
+               // A new event are supposed to be received upon cnf ready
+               // so not requeue
+               return ctrl.Result{}, nil
+       }
+       finalizerName := handler.GetFinalizer()
+       delete_timestamp := getDeletionTempstamp(instance)
+
+       if delete_timestamp.IsZero() {
+               // creating or updating CR
+               if cnf == nil {
+                       // no cnf exists
+                       log.Info("No cnf exist, so not create/update " + handler.GetType())
+                       return ctrl.Result{}, nil
+               }
+               changed, err := cnf.AddOrUpdateObject(handler, instance)
+               if err != nil {
+                       log.Error(err, "Failed to add/update "+handler.GetType())
+                       setStatus(instance, batchv1alpha1.SdewanStatus{State: batchv1alpha1.Applying, Message: err.Error()})
+                       err = r.Status().Update(ctx, instance)
+                       if err != nil {
+                               log.Error(err, "Failed to update status for "+handler.GetType())
+                               return ctrl.Result{}, err
+                       }
+                       return ctrl.Result{RequeueAfter: during}, nil
+               }
+               finalizers := getFinalizers(instance)
+               if !containsString(finalizers, finalizerName) {
+                       log.Info("Adding finalizer for " + handler.GetType())
+                       appendFinalizer(instance, finalizerName)
+                       if err := r.Update(ctx, instance); err != nil {
+                               return ctrl.Result{}, err
+                       }
+               }
+               if changed {
+                       setStatus(instance, batchv1alpha1.SdewanStatus{State: batchv1alpha1.InSync})
+
+                       err = r.Status().Update(ctx, instance)
+                       if err != nil {
+                               log.Error(err, "Failed to update status for "+handler.GetType())
+                               return ctrl.Result{}, err
+                       }
+               }
+       } else {
+               // deletin CR
+               if cnf == nil {
+                       // no cnf exists
+                       finalizers := getFinalizers(instance)
+                       if containsString(finalizers, finalizerName) {
+                               // instance.ObjectMeta.Finalizers = removeString(instance.ObjectMeta.Finalizers, finalizerName)
+                               removeFinalizer(instance, finalizerName)
+                               if err := r.Update(ctx, instance); err != nil {
+                                       return ctrl.Result{}, err
+                               }
+                       }
+                       return ctrl.Result{}, nil
+               }
+               _, err := cnf.DeleteObject(handler, instance)
+
+               if err != nil {
+                       log.Error(err, "Failed to delete "+handler.GetType())
+                       setStatus(instance, batchv1alpha1.SdewanStatus{State: batchv1alpha1.Deleting, Message: err.Error()})
+                       err = r.Status().Update(ctx, instance)
+                       if err != nil {
+                               log.Error(err, "Failed to update status for "+handler.GetType())
+                               return ctrl.Result{}, err
+                       }
+                       return ctrl.Result{RequeueAfter: during}, nil
+               }
+               finalizers := getFinalizers(instance)
+               if containsString(finalizers, finalizerName) {
+                       removeFinalizer(instance, finalizerName)
+                       if err := r.Update(ctx, instance); err != nil {
+                               return ctrl.Result{}, err
+                       }
+               }
+       }
+
+       return ctrl.Result{}, nil
+}
index 842f316..531e942 100644 (file)
@@ -17,133 +17,118 @@ package controllers
 
 import (
        "context"
-       "time"
+       "reflect"
+       "strconv"
 
        "github.com/go-logr/logr"
-       "k8s.io/apimachinery/pkg/api/errors"
-       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       appsv1 "k8s.io/api/apps/v1"
        "k8s.io/apimachinery/pkg/runtime"
        ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/builder"
        "sigs.k8s.io/controller-runtime/pkg/client"
+       "sigs.k8s.io/controller-runtime/pkg/predicate"
 
        batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
-       "sdewan.akraino.org/sdewan/cnfprovider"
+       "sdewan.akraino.org/sdewan/openwrt"
 )
 
-// Mwan3PolicyReconciler reconciles a Mwan3Policy object
-type Mwan3PolicyReconciler struct {
-       client.Client
-       Log    logr.Logger
-       Scheme *runtime.Scheme
+var mwan3PolicyHandler = new(Mwan3PolicyHandler)
+
+type Mwan3PolicyHandler struct {
 }
 
-// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=mwan3policies,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=mwan3policies/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups=extensions,resources=deployments,verbs=get;list;watch
+func (m *Mwan3PolicyHandler) GetType() string {
+       return "Mwan3Policy"
+}
 
-func (r *Mwan3PolicyReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
-       ctx := context.Background()
-       log := r.Log.WithValues("mwan3policy", req.NamespacedName)
+func (m *Mwan3PolicyHandler) GetName(instance runtime.Object) string {
+       policy := instance.(*batchv1alpha1.Mwan3Policy)
+       return policy.Name
+}
 
-       // your logic here
-       during, _ := time.ParseDuration("5s")
+func (m *Mwan3PolicyHandler) GetFinalizer() string {
+       return "rule.finalizers.sdewan.akraino.org"
+}
+
+func (m *Mwan3PolicyHandler) GetInstance(r client.Client, ctx context.Context, req ctrl.Request) (runtime.Object, error) {
        instance := &batchv1alpha1.Mwan3Policy{}
        err := r.Get(ctx, req.NamespacedName, instance)
-       if err != nil {
-               if errors.IsNotFound(err) {
-                       // No instance
-                       return ctrl.Result{}, nil
-               }
-               // Error reading the object - requeue the request.
-               return ctrl.Result{RequeueAfter: during}, nil
-       }
-       cnf, err := cnfprovider.NewWrt(req.NamespacedName.Namespace, instance.Labels["sdewanPurpose"], r.Client)
-       if err != nil {
-               log.Error(err, "Failed to get cnf")
-               // A new event are supposed to be received upon cnf ready
-               // so not requeue
-               return ctrl.Result{}, nil
-       }
-       finalizerName := "rule.finalizers.sdewan.akraino.org"
-       if instance.ObjectMeta.DeletionTimestamp.IsZero() {
-               // creating or updating CR
-               if cnf == nil {
-                       // no cnf exists
-                       log.Info("No cnf exist, so not create/update mwan3 policy")
-                       return ctrl.Result{}, nil
-               }
-               changed, err := cnf.AddUpdateMwan3Policy(instance)
-               if err != nil {
-                       log.Error(err, "Failed to add/update mwan3 policy")
-                       return ctrl.Result{RequeueAfter: during}, nil
-               }
-               if !containsString(instance.ObjectMeta.Finalizers, finalizerName) {
-                       log.Info("Adding finalizer for mwan3 policy")
-                       instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, finalizerName)
-                       if err := r.Update(ctx, instance); err != nil {
-                               return ctrl.Result{}, err
-                       }
-               }
-               if changed {
-                       instance.Status.AppliedVersion = instance.ResourceVersion
-                       instance.Status.AppliedTime = &metav1.Time{Time: time.Now()}
-                       instance.Status.InSync = true
-                       err = r.Status().Update(ctx, instance)
-                       if err != nil {
-                               log.Error(err, "Failed to update mwan3 policy status")
-                               return ctrl.Result{}, err
-                       }
-               }
-       } else {
-               // deletin CR
-               if cnf == nil {
-                       // no cnf exists
-                       if containsString(instance.ObjectMeta.Finalizers, finalizerName) {
-                               instance.ObjectMeta.Finalizers = removeString(instance.ObjectMeta.Finalizers, finalizerName)
-                               if err := r.Update(ctx, instance); err != nil {
-                                       return ctrl.Result{}, err
-                               }
-                       }
-                       return ctrl.Result{}, nil
-               }
-               _, err := cnf.DeleteMwan3Policy(instance)
+       return instance, err
+}
+
+func (m *Mwan3PolicyHandler) Convert(instance runtime.Object, deployment appsv1.Deployment) (openwrt.IOpenWrtObject, error) {
+       policy := instance.(*batchv1alpha1.Mwan3Policy)
+       members := make([]openwrt.SdewanMember, len(policy.Spec.Members))
+       for i, membercr := range policy.Spec.Members {
+               iface, err := net2iface(membercr.Network, deployment)
                if err != nil {
-                       log.Error(err, "Failed to delete mwan3 policy")
-                       return ctrl.Result{RequeueAfter: during}, nil
+                       return nil, err
                }
-               if containsString(instance.ObjectMeta.Finalizers, finalizerName) {
-                       instance.ObjectMeta.Finalizers = removeString(instance.ObjectMeta.Finalizers, finalizerName)
-                       if err := r.Update(ctx, instance); err != nil {
-                               return ctrl.Result{}, err
-                       }
+               members[i] = openwrt.SdewanMember{
+                       Interface: iface,
+                       Metric:    strconv.Itoa(membercr.Metric),
+                       Weight:    strconv.Itoa(membercr.Weight),
                }
        }
+       return &openwrt.SdewanPolicy{Name: policy.Name, Members: members}, nil
+}
 
-       return ctrl.Result{}, nil
+func (m *Mwan3PolicyHandler) IsEqual(instance1 openwrt.IOpenWrtObject, instance2 openwrt.IOpenWrtObject) bool {
+       policy1 := instance1.(*openwrt.SdewanPolicy)
+       policy2 := instance2.(*openwrt.SdewanPolicy)
+       return reflect.DeepEqual(*policy1, *policy2)
 }
 
-func (r *Mwan3PolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
-       return ctrl.NewControllerManagedBy(mgr).
-               For(&batchv1alpha1.Mwan3Policy{}).
-               Complete(r)
+func (m *Mwan3PolicyHandler) GetObject(clientInfo *openwrt.OpenwrtClientInfo, name string) (openwrt.IOpenWrtObject, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
+       ret, err := mwan3.GetPolicy(name)
+       return ret, err
 }
 
-// Helper functions to check and remove string from a slice of strings.
-func containsString(slice []string, s string) bool {
-       for _, item := range slice {
-               if item == s {
-                       return true
-               }
-       }
-       return false
+func (m *Mwan3PolicyHandler) CreateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
+       policy := instance.(*openwrt.SdewanPolicy)
+       return mwan3.CreatePolicy(*policy)
 }
 
-func removeString(slice []string, s string) (result []string) {
-       for _, item := range slice {
-               if item == s {
-                       continue
-               }
-               result = append(result, item)
-       }
-       return
+func (m *Mwan3PolicyHandler) UpdateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
+       policy := instance.(*openwrt.SdewanPolicy)
+       return mwan3.UpdatePolicy(*policy)
+}
+
+func (m *Mwan3PolicyHandler) DeleteObject(clientInfo *openwrt.OpenwrtClientInfo, name string) error {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
+       return mwan3.DeletePolicy(name)
+}
+
+func (m *Mwan3PolicyHandler) Restart(clientInfo *openwrt.OpenwrtClientInfo) (bool, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       service := openwrt.ServiceClient{OpenwrtClient: openwrtClient}
+       return service.ExecuteService("mwan3", "restart")
+}
+
+// Mwan3PolicyReconciler reconciles a Mwan3Policy object
+type Mwan3PolicyReconciler struct {
+       client.Client
+       Log    logr.Logger
+       Scheme *runtime.Scheme
+}
+
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=mwan3policies,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=mwan3policies/status,verbs=get;update;patch
+
+func (r *Mwan3PolicyReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
+       return ProcessReconcile(r, r.Log, req, mwan3PolicyHandler)
+}
+
+func (r *Mwan3PolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
+       ps := builder.WithPredicates(predicate.GenerationChangedPredicate{})
+       return ctrl.NewControllerManagedBy(mgr).
+               For(&batchv1alpha1.Mwan3Policy{}, ps).
+               Complete(r)
 }
diff --git a/platform/crd-ctrlr/src/controllers/mwan3rule_controller.go b/platform/crd-ctrlr/src/controllers/mwan3rule_controller.go
new file mode 100644 (file)
index 0000000..a89a946
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+       "context"
+       "reflect"
+
+       "github.com/go-logr/logr"
+       appsv1 "k8s.io/api/apps/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/builder"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       "sigs.k8s.io/controller-runtime/pkg/predicate"
+
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+       "sdewan.akraino.org/sdewan/openwrt"
+)
+
+var mwan3RuleHandler = new(Mwan3RuleHandler)
+
+type Mwan3RuleHandler struct {
+}
+
+func (m *Mwan3RuleHandler) GetType() string {
+       return "Mwan3Rule"
+}
+
+func (m *Mwan3RuleHandler) GetName(instance runtime.Object) string {
+       Rule := instance.(*batchv1alpha1.Mwan3Rule)
+       return Rule.Name
+}
+
+func (m *Mwan3RuleHandler) GetFinalizer() string {
+       return "rule.finalizers.sdewan.akraino.org"
+}
+
+func (m *Mwan3RuleHandler) GetInstance(r client.Client, ctx context.Context, req ctrl.Request) (runtime.Object, error) {
+       instance := &batchv1alpha1.Mwan3Rule{}
+       err := r.Get(ctx, req.NamespacedName, instance)
+       return instance, err
+}
+
+func (m *Mwan3RuleHandler) Convert(instance runtime.Object, deployment appsv1.Deployment) (openwrt.IOpenWrtObject, error) {
+       rule := instance.(*batchv1alpha1.Mwan3Rule)
+       openwrtrule := openwrt.SdewanRule{
+               Name:     rule.Name,
+               Policy:   rule.Spec.Policy,
+               SrcIp:    rule.Spec.SrcIp,
+               SrcPort:  rule.Spec.SrcPort,
+               DestIp:   rule.Spec.DestIp,
+               DestPort: rule.Spec.DestPort,
+               Proto:    rule.Spec.Proto,
+               Family:   rule.Spec.Family,
+               Sticky:   rule.Spec.Sticky,
+               Timeout:  rule.Spec.Timeout,
+       }
+       return &openwrtrule, nil
+}
+
+func (m *Mwan3RuleHandler) IsEqual(instance1 openwrt.IOpenWrtObject, instance2 openwrt.IOpenWrtObject) bool {
+       Rule1 := instance1.(*openwrt.SdewanRule)
+       Rule2 := instance2.(*openwrt.SdewanRule)
+       return reflect.DeepEqual(*Rule1, *Rule2)
+}
+
+func (m *Mwan3RuleHandler) GetObject(clientInfo *openwrt.OpenwrtClientInfo, name string) (openwrt.IOpenWrtObject, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
+       ret, err := mwan3.GetRule(name)
+       return ret, err
+}
+
+func (m *Mwan3RuleHandler) CreateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
+       Rule := instance.(*openwrt.SdewanRule)
+       return mwan3.CreateRule(*Rule)
+}
+
+func (m *Mwan3RuleHandler) UpdateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
+       Rule := instance.(*openwrt.SdewanRule)
+       return mwan3.UpdateRule(*Rule)
+}
+
+func (m *Mwan3RuleHandler) DeleteObject(clientInfo *openwrt.OpenwrtClientInfo, name string) error {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
+       return mwan3.DeleteRule(name)
+}
+
+func (m *Mwan3RuleHandler) Restart(clientInfo *openwrt.OpenwrtClientInfo) (bool, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       service := openwrt.ServiceClient{OpenwrtClient: openwrtClient}
+       return service.ExecuteService("mwan3", "restart")
+}
+
+// Mwan3RuleReconciler reconciles a Mwan3Rule object
+type Mwan3RuleReconciler struct {
+       client.Client
+       Log    logr.Logger
+       Scheme *runtime.Scheme
+}
+
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=mwan3rules,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=mwan3rules/status,verbs=get;update;patch
+
+func (r *Mwan3RuleReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
+       return ProcessReconcile(r, r.Log, req, mwan3RuleHandler)
+}
+
+func (r *Mwan3RuleReconciler) SetupWithManager(mgr ctrl.Manager) error {
+       ps := builder.WithPredicates(predicate.GenerationChangedPredicate{})
+       return ctrl.NewControllerManagedBy(mgr).
+               For(&batchv1alpha1.Mwan3Rule{}, ps).
+               Complete(r)
+}
index f02eb83..b93ab6e 100644 (file)
@@ -24,11 +24,13 @@ import (
 
        "k8s.io/client-go/kubernetes/scheme"
        "k8s.io/client-go/rest"
-       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
        "sigs.k8s.io/controller-runtime/pkg/client"
        "sigs.k8s.io/controller-runtime/pkg/envtest"
+       "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
        logf "sigs.k8s.io/controller-runtime/pkg/log"
        "sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
        // +kubebuilder:scaffold:imports
 )
 
@@ -44,7 +46,7 @@ func TestAPIs(t *testing.T) {
 
        RunSpecsWithDefaultAndCustomReporters(t,
                "Controller Suite",
-               []Reporter{envtest.NewlineReporter{}})
+               []Reporter{printer.NewlineReporter{}})
 }
 
 var _ = BeforeSuite(func(done Done) {
@@ -63,6 +65,9 @@ var _ = BeforeSuite(func(done Done) {
        err = batchv1alpha1.AddToScheme(scheme.Scheme)
        Expect(err).NotTo(HaveOccurred())
 
+       err = batchv1alpha1.AddToScheme(scheme.Scheme)
+       Expect(err).NotTo(HaveOccurred())
+
        // +kubebuilder:scaffold:scheme
 
        k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
index 4b67fd8..1c397da 100644 (file)
@@ -4,10 +4,10 @@ go 1.14
 
 require (
        github.com/go-logr/logr v0.1.0
-       github.com/onsi/ginkgo v1.8.0
-       github.com/onsi/gomega v1.5.0
-       k8s.io/api v0.0.0-20190918155943-95b840bb6a1f
-       k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655
-       k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90
-       sigs.k8s.io/controller-runtime v0.4.0
+       github.com/onsi/ginkgo v1.11.0
+       github.com/onsi/gomega v1.8.1
+       k8s.io/api v0.18.2
+       k8s.io/apimachinery v0.18.2
+        k8s.io/client-go v0.18.2
+       sigs.k8s.io/controller-runtime v0.6.0
 )
index 9e3159d..6881908 100644 (file)
@@ -15,16 +15,27 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@@ -33,8 +44,11 @@ github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHo
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -42,19 +56,31 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
 github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
 github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
@@ -63,42 +89,62 @@ github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70t
 github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
+github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
 github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
 github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
 github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
 github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
 github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
 github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
 github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
+github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
 github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
+github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
 github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
 github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
+github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
 github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
 github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.19.2 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE=
 github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
+github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
 github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
+github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
 github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
 github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE=
 github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
 github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
+github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
 github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE=
 github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -108,12 +154,15 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
 github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
+github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -121,14 +170,19 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
 github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
 github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
 github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
 github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -138,16 +192,22 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
 github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -158,7 +218,13 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
 github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4=
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -171,16 +237,25 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
 github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
 github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
+github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
+github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -191,26 +266,48 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
 github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
+github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
 github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
 github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -219,26 +316,46 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
 github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
 golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
+golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -254,15 +371,22 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
 golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
+golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@@ -276,7 +400,10 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -286,18 +413,27 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
 golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU=
+golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -305,7 +441,9 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
@@ -322,60 +460,119 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 k8s.io/api v0.0.0-20190918155943-95b840bb6a1f h1:8FRUST8oUkEI45WYKyD8ed7Ad0Kg5v11zHyPkEVb2xo=
 k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
+k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc=
+k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
+k8s.io/api v0.18.2 h1:wG5g5ZmSVgm5B+eHMIbI9EGATS2L8Z72rda19RIEgY8=
+k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
 k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 h1:V6ndwCPoao1yZ52agqOKaUAl7DYWVGiXjV7ePA2i610=
 k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
+k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss=
+k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
+k8s.io/apiextensions-apiserver v0.18.2 h1:I4v3/jAuQC+89L3Z7dDgAiN4EOjN6sbm6iBqQwHTah8=
+k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
 k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 h1:CS1tBQz3HOXiseWZu6ZicKX361CZLT97UFnnPx0aqBw=
 k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
+k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4=
+k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
+k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA=
+k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
 k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
+k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
+k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
 k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 h1:mLmhKUm1X+pXu0zXMEzNsOF5E2kKFGe5o6BZBIIqA6A=
 k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
+k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc=
+k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI=
+k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE=
+k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
+k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E=
+k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
+k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
 k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
+k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
+k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
 k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
+k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
+k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM=
 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=
 k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
+k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
+k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJDVK9qPLhM+sRHYanNKw0EQ=
 k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
+k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM=
+k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
 k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE=
 k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
+k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
+k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
 modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
 modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
 modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
 modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
+sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
 sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
 sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
+sigs.k8s.io/controller-runtime v0.5.0 h1:CbqIy5fbUX+4E9bpnBFd204YAzRYlM9SWW77BbrcDQo=
+sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8=
+sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM=
+sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
+sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
+sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
+sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
+sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
+sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
 sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=
 sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
 sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
index b67302d..fbb543c 100644 (file)
@@ -22,10 +22,11 @@ import (
        "k8s.io/apimachinery/pkg/runtime"
        clientgoscheme "k8s.io/client-go/kubernetes/scheme"
        _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
-       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
-       "sdewan.akraino.org/sdewan/controllers"
        ctrl "sigs.k8s.io/controller-runtime"
        "sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+       "sdewan.akraino.org/sdewan/controllers"
        // +kubebuilder:scaffold:imports
 )
 
@@ -57,6 +58,7 @@ func main() {
                Scheme:             scheme,
                MetricsBindAddress: metricsAddr,
                LeaderElection:     enableLeaderElection,
+               LeaderElectionID:   "controller-leader-election-helper",
                Port:               9443,
        })
        if err != nil {
@@ -72,6 +74,14 @@ func main() {
                setupLog.Error(err, "unable to create controller", "controller", "Mwan3Policy")
                os.Exit(1)
        }
+       if err = (&controllers.Mwan3RuleReconciler{
+               Client: mgr.GetClient(),
+               Log:    ctrl.Log.WithName("controllers").WithName("Mwan3Rule"),
+               Scheme: mgr.GetScheme(),
+       }).SetupWithManager(mgr); err != nil {
+               setupLog.Error(err, "unable to create controller", "controller", "Mwan3Rule")
+               os.Exit(1)
+       }
        // +kubebuilder:scaffold:builder
 
        setupLog.Info("starting manager")
index 4bc16a7..0cacfa4 100644 (file)
@@ -115,7 +115,7 @@ func (f *FirewallClient) GetZones() (*SdewanFirewallZones, error) {
 func (m *FirewallClient) GetZone(zone string) (*SdewanFirewallZone, error) {
        var response string
        var err error
-       response, err = m.OpenwrtClient.Get(firewallBaseURL + "zone/" + zone)
+       response, err = m.OpenwrtClient.Get(firewallBaseURL + "zones/" + zone)
        if err != nil {
                return nil, err
        }
@@ -134,7 +134,7 @@ func (m *FirewallClient) CreateZone(zone SdewanFirewallZone) (*SdewanFirewallZon
        var response string
        var err error
        zone_obj, _ := json.Marshal(zone)
-       response, err = m.OpenwrtClient.Post(firewallBaseURL+"zone", string(zone_obj))
+       response, err = m.OpenwrtClient.Post(firewallBaseURL+"zones", string(zone_obj))
        if err != nil {
                return nil, err
        }
@@ -150,7 +150,7 @@ func (m *FirewallClient) CreateZone(zone SdewanFirewallZone) (*SdewanFirewallZon
 
 // delete zone
 func (m *FirewallClient) DeleteZone(zone_name string) error {
-       _, err := m.OpenwrtClient.Delete(firewallBaseURL + "zone/" + zone_name)
+       _, err := m.OpenwrtClient.Delete(firewallBaseURL + "zones/" + zone_name)
        if err != nil {
                return err
        }
@@ -164,7 +164,7 @@ func (m *FirewallClient) UpdateZone(zone SdewanFirewallZone) (*SdewanFirewallZon
        var err error
        zone_obj, _ := json.Marshal(zone)
        zone_name := zone.Name
-       response, err = m.OpenwrtClient.Put(firewallBaseURL+"zone/"+zone_name, string(zone_obj))
+       response, err = m.OpenwrtClient.Put(firewallBaseURL+"zones/"+zone_name, string(zone_obj))
        if err != nil {
                return nil, err
        }
@@ -201,7 +201,7 @@ func (f *FirewallClient) GetRules() (*SdewanFirewallRules, error) {
 func (m *FirewallClient) GetRule(rule string) (*SdewanFirewallRule, error) {
        var response string
        var err error
-       response, err = m.OpenwrtClient.Get(firewallBaseURL + "rule/" + rule)
+       response, err = m.OpenwrtClient.Get(firewallBaseURL + "rules/" + rule)
        if err != nil {
                return nil, err
        }
@@ -220,7 +220,7 @@ func (m *FirewallClient) CreateRule(rule SdewanFirewallRule) (*SdewanFirewallRul
        var response string
        var err error
        rule_obj, _ := json.Marshal(rule)
-       response, err = m.OpenwrtClient.Post(firewallBaseURL+"rule", string(rule_obj))
+       response, err = m.OpenwrtClient.Post(firewallBaseURL+"rules", string(rule_obj))
        if err != nil {
                return nil, err
        }
@@ -236,7 +236,7 @@ func (m *FirewallClient) CreateRule(rule SdewanFirewallRule) (*SdewanFirewallRul
 
 // delete rule
 func (m *FirewallClient) DeleteRule(rule_name string) error {
-       _, err := m.OpenwrtClient.Delete(firewallBaseURL + "rule/" + rule_name)
+       _, err := m.OpenwrtClient.Delete(firewallBaseURL + "rules/" + rule_name)
        if err != nil {
                return err
        }
@@ -250,7 +250,7 @@ func (m *FirewallClient) UpdateRule(rule SdewanFirewallRule) (*SdewanFirewallRul
        var err error
        rule_obj, _ := json.Marshal(rule)
        rule_name := rule.Name
-       response, err = m.OpenwrtClient.Put(firewallBaseURL+"rule/"+rule_name, string(rule_obj))
+       response, err = m.OpenwrtClient.Put(firewallBaseURL+"rules/"+rule_name, string(rule_obj))
        if err != nil {
                return nil, err
        }
@@ -287,7 +287,7 @@ func (f *FirewallClient) GetForwardings() (*SdewanFirewallForwardings, error) {
 func (m *FirewallClient) GetForwarding(forwarding string) (*SdewanFirewallForwarding, error) {
        var response string
        var err error
-       response, err = m.OpenwrtClient.Get(firewallBaseURL + "forwarding/" + forwarding)
+       response, err = m.OpenwrtClient.Get(firewallBaseURL + "forwardings/" + forwarding)
        if err != nil {
                return nil, err
        }
@@ -306,7 +306,7 @@ func (m *FirewallClient) CreateForwarding(forwarding SdewanFirewallForwarding) (
        var response string
        var err error
        forwarding_obj, _ := json.Marshal(forwarding)
-       response, err = m.OpenwrtClient.Post(firewallBaseURL+"forwarding", string(forwarding_obj))
+       response, err = m.OpenwrtClient.Post(firewallBaseURL+"forwardings", string(forwarding_obj))
        if err != nil {
                return nil, err
        }
@@ -322,7 +322,7 @@ func (m *FirewallClient) CreateForwarding(forwarding SdewanFirewallForwarding) (
 
 // delete forwarding
 func (m *FirewallClient) DeleteForwarding(forwarding_name string) error {
-       _, err := m.OpenwrtClient.Delete(firewallBaseURL + "forwarding/" + forwarding_name)
+       _, err := m.OpenwrtClient.Delete(firewallBaseURL + "forwardings/" + forwarding_name)
        if err != nil {
                return err
        }
@@ -336,7 +336,7 @@ func (m *FirewallClient) UpdateForwarding(forwarding SdewanFirewallForwarding) (
        var err error
        forwarding_obj, _ := json.Marshal(forwarding)
        forwarding_name := forwarding.Name
-       response, err = m.OpenwrtClient.Put(firewallBaseURL+"forwarding/"+forwarding_name, string(forwarding_obj))
+       response, err = m.OpenwrtClient.Put(firewallBaseURL+"forwardings/"+forwarding_name, string(forwarding_obj))
        if err != nil {
                return nil, err
        }
@@ -373,7 +373,7 @@ func (f *FirewallClient) GetRedirects() (*SdewanFirewallRedirects, error) {
 func (m *FirewallClient) GetRedirect(redirect string) (*SdewanFirewallRedirect, error) {
        var response string
        var err error
-       response, err = m.OpenwrtClient.Get(firewallBaseURL + "redirect/" + redirect)
+       response, err = m.OpenwrtClient.Get(firewallBaseURL + "redirects/" + redirect)
        if err != nil {
                return nil, err
        }
@@ -392,7 +392,7 @@ func (m *FirewallClient) CreateRedirect(redirect SdewanFirewallRedirect) (*Sdewa
        var response string
        var err error
        redirect_obj, _ := json.Marshal(redirect)
-       response, err = m.OpenwrtClient.Post(firewallBaseURL+"redirect", string(redirect_obj))
+       response, err = m.OpenwrtClient.Post(firewallBaseURL+"redirects", string(redirect_obj))
        if err != nil {
                return nil, err
        }
@@ -408,7 +408,7 @@ func (m *FirewallClient) CreateRedirect(redirect SdewanFirewallRedirect) (*Sdewa
 
 // delete redirect
 func (m *FirewallClient) DeleteRedirect(redirect_name string) error {
-       _, err := m.OpenwrtClient.Delete(firewallBaseURL + "redirect/" + redirect_name)
+       _, err := m.OpenwrtClient.Delete(firewallBaseURL + "redirects/" + redirect_name)
        if err != nil {
                return err
        }
@@ -422,7 +422,7 @@ func (m *FirewallClient) UpdateRedirect(redirect SdewanFirewallRedirect) (*Sdewa
        var err error
        redirect_obj, _ := json.Marshal(redirect)
        redirect_name := redirect.Name
-       response, err = m.OpenwrtClient.Put(firewallBaseURL+"redirect/"+redirect_name, string(redirect_obj))
+       response, err = m.OpenwrtClient.Put(firewallBaseURL+"redirects/"+redirect_name, string(redirect_obj))
        if err != nil {
                return nil, err
        }
index 5175bd6..9d6e7b1 100644 (file)
@@ -83,7 +83,7 @@ func (f *IpsecClient) GetProposals() (*SdewanIpsecProposals, error) {
 func (m *IpsecClient) GetProposal(proposal string) (*SdewanIpsecProposal, error) {
        var response string
        var err error
-       response, err = m.OpenwrtClient.Get(ipsecBaseURL + "proposal/" + proposal)
+       response, err = m.OpenwrtClient.Get(ipsecBaseURL + "proposals/" + proposal)
        if err != nil {
                return nil, err
        }
@@ -102,7 +102,7 @@ func (m *IpsecClient) CreateProposal(proposal SdewanIpsecProposal) (*SdewanIpsec
        var response string
        var err error
        proposal_obj, _ := json.Marshal(proposal)
-       response, err = m.OpenwrtClient.Post(ipsecBaseURL+"proposal", string(proposal_obj))
+       response, err = m.OpenwrtClient.Post(ipsecBaseURL+"proposals", string(proposal_obj))
        if err != nil {
                return nil, err
        }
@@ -118,7 +118,7 @@ func (m *IpsecClient) CreateProposal(proposal SdewanIpsecProposal) (*SdewanIpsec
 
 // delete proposal
 func (m *IpsecClient) DeleteProposal(proposal_name string) error {
-       _, err := m.OpenwrtClient.Delete(ipsecBaseURL + "proposal/" + proposal_name)
+       _, err := m.OpenwrtClient.Delete(ipsecBaseURL + "proposals/" + proposal_name)
        if err != nil {
                return err
        }
@@ -132,7 +132,7 @@ func (m *IpsecClient) UpdateProposal(proposal SdewanIpsecProposal) (*SdewanIpsec
        var err error
        proposal_obj, _ := json.Marshal(proposal)
        proposal_name := proposal.Name
-       response, err = m.OpenwrtClient.Put(ipsecBaseURL+"proposal/"+proposal_name, string(proposal_obj))
+       response, err = m.OpenwrtClient.Put(ipsecBaseURL+"proposals/"+proposal_name, string(proposal_obj))
        if err != nil {
                return nil, err
        }
@@ -169,7 +169,7 @@ func (f *IpsecClient) GetSites() (*SdewanIpsecSites, error) {
 func (m *IpsecClient) GetSite(site string) (*SdewanIpsecSite, error) {
        var response string
        var err error
-       response, err = m.OpenwrtClient.Get(ipsecBaseURL + "site/" + site)
+       response, err = m.OpenwrtClient.Get(ipsecBaseURL + "sites/" + site)
        if err != nil {
                return nil, err
        }
@@ -188,7 +188,7 @@ func (m *IpsecClient) CreateSite(site SdewanIpsecSite) (*SdewanIpsecSite, error)
        var response string
        var err error
        site_obj, _ := json.Marshal(site)
-       response, err = m.OpenwrtClient.Post(ipsecBaseURL+"site", string(site_obj))
+       response, err = m.OpenwrtClient.Post(ipsecBaseURL+"sites", string(site_obj))
        if err != nil {
                return nil, err
        }
@@ -204,7 +204,7 @@ func (m *IpsecClient) CreateSite(site SdewanIpsecSite) (*SdewanIpsecSite, error)
 
 // delete site
 func (m *IpsecClient) DeleteSite(site_name string) error {
-       _, err := m.OpenwrtClient.Delete(ipsecBaseURL + "site/" + site_name)
+       _, err := m.OpenwrtClient.Delete(ipsecBaseURL + "sites/" + site_name)
        if err != nil {
                return err
        }
@@ -218,7 +218,7 @@ func (m *IpsecClient) UpdateSite(site SdewanIpsecSite) (*SdewanIpsecSite, error)
        var err error
        site_obj, _ := json.Marshal(site)
        site_name := site.Name
-       response, err = m.OpenwrtClient.Put(ipsecBaseURL+"site/"+site_name, string(site_obj))
+       response, err = m.OpenwrtClient.Put(ipsecBaseURL+"sites/"+site_name, string(site_obj))
        if err != nil {
                return nil, err
        }
index 8d6c6a2..a5f508a 100644 (file)
@@ -51,6 +51,10 @@ type SdewanPolicies struct {
        Policies []SdewanPolicy `json:"policies"`
 }
 
+func (o *SdewanPolicy) GetName() string {
+       return o.Name
+}
+
 // MWAN3 Rule
 type SdewanRule struct {
        Name     string `json:"name"`
@@ -69,6 +73,10 @@ type SdewanRules struct {
        Rules []SdewanRule `json:"rules"`
 }
 
+func (o *SdewanRule) GetName() string {
+       return o.Name
+}
+
 // get interface status
 func (m *Mwan3Client) GetInterfaceStatus() (*InterfaceStatus, error) {
        response, err := m.OpenwrtClient.Get("admin/status/mwan/interface_status")
@@ -108,7 +116,6 @@ func (m *Mwan3Client) GetPolicy(policy_name string) (*SdewanPolicy, error) {
        if err != nil {
                return nil, err
        }
-
        var sdewanPolicy SdewanPolicy
        err2 := json.Unmarshal([]byte(response), &sdewanPolicy)
        if err2 != nil {
index 2d0dee6..fa15246 100644 (file)
@@ -7,8 +7,13 @@ import (
        "net/http"
        "runtime"
        "strings"
+       "sync"
 )
 
+type IOpenWrtObject interface {
+       GetName() string
+}
+
 type OpenwrtError struct {
        Code    int
        Message string
@@ -18,33 +23,55 @@ func (e *OpenwrtError) Error() string {
        return fmt.Sprintf("Error Code: %d, Error Message: %s", e.Code, e.Message)
 }
 
+type OpenwrtClientInfo struct {
+       Ip       string
+       User     string
+       Password string
+}
+
 type openwrtClient struct {
-       ip       string
-       user     string
-       password string
-       token    string
+       OpenwrtClientInfo
+       token string
+}
+
+type safeOpenwrtClient struct {
+       clients map[string]*openwrtClient
+       mux     sync.Mutex
 }
 
+var gclients = safeOpenwrtClient{clients: make(map[string]*openwrtClient)}
+
 func CloseClient(o *openwrtClient) {
        o.logout()
        runtime.SetFinalizer(o, nil)
 }
 
-func NewOpenwrtClient(ip string, user string, password string) *openwrtClient {
-       client := &openwrtClient{
-               ip:       ip,
-               user:     user,
-               password: password,
-               token:    "",
+func GetOpenwrtClient(clientInfo OpenwrtClientInfo) *openwrtClient {
+       return gclients.GetClient(clientInfo.Ip, clientInfo.User, clientInfo.Password)
+}
+
+// SafeOpenwrtClients
+func (s *safeOpenwrtClient) GetClient(ip string, user string, password string) *openwrtClient {
+       s.mux.Lock()
+       defer s.mux.Unlock()
+       key := ip + "-" + user + "-" + password
+       if s.clients[key] == nil {
+               s.clients[key] = &openwrtClient{
+                       OpenwrtClientInfo: OpenwrtClientInfo{
+                               Ip:       ip,
+                               User:     user,
+                               Password: password,
+                       },
+                       token: "",
+               }
        }
 
-       runtime.SetFinalizer(client, CloseClient)
-       return client
+       return s.clients[key]
 }
 
 // openwrt base URL
 func (o *openwrtClient) getBaseURL() string {
-       return "http://" + o.ip + "/cgi-bin/luci/"
+       return "http://" + o.Ip + "/cgi-bin/luci/"
 }
 
 // login to openwrt http server
@@ -57,7 +84,7 @@ func (o *openwrtClient) login() error {
        }
 
        // login
-       login_info := "luci_username=" + o.user + "&luci_password=" + o.password
+       login_info := "luci_username=" + o.User + "&luci_password=" + o.Password
        var req_body = []byte(login_info)
        req, _ := http.NewRequest("POST", o.getBaseURL(), bytes.NewBuffer(req_body))
        req.Header.Add("Content-Type", "application/x-www-form-urlencoded")