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