src: Add DMA localagent
[barometer.git] / src / dma / cmd / infofetch / virsh_domain.go
1 /*
2  * Copyright 2017 Red Hat
3  *
4  *   Licensed under the Apache License, Version 2.0 (the "License");
5  *   you may not use this file except in compliance with the License.
6  *   You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *   Unless required by applicable law or agreed to in writing, software
11  *   distributed under the License is distributed on an "AS IS" BASIS,
12  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *   See the License for the specific language governing permissions and
14  *   limitations under the License.
15  */
16
17 package main
18
19 import (
20         "context"
21         "encoding/json"
22         "encoding/xml"
23         "fmt"
24         libvirt "github.com/libvirt/libvirt-go"
25         "log"
26 )
27
28 type instance struct {
29         Name  string `xml:"name"`
30         Owner struct {
31                 User    string `xml:"user"`
32                 Project string `xml:"project"`
33         } `xml:"owner"`
34         Flavor struct {
35                 Name string `xml:"name,attr"`
36         } `xml:"flavor"`
37 }
38
39 type domain struct {
40         Name    string `xml:"name"`
41         Devices struct {
42                 Interfaces []struct {
43                         Type string `xml:"type,attr"`
44                         Mac  struct {
45                                 Address string `xml:"address,attr"`
46                         } `xml:"mac"`
47                         Target struct {
48                                 Dev string `xml:"dev,attr"`
49                         } `xml:"target"`
50                 } `xml:"interface"`
51         } `xml:"devices"`
52 }
53
54 type osVMAnnotation struct {
55         Name    string
56         Owner   string
57         Project string
58         Flavor  string
59 }
60
61 type osVMInterfaceAnnotation struct {
62         Type    string
63         MacAddr string
64         Target  string
65         VMName  string
66 }
67
68 func parseNovaMetadata(metadata string) (*osVMAnnotation, error) {
69         data := new(instance)
70
71         if err := xml.Unmarshal([]byte(metadata), data); err != nil {
72                 log.Println("XML Unmarshal error:", err)
73                 return nil, err
74         }
75         log.Printf("Get name: %s user: %s, project: %s, flavor: %s", data.Name, data.Owner.User, data.Owner.Project, data.Flavor.Name)
76         return &osVMAnnotation{
77                 Name:    data.Name,
78                 Owner:   data.Owner.User,
79                 Project: data.Owner.Project,
80                 Flavor:  data.Flavor.Name}, nil
81 }
82
83 func parseXMLForMAC(dumpxml string) (*[]osVMInterfaceAnnotation, error) {
84         data := new(domain)
85
86         if err := xml.Unmarshal([]byte(dumpxml), data); err != nil {
87                 log.Println("XML Unmarshal error:", err)
88                 return nil, err
89         }
90
91         ifAnnotation := make([]osVMInterfaceAnnotation, len(data.Devices.Interfaces))
92         for i, v := range data.Devices.Interfaces {
93                 log.Printf("Interface type: %s, mac_addr: %s, target_dev: %s", v.Type, v.Mac.Address, v.Target.Dev)
94                 ifAnnotation[i] = osVMInterfaceAnnotation{
95                         Type:    v.Type,
96                         MacAddr: v.Mac.Address,
97                         Target:  v.Target.Dev,
98                         VMName:  data.Name}
99         }
100         return &ifAnnotation, nil
101 }
102
103 func setInterfaceAnnotation(ifInfo *[]osVMInterfaceAnnotation, vmIfInfoChan chan string) {
104         for _, v := range *ifInfo {
105                 ifInfoJSON, err := json.Marshal(v)
106                 if err != nil {
107                         log.Fatalf("Err: %v", err)
108                 }
109                 infoPool.Set(fmt.Sprintf("if/%s/%s", v.Target, "network"), string(ifInfoJSON))
110
111                 vmIfInfoChan <- fmt.Sprintf("if/%s/%s", v.Target, "network")
112         }
113         return
114 }
115
116 func domainEventLifecycleCallback(vmIfInfo chan string) func(c *libvirt.Connect, d *libvirt.Domain, event *libvirt.DomainEventLifecycle) {
117
118         return func(c *libvirt.Connect,
119                 d *libvirt.Domain, event *libvirt.DomainEventLifecycle) {
120                 domName, _ := d.GetName()
121
122                 switch event.Event {
123                 case libvirt.DOMAIN_EVENT_DEFINED:
124                         // VM defined: vmname (libvirt, nova), user, project, flavor
125                         // Redis: <vnname>/vminfo
126                         log.Printf("Event defined: domName: %s, event: %v", domName, event)
127                         metadata, err := d.GetMetadata(libvirt.DOMAIN_METADATA_ELEMENT, "http://openstack.org/xmlns/libvirt/nova/1.0", libvirt.DOMAIN_AFFECT_CONFIG)
128                         if err != nil {
129                                 log.Fatalf("Err: %v", err)
130                         }
131                         vmInfo, err := parseNovaMetadata(metadata)
132                         if err != nil {
133                                 log.Fatalf("Err: %v", err)
134                         }
135                         vmInfoJSON, err := json.Marshal(vmInfo)
136                         if err != nil {
137                                 log.Fatalf("Err: %v", err)
138                         }
139                         infoPool.Set(fmt.Sprintf("vm/%s/%s", domName, "vminfo"), string(vmInfoJSON))
140                 case libvirt.DOMAIN_EVENT_STARTED:
141                         // VM started: interface type, interface mac addr, intarface type
142                         // Redis: <vnname>/interfaces
143                         log.Printf("Event started: domName: %s, event: %v", domName, event)
144
145                         xml, err := d.GetXMLDesc(0)
146                         if err != nil {
147                                 log.Fatalf("Err: %v", err)
148                         }
149                         ifInfo, err := parseXMLForMAC(xml)
150                         if err != nil {
151                                 log.Fatalf("Err: %v", err)
152                         }
153                         setInterfaceAnnotation(ifInfo, vmIfInfo)
154
155                         ifInfoJSON, err := json.Marshal(ifInfo)
156                         if err != nil {
157                                 log.Fatalf("Err: %v", err)
158                         }
159                         infoPool.Set(fmt.Sprintf("vm/%s/%s", domName, "interfaces"), string(ifInfoJSON))
160                 case libvirt.DOMAIN_EVENT_UNDEFINED:
161                         log.Printf("Event undefined: domName: %s, event: %v", domName, event)
162                         vmIFInfo, err := infoPool.Get(fmt.Sprintf("vm/%s/%s", domName, "interfaces"))
163                         if err != nil {
164                                 log.Fatalf("Err: %v", err)
165                         } else {
166                                 var interfaces []osVMInterfaceAnnotation
167                                 err = json.Unmarshal([]byte(vmIFInfo), &interfaces)
168                                 if err != nil {
169                                         log.Fatalf("Err: %v", err)
170                                 } else {
171                                         for _, v := range interfaces {
172                                                 infoPool.Del(fmt.Sprintf("if/%s/%s", v.Target, "network"))
173                                                 infoPool.Del(fmt.Sprintf("if/%s/%s", v.Target, "neutron_network"))
174                                         }
175                                 }
176                         }
177                         infoPool.Del(fmt.Sprintf("vm/%s/%s", domName, "vminfo"))
178                         infoPool.Del(fmt.Sprintf("vm/%s/%s", domName, "interfaces"))
179                 default:
180                         log.Printf("Event misc: domName: %s, event: %v", domName, event)
181                 }
182         }
183 }
184
185 // GetActiveDomain gets all active domain information from libvirt and it should be called at startup to get
186 // current running domain information
187 func GetActiveDomain(conn *libvirt.Connect, vmIfInfoChan chan string) error {
188         doms, err := conn.ListAllDomains(libvirt.CONNECT_LIST_DOMAINS_ACTIVE)
189         if err != nil {
190                 log.Fatalf("libvirt dom list error: %s", err)
191                 return err
192         }
193
194         for _, d := range doms {
195                 name, err := d.GetName()
196
197                 // Get VM Info
198                 metadata, err := d.GetMetadata(libvirt.DOMAIN_METADATA_ELEMENT, "http://openstack.org/xmlns/libvirt/nova/1.0", libvirt.DOMAIN_AFFECT_CONFIG)
199                 if err != nil {
200                         log.Fatalf("Err: %v", err)
201                         return err
202                 }
203                 vmInfo, err := parseNovaMetadata(metadata)
204                 if err != nil {
205                         log.Fatalf("Err: %v", err)
206                         return err
207                 }
208                 vmInfoJSON, err := json.Marshal(vmInfo)
209                 if err != nil {
210                         log.Fatalf("Err: %v", err)
211                         return err
212                 }
213                 infoPool.Set(fmt.Sprintf("vm/%s/%s", name, "vminfo"), string(vmInfoJSON))
214
215                 // Get Network info
216                 xml, err := d.GetXMLDesc(0)
217                 if err != nil {
218                         log.Fatalf("Err: %v", err)
219                         return err
220                 }
221                 ifInfo, err := parseXMLForMAC(xml)
222                 if err != nil {
223                         log.Fatalf("Err: %v", err)
224                         return err
225                 }
226                 setInterfaceAnnotation(ifInfo, vmIfInfoChan)
227
228                 ifInfoJSON, err := json.Marshal(ifInfo)
229                 if err != nil {
230                         log.Fatalf("Err: %v", err)
231                         return err
232                 }
233                 infoPool.Set(fmt.Sprintf("vm/%s/%s", name, "interfaces"), string(ifInfoJSON))
234         }
235         return nil
236 }
237
238 // RunVirshEventLoop is event loop to watch libvirt update
239 func RunVirshEventLoop(ctx context.Context, conn *libvirt.Connect, vmIfInfoChan chan string) error {
240         callbackID, err := conn.DomainEventLifecycleRegister(nil, domainEventLifecycleCallback(vmIfInfoChan))
241         if err != nil {
242                 log.Fatalf("Err: callbackid: %d %v", callbackID, err)
243         }
244
245         libvirt.EventAddTimeout(5000, func(timer int) { return }) // 5000 = 5sec
246         log.Printf("Entering libvirt event loop()")
247 EVENTLOOP:
248         for {
249                 select {
250                 case <-ctx.Done():
251                         break EVENTLOOP
252                 default:
253                         if err := libvirt.EventRunDefaultImpl(); err != nil {
254                                 log.Fatalf("%v", err)
255                         }
256                 }
257         }
258         log.Printf("Quitting libvirt event loop()")
259
260         if err := conn.DomainEventDeregister(callbackID); err != nil {
261                 log.Fatalf("%v", err)
262         }
263         return nil
264 }