Fixed Bug in controller
[icn.git] / cmd / bpa-operator / pkg / controller / provisioning / provisioning_controller.go
1 package provisioning
2
3 import (
4         "context"
5         "os"
6         "fmt"
7         "bytes"
8         "regexp"
9         "strings"
10         "io/ioutil"
11         "path/filepath"
12         "os/user"
13         "os/exec"
14
15
16         bpav1alpha1 "github.com/bpa-operator/pkg/apis/bpa/v1alpha1"
17         metav1 "k8s.io/apimachinery/pkg/apis/meta/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/tools/clientcmd"
23         "k8s.io/client-go/dynamic"
24         "sigs.k8s.io/controller-runtime/pkg/client"
25         "sigs.k8s.io/controller-runtime/pkg/controller"
26         "sigs.k8s.io/controller-runtime/pkg/handler"
27         "sigs.k8s.io/controller-runtime/pkg/manager"
28         "sigs.k8s.io/controller-runtime/pkg/reconcile"
29         logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
30         "sigs.k8s.io/controller-runtime/pkg/source"
31         "gopkg.in/ini.v1"
32 )
33
34 var log = logf.Log.WithName("controller_provisioning")
35 //Todo: Should be an input from the user
36 var dhcpLeaseFile = "/var/lib/dhcp/dhcpd.leases"
37 var kudInstallerScript = "/root/icn/deploy/kud/multicloud-k8s/kud/hosting_providers/vagrant"
38
39 /**
40 * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
41 * business logic.  Delete these comments after modifying this file.*
42  */
43
44 // Add creates a new Provisioning Controller and adds it to the Manager. The Manager will set fields on the Controller
45 // and Start it when the Manager is Started.
46 func Add(mgr manager.Manager) error {
47         return add(mgr, newReconciler(mgr))
48 }
49
50 // newReconciler returns a new reconcile.Reconciler
51 func newReconciler(mgr manager.Manager) reconcile.Reconciler {
52         return &ReconcileProvisioning{client: mgr.GetClient(), scheme: mgr.GetScheme()}
53 }
54
55 // add adds a new Controller to mgr with r as the reconcile.Reconciler
56 func add(mgr manager.Manager, r reconcile.Reconciler) error {
57         // Create a new controller
58         c, err := controller.New("provisioning-controller", mgr, controller.Options{Reconciler: r})
59         if err != nil {
60                 return err
61         }
62
63         // Watch for changes to primary resource Provisioning
64         err = c.Watch(&source.Kind{Type: &bpav1alpha1.Provisioning{}}, &handler.EnqueueRequestForObject{})
65         if err != nil {
66                 return err
67         }
68
69
70         return nil
71 }
72
73 // blank assignment to verify that ReconcileProvisioning implements reconcile.Reconciler
74 var _ reconcile.Reconciler = &ReconcileProvisioning{}
75
76 // ReconcileProvisioning reconciles a Provisioning object
77 type ReconcileProvisioning struct {
78         // This client, initialized using mgr.Client() above, is a split client
79         // that reads objects from the cache and writes to the apiserver
80         client client.Client
81         scheme *runtime.Scheme
82 }
83
84 // Reconcile reads that state of the cluster for a Provisioning object and makes changes based on the state read
85 // and what is in the Provisioning.Spec
86 // TODO(user): Modify this Reconcile function to implement your Controller logic.  This example creates
87 // a Pod as an example
88 // Note:
89 // The Controller will requeue the Request to be processed again if the returned error is non-nil or
90 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
91 func (r *ReconcileProvisioning) Reconcile(request reconcile.Request) (reconcile.Result, error) {
92         reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
93         reqLogger.Info("Reconciling Provisioning")
94
95         // Fetch the Provisioning instance
96         provisioningInstance := &bpav1alpha1.Provisioning{}
97         err := r.client.Get(context.TODO(), request.NamespacedName, provisioningInstance)
98         if err != nil {
99                 if errors.IsNotFound(err) {
100                         // Request object not found, could have been deleted after reconcile request.
101                         // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
102                         // Return and don't requeue
103                         return reconcile.Result{}, nil
104                 }
105                 // Error reading the object - requeue the request.
106                 return reconcile.Result{}, err
107         }
108
109         mastersList := provisioningInstance.Spec.Masters
110         workersList := provisioningInstance.Spec.Workers
111         bareMetalHostList, _ := listBareMetalHosts()
112
113
114         var allString string
115         var masterString string
116         var workerString string
117
118        //Iterate through mastersList and get all the mac addresses and IP addresses
119
120         for _, masterMap := range mastersList {
121
122                 for masterLabel, master := range masterMap {
123                    containsMac, bmhCR := checkMACaddress(bareMetalHostList, master.MACaddress)
124                    if containsMac{
125                       //fmt.Println( master.MACaddress)
126                       fmt.Printf("BareMetalHost CR %s has NIC with MAC Address %s\n", bmhCR, master.MACaddress)
127
128                       //Get IP address of master
129                       hostIPaddress, err := getHostIPaddress(master.MACaddress, dhcpLeaseFile )
130                      if err != nil {
131                         fmt.Printf("IP address not found for host with MAC address %s \n", master.MACaddress)
132                      }
133
134
135
136                       allString += masterLabel + "  ansible_ssh_host="  + hostIPaddress + " ansible_ssh_port=22" + "\n"
137                       masterString += masterLabel + "\n"
138
139                       fmt.Printf("%s : %s \n", hostIPaddress, master.MACaddress)
140
141
142
143                    } else {
144
145                       fmt.Printf("Host with MAC Address %s not found\n", master.MACaddress)
146                    }
147              }
148         }
149
150
151         //Iterate through workersList and get all the mac addresses
152         for _, workerMap := range workersList {
153
154                 for workerLabel, worker := range workerMap {
155                    containsMac, bmhCR := checkMACaddress(bareMetalHostList, worker.MACaddress)
156                    if containsMac{
157                       //fmt.Println( worker.MACaddress)
158                       fmt.Printf("Host %s matches that macAddress\n", bmhCR)
159
160                       //Get IP address of worker
161                       hostIPaddress, err := getHostIPaddress(worker.MACaddress, dhcpLeaseFile )
162                      if err != nil {
163                         fmt.Printf("IP address not found for host with MAC address %s \n", worker.MACaddress)
164                      }
165                       fmt.Printf("%s : %s \n", hostIPaddress, worker.MACaddress)
166
167                       allString += workerLabel + "  ansible_ssh_host="  + hostIPaddress + " ansible_ssh_port=22" + "\n"
168                       workerString += workerLabel + "\n"
169
170                    }else {
171
172                       fmt.Printf("Host with MAC Address %s not found\n", worker.MACaddress)
173                    }
174
175              }
176         }
177
178
179         //Create host.ini file
180         iniHostFilePath := provisioningInstance.Spec.HostsFile
181         newFile, err := os.Create(iniHostFilePath)
182         defer newFile.Close()
183
184
185         if err != nil {
186            fmt.Printf("Error occured while creating file \n %v", err)
187         }
188
189         hostFile, err := ini.Load(iniHostFilePath)
190         if err != nil {
191            fmt.Printf("Error occured while Loading file \n %v", err)
192         }
193
194         _, err = hostFile.NewRawSection("all", allString)
195         if err != nil {
196            fmt.Printf("Error occured while creating section \n %v", err)
197         }
198         _, err = hostFile.NewRawSection("kube-master", masterString)
199         if err != nil {
200            fmt.Printf("Error occured while creating section \n %v", err)
201         }
202
203         _, err = hostFile.NewRawSection("kube-node", workerString)
204         if err != nil {
205            fmt.Printf("Error occured while creating section \n %v", err)
206         }
207
208         _, err = hostFile.NewRawSection("etcd", masterString)
209         if err != nil {
210            fmt.Printf("Error occured while creating section \n %v", err)
211         }
212
213         _, err = hostFile.NewRawSection("k8s-cluser:children", "kube-node\n" + "kube-master")
214         if err != nil {
215            fmt.Printf("Error occured while creating section \n %v", err)
216         }
217
218
219         hostFile.SaveTo(iniHostFilePath)
220
221        //TODO: Test KUD installer part
222        //Copy host.ini file to the right path and install KUD
223        dstIniPath := kudInstallerScript + "/inventory/hosts.ini"
224        kudInstaller(iniHostFilePath, dstIniPath, kudInstallerScript)
225
226         return reconcile.Result{}, nil
227 }
228
229
230 //Function to Get List containing baremetal hosts
231 func listBareMetalHosts() (*unstructured.UnstructuredList, error) {
232
233      //Get Current User and kube config file
234      usr, err := user.Current()
235      if err != nil {
236         fmt.Println("Could not get current user\n")
237         return &unstructured.UnstructuredList{}, err
238      }
239
240      kubeConfig := filepath.Join(usr.HomeDir, ".kube", "config")
241
242      //Build Config Flags
243      config, err :=  clientcmd.BuildConfigFromFlags("", kubeConfig)
244      if err != nil {
245         fmt.Println("Could not build config\n")
246         return &unstructured.UnstructuredList{}, err
247      }
248
249     //Create Dynamic Client  for BareMetalHost CRD
250     bmhDynamicClient, err := dynamic.NewForConfig(config)
251
252     if err != nil {
253        fmt.Println("Could not create dynamic client for bareMetalHosts\n")
254        return &unstructured.UnstructuredList{}, err
255     }
256
257     //Create GVR representing a BareMetalHost CR
258     bmhGVR := schema.GroupVersionResource{
259       Group:    "metal3.io",
260       Version:  "v1alpha1",
261       Resource: "baremetalhosts",
262     }
263
264     //Get List containing all BareMetalHosts CRs
265     bareMetalHosts, err := bmhDynamicClient.Resource(bmhGVR).List(metav1.ListOptions{})
266     if err != nil {
267        fmt.Println("Error occured, cannot get BareMetalHosts list\n")
268        return &unstructured.UnstructuredList{}, err
269     }
270
271     return bareMetalHosts, nil
272 }
273
274 //Function to check if BareMetalHost containing MAC address exist
275 func checkMACaddress(bareMetalHostList *unstructured.UnstructuredList, macAddress string) (bool, string) {
276
277      //Convert macAddress to byte array for comparison
278      macAddressByte :=  []byte(macAddress)
279      macBool := false
280
281      for _, bareMetalHost := range bareMetalHostList.Items {
282          bmhJson, _ := bareMetalHost.MarshalJSON()
283
284          macBool = bytes.Contains(bmhJson, macAddressByte)
285          if macBool{
286              return macBool, bareMetalHost.GetName()
287          }
288
289       }
290
291          return macBool, ""
292
293 }
294
295 func getHostIPaddress(macAddress string, dhcpLeaseFilePath string ) (string, error) {
296
297      //Read the dhcp lease file
298      dhcpFile, err := ioutil.ReadFile(dhcpLeaseFilePath)
299      if err != nil {
300         fmt.Println("Failed to read lease file\n")
301         return "", err
302      }
303
304      dhcpLeases := string(dhcpFile)
305
306      //Regex to use to search dhcpLeases
307      regex := "lease.*{|ethernet.*"
308      re, err := regexp.Compile(regex)
309      if err != nil {
310         fmt.Println("Could not create Regexp object\n")
311         return "", err
312      }
313
314      //Get String containing leased Ip addresses and Corressponding MAC addresses
315      out := re.FindAllString(dhcpLeases, -1)
316      outString := strings.Join(out, " ")
317      stringReplacer := strings.NewReplacer("lease", "", "{ ethernet ", "", ";", "")
318      replaced := stringReplacer.Replace(outString)
319      ipMacList := strings.Fields(replaced)
320
321
322      //Get IP addresses corresponding to Input MAC Address
323      for idx := len(ipMacList)-1 ; idx >= 0; idx -- {
324          item := ipMacList[idx]
325          if item == macAddress  {
326             ipAdd := ipMacList[idx -1]
327             return ipAdd, nil
328     }
329
330  }
331      return "", nil
332 }
333
334 func kudInstaller(srcIniPath, dstIniPath, kudInstallerPath string) {
335
336       err := os.Chdir(kudInstallerPath)
337       if err != nil {
338           fmt.Printf("Could not change directory %v", err)
339           return
340         }
341
342       commands := "cp " + srcIniPath + " "  + dstIniPath + "; ./installer.sh| tee kud_installer.log"
343
344       cmd := exec.Command("/bin/bash", "-c", commands)
345       err = cmd.Run()
346
347       if err != nil {
348           fmt.Printf("Error Occured while running KUD install scripts %v", err)
349           return
350         }
351
352       return
353 }