Additions to measurements
[ric-plt/vespamgr.git] / cmd / vesmgr / vesmgr.go
1 /*
2  *  Copyright (c) 2019 AT&T Intellectual Property.
3  *  Copyright (c) 2018-2019 Nokia.
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *  This source code is part of the near-RT RIC (RAN Intelligent Controller)
18  *  platform project (RICP).
19  *
20  */
21
22 package main
23
24 import (
25         "errors"
26         "io/ioutil"
27         "net"
28         "net/http"
29         "os"
30         "time"
31
32         mdcloggo "gerrit.o-ran-sc.org/r/com/golog.git"
33 )
34
35 var appmgrDomain string
36
37 const appmgrXAppConfigPath = "/ric/v1/config"
38 const appmgrPort = "8080"
39
40 // VesMgr contains runtime information of the vesmgr process
41 type VesMgr struct {
42         myIPAddress         string
43         chXAppSubscriptions chan subscriptionNotification
44         chXAppNotifications chan []byte
45         chSupervision       chan chan string
46         chVesagent          chan error
47         vesagent            cmdRunner
48         httpServer          HTTPServer
49 }
50
51 type subscriptionNotification struct {
52         subscribed bool
53         err        error
54         subsID     string
55 }
56
57 var logger *mdcloggo.MdcLogger
58
59 // Version information, which is filled during compilation
60 // Version tag of vesmgr container
61 var Version string
62
63 // Hash of the git commit used in building
64 var Hash string
65
66 const vesmgrXappNotifPort = "8080"
67 const vesmgrXappNotifPath = "/vesmgr_xappnotif/"
68 const timeoutPostXAppSubscriptions = 5
69 const vespaConfigFile = "/etc/ves-agent/ves-agent.yaml"
70
71 func init() {
72         logger, _ = mdcloggo.InitLogger("vesmgr")
73 }
74
75 func getMyIP() (myIP string, retErr error) {
76         addrs, err := net.InterfaceAddrs()
77         if err != nil {
78                 logger.Error("net.InterfaceAddrs failed: %s", err.Error())
79                 return "", err
80         }
81         for _, addr := range addrs {
82                 // check the address type and if it is not a loopback take it
83                 if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
84                         if ipnet.IP.To4() != nil {
85                                 logger.Info("My IP Address: %s", ipnet.IP.String())
86                                 return ipnet.IP.String(), nil
87                         }
88                 }
89         }
90         return "", nil
91 }
92
93 func createConf(fname string, xappMetrics []byte) {
94         f, err := os.Create(fname)
95         if err != nil {
96                 logger.Error("Cannot create vespa conf file: %s", err.Error())
97                 os.Exit(1)
98         }
99         defer f.Close()
100
101         createVespaConfig(f, xappMetrics)
102         logger.Info("Vespa config created")
103 }
104
105 func (vesmgr *VesMgr) subscribeXAppNotifications() {
106         xappNotifURL := "http://" + vesmgr.myIPAddress + ":" + vesmgrXappNotifPort + vesmgrXappNotifPath
107         subsURL := "http://" + appmgrDomain + ":" + appmgrPort + appmgrSubsPath
108         go subscribexAppNotifications(xappNotifURL, vesmgr.chXAppSubscriptions, timeoutPostXAppSubscriptions, subsURL)
109         logger.Info("xApp notifications subscribed from %s", subsURL)
110 }
111
112 // Init initializes the vesmgr
113 func (vesmgr *VesMgr) Init(listenPort string) *VesMgr {
114         logger.Info("vesmgrInit")
115         logger.Info("version: %s (%s)", Version, Hash)
116
117         var err error
118         if vesmgr.myIPAddress, err = getMyIP(); err != nil || vesmgr.myIPAddress == "" {
119                 logger.Error("Cannot get myIPAddress: IP %s", vesmgr.myIPAddress)
120                 panic("Cannot get my IP address")
121         }
122
123         var ok bool
124         appmgrDomain, ok = os.LookupEnv("VESMGR_APPMGRDOMAIN")
125         if ok {
126                 logger.Info("Using appmgrdomain %s", appmgrDomain)
127         } else {
128                 appmgrDomain = "service-ricplt-appmgr-http.ricplt.svc.cluster.local"
129                 logger.Info("Using default appmgrdomain %s", appmgrDomain)
130         }
131         vesmgr.chXAppSubscriptions = make(chan subscriptionNotification)
132         // Create notifications as buffered channel so that
133         // xappmgr does not block if we are stuck somewhere
134         vesmgr.chXAppNotifications = make(chan []byte, 10)
135         vesmgr.chSupervision = make(chan chan string)
136         vesmgr.chVesagent = make(chan error)
137         vesmgr.httpServer = HTTPServer{}
138         vesmgr.httpServer.init(vesmgr.myIPAddress + ":" + listenPort)
139         vesmgr.vesagent = makeRunner("ves-agent", "-i", os.Getenv("VESMGR_HB_INTERVAL"),
140                 "-m", os.Getenv("VESMGR_MEAS_INTERVAL"), "--Measurement.Prometheus.Address",
141                 os.Getenv("VESMGR_PROMETHEUS_ADDR"), "--AlertManager.Bind", os.Getenv("VESMGR_ALERTMANAGER_BIND_ADDR"),
142                 "--Debug")
143         return vesmgr
144 }
145
146 func (vesmgr *VesMgr) startVesagent() {
147         vesmgr.vesagent.run(vesmgr.chVesagent)
148 }
149
150 func (vesmgr *VesMgr) killVespa() error {
151         logger.Info("Killing vespa")
152         err := vesmgr.vesagent.kill()
153         if err != nil {
154                 logger.Error("Cannot kill vespa: %s", err.Error())
155                 return err
156         }
157         return <-vesmgr.chVesagent // wait vespa exit
158 }
159
160 func queryXAppsConfig(appmgrURL string, timeout time.Duration) ([]byte, error) {
161         emptyConfig := []byte("{}")
162         logger.Info("query xAppConfig started, url %s", appmgrURL)
163         req, err := http.NewRequest("GET", appmgrURL, nil)
164         if err != nil {
165                 logger.Error("Failed to create a HTTP request: %s", err)
166                 return emptyConfig, err
167         }
168         req.Header.Set("Content-Type", "application/json")
169         client := &http.Client{}
170         client.Timeout = time.Second * timeout
171         resp, err := client.Do(req)
172         if err != nil {
173                 logger.Error("Query xApp config failed: %s", err)
174                 return emptyConfig, err
175         }
176         defer resp.Body.Close()
177         if resp.StatusCode == http.StatusOK {
178                 body, err := ioutil.ReadAll(resp.Body)
179                 if err != nil {
180                         logger.Error("Failed to read xApp config body: %s", err)
181                         return emptyConfig, err
182                 }
183                 logger.Info("query xAppConfig completed")
184                 return body, nil
185         }
186         logger.Error("Error from xApp config query: %s", resp.Status)
187         return emptyConfig, errors.New(resp.Status)
188 }
189
190 func queryConf() (appConfig []byte, err error) {
191         for i := 0; i < 10; i++ {
192                 appConfig, err = queryXAppsConfig("http://"+appmgrDomain+":"+appmgrPort+appmgrXAppConfigPath, 10*time.Second)
193                 if len(appConfig) > 0 {
194                         break
195                 }
196                 time.Sleep(5 * time.Second)
197         }
198         return appConfig, err
199 }
200
201 func (vesmgr *VesMgr) emptyNotificationsChannel() {
202         for {
203                 select {
204                 case <-vesmgr.chXAppNotifications:
205                         // we don't care the content
206                 default:
207                         return
208                 }
209         }
210 }
211
212 func (vesmgr *VesMgr) servRequest() {
213         select {
214         case supervision := <-vesmgr.chSupervision:
215                 logger.Info("vesmgr: supervision")
216                 supervision <- "OK"
217         case xAppNotif := <-vesmgr.chXAppNotifications:
218                 logger.Info("vesmgr: xApp notification")
219                 logger.Info(string(xAppNotif))
220                 vesmgr.emptyNotificationsChannel()
221                 /*
222                 * If xapp config query fails then we cannot create
223                 * a new configuration and kill vespa.
224                 * In that case we assume that
225                 * the situation is fixed when the next
226                 * xapp notif comes
227                  */
228                 xappConfig, err := queryConf()
229                 if err == nil {
230                         vesmgr.killVespa()
231                         createConf(vespaConfigFile, xappConfig)
232                         vesmgr.startVesagent()
233                 }
234         case err := <-vesmgr.chVesagent:
235                 logger.Error("Vesagent exited: " + err.Error())
236                 os.Exit(1)
237         }
238 }
239
240 func (vesmgr *VesMgr) waitSubscriptionLoop() {
241         for {
242                 select {
243                 case supervision := <-vesmgr.chSupervision:
244                         logger.Info("vesmgr: supervision")
245                         supervision <- "OK"
246                 case isSubscribed := <-vesmgr.chXAppSubscriptions:
247                         if isSubscribed.err != nil {
248                                 logger.Error("Failed to make xApp subscriptions, vesmgr exiting: %s", isSubscribed.err)
249                                 os.Exit(1)
250                         }
251                         return
252                 }
253         }
254 }
255
256 // Run the vesmgr process main loop
257 func (vesmgr *VesMgr) Run() {
258         logger.Info("vesmgr main loop ready")
259
260         vesmgr.httpServer.start(vesmgrXappNotifPath, vesmgr.chXAppNotifications, vesmgr.chSupervision)
261
262         vesmgr.subscribeXAppNotifications()
263
264         vesmgr.waitSubscriptionLoop()
265
266         xappConfig, _ := queryConf()
267
268         createConf(vespaConfigFile, xappConfig)
269
270         vesmgr.startVesagent()
271         for {
272                 vesmgr.servRequest()
273         }
274 }