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