Merge "BPA Provisioning CRD and Controller" into dev/icn-v0.1.0
[icn.git] / cmd / bpa-operator / pkg / controller / provisioning / provisioning_controller.go
diff --git a/cmd/bpa-operator/pkg/controller/provisioning/provisioning_controller.go b/cmd/bpa-operator/pkg/controller/provisioning/provisioning_controller.go
new file mode 100644 (file)
index 0000000..e7e7c34
--- /dev/null
@@ -0,0 +1,323 @@
+package provisioning
+
+import (
+       "context"
+        "os"
+        "fmt"
+        "bytes"
+       "regexp"
+       "strings"
+       "io/ioutil"
+        "path/filepath"
+        "os/user"
+
+
+       bpav1alpha1 "github.com/bpa-operator/pkg/apis/bpa/v1alpha1"
+        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+        "k8s.io/apimachinery/pkg/runtime/schema"
+       "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/runtime"
+        "k8s.io/client-go/tools/clientcmd"
+        "k8s.io/client-go/dynamic"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       "sigs.k8s.io/controller-runtime/pkg/controller"
+       "sigs.k8s.io/controller-runtime/pkg/handler"
+       "sigs.k8s.io/controller-runtime/pkg/manager"
+       "sigs.k8s.io/controller-runtime/pkg/reconcile"
+       logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
+       "sigs.k8s.io/controller-runtime/pkg/source"
+        "gopkg.in/ini.v1"
+)
+
+var log = logf.Log.WithName("controller_provisioning")
+var dhcpLeaseFile = "/var/lib/dhcp/dhcpd.leases"
+
+/**
+* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
+* business logic.  Delete these comments after modifying this file.*
+ */
+
+// Add creates a new Provisioning Controller and adds it to the Manager. The Manager will set fields on the Controller
+// and Start it when the Manager is Started.
+func Add(mgr manager.Manager) error {
+       return add(mgr, newReconciler(mgr))
+}
+
+// newReconciler returns a new reconcile.Reconciler
+func newReconciler(mgr manager.Manager) reconcile.Reconciler {
+       return &ReconcileProvisioning{client: mgr.GetClient(), scheme: mgr.GetScheme()}
+}
+
+// add adds a new Controller to mgr with r as the reconcile.Reconciler
+func add(mgr manager.Manager, r reconcile.Reconciler) error {
+       // Create a new controller
+       c, err := controller.New("provisioning-controller", mgr, controller.Options{Reconciler: r})
+       if err != nil {
+               return err
+       }
+
+       // Watch for changes to primary resource Provisioning
+       err = c.Watch(&source.Kind{Type: &bpav1alpha1.Provisioning{}}, &handler.EnqueueRequestForObject{})
+       if err != nil {
+               return err
+       }
+
+
+       return nil
+}
+
+// blank assignment to verify that ReconcileProvisioning implements reconcile.Reconciler
+var _ reconcile.Reconciler = &ReconcileProvisioning{}
+
+// ReconcileProvisioning reconciles a Provisioning object
+type ReconcileProvisioning struct {
+       // This client, initialized using mgr.Client() above, is a split client
+       // that reads objects from the cache and writes to the apiserver
+       client client.Client
+       scheme *runtime.Scheme
+}
+
+// Reconcile reads that state of the cluster for a Provisioning object and makes changes based on the state read
+// and what is in the Provisioning.Spec
+// TODO(user): Modify this Reconcile function to implement your Controller logic.  This example creates
+// a Pod as an example
+// Note:
+// The Controller will requeue the Request to be processed again if the returned error is non-nil or
+// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
+func (r *ReconcileProvisioning) Reconcile(request reconcile.Request) (reconcile.Result, error) {
+       reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
+       reqLogger.Info("Reconciling Provisioning")
+
+       // Fetch the Provisioning instance
+       provisioningInstance := &bpav1alpha1.Provisioning{}
+       err := r.client.Get(context.TODO(), request.NamespacedName, provisioningInstance)
+       if err != nil {
+               if errors.IsNotFound(err) {
+                       // Request object not found, could have been deleted after reconcile request.
+                       // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
+                       // Return and don't requeue
+                       return reconcile.Result{}, nil
+               }
+               // Error reading the object - requeue the request.
+               return reconcile.Result{}, err
+       }
+
+        mastersList := provisioningInstance.Spec.Masters
+        workersList := provisioningInstance.Spec.Workers
+        bareMetalHostList, _ := listBareMetalHosts()
+
+
+        var allString string
+        var masterString string
+        var workerString string
+
+       //Iterate through mastersList and get all the mac addresses and IP addresses
+
+        for _, masterMap := range mastersList {
+
+                for masterLabel, master := range masterMap {
+                   containsMac, bmhCR := checkMACaddress(bareMetalHostList, master.MACaddress)
+                   if containsMac{
+                      //fmt.Println( master.MACaddress)
+                      fmt.Printf("BareMetalHost CR %s has NIC with MAC Address %s\n", bmhCR, master.MACaddress)
+
+                      //Get IP address of master
+                      hostIPaddress, err := getHostIPaddress(master.MACaddress, dhcpLeaseFile )
+                     if err != nil {
+                        fmt.Printf("IP address not found for host with MAC address %s \n", master.MACaddress)
+                     }
+
+
+
+                      allString += masterLabel + "  ansible_host="  + hostIPaddress + " ip=" + hostIPaddress + "\n"
+                      masterString += masterLabel + "\n"
+
+                      fmt.Printf("%s : %s \n", hostIPaddress, master.MACaddress)
+
+
+
+                   } else {
+
+                      fmt.Printf("Host with MAC Address %s not found\n", master.MACaddress)
+                   }
+             }
+        }
+
+
+        //Iterate through workersList and get all the mac addresses
+        for _, workerMap := range workersList {
+
+                for workerLabel, worker := range workerMap {
+                   containsMac, bmhCR := checkMACaddress(bareMetalHostList, worker.MACaddress)
+                   if containsMac{
+                      //fmt.Println( worker.MACaddress)
+                      fmt.Printf("Host %s matches that macAddress\n", bmhCR)
+
+                      //Get IP address of worker
+                      hostIPaddress, err := getHostIPaddress(worker.MACaddress, dhcpLeaseFile )
+                     if err != nil {
+                        fmt.Printf("IP address not found for host with MAC address %s \n", worker.MACaddress)
+                     }
+                      fmt.Printf("%s : %s \n", hostIPaddress, worker.MACaddress)
+
+                      allString += workerLabel + "  ansible_host="  + hostIPaddress + " ip=" + hostIPaddress + "\n"
+                      workerString += workerLabel + "\n"
+
+                   }else {
+
+                      fmt.Printf("Host with MAC Address %s not found\n", worker.MACaddress)
+                   }
+
+             }
+        }
+
+
+        //Create host.ini file
+        iniHostFilePath := provisioningInstance.Spec.HostsFile
+        newFile, err := os.Create(iniHostFilePath)
+        defer newFile.Close()
+
+
+        if err != nil {
+           fmt.Printf("Error occured while creating file \n %v", err)
+        }
+
+        hostFile, err := ini.Load(iniHostFilePath)
+        if err != nil {
+           fmt.Printf("Error occured while Loading file \n %v", err)
+        }
+
+        _, err = hostFile.NewRawSection("all", allString)
+        if err != nil {
+           fmt.Printf("Error occured while creating section \n %v", err)
+        }
+        _, err = hostFile.NewRawSection("kube-master", masterString)
+        if err != nil {
+           fmt.Printf("Error occured while creating section \n %v", err)
+        }
+
+        _, err = hostFile.NewRawSection("kube-node", workerString)
+        if err != nil {
+           fmt.Printf("Error occured while creating section \n %v", err)
+        }
+
+        _, err = hostFile.NewRawSection("etcd", masterString)
+        if err != nil {
+           fmt.Printf("Error occured while creating section \n %v", err)
+        }
+
+        _, err = hostFile.NewRawSection("k8s-cluser:children", "kube-node\n" + "kube-master")
+        if err != nil {
+           fmt.Printf("Error occured while creating section \n %v", err)
+        }
+
+
+        hostFile.SaveTo(iniHostFilePath)
+       return reconcile.Result{}, nil
+}
+
+
+//Function to Get List containing baremetal hosts
+func listBareMetalHosts() (*unstructured.UnstructuredList, error) {
+
+     //Get Current User and kube config file
+     usr, err := user.Current()
+     if err != nil {
+        fmt.Println("Could not get current user\n")
+        return &unstructured.UnstructuredList{}, err
+     }
+
+     kubeConfig := filepath.Join(usr.HomeDir, ".kube", "config")
+
+     //Build Config Flags
+     config, err :=  clientcmd.BuildConfigFromFlags("", kubeConfig)
+     if err != nil {
+        fmt.Println("Could not build config\n")
+        return &unstructured.UnstructuredList{}, err
+     }
+
+    //Create Dynamic Client  for BareMetalHost CRD
+    bmhDynamicClient, err := dynamic.NewForConfig(config)
+
+    if err != nil {
+       fmt.Println("Could not create dynamic client for bareMetalHosts\n")
+       return &unstructured.UnstructuredList{}, err
+    }
+
+    //Create GVR representing a BareMetalHost CR
+    bmhGVR := schema.GroupVersionResource{
+      Group:    "metal3.io",
+      Version:  "v1alpha1",
+      Resource: "baremetalhosts",
+    }
+
+    //Get List containing all BareMetalHosts CRs
+    bareMetalHosts, err := bmhDynamicClient.Resource(bmhGVR).List(metav1.ListOptions{})
+    if err != nil {
+       fmt.Println("Error occured, cannot get BareMetalHosts list\n")
+       return &unstructured.UnstructuredList{}, err
+    }
+
+    return bareMetalHosts, nil
+}
+
+//Function to check if BareMetalHost containing MAC address exist
+func checkMACaddress(bareMetalHostList *unstructured.UnstructuredList, macAddress string) (bool, string) {
+
+     //Convert macAddress to byte array for comparison
+     macAddressByte :=  []byte(macAddress)
+     macBool := false
+
+     for _, bareMetalHost := range bareMetalHostList.Items {
+         bmhJson, _ := bareMetalHost.MarshalJSON()
+
+         macBool = bytes.Contains(bmhJson, macAddressByte)
+         if macBool{
+             return macBool, bareMetalHost.GetName()
+         }
+
+      }
+
+         return macBool, ""
+
+}
+
+func getHostIPaddress(macAddress string, dhcpLeaseFilePath string ) (string, error) {
+
+     //Read the dhcp lease file
+     dhcpFile, err := ioutil.ReadFile(dhcpLeaseFilePath)
+     if err != nil {
+        fmt.Println("Failed to read lease file\n")
+        return "", err
+     }
+
+     dhcpLeases := string(dhcpFile)
+
+     //Regex to use to search dhcpLeases
+     regex := "lease.*{|ethernet.*"
+     re, err := regexp.Compile(regex)
+     if err != nil {
+        fmt.Println("Could not create Regexp object\n")
+        return "", err
+     }
+
+     //Get String containing leased Ip addresses and Corressponding MAC addresses
+     out := re.FindAllString(dhcpLeases, -1)
+     outString := strings.Join(out, " ")
+     stringReplacer := strings.NewReplacer("lease", "", "{ ethernet ", "", ";", "")
+     replaced := stringReplacer.Replace(outString)
+     ipMacList := strings.Fields(replaced)
+
+
+     //Get IP addresses corresponding to Input MAC Address
+     for idx := len(ipMacList)-1 ; idx >= 0; idx -- {
+         item := ipMacList[idx]
+         if item == macAddress  {
+            ipAdd := ipMacList[idx -1]
+            return ipAdd, nil
+    }
+
+ }
+     return "", nil
+}