Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / pkg / controller / provisioning / provisioning_controller.go
1 package provisioning
2
3 import (
4         "context"
5         "os"
6         "fmt"
7         "time"
8         "bytes"
9         "regexp"
10         "strings"
11         "io/ioutil"
12
13
14         bpav1alpha1 "github.com/bpa-operator/pkg/apis/bpa/v1alpha1"
15         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16         corev1 "k8s.io/api/core/v1"
17         batchv1 "k8s.io/api/batch/v1"
18         "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
19         "k8s.io/apimachinery/pkg/runtime/schema"
20         "k8s.io/apimachinery/pkg/api/errors"
21         "k8s.io/apimachinery/pkg/runtime"
22         "k8s.io/client-go/dynamic"
23         "k8s.io/client-go/rest"
24
25         "k8s.io/client-go/kubernetes"
26         "sigs.k8s.io/controller-runtime/pkg/client"
27         "sigs.k8s.io/controller-runtime/pkg/client/config"
28         "sigs.k8s.io/controller-runtime/pkg/controller"
29         "sigs.k8s.io/controller-runtime/pkg/handler"
30         "sigs.k8s.io/controller-runtime/pkg/manager"
31         "sigs.k8s.io/controller-runtime/pkg/reconcile"
32         logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
33         "sigs.k8s.io/controller-runtime/pkg/source"
34         "gopkg.in/ini.v1"
35         "golang.org/x/crypto/ssh"
36 )
37
38 var log = logf.Log.WithName("controller_provisioning")
39
40 /**
41 * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
42 * business logic.  Delete these comments after modifying this file.*
43  */
44
45 // Add creates a new Provisioning Controller and adds it to the Manager. The Manager will set fields on the Controller
46 // and Start it when the Manager is Started.
47 func Add(mgr manager.Manager) error {
48         return add(mgr, newReconciler(mgr))
49 }
50
51 // newReconciler returns a new reconcile.Reconciler
52 func newReconciler(mgr manager.Manager) reconcile.Reconciler {
53         return &ReconcileProvisioning{client: mgr.GetClient(), scheme: mgr.GetScheme()}
54 }
55
56 // add adds a new Controller to mgr with r as the reconcile.Reconciler
57 func add(mgr manager.Manager, r reconcile.Reconciler) error {
58         // Create a new controller
59         c, err := controller.New("provisioning-controller", mgr, controller.Options{Reconciler: r})
60         if err != nil {
61                 return err
62         }
63
64         // Watch for changes to primary resource Provisioning
65         err = c.Watch(&source.Kind{Type: &bpav1alpha1.Provisioning{}}, &handler.EnqueueRequestForObject{})
66         if err != nil {
67                 return err
68         }
69
70         // Watch for changes to resource configmap created as a consequence of the provisioning CR
71         err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForOwner{
72                 IsController: true,
73                 OwnerType:   &bpav1alpha1.Provisioning{},
74         })
75
76         if err != nil {
77                 return err
78         }
79
80        //Watch for changes to job resource also created as a consequence of the provisioning CR
81        err = c.Watch(&source.Kind{Type: &batchv1.Job{}}, &handler.EnqueueRequestForOwner{
82                 IsController: true,
83                 OwnerType:   &bpav1alpha1.Provisioning{},
84         })
85
86         if err != nil {
87                 return err
88         }
89
90
91         // Watch for changes to resource software CR
92         err = c.Watch(&source.Kind{Type: &bpav1alpha1.Software{}}, &handler.EnqueueRequestForObject{})
93         if err != nil {
94                 return err
95         }
96
97
98
99         return nil
100 }
101
102 // blank assignment to verify that ReconcileProvisioning implements reconcile.Reconciler
103 var _ reconcile.Reconciler = &ReconcileProvisioning{}
104
105 // ReconcileProvisioning reconciles a Provisioning object
106 type ReconcileProvisioning struct {
107         // This client, initialized using mgr.Client() above, is a split client
108         // that reads objects from the cache and writes to the apiserver
109         client client.Client
110         scheme *runtime.Scheme
111 }
112
113 // Reconcile reads that state of the cluster for a Provisioning object and makes changes based on the state read
114 // and what is in the Provisioning.Spec
115 // TODO(user): Modify this Reconcile function to implement your Controller logic.  This example creates
116 // a Pod as an example
117 // Note:
118 // The Controller will requeue the Request to be processed again if the returned error is non-nil or
119 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
120 func (r *ReconcileProvisioning) Reconcile(request reconcile.Request) (reconcile.Result, error) {
121         reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
122         //reqLogger.Info("Reconciling Provisioning")
123         fmt.Printf("\n\n")
124         reqLogger.Info("Reconciling Custom Resource")
125
126
127
128         // Fetch the Provisioning instance
129         provisioningInstance := &bpav1alpha1.Provisioning{}
130         softwareInstance := &bpav1alpha1.Software{}
131         err := r.client.Get(context.TODO(), request.NamespacedName, provisioningInstance)
132         provisioningCreated := true
133         if err != nil {
134
135                          //Check if its a Software Instance
136                          err = r.client.Get(context.TODO(), request.NamespacedName, softwareInstance)
137                          if err != nil {
138                              if errors.IsNotFound(err) {
139                                 // Request object not found, could have been deleted after reconcile request.
140                                 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
141                                 // Return and don't requeue
142                                 return reconcile.Result{}, nil
143                              }
144
145                          // Error reading the object - requeue the request.
146                          return reconcile.Result{}, err
147                          }
148
149                          //No error occured and so a Software CR was created not a Provisoning CR
150                          provisioningCreated = false
151         }
152
153
154         masterTag := "MASTER_"
155         workerTag := "WORKER_"
156
157         config, err :=  config.GetConfig()
158         if err != nil {
159            fmt.Printf("Could not get kube config, Error: %v\n", err)
160            return reconcile.Result{}, err
161         }
162
163         clientset, err := kubernetes.NewForConfig(config)
164         if err != nil {
165            fmt.Printf("Could not create clientset, Error: %v\n", err)
166            return reconcile.Result{}, err
167         }
168         if provisioningCreated {
169
170         ///////////////////////////////////////////////////////////////////////////////////////////////
171         ////////////////         Provisioning CR was created so install KUD          /////////////////
172         //////////////////////////////////////////////////////////////////////////////////////////////
173         clusterName := provisioningInstance.Labels["cluster"]
174         mastersList := provisioningInstance.Spec.Masters
175         workersList := provisioningInstance.Spec.Workers
176         dhcpLeaseFile := provisioningInstance.Spec.DHCPleaseFile
177         kudInstallerScript := provisioningInstance.Spec.KUDInstaller
178         multiClusterDir := provisioningInstance.Spec.MultiClusterPath
179
180
181         bareMetalHostList, _ := listBareMetalHosts(config)
182
183
184         var allString string
185         var masterString string
186         var workerString string
187
188         defaultDHCPFile := "/var/lib/dhcp/dhcpd.leases"
189         defaultKUDInstallerPath := "/multicloud-k8s/kud/hosting_providers/vagrant"
190         defaultMultiClusterDir := "/multi-cluster"
191
192         //Give Default values for paths if no path is given in the CR
193         if dhcpLeaseFile == "" {
194            dhcpLeaseFile = defaultDHCPFile
195         }
196
197         if kudInstallerScript == "" {
198            kudInstallerScript = defaultKUDInstallerPath
199         }
200
201         if multiClusterDir == "" {
202            multiClusterDir = defaultMultiClusterDir
203         }
204
205         //Create Directory for the specific cluster
206         clusterDir := multiClusterDir + "/" + clusterName
207         os.MkdirAll(clusterDir, os.ModePerm)
208
209         //Create Maps to be used for cluster ip address to label configmap
210         clusterLabel := make(map[string]string)
211         clusterLabel["cluster"] = clusterName
212         clusterData := make(map[string]string)
213
214
215
216        //Iterate through mastersList and get all the mac addresses and IP addresses
217        for _, masterMap := range mastersList {
218
219                 for masterLabel, master := range masterMap {
220                    masterMAC := master.MACaddress
221
222                    if masterMAC == "" {
223                       err = fmt.Errorf("MAC address for masterNode %s not provided\n", masterLabel)
224                       return reconcile.Result{}, err
225                    }
226                    containsMac, bmhCR := checkMACaddress(bareMetalHostList, masterMAC)
227                    if containsMac{
228                       fmt.Printf("BareMetalHost CR %s has NIC with MAC Address %s\n", bmhCR, masterMAC)
229
230                       //Get IP address of master
231                       hostIPaddress, err := getHostIPaddress(masterMAC, dhcpLeaseFile )
232                       if err != nil || hostIPaddress == ""{
233                         err = fmt.Errorf("IP address not found for host with MAC address %s \n", masterMAC)
234                         return reconcile.Result{}, err
235                       }
236
237                       allString += masterLabel + "  ansible_ssh_host="  + hostIPaddress + " ansible_ssh_port=22" + "\n"
238                       masterString += masterLabel + "\n"
239                       clusterData[masterTag + masterLabel] = hostIPaddress
240
241                       fmt.Printf("%s : %s \n", hostIPaddress, masterMAC)
242
243                       if len(workersList) != 0 {
244
245                           //Iterate through workersList and get all the mac addresses
246                           for _, workerMap := range workersList {
247
248                               //Get worker labels from the workermap
249                               for workerLabel, worker := range workerMap {
250
251                                   //Check if workerString already contains worker label
252                                   containsWorkerLabel := strings.Contains(workerString, workerLabel)
253                                   workerMAC := worker.MACaddress
254
255                                    //Error occurs if the same label is given to different hosts (assumption,
256                                    //each MAC address represents a unique host
257                                    if workerLabel == masterLabel && workerMAC != masterMAC && workerMAC != "" {
258                                      if containsWorkerLabel {
259                                             strings.ReplaceAll(workerString, workerLabel, "")
260                                          }
261                                       err = fmt.Errorf(`A node with label %s already exists, modify resource and assign a
262                                       different label to node with MACAddress %s`, workerLabel, workerMAC)
263                                       return reconcile.Result{}, err
264
265                                    //same node performs worker and master roles
266                                    } else if workerLabel == masterLabel && !containsWorkerLabel {
267                                         workerString += workerLabel + "\n"
268
269                                         //Add host to ip address config map with worker tag
270                                         hostIPaddress = clusterData[masterTag + masterLabel]
271                                         clusterData[workerTag + masterLabel] = hostIPaddress
272
273                                    //Error occurs if the same node is given different labels
274                                    } else if workerLabel != masterLabel && workerMAC == masterMAC {
275                                          if containsWorkerLabel {
276                                             strings.ReplaceAll(workerString, workerLabel, "")
277                                          }
278                                          err = fmt.Errorf(`A node with label %s already exists, modify resource and assign a
279                                                         different label to node with MACAddress %s`, workerLabel, workerMAC)
280                                          return reconcile.Result{}, err
281
282                                    //worker node is different from any master node and it has not been added to the worker list
283                                    } else if workerLabel != masterLabel && !containsWorkerLabel {
284
285                                         // Error occurs if MAC address not provided for worker node not matching master
286                                         if workerMAC == "" {
287                                           err = fmt.Errorf("MAC address for worker %s not provided", workerLabel)
288                                           return reconcile.Result{}, err
289                                          }
290
291                                         containsMac, bmhCR := checkMACaddress(bareMetalHostList, workerMAC)
292                                         if containsMac{
293                                            fmt.Printf("Host %s matches that macAddress\n", bmhCR)
294
295                                            //Get IP address of worker
296                                            hostIPaddress, err := getHostIPaddress(workerMAC, dhcpLeaseFile )
297                                            if err != nil {
298                                               fmt.Errorf("IP address not found for host with MAC address %s \n", workerMAC)
299                                               return reconcile.Result{}, err
300                                            }
301                                            fmt.Printf("%s : %s \n", hostIPaddress, workerMAC)
302
303
304
305                                            allString += workerLabel + "  ansible_ssh_host="  + hostIPaddress + " ansible_ssh_port=22" + "\n"
306                                            workerString += workerLabel + "\n"
307                                            clusterData[workerTag + workerLabel] = hostIPaddress
308
309                                        //No host found that matches the worker MAC
310                                        } else {
311
312                                             err = fmt.Errorf("Host with MAC Address %s not found\n", workerMAC)
313                                             return reconcile.Result{}, err
314                                           }
315                                      }
316
317                          }
318                        }
319                    //No worker node specified, add master as worker node
320                    } else if len(workersList) == 0 && !strings.Contains(workerString, masterLabel) {
321                        workerString += masterLabel + "\n"
322
323                        //Add host to ip address config map with worker tag
324                        hostIPaddress = clusterData[masterTag + masterLabel]
325                        clusterData[workerTag + masterLabel] = hostIPaddress
326                    }
327
328                    //No host matching master MAC found
329                    } else {
330                       err = fmt.Errorf("Host with MAC Address %s not found\n", masterMAC)
331                       return reconcile.Result{}, err
332                    }
333              }
334         }
335
336         //Create host.ini file
337         //iniHostFilePath := kudInstallerScript + "/inventory/hosts.ini"
338         iniHostFilePath := clusterDir + "/hosts.ini"
339         newFile, err := os.Create(iniHostFilePath)
340         defer newFile.Close()
341
342
343         if err != nil {
344            fmt.Printf("Error occured while creating file \n %v", err)
345            return reconcile.Result{}, err
346         }
347
348         hostFile, err := ini.Load(iniHostFilePath)
349         if err != nil {
350            fmt.Printf("Error occured while Loading file \n %v", err)
351            return reconcile.Result{}, err
352         }
353
354         _, err = hostFile.NewRawSection("all", allString)
355         if err != nil {
356            fmt.Printf("Error occured while creating section \n %v", err)
357            return reconcile.Result{}, err
358         }
359         _, err = hostFile.NewRawSection("kube-master", masterString)
360         if err != nil {
361            fmt.Printf("Error occured while creating section \n %v", err)
362            return reconcile.Result{}, err
363         }
364
365         _, err = hostFile.NewRawSection("kube-node", workerString)
366         if err != nil {
367            fmt.Printf("Error occured while creating section \n %v", err)
368            return reconcile.Result{}, err
369         }
370
371         _, err = hostFile.NewRawSection("etcd", masterString)
372         if err != nil {
373            fmt.Printf("Error occured while creating section \n %v", err)
374            return reconcile.Result{}, err
375         }
376
377         _, err = hostFile.NewRawSection("k8s-cluster:children", "kube-node\n" + "kube-master")
378         if err != nil {
379            fmt.Printf("Error occured while creating section \n %v", err)
380            return reconcile.Result{}, err
381         }
382
383
384         //Create host.ini file for KUD
385         hostFile.SaveTo(iniHostFilePath)
386
387         //Install KUD
388         err = createKUDinstallerJob(clusterName, request.Namespace, clusterLabel, clientset)
389         if err != nil {
390            fmt.Printf("Error occured while creating KUD Installer job for cluster %v\n ERROR: %v", clusterName, err)
391            return reconcile.Result{}, err
392         }
393
394         //Start separate thread to keep checking job status, Create an IP address configmap
395         //for cluster if KUD is successfully installed
396         go checkJob(clusterName, request.Namespace, clusterData, clusterLabel, clientset)
397
398         return reconcile.Result{}, nil
399
400        }
401
402
403
404         ///////////////////////////////////////////////////////////////////////////////////////////////
405         ////////////////         Software CR was created so install software         /////////////////
406         //////////////////////////////////////////////////////////////////////////////////////////////
407         softwareClusterName, masterSoftwareList, workerSoftwareList := getSoftwareList(softwareInstance)
408         defaultSSHPrivateKey := "/root/.ssh/id_rsa"
409
410         //Get IP address configmap for the cluster
411         clusterConfigMapData, err := getConfigMapData(request.Namespace, softwareClusterName, clientset)
412         if err != nil {
413            fmt.Printf("Error occured while retrieving IP address Data for cluster %s, ERROR: %v\n", softwareClusterName, err)
414            return reconcile.Result{}, err
415         }
416
417         for hostLabel, ipAddress := range clusterConfigMapData {
418
419             if strings.Contains(hostLabel, masterTag) {
420                // Its a master node, install master software
421                err = softwareInstaller(ipAddress, defaultSSHPrivateKey, masterSoftwareList)
422                if err != nil {
423                   fmt.Printf("Error occured while installing master software in host %s, ERROR: %v\n", hostLabel, err)
424                }
425             } else if strings.Contains(hostLabel, workerTag) {
426               // Its a worker node, install worker software
427               err = softwareInstaller(ipAddress, defaultSSHPrivateKey, workerSoftwareList)
428               if err != nil {
429                   fmt.Printf("Error occured while installing worker software in host %s, ERROR: %v\n", hostLabel, err)
430                }
431
432             }
433
434         }
435
436         return reconcile.Result{}, nil
437 }
438
439
440 //Function to Get List containing baremetal hosts
441 func listBareMetalHosts(config *rest.Config) (*unstructured.UnstructuredList, error) {
442
443     //Create Dynamic Client  for BareMetalHost CRD
444     bmhDynamicClient, err := dynamic.NewForConfig(config)
445
446     if err != nil {
447        fmt.Println("Could not create dynamic client for bareMetalHosts, Error: %v\n", err)
448        return &unstructured.UnstructuredList{}, err
449     }
450
451     //Create GVR representing a BareMetalHost CR
452     bmhGVR := schema.GroupVersionResource{
453       Group:    "metal3.io",
454       Version:  "v1alpha1",
455       Resource: "baremetalhosts",
456     }
457
458     //Get List containing all BareMetalHosts CRs
459     bareMetalHosts, err := bmhDynamicClient.Resource(bmhGVR).List(metav1.ListOptions{})
460     if err != nil {
461        fmt.Println("Error occured, cannot get BareMetalHosts list, Error: %v\n", err)
462        return &unstructured.UnstructuredList{}, err
463     }
464
465     return bareMetalHosts, nil
466 }
467
468
469 //Function to check if BareMetalHost containing MAC address exist
470 func checkMACaddress(bareMetalHostList *unstructured.UnstructuredList, macAddress string) (bool, string) {
471
472      //Convert macAddress to byte array for comparison
473      macAddressByte :=  []byte(macAddress)
474      macBool := false
475
476      for _, bareMetalHost := range bareMetalHostList.Items {
477          bmhJson, _ := bareMetalHost.MarshalJSON()
478
479          macBool = bytes.Contains(bmhJson, macAddressByte)
480          if macBool{
481              return macBool, bareMetalHost.GetName()
482          }
483
484       }
485
486          return macBool, ""
487
488 }
489
490
491 //Function to get the IP address of a host from the DHCP file
492 func getHostIPaddress(macAddress string, dhcpLeaseFilePath string ) (string, error) {
493
494      //Read the dhcp lease file
495      dhcpFile, err := ioutil.ReadFile(dhcpLeaseFilePath)
496      if err != nil {
497         fmt.Println("Failed to read lease file\n")
498         return "", err
499      }
500
501      dhcpLeases := string(dhcpFile)
502
503      //Regex to use to search dhcpLeases
504      reg := "lease.*{|ethernet.*|\n. binding state.*"
505      re, err := regexp.Compile(reg)
506      if err != nil {
507         fmt.Println("Could not create Regexp object, Error %v occured\n", err)
508         return "", err
509      }
510
511      //Get String containing leased Ip addresses and Corressponding MAC addresses
512      out := re.FindAllString(dhcpLeases, -1)
513      outString := strings.Join(out, " ")
514      stringReplacer := strings.NewReplacer("lease", "", "ethernet ", "", ";", "",
515      " binding state", "", "{", "")
516      replaced := stringReplacer.Replace(outString)
517      ipMacList := strings.Fields(replaced)
518
519
520      //Get IP addresses corresponding to Input MAC Address
521      for idx := len(ipMacList)-1 ; idx >= 0; idx -- {
522          item := ipMacList[idx]
523          if item == macAddress  {
524
525             leaseState := ipMacList[idx -1]
526             if leaseState != "active" {
527                err := fmt.Errorf("No active ip address lease found for MAC address %s \n", macAddress)
528                fmt.Printf("%v\n", err)
529                return "", err
530             }
531             ipAdd := ipMacList[idx - 2]
532             return ipAdd, nil
533     }
534
535  }
536      return "", nil
537 }
538
539 //Function to create configmap 
540 func createConfigMap(data, labels map[string]string, namespace string, clientset *kubernetes.Clientset) error{
541
542      configmapClient := clientset.CoreV1().ConfigMaps(namespace)
543
544      configmap := &corev1.ConfigMap{
545
546         ObjectMeta: metav1.ObjectMeta{
547                         Name: labels["cluster"] + "-configmap",
548                         Labels: labels,
549                 },
550         Data: data,
551      }
552
553
554       _, err := configmapClient.Create(configmap)
555       if err != nil {
556          return err
557
558       }
559       return nil
560
561 }
562
563 //Function to get configmap Data
564 func getConfigMapData(namespace, clusterName string, clientset *kubernetes.Clientset) (map[string]string, error) {
565
566      configmapClient := clientset.CoreV1().ConfigMaps(namespace)
567      configmapName := clusterName + "-configmap"
568      clusterConfigmap, err := configmapClient.Get(configmapName, metav1.GetOptions{})
569      if err != nil {
570         return nil, err
571      }
572
573      configmapData := clusterConfigmap.Data
574      return configmapData, nil
575 }
576
577
578 //Function to create job for KUD installation
579 func createKUDinstallerJob(clusterName, namespace string, labels map[string]string, clientset *kubernetes.Clientset) error{
580
581     var backOffLimit int32 = 0
582     var privi bool = true
583
584
585     jobClient := clientset.BatchV1().Jobs("default")
586
587         job := &batchv1.Job{
588
589         ObjectMeta: metav1.ObjectMeta{
590                         Name: "kud-" + clusterName,
591                        Labels: labels,
592                 },
593                 Spec: batchv1.JobSpec{
594                       Template: corev1.PodTemplateSpec{
595                                 ObjectMeta: metav1.ObjectMeta{
596                                         Labels: labels,
597                                 },
598
599
600                         Spec: corev1.PodSpec{
601                               HostNetwork: true,
602                               Containers: []corev1.Container{{
603                                           Name: "kud",
604                                           Image: "github.com/onap/multicloud-k8s:latest",
605                                           ImagePullPolicy: "IfNotPresent",
606                                           VolumeMounts: []corev1.VolumeMount{{
607                                                         Name: "multi-cluster",
608                                                         MountPath: "/opt/kud/multi-cluster",
609                                                         },
610                                                         {
611                                                         Name: "secret-volume",
612                                                         MountPath: "/.ssh",
613                                                         },
614
615                                            },
616                                            Command: []string{"/bin/sh","-c"},
617                                            Args: []string{"cp -r /.ssh /root/; chmod -R 600 /root/.ssh; ./installer --cluster " +  clusterName},
618                                            SecurityContext: &corev1.SecurityContext{
619                                                             Privileged : &privi,
620
621                                            },
622                                           },
623                                  },
624                                  Volumes: []corev1.Volume{{
625                                           Name: "multi-cluster",
626                                           VolumeSource: corev1.VolumeSource{
627                                                        HostPath: &corev1.HostPathVolumeSource{
628                                                               Path : "/opt/kud/multi-cluster",
629                                                      }}},
630                                           {
631                                           Name: "secret-volume",
632                                           VolumeSource: corev1.VolumeSource{
633                                                         Secret: &corev1.SecretVolumeSource{
634                                                               SecretName: "ssh-key-secret",
635                                                         },
636
637                                           }}},
638                                  RestartPolicy: "Never",
639                               },
640
641                              },
642                              BackoffLimit : &backOffLimit,
643                              },
644
645                          }
646                     _, err := jobClient.Create(job)
647                     if err != nil {
648                        fmt.Printf("ERROR occured while creating job to install KUD\n ERROR:%v", err)
649                        return err
650                     }
651                     return nil
652
653 }
654
655 //Function to Check if job succeeded
656 func checkJob(clusterName, namespace string, data, labels map[string]string, clientset *kubernetes.Clientset) {
657
658      fmt.Printf("\nChecking job status for cluster %s\n", clusterName)
659      jobName := "kud-" + clusterName
660      jobClient := clientset.BatchV1().Jobs(namespace)
661
662      for {
663          time.Sleep(2 * time.Second)
664
665          job, err := jobClient.Get(jobName, metav1.GetOptions{})
666          if err != nil {
667             fmt.Printf("ERROR: %v occured while retrieving job: %s", err, jobName)
668             return
669          }
670          jobSucceeded := job.Status.Succeeded
671          jobFailed := job.Status.Failed
672
673          if jobSucceeded == 1 {
674             fmt.Printf("\n Job succeeded, KUD successfully installed in Cluster %s\n", clusterName)
675
676             //KUD was installed successfully create configmap to store IP address info for the cluster
677             err = createConfigMap(data, labels, namespace, clientset)
678             if err != nil {
679                fmt.Printf("Error occured while creating Ip address configmap for cluster %v\n ERROR: %v", clusterName, err)
680                return
681             }
682             return
683          }
684
685         if jobFailed == 1 {
686            fmt.Printf("\n Job Failed, KUD not installed in Cluster %s, check pod logs\n", clusterName)
687            return
688         }
689
690      }
691     return
692
693 }
694 //Function to get software list from software CR
695 func getSoftwareList(softwareCR *bpav1alpha1.Software) (string, []interface{}, []interface{}) {
696
697      CRclusterName := softwareCR.GetLabels()["cluster"]
698
699      masterSofwareList := softwareCR.Spec.MasterSoftware
700      workerSoftwareList := softwareCR.Spec.WorkerSoftware
701
702      return CRclusterName, masterSofwareList, workerSoftwareList
703 }
704
705 //Function to install software in clusterHosts
706 func softwareInstaller(ipAddress, sshPrivateKey string, softwareList []interface{}) error {
707
708      var installString string
709      for _, software := range softwareList {
710
711         switch t := software.(type){
712         case string:
713              installString += software.(string) + " "
714         case interface{}:
715              softwareMap, errBool := software.(map[string]interface{})
716              if !errBool {
717                 fmt.Printf("Error occured, cannot install software %v\n", software)
718              }
719              for softwareName, versionMap := range softwareMap {
720
721                  versionMAP, _ := versionMap.(map[string]interface{})
722                  version := versionMAP["version"].(string)
723                  installString += softwareName + "=" + version + " "
724              }
725         default:
726             fmt.Printf("invalid format %v\n", t)
727         }
728
729      }
730
731      err := sshInstaller(installString, sshPrivateKey, ipAddress)
732      if err != nil {
733         return err
734      }
735      return nil
736
737 }
738
739 //Function to Run Installation commands via ssh
740 func sshInstaller(softwareString, sshPrivateKey, ipAddress string) error {
741
742      buffer, err := ioutil.ReadFile(sshPrivateKey)
743      if err != nil {
744         return err
745      }
746
747      key, err := ssh.ParsePrivateKey(buffer)
748      if err != nil {
749         return err
750      }
751
752      sshConfig := &ssh.ClientConfig{
753         User: "root",
754         Auth: []ssh.AuthMethod{
755               ssh.PublicKeys(key),
756      },
757
758      HostKeyCallback: ssh.InsecureIgnoreHostKey(),
759      }
760
761     client, err := ssh.Dial("tcp", ipAddress + ":22", sshConfig)
762     if err != nil {
763        return err
764     }
765
766     session, err := client.NewSession()
767     if err != nil {
768        return err
769     }
770
771     defer session.Close()
772     defer client.Close()
773
774     cmd := "sudo apt-get update && apt-get install " + softwareString + "-y"
775     err = session.Start(cmd)
776
777     if err != nil {
778        return err
779     }
780
781     return nil
782
783 }