# This source code is part of the near-RT RIC (RAN Intelligent Controller)
# platform project (RICP).
#
-# Start from golang v1.12 base image
-FROM golang:1.12 as gobuild
+
+FROM golang:1.12 as gobuild-vespamgr
+
+# Install utilities
+RUN apt update && apt install -y iputils-ping net-tools curl sudo
# Set the Working Directory for ves-agent inside the container
RUN mkdir -p $GOPATH/src/VESPA
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH && \
go install -v ./ves-agent
-# Set the Working Directory for vesmgr inside the container
-RUN mkdir -p $GOPATH/src/vesmgr
-WORKDIR $GOPATH/src/vesmgr
-
-# Copy vesmgr to the Working Directory
-COPY $HOME/ .
+# Set the Working Directory for vespamgr inside the container
+RUN mkdir -p /go/src/vespamgr
+RUN mkdir -p /cfg
+COPY . /go/src/vespamgr
+WORKDIR /go/src/vespamgr
RUN ./build_vesmgr.sh
-#################
-#
-# Second phase, copy compiled stuff to a runtime container
-
-# Ubuntu or something smaller?
+# Final, executable and deployable container
FROM ubuntu:18.04
-# For trouble-shooting
-RUN apt-get update; apt-get install -y \
- iputils-ping \
- net-tools \
- curl \
- tcpdump
-# Create the configuration directory for ves agent
RUN mkdir -p /etc/ves-agent
-COPY --from=gobuild root/go/bin /root/go/bin
+
+COPY --from=gobuild-vespamgr /usr/local/lib /usr/local/lib
+COPY --from=gobuild-vespamgr /root/go/bin /root/go/bin
+COPY --from=gobuild-vespamgr /root/go/bin/vespamgr /usr/local/bin/vesmgr
+COPY --from=gobuild-vespamgr /root/go/bin/vespamgr /vespamgr
+COPY --from=gobuild-vespamgr /go/src/vespamgr/config/* /cfg/
+
+RUN ldconfig
+
+ENV CFG_FILE=/cfg/config-file.json
+ENV RMR_SEED_RT=/cfg/uta_rtg.rt
ENV PATH="/root/go/bin:${PATH}"
-ENTRYPOINT ["vesmgr"]
+ENTRYPOINT ["/vespamgr"]
# platform project (RICP).
#
-
-set -e
-set -x
-# Load modules
-GO111MODULE=on go mod download
+# Install RMR from deb packages at packagecloud.io
+rmr=rmr_4.1.2_amd64.deb
+wget --content-disposition https://packagecloud.io/o-ran-sc/release/packages/debian/stretch/$rmr/download.deb
+sudo dpkg -i $rmr
+rm $rmr
+rmrdev=rmr-dev_4.1.2_amd64.deb
+wget --content-disposition https://packagecloud.io/o-ran-sc/release/packages/debian/stretch/$rmrdev/download.deb
+sudo dpkg -i $rmrdev
+rm $rmrdev
+
+# Required to find nng and rmr libs
+export LD_LIBRARY_PATH=/usr/local/lib
+
+# Go install, build, etc
export GOPATH=$HOME/go
-export PATH=$GOPATH/bin:$GOROOT/bin:$PATH
+export PATH=$GOPATH/bin:$PATH
+
+# xApp-framework stuff
+export CFG_FILE=$PWD/config/config-file-ut.json
+export RMR_SEED_RT=$PWD/config/uta_rtg.rt
+
+GO111MODULE=on GO_ENABLED=0 GOOS=linux
# Run vesmgr UT
-go test ./...
+GO111MODULE=on go test -v -p 1 -cover -coverprofile=coverage.out ./...
# setup version tag
if [ -f container-tag.yaml ]
hash=$(git rev-parse --short HEAD || true)
-# Install vesmgr
-go install -ldflags "-X main.Version=$tag -X main.Hash=$hash" -v ./cmd/vesmgr
-
-
+# Install vespamgr
+go install -ldflags "-X main.Version=$tag -X main.Hash=$hash" -v $PWD/cmd/vespamgr
+++ /dev/null
-/*
- * Copyright (c) 2019 AT&T Intellectual Property.
- * Copyright (c) 2018-2019 Nokia.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This source code is part of the near-RT RIC (RAN Intelligent Controller)
- * platform project (RICP).
- *
- */
-
-package main
-
-import (
- "fmt"
- "io/ioutil"
- "net"
- "net/http"
-)
-
-// SupervisionURL is the url where kubernetes posts alive queries
-const SupervisionURL = "/supervision/"
-
-// HTTPServer is the VesMgr HTTP server struct
-type HTTPServer struct {
- listener net.Listener
-}
-
-func (s *HTTPServer) init(address string) *HTTPServer {
- var err error
- s.listener, err = net.Listen("tcp", address)
- if err != nil {
- panic("Cannot listen:" + err.Error())
- }
- return s
-}
-
-func (s *HTTPServer) start(notifPath string, notifCh chan []byte, supCh chan chan string) {
- go runHTTPServer(s.listener, notifPath, notifCh, supCh)
-}
-
-func (s *HTTPServer) addr() net.Addr {
- return s.listener.Addr()
-}
-
-func runHTTPServer(listener net.Listener, xappNotifURL string, notifCh chan []byte, supervisionCh chan chan string) {
-
- logger.Info("vesmgr http server serving at %s", listener.Addr())
-
- http.HandleFunc(xappNotifURL, func(w http.ResponseWriter, r *http.Request) {
-
- switch r.Method {
- case "POST":
- logger.Info("httpServer: POST in %s", xappNotifURL)
- body, err := ioutil.ReadAll(r.Body)
- defer r.Body.Close()
- if err != nil {
- logger.Error("httpServer: Invalid body in POST request")
- return
- }
- notifCh <- body
- return
- default:
- logger.Error("httpServer: Invalid method %s to %s", r.Method, r.URL.Path)
- http.Error(w, "405 method not allowed", http.StatusMethodNotAllowed)
- return
- }
- })
-
- http.HandleFunc(SupervisionURL, func(w http.ResponseWriter, r *http.Request) {
-
- switch r.Method {
- case "GET":
- logger.Info("httpServer: GET supervision")
- supervisionAckCh := make(chan string)
- // send supervision to the main loop
- supervisionCh <- supervisionAckCh
- reply := <-supervisionAckCh
- logger.Info("httpServer: supervision ack from the main loop: %s", reply)
- fmt.Fprintf(w, reply)
- return
- default:
- logger.Error("httpServer: invalid method %s to %s", r.Method, r.URL.Path)
- http.Error(w, "405 method not allowed", http.StatusMethodNotAllowed)
- return
- }
-
- })
-
- http.Serve(listener, nil)
-}
+++ /dev/null
-/*
- * Copyright (c) 2019 AT&T Intellectual Property.
- * Copyright (c) 2018-2019 Nokia.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This source code is part of the near-RT RIC (RAN Intelligent Controller)
- * platform project (RICP).
- *
- */
-package main
-
-import (
- "io/ioutil"
- "net/http"
- "os"
- "strings"
- "testing"
-
- "github.com/stretchr/testify/suite"
-)
-
-type HTTPServerTestSuite struct {
- suite.Suite
- chNotif chan []byte
- chSupervision chan chan string
- server HTTPServer
-}
-
-// suite setup creates the HTTP server
-func (suite *HTTPServerTestSuite) SetupSuite() {
- os.Unsetenv("http_proxy")
- os.Unsetenv("HTTP_PROXY")
- suite.chNotif = make(chan []byte)
- suite.chSupervision = make(chan chan string)
- suite.server = HTTPServer{}
- suite.server.init(":0")
- suite.server.start("/vesmgr_notif/", suite.chNotif, suite.chSupervision)
-}
-
-func (suite *HTTPServerTestSuite) TestHtppServerSupervisionInvalidOperation() {
- resp, reply := suite.doPost("http://"+suite.server.addr().String()+SupervisionURL, "supervision")
- suite.Equal("405 method not allowed\n", reply)
- suite.Equal(405, resp.StatusCode)
- suite.Equal("405 Method Not Allowed", resp.Status)
-}
-
-func (suite *HTTPServerTestSuite) doGet(url string) (*http.Response, string) {
- resp, err := http.Get(url)
- suite.Nil(err)
-
- defer resp.Body.Close()
- contents, err := ioutil.ReadAll(resp.Body)
- suite.Nil(err)
- return resp, string(contents)
-}
-
-func (suite *HTTPServerTestSuite) doPost(serverURL string, msg string) (*http.Response, string) {
- resp, err := http.Post(serverURL, "data", strings.NewReader(msg))
- suite.Nil(err)
-
- defer resp.Body.Close()
- contents, err := ioutil.ReadAll(resp.Body)
- suite.Nil(err)
- return resp, string(contents)
-}
-
-func replySupervision(chSupervision chan chan string, reply string) {
- chSupervisionAck := <-chSupervision
- chSupervisionAck <- reply
-}
-
-func (suite *HTTPServerTestSuite) TestHttpServerSupervision() {
-
- // start the "main loop" to reply to the supervision to the HTTPServer
- go replySupervision(suite.chSupervision, "I'm just fine")
-
- resp, reply := suite.doGet("http://" + suite.server.addr().String() + SupervisionURL)
-
- suite.Equal("I'm just fine", reply)
- suite.Equal(200, resp.StatusCode)
- suite.Equal("200 OK", resp.Status)
-}
-
-func (suite *HTTPServerTestSuite) TestHttpServerInvalidUrl() {
- resp, reply := suite.doPost("http://"+suite.server.addr().String()+"/invalid_url", "foo")
- suite.Equal("404 page not found\n", reply)
- suite.Equal(404, resp.StatusCode)
- suite.Equal("404 Not Found", resp.Status)
-}
-
-func readXAppNotification(chNotif chan []byte, ch chan []byte) {
- notification := <-chNotif
- ch <- notification
-}
-
-func (suite *HTTPServerTestSuite) TestHttpServerXappNotif() {
- // start the "main loop" to receive the xAppNotification message from the HTTPServer
- ch := make(chan []byte)
- go readXAppNotification(suite.chNotif, ch)
-
- resp, reply := suite.doPost("http://"+suite.server.addr().String()+"/vesmgr_notif/", "test data")
- suite.Equal("", reply)
- suite.Equal(200, resp.StatusCode)
- suite.Equal("200 OK", resp.Status)
- notification := <-ch
- suite.Equal([]byte("test data"), notification)
-}
-
-func (suite *HTTPServerTestSuite) TestHttpServerXappNotifInvalidOperation() {
- resp, reply := suite.doGet("http://" + suite.server.addr().String() + "/vesmgr_notif/")
- suite.Equal("405 method not allowed\n", reply)
- suite.Equal(405, resp.StatusCode)
- suite.Equal("405 Method Not Allowed", resp.Status)
-}
-
-func TestHttpServerSuite(t *testing.T) {
- suite.Run(t, new(HTTPServerTestSuite))
-}
+++ /dev/null
-/*
- * Copyright (c) 2019 AT&T Intellectual Property.
- * Copyright (c) 2018-2019 Nokia.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This source code is part of the near-RT RIC (RAN Intelligent Controller)
- * platform project (RICP).
- *
- */
-
-package main
-
-func main() {
- vesmgr := VesMgr{}
- vesmgr.Init(vesmgrXappNotifPort).Run()
-}
+++ /dev/null
-/*
- * Copyright (c) 2019 AT&T Intellectual Property.
- * Copyright (c) 2018-2019 Nokia.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This source code is part of the near-RT RIC (RAN Intelligent Controller)
- * platform project (RICP).
- *
- */
-
-package main
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io/ioutil"
- "net/http"
- "time"
-)
-
-// appmgr API
-const appmgrSubsPath = "/ric/v1/subscriptions"
-
-var errPostingFailed = errors.New("Posting subscriptions failed")
-var errWrongStatusCode = errors.New("Wrong subscriptions response StatusCode")
-
-func subscribexAppNotifications(targetURL string, subscriptions chan subscriptionNotification, timeout time.Duration, subsURL string) {
- requestBody := []byte(fmt.Sprintf(`{"Data": {"maxRetries": 5, "retryTimer": 5, "eventType":"all", "targetUrl": "%v"}}`, targetURL))
- req, err := http.NewRequest("POST", subsURL, bytes.NewBuffer(requestBody))
- if err != nil {
- logger.Error("Setting NewRequest failed: %s", err)
- subscriptions <- subscriptionNotification{false, err, ""}
- return
- }
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{}
- client.Timeout = time.Second * timeout
- var subsID string
- for {
- subsID, err = subscribexAppNotificationsClientDo(req, client)
- if err == nil {
- break
- } else if err != errPostingFailed && err != errWrongStatusCode {
- subscriptions <- subscriptionNotification{false, err, ""}
- return
- }
- time.Sleep(5 * time.Second)
- }
- subscriptions <- subscriptionNotification{true, nil, subsID}
-}
-
-func subscribexAppNotificationsClientDo(req *http.Request, client *http.Client) (string, error) {
- resp, err := client.Do(req)
- if err != nil {
- logger.Error("Posting subscriptions failed: %v", err)
- return "", errPostingFailed
- }
- defer resp.Body.Close()
- if resp.StatusCode == http.StatusCreated {
- logger.Info("Subscriptions response StatusCode: %d", resp.StatusCode)
- logger.Info("Subscriptions response headers: %s", resp.Header)
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- logger.Error("Subscriptions response Body read failed: %s", err)
- return "", err
- }
- logger.Info("Response Body: %s", body)
- var result map[string]interface{}
- if err := json.Unmarshal([]byte(body), &result); err != nil {
- logger.Error("json.Unmarshal failed: %s", err)
- return "", err
- }
- logger.Info("Subscription id from the response: %s", result["id"].(string))
- return result["id"].(string), nil
- }
- logger.Error("Wrong subscriptions response StatusCode: %d", resp.StatusCode)
- return "", errWrongStatusCode
-}
+++ /dev/null
-/*
- * Copyright (c) 2019 AT&T Intellectual Property.
- * Copyright (c) 2018-2019 Nokia.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This source code is part of the near-RT RIC (RAN Intelligent Controller)
- * platform project (RICP).
- *
- */
-
-package main
-
-import (
- "errors"
- "fmt"
- "io/ioutil"
- "net"
- "net/http"
- "os"
- "time"
-
- mdcloggo "gerrit.o-ran-sc.org/r/com/golog.git"
-)
-
-var appmgrDomain string
-
-const appmgrXAppConfigPath = "/ric/v1/config"
-const appmgrPort = "8080"
-
-// VesMgr contains runtime information of the vesmgr process
-type VesMgr struct {
- myIPAddress string
- chXAppSubscriptions chan subscriptionNotification
- chXAppNotifications chan []byte
- chSupervision chan chan string
- chVesagent chan error
- vesagent cmdRunner
- httpServer HTTPServer
-}
-
-type subscriptionNotification struct {
- subscribed bool
- err error
- subsID string
-}
-
-var logger *mdcloggo.MdcLogger
-
-// Version information, which is filled during compilation
-// Version tag of vesmgr container
-var Version string
-
-// Hash of the git commit used in building
-var Hash string
-
-const vesmgrXappNotifPort = "8080"
-const vesmgrXappNotifPath = "/vesmgr_xappnotif/"
-const timeoutPostXAppSubscriptions = 5
-const vespaConfigFile = "/etc/ves-agent/ves-agent.yaml"
-
-func init() {
- logger, _ = mdcloggo.InitLogger("vesmgr")
-}
-
-func getMyIP() (myIP string, retErr error) {
- addrs, err := net.InterfaceAddrs()
- if err != nil {
- logger.Error("net.InterfaceAddrs failed: %s", err.Error())
- return "", err
- }
- for _, addr := range addrs {
- // check the address type and if it is not a loopback take it
- if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
- if ipnet.IP.To4() != nil {
- logger.Info("My IP Address: %s", ipnet.IP.String())
- return ipnet.IP.String(), nil
- }
- }
- }
- return "", nil
-}
-
-func createConf(fname string, xappMetrics []byte) {
- f, err := os.Create(fname)
- if err != nil {
- logger.Error("Cannot create vespa conf file: %s", err.Error())
- os.Exit(1)
- }
- defer f.Close()
-
- createVespaConfig(f, xappMetrics)
- logger.Info("Vespa config created")
-}
-
-func (vesmgr *VesMgr) subscribeXAppNotifications() {
- xappNotifURL := "http://" + vesmgr.myIPAddress + ":" + vesmgrXappNotifPort + vesmgrXappNotifPath
- subsURL := "http://" + appmgrDomain + ":" + appmgrPort + appmgrSubsPath
- go subscribexAppNotifications(xappNotifURL, vesmgr.chXAppSubscriptions, timeoutPostXAppSubscriptions, subsURL)
- logger.Info("xApp notifications subscribed from %s", subsURL)
-}
-
-// Init initializes the vesmgr
-func (vesmgr *VesMgr) Init(listenPort string) *VesMgr {
- logger.Info("vesmgrInit")
- logger.Info("version: %s (%s)", Version, Hash)
-
- var err error
- if vesmgr.myIPAddress, err = getMyIP(); err != nil || vesmgr.myIPAddress == "" {
- logger.Error("Cannot get myIPAddress: IP %s", vesmgr.myIPAddress)
- panic("Cannot get my IP address")
- }
-
- var ok bool
- appmgrDomain, ok = os.LookupEnv("VESMGR_APPMGRDOMAIN")
- if ok {
- logger.Info("Using appmgrdomain %s", appmgrDomain)
- } else {
- pltnamespace := os.Getenv("PLT_NAMESPACE")
- if pltnamespace == "" {
- pltnamespace = "ricplt"
- }
- appmgrDomain = fmt.Sprintf("service-%s-appmgr-http.%s.svc.cluster.local", pltnamespace, pltnamespace)
- logger.Info("Using default appmgrdomain %s", appmgrDomain)
- }
- vesmgr.chXAppSubscriptions = make(chan subscriptionNotification)
- // Create notifications as buffered channel so that
- // xappmgr does not block if we are stuck somewhere
- vesmgr.chXAppNotifications = make(chan []byte, 10)
- vesmgr.chSupervision = make(chan chan string)
- vesmgr.chVesagent = make(chan error)
- vesmgr.httpServer = HTTPServer{}
- vesmgr.httpServer.init(vesmgr.myIPAddress + ":" + listenPort)
- vesmgr.vesagent = makeRunner("ves-agent", "-i", os.Getenv("VESMGR_HB_INTERVAL"),
- "-m", os.Getenv("VESMGR_MEAS_INTERVAL"), "--Measurement.Prometheus.Address",
- os.Getenv("VESMGR_PROMETHEUS_ADDR"), "--AlertManager.Bind", os.Getenv("VESMGR_ALERTMANAGER_BIND_ADDR"),
- "--Debug")
- return vesmgr
-}
-
-func (vesmgr *VesMgr) startVesagent() {
- vesmgr.vesagent.run(vesmgr.chVesagent)
-}
-
-func (vesmgr *VesMgr) killVespa() error {
- logger.Info("Killing vespa")
- err := vesmgr.vesagent.kill()
- if err != nil {
- logger.Error("Cannot kill vespa: %s", err.Error())
- return err
- }
- return <-vesmgr.chVesagent // wait vespa exit
-}
-
-func queryXAppsConfig(appmgrURL string, timeout time.Duration) ([]byte, error) {
- emptyConfig := []byte("{}")
- logger.Info("query xAppConfig started, url %s", appmgrURL)
- req, err := http.NewRequest("GET", appmgrURL, nil)
- if err != nil {
- logger.Error("Failed to create a HTTP request: %s", err)
- return emptyConfig, err
- }
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{}
- client.Timeout = time.Second * timeout
- resp, err := client.Do(req)
- if err != nil {
- logger.Error("Query xApp config failed: %s", err)
- return emptyConfig, err
- }
- defer resp.Body.Close()
- if resp.StatusCode == http.StatusOK {
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- logger.Error("Failed to read xApp config body: %s", err)
- return emptyConfig, err
- }
- logger.Info("query xAppConfig completed")
- return body, nil
- }
- logger.Error("Error from xApp config query: %s", resp.Status)
- return emptyConfig, errors.New(resp.Status)
-}
-
-func queryConf() (appConfig []byte, err error) {
- for i := 0; i < 10; i++ {
- appConfig, err = queryXAppsConfig("http://"+appmgrDomain+":"+appmgrPort+appmgrXAppConfigPath, 10*time.Second)
- if len(appConfig) > 0 {
- break
- }
- time.Sleep(5 * time.Second)
- }
- return appConfig, err
-}
-
-func (vesmgr *VesMgr) emptyNotificationsChannel() {
- for {
- select {
- case <-vesmgr.chXAppNotifications:
- // we don't care the content
- default:
- return
- }
- }
-}
-
-func (vesmgr *VesMgr) servRequest() {
- select {
- case supervision := <-vesmgr.chSupervision:
- logger.Info("vesmgr: supervision")
- supervision <- "OK"
- case xAppNotif := <-vesmgr.chXAppNotifications:
- logger.Info("vesmgr: xApp notification")
- logger.Info(string(xAppNotif))
- vesmgr.emptyNotificationsChannel()
- /*
- * If xapp config query fails then we cannot create
- * a new configuration and kill vespa.
- * In that case we assume that
- * the situation is fixed when the next
- * xapp notif comes
- */
- xappConfig, err := queryConf()
- if err == nil {
- vesmgr.killVespa()
- createConf(vespaConfigFile, xappConfig)
- vesmgr.startVesagent()
- }
- case err := <-vesmgr.chVesagent:
- logger.Error("Vesagent exited: " + err.Error())
- os.Exit(1)
- }
-}
-
-func (vesmgr *VesMgr) waitSubscriptionLoop() {
- for {
- select {
- case supervision := <-vesmgr.chSupervision:
- logger.Info("vesmgr: supervision")
- supervision <- "OK"
- case isSubscribed := <-vesmgr.chXAppSubscriptions:
- if isSubscribed.err != nil {
- logger.Error("Failed to make xApp subscriptions, vesmgr exiting: %s", isSubscribed.err)
- os.Exit(1)
- }
- return
- }
- }
-}
-
-// Run the vesmgr process main loop
-func (vesmgr *VesMgr) Run() {
- logger.Info("vesmgr main loop ready")
-
- vesmgr.httpServer.start(vesmgrXappNotifPath, vesmgr.chXAppNotifications, vesmgr.chSupervision)
-
- vesmgr.subscribeXAppNotifications()
-
- vesmgr.waitSubscriptionLoop()
-
- xappConfig, _ := queryConf()
-
- createConf(vespaConfigFile, xappConfig)
-
- vesmgr.startVesagent()
- for {
- vesmgr.servRequest()
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2019 AT&T Intellectual Property.
- * Copyright (c) 2018-2019 Nokia.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This source code is part of the near-RT RIC (RAN Intelligent Controller)
- * platform project (RICP).
- *
- */
-
-package main
-
-import (
- "fmt"
- "net"
- "net/http"
- "net/url"
- "os"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/suite"
-)
-
-type do func(w http.ResponseWriter)
-
-type QueryXAppsConfigTestSuite struct {
- suite.Suite
- listener net.Listener
- xAppMgrFunc do
- mu sync.Mutex
-}
-
-// suite setup creates the HTTP server
-func (suite *QueryXAppsConfigTestSuite) SetupSuite() {
- os.Unsetenv("http_proxy")
- os.Unsetenv("HTTP_PROXY")
- var err error
- suite.listener, err = net.Listen("tcp", ":0")
- suite.Nil(err)
- go runXAppMgr(suite.listener, "/test_url/", suite)
-}
-
-func runXAppMgr(listener net.Listener, url string, suite *QueryXAppsConfigTestSuite) {
-
- http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
- switch r.Method {
- case "GET":
- suite.mu.Lock()
- defer suite.mu.Unlock()
- suite.xAppMgrFunc(w)
- }
- })
-
- http.Serve(listener, nil)
-}
-
-func (suite *QueryXAppsConfigTestSuite) TestQueryXAppsConfigFailsWithTimeout() {
- doSleep := func(w http.ResponseWriter) {
- time.Sleep(time.Second * 2)
- }
-
- suite.mu.Lock()
- suite.xAppMgrFunc = doSleep
- suite.mu.Unlock()
-
- data, err := queryXAppsConfig("http://"+suite.listener.Addr().String()+"/test_url/", 1)
- suite.Equal([]byte("{}"), data)
- suite.NotNil(err)
- e, ok := err.(*url.Error)
- suite.Equal(ok, true)
- suite.Equal(e.Timeout(), true)
-}
-
-func (suite *QueryXAppsConfigTestSuite) TestQueryXAppsConfigFailsWithAnErrorReply() {
- doReplyWithErr := func(w http.ResponseWriter) {
- http.Error(w, "405 method not allowed", http.StatusMethodNotAllowed)
- }
-
- suite.mu.Lock()
- suite.xAppMgrFunc = doReplyWithErr
- suite.mu.Unlock()
-
- data, err := queryXAppsConfig("http://"+suite.listener.Addr().String()+"/test_url/", 1)
- suite.Equal([]byte("{}"), data)
- suite.NotNil(err)
- suite.Equal("405 Method Not Allowed", err.Error())
-}
-
-func (suite *QueryXAppsConfigTestSuite) TestQueryXAppsConfigOk() {
- doReply := func(w http.ResponseWriter) {
- fmt.Fprintf(w, "reply message")
- }
-
- suite.mu.Lock()
- suite.xAppMgrFunc = doReply
- suite.mu.Unlock()
-
- data, err := queryXAppsConfig("http://"+suite.listener.Addr().String()+"/test_url/", 1)
- suite.NotNil(data)
- suite.Nil(err)
- suite.Equal(data, []byte("reply message"))
-}
-
-func TestQueryXAppsConfigTestSuite(t *testing.T) {
- suite.Run(t, new(QueryXAppsConfigTestSuite))
-}
+++ /dev/null
-/*
- * Copyright (c) 2019 AT&T Intellectual Property.
- * Copyright (c) 2018-2019 Nokia.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This source code is part of the near-RT RIC (RAN Intelligent Controller)
- * platform project (RICP).
- *
- */
-
-package main
-
-import (
- "errors"
- "os"
- "os/exec"
- "path/filepath"
- "strconv"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/suite"
-)
-
-func TestGetMyIP(t *testing.T) {
- myIPAddress, err := getMyIP()
- assert.NotEqual(t, string(""), myIPAddress)
- assert.Nil(t, err)
-}
-
-func TestConfCreate(t *testing.T) {
- tmpfile := filepath.Join(os.TempDir(), "vestest."+strconv.Itoa(os.Getpid()))
- defer os.Remove(tmpfile) // clean up
- createConf(tmpfile, []byte("{}"))
- _, err := os.Stat(tmpfile)
- assert.Nil(t, err)
-}
-
-type VesmgrTestSuite struct {
- suite.Suite
- vesmgr VesMgr
-}
-
-func (suite *VesmgrTestSuite) SetupSuite() {
- suite.vesmgr = VesMgr{}
- suite.vesmgr.Init("0")
- logger.MdcAdd("Testvesmgr", "0.0.1")
- os.Setenv("VESMGR_HB_INTERVAL", "30s")
- os.Setenv("VESMGR_MEAS_INTERVAL", "30s")
- os.Setenv("VESMGR_PRICOLLECTOR_ADDR", "127.1.1.1")
- os.Setenv("VESMGR_PRICOLLECTOR_PORT", "8443")
- os.Setenv("VESMGR_PROMETHEUS_ADDR", "http://localhost:9090")
-}
-
-func (suite *VesmgrTestSuite) TestMainLoopSupervision() {
- go suite.vesmgr.servRequest()
- ch := make(chan string)
- suite.vesmgr.chSupervision <- ch
- reply := <-ch
- suite.Equal("OK", reply)
-}
-
-func (suite *VesmgrTestSuite) TestMainLoopVesagentError() {
- if os.Getenv("TEST_VESPA_EXIT") == "1" {
- // we're run in a new process, now make vesmgr main loop exit
- go suite.vesmgr.servRequest()
- suite.vesmgr.chVesagent <- errors.New("vesagent killed")
- // we should never actually end up to this sleep, since the runVesmgr should exit
- time.Sleep(3 * time.Second)
- return
- }
-
- // Run the vesmgr exit test as a separate process
- cmd := exec.Command(os.Args[0], "-test.run", "TestVesMgrSuite", "-testify.m", "TestMainLoopVesagentError")
- cmd.Env = append(os.Environ(), "TEST_VESPA_EXIT=1")
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- err := cmd.Run()
- // check that vesmgr existed with status 1
-
- e, ok := err.(*exec.ExitError)
- suite.True(ok)
- suite.Equal("exit status 1", e.Error())
-}
-
-func (suite *VesmgrTestSuite) TestWaitSubscriptionLoopRespondsSupervisionAndBreaksWhenReceivedSubsNotif() {
- go func() {
- time.Sleep(time.Second)
- ch := make(chan string)
- suite.vesmgr.chSupervision <- ch
- suite.Equal("OK", <-ch)
- suite.vesmgr.chSupervision <- ch
- suite.Equal("OK", <-ch)
- suite.vesmgr.chXAppSubscriptions <- subscriptionNotification{true, nil, ""}
- }()
-
- suite.vesmgr.waitSubscriptionLoop()
-}
-
-func (suite *VesmgrTestSuite) TestEmptyNotificationChannelReadsAllMsgsFromCh() {
- go func() {
- for i := 0; i < 11; i++ {
- suite.vesmgr.chXAppNotifications <- []byte("hello")
- }
- }()
- time.Sleep(500 * time.Millisecond)
- <-suite.vesmgr.chXAppNotifications
- suite.vesmgr.emptyNotificationsChannel()
- select {
- case <-suite.vesmgr.chXAppNotifications:
- suite.Fail("Got unexpected notification")
- default:
- // ok
- }
-}
-
-func (suite *VesmgrTestSuite) TestVespaKilling() {
- suite.vesmgr.vesagent = makeRunner("sleep", "20")
- suite.vesmgr.startVesagent()
- suite.NotNil(suite.vesmgr.killVespa())
-}
-
-func (suite *VesmgrTestSuite) TestVespaKillingAlreadyKilled() {
- suite.vesmgr.vesagent = makeRunner("sleep", "20")
- suite.vesmgr.startVesagent()
- suite.NotNil(suite.vesmgr.killVespa())
- // Just check that second kill does not block execution
- suite.NotNil(suite.vesmgr.killVespa())
-}
-
-func TestVesMgrSuite(t *testing.T) {
- suite.Run(t, new(VesmgrTestSuite))
-}
"io"
"io/ioutil"
"os"
- "strconv"
"strings"
"time"
+ app "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp"
"gopkg.in/yaml.v2"
)
const defaultVNFName = "Vespa"
const defaultNFNamingCode = "ricp"
-func readSystemUUID() string {
+func (v *VespaMgr) readSystemUUID() string {
data, err := ioutil.ReadFile("/sys/class/dmi/id/product_uuid")
if err != nil {
return defaultReportingEntityID
return strings.TrimSpace(string(data))
}
-func getVNFName() string {
+func (v *VespaMgr) getVNFName() string {
VNFName := os.Getenv("VESMGR_VNFNAME")
if VNFName == "" {
return defaultVNFName
return VNFName
}
-func getNFNamingCode() string {
+func (v *VespaMgr) getNFNamingCode() string {
NFNamingCode := os.Getenv("VESMGR_NFNAMINGCODE")
if NFNamingCode == "" {
return defaultNFNamingCode
return NFNamingCode
}
-func basicVespaConf() VESAgentConfiguration {
+func (v *VespaMgr) BasicVespaConf() VESAgentConfiguration {
var vespaconf = VESAgentConfiguration{
DataDir: "/tmp/data",
Debug: false,
Event: EventConfiguration{
- VNFName: getVNFName(),
+ VNFName: v.getVNFName(),
ReportingEntityName: "Vespa",
- ReportingEntityID: readSystemUUID(),
+ ReportingEntityID: v.readSystemUUID(),
MaxSize: 2000000,
- NfNamingCode: getNFNamingCode(),
+ NfNamingCode: v.getNFNamingCode(),
NfcNamingCodes: []NfcNamingCode{},
RetryInterval: time.Second * 5,
MaxMissed: 2,
return vespaconf
}
-// AppMetricsStruct contains xapplication metrics definition
-type AppMetricsStruct struct {
- MoId string
- MeasType string
- MeasId string
- MeasInterval string
- ObjectName string
- ObjectInstance string
- CounterId string
-}
-
-// AppMetrics contains metrics definitions for all Xapps
-type AppMetrics map[string]AppMetricsStruct
-
// Parses the metrics data from an array of bytes, which is expected to contain a JSON
// array with structs of the following format:
//
// ]
// }
// }
-func parseMetricsFromDescriptor(descriptor []byte, appMetrics AppMetrics) AppMetrics {
+func (v *VespaMgr) ParseMetricsFromDescriptor(descriptor []byte, appMetrics AppMetrics) AppMetrics {
var desc []map[string]interface{}
json.Unmarshal(descriptor, &desc)
- for _, app := range desc {
- config, configOk := app["config"]
+ for _, appl := range desc {
+ config, configOk := appl["config"]
if !configOk {
- logger.Info("No xApp config found!")
+ app.Logger.Info("No xApp config found!")
continue
}
measurements, measurementsOk := config.(map[string]interface{})["measurements"]
if !measurementsOk {
- logger.Info("No xApp metrics found!")
+ app.Logger.Info("No xApp metrics found!")
continue
}
measInterval, measIntervalOk := m.(map[string]interface{})["measInterval"].(string)
metrics, metricsOk := m.(map[string]interface{})["metrics"]
if !metricsOk || !measTypeOk || !measIdOk || !moIdOk || !measIntervalOk {
- logger.Info("No metrics found for moId=%s measType=%s measId=%s measInterval=%s", moId, measId, measType, measInterval)
+ app.Logger.Info("No metrics found for moId=%s measType=%s measId=%s measInterval=%s", moId, measId, measType, measInterval)
continue
}
- logger.Info("Parsed measurement: moId=%s type=%s id=%s interval=%s", moId, measType, measId, measInterval)
+ app.Logger.Info("Parsed measurement: moId=%s type=%s id=%s interval=%s", moId, measType, measId, measInterval)
- parseMetricsRules(metrics.([]interface{}), appMetrics, moId, measType, measId, measInterval)
+ v.ParseMetricsRules(metrics.([]interface{}), appMetrics, moId, measType, measId, measInterval)
}
}
return appMetrics
// of the following format:
// { "name": xxx, "objectName": yyy, "objectInstance": zzz }
// Entries, which do not have all the necessary fields, are ignored.
-func parseMetricsRules(metricsMap []interface{}, appMetrics AppMetrics, moId, measType, measId, measInterval string) AppMetrics {
+func (v *VespaMgr) ParseMetricsRules(metricsMap []interface{}, appMetrics AppMetrics, moId, measType, measId, measInterval string) AppMetrics {
for _, element := range metricsMap {
name, nameOk := element.(map[string]interface{})["name"].(string)
if nameOk {
counterId, counterIdOk := element.(map[string]interface{})["counterId"].(string)
if !alreadyFound && objectNameOk && objectInstanceOk && counterIdOk {
appMetrics[name] = AppMetricsStruct{moId, measType, measId, measInterval, objectName, objectInstance, counterId}
- logger.Info("Parsed counter name=%s %s/%s M%sC%s", name, objectName, objectInstance, measId, counterId)
+ app.Logger.Info("Parsed counter name=%s %s/%s M%sC%s", name, objectName, objectInstance, measId, counterId)
}
if alreadyFound {
- logger.Info("skipped duplicate counter %s", name)
+ app.Logger.Info("skipped duplicate counter %s", name)
}
}
}
return appMetrics
}
-func getRules(vespaconf *VESAgentConfiguration, xAppConfig []byte) bool {
+func (v *VespaMgr) GetRules(vespaconf *VESAgentConfiguration, xAppConfig []byte) bool {
makeRule := func(expr string, value AppMetricsStruct) MetricRule {
return MetricRule{
Target: "AdditionalObjects",
}
}
appMetrics := make(AppMetrics)
- metrics := parseMetricsFromDescriptor(xAppConfig, appMetrics)
+ metrics := v.ParseMetricsFromDescriptor(xAppConfig, appMetrics)
- if pltFile := os.Getenv("VESMGR_PLT_CFG_FILE"); pltFile != "" {
+ if pltFile := os.Getenv("VESMGR_PLT_CFG_FILE"); pltFile != "" {
pltConfig, err := ioutil.ReadFile(pltFile)
if err != nil {
- logger.Error("Unable to read platform config file: %v", err)
+ app.Logger.Error("Unable to read platform config file: %v", err)
} else {
- metrics = parseMetricsFromDescriptor(pltConfig, metrics)
+ metrics = v.ParseMetricsFromDescriptor(pltConfig, metrics)
}
}
vespaconf.Measurement.Prometheus.Rules.Metrics = append(vespaconf.Measurement.Prometheus.Rules.Metrics, makeRule(key, value))
}
if len(vespaconf.Measurement.Prometheus.Rules.Metrics) == 0 {
- logger.Info("vespa config with empty metrics")
+ app.Logger.Info("vespa config with empty metrics")
}
return len(vespaconf.Measurement.Prometheus.Rules.Metrics) > 0
}
-func getCollectorConfiguration(vespaconf *VESAgentConfiguration) {
- vespaconf.PrimaryCollector.User = os.Getenv("VESMGR_PRICOLLECTOR_USER")
- vespaconf.PrimaryCollector.Password = os.Getenv("VESMGR_PRICOLLECTOR_PASSWORD")
- vespaconf.PrimaryCollector.PassPhrase = os.Getenv("VESMGR_PRICOLLECTOR_PASSPHRASE")
- vespaconf.PrimaryCollector.FQDN = os.Getenv("VESMGR_PRICOLLECTOR_ADDR")
- vespaconf.PrimaryCollector.ServerRoot = os.Getenv("VESMGR_PRICOLLECTOR_SERVERROOT")
- vespaconf.PrimaryCollector.Topic = os.Getenv("VESMGR_PRICOLLECTOR_TOPIC")
- portStr := os.Getenv("VESMGR_PRICOLLECTOR_PORT")
-
- if portStr == "" {
- vespaconf.PrimaryCollector.Port = 8443
- } else {
- port, _ := strconv.Atoi(portStr)
- vespaconf.PrimaryCollector.Port = port
- }
-
- secureStr := os.Getenv("VESMGR_PRICOLLECTOR_SECURE")
- if secureStr == "true" {
- vespaconf.PrimaryCollector.Secure = true
- } else {
- vespaconf.PrimaryCollector.Secure = false
- }
+func (v *VespaMgr) GetCollectorConfiguration(vespaconf *VESAgentConfiguration) {
+ vespaconf.PrimaryCollector.User = app.Config.GetString("controls.collector.primaryUser")
+ vespaconf.PrimaryCollector.Password = app.Config.GetString("controls.collector.primaryPassword")
+ vespaconf.PrimaryCollector.PassPhrase = ""
+ vespaconf.PrimaryCollector.FQDN = app.Config.GetString("controls.collector.primaryAddr")
+ vespaconf.PrimaryCollector.ServerRoot = app.Config.GetString("controls.collector.serverRoot")
+ vespaconf.PrimaryCollector.Topic = ""
+ vespaconf.PrimaryCollector.Port = app.Config.GetInt("controls.collector.primaryPort")
+ vespaconf.PrimaryCollector.Secure = app.Config.GetBool("controls.collector.secure")
}
-func createVespaConfig(writer io.Writer, xAppStatus []byte) {
- vespaconf := basicVespaConf()
+func (v *VespaMgr) CreateConfig(writer io.Writer, xAppStatus []byte) {
+ vespaconf := v.BasicVespaConf()
- getRules(&vespaconf, xAppStatus)
+ v.GetRules(&vespaconf, xAppStatus)
- getCollectorConfiguration(&vespaconf)
+ v.GetCollectorConfiguration(&vespaconf)
err := yaml.NewEncoder(writer).Encode(vespaconf)
if err != nil {
- logger.Error("Cannot write vespa conf file: %s", err.Error())
+ app.Logger.Error("Cannot write vespa conf file: %s", err.Error())
return
}
+ app.Logger.Info("Config file written to: %s", app.Config.GetString("controls.vesagent.configFile"))
}
"bytes"
"encoding/json"
"io/ioutil"
- "os"
"testing"
"time"
"gopkg.in/yaml.v2"
)
+var vespaMgr *VespaMgr
+
func testBaseConf(t *testing.T, vesconf VESAgentConfiguration) {
+ vespaMgr = NewVespaMgr()
+
assert.Equal(t, "/tmp/data", vesconf.DataDir)
assert.False(t, vesconf.Debug)
assert.Equal(t, vesconf.Event.MaxMissed, 2)
}
func TestBasicConfigContainsCorrectValues(t *testing.T) {
- vesconf := basicVespaConf()
+ vesconf := vespaMgr.BasicVespaConf()
testBaseConf(t, vesconf)
}
-func TestBasicConfigContainsCorrectVNFName(t *testing.T) {
- os.Setenv("VESMGR_VNFNAME", "VNF-111")
- os.Setenv("VESMGR_NFNAMINGCODE", "code55")
- vesconf := basicVespaConf()
- assert.Equal(t, vesconf.Event.VNFName, "VNF-111")
- assert.Equal(t, vesconf.Event.NfNamingCode, "code55")
- os.Unsetenv("VESMGR_VNFNAME")
- os.Unsetenv("VESMGR_NFNAMINGCODE")
-}
-
-func TestCollectorConfiguration(t *testing.T) {
- os.Unsetenv("VESMGR_VNFNAME")
- os.Unsetenv("VESMGR_NFNAMINGCODE")
- os.Setenv("VESMGR_PRICOLLECTOR_USER", "user123")
- os.Setenv("VESMGR_PRICOLLECTOR_PASSWORD", "pass123")
- os.Setenv("VESMGR_PRICOLLECTOR_PASSPHRASE", "phrase123")
- os.Setenv("VESMGR_PRICOLLECTOR_ADDR", "1.2.3.4")
- os.Setenv("VESMGR_PRICOLLECTOR_PORT", "1234")
- os.Setenv("VESMGR_PRICOLLECTOR_SERVERROOT", "vescollector")
- os.Setenv("VESMGR_PRICOLLECTOR_TOPIC", "sometopic")
- os.Setenv("VESMGR_PRICOLLECTOR_SECURE", "true")
-
- vesconf := basicVespaConf()
- getCollectorConfiguration(&vesconf)
-
- assert.Equal(t, "user123", vesconf.PrimaryCollector.User)
- assert.Equal(t, "pass123", vesconf.PrimaryCollector.Password)
- assert.Equal(t, "phrase123", vesconf.PrimaryCollector.PassPhrase)
- assert.Equal(t, "1.2.3.4", vesconf.PrimaryCollector.FQDN)
- assert.Equal(t, 1234, vesconf.PrimaryCollector.Port)
- assert.Equal(t, "vescollector", vesconf.PrimaryCollector.ServerRoot)
- assert.Equal(t, "sometopic", vesconf.PrimaryCollector.Topic)
- assert.True(t, vesconf.PrimaryCollector.Secure)
-}
-
-func TestCollectorConfigurationWhenEnvironmentVariablesAreNotDefined(t *testing.T) {
- os.Unsetenv("VESMGR_VNFNAME")
- os.Unsetenv("VESMGR_NFNAMINGCODE")
- os.Unsetenv("VESMGR_PRICOLLECTOR_USER")
- os.Unsetenv("VESMGR_PRICOLLECTOR_PASSWORD")
- os.Unsetenv("VESMGR_PRICOLLECTOR_PASSPHRASE")
- os.Unsetenv("VESMGR_PRICOLLECTOR_ADDR")
- os.Unsetenv("VESMGR_PRICOLLECTOR_PORT")
- os.Unsetenv("VESMGR_PRICOLLECTOR_SERVERROOT")
- os.Unsetenv("VESMGR_PRICOLLECTOR_TOPIC")
- os.Unsetenv("VESMGR_PRICOLLECTOR_SECURE")
-
- vesconf := basicVespaConf()
- getCollectorConfiguration(&vesconf)
-
- assert.Equal(t, "", vesconf.PrimaryCollector.User)
- assert.Equal(t, "", vesconf.PrimaryCollector.Password)
- assert.Equal(t, "", vesconf.PrimaryCollector.PassPhrase)
- assert.Equal(t, "", vesconf.PrimaryCollector.FQDN)
- assert.Equal(t, 8443, vesconf.PrimaryCollector.Port)
- assert.Equal(t, "", vesconf.PrimaryCollector.ServerRoot)
- assert.Equal(t, "", vesconf.PrimaryCollector.Topic)
- assert.False(t, vesconf.PrimaryCollector.Secure)
-}
-
-func TestCollectorConfigurationWhenPrimaryCollectorPortIsNotInteger(t *testing.T) {
- os.Setenv("VESMGR_PRICOLLECTOR_PORT", "abcd")
- vesconf := basicVespaConf()
- getCollectorConfiguration(&vesconf)
- assert.Equal(t, 0, vesconf.PrimaryCollector.Port)
-}
-
-func TestCollectorConfigurationWhenPrimaryCollectorSecureIsNotTrueOrFalse(t *testing.T) {
- os.Setenv("VESMGR_PRICOLLECTOR_SECURE", "foo")
- vesconf := basicVespaConf()
- getCollectorConfiguration(&vesconf)
- assert.False(t, vesconf.PrimaryCollector.Secure)
-}
-
func TestYamlGenerationWithoutXAppsConfig(t *testing.T) {
buffer := new(bytes.Buffer)
- createVespaConfig(buffer, []byte{})
+ vespaMgr.CreateConfig(buffer, []byte{})
var vesconf VESAgentConfiguration
err := yaml.Unmarshal(buffer.Bytes(), &vesconf)
assert.Nil(t, err)
buffer := new(bytes.Buffer)
bytes, err := ioutil.ReadFile("../../test/xApp_config_test_output.json")
assert.Nil(t, err)
- createVespaConfig(buffer, bytes)
+ vespaMgr.CreateConfig(buffer, bytes)
var vesconf VESAgentConfiguration
err = yaml.Unmarshal(buffer.Bytes(), &vesconf)
assert.Nil(t, err)
{ "name": "ricxapp_SDL_StoreError", "objectName": "ricxappSDLStoreErrorCounter", "objectInstance": "ricxappSDLStoreError", "counterId": "0011" } ]}`
appMetrics := make(AppMetrics)
m := metricsStringToInterfaceArray(metricsJSON)
- appMetrics = parseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
+ appMetrics = vespaMgr.ParseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
assert.Len(t, appMetrics, 6)
assert.Equal(t, "ricxappRMRreceivedCounter", appMetrics["ricxapp_RMR_Received"].ObjectName)
assert.Equal(t, "ricxappRMRTransmitErrorCounter", appMetrics["ricxapp_RMR_TransmitError"].ObjectName)
appMetrics := make(AppMetrics)
metricsJSON := `{"metrics": []`
m := metricsStringToInterfaceArray(metricsJSON)
- appMetrics = parseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
+ appMetrics = vespaMgr.ParseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
assert.Empty(t, appMetrics)
}
metricsJSON := `{"metrics": [
{ "additionalField": "valueIgnored", "name": "ricxapp_RMR_Received", "objectName": "ricxappRMRreceivedCounter", "objectInstance": "ricxappRMRReceived", "counterId": "0011" }]}`
m := metricsStringToInterfaceArray(metricsJSON)
- appMetrics = parseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
+ appMetrics = vespaMgr.ParseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
assert.Len(t, appMetrics, 1)
assert.Equal(t, "ricxappRMRreceivedCounter", appMetrics["ricxapp_RMR_Received"].ObjectName)
assert.Equal(t, "ricxappRMRReceived", appMetrics["ricxapp_RMR_Received"].ObjectInstance)
{ "name": "ricxapp_RMR_ReceiveError", "objectInstance": "ricxappRMRReceiveError" },
{ "name": "ricxapp_RMR_Transmitted", "objectName": "ricxappRMRTransmittedCounter", "objectInstance": "ricxappRMRTransmitted", "counterId": "0011" }]}`
m := metricsStringToInterfaceArray(metricsJSON)
- appMetrics = parseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
+ appMetrics = vespaMgr.ParseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
assert.Len(t, appMetrics, 2)
assert.Equal(t, "ricxappRMRreceivedCounter", appMetrics["ricxapp_RMR_Received"].ObjectName)
assert.Equal(t, "ricxappRMRTransmittedCounter", appMetrics["ricxapp_RMR_Transmitted"].ObjectName)
{ "name": "ricxapp_RMR_Received", "objectName": "ricxappRMRreceivedCounterXXX", "objectInstance": "ricxappRMRReceivedXXX", "counterId": "0011" },
{ "name": "ricxapp_RMR_Transmitted", "objectName": "ricxappRMRTransmittedCounter", "objectInstance": "ricxappRMRTransmitted", "counterId": "0011" }]}`
m := metricsStringToInterfaceArray(metricsJSON)
- appMetrics = parseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
+ appMetrics = vespaMgr.ParseMetricsRules(m, appMetrics, "SEP/XAPP", "X2", "1234", "60")
assert.Len(t, appMetrics, 2)
assert.Equal(t, "ricxappRMRreceivedCounter", appMetrics["ricxapp_RMR_Received"].ObjectName)
assert.Equal(t, "ricxappRMRReceived", appMetrics["ricxapp_RMR_Received"].ObjectInstance)
{ "name": "ricxapp_RMR_Transmitted", "objectName": "ricxappRMRTransmittedCounter", "objectInstance": "ricxappRMRTransmitted", "counterId": "0011" }]}`
m1 := metricsStringToInterfaceArray(metricsJSON1)
m2 := metricsStringToInterfaceArray(metricsJSON2)
- appMetrics = parseMetricsRules(m1, appMetrics, "SEP/XAPP", "X2", "1234", "60")
- appMetrics = parseMetricsRules(m2, appMetrics, "SEP/XAPP", "X2", "1234", "60")
+ appMetrics = vespaMgr.ParseMetricsRules(m1, appMetrics, "SEP/XAPP", "X2", "1234", "60")
+ appMetrics = vespaMgr.ParseMetricsRules(m2, appMetrics, "SEP/XAPP", "X2", "1234", "60")
assert.Len(t, appMetrics, 2)
assert.Equal(t, "ricxappRMRreceivedCounter", appMetrics["ricxapp_RMR_Received"].ObjectName)
assert.Equal(t, "ricxappRMRReceived", appMetrics["ricxapp_RMR_Received"].ObjectInstance)
bytes, err := ioutil.ReadFile("../../test/xApp_config_test_output.json")
assert.Nil(t, err)
- appMetrics = parseMetricsFromDescriptor(bytes, appMetrics)
+ appMetrics = vespaMgr.ParseMetricsFromDescriptor(bytes, appMetrics)
assert.Len(t, appMetrics, 4)
assert.Equal(t, "App1ExampleCounterOneObject", appMetrics["App1ExampleCounterOne"].ObjectName)
assert.Equal(t, "App1ExampleCounterOneObjectInstance", appMetrics["App1ExampleCounterOne"].ObjectInstance)
{{"metadata": "something", "descriptor": "somethingelse"}}]`
metricsBytes := []byte(metricsJSON)
appMetrics := make(AppMetrics)
- appMetrics = parseMetricsFromDescriptor(metricsBytes, appMetrics)
+ appMetrics = vespaMgr.ParseMetricsFromDescriptor(metricsBytes, appMetrics)
assert.Empty(t, appMetrics)
}
{{"metadata": "something", "descriptor": "somethingelse", "config":{}}}]`
metricsBytes := []byte(metricsJSON)
appMetrics := make(AppMetrics)
- appMetrics = parseMetricsFromDescriptor(metricsBytes, appMetrics)
+ appMetrics = vespaMgr.ParseMetricsFromDescriptor(metricsBytes, appMetrics)
assert.Empty(t, appMetrics)
}
"os/exec"
)
-type cmdRunner struct {
+type CommandRunner struct {
exe string
args []string
cmd *exec.Cmd
}
-func (r *cmdRunner) run(result chan error) {
+func (r *CommandRunner) Run(result chan error) {
r.cmd = exec.Command(r.exe, r.args...)
r.cmd.Stdout = os.Stdout
r.cmd.Stderr = os.Stderr
}()
}
-func (r *cmdRunner) kill() error {
- return r.cmd.Process.Kill()
+func (r *CommandRunner) Kill() error {
+ if r.cmd != nil {
+ return r.cmd.Process.Kill()
+ }
+ return nil
}
-func makeRunner(exe string, arg ...string) cmdRunner {
- r := cmdRunner{exe: exe, args: arg}
+func NewCommandRunner(exe string, arg ...string) *CommandRunner {
+ r := &CommandRunner{exe: exe, args: arg}
return r
}
)
func TestProcessRunning(t *testing.T) {
- r := makeRunner("echo", "a")
+ r := NewCommandRunner("echo", "a")
ch := make(chan error)
- r.run(ch)
+ r.Run(ch)
err := <-ch
assert.Nil(t, err)
}
func TestProcessKill(t *testing.T) {
- r := makeRunner("sleep", "20")
+ r := NewCommandRunner("sleep", "20")
ch := make(chan error)
- r.run(ch)
- assert.Nil(t, r.kill())
+ r.Run(ch)
+ assert.Nil(t, r.Kill())
<-ch // wait and seee that kills is actually done
}
func TestProcessRunningFails(t *testing.T) {
- r := makeRunner("foobarbaz")
+ r := NewCommandRunner("foobarbaz")
ch := make(chan error)
- r.run(ch)
+ r.Run(ch)
err := <-ch
assert.NotNil(t, err)
}
"time"
)
+type VespaMgr struct {
+ rmrReady bool
+ vesAgent *CommandRunner
+ chVesagent chan error
+ appmgrHost string
+ appmgrUrl string
+ appmgrNotifUrl string
+ appmgrSubsUrl string
+ appmgrRetry int
+ hbInterval string
+ measInterval string
+ prometheusAddr string
+ alertManagerBindAddr string
+ subscriptionId string
+}
+
// Structs are copied from https://github.com/nokia/ONAP-VESPA/tree/master/ves-agent/config
// and from https://github.com/nokia/ONAP-VESPA/blob/master/govel/config.go
// Using tag v0.3.0
CaCert string `yaml:"caCert,omitempty"` // Root certificate content
DataDir string `yaml:"datadir"` // Path to directory containing data
}
+
+// AppMetricsStruct contains xapplication metrics definition
+type AppMetricsStruct struct {
+ MoId string
+ MeasType string
+ MeasId string
+ MeasInterval string
+ ObjectName string
+ ObjectInstance string
+ CounterId string
+}
+
+// AppMetrics contains metrics definitions for all Xapps
+type AppMetrics map[string]AppMetricsStruct
+
+var Version string
+var Hash string
--- /dev/null
+/*
+ * Copyright (c) 2020 AT&T Intellectual Property.
+ * Copyright (c) 2020 Nokiv.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This source code is part of the near-RT RIC (RAN Intelligent Controller)
+ * platform project (RICP).
+ */
+
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ app "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp"
+)
+
+func NewVespaMgr() *VespaMgr {
+ return &VespaMgr{
+ rmrReady: false,
+ chVesagent: make(chan error),
+ appmgrHost: app.Config.GetString("controls.appManager.host"),
+ appmgrUrl: app.Config.GetString("controls.appManager.path"),
+ appmgrNotifUrl: app.Config.GetString("controls.appManager.notificationUrl"),
+ appmgrSubsUrl: app.Config.GetString("controls.appManager.subscriptionUrl"),
+ appmgrRetry: app.Config.GetInt("controls.appManager.appmgrRetry"),
+ hbInterval: app.Config.GetString("controls.vesagent.hbInterval"),
+ measInterval: app.Config.GetString("controls.vesagent.measInterval"),
+ prometheusAddr: app.Config.GetString("controls.vesagent.prometheusAddr"),
+ alertManagerBindAddr: app.Config.GetString("controls.vesagent.alertManagerBindAddr"),
+ }
+}
+
+func (v *VespaMgr) Run(sdlcheck, runXapp bool) {
+ app.Logger.SetMdc("vespamgr", fmt.Sprintf("%s:%s", Version, Hash))
+ app.SetReadyCB(func(d interface{}) { v.rmrReady = true }, true)
+ app.Resource.InjectStatusCb(v.StatusCB)
+ app.AddConfigChangeListener(v.ConfigChangeCB)
+
+ measUrl := app.Config.GetString("controls.measurementUrl")
+ app.Resource.InjectRoute(v.appmgrNotifUrl, v.HandlexAppNotification, "POST")
+ app.Resource.InjectRoute(measUrl, v.HandleMeasurements, "POST")
+ app.Resource.InjectRoute("/supervision", v.HandleSupervision, "GET") // @todo: remove this
+
+ go v.SubscribeXappNotif(fmt.Sprintf("%s%s", v.appmgrHost, v.appmgrSubsUrl))
+
+ if runXapp {
+ app.RunWithParams(v, sdlcheck)
+ }
+}
+
+func (v *VespaMgr) Consume(rp *app.RMRParams) (err error) {
+ app.Logger.Info("Message received!")
+
+ app.Rmr.Free(rp.Mbuf)
+ return nil
+}
+
+func (v *VespaMgr) StatusCB() bool {
+ if !v.rmrReady {
+ app.Logger.Info("RMR not ready yet!")
+ }
+
+ return v.rmrReady
+}
+
+func (v *VespaMgr) ConfigChangeCB(configparam string) {
+ return
+}
+
+func (v *VespaMgr) CreateConf(fname string, xappMetrics []byte) {
+ f, err := os.Create(fname)
+ if err != nil {
+ app.Logger.Error("os.Create failed: %s", err.Error())
+ return
+ }
+ defer f.Close()
+
+ v.CreateConfig(f, xappMetrics)
+}
+
+func (v *VespaMgr) QueryXappConf(appmgrUrl string) (appConfig []byte, err error) {
+ client := http.Client{Timeout: 10 * time.Second}
+
+ for i := 0; i < v.appmgrRetry; i++ {
+ app.Logger.Info("Getting xApp config from: %s [%d]", appmgrUrl, v.appmgrRetry)
+
+ resp, err := client.Get(appmgrUrl)
+ if err != nil || resp == nil {
+ app.Logger.Error("client.Get failed: %v", err)
+ time.Sleep(5 * time.Second)
+ continue
+ }
+
+ defer resp.Body.Close()
+ appConfig, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ app.Logger.Error("ioutil.ReadAll failed: %v", err)
+ time.Sleep(5 * time.Second)
+ continue
+ }
+
+ app.Logger.Info("Received xApp config: %d", len(appConfig))
+ if len(appConfig) > 0 {
+ return appConfig, err
+ }
+ }
+
+ return appConfig, err
+}
+
+func (v *VespaMgr) ReadPayload(w http.ResponseWriter, r *http.Request) ([]byte, error) {
+ payload, err := ioutil.ReadAll(r.Body)
+ defer r.Body.Close()
+ if err != nil {
+ app.Logger.Error("ioutil.ReadAll failed: %v", err)
+ return payload, err
+ }
+ v.respondWithJSON(w, http.StatusOK, err)
+
+ return payload, err
+}
+
+func (v *VespaMgr) HandleSupervision(w http.ResponseWriter, r *http.Request) {
+ v.respondWithJSON(w, http.StatusOK, nil)
+}
+
+func (v *VespaMgr) HandleMeasurements(w http.ResponseWriter, r *http.Request) {
+ app.Logger.Info("HandleMeasurements called!")
+ if appConfig, err := v.ReadPayload(w, r); err == nil {
+ v.CreateConf(app.Config.GetString("controls.vesagent.configFile"), appConfig)
+ }
+}
+
+func (v *VespaMgr) HandlexAppNotification(w http.ResponseWriter, r *http.Request) {
+ if _, err := v.ReadPayload(w, r); err != nil {
+ return
+ }
+
+ app.Logger.Info("xApp event notification received!")
+ if appConfig, err := v.QueryXappConf(fmt.Sprintf("%s%s", v.appmgrHost, v.appmgrUrl)); err == nil {
+ v.CreateConf(app.Config.GetString("controls.vesagent.configFile"), appConfig)
+ v.RestartVesagent()
+ }
+}
+
+func (v *VespaMgr) DoSubscribe(appmgrUrl string, subscriptionData []byte) string {
+ resp, err := http.Post(appmgrUrl, "application/json", bytes.NewBuffer(subscriptionData))
+ if err != nil || resp == nil || resp.StatusCode != http.StatusCreated {
+ app.Logger.Error("http.Post failed: %s", err)
+ return ""
+ }
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ app.Logger.Error("ioutil.ReadAll for body failed: %s", err)
+ return ""
+ }
+
+ var result map[string]interface{}
+ if err := json.Unmarshal([]byte(body), &result); err != nil {
+ app.Logger.Error("json.Unmarshal failed: %s", err)
+ return ""
+ }
+ v.subscriptionId = result["id"].(string)
+ app.Logger.Info("Subscription id from the response: %s", v.subscriptionId)
+
+ return v.subscriptionId
+}
+
+func (v *VespaMgr) SubscribeXappNotif(appmgrUrl string) {
+ targetUrl := fmt.Sprintf("%s%s", app.Config.GetString("controls.host"), v.appmgrNotifUrl)
+ subscriptionData := []byte(fmt.Sprintf(`{"Data": {"maxRetries": 5, "retryTimer": 5, "eventType":"all", "targetUrl": "%v"}}`, targetUrl))
+
+ for {
+ app.Logger.Info("Subscribing xApp notification from: %v", appmgrUrl)
+
+ if id := v.DoSubscribe(appmgrUrl, subscriptionData); id != "" {
+ app.Logger.Info("Subscription done, id=%s", id)
+ break
+ }
+
+ app.Logger.Info("Subscription failed, retyring after short delay ...")
+ time.Sleep(5 * time.Second)
+ }
+
+ if xappConfig, err := v.QueryXappConf(fmt.Sprintf("%s%s", v.appmgrHost, v.appmgrUrl)); err == nil {
+ v.CreateConf(app.Config.GetString("controls.vesagent.configFile"), xappConfig)
+ v.RestartVesagent()
+ }
+}
+
+func (v *VespaMgr) respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ if payload != nil {
+ response, _ := json.Marshal(payload)
+ w.Write(response)
+ }
+}
+
+func (v *VespaMgr) StartVesagent() {
+ v.vesAgent = NewCommandRunner("ves-agent", "-i", v.hbInterval, "-m", v.measInterval, "--Debug",
+ "--Measurement.Prometheus.Address", v.prometheusAddr, "--AlertManager.Bind", v.alertManagerBindAddr)
+
+ v.vesAgent.Run(v.chVesagent)
+}
+
+func (v *VespaMgr) RestartVesagent() {
+ if strings.Contains(app.Config.GetString("controls.host"), "localhost") {
+ return
+ }
+
+ if v.vesAgent != nil {
+ err := v.vesAgent.Kill()
+ if err != nil {
+ app.Logger.Error("Couldn't kill vespa-agent: %s", err.Error())
+ return
+ }
+ <-v.chVesagent
+ }
+
+ v.StartVesagent()
+}
+
+func main() {
+ NewVespaMgr().Run(false, true)
+}
"encoding/json"
"fmt"
"io/ioutil"
+ "net"
"net/http"
"net/http/httptest"
+ "os"
"testing"
+ "time"
+ app "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp"
"github.com/stretchr/testify/suite"
)
-type AppmgrHTTPServerTestSuite struct {
+type VespaMgrTestSuite struct {
suite.Suite
- subscriptions chan subscriptionNotification
- xappNotifURL string
+ vespaMgr *VespaMgr
}
// suite setup
-func (suite *AppmgrHTTPServerTestSuite) SetupSuite() {
- // the url here is not actually used anywhere
- suite.xappNotifURL = "http://127.0.0.1:8080" + vesmgrXappNotifPath
- suite.subscriptions = make(chan subscriptionNotification)
+func (suite *VespaMgrTestSuite) SetupSuite() {
+ os.Unsetenv("http_proxy")
+ os.Unsetenv("HTTP_PROXY")
+ suite.vespaMgr = NewVespaMgr()
}
-// test setup
-func (suite *AppmgrHTTPServerTestSuite) SetupTest() {
- suite.subscriptions = make(chan subscriptionNotification)
-}
-
-func (suite *AppmgrHTTPServerTestSuite) TestSubscribexAppNotifications() {
+func (suite *VespaMgrTestSuite) TestSubscribexAppNotifications() {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+
body, _ := ioutil.ReadAll(req.Body)
var result map[string]interface{}
err := json.Unmarshal([]byte(body), &result)
}))
defer testServer.Close()
- go subscribexAppNotifications(suite.xappNotifURL, suite.subscriptions, 1, testServer.URL)
- isSubscribed := <-suite.subscriptions
- suite.Nil(isSubscribed.err)
- suite.Equal("deadbeef1234567890", isSubscribed.subsID)
+ suite.vespaMgr.SubscribeXappNotif(testServer.URL)
+ suite.Equal("deadbeef1234567890", suite.vespaMgr.subscriptionId)
}
-func (suite *AppmgrHTTPServerTestSuite) TestSubscribexAppNotificationsWrongStatus() {
+func (suite *VespaMgrTestSuite) TestSubscribexAppNotificationsWrongStatus() {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Add("Content-Type", "application/json")
res.WriteHeader(http.StatusUnauthorized)
}))
defer testServer.Close()
- requestBody := []byte(fmt.Sprintf(`{"maxRetries": 5, "retryTimer": 5, "eventType":"all", "targetUrl": "%v"}`, suite.xappNotifURL))
+ requestBody := []byte(fmt.Sprintf(`{"maxRetries": 5, "retryTimer": 5, "eventType":"all", "targetUrl": "%v"}`, "localhost:8080"))
req, _ := http.NewRequest("POST", testServer.URL, bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
- client := &http.Client{}
-
- subsID, err := subscribexAppNotificationsClientDo(req, client)
- suite.Equal(errWrongStatusCode, err)
- // after failed POST vesmgr.appmgrSubsId holds an initial values
- suite.Equal("", subsID)
-}
-
-func (suite *AppmgrHTTPServerTestSuite) TestSubscribexAppNotificationsWrongUrl() {
- // use fake appmgrUrl that is not served in unit test
- appmgrURL := "/I_do_not_exist/"
- requestBody := []byte(fmt.Sprintf(`{"maxRetries": 5, "retryTimer": 5, "eventType":"all", "targetUrl": "%v"}`, suite.xappNotifURL))
- req, _ := http.NewRequest("POST", appmgrURL, bytes.NewBuffer(requestBody))
- req.Header.Set("Content-Type", "application/json")
- client := &http.Client{}
- subsID, err := subscribexAppNotificationsClientDo(req, client)
- suite.Equal(errPostingFailed, err)
- // after failed POST vesmgr.appmgrSubsId holds an initial values
- suite.Equal("", subsID)
+ suite.vespaMgr.subscriptionId = ""
+ suite.vespaMgr.DoSubscribe(testServer.URL, requestBody)
+ suite.Equal("", suite.vespaMgr.subscriptionId)
}
-func (suite *AppmgrHTTPServerTestSuite) TestSubscribexAppNotificationsReadBodyFails() {
+func (suite *VespaMgrTestSuite) TestSubscribexAppNotificationsReadBodyFails() {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Length", "1")
res.Header().Add("Content-Type", "application/json")
}))
defer testServer.Close()
- go subscribexAppNotifications(suite.xappNotifURL, suite.subscriptions, 1, testServer.URL)
- isSubscribed := <-suite.subscriptions
- suite.Equal("unexpected EOF", isSubscribed.err.Error())
- suite.Equal("", isSubscribed.subsID)
+ suite.vespaMgr.subscriptionId = ""
+ suite.vespaMgr.DoSubscribe(testServer.URL, []byte{})
+ suite.Equal("", suite.vespaMgr.subscriptionId)
}
-func (suite *AppmgrHTTPServerTestSuite) TestSubscribexAppNotificationsUnMarshalFails() {
+func (suite *VespaMgrTestSuite) TestSubscribexAppNotificationsUnMarshalFails() {
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
res.Header().Add("Content-Type", "application/json")
res.WriteHeader(http.StatusCreated)
}))
defer testServer.Close()
- go subscribexAppNotifications(suite.xappNotifURL, suite.subscriptions, 1, testServer.URL)
- isSubscribed := <-suite.subscriptions
- suite.Equal("invalid character 'd' after object key", isSubscribed.err.Error())
- suite.Equal("", isSubscribed.subsID)
+ suite.vespaMgr.subscriptionId = ""
+ suite.vespaMgr.DoSubscribe(testServer.URL, []byte{})
+ suite.Equal("", suite.vespaMgr.subscriptionId)
+}
+
+func (suite *VespaMgrTestSuite) TestQueryXAppsConfigOk() {
+ listener, err := net.Listen("tcp", ":0")
+ suite.Nil(err)
+
+ http.HandleFunc("/test_url/", func(w http.ResponseWriter, r *http.Request) {
+ switch r.Method {
+ case "GET":
+ fmt.Fprintf(w, "reply message")
+ }
+ })
+
+ go http.Serve(listener, nil)
+
+ xappConfig, err := suite.vespaMgr.QueryXappConf("http://" + listener.Addr().String() + "/test_url/")
+ suite.NotNil(xappConfig)
+ suite.Nil(err)
+ suite.Equal(xappConfig, []byte("reply message"))
+}
+
+func (suite *VespaMgrTestSuite) TestHandlexAppNotification() {
+ data, err := ioutil.ReadFile("../../test/xApp_config_test_output.json")
+ suite.Nil(err)
+
+ pbodyEn, _ := json.Marshal(data)
+ req, _ := http.NewRequest("POST", "/ric/v1/xappnotif", bytes.NewBuffer(pbodyEn))
+ handleFunc := http.HandlerFunc(suite.vespaMgr.HandlexAppNotification)
+ response := executeRequest(req, handleFunc)
+ suite.Equal(http.StatusOK, response.Code)
+}
+
+func (suite *VespaMgrTestSuite) TestSubscribexAppNotificationsOnStartup() {
+ suite.vespaMgr.Run(false, false)
+ time.Sleep(2 * time.Second)
+
+ suite.vespaMgr.Consume(&app.RMRParams{})
+ suite.vespaMgr.StatusCB()
+ suite.vespaMgr.rmrReady = true
+ suite.vespaMgr.StatusCB()
+}
+
+func executeRequest(req *http.Request, handleR http.HandlerFunc) *httptest.ResponseRecorder {
+ rr := httptest.NewRecorder()
+ handleR.ServeHTTP(rr, req)
+ return rr
}
-func TestAppmgrHttpServerTestSuite(t *testing.T) {
- suite.Run(t, new(AppmgrHTTPServerTestSuite))
+func TestVespaMgrTestSuite(t *testing.T) {
+ suite.Run(t, new(VespaMgrTestSuite))
}
--- /dev/null
+{
+ "name": "vespamgr",
+ "version": "0.7.0",
+ "vendor": "Nokia",
+ "moId": "SEP",
+ "containers": [],
+ "livenessProbe": {
+ "httpGet": {
+ "path": "ric/v1/health/alive",
+ "port": 8080
+ },
+ "initialDelaySeconds": 5,
+ "periodSeconds": 15
+ },
+ "readinessProbe": {
+ "httpGet": {
+ "path": "ric/v1/health/ready",
+ "port": 8080
+ },
+ "initialDelaySeconds": 5,
+ "periodSeconds": 15
+ },
+ "messaging": {
+ "ports": [
+ {
+ "name": "http",
+ "container": "qos",
+ "port": 8088,
+ "description": "http service"
+ },
+ {
+ "name": "rmr-data",
+ "container": "qos",
+ "port": 4560,
+ "maxSize": 2072,
+ "threadType": 0,
+ "lowLatency": false,
+ "rxMessages": [],
+ "txMessages": [],
+ "policies": [],
+ "description": "rmr data port for qos"
+ },
+ {
+ "name": "rmr-route",
+ "container": "qos",
+ "port": 4561,
+ "description": "rmr route port for qos"
+ }
+ ]
+ },
+ "controls": {
+ "logger": {
+ "level": 4
+ },
+ "host": "localhost:8080",
+ "measurementUrl": "/ric/v1/measurements",
+ "appManager": {
+ "host": "http://localhost:8080",
+ "path": "/ric/v1/config",
+ "notificationUrl": "/ric/v1/xappnotif",
+ "subscriptionUrl": "/ric/v1/subscriptions",
+ "appmgrRetry": 2
+ },
+ "vesagent": {
+ "configFile": "/tmp/ves-agent.yaml",
+ "hbInterval": "60s",
+ "measInterval": "30s",
+ "prometheusAddr": "http://infra-cpro-server:80",
+ "alertManagerBindAddr": ":9095"
+ },
+ "collector": {
+ "primaryAddr": "localhost",
+ "secondaryAddr": "localhost",
+ "serverRoot": "0",
+ "primaryPort": 8443,
+ "primaryUser": "sample1",
+ "primaryPassword": "sample1",
+ "secure": false
+ }
+ },
+ "faults": { },
+ "metrics": []
+}
--- /dev/null
+{
+ "name": "vespamgr",
+ "version": "0.7.0",
+ "vendor": "Nokia",
+ "moId": "SEP",
+ "containers": [],
+ "livenessProbe": {
+ "httpGet": {
+ "path": "ric/v1/health/alive",
+ "port": 8080
+ },
+ "initialDelaySeconds": 5,
+ "periodSeconds": 15
+ },
+ "readinessProbe": {
+ "httpGet": {
+ "path": "ric/v1/health/ready",
+ "port": 8080
+ },
+ "initialDelaySeconds": 5,
+ "periodSeconds": 15
+ },
+ "messaging": {
+ "ports": [
+ {
+ "name": "http",
+ "container": "qos",
+ "port": 8080,
+ "description": "http service"
+ },
+ {
+ "name": "rmr-data",
+ "container": "qos",
+ "port": 4560,
+ "maxSize": 2072,
+ "threadType": 0,
+ "lowLatency": false,
+ "rxMessages": [],
+ "txMessages": [],
+ "policies": [],
+ "description": "rmr data port for qos"
+ },
+ {
+ "name": "rmr-route",
+ "container": "qos",
+ "port": 4561,
+ "description": "rmr route port for qos"
+ }
+ ]
+ },
+ "controls": {
+ "logger": {
+ "level": 4
+ },
+ "host": "http://service-ricplt-vespamgr-http.ricplt.svc.cluster.local:8080",
+ "measurementUrl": "/ric/v1/measurements",
+ "appManager": {
+ "host": "http://service-ricplt-appmgr-http.ricplt.svc.cluster.local:8080",
+ "path": "/ric/v1/config",
+ "notificationUrl": "/ric/v1/xappnotif",
+ "subscriptionUrl": "/ric/v1/subscriptions",
+ "appmgrRetry": 2
+ },
+ "vesagent": {
+ "configFile": "/etc/ves-agent/ves-agent.yaml",
+ "hbInterval": "60s",
+ "measInterval": "30s",
+ "prometheusAddr": "http://infra-cpro-server:80",
+ "alertManagerBindAddr": ":9095"
+ },
+ "collector": {
+ "primaryAddr": "pod-ves-simulator",
+ "secondaryAddr": "pod-ves-simulator",
+ "serverRoot": "0",
+ "primaryPort": 8443,
+ "primaryUser": "sample1",
+ "primaryPassword": "sample1",
+ "secure": false
+ }
+ },
+ "faults": { },
+ "metrics": []
+}
\ No newline at end of file
--- /dev/null
+newrt|start
+rte|13111|127.0.0.1:4588
+rte|13111|127.0.0.1:4560
+newrt|end
# The Jenkins job uses this string for the tag in the image name
# for example nexus3.o-ran-sc.org:10004/my-image-name:0.0.1
---
-tag: 0.5.4
+tag: 0.7.0
-module RIC_VESAgent
+module gerrit.o-ran-sc.org/r/ric-plt/vespamgr
-go 1.12
+go 1.13
+
+replace gerrit.o-ran-sc.org/r/ric-plt/xapp-frame => gerrit.o-ran-sc.org/r/ric-plt/xapp-frame.git v0.6.7
+
+replace gerrit.o-ran-sc.org/r/ric-plt/sdlgo => gerrit.o-ran-sc.org/r/ric-plt/sdlgo.git v0.5.2
+
+replace gerrit.o-ran-sc.org/r/com/golog => gerrit.o-ran-sc.org/r/com/golog.git v0.0.2
require (
gerrit.o-ran-sc.org/r/com/golog.git v0.0.1
- github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/stretchr/testify v1.3.0
- gopkg.in/yaml.v2 v2.2.2
+ gerrit.o-ran-sc.org/r/ric-plt/xapp-frame v0.0.0-00010101000000-000000000000
+ github.com/stretchr/testify v1.5.1
+ gopkg.in/yaml.v2 v2.2.4
)
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
gerrit.o-ran-sc.org/r/com/golog.git v0.0.1 h1:9RfO/Whehaaq5KiJTT7s+YOzmi9mT1C3HktfhwwMEmw=
gerrit.o-ran-sc.org/r/com/golog.git v0.0.1/go.mod h1:b8YB31U8/4iRpABioeSzGi/YMzOQ/Zq7hrJmmXKqlJk=
+gerrit.o-ran-sc.org/r/com/golog.git v0.0.2 h1:Ix6SgFuzd6yW6Ur6+qDlGhDO65UYs8PiIkeAL1VaQ2o=
+gerrit.o-ran-sc.org/r/com/golog.git v0.0.2/go.mod h1:A7hUL52YQSO4dFIZNcj76XQ09C9PftAe3LyL7kqBnok=
+gerrit.o-ran-sc.org/r/ric-plt/alarm-go.git/alarm v0.4.0 h1:Uvyfuq2jyb1aosy2BEzH1g3pe2gy4sMgpsXlvjVeBzI=
+gerrit.o-ran-sc.org/r/ric-plt/alarm-go.git/alarm v0.4.0/go.mod h1:AdEWKtERGvOQy9ybLhyhrb9w9LLVn8i9xzTwoR5n4BY=
+gerrit.o-ran-sc.org/r/ric-plt/alarm-go.git/alarm v0.4.2 h1:XNfkp3PwZ7pfkPszX7NaX6DzToCGjcWTLbIHYqCFNu0=
+gerrit.o-ran-sc.org/r/ric-plt/alarm-go.git/alarm v0.4.2/go.mod h1:AdEWKtERGvOQy9ybLhyhrb9w9LLVn8i9xzTwoR5n4BY=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/common v1.0.21 h1:eK9nUZOTMJ/EnMpH9bkWtMgOvCn3u4+PNCb9gu10s6w=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/common v1.0.21/go.mod h1:QJ1uPPZosGbhxUWpUpeM5fLqFHdnWTrVnvW2DgyOCes=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/common v1.0.35 h1:TGXHb4DNY8on+ej4S9VUnk2HibIC/5chDy64OE+bQBQ=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/common v1.0.35/go.mod h1:QJ1uPPZosGbhxUWpUpeM5fLqFHdnWTrVnvW2DgyOCes=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/entities v1.0.21 h1:PQ/Mu2ol+8Oh/0BqCWWhPlVVoRCg5dQDEGm4+Opp5w4=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/entities v1.0.21/go.mod h1:GXiXLz4ORBeIr0FLIbzENRykgh3Po5uPkX2jICxnRF0=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/entities v1.0.35 h1:tkM3yE8UzmuH4nf9TqAmiNBSuIZ2CtcMRH2eBIYIzpQ=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/entities v1.0.35/go.mod h1:G+4sUBMbLfQ+RrGS65U15tKmbnP+/1b5oLTPmMfyfT4=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/reader v1.0.21 h1:N3UbqJ9WqC8JEz/TwHHwZwCFAW6VTlZLpD5lnbdD+Y8=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/reader v1.0.21/go.mod h1:SQBZLy1HP94i1vQ3y730wGFsrHqZtgPaEkzPgtqBNw0=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/reader v1.0.35 h1:LcxnUUDwsCzYEISKmkjkyYfg/lnLt8ofkPiGK69vNIA=
+gerrit.o-ran-sc.org/r/ric-plt/nodeb-rnib.git/reader v1.0.35/go.mod h1:2bSaXTpECbZieB8bMnubTqMwF3n+mMBxlTaAXvcduNg=
+gerrit.o-ran-sc.org/r/ric-plt/sdlgo.git v0.5.2 h1:UK7awyRKIkVdokWvvkYvazlg3EWIfMnIqCcJxTnLlDA=
+gerrit.o-ran-sc.org/r/ric-plt/sdlgo.git v0.5.2/go.mod h1:y2WhrCvdLkAKdH+ySdHSOSehACJkTMyZghCGVcqoZzc=
+gerrit.o-ran-sc.org/r/ric-plt/xapp-frame.git v0.4.0 h1:FGQY+g9r+xxqNrU2V0VvwcpibAEgRnIAN43+4ULC8qE=
+gerrit.o-ran-sc.org/r/ric-plt/xapp-frame.git v0.4.0/go.mod h1:lW3UYpVXwPiOR39t6JgNCE4kdmMSdPVelMPC/Pp9fQM=
+gerrit.o-ran-sc.org/r/ric-plt/xapp-frame.git v0.4.4 h1:aUsIGYCHYsNX0pQxLL+sLTOzBLCaQ1p2/tMY3UEYcMs=
+gerrit.o-ran-sc.org/r/ric-plt/xapp-frame.git v0.4.4/go.mod h1:29CdUcCrNEqVBjQWAlgeGrYxSWqSZ4e67xzhuSf6BkI=
+gerrit.o-ran-sc.org/r/ric-plt/xapp-frame.git v0.6.7 h1:lTURRbfaV0kPcplGF7jyowRXoUkNknRzd7Y51ZPs5PI=
+gerrit.o-ran-sc.org/r/ric-plt/xapp-frame.git v0.6.7/go.mod h1:fpHeoGISAkz6bNfgZtq8Ycg9i9KdGgUBP2jLs6TGk2g=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
+github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
+github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI=
+github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
+github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
+github.com/go-openapi/errors v0.19.3 h1:7MGZI1ibQDLasvAz8HuhvYk9eNJbJkCOXWsSjjMS+Zc=
+github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
+github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
+github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
+github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
+github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
+github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
+github.com/go-openapi/loads v0.19.4 h1:5I4CCSqoWzT+82bBkNIvmLc0UOsoKKQ4Fz+3VxOB7SY=
+github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
+github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
+github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
+github.com/go-openapi/runtime v0.19.4 h1:csnOgcgAiuGoM/Po7PEpKDoNulCcF3FGbSnbHfxgjMI=
+github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
+github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
+github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
+github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
+github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
+github.com/go-openapi/strfmt v0.19.4 h1:eRvaqAhpL0IL6Trh5fDsGnGhiXndzHFuA05w6sXH6/g=
+github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
+github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.7 h1:VRuXN2EnMSsZdauzdss6JBC29YotDqG59BZ+tdlIL1s=
+github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
+github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
+github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
+github.com/go-openapi/validate v0.19.6 h1:WsKw9J1WzYBVxWRYwLqEk3325RL6G0SSWksuamkk6q0=
+github.com/go-openapi/validate v0.19.6/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
+github.com/go-redis/redis v6.15.3+incompatible h1:NZ0O90AhLSvSrvLZ/S9h7D4kl1mW2PrKyxL7MyBKO2g=
+github.com/go-redis/redis v6.15.3+incompatible/go.mod h1:W2YCLaZryXHirdd9QqwkiVUxCQsrx8SbLq9Uqk7JS7A=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU=
+github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
+github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
+github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA=
+go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
+k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw=
+k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=