From 2bfaef02e6141e5870ee0fca20b428223d00d8e9 Mon Sep 17 00:00:00 2001 From: Mohamed Abukar Date: Thu, 3 Dec 2020 18:20:05 +0200 Subject: [PATCH] Redesign and taking xapp-framework into use Change-Id: I600f667047ea2a1f946107546c32a3fb7e56b9f3 Signed-off-by: Mohamed Abukar --- Dockerfile | 45 ++-- build_vesmgr.sh | 35 ++- cmd/vesmgr/httpserver.go | 101 ------- cmd/vesmgr/httpserver_test.go | 129 --------- cmd/vesmgr/main.go | 27 -- cmd/vesmgr/subscribexAPPNotifications.go | 91 ------- cmd/vesmgr/vesmgr.go | 279 ------------------- cmd/vesmgr/vesmgr_queryxappconfig_test.go | 119 -------- cmd/vesmgr/vesmgr_test.go | 145 ---------- cmd/{vesmgr => vespamgr}/config.go | 106 +++----- cmd/{vesmgr => vespamgr}/config_test.go | 105 ++------ cmd/{vesmgr => vespamgr}/subprocess.go | 15 +- cmd/{vesmgr => vespamgr}/subprocess_test.go | 14 +- cmd/{vesmgr/vespaconf.go => vespamgr/types.go} | 33 +++ cmd/vespamgr/vespamgr.go | 246 +++++++++++++++++ .../vespamgr_test.go} | 125 +++++---- config/config-file-ut.json | 83 ++++++ config/config-file.json | 83 ++++++ config/uta_rtg.rt | 4 + container-tag.yaml | 2 +- go.mod | 16 +- go.sum | 299 +++++++++++++++++++++ 22 files changed, 955 insertions(+), 1147 deletions(-) mode change 100644 => 100755 Dockerfile delete mode 100644 cmd/vesmgr/httpserver.go delete mode 100644 cmd/vesmgr/httpserver_test.go delete mode 100644 cmd/vesmgr/main.go delete mode 100644 cmd/vesmgr/subscribexAPPNotifications.go delete mode 100755 cmd/vesmgr/vesmgr.go delete mode 100755 cmd/vesmgr/vesmgr_queryxappconfig_test.go delete mode 100644 cmd/vesmgr/vesmgr_test.go rename cmd/{vesmgr => vespamgr}/config.go (64%) rename cmd/{vesmgr => vespamgr}/config_test.go (70%) rename cmd/{vesmgr => vespamgr}/subprocess.go (79%) mode change 100644 => 100755 rename cmd/{vesmgr => vespamgr}/subprocess_test.go (87%) mode change 100644 => 100755 rename cmd/{vesmgr/vespaconf.go => vespamgr/types.go} (86%) mode change 100644 => 100755 create mode 100755 cmd/vespamgr/vespamgr.go rename cmd/{vesmgr/subscribexAPPNotifications_test.go => vespamgr/vespamgr_test.go} (51%) mode change 100644 => 100755 create mode 100755 config/config-file-ut.json create mode 100755 config/config-file.json create mode 100644 config/uta_rtg.rt diff --git a/Dockerfile b/Dockerfile old mode 100644 new mode 100755 index ec5fd57..c741132 --- a/Dockerfile +++ b/Dockerfile @@ -17,8 +17,11 @@ # 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 @@ -34,32 +37,30 @@ RUN export GOPATH=$HOME/go && \ 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"] diff --git a/build_vesmgr.sh b/build_vesmgr.sh index c1d2bf3..6166a6a 100755 --- a/build_vesmgr.sh +++ b/build_vesmgr.sh @@ -19,16 +19,31 @@ # 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 ] @@ -40,7 +55,5 @@ fi 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 diff --git a/cmd/vesmgr/httpserver.go b/cmd/vesmgr/httpserver.go deleted file mode 100644 index 1ac0999..0000000 --- a/cmd/vesmgr/httpserver.go +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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) -} diff --git a/cmd/vesmgr/httpserver_test.go b/cmd/vesmgr/httpserver_test.go deleted file mode 100644 index 434c4c4..0000000 --- a/cmd/vesmgr/httpserver_test.go +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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)) -} diff --git a/cmd/vesmgr/main.go b/cmd/vesmgr/main.go deleted file mode 100644 index f905da2..0000000 --- a/cmd/vesmgr/main.go +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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() -} diff --git a/cmd/vesmgr/subscribexAPPNotifications.go b/cmd/vesmgr/subscribexAPPNotifications.go deleted file mode 100644 index c87a0a3..0000000 --- a/cmd/vesmgr/subscribexAPPNotifications.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 -} diff --git a/cmd/vesmgr/vesmgr.go b/cmd/vesmgr/vesmgr.go deleted file mode 100755 index 5086098..0000000 --- a/cmd/vesmgr/vesmgr.go +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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() - } -} diff --git a/cmd/vesmgr/vesmgr_queryxappconfig_test.go b/cmd/vesmgr/vesmgr_queryxappconfig_test.go deleted file mode 100755 index 053adcc..0000000 --- a/cmd/vesmgr/vesmgr_queryxappconfig_test.go +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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)) -} diff --git a/cmd/vesmgr/vesmgr_test.go b/cmd/vesmgr/vesmgr_test.go deleted file mode 100644 index 21c69c0..0000000 --- a/cmd/vesmgr/vesmgr_test.go +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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)) -} diff --git a/cmd/vesmgr/config.go b/cmd/vespamgr/config.go similarity index 64% rename from cmd/vesmgr/config.go rename to cmd/vespamgr/config.go index 8ad122d..b7779f2 100755 --- a/cmd/vesmgr/config.go +++ b/cmd/vespamgr/config.go @@ -27,10 +27,10 @@ import ( "io" "io/ioutil" "os" - "strconv" "strings" "time" + app "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp" "gopkg.in/yaml.v2" ) @@ -38,7 +38,7 @@ const defaultReportingEntityID = "00000000-0000-0000-0000-000000000000" 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 @@ -46,7 +46,7 @@ func readSystemUUID() string { return strings.TrimSpace(string(data)) } -func getVNFName() string { +func (v *VespaMgr) getVNFName() string { VNFName := os.Getenv("VESMGR_VNFNAME") if VNFName == "" { return defaultVNFName @@ -54,7 +54,7 @@ func getVNFName() string { return VNFName } -func getNFNamingCode() string { +func (v *VespaMgr) getNFNamingCode() string { NFNamingCode := os.Getenv("VESMGR_NFNAMINGCODE") if NFNamingCode == "" { return defaultNFNamingCode @@ -62,16 +62,16 @@ func getNFNamingCode() string { 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, @@ -95,20 +95,6 @@ func basicVespaConf() VESAgentConfiguration { 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: // @@ -125,19 +111,19 @@ type AppMetrics map[string]AppMetricsStruct // ] // } // } -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 } @@ -148,12 +134,12 @@ func parseMetricsFromDescriptor(descriptor []byte, appMetrics AppMetrics) AppMet 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 @@ -163,7 +149,7 @@ func parseMetricsFromDescriptor(descriptor []byte, appMetrics AppMetrics) AppMet // 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 { @@ -173,17 +159,17 @@ func parseMetricsRules(metricsMap []interface{}, appMetrics AppMetrics, moId, me 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", @@ -200,14 +186,14 @@ func getRules(vespaconf *VESAgentConfiguration, xAppConfig []byte) bool { } } 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) } } @@ -216,46 +202,34 @@ func getRules(vespaconf *VESAgentConfiguration, xAppConfig []byte) bool { 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")) } diff --git a/cmd/vesmgr/config_test.go b/cmd/vespamgr/config_test.go similarity index 70% rename from cmd/vesmgr/config_test.go rename to cmd/vespamgr/config_test.go index 2217c92..eacbcbd 100755 --- a/cmd/vesmgr/config_test.go +++ b/cmd/vespamgr/config_test.go @@ -23,7 +23,6 @@ import ( "bytes" "encoding/json" "io/ioutil" - "os" "testing" "time" @@ -31,7 +30,11 @@ import ( "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) @@ -48,87 +51,13 @@ func testBaseConf(t *testing.T, vesconf VESAgentConfiguration) { } 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) @@ -140,7 +69,7 @@ func TestYamlGenerationWithXAppsConfig(t *testing.T) { 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) @@ -165,7 +94,7 @@ func TestParseMetricsRules(t *testing.T) { { "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) @@ -176,7 +105,7 @@ func TestParseMetricsRulesNoMetrics(t *testing.T) { 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) } @@ -185,7 +114,7 @@ func TestParseMetricsRulesAdditionalFields(t *testing.T) { 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) @@ -198,7 +127,7 @@ func TestParseMetricsRulesMissingFields(t *testing.T) { { "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) @@ -213,7 +142,7 @@ func TestParseMetricsRulesDuplicateDefinitionIsIgnored(t *testing.T) { { "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) @@ -227,8 +156,8 @@ func TestParseMetricsRulesIncrementalFillOfAppMetrics(t *testing.T) { { "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) @@ -239,7 +168,7 @@ func TestParseXAppDescriptor(t *testing.T) { 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) @@ -256,7 +185,7 @@ func TestParseXAppDescriptorWithNoConfig(t *testing.T) { {{"metadata": "something", "descriptor": "somethingelse"}}]` metricsBytes := []byte(metricsJSON) appMetrics := make(AppMetrics) - appMetrics = parseMetricsFromDescriptor(metricsBytes, appMetrics) + appMetrics = vespaMgr.ParseMetricsFromDescriptor(metricsBytes, appMetrics) assert.Empty(t, appMetrics) } @@ -265,6 +194,6 @@ func TestParseXAppDescriptorWithNoMetrics(t *testing.T) { {{"metadata": "something", "descriptor": "somethingelse", "config":{}}}]` metricsBytes := []byte(metricsJSON) appMetrics := make(AppMetrics) - appMetrics = parseMetricsFromDescriptor(metricsBytes, appMetrics) + appMetrics = vespaMgr.ParseMetricsFromDescriptor(metricsBytes, appMetrics) assert.Empty(t, appMetrics) } diff --git a/cmd/vesmgr/subprocess.go b/cmd/vespamgr/subprocess.go old mode 100644 new mode 100755 similarity index 79% rename from cmd/vesmgr/subprocess.go rename to cmd/vespamgr/subprocess.go index 0c84312..0ea78cf --- a/cmd/vesmgr/subprocess.go +++ b/cmd/vespamgr/subprocess.go @@ -25,13 +25,13 @@ import ( "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 @@ -45,11 +45,14 @@ func (r *cmdRunner) run(result chan error) { }() } -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 } diff --git a/cmd/vesmgr/subprocess_test.go b/cmd/vespamgr/subprocess_test.go old mode 100644 new mode 100755 similarity index 87% rename from cmd/vesmgr/subprocess_test.go rename to cmd/vespamgr/subprocess_test.go index d20a235..019e3c3 --- a/cmd/vesmgr/subprocess_test.go +++ b/cmd/vespamgr/subprocess_test.go @@ -27,25 +27,25 @@ import ( ) 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) } diff --git a/cmd/vesmgr/vespaconf.go b/cmd/vespamgr/types.go old mode 100644 new mode 100755 similarity index 86% rename from cmd/vesmgr/vespaconf.go rename to cmd/vespamgr/types.go index bbff8d2..c364196 --- a/cmd/vesmgr/vespaconf.go +++ b/cmd/vespamgr/types.go @@ -25,6 +25,22 @@ import ( "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 @@ -115,3 +131,20 @@ type VESAgentConfiguration struct { 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 diff --git a/cmd/vespamgr/vespamgr.go b/cmd/vespamgr/vespamgr.go new file mode 100755 index 0000000..1a5246f --- /dev/null +++ b/cmd/vespamgr/vespamgr.go @@ -0,0 +1,246 @@ +/* + * 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) +} diff --git a/cmd/vesmgr/subscribexAPPNotifications_test.go b/cmd/vespamgr/vespamgr_test.go old mode 100644 new mode 100755 similarity index 51% rename from cmd/vesmgr/subscribexAPPNotifications_test.go rename to cmd/vespamgr/vespamgr_test.go index 1521da3..2b84ef5 --- a/cmd/vesmgr/subscribexAPPNotifications_test.go +++ b/cmd/vespamgr/vespamgr_test.go @@ -26,33 +26,32 @@ import ( "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) @@ -68,13 +67,11 @@ func (suite *AppmgrHTTPServerTestSuite) TestSubscribexAppNotifications() { })) 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) @@ -82,32 +79,16 @@ func (suite *AppmgrHTTPServerTestSuite) TestSubscribexAppNotificationsWrongStatu })) 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") @@ -115,13 +96,12 @@ func (suite *AppmgrHTTPServerTestSuite) TestSubscribexAppNotificationsReadBodyFa })) 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) @@ -129,12 +109,57 @@ func (suite *AppmgrHTTPServerTestSuite) TestSubscribexAppNotificationsUnMarshalF })) 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)) } diff --git a/config/config-file-ut.json b/config/config-file-ut.json new file mode 100755 index 0000000..497242f --- /dev/null +++ b/config/config-file-ut.json @@ -0,0 +1,83 @@ +{ + "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": [] +} diff --git a/config/config-file.json b/config/config-file.json new file mode 100755 index 0000000..eead269 --- /dev/null +++ b/config/config-file.json @@ -0,0 +1,83 @@ +{ + "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 diff --git a/config/uta_rtg.rt b/config/uta_rtg.rt new file mode 100644 index 0000000..900f071 --- /dev/null +++ b/config/uta_rtg.rt @@ -0,0 +1,4 @@ +newrt|start +rte|13111|127.0.0.1:4588 +rte|13111|127.0.0.1:4560 +newrt|end diff --git a/container-tag.yaml b/container-tag.yaml index 795ef43..31badd2 100644 --- a/container-tag.yaml +++ b/container-tag.yaml @@ -1,4 +1,4 @@ # 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 diff --git a/go.mod b/go.mod index 10cf8df..c4111ca 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,16 @@ -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 ) diff --git a/go.sum b/go.sum index 94304a2..6cf4488 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,313 @@ +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= -- 2.16.6