Application CR implemention 30/3930/5
authorLe Yao <le.yao@intel.com>
Mon, 23 Nov 2020 06:15:36 +0000 (06:15 +0000)
committerLe Yao <le.yao@intel.com>
Tue, 8 Dec 2020 04:45:39 +0000 (04:45 +0000)
Implement Application CR and CR controller.
Call Application RESTful API to add rules for Application deployment.
Handle missing errors.

Signed-off-by: Le Yao <le.yao@intel.com>
Change-Id: I425f431bea20f372dc95cde1439259ad29e93773

16 files changed:
platform/crd-ctrlr/src/PROJECT
platform/crd-ctrlr/src/api/v1alpha1/sdewanapplication_types.go [new file with mode: 0644]
platform/crd-ctrlr/src/api/v1alpha1/zz_generated.deepcopy.go
platform/crd-ctrlr/src/config/crd/bases/batch.sdewan.akraino.org_sdewanapplications.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/crd/kustomization.yaml
platform/crd-ctrlr/src/config/crd/patches/cainjection_in_sdewanapplications.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/crd/patches/webhook_in_sdewanapplications.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/rbac/role.yaml
platform/crd-ctrlr/src/config/rbac/sdewanapplication_editor_role.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/rbac/sdewanapplication_viewer_role.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/config/samples/batch_v1alpha1_sdewanapplication.yaml [new file with mode: 0644]
platform/crd-ctrlr/src/controllers/base_controller.go
platform/crd-ctrlr/src/controllers/sdewanapplication_controller.go [new file with mode: 0644]
platform/crd-ctrlr/src/controllers/suite_test.go
platform/crd-ctrlr/src/main.go
platform/crd-ctrlr/src/openwrt/app.go [new file with mode: 0644]

index a50160b..7c4a411 100644 (file)
@@ -34,4 +34,7 @@ resources:
 - group: batch
   kind: CNFService
   version: v1alpha1
+- group: batch
+  kind: SdewanApplication
+  version: v1alpha1
 version: "2"
