Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 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. |
Roni Riska | 6ffba08 | 2019-11-27 10:59:54 +0200 | [diff] [blame] | 16 | * |
| 17 | * This source code is part of the near-RT RIC (RAN Intelligent Controller) |
| 18 | * platform project (RICP). |
| 19 | * |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 20 | */ |
| 21 | |
| 22 | package main |
| 23 | |
| 24 | import ( |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 25 | "errors" |
| 26 | "io/ioutil" |
| 27 | "net" |
| 28 | "net/http" |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 29 | "os" |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 30 | "time" |
| 31 | |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 32 | mdcloggo "gerrit.o-ran-sc.org/r/com/golog.git" |
| 33 | ) |
| 34 | |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 35 | var appmgrDomain string |
| 36 | |
| 37 | const appmgrXAppConfigPath = "/ric/v1/config" |
| 38 | const appmgrPort = "8080" |
| 39 | |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 40 | // VesMgr contains runtime information of the vesmgr process |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 41 | type VesMgr struct { |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 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 |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 49 | } |
| 50 | |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 51 | type subscriptionNotification struct { |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 52 | subscribed bool |
| 53 | err error |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 54 | subsID string |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 55 | } |
| 56 | |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 57 | var logger *mdcloggo.MdcLogger |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 58 | |
Roni Riska | 364295f | 2019-09-30 09:39:12 +0300 | [diff] [blame] | 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 | |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 66 | const vesmgrXappNotifPort = "8080" |
| 67 | const vesmgrXappNotifPath = "/vesmgr_xappnotif/" |
| 68 | const timeoutPostXAppSubscriptions = 5 |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 69 | const vespaConfigFile = "/etc/ves-agent/ves-agent.yaml" |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 70 | |
| 71 | func init() { |
| 72 | logger, _ = mdcloggo.InitLogger("vesmgr") |
| 73 | } |
| 74 | |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 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 | } |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 92 | |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 93 | func createConf(fname string, xappMetrics []byte) { |
| 94 | f, err := os.Create(fname) |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 95 | if err != nil { |
| 96 | logger.Error("Cannot create vespa conf file: %s", err.Error()) |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 97 | os.Exit(1) |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 98 | } |
| 99 | defer f.Close() |
| 100 | |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 101 | createVespaConfig(f, xappMetrics) |
| 102 | logger.Info("Vespa config created") |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 103 | } |
| 104 | |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 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) |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 110 | } |
| 111 | |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 112 | // Init initializes the vesmgr |
| 113 | func (vesmgr *VesMgr) Init(listenPort string) *VesMgr { |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 114 | logger.Info("vesmgrInit") |
Roni Riska | 364295f | 2019-09-30 09:39:12 +0300 | [diff] [blame] | 115 | logger.Info("version %s (%s)", Version, Hash) |
| 116 | |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 117 | var err error |
| 118 | if vesmgr.myIPAddress, err = getMyIP(); err != nil || vesmgr.myIPAddress == "" { |
| 119 | logger.Error("Cannot get myIPAddress: IP %s, err %s", vesmgr.myIPAddress, err.Error()) |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 120 | panic("Cannot get my IP address") |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 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 | } |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 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")) |
| 142 | return vesmgr |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 143 | } |
| 144 | |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 145 | func (vesmgr *VesMgr) startVesagent() { |
| 146 | vesmgr.vesagent.run(vesmgr.chVesagent) |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 147 | } |
| 148 | |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 149 | func (vesmgr *VesMgr) killVespa() error { |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 150 | logger.Info("Killing vespa") |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 151 | err := vesmgr.vesagent.kill() |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 152 | if err != nil { |
| 153 | logger.Error("Cannot kill vespa: %s", err.Error()) |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 154 | return err |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 155 | } |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 156 | return <-vesmgr.chVesagent // wait vespa exit |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 157 | } |
| 158 | |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 159 | func queryXAppsConfig(appmgrURL string, timeout time.Duration) ([]byte, error) { |
| 160 | emptyConfig := []byte("{}") |
| 161 | logger.Info("query xAppConfig started, url %s", appmgrURL) |
| 162 | req, err := http.NewRequest("GET", appmgrURL, nil) |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 163 | if err != nil { |
| 164 | logger.Error("Failed to create a HTTP request: %s", err) |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 165 | return emptyConfig, err |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 166 | } |
| 167 | req.Header.Set("Content-Type", "application/json") |
| 168 | client := &http.Client{} |
| 169 | client.Timeout = time.Second * timeout |
| 170 | resp, err := client.Do(req) |
| 171 | if err != nil { |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 172 | logger.Error("Query xApp config failed: %s", err) |
| 173 | return emptyConfig, err |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 174 | } |
| 175 | defer resp.Body.Close() |
| 176 | if resp.StatusCode == http.StatusOK { |
| 177 | body, err := ioutil.ReadAll(resp.Body) |
| 178 | if err != nil { |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 179 | logger.Error("Failed to read xApp config body: %s", err) |
| 180 | return emptyConfig, err |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 181 | } |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 182 | logger.Info("query xAppConfig completed") |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 183 | return body, nil |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 184 | } |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 185 | logger.Error("Error from xApp config query: %s", resp.Status) |
| 186 | return emptyConfig, errors.New(resp.Status) |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 187 | } |
| 188 | |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 189 | func queryConf() ([]byte, error) { |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 190 | return queryXAppsConfig("http://"+appmgrDomain+":"+appmgrPort+appmgrXAppConfigPath, |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 191 | 10*time.Second) |
| 192 | } |
| 193 | |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 194 | func (vesmgr *VesMgr) emptyNotificationsChannel() { |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 195 | for { |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 196 | select { |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 197 | case <-vesmgr.chXAppNotifications: |
| 198 | // we don't care the content |
| 199 | default: |
| 200 | return |
| 201 | } |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | func (vesmgr *VesMgr) servRequest() { |
| 206 | select { |
| 207 | case supervision := <-vesmgr.chSupervision: |
| 208 | logger.Info("vesmgr: supervision") |
| 209 | supervision <- "OK" |
| 210 | case xAppNotif := <-vesmgr.chXAppNotifications: |
| 211 | logger.Info("vesmgr: xApp notification") |
| 212 | logger.Info(string(xAppNotif)) |
| 213 | vesmgr.emptyNotificationsChannel() |
| 214 | /* |
| 215 | * If xapp config query fails then we cannot create |
| 216 | * a new configuration and kill vespa. |
| 217 | * In that case we assume that |
| 218 | * the situation is fixed when the next |
| 219 | * xapp notif comes |
| 220 | */ |
| 221 | xappConfig, err := queryConf() |
| 222 | if err == nil { |
| 223 | vesmgr.killVespa() |
| 224 | createConf(vespaConfigFile, xappConfig) |
| 225 | vesmgr.startVesagent() |
| 226 | } |
| 227 | case err := <-vesmgr.chVesagent: |
| 228 | logger.Error("Vesagent exited: " + err.Error()) |
| 229 | os.Exit(1) |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | func (vesmgr *VesMgr) waitSubscriptionLoop() { |
| 234 | for { |
| 235 | select { |
| 236 | case supervision := <-vesmgr.chSupervision: |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 237 | logger.Info("vesmgr: supervision") |
| 238 | supervision <- "OK" |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 239 | case isSubscribed := <-vesmgr.chXAppSubscriptions: |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 240 | if isSubscribed.err != nil { |
| 241 | logger.Error("Failed to make xApp subscriptions, vesmgr exiting: %s", isSubscribed.err) |
| 242 | os.Exit(1) |
| 243 | } |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 244 | return |
Katri Turunen | 412df96 | 2019-09-16 08:48:18 +0300 | [diff] [blame] | 245 | } |
Katri Turunen | 4b74f01 | 2019-08-15 10:49:36 +0300 | [diff] [blame] | 246 | } |
| 247 | } |
Roni Riska | fc77ebb | 2019-09-26 08:20:44 +0300 | [diff] [blame] | 248 | |
| 249 | // Run the vesmgr process main loop |
| 250 | func (vesmgr *VesMgr) Run() { |
| 251 | logger.Info("vesmgr main loop ready") |
| 252 | vesmgr.httpServer.start(vesmgrXappNotifPath, vesmgr.chXAppNotifications, vesmgr.chSupervision) |
| 253 | vesmgr.subscribeXAppNotifications() |
| 254 | vesmgr.waitSubscriptionLoop() |
| 255 | xappConfig, _ := queryConf() |
| 256 | createConf(vespaConfigFile, xappConfig) |
| 257 | vesmgr.startVesagent() |
| 258 | for { |
| 259 | vesmgr.servRequest() |
| 260 | } |
| 261 | } |