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