diff --git a/platform/crd-ctrlr/src/api/v1alpha1/sdewanapplication_types.go b/platform/crd-ctrlr/src/api/v1alpha1/sdewanapplication_types.go
new file mode 100644 (file)
index 0000000..1a70918
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+
+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.
+
+// SdewanApplicationSpec defines the desired state of SdewanApplication
+type SdewanApplicationSpec struct {
+       PodSelector  *metav1.LabelSelector `json:"podSelector,omitempty"`
+       AppNamespace string                `json:"appNamespace,omitempty"`
+}
+
+type ApplicationInfo struct {
+       IpList string
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+
+// SdewanApplication is the Schema for the sdewanapplications API
+type SdewanApplication struct {
+       metav1.TypeMeta   `json:",inline"`
+       metav1.ObjectMeta `json:"metadata,omitempty"`
+
+       Spec    SdewanApplicationSpec `json:"spec,omitempty"`
+       Status  SdewanStatus          `json:"status,omitempty"`
+       AppInfo ApplicationInfo       `json:"-"`
+}
+
+// +kubebuilder:object:root=true
+
+// SdewanApplicationList contains a list of SdewanApplication
+type SdewanApplicationList struct {
+       metav1.TypeMeta `json:",inline"`
+       metav1.ListMeta `json:"metadata,omitempty"`
+       Items           []SdewanApplication `json:"items"`
+}
+
+func init() {
+       SchemeBuilder.Register(&SdewanApplication{}, &SdewanApplicationList{})
+}
index 83a43ee..ed719e7 100644 (file)
@@ -20,9 +20,25 @@ limitations under the License.
 package v1alpha1
 
 import (
+       "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime"
 )
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ApplicationInfo) DeepCopyInto(out *ApplicationInfo) {
+       *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationInfo.
+func (in *ApplicationInfo) DeepCopy() *ApplicationInfo {
+       if in == nil {
+               return nil
+       }
+       out := new(ApplicationInfo)
+       in.DeepCopyInto(out)
+       return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in BucketPermission) DeepCopyInto(out *BucketPermission) {
        {
@@ -955,6 +971,86 @@ func (in *Mwan3RuleSpec) DeepCopy() *Mwan3RuleSpec {
        return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SdewanApplication) DeepCopyInto(out *SdewanApplication) {
+       *out = *in
+       out.TypeMeta = in.TypeMeta
+       in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+       in.Spec.DeepCopyInto(&out.Spec)
+       in.Status.DeepCopyInto(&out.Status)
+       out.AppInfo = in.AppInfo
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SdewanApplication.
+func (in *SdewanApplication) DeepCopy() *SdewanApplication {
+       if in == nil {
+               return nil
+       }
+       out := new(SdewanApplication)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SdewanApplication) 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 *SdewanApplicationList) DeepCopyInto(out *SdewanApplicationList) {
+       *out = *in
+       out.TypeMeta = in.TypeMeta
+       in.ListMeta.DeepCopyInto(&out.ListMeta)
+       if in.Items != nil {
+               in, out := &in.Items, &out.Items
+               *out = make([]SdewanApplication, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SdewanApplicationList.
+func (in *SdewanApplicationList) DeepCopy() *SdewanApplicationList {
+       if in == nil {
+               return nil
+       }
+       out := new(SdewanApplicationList)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SdewanApplicationList) 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 *SdewanApplicationSpec) DeepCopyInto(out *SdewanApplicationSpec) {
+       *out = *in
+       if in.PodSelector != nil {
+               in, out := &in.PodSelector, &out.PodSelector
+               *out = new(v1.LabelSelector)
+               (*in).DeepCopyInto(*out)
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SdewanApplicationSpec.
+func (in *SdewanApplicationSpec) DeepCopy() *SdewanApplicationSpec {
+       if in == nil {
+               return nil
+       }
+       out := new(SdewanApplicationSpec)
+       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/config/crd/bases/batch.sdewan.akraino.org_sdewanapplications.yaml b/platform/crd-ctrlr/src/config/crd/bases/batch.sdewan.akraino.org_sdewanapplications.yaml
new file mode 100644 (file)
index 0000000..acea1b0
--- /dev/null
@@ -0,0 +1,115 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.2.5
+  creationTimestamp: null
+  name: sdewanapplications.batch.sdewan.akraino.org
+spec:
+  group: batch.sdewan.akraino.org
+  names:
+    kind: SdewanApplication
+    listKind: SdewanApplicationList
+    plural: sdewanapplications
+    singular: sdewanapplication
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: SdewanApplication is the Schema for the sdewanapplications 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:
+          description: SdewanApplicationSpec defines the desired state of SdewanApplication
+          properties:
+            appNamespace:
+              type: string
+            podSelector:
+              description: A label selector is a label query over a set of resources.
+                The result of matchLabels and matchExpressions are ANDed. An empty
+                label selector matches all objects. A null label selector matches
+                no objects.
+              properties:
+                matchExpressions:
+                  description: matchExpressions is a list of label selector requirements.
+                    The requirements are ANDed.
+                  items:
+                    description: A label selector requirement is a selector that contains
+                      values, a key, and an operator that relates the key and values.
+                    properties:
+                      key:
+                        description: key is the label key that the selector applies
+                          to.
+                        type: string
+                      operator:
+                        description: operator represents a key's relationship to a
+                          set of values. Valid operators are In, NotIn, Exists and
+                          DoesNotExist.
+                        type: string
+                      values:
+                        description: values is an array of string values. If the operator
+                          is In or NotIn, the values array must be non-empty. If the
+                          operator is Exists or DoesNotExist, the values array must
+                          be empty. This array is replaced during a strategic merge
+                          patch.
+                        items:
+                          type: string
+                        type: array
+                    required:
+                    - key
+                    - operator
+                    type: object
+                  type: array
+                matchLabels:
+                  additionalProperties:
+                    type: string
+                  description: matchLabels is a map of {key,value} pairs. A single
+                    {key,value} in the matchLabels map is equivalent to an element
+                    of matchExpressions, whose key field is "key", the operator is
+                    "In", and the values array contains only "value". The requirements
+                    are ANDed.
+                  type: object
+              type: object
+          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 55e8f20..279ef89 100644 (file)
@@ -13,6 +13,7 @@ resources:
 - bases/batch.sdewan.akraino.org_ipsechosts.yaml
 - bases/batch.sdewan.akraino.org_ipsecsites.yaml
 - bases/batch.sdewan.akraino.org_cnfservices.yaml
+- bases/batch.sdewan.akraino.org_sdewanapplications.yaml
 # +kubebuilder:scaffold:crdkustomizeresource
 
 patchesStrategicMerge:
@@ -29,6 +30,7 @@ patchesStrategicMerge:
 #- patches/webhook_in_ipsechosts.yaml
 #- patches/webhook_in_ipsecsites.yaml
 #- patches/webhook_in_cnfservices.yaml
+#- patches/webhook_in_sdewanapplications.yaml
 # +kubebuilder:scaffold:crdkustomizewebhookpatch
 
 # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
@@ -44,6 +46,7 @@ patchesStrategicMerge:
 #- patches/cainjection_in_ipsechosts.yaml
 #- patches/cainjection_in_ipsecsites.yaml
 #- patches/cainjection_in_cnfservices.yaml
+#- patches/cainjection_in_sdewanapplications.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_sdewanapplications.yaml b/platform/crd-ctrlr/src/config/crd/patches/cainjection_in_sdewanapplications.yaml
new file mode 100644 (file)
index 0000000..1107664
--- /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: sdewanapplications.batch.sdewan.akraino.org
diff --git a/platform/crd-ctrlr/src/config/crd/patches/webhook_in_sdewanapplications.yaml b/platform/crd-ctrlr/src/config/crd/patches/webhook_in_sdewanapplications.yaml
new file mode 100644 (file)
index 0000000..898c876
--- /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: sdewanapplications.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
index 81499f9..c2ce53f 100644 (file)
@@ -242,6 +242,26 @@ rules:
   - get
   - patch
   - update
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewanapplications
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewanapplications/status
+  verbs:
+  - get
+  - patch
+  - update
 - apiGroups:
   - rbac.authorization.k8s.io
   resources:
diff --git a/platform/crd-ctrlr/src/config/rbac/sdewanapplication_editor_role.yaml b/platform/crd-ctrlr/src/config/rbac/sdewanapplication_editor_role.yaml
new file mode 100644 (file)
index 0000000..c4ff836
--- /dev/null
@@ -0,0 +1,24 @@
+# permissions for end users to edit sdewanapplications.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: sdewanapplication-editor-role
+rules:
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewanapplications
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewanapplications/status
+  verbs:
+  - get
diff --git a/platform/crd-ctrlr/src/config/rbac/sdewanapplication_viewer_role.yaml b/platform/crd-ctrlr/src/config/rbac/sdewanapplication_viewer_role.yaml
new file mode 100644 (file)
index 0000000..984f543
--- /dev/null
@@ -0,0 +1,20 @@
+# permissions for end users to view sdewanapplications.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: sdewanapplication-viewer-role
+rules:
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewanapplications
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewanapplications/status
+  verbs:
+  - get
diff --git a/platform/crd-ctrlr/src/config/samples/batch_v1alpha1_sdewanapplication.yaml b/platform/crd-ctrlr/src/config/samples/batch_v1alpha1_sdewanapplication.yaml
new file mode 100644 (file)
index 0000000..4d2d975
--- /dev/null
@@ -0,0 +1,12 @@
+apiVersion: batch.sdewan.akraino.org/v1alpha1
+kind: SdewanApplication
+metadata:
+  name: sdewanapplication-sample
+  labels:
+    sdewanPurpose: cnf1
+spec:
+  appNamespace: default
+  podSelector:
+    matchLabels:
+      app: myapp
+      #key: value
index fdd36a0..efeaa04 100644 (file)
@@ -244,6 +244,11 @@ func ProcessReconcile(r client.Client, logger logr.Logger, req ctrl.Request, han
                        // No instance
                        return ctrl.Result{}, nil
                }
+
+               err1, ok := err.(*AppCRError)
+               if ok && err1.Code == 404 {
+                       return ctrl.Result{}, nil
+               }
                // Error reading the object - requeue the request.
                return ctrl.Result{RequeueAfter: during}, nil
        }
@@ -316,7 +321,8 @@ func ProcessReconcile(r client.Client, logger logr.Logger, req ctrl.Request, han
                _, err := cnf.DeleteObject(handler, instance)
 
                if err != nil {
-                       if err.(*openwrt.OpenwrtError).Code != 404 {
+                       err2, ok := err.(*openwrt.OpenwrtError)
+                       if !ok || err2.Code != 404 {
                                log.Error(err, "Failed to delete "+handler.GetType())
                                setStatus(instance, batchv1alpha1.SdewanStatus{State: batchv1alpha1.Deleting, Message: err.Error()})
                                err = r.Status().Update(ctx, instance)
diff --git a/platform/crd-ctrlr/src/controllers/sdewanapplication_controller.go b/platform/crd-ctrlr/src/controllers/sdewanapplication_controller.go
new file mode 100644 (file)
index 0000000..dfd8eea
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+
+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"
+       "fmt"
+       "reflect"
+
+       "github.com/go-logr/logr"
+       appsv1 "k8s.io/api/apps/v1"
+       corev1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       "sigs.k8s.io/controller-runtime/pkg/handler"
+       "sigs.k8s.io/controller-runtime/pkg/source"
+
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+       "sdewan.akraino.org/sdewan/openwrt"
+)
+
+var sdewanApplicationHandler = new(SdewanApplicationHandler)
+
+type SdewanApplicationHandler struct {
+}
+
+type AppCRError struct {
+       Code    int
+       Message string
+}
+
+func (e AppCRError) Error() string {
+       return fmt.Sprintf("Error Code: %d, Error Message: %s", e.Code, e.Message)
+}
+
+func (m *SdewanApplicationHandler) GetType() string {
+       return "sdewanApplication"
+}
+
+func (m *SdewanApplicationHandler) GetName(instance runtime.Object) string {
+       app := instance.(*batchv1alpha1.SdewanApplication)
+       return app.Name
+}
+
+func (m *SdewanApplicationHandler) GetFinalizer() string {
+       return "rule.finalizers.sdewan.akraino.org"
+}
+
+func (m *SdewanApplicationHandler) GetInstance(r client.Client, ctx context.Context, req ctrl.Request) (runtime.Object, error) {
+       instance := &batchv1alpha1.SdewanApplication{}
+       err := r.Get(ctx, req.NamespacedName, instance)
+       if err == nil {
+               ps := instance.Spec.PodSelector.MatchLabels
+               ns := instance.Spec.AppNamespace
+               podList := &corev1.PodList{}
+               r.List(ctx, podList, client.MatchingLabels(ps), client.InNamespace(ns))
+               ips := ""
+               for _, item := range podList.Items {
+                       if ips == "" {
+                               ips = item.Status.PodIP
+                       } else {
+                               ips = ips + "," + item.Status.PodIP
+                       }
+               }
+               instance.AppInfo.IpList = ips
+       }
+
+       if instance.AppInfo.IpList == "" {
+               return instance, &AppCRError{Code: 404, Message: "Application not found"}
+       }
+
+       return instance, err
+}
+
+func (m *SdewanApplicationHandler) Convert(instance runtime.Object, deployment appsv1.Deployment) (openwrt.IOpenWrtObject, error) {
+       app := instance.(*batchv1alpha1.SdewanApplication)
+       openwrtapp := openwrt.SdewanApp{
+               Name:   app.Name,
+               IpList: app.AppInfo.IpList,
+       }
+       return &openwrtapp, nil
+}
+
+func (m *SdewanApplicationHandler) IsEqual(instance1 openwrt.IOpenWrtObject, instance2 openwrt.IOpenWrtObject) bool {
+       app1 := instance1.(*openwrt.SdewanApp)
+       app2 := instance2.(*openwrt.SdewanApp)
+       return reflect.DeepEqual(*app1, *app2)
+}
+
+func (m *SdewanApplicationHandler) GetObject(clientInfo *openwrt.OpenwrtClientInfo, name string) (openwrt.IOpenWrtObject, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       app := openwrt.AppClient{OpenwrtClient: openwrtClient}
+       ret, err := app.GetApp(name)
+       return ret, err
+}
+
+func (m *SdewanApplicationHandler) CreateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       app := openwrt.AppClient{OpenwrtClient: openwrtClient}
+       application := instance.(*openwrt.SdewanApp)
+       return app.CreateApp(*application)
+}
+
+func (m *SdewanApplicationHandler) UpdateObject(clientInfo *openwrt.OpenwrtClientInfo, instance openwrt.IOpenWrtObject) (openwrt.IOpenWrtObject, error) {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       app := openwrt.AppClient{OpenwrtClient: openwrtClient}
+       application := instance.(*openwrt.SdewanApp)
+       return app.UpdateApp(*application)
+}
+
+func (m *SdewanApplicationHandler) DeleteObject(clientInfo *openwrt.OpenwrtClientInfo, name string) error {
+       openwrtClient := openwrt.GetOpenwrtClient(*clientInfo)
+       app := openwrt.AppClient{OpenwrtClient: openwrtClient}
+       return app.DeleteApp(name)
+}
+
+func (m *SdewanApplicationHandler) Restart(clientInfo *openwrt.OpenwrtClientInfo) (bool, error) {
+       return true, nil
+}
+
+// SdewanApplicationReconciler reconciles a SdewanApplication object
+type SdewanApplicationReconciler struct {
+       client.Client
+       Log    logr.Logger
+       Scheme *runtime.Scheme
+}
+
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=sdewanapplications,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=sdewanapplications/status,verbs=get;update;patch
+
+func (r *SdewanApplicationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
+       return ProcessReconcile(r, r.Log, req, sdewanApplicationHandler)
+}
+
+func (r *SdewanApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error {
+       return ctrl.NewControllerManagedBy(mgr).
+               For(&batchv1alpha1.SdewanApplication{}).
+               Watches(
+                       &source.Kind{Type: &corev1.Service{}},
+                       &handler.EnqueueRequestsFromMapFunc{
+                               ToRequests: handler.ToRequestsFunc(GetServiceToRequestsFunc(r)),
+                       },
+                       IPFilter).
+               Complete(r)
+}
index e096a3c..bf86e57 100644 (file)
@@ -92,6 +92,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 0c16027..c50c83d 100644 (file)
@@ -217,6 +217,14 @@ func main() {
                setupLog.Error(err, "unable to create controller", "controller", "CNFService")
                os.Exit(1)
        }
+       if err = (&controllers.SdewanApplicationReconciler{
+               Client: mgr.GetClient(),
+               Log:    ctrl.Log.WithName("controllers").WithName("SdewanApplication"),
+               Scheme: mgr.GetScheme(),
+       }).SetupWithManager(mgr); err != nil {
+               setupLog.Error(err, "unable to create controller", "controller", "SdewanApplication")
+               os.Exit(1)
+       }
        // +kubebuilder:scaffold:builder
 
        setupLog.Info("starting manager")
diff --git a/platform/crd-ctrlr/src/openwrt/app.go b/platform/crd-ctrlr/src/openwrt/app.go
new file mode 100644 (file)
index 0000000..d465d05
--- /dev/null
@@ -0,0 +1,105 @@
+package openwrt
+
+import (
+       "encoding/json"
+)
+
+const (
+       appBaseURL = "sdewan/application/v1/"
+)
+
+type AppClient struct {
+       OpenwrtClient *openwrtClient
+}
+
+// App Info
+type SdewanApp struct {
+       Name   string `json:"name"`
+       IpList string `json:"iplist"`
+}
+
+type SdewanApps struct {
+       Apps []SdewanApp `json:"apps"`
+}
+
+func (o *SdewanApp) GetName() string {
+       return o.Name
+}
+
+// App APIs
+// get apps
+func (m *AppClient) GetApps() (*SdewanApps, error) {
+       response, err := m.OpenwrtClient.Get(appBaseURL + "applications")
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanApps SdewanApps
+       err2 := json.Unmarshal([]byte(response), &sdewanApps)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanApps, nil
+}
+
+// get app
+func (m *AppClient) GetApp(app_name string) (*SdewanApp, error) {
+       response, err := m.OpenwrtClient.Get(appBaseURL + "applications/" + app_name)
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanApp SdewanApp
+       err2 := json.Unmarshal([]byte(response), &sdewanApp)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanApp, nil
+}
+
+// create app
+func (m *AppClient) CreateApp(app SdewanApp) (*SdewanApp, error) {
+       app_obj, _ := json.Marshal(app)
+       response, err := m.OpenwrtClient.Post(appBaseURL+"applications/", string(app_obj))
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanApp SdewanApp
+       err2 := json.Unmarshal([]byte(response), &sdewanApp)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanApp, nil
+}
+
+// delete app
+func (m *AppClient) DeleteApp(app_name string) error {
+       _, err := m.OpenwrtClient.Delete(appBaseURL + "applications/" + app_name)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// update app
+func (m *AppClient) UpdateApp(app SdewanApp) (*SdewanApp, error) {
+       app_obj, _ := json.Marshal(app)
+       app_name := app.Name
+       response, err := m.OpenwrtClient.Put(appBaseURL+"applications/"+app_name, string(app_obj))
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanApp SdewanApp
+       err2 := json.Unmarshal([]byte(response), &sdewanApp)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanApp, nil
+}