<commit_before>// package server implements the server logic of
// cxy-sdn software including a apiserver and consul
// datastore backend and ovs connection manager

package server

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/mux"
	"net"
	"net/http"
	"net/url"
)

type HttpErr struct {
	code    int
	message string
}

const version = "6.0"

type HttpApiFunc func(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr

// myHandler implement http.Handler
type myHandler struct {
	*Daemon
	fct HttpApiFunc
}

func (handler myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	err := handler.fct(handler.Daemon, w, r)
	if err != nil {
		http.Error(w, err.message, err.code)
	}
}

type BridgeConf struct {
	BridgeIP   string `json:"bridgeIP"`
	BridgeName string `json:"bridgeName"`
	BridgeCIDR string `json:"bridgeCIDR"`
	BridgeMTU  int    `json:"bridgeMTU"`
}

type Connection struct {
	ContainerID      string        `json:"containerID"`
	ContainerName    string        `json:"containerName"`
	ContainerPID     string        `json:"containerPID"`
	RequestIp        string        `json:"requestIP,omitempty"`
	Network          string        `json:"network"`
	OvsPortID        string        `json:"ovsPortID"`
	BandWidth        string        `json:"bandWidth,omitempty"`
	Delay            string        `json:"delay,omitempty"`
	RXTotal          uint64        `json:"rxKbytes"` // in KB
	TXTotal          uint64        `json:"txKbytes"` // in KB
	RXRate           float64       `json:"rxRate"`   // in Kb/s
	TXRate           float64       `json:"txRate"`   // in Kb/s
	ConnectionDetail OvsConnection `json:"ovs_connectionDetails"`
}

func ServeApi(d *Daemon) {
	server := &http.Server{
		Addr:    "127.0.0.1:8888",
		Handler: createRouter(d),
	}
	server.ListenAndServe()
}

func createRouter(d *Daemon) *mux.Router {
	r := mux.NewRouter()
	m := map[string]map[string]HttpApiFunc{
		"GET": {
			"/version":            getVersion,
			"/configuration":      getConf,
			"/networks":           getNets,
			"/network/{name:.*}":  getNet,
			"/connections":        getConns,
			"/connection/{id:.*}": getConn,
		},
		"POST": {
			"/configuration": setConf,
			"/network":       createNet,
			"/cluster/join":  joinCluster,
			"/cluster/leave": leaveCluster,
			"/connection":    createConn,
			"/qos/{id:.*}":   createQos,
		},
		"PUT": {
			"/qos/{id:.*}": updateQos,
		},
		"DELETE": {
			"/network/{name:.*}":  delNet,
			"/connection/{id:.*}": delConn,
		},
	}

	for method, routes := range m {
		for uri, Func := range routes {
			handler := myHandler{d, Func}
			r.Path(uri).Methods(method).Handler(handler)
		}
	}
	return r
}

// return the cxy-sdn version
func getVersion(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	w.Write([]byte(version))

	return nil
}

// get the ovs bridge conf
func getConf(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	conf, _ := json.Marshal(d.bridgeConf)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(conf)

	return nil
}

// set the bridge conf
func setConf(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	if r.Body == nil {
		return &HttpErr{http.StatusBadRequest, "SetConf request has no body"}
	}

	cfg := &BridgeConf{}

	err := json.NewDecoder(r.Body).Decode(cfg)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, "setConf json decode failed"}
	}

	d.bridgeConf = cfg
	return nil
}

// get all the existing network
func getNets(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	networks, err := GetNetworks()
	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	data, err := json.Marshal(networks)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)
	return nil
}

// get one specified network detail
func getNet(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	// get the network name
	vars := mux.Vars(r)
	name := vars["name"]

	network, err := GetNetwork(name)

	if err != nil {
		return &HttpErr{http.StatusNotFound, err.Error()}
	}

	data, err := json.Marshal(network)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	w.Header().Set("Content-type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// create a network
func createNet(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	if r.Body == nil {
		return &HttpErr{http.StatusBadRequest, "request body is empty"}
	}

	network := &Network{}
	err := json.NewDecoder(r.Body).Decode(network)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	_, cidr, err := net.ParseCIDR(network.Subnet)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	newNet, err := CreateNetwork(network.Name, cidr)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	data, _ := json.Marshal(newNet)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// delete one specified network
func delNet(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	vars := mux.Vars(r)
	name := vars["name"]

	err := DeleteNetwork(name)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	return nil
}

// node join the cluster
func joinCluster(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	if r.URL.RawQuery == "" {
		return &HttpErr{http.StatusBadRequest, "address missing"}
	}

	kvs, err := url.ParseQuery(r.URL.RawQuery)

	if err != nil {
		return &HttpErr{http.StatusBadRequest, "parse query string error"}
	}

	addr, ok := kvs["address"]

	if !ok || addr[0] == "" {
		return &HttpErr{http.StatusBadRequest, "address parameter not exist"}
	}

	fmt.Println("Join to cluster", addr[0])

	ip := net.ParseIP(addr[0])
	if ip == nil {
		return &HttpErr{http.StatusBadRequest, "Invalid IP address"}
	}

	d.clusterChan <- &NodeCtx{ip.String(), nodeJoin}
	return nil
}

// node leave the cluster
func leaveCluster(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	fmt.Println("Node leave cluster")
	d.clusterChan <- &NodeCtx{"", nodeLeave}

	return nil
}

// get all connections
func getConns(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	d.connections.RLock()
	data, err := json.Marshal(d.connections.rm)
	d.connections.RUnlock()

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// get one specified connection
func getConn(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	vars := mux.Vars(r)

	containerId := vars["id"]
	d.connections.RLock()
	con := d.connections.Get(containerId)
	d.connections.RUnlock()

	if con == nil {
		return &HttpErr{http.StatusNotFound, containerId}
	}

	data, err := json.Marshal(con.(*Connection))

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// create a container connection
func createConn(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	if r.Body == nil {
		return &HttpErr{http.StatusBadRequest, "request body is empty"}
	}

	con := &Connection{}
	err := json.NewDecoder(r.Body).Decode(con)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	if con.Network == "" {
		con.Network = defaultNetwork
	}

	ctx := &ConnectionCtx{
		addConn,
		con,
		make(chan *Connection),
	}

	d.connectionChan <- ctx

	res := <-ctx.Result

	if res.OvsPortID == "-1" {
		return &HttpErr{http.StatusBadRequest, "resp body not valid"}
	}

	data, _ := json.Marshal(res)
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// delete the ovs and container connection
func delConn(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	vars := mux.Vars(r)
	containerId := vars["id"]

	con := d.connections.Get(containerId)

	if con == nil {
		return &HttpErr{http.StatusNotFound, "container not found"}
	}

	ctx := &ConnectionCtx{
		deleteConn,
		con.(*Connection),
		make(chan *Connection),
	}

	d.connectionChan <- ctx
	<-ctx.Result

	return nil
}

// create a qos for a container
func createQos(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	bw := r.FormValue("bw")
	delay := r.FormValue("delay")

	vars := mux.Vars(r)
	containerId := vars["id"]

	if bw == "" && delay == "" {
		return &HttpErr{http.StatusBadRequest, "bw and delay is empty"}
	}

	ok := d.connections.Check(containerId)

	if !ok {
		return &HttpErr{http.StatusNotFound, "container not found"}
	}

	if err := addQos(d, containerId, bw, delay); err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	return nil
}

// update a qos for a container
func updateQos(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	bw := r.FormValue("bw")
	delay := r.FormValue("delay")

	vars := mux.Vars(r)
	containerId := vars["id"]

	if bw == "" && delay == "" {
		return &HttpErr{http.StatusBadRequest, "bw and delay is empty"}
	}

	ok := d.connections.Check(containerId)

	if !ok {
		return &HttpErr{http.StatusNotFound, "container not found"}
	}

	if err := changeQos(d, containerId, bw, delay); err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	return nil
}
<commit_msg>remove unnecessary lock<commit_after>// package server implements the server logic of
// cxy-sdn software including a apiserver and consul
// datastore backend and ovs connection manager

package server

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/mux"
	"net"
	"net/http"
	"net/url"
)

type HttpErr struct {
	code    int
	message string
}

const version = "6.0"

type HttpApiFunc func(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr

// myHandler implement http.Handler
type myHandler struct {
	*Daemon
	fct HttpApiFunc
}

func (handler myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	err := handler.fct(handler.Daemon, w, r)
	if err != nil {
		http.Error(w, err.message, err.code)
	}
}

type BridgeConf struct {
	BridgeIP   string `json:"bridgeIP"`
	BridgeName string `json:"bridgeName"`
	BridgeCIDR string `json:"bridgeCIDR"`
	BridgeMTU  int    `json:"bridgeMTU"`
}

type Connection struct {
	ContainerID      string        `json:"containerID"`
	ContainerName    string        `json:"containerName"`
	ContainerPID     string        `json:"containerPID"`
	RequestIp        string        `json:"requestIP,omitempty"`
	Network          string        `json:"network"`
	OvsPortID        string        `json:"ovsPortID"`
	BandWidth        string        `json:"bandWidth,omitempty"`
	Delay            string        `json:"delay,omitempty"`
	RXTotal          uint64        `json:"rxKbytes"` // in KB
	TXTotal          uint64        `json:"txKbytes"` // in KB
	RXRate           float64       `json:"rxRate"`   // in Kb/s
	TXRate           float64       `json:"txRate"`   // in Kb/s
	ConnectionDetail OvsConnection `json:"ovs_connectionDetails"`
}

func ServeApi(d *Daemon) {
	server := &http.Server{
		Addr:    "127.0.0.1:8888",
		Handler: createRouter(d),
	}
	server.ListenAndServe()
}

func createRouter(d *Daemon) *mux.Router {
	r := mux.NewRouter()
	m := map[string]map[string]HttpApiFunc{
		"GET": {
			"/version":            getVersion,
			"/configuration":      getConf,
			"/networks":           getNets,
			"/network/{name:.*}":  getNet,
			"/connections":        getConns,
			"/connection/{id:.*}": getConn,
		},
		"POST": {
			"/configuration": setConf,
			"/network":       createNet,
			"/cluster/join":  joinCluster,
			"/cluster/leave": leaveCluster,
			"/connection":    createConn,
			"/qos/{id:.*}":   createQos,
		},
		"PUT": {
			"/qos/{id:.*}": updateQos,
		},
		"DELETE": {
			"/network/{name:.*}":  delNet,
			"/connection/{id:.*}": delConn,
		},
	}

	for method, routes := range m {
		for uri, Func := range routes {
			handler := myHandler{d, Func}
			r.Path(uri).Methods(method).Handler(handler)
		}
	}
	return r
}

// return the cxy-sdn version
func getVersion(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	w.Write([]byte(version))

	return nil
}

// get the ovs bridge conf
func getConf(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	conf, _ := json.Marshal(d.bridgeConf)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(conf)

	return nil
}

// set the bridge conf
func setConf(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	if r.Body == nil {
		return &HttpErr{http.StatusBadRequest, "SetConf request has no body"}
	}

	cfg := &BridgeConf{}

	err := json.NewDecoder(r.Body).Decode(cfg)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, "setConf json decode failed"}
	}

	d.bridgeConf = cfg
	return nil
}

// get all the existing network
func getNets(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	networks, err := GetNetworks()
	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	data, err := json.Marshal(networks)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)
	return nil
}

// get one specified network detail
func getNet(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	// get the network name
	vars := mux.Vars(r)
	name := vars["name"]

	network, err := GetNetwork(name)

	if err != nil {
		return &HttpErr{http.StatusNotFound, err.Error()}
	}

	data, err := json.Marshal(network)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	w.Header().Set("Content-type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// create a network
func createNet(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	if r.Body == nil {
		return &HttpErr{http.StatusBadRequest, "request body is empty"}
	}

	network := &Network{}
	err := json.NewDecoder(r.Body).Decode(network)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	_, cidr, err := net.ParseCIDR(network.Subnet)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	newNet, err := CreateNetwork(network.Name, cidr)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	data, _ := json.Marshal(newNet)

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// delete one specified network
func delNet(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	vars := mux.Vars(r)
	name := vars["name"]

	err := DeleteNetwork(name)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	return nil
}

// node join the cluster
func joinCluster(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	if r.URL.RawQuery == "" {
		return &HttpErr{http.StatusBadRequest, "address missing"}
	}

	kvs, err := url.ParseQuery(r.URL.RawQuery)

	if err != nil {
		return &HttpErr{http.StatusBadRequest, "parse query string error"}
	}

	addr, ok := kvs["address"]

	if !ok || addr[0] == "" {
		return &HttpErr{http.StatusBadRequest, "address parameter not exist"}
	}

	fmt.Println("Join to cluster", addr[0])

	ip := net.ParseIP(addr[0])
	if ip == nil {
		return &HttpErr{http.StatusBadRequest, "Invalid IP address"}
	}

	d.clusterChan <- &NodeCtx{ip.String(), nodeJoin}
	return nil
}

// node leave the cluster
func leaveCluster(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	fmt.Println("Node leave cluster")
	d.clusterChan <- &NodeCtx{"", nodeLeave}

	return nil
}

// get all connections
func getConns(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	d.connections.RLock()
	data, err := json.Marshal(d.connections.rm)
	d.connections.RUnlock()

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// get one specified connection
func getConn(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	vars := mux.Vars(r)

	containerId := vars["id"]
	con := d.connections.Get(containerId)

	if con == nil {
		return &HttpErr{http.StatusNotFound, containerId}
	}

	data, err := json.Marshal(con.(*Connection))

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// create a container connection
func createConn(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	if r.Body == nil {
		return &HttpErr{http.StatusBadRequest, "request body is empty"}
	}

	con := &Connection{}
	err := json.NewDecoder(r.Body).Decode(con)

	if err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	if con.Network == "" {
		con.Network = defaultNetwork
	}

	ctx := &ConnectionCtx{
		addConn,
		con,
		make(chan *Connection),
	}

	d.connectionChan <- ctx

	res := <-ctx.Result

	if res.OvsPortID == "-1" {
		return &HttpErr{http.StatusBadRequest, "resp body not valid"}
	}

	data, _ := json.Marshal(res)
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write(data)

	return nil
}

// delete the ovs and container connection
func delConn(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	vars := mux.Vars(r)
	containerId := vars["id"]

	con := d.connections.Get(containerId)

	if con == nil {
		return &HttpErr{http.StatusNotFound, "container not found"}
	}

	ctx := &ConnectionCtx{
		deleteConn,
		con.(*Connection),
		make(chan *Connection),
	}

	d.connectionChan <- ctx
	<-ctx.Result

	return nil
}

// create a qos for a container
func createQos(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	bw := r.FormValue("bw")
	delay := r.FormValue("delay")

	vars := mux.Vars(r)
	containerId := vars["id"]

	if bw == "" && delay == "" {
		return &HttpErr{http.StatusBadRequest, "bw and delay is empty"}
	}

	ok := d.connections.Check(containerId)

	if !ok {
		return &HttpErr{http.StatusNotFound, "container not found"}
	}

	if err := addQos(d, containerId, bw, delay); err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	return nil
}

// update a qos for a container
func updateQos(d *Daemon, w http.ResponseWriter, r *http.Request) *HttpErr {
	bw := r.FormValue("bw")
	delay := r.FormValue("delay")

	vars := mux.Vars(r)
	containerId := vars["id"]

	if bw == "" && delay == "" {
		return &HttpErr{http.StatusBadRequest, "bw and delay is empty"}
	}

	ok := d.connections.Check(containerId)

	if !ok {
		return &HttpErr{http.StatusNotFound, "container not found"}
	}

	if err := changeQos(d, containerId, bw, delay); err != nil {
		return &HttpErr{http.StatusInternalServerError, err.Error()}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation.
//
// 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.

package server

import (
	log "github.com/Sirupsen/logrus"
	"github.com/osrg/gobgp/config"
	"github.com/osrg/gobgp/packet"
	"github.com/osrg/gobgp/table"
	"net"
	"strconv"
	"time"
)

type broadcastBMPMsg struct {
	ch      chan *broadcastBMPMsg
	msgList []*bgp.BMPMessage
	conn    *net.TCPConn
}

func (m *broadcastBMPMsg) send() {
	m.ch <- m
}

type bmpConn struct {
	conn *net.TCPConn
	host string
}

type bmpClient struct {
	ch     chan *broadcastBMPMsg
	connCh chan *bmpConn
}

func newBMPClient(conf config.BmpServers, connCh chan *bmpConn) (*bmpClient, error) {
	b := &bmpClient{}
	if len(conf.BmpServerList) == 0 {
		return b, nil
	}

	b.ch = make(chan *broadcastBMPMsg)
	b.connCh = connCh

	tryConnect := func(host string) {
		for {
			conn, err := net.Dial("tcp", host)
			if err != nil {
				time.Sleep(30 * time.Second)
			} else {
				log.Info("bmp server is connected, ", host)
				connCh <- &bmpConn{
					conn: conn.(*net.TCPConn),
					host: host,
				}
				break
			}
		}
	}

	for _, c := range conf.BmpServerList {
		b := c.Config
		go tryConnect(net.JoinHostPort(b.Address, strconv.Itoa(int(b.Port))))
	}

	go func() {
		connMap := make(map[string]*net.TCPConn)
		for {
			select {
			case m := <-b.ch:
				if m.conn != nil {
					i := bgp.NewBMPInitiation([]bgp.BMPTLV{})
					buf, _ := i.Serialize()
					_, err := m.conn.Write(buf)
					if err == nil {
						connMap[m.conn.RemoteAddr().String()] = m.conn
					}
				}

				for host, conn := range connMap {
					if m.conn != nil && m.conn != conn {
						continue
					}

					for _, msg := range m.msgList {
						if msg.Header.Type == bgp.BMP_MSG_ROUTE_MONITORING {
							c := func() *config.BmpServerConfig {
								for _, c := range conf.BmpServerList {
									b := &c.Config
									if host == net.JoinHostPort(b.Address, strconv.Itoa(int(b.Port))) {
										return b
									}
								}
								return nil
							}()
							if c == nil {
								log.Fatal(host)
							}
							ph := msg.PeerHeader
							switch c.RouteMonitoringPolicy {
							case config.BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY:
								if ph.IsPostPolicy != false {
									continue
								}
							case config.BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY:
								if ph.IsPostPolicy != true {
									continue
								}
							}

						}
						b, _ := msg.Serialize()
						_, err := conn.Write(b)
						if err != nil {
							delete(connMap, host)
							go tryConnect(host)
							break
						}
					}
				}
			}
		}
	}()

	return b, nil
}

func (c *bmpClient) send() chan *broadcastBMPMsg {
	return c.ch
}

func bmpPeerUp(laddr string, lport, rport uint16, sent, recv *bgp.BGPMessage, t int, policy bool, pd uint64, peeri *table.PeerInfo, timestamp int64) *bgp.BMPMessage {
	ph := bgp.NewBMPPeerHeader(uint8(t), policy, pd, peeri.Address.String(), peeri.AS, peeri.LocalID.String(), float64(timestamp))
	return bgp.NewBMPPeerUpNotification(*ph, laddr, lport, rport, sent, recv)
}

func bmpPeerDown(reason uint8, t int, policy bool, pd uint64, peeri *table.PeerInfo, timestamp int64) *bgp.BMPMessage {
	ph := bgp.NewBMPPeerHeader(uint8(t), policy, pd, peeri.Address.String(), peeri.AS, peeri.LocalID.String(), float64(timestamp))
	return bgp.NewBMPPeerDownNotification(*ph, reason, nil, []byte{})
}

func bmpPeerRoute(t int, policy bool, pd uint64, peeri *table.PeerInfo, timestamp int64, payload []byte) *bgp.BMPMessage {
	ph := bgp.NewBMPPeerHeader(uint8(t), policy, pd, peeri.Address.String(), peeri.AS, peeri.LocalID.String(), float64(timestamp))
	m := bgp.NewBMPRouteMonitoring(*ph, nil)
	body := m.Body.(*bgp.BMPRouteMonitoring)
	body.BGPUpdatePayload = payload
	return m
}
<commit_msg>bmp: detect bmp connection lost earlier and recover earlier<commit_after>// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation.
//
// 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.

package server

import (
	log "github.com/Sirupsen/logrus"
	"github.com/osrg/gobgp/config"
	"github.com/osrg/gobgp/packet"
	"github.com/osrg/gobgp/table"
	"net"
	"strconv"
	"time"
)

type broadcastBMPMsg struct {
	ch      chan *broadcastBMPMsg
	msgList []*bgp.BMPMessage
	conn    *net.TCPConn
}

func (m *broadcastBMPMsg) send() {
	m.ch <- m
}

type bmpConn struct {
	conn *net.TCPConn
	host string
}

type bmpClient struct {
	ch     chan *broadcastBMPMsg
	connCh chan *bmpConn
}

func newBMPClient(conf config.BmpServers, connCh chan *bmpConn) (*bmpClient, error) {
	b := &bmpClient{}
	if len(conf.BmpServerList) == 0 {
		return b, nil
	}

	b.ch = make(chan *broadcastBMPMsg)
	b.connCh = connCh

	endCh := make(chan *net.TCPConn)

	tryConnect := func(host string) {
		interval := 1
		for {
			log.Debug("connecting bmp server: ", host)
			conn, err := net.Dial("tcp", host)
			if err != nil {
				time.Sleep(time.Duration(interval) * time.Second)
				if interval < 30 {
					interval *= 2
				}
			} else {
				log.Info("bmp server is connected, ", host)
				go func() {
					buf := make([]byte, 1)
					for {
						_, err := conn.Read(buf)
						if err != nil {
							endCh <- conn.(*net.TCPConn)
							return
						}
					}
				}()
				connCh <- &bmpConn{
					conn: conn.(*net.TCPConn),
					host: host,
				}
				break
			}
		}
	}

	for _, c := range conf.BmpServerList {
		b := c.Config
		go tryConnect(net.JoinHostPort(b.Address, strconv.Itoa(int(b.Port))))
	}

	go func() {
		connMap := make(map[string]*net.TCPConn)
		for {
			select {
			case m := <-b.ch:
				if m.conn != nil {
					i := bgp.NewBMPInitiation([]bgp.BMPTLV{})
					buf, _ := i.Serialize()
					_, err := m.conn.Write(buf)
					if err == nil {
						connMap[m.conn.RemoteAddr().String()] = m.conn
					}
				}

				for host, conn := range connMap {
					if m.conn != nil && m.conn != conn {
						continue
					}

					for _, msg := range m.msgList {
						if msg.Header.Type == bgp.BMP_MSG_ROUTE_MONITORING {
							c := func() *config.BmpServerConfig {
								for _, c := range conf.BmpServerList {
									b := &c.Config
									if host == net.JoinHostPort(b.Address, strconv.Itoa(int(b.Port))) {
										return b
									}
								}
								return nil
							}()
							if c == nil {
								log.Fatal(host)
							}
							ph := msg.PeerHeader
							switch c.RouteMonitoringPolicy {
							case config.BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY:
								if ph.IsPostPolicy != false {
									continue
								}
							case config.BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY:
								if ph.IsPostPolicy != true {
									continue
								}
							}

						}
						b, _ := msg.Serialize()
						if _, err := conn.Write(b); err != nil {
							break
						}
					}
				}
			case conn := <-endCh:
				host := conn.RemoteAddr().String()
				log.Debugf("bmp connection to %s killed", host)
				delete(connMap, host)
				go tryConnect(host)
			}
		}
	}()

	return b, nil
}

func (c *bmpClient) send() chan *broadcastBMPMsg {
	return c.ch
}

func bmpPeerUp(laddr string, lport, rport uint16, sent, recv *bgp.BGPMessage, t int, policy bool, pd uint64, peeri *table.PeerInfo, timestamp int64) *bgp.BMPMessage {
	ph := bgp.NewBMPPeerHeader(uint8(t), policy, pd, peeri.Address.String(), peeri.AS, peeri.LocalID.String(), float64(timestamp))
	return bgp.NewBMPPeerUpNotification(*ph, laddr, lport, rport, sent, recv)
}

func bmpPeerDown(reason uint8, t int, policy bool, pd uint64, peeri *table.PeerInfo, timestamp int64) *bgp.BMPMessage {
	ph := bgp.NewBMPPeerHeader(uint8(t), policy, pd, peeri.Address.String(), peeri.AS, peeri.LocalID.String(), float64(timestamp))
	return bgp.NewBMPPeerDownNotification(*ph, reason, nil, []byte{})
}

func bmpPeerRoute(t int, policy bool, pd uint64, peeri *table.PeerInfo, timestamp int64, payload []byte) *bgp.BMPMessage {
	ph := bgp.NewBMPPeerHeader(uint8(t), policy, pd, peeri.Address.String(), peeri.AS, peeri.LocalID.String(), float64(timestamp))
	m := bgp.NewBMPRouteMonitoring(*ph, nil)
	body := m.Body.(*bgp.BMPRouteMonitoring)
	body.BGPUpdatePayload = payload
	return m
}
<|endoftext|>


<commit_before>// +build linux

package ls

import (
	"fmt"
	"os"
	"os/user"
	"syscall"
	"unsafe"
)

var userLookupCache = make(map[string]string)

func userLookUp(id string) (string, error) {
	if v, ok := userLookupCache[id]; ok {
		return v, nil
	} else {
		u, err := user.LookupId(id)
		if err == nil {
			userLookupCache[id] = u.Name
			return u.Name, nil
		}
		return "", err
	}
}

type LongInfo struct {
	UserName, GroupName string
	HardLinks           int
}

func GetTermSize() (int, int, error) {
	var dimensions [4]uint16

	fd := os.Stdout.Fd()
	if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {
		return -1, -1, err
	}
	return int(dimensions[1]), int(dimensions[0]), nil
}

const ioctlReadTermios = 0x5401

func IsTerminal(fd int) bool {
	var termios syscall.Termios
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
	return err == 0
}

func GetLongInfo(info os.FileInfo) *LongInfo {
	stat := info.Sys().(*syscall.Stat_t)
	userName := fmt.Sprintf("%d", stat.Uid)
	if u, err := userLookUp(userName); err == nil {
		userName = u
	}
	group := fmt.Sprintf("%d", stat.Gid)
	if g, err := userLookUp(group); err == nil {
		group = g
	}
	return &LongInfo{userName, group, int(stat.Nlink)}
}
<commit_msg>fix username lookup, add groupname lookup for linux<commit_after>// +build linux

package ls

import (
	"fmt"
	"os"
	"os/user"
	"syscall"
	"unsafe"
	"runtime"
	"strconv"
)

/*
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <stdlib.h>

static int mygetgrgid_r(int gid, struct group *grp,
	char *buf, size_t buflen, struct group **result) {
	return getgrgid_r(gid, grp, buf, buflen, result);
}
*/
import "C"

type UnknownGroupIdError int

func (e UnknownGroupIdError) Error() string {
	return "user: unknown group id " + strconv.Itoa(int(e))
}

// taken from os/user/lookup_unix.go
func groupNameOSLookup(gid int) (string, error) {
	var grp C.struct_group
	var result *C.struct_group

	var bufSize C.long
	if runtime.GOOS == "dragonfly" || runtime.GOOS == "freebsd" {
		// DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX
		// and just return -1.  So just use the same
		// size that Linux returns.
		bufSize = 1024
	} else {
		bufSize = C.sysconf(C._SC_GETGR_R_SIZE_MAX)
		if bufSize <= 0 || bufSize > 1<<20 {
			return "", fmt.Errorf("user: unreasonable _SC_GETGR_R_SIZE_MAX of %d", bufSize)
		}
	}
	buf := C.malloc(C.size_t(bufSize))
	defer C.free(buf)
	var rv C.int
	// mygetgrgid_r is a wrapper around getgrgid_r to
	// to avoid using gid_t because C.gid_t(uid) for
	// unknown reasons doesn't work on linux.
	rv = C.mygetgrgid_r(C.int(gid),
	&grp,
	(*C.char)(buf),
	C.size_t(bufSize),
	&result)
	if rv != 0 {
		return "", fmt.Errorf("ls: lookup group failed id %d: %s", gid, syscall.Errno(rv))
	}
	if result == nil {
		return "", UnknownGroupIdError(gid)
	}
	return C.GoString(grp.gr_name), nil
}

var groupLookupCache = make(map[string]string)

func groupLookup(id string) (string, error) {
	if v, ok := groupLookupCache[id]; ok {
		return v, nil
	} else {
		i, e := strconv.Atoi(id)
		if e != nil {
			return "", e
		}
		g, err := groupNameOSLookup(i)
		if err == nil {
			groupLookupCache[id] = g
			return g, nil
		}
		return "", err
	}
}

var userLookupCache = make(map[string]string)

func userLookUp(id string) (string, error) {
	if v, ok := userLookupCache[id]; ok {
		return v, nil
	} else {
		u, err := user.LookupId(id)
		if err == nil {
			userLookupCache[id] = u.Username
			return u.Name, nil
		}
		return "", err
	}
}

type LongInfo struct {
	UserName, GroupName string
	HardLinks           int
}

func GetTermSize() (int, int, error) {
	var dimensions [4]uint16

	fd := os.Stdout.Fd()
	if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {
		return -1, -1, err
	}
	return int(dimensions[1]), int(dimensions[0]), nil
}

const ioctlReadTermios = 0x5401

func IsTerminal(fd int) bool {
	var termios syscall.Termios
	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
	return err == 0
}

func GetLongInfo(info os.FileInfo) *LongInfo {
	stat := info.Sys().(*syscall.Stat_t)
	userName := fmt.Sprintf("%d", stat.Uid)
	if u, err := userLookUp(userName); err == nil {
		userName = u
	}
	group := fmt.Sprintf("%d", stat.Gid)
	if g, err := groupLookup(group); err == nil {
		group = g
	}
	return &LongInfo{userName, group, int(stat.Nlink)}
}
<|endoftext|>


<commit_before>package cl11

import (
	"log"

	"testing"
)

func TestCreateContext(t *testing.T) {
	platforms, err := GetPlatforms()
	if err != nil {
		t.Fatal(err)
	}
	for i := range platforms {
		devices, err := platforms[i].GetDevices()
		if err != nil {
			t.Fatal(err)
		}
		for j := range devices {
			_, err = CreateContext(nil, []*Device{devices[j]}, func(err string, data []byte) {})
			if err != nil {
				t.Error(err)
			}
		}
	}
}

func ExampleContext() {
	platforms, err := GetPlatforms()
	if err != nil {
		log.Fatalln(err)
	}
	for i := range platforms {
		devices, err := platforms[i].GetDevices()
		if err != nil {
			log.Fatalln(err)
		}
		for j := range devices {
			_, err = CreateContext(nil, []*Device{devices[j]}, func(err string, data []byte) {})
			if err != nil {
				log.Fatalln(err)
			}
			log.Println("created context on", devices[j])
		}
	}
}

// Creates all possible contexts.
func createContexts(t *testing.T) []*Context {
	var contexts []*Context
	platforms, err := GetPlatforms()
	if err != nil {
		t.Fatal(err)
	}
	for i := range platforms {
		devices, err := platforms[i].GetDevices()
		if err != nil {
			t.Error(err)
			continue
		}
		for j := range devices {
			c, err := CreateContext(nil, []*Device{devices[j]}, func(err string, data []byte) {})
			if err != nil {
				t.Error(err)
				continue
			}
			contexts = append(contexts, c)
		}
	}
	return contexts
}
<commit_msg>Removed create contexts since it is now in create command queue.<commit_after>package cl11

import (
	"log"

	"testing"
)

func ExampleContext() {
	platforms, err := GetPlatforms()
	if err != nil {
		log.Fatalln(err)
	}
	for i := range platforms {
		devices, err := platforms[i].GetDevices()
		if err != nil {
			log.Fatalln(err)
		}
		for j := range devices {
			_, err = CreateContext(nil, []*Device{devices[j]}, func(err string, data []byte) {})
			if err != nil {
				log.Fatalln(err)
			}
			log.Println("created context on", devices[j])
		}
	}
}

// Creates all possible contexts.
func createContexts(t *testing.T) []*Context {
	var contexts []*Context
	platforms, err := GetPlatforms()
	if err != nil {
		t.Fatal(err)
	}
	for i := range platforms {
		devices, err := platforms[i].GetDevices()
		if err != nil {
			t.Error(err)
			continue
		}
		for j := range devices {
			c, err := CreateContext(nil, []*Device{devices[j]}, func(err string, data []byte) {})
			if err != nil {
				t.Error(err)
				continue
			}
			contexts = append(contexts, c)
		}
	}
	return contexts
}
<|endoftext|>


<commit_before>package server

import (
	"sync"
	"time"

	log "github.com/cihub/seelog"
	"golang.org/x/net/context"
	"gopkg.in/tomb.v2"

	"github.com/mondough/mercury"
	"github.com/mondough/mercury/transport"
	terrors "github.com/mondough/typhon/errors"
	tmsg "github.com/mondough/typhon/message"
	ttrans "github.com/mondough/typhon/transport"
)

const (
	connectTimeout = 30 * time.Second
)

var (
	ErrAlreadyRunning   error = terrors.InternalService("server_already_running", "Server is already running", nil)
	ErrTransportClosed  error = terrors.InternalService("transport_closed", "Transport closed", nil)
	errEndpointNotFound       = terrors.BadRequest("endpoint_not_found", "Endpoint not found", nil)
	defaultMiddleware   []ServerMiddleware
	defaultMiddlewareM  sync.RWMutex
)

func NewServer(name string) Server {
	defaultMiddlewareM.RLock()
	middleware := defaultMiddleware
	defaultMiddlewareM.RUnlock()

	return &server{
		name:       name,
		middleware: middleware,
	}
}

func SetDefaultMiddleware(middleware []ServerMiddleware) {
	defaultMiddlewareM.Lock()
	defer defaultMiddlewareM.Unlock()
	defaultMiddleware = middleware
}

type server struct {
	name        string              // server name (registered with the transport; immutable)
	endpoints   map[string]Endpoint // endpoint name: Endpoint
	endpointsM  sync.RWMutex        // protects endpoints
	workerTomb  *tomb.Tomb          // runs as long as there is a worker consuming Requests
	workerTombM sync.RWMutex        // protects workerTomb
	middleware  []ServerMiddleware  // applied in-order for requests, reverse-order for responses
	middlewareM sync.RWMutex        // protects middleware
}

func (s *server) Name() string {
	return s.name
}

func (s *server) AddEndpoints(eps ...Endpoint) {
	s.endpointsM.Lock()
	defer s.endpointsM.Unlock()
	if s.endpoints == nil {
		s.endpoints = make(map[string]Endpoint, len(eps))
	}
	for _, e := range eps {
		// if e.Request == nil || e.Response == nil {
		// 	panic(fmt.Sprintf("Endpoint \"%s\" must have Request and Response defined", e.Name))
		// }
		s.endpoints[e.Name] = e
	}
}

func (s *server) RemoveEndpoints(eps ...Endpoint) {
	s.endpointsM.Lock()
	defer s.endpointsM.Unlock()
	for _, e := range eps {
		delete(s.endpoints, e.Name)
	}
}
func (s *server) Endpoints() []Endpoint {
	s.endpointsM.RLock()
	defer s.endpointsM.RUnlock()
	result := make([]Endpoint, 0, len(s.endpoints))
	for _, ep := range s.endpoints {
		result = append(result, ep)
	}
	return result
}

func (s *server) Endpoint(name string) (Endpoint, bool) {
	s.endpointsM.RLock()
	defer s.endpointsM.RUnlock()
	ep, ok := s.endpoints[name]
	return ep, ok
}

func (s *server) start(trans transport.Transport) (*tomb.Tomb, error) {
	s.workerTombM.Lock()
	if s.workerTomb != nil {
		s.workerTombM.Unlock()
		return nil, ErrAlreadyRunning
	}
	tm := new(tomb.Tomb)
	s.workerTomb = tm
	s.workerTombM.Unlock()

	stop := func() {
		trans.StopListening(s.Name())
		s.workerTombM.Lock()
		s.workerTomb = nil
		s.workerTombM.Unlock()
	}

	var inbound chan tmsg.Request
	connect := func() error {
		select {
		case <-trans.Ready():
			inbound = make(chan tmsg.Request, 500)
			return trans.Listen(s.Name(), inbound)

		case <-time.After(connectTimeout):
			log.Warnf("[Mercury:Server] Timed out after %s waiting for transport readiness", connectTimeout.String())
			return ttrans.ErrTimeout
		}
	}

	// Block here purposefully (deliberately not in the goroutine below, because we want to report a connection error
	// to the caller)
	if err := connect(); err != nil {
		stop()
		return nil, err
	}

	tm.Go(func() error {
		defer stop()
		for {
			select {
			case req, ok := <-inbound:
				if !ok {
					// Received because the channel closed; try to reconnect
					log.Warn("[Mercury:Server] Inbound channel closed; trying to reconnect…")
					if err := connect(); err != nil {
						log.Criticalf("[Mercury:Server] Could not reconnect after channel close: %s", err)
						return err
					}
				} else {
					go s.handle(trans, req)
				}

			case <-tm.Dying():
				return tomb.ErrDying
			}
		}
	})
	return tm, nil
}

func (s *server) Start(trans transport.Transport) error {
	_, err := s.start(trans)
	return err
}

func (s *server) Run(trans transport.Transport) {
	if tm, err := s.start(trans); err != nil || tm == nil {
		panic(err)
	} else if err := tm.Wait(); err != nil {
		panic(err)
	}
}

func (s *server) Stop() {
	s.workerTombM.RLock()
	tm := s.workerTomb
	s.workerTombM.RUnlock()
	if tm != nil {
		tm.Killf("Stop() called")
		tm.Wait()
	}
}

func (s *server) applyRequestMiddleware(req mercury.Request) (mercury.Request, mercury.Response) {
	s.middlewareM.RLock()
	mws := s.middleware
	s.middlewareM.RUnlock()
	for _, mw := range mws {
		if req_, rsp := mw.ProcessServerRequest(req); rsp != nil {
			return req_, rsp
		} else {
			req = req_
		}
	}
	return req, nil
}

func (s *server) applyResponseMiddleware(rsp mercury.Response, ctx context.Context) mercury.Response {
	s.middlewareM.RLock()
	mws := s.middleware
	s.middlewareM.RUnlock()
	for i := len(mws) - 1; i >= 0; i-- { // reverse order
		mw := mws[i]
		rsp = mw.ProcessServerResponse(rsp, ctx)
	}
	return rsp
}

func (s *server) handle(trans transport.Transport, req_ tmsg.Request) {
	req := mercury.FromTyphonRequest(req_)
	req, rsp := s.applyRequestMiddleware(req)

	if rsp == nil {
		if ep, ok := s.Endpoint(req.Endpoint()); !ok {
			log.Warnf("[Mercury:Server] Received request %s for unknown endpoint %s", req.Id(), req.Endpoint())
			rsp = ErrorResponse(req, errEndpointNotFound)
		} else {
			if rsp_, err := ep.Handle(req); err != nil {
				log.Debugf("[Mercury:Server] Got error from endpoint %s for request %s: %v", ep.Name, req.Id(), err)
				rsp = ErrorResponse(req, err)
				// @todo happy to remove this verbose logging once we have tracing... For now it will allow us to debug things
				log.Debugf("[Mercury:Server] Full request: %+v", req.Body())
				log.Debugf("[Mercury:Server] Full error: %+v", rsp.Body())
			} else if rsp_ == nil {
				rsp = req.Response(nil)
			} else {
				rsp = rsp_
			}
		}
	}
	rsp = s.applyResponseMiddleware(rsp, req)
	if rsp != nil {
		trans.Respond(req, rsp)
	}
}

func (s *server) Middleware() []ServerMiddleware {
	// Note that no operation exists that mutates a particular element; this is very deliberate and means we do not
	// need to hold a read lock when iterating over the middleware slice, only when getting a reference to the slice.
	s.middlewareM.RLock()
	mws := s.middleware
	s.middlewareM.RUnlock()
	result := make([]ServerMiddleware, len(mws))
	copy(result, mws)
	return result
}

func (s *server) SetMiddleware(mws []ServerMiddleware) {
	s.middlewareM.Lock()
	defer s.middlewareM.Unlock()
	s.middleware = mws
}

func (s *server) AddMiddleware(mw ServerMiddleware) {
	s.middlewareM.Lock()
	defer s.middlewareM.Unlock()
	s.middleware = append(s.middleware, mw)
}

// ErrorResponse constructs a response for the given request, with the given error as its contents. Mercury clients
// know how to unmarshal these errors.
func ErrorResponse(req mercury.Request, err error) mercury.Response {
	rsp := req.Response(nil)
	var terr *terrors.Error
	if err != nil {
		terr = terrors.Wrap(err, nil).(*terrors.Error)
	}
	rsp.SetBody(terrors.Marshal(terr))
	if err := tmsg.ProtoMarshaler().MarshalBody(rsp); err != nil {
		log.Errorf("[Mercury:Server] Failed to marshal error response: %v", err)
		return nil // Not much we can do here
	}
	rsp.SetIsError(true)
	return rsp
}
<commit_msg>Remove dotted code for `ErrAlreadyRunning` and `ErrTransportClosed` to avoid leaking implementation details to outside world<commit_after>package server

import (
	"sync"
	"time"

	log "github.com/cihub/seelog"
	"golang.org/x/net/context"
	"gopkg.in/tomb.v2"

	"github.com/mondough/mercury"
	"github.com/mondough/mercury/transport"
	terrors "github.com/mondough/typhon/errors"
	tmsg "github.com/mondough/typhon/message"
	ttrans "github.com/mondough/typhon/transport"
)

const (
	connectTimeout = 30 * time.Second
)

var (
	ErrAlreadyRunning   error = terrors.InternalService("", "Server is already running", nil) // empty dotted code so impl details don't leak outside
	ErrTransportClosed  error = terrors.InternalService("", "Transport closed", nil)
	errEndpointNotFound       = terrors.BadRequest("endpoint_not_found", "Endpoint not found", nil)
	defaultMiddleware   []ServerMiddleware
	defaultMiddlewareM  sync.RWMutex
)

func NewServer(name string) Server {
	defaultMiddlewareM.RLock()
	middleware := defaultMiddleware
	defaultMiddlewareM.RUnlock()

	return &server{
		name:       name,
		middleware: middleware,
	}
}

func SetDefaultMiddleware(middleware []ServerMiddleware) {
	defaultMiddlewareM.Lock()
	defer defaultMiddlewareM.Unlock()
	defaultMiddleware = middleware
}

type server struct {
	name        string              // server name (registered with the transport; immutable)
	endpoints   map[string]Endpoint // endpoint name: Endpoint
	endpointsM  sync.RWMutex        // protects endpoints
	workerTomb  *tomb.Tomb          // runs as long as there is a worker consuming Requests
	workerTombM sync.RWMutex        // protects workerTomb
	middleware  []ServerMiddleware  // applied in-order for requests, reverse-order for responses
	middlewareM sync.RWMutex        // protects middleware
}

func (s *server) Name() string {
	return s.name
}

func (s *server) AddEndpoints(eps ...Endpoint) {
	s.endpointsM.Lock()
	defer s.endpointsM.Unlock()
	if s.endpoints == nil {
		s.endpoints = make(map[string]Endpoint, len(eps))
	}
	for _, e := range eps {
		// if e.Request == nil || e.Response == nil {
		// 	panic(fmt.Sprintf("Endpoint \"%s\" must have Request and Response defined", e.Name))
		// }
		s.endpoints[e.Name] = e
	}
}

func (s *server) RemoveEndpoints(eps ...Endpoint) {
	s.endpointsM.Lock()
	defer s.endpointsM.Unlock()
	for _, e := range eps {
		delete(s.endpoints, e.Name)
	}
}
func (s *server) Endpoints() []Endpoint {
	s.endpointsM.RLock()
	defer s.endpointsM.RUnlock()
	result := make([]Endpoint, 0, len(s.endpoints))
	for _, ep := range s.endpoints {
		result = append(result, ep)
	}
	return result
}

func (s *server) Endpoint(name string) (Endpoint, bool) {
	s.endpointsM.RLock()
	defer s.endpointsM.RUnlock()
	ep, ok := s.endpoints[name]
	return ep, ok
}

func (s *server) start(trans transport.Transport) (*tomb.Tomb, error) {
	s.workerTombM.Lock()
	if s.workerTomb != nil {
		s.workerTombM.Unlock()
		return nil, ErrAlreadyRunning
	}
	tm := new(tomb.Tomb)
	s.workerTomb = tm
	s.workerTombM.Unlock()

	stop := func() {
		trans.StopListening(s.Name())
		s.workerTombM.Lock()
		s.workerTomb = nil
		s.workerTombM.Unlock()
	}

	var inbound chan tmsg.Request
	connect := func() error {
		select {
		case <-trans.Ready():
			inbound = make(chan tmsg.Request, 500)
			return trans.Listen(s.Name(), inbound)

		case <-time.After(connectTimeout):
			log.Warnf("[Mercury:Server] Timed out after %s waiting for transport readiness", connectTimeout.String())
			return ttrans.ErrTimeout
		}
	}

	// Block here purposefully (deliberately not in the goroutine below, because we want to report a connection error
	// to the caller)
	if err := connect(); err != nil {
		stop()
		return nil, err
	}

	tm.Go(func() error {
		defer stop()
		for {
			select {
			case req, ok := <-inbound:
				if !ok {
					// Received because the channel closed; try to reconnect
					log.Warn("[Mercury:Server] Inbound channel closed; trying to reconnect…")
					if err := connect(); err != nil {
						log.Criticalf("[Mercury:Server] Could not reconnect after channel close: %s", err)
						return err
					}
				} else {
					go s.handle(trans, req)
				}

			case <-tm.Dying():
				return tomb.ErrDying
			}
		}
	})
	return tm, nil
}

func (s *server) Start(trans transport.Transport) error {
	_, err := s.start(trans)
	return err
}

func (s *server) Run(trans transport.Transport) {
	if tm, err := s.start(trans); err != nil || tm == nil {
		panic(err)
	} else if err := tm.Wait(); err != nil {
		panic(err)
	}
}

func (s *server) Stop() {
	s.workerTombM.RLock()
	tm := s.workerTomb
	s.workerTombM.RUnlock()
	if tm != nil {
		tm.Killf("Stop() called")
		tm.Wait()
	}
}

func (s *server) applyRequestMiddleware(req mercury.Request) (mercury.Request, mercury.Response) {
	s.middlewareM.RLock()
	mws := s.middleware
	s.middlewareM.RUnlock()
	for _, mw := range mws {
		if req_, rsp := mw.ProcessServerRequest(req); rsp != nil {
			return req_, rsp
		} else {
			req = req_
		}
	}
	return req, nil
}

func (s *server) applyResponseMiddleware(rsp mercury.Response, ctx context.Context) mercury.Response {
	s.middlewareM.RLock()
	mws := s.middleware
	s.middlewareM.RUnlock()
	for i := len(mws) - 1; i >= 0; i-- { // reverse order
		mw := mws[i]
		rsp = mw.ProcessServerResponse(rsp, ctx)
	}
	return rsp
}

func (s *server) handle(trans transport.Transport, req_ tmsg.Request) {
	req := mercury.FromTyphonRequest(req_)
	req, rsp := s.applyRequestMiddleware(req)

	if rsp == nil {
		if ep, ok := s.Endpoint(req.Endpoint()); !ok {
			log.Warnf("[Mercury:Server] Received request %s for unknown endpoint %s", req.Id(), req.Endpoint())
			rsp = ErrorResponse(req, errEndpointNotFound)
		} else {
			if rsp_, err := ep.Handle(req); err != nil {
				log.Debugf("[Mercury:Server] Got error from endpoint %s for request %s: %v", ep.Name, req.Id(), err)
				rsp = ErrorResponse(req, err)
				// @todo happy to remove this verbose logging once we have tracing... For now it will allow us to debug things
				log.Debugf("[Mercury:Server] Full request: %+v", req.Body())
				log.Debugf("[Mercury:Server] Full error: %+v", rsp.Body())
			} else if rsp_ == nil {
				rsp = req.Response(nil)
			} else {
				rsp = rsp_
			}
		}
	}
	rsp = s.applyResponseMiddleware(rsp, req)
	if rsp != nil {
		trans.Respond(req, rsp)
	}
}

func (s *server) Middleware() []ServerMiddleware {
	// Note that no operation exists that mutates a particular element; this is very deliberate and means we do not
	// need to hold a read lock when iterating over the middleware slice, only when getting a reference to the slice.
	s.middlewareM.RLock()
	mws := s.middleware
	s.middlewareM.RUnlock()
	result := make([]ServerMiddleware, len(mws))
	copy(result, mws)
	return result
}

func (s *server) SetMiddleware(mws []ServerMiddleware) {
	s.middlewareM.Lock()
	defer s.middlewareM.Unlock()
	s.middleware = mws
}

func (s *server) AddMiddleware(mw ServerMiddleware) {
	s.middlewareM.Lock()
	defer s.middlewareM.Unlock()
	s.middleware = append(s.middleware, mw)
}

// ErrorResponse constructs a response for the given request, with the given error as its contents. Mercury clients
// know how to unmarshal these errors.
func ErrorResponse(req mercury.Request, err error) mercury.Response {
	rsp := req.Response(nil)
	var terr *terrors.Error
	if err != nil {
		terr = terrors.Wrap(err, nil).(*terrors.Error)
	}
	rsp.SetBody(terrors.Marshal(terr))
	if err := tmsg.ProtoMarshaler().MarshalBody(rsp); err != nil {
		log.Errorf("[Mercury:Server] Failed to marshal error response: %v", err)
		return nil // Not much we can do here
	}
	rsp.SetIsError(true)
	return rsp
}
<|endoftext|>


<commit_before>package server

import (
	"path"
	"sync/atomic"
	"time"

	"golang.org/x/net/context"

	"github.com/coreos/etcd/clientv3"
	"github.com/golang/protobuf/proto"
	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/kvproto/pkg/pdpb"
)

const (
	// update timestamp every updateTimestampStep milliseconds.
	updateTimestampStep = int64(10)
	maxLogical          = int64(1 << 18)
)

type atomicObject struct {
	physical time.Time
	logical  int64
}

func (s *Server) getTimestampPath() string {
	return path.Join(s.rootPath, "timestamp")
}

func (s *Server) loadTimestamp() (int64, error) {
	data, err := getValue(s.client, s.getTimestampPath())
	if err != nil {
		return 0, errors.Trace(err)
	}
	if data == nil {
		return 0, nil
	}

	ts, err := bytesToUint64(data)
	if err != nil {
		return 0, errors.Trace(err)
	}

	return int64(ts), nil
}

// save timestamp, if lastTs is 0, we think the timestamp doesn't exist, so create it,
// otherwise, update it.
func (s *Server) saveTimestamp(now time.Time) error {
	data := uint64ToBytes(uint64(now.UnixNano()))
	key := s.getTimestampPath()

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	resp, err := s.client.Txn(ctx).
		If(s.leaderCmp()).
		Then(clientv3.OpPut(key, string(data))).
		Commit()
	cancel()
	if err != nil {
		return errors.Trace(err)
	}
	if !resp.Succeeded {
		return errors.New("save timestamp failed, maybe we lost leader")
	}

	s.lastSavedTime = now

	return nil
}

func (s *Server) syncTimestamp() error {
	last, err := s.loadTimestamp()

	if err != nil {
		return errors.Trace(err)
	}

	var now time.Time

	for {
		now = time.Now()

		since := (now.UnixNano() - last) / 1e6
		if since <= 0 {
			return errors.Errorf("%s <= last saved time %s", now, time.Unix(0, last))
		}

		// TODO: can we speed up this?
		if wait := 2*s.cfg.TsoSaveInterval - since; wait > 0 {
			log.Warnf("wait %d milliseconds to guarantee valid generated timestamp", wait)
			time.Sleep(time.Duration(wait) * time.Millisecond)
			continue
		}

		break
	}

	if err = s.saveTimestamp(now); err != nil {
		return errors.Trace(err)
	}

	log.Debug("sync and save timestamp ok")

	current := &atomicObject{
		physical: now,
	}
	s.ts.Store(current)

	return nil
}

func (s *Server) updateTimestamp() error {
	prev := s.ts.Load().(*atomicObject)
	now := time.Now()

	// ms
	since := now.Sub(prev.physical).Nanoseconds() / 1e6
	if since > 2*updateTimestampStep {
		log.Warnf("clock offset: %v, prev: %v, now %v", since, prev.physical, now)
	}
	// Avoid the same physical time stamp
	if since <= 0 {
		log.Warn("invalid physical time stamp, re-update later again")
		return nil
	}

	if now.Sub(s.lastSavedTime).Nanoseconds()/1e6 > s.cfg.TsoSaveInterval {
		if err := s.saveTimestamp(now); err != nil {
			return errors.Trace(err)
		}
	}

	current := &atomicObject{
		physical: now,
	}
	s.ts.Store(current)

	return nil
}

const maxRetryNum = 100

func (s *Server) getRespTS() *pdpb.Timestamp {
	resp := &pdpb.Timestamp{}
	for i := 0; i < maxRetryNum; i++ {
		current, ok := s.ts.Load().(*atomicObject)
		if !ok {
			log.Errorf("we haven't synced timestamp ok, wait and retry")
			time.Sleep(200 * time.Millisecond)
			continue
		}

		resp.Physical = proto.Int64(int64(current.physical.UnixNano()) / 1e6)
		resp.Logical = proto.Int64(atomic.AddInt64(&current.logical, 1))
		if *resp.Logical >= maxLogical {
			log.Errorf("logical part outside of max logical interval %v, please check ntp time", resp)
			time.Sleep(50 * time.Millisecond)
			continue
		}
		return resp
	}
	return nil
}
<commit_msg>server: add tso retry number log info.<commit_after>package server

import (
	"path"
	"sync/atomic"
	"time"

	"golang.org/x/net/context"

	"github.com/coreos/etcd/clientv3"
	"github.com/golang/protobuf/proto"
	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/kvproto/pkg/pdpb"
)

const (
	// update timestamp every updateTimestampStep milliseconds.
	updateTimestampStep = int64(10)
	maxLogical          = int64(1 << 18)
)

type atomicObject struct {
	physical time.Time
	logical  int64
}

func (s *Server) getTimestampPath() string {
	return path.Join(s.rootPath, "timestamp")
}

func (s *Server) loadTimestamp() (int64, error) {
	data, err := getValue(s.client, s.getTimestampPath())
	if err != nil {
		return 0, errors.Trace(err)
	}
	if data == nil {
		return 0, nil
	}

	ts, err := bytesToUint64(data)
	if err != nil {
		return 0, errors.Trace(err)
	}

	return int64(ts), nil
}

// save timestamp, if lastTs is 0, we think the timestamp doesn't exist, so create it,
// otherwise, update it.
func (s *Server) saveTimestamp(now time.Time) error {
	data := uint64ToBytes(uint64(now.UnixNano()))
	key := s.getTimestampPath()

	ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
	resp, err := s.client.Txn(ctx).
		If(s.leaderCmp()).
		Then(clientv3.OpPut(key, string(data))).
		Commit()
	cancel()
	if err != nil {
		return errors.Trace(err)
	}
	if !resp.Succeeded {
		return errors.New("save timestamp failed, maybe we lost leader")
	}

	s.lastSavedTime = now

	return nil
}

func (s *Server) syncTimestamp() error {
	last, err := s.loadTimestamp()

	if err != nil {
		return errors.Trace(err)
	}

	var now time.Time

	for {
		now = time.Now()

		since := (now.UnixNano() - last) / 1e6
		if since <= 0 {
			return errors.Errorf("%s <= last saved time %s", now, time.Unix(0, last))
		}

		// TODO: can we speed up this?
		if wait := 2*s.cfg.TsoSaveInterval - since; wait > 0 {
			log.Warnf("wait %d milliseconds to guarantee valid generated timestamp", wait)
			time.Sleep(time.Duration(wait) * time.Millisecond)
			continue
		}

		break
	}

	if err = s.saveTimestamp(now); err != nil {
		return errors.Trace(err)
	}

	log.Debug("sync and save timestamp ok")

	current := &atomicObject{
		physical: now,
	}
	s.ts.Store(current)

	return nil
}

func (s *Server) updateTimestamp() error {
	prev := s.ts.Load().(*atomicObject)
	now := time.Now()

	// ms
	since := now.Sub(prev.physical).Nanoseconds() / 1e6
	if since > 2*updateTimestampStep {
		log.Warnf("clock offset: %v, prev: %v, now %v", since, prev.physical, now)
	}
	// Avoid the same physical time stamp
	if since <= 0 {
		log.Warn("invalid physical time stamp, re-update later again")
		return nil
	}

	if now.Sub(s.lastSavedTime).Nanoseconds()/1e6 > s.cfg.TsoSaveInterval {
		if err := s.saveTimestamp(now); err != nil {
			return errors.Trace(err)
		}
	}

	current := &atomicObject{
		physical: now,
	}
	s.ts.Store(current)

	return nil
}

const maxRetryNum = 100

func (s *Server) getRespTS() *pdpb.Timestamp {
	resp := &pdpb.Timestamp{}
	for i := 0; i < maxRetryNum; i++ {
		current, ok := s.ts.Load().(*atomicObject)
		if !ok {
			log.Errorf("we haven't synced timestamp ok, wait and retry, retry number %d", i)
			time.Sleep(200 * time.Millisecond)
			continue
		}

		resp.Physical = proto.Int64(int64(current.physical.UnixNano()) / 1e6)
		resp.Logical = proto.Int64(atomic.AddInt64(&current.logical, 1))
		if *resp.Logical >= maxLogical {
			log.Errorf("logical part outside of max logical interval %v, please check ntp time, retry number %d", resp, i)
			time.Sleep(50 * time.Millisecond)
			continue
		}
		return resp
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The Kubeflow Authors.

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.
*/

package mysql

import (
	crand "crypto/rand"
	"database/sql"
	"fmt"
	"math/big"
	"math/rand"
	"os"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"k8s.io/klog"

	v1beta1 "github.com/kubeflow/katib/pkg/apis/manager/v1beta1"
	"github.com/kubeflow/katib/pkg/db/v1beta1/common"
	"github.com/kubeflow/katib/pkg/util/v1beta1/env"
)

const (
	dbDriver = "mysql"
	//dbNameTmpl   = "root:%s@tcp(%s:%s)/%s?timeout=5s"
	dbNameTmpl   = "%s:%s@tcp(%s:%s)/%s?timeout=5s"
	mysqlTimeFmt = "2006-01-02 15:04:05.999999"

	connectInterval = 5 * time.Second
	connectTimeout  = 60 * time.Second
)

type dbConn struct {
	db *sql.DB
}

func getDbName() string {
	dbPassEnvName := common.DBPasswordEnvName
	dbPass := os.Getenv(dbPassEnvName)
	dbUser := env.GetEnvOrDefault(
		common.DBUserEnvName, common.DefaultMySQLUser)
	dbHost := env.GetEnvOrDefault(
		common.MySQLDBHostEnvName, common.DefaultMySQLHost)
	dbPort := env.GetEnvOrDefault(
		common.MySQLDBPortEnvName, common.DefaultMySQLPort)
	dbName := env.GetEnvOrDefault(common.MySQLDatabase,
		common.DefaultMySQLDatabase)

	return fmt.Sprintf(dbNameTmpl, dbUser, dbPass, dbHost, dbPort, dbName)
}

func openSQLConn(driverName string, dataSourceName string, interval time.Duration,
	timeout time.Duration) (*sql.DB, error) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	timeoutC := time.After(timeout)
	for {
		select {
		case <-ticker.C:
			if db, err := sql.Open(driverName, dataSourceName); err == nil {
				if err = db.Ping(); err == nil {
					return db, nil
				}
				klog.Errorf("Ping to Katib db failed: %v", err)
			} else {
				klog.Errorf("Open sql connection failed: %v", err)
			}
		case <-timeoutC:
			return nil, fmt.Errorf("Timeout waiting for DB conn successfully opened.")
		}
	}
}

func NewWithSQLConn(db *sql.DB) (common.KatibDBInterface, error) {
	d := new(dbConn)
	d.db = db
	seed, err := crand.Int(crand.Reader, big.NewInt(1<<63-1))
	if err != nil {
		return nil, fmt.Errorf("RNG initialization failed: %v", err)
	}
	// We can do the following instead, but it creates a locking issue
	//d.rng = rand.New(rand.NewSource(seed.Int64()))
	rand.Seed(seed.Int64())

	return d, nil
}

func NewDBInterface() (common.KatibDBInterface, error) {
	db, err := openSQLConn(dbDriver, getDbName(), connectInterval, connectTimeout)
	if err != nil {
		return nil, fmt.Errorf("DB open failed: %v", err)
	}
	return NewWithSQLConn(db)
}

func (d *dbConn) RegisterObservationLog(trialName string, observationLog *v1beta1.ObservationLog) error {
	sqlQuery := "INSERT INTO observation_logs (trial_name, time, metric_name, value) VALUES "
	values := []interface{}{}

	for _, mlog := range observationLog.MetricLogs {
		if mlog.TimeStamp == "" {
			continue
		}
		t, err := time.Parse(time.RFC3339Nano, mlog.TimeStamp)
		if err != nil {
			return fmt.Errorf("Error parsing start time %s: %v", mlog.TimeStamp, err)
		}
		sqlTimeStr := t.UTC().Format(mysqlTimeFmt)

		sqlQuery += "(?, ?, ?, ?),"
		values = append(values, trialName, sqlTimeStr, mlog.Metric.Name, mlog.Metric.Value)
	}
	sqlQuery = sqlQuery[0 : len(sqlQuery)-1]

	// Prepare the statement
	stmt, err := d.db.Prepare(sqlQuery)
	if err != nil {
		return fmt.Errorf("Pepare SQL statement failed: %v", err)
	}

	// Execute INSERT
	_, err = stmt.Exec(values...)
	if err != nil {
		return fmt.Errorf("Execute SQL INSERT failed: %v", err)
	}

	return nil
}

func (d *dbConn) DeleteObservationLog(trialName string) error {
	_, err := d.db.Exec("DELETE FROM observation_logs WHERE trial_name = ?", trialName)
	return err
}

func (d *dbConn) GetObservationLog(trialName string, metricName string, startTime string, endTime string) (*v1beta1.ObservationLog, error) {
	qfield := []interface{}{trialName}
	qstr := ""
	if metricName != "" {
		qstr += " AND metric_name = ?"
		qfield = append(qfield, metricName)
	}
	if startTime != "" {
		s_time, err := time.Parse(time.RFC3339Nano, startTime)
		if err != nil {
			return nil, fmt.Errorf("Error parsing start time %s: %v", startTime, err)
		}
		formattedStartTime := s_time.UTC().Format(mysqlTimeFmt)
		qstr += " AND time >= ?"
		qfield = append(qfield, formattedStartTime)
	}
	if endTime != "" {
		e_time, err := time.Parse(time.RFC3339Nano, endTime)
		if err != nil {
			return nil, fmt.Errorf("Error parsing completion time %s: %v", endTime, err)
		}
		formattedEndTime := e_time.UTC().Format(mysqlTimeFmt)
		qstr += " AND time <= ?"
		qfield = append(qfield, formattedEndTime)
	}
	rows, err := d.db.Query("SELECT time, metric_name, value FROM observation_logs WHERE trial_name = ?"+qstr+" ORDER BY time",
		qfield...)
	if err != nil {
		return nil, fmt.Errorf("Failed to get ObservationLogs %v", err)
	}
	result := &v1beta1.ObservationLog{
		MetricLogs: []*v1beta1.MetricLog{},
	}
	for rows.Next() {
		var mname, mvalue, sqlTimeStr string
		err := rows.Scan(&sqlTimeStr, &mname, &mvalue)
		if err != nil {
			klog.Errorf("Error scanning log: %v", err)
			continue
		}
		ptime, err := time.Parse(mysqlTimeFmt, sqlTimeStr)
		if err != nil {
			klog.Errorf("Error parsing time %s: %v", sqlTimeStr, err)
			continue
		}
		timeStamp := ptime.UTC().Format(time.RFC3339Nano)
		result.MetricLogs = append(result.MetricLogs, &v1beta1.MetricLog{
			TimeStamp: timeStamp,
			Metric: &v1beta1.Metric{
				Name:  mname,
				Value: mvalue,
			},
		})
	}
	return result, nil
}
<commit_msg>fix: close mysql statement and rows resources when sql exec end (#1720)<commit_after>/*
Copyright 2021 The Kubeflow Authors.

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.
*/

package mysql

import (
	crand "crypto/rand"
	"database/sql"
	"fmt"
	"math/big"
	"math/rand"
	"os"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"k8s.io/klog"

	v1beta1 "github.com/kubeflow/katib/pkg/apis/manager/v1beta1"
	"github.com/kubeflow/katib/pkg/db/v1beta1/common"
	"github.com/kubeflow/katib/pkg/util/v1beta1/env"
)

const (
	dbDriver = "mysql"
	//dbNameTmpl   = "root:%s@tcp(%s:%s)/%s?timeout=5s"
	dbNameTmpl   = "%s:%s@tcp(%s:%s)/%s?timeout=5s"
	mysqlTimeFmt = "2006-01-02 15:04:05.999999"

	connectInterval = 5 * time.Second
	connectTimeout  = 60 * time.Second
)

type dbConn struct {
	db *sql.DB
}

func getDbName() string {
	dbPassEnvName := common.DBPasswordEnvName
	dbPass := os.Getenv(dbPassEnvName)
	dbUser := env.GetEnvOrDefault(
		common.DBUserEnvName, common.DefaultMySQLUser)
	dbHost := env.GetEnvOrDefault(
		common.MySQLDBHostEnvName, common.DefaultMySQLHost)
	dbPort := env.GetEnvOrDefault(
		common.MySQLDBPortEnvName, common.DefaultMySQLPort)
	dbName := env.GetEnvOrDefault(common.MySQLDatabase,
		common.DefaultMySQLDatabase)

	return fmt.Sprintf(dbNameTmpl, dbUser, dbPass, dbHost, dbPort, dbName)
}

func openSQLConn(driverName string, dataSourceName string, interval time.Duration,
	timeout time.Duration) (*sql.DB, error) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	timeoutC := time.After(timeout)
	for {
		select {
		case <-ticker.C:
			if db, err := sql.Open(driverName, dataSourceName); err == nil {
				if err = db.Ping(); err == nil {
					return db, nil
				}
				klog.Errorf("Ping to Katib db failed: %v", err)
			} else {
				klog.Errorf("Open sql connection failed: %v", err)
			}
		case <-timeoutC:
			return nil, fmt.Errorf("Timeout waiting for DB conn successfully opened.")
		}
	}
}

func NewWithSQLConn(db *sql.DB) (common.KatibDBInterface, error) {
	d := new(dbConn)
	d.db = db
	seed, err := crand.Int(crand.Reader, big.NewInt(1<<63-1))
	if err != nil {
		return nil, fmt.Errorf("RNG initialization failed: %v", err)
	}
	// We can do the following instead, but it creates a locking issue
	//d.rng = rand.New(rand.NewSource(seed.Int64()))
	rand.Seed(seed.Int64())

	return d, nil
}

func NewDBInterface() (common.KatibDBInterface, error) {
	db, err := openSQLConn(dbDriver, getDbName(), connectInterval, connectTimeout)
	if err != nil {
		return nil, fmt.Errorf("DB open failed: %v", err)
	}
	return NewWithSQLConn(db)
}

func (d *dbConn) RegisterObservationLog(trialName string, observationLog *v1beta1.ObservationLog) error {
	sqlQuery := "INSERT INTO observation_logs (trial_name, time, metric_name, value) VALUES "
	values := []interface{}{}

	for _, mlog := range observationLog.MetricLogs {
		if mlog.TimeStamp == "" {
			continue
		}
		t, err := time.Parse(time.RFC3339Nano, mlog.TimeStamp)
		if err != nil {
			return fmt.Errorf("Error parsing start time %s: %v", mlog.TimeStamp, err)
		}
		sqlTimeStr := t.UTC().Format(mysqlTimeFmt)

		sqlQuery += "(?, ?, ?, ?),"
		values = append(values, trialName, sqlTimeStr, mlog.Metric.Name, mlog.Metric.Value)
	}
	sqlQuery = sqlQuery[0 : len(sqlQuery)-1]

	// Prepare the statement
	stmt, err := d.db.Prepare(sqlQuery)
	if err != nil {
		return fmt.Errorf("Prepare SQL statement failed: %v", err)
	}

	// Close the statement
	defer stmt.Close()

	// Execute INSERT
	_, err = stmt.Exec(values...)
	if err != nil {
		return fmt.Errorf("Execute SQL INSERT failed: %v", err)
	}

	return nil
}

func (d *dbConn) DeleteObservationLog(trialName string) error {
	_, err := d.db.Exec("DELETE FROM observation_logs WHERE trial_name = ?", trialName)
	return err
}

func (d *dbConn) GetObservationLog(trialName string, metricName string, startTime string, endTime string) (*v1beta1.ObservationLog, error) {
	qfield := []interface{}{trialName}
	qstr := ""
	if metricName != "" {
		qstr += " AND metric_name = ?"
		qfield = append(qfield, metricName)
	}
	if startTime != "" {
		s_time, err := time.Parse(time.RFC3339Nano, startTime)
		if err != nil {
			return nil, fmt.Errorf("Error parsing start time %s: %v", startTime, err)
		}
		formattedStartTime := s_time.UTC().Format(mysqlTimeFmt)
		qstr += " AND time >= ?"
		qfield = append(qfield, formattedStartTime)
	}
	if endTime != "" {
		e_time, err := time.Parse(time.RFC3339Nano, endTime)
		if err != nil {
			return nil, fmt.Errorf("Error parsing completion time %s: %v", endTime, err)
		}
		formattedEndTime := e_time.UTC().Format(mysqlTimeFmt)
		qstr += " AND time <= ?"
		qfield = append(qfield, formattedEndTime)
	}
	rows, err := d.db.Query("SELECT time, metric_name, value FROM observation_logs WHERE trial_name = ?"+qstr+" ORDER BY time",
		qfield...)
	if err != nil {
		return nil, fmt.Errorf("Failed to get ObservationLogs %v", err)
	}
	// Close the rows
	defer rows.Close()
	result := &v1beta1.ObservationLog{
		MetricLogs: []*v1beta1.MetricLog{},
	}
	for rows.Next() {
		var mname, mvalue, sqlTimeStr string
		err := rows.Scan(&sqlTimeStr, &mname, &mvalue)
		if err != nil {
			klog.Errorf("Error scanning log: %v", err)
			continue
		}
		ptime, err := time.Parse(mysqlTimeFmt, sqlTimeStr)
		if err != nil {
			klog.Errorf("Error parsing time %s: %v", sqlTimeStr, err)
			continue
		}
		timeStamp := ptime.UTC().Format(time.RFC3339Nano)
		result.MetricLogs = append(result.MetricLogs, &v1beta1.MetricLog{
			TimeStamp: timeStamp,
			Metric: &v1beta1.Metric{
				Name:  mname,
				Value: mvalue,
			},
		})
	}
	return result, nil
}
<|endoftext|>


<commit_before>package alm

import (
	"errors"
	"fmt"
	"time"

	log "github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/client-go/tools/cache"

	"github.com/coreos-inc/alm/pkg/annotator"
	"github.com/coreos-inc/alm/pkg/apis/clusterserviceversion/v1alpha1"
	"github.com/coreos-inc/alm/pkg/client"
	"github.com/coreos-inc/alm/pkg/install"
	"github.com/coreos-inc/alm/pkg/queueinformer"
)

var ErrRequirementsNotMet = errors.New("requirements were not met")

const (
	FallbackWakeupInterval = 30 * time.Second
)

type ALMOperator struct {
	*queueinformer.Operator
	csvClient client.ClusterServiceVersionInterface
	resolver  install.StrategyResolverInterface
	annotator *annotator.Annotator
}

func NewALMOperator(kubeconfig string, wakeupInterval time.Duration, annotations map[string]string, namespaces []string) (*ALMOperator, error) {
	if wakeupInterval < 0 {
		wakeupInterval = FallbackWakeupInterval
	}
	if len(namespaces) < 1 {
		namespaces = []string{metav1.NamespaceAll}
	}
	csvClient, err := client.NewClusterServiceVersionClient(kubeconfig)
	if err != nil {
		return nil, err
	}
	queueOperator, err := queueinformer.NewOperator(kubeconfig)
	if err != nil {
		return nil, err
	}
	namespaceAnnotator := annotator.NewAnnotator(queueOperator.OpClient, annotations)

	op := &ALMOperator{
		queueOperator,
		csvClient,
		&install.StrategyResolver{},
		namespaceAnnotator,
	}

	// if watching all namespaces, set up a watch to annotate new namespaces
	if len(namespaces) == 1 && namespaces[0] == metav1.NamespaceAll {
		log.Debug("watching all namespaces, setting up queue")
		namespaceWatcher := cache.NewListWatchFromClient(
			queueOperator.OpClient.KubernetesInterface().CoreV1().RESTClient(),
			"namespaces",
			metav1.NamespaceAll,
			fields.Everything(),
		)
		namespaceInformer := cache.NewSharedIndexInformer(
			namespaceWatcher,
			&corev1.Namespace{},
			wakeupInterval,
			cache.Indexers{},
		)
		queueInformer := queueinformer.NewInformer(
			"namespaces",
			namespaceInformer,
			op.annotateNamespace,
			nil,
		)
		op.RegisterQueueInformer(queueInformer)
	}

	// annotate namespaces that ALM operator manages
	if err := namespaceAnnotator.AnnotateNamespaces(namespaces); err != nil {
		return nil, err
	}

	// set up watch on CSVs
	for _, namespace := range namespaces {
		log.Debugf("watching for CSVs in namespace %s", namespace)
		csvWatcher := cache.NewListWatchFromClient(
			csvClient,
			"clusterserviceversion-v1s",
			namespace,
			fields.Everything(),
		)
		csvInformer := cache.NewSharedIndexInformer(
			csvWatcher,
			&v1alpha1.ClusterServiceVersion{},
			wakeupInterval,
			cache.Indexers{},
		)
		queueInformer := queueinformer.NewInformer(
			"clusterserviceversions",
			csvInformer,
			op.syncClusterServiceVersion,
			nil,
		)
		op.RegisterQueueInformer(queueInformer)
	}

	return op, nil
}

// syncClusterServiceVersion is the method that gets called when we see a CSV event in the cluster
func (a *ALMOperator) syncClusterServiceVersion(obj interface{}) (syncError error) {
	clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion)
	if !ok {
		log.Debugf("wrong type: %#v", obj)
		return fmt.Errorf("casting ClusterServiceVersion failed")
	}

	log.Infof("syncing ClusterServiceVersion: %s", clusterServiceVersion.SelfLink)

	syncError = a.transitionCSVState(clusterServiceVersion)

	// Update CSV with status of transition. Log errors if we can't write them to the status.
	if _, err := a.csvClient.UpdateCSV(clusterServiceVersion); err != nil {
		updateErr := errors.New("error updating ClusterServiceVersion status: " + err.Error())
		if syncError == nil {
			log.Info(updateErr)
			return updateErr
		}
		syncError = fmt.Errorf("error transitioning ClusterServiceVersion: %s and error updating CSV status: %s", syncError, updateErr)
		log.Info(syncError)
	}
	return
}

// transitionCSVState moves the CSV status state machine along based on the current value and the current cluster
// state.
func (a *ALMOperator) transitionCSVState(csv *v1alpha1.ClusterServiceVersion) (syncError error) {
	switch csv.Status.Phase {
	case v1alpha1.CSVPhaseNone:
		log.Infof("scheduling ClusterServiceVersion for requirement verification: %s", csv.SelfLink)
		csv.SetPhase(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsUnknown, "requirements not yet checked")
	case v1alpha1.CSVPhasePending:
		met, statuses := a.requirementStatus(csv)

		if !met {
			log.Info("requirements were not met")
			csv.SetPhase(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsNotMet, "one or more requirements couldn't be found")
			csv.SetRequirementStatus(statuses)
			syncError = ErrRequirementsNotMet
			return
		}

		log.Infof("scheduling ClusterServiceVersion for install: %s", csv.SelfLink)
		csv.SetPhase(v1alpha1.CSVPhaseInstalling, v1alpha1.CSVReasonRequirementsMet, "all requirements found, attempting install")
		csv.SetRequirementStatus(statuses)
	case v1alpha1.CSVPhaseInstalling:
		strategy, err := a.resolver.UnmarshalStrategy(csv.Spec.InstallStrategy)
		if err != nil {
			csv.SetPhase(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonInvalidStrategy, fmt.Sprintf("install strategy invalid: %s", err))
			return
		}
		installer := a.resolver.InstallerForStrategy(strategy.GetStrategyName(), a.OpClient, csv.ObjectMeta)
		installed, err := installer.CheckInstalled(strategy)
		if err != nil {
			// TODO: add a retry count, don't give up on first failure
			csv.SetPhase(v1alpha1.CSVPhaseUnknown, v1alpha1.CSVReasonInstallCheckFailed, fmt.Sprintf("install check failed: %s", err))
			return
		}
		if installed {
			log.Infof(
				"%s install strategy successful for %s",
				csv.Spec.InstallStrategy.StrategyName,
				csv.SelfLink,
			)
			csv.SetPhase(v1alpha1.CSVPhaseSucceeded, v1alpha1.CSVReasonInstallSuccessful, "install strategy completed with no errors")
			return
		}
		// We transition to ComponentFailed if install failed, but we don't transition to succeeded here. Instead we let
		// this queue pick the object back up, and transition to Succeeded once we verify the install
		// with the install strategy's `CheckInstall`
		syncError = installer.Install(strategy)
		if syncError != nil {
			csv.SetPhase(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonComponentFailed, fmt.Sprintf("install strategy failed: %s", err))
			return
		} else {
			csv.SetPhase(v1alpha1.CSVPhaseSucceeded, v1alpha1.CSVReasonInstallSuccessful, "install strategy completed with no errors")
			return
		}
	case v1alpha1.CSVPhaseSucceeded:
		strategy, err := a.resolver.UnmarshalStrategy(csv.Spec.InstallStrategy)
		if err != nil {
			csv.SetPhase(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonInvalidStrategy, fmt.Sprintf("install strategy invalid: %s", err))
			return
		}
		installer := a.resolver.InstallerForStrategy(strategy.GetStrategyName(), a.OpClient, csv.ObjectMeta)
		installed, err := installer.CheckInstalled(strategy)

		// if already installed, don't transition to pending if we can't query
		if err != nil {
			csv.SetPhase(v1alpha1.CSVPhaseSucceeded, v1alpha1.CSVReasonInstallCheckFailed, fmt.Sprintf("install check failed: %s", err))
			return
		}
		// transition back to pending
		if !installed {
			csv.SetPhase(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonComponentUnhealthy, "component unhealthy, rechecking and re-installing")
			return
		}
	}
	return
}

func (a *ALMOperator) requirementStatus(csv *v1alpha1.ClusterServiceVersion) (met bool, statuses []v1alpha1.RequirementStatus) {
	met = true
	for _, r := range csv.GetAllCRDDescriptions() {
		status := v1alpha1.RequirementStatus{
			Group:   "apiextensions.k8s.io",
			Version: "v1beta1",
			Kind:    "CustomResourceDefinition",
			Name:    r.Name,
		}
		crd, err := a.OpClient.GetCustomResourceDefinitionKind(r.Name)
		if err != nil {
			status.Status = "NotPresent"
			met = false
		} else {
			status.Status = "Present"
			status.UUID = string(crd.GetUID())
		}
		statuses = append(statuses, status)
	}
	return
}

// annotateNamespace is the method that gets called when we see a namespace event in the cluster
func (a *ALMOperator) annotateNamespace(obj interface{}) (syncError error) {
	namespace, ok := obj.(*corev1.Namespace)
	if !ok {
		log.Debugf("wrong type: %#v", obj)
		return fmt.Errorf("casting Namespace failed")
	}

	log.Infof("syncing Namespace: %s", namespace.GetName())
	if err := a.annotator.AnnotateNamespace(namespace); err != nil {
		log.Infof("error annotating namespace '%s'", namespace.GetName())
		return err
	}
	return nil
}
<commit_msg>fix(pkg/operators/alm/operator): put correct error in status on install failure<commit_after>package alm

import (
	"errors"
	"fmt"
	"time"

	log "github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/client-go/tools/cache"

	"github.com/coreos-inc/alm/pkg/annotator"
	"github.com/coreos-inc/alm/pkg/apis/clusterserviceversion/v1alpha1"
	"github.com/coreos-inc/alm/pkg/client"
	"github.com/coreos-inc/alm/pkg/install"
	"github.com/coreos-inc/alm/pkg/queueinformer"
)

var ErrRequirementsNotMet = errors.New("requirements were not met")

const (
	FallbackWakeupInterval = 30 * time.Second
)

type ALMOperator struct {
	*queueinformer.Operator
	csvClient client.ClusterServiceVersionInterface
	resolver  install.StrategyResolverInterface
	annotator *annotator.Annotator
}

func NewALMOperator(kubeconfig string, wakeupInterval time.Duration, annotations map[string]string, namespaces []string) (*ALMOperator, error) {
	if wakeupInterval < 0 {
		wakeupInterval = FallbackWakeupInterval
	}
	if len(namespaces) < 1 {
		namespaces = []string{metav1.NamespaceAll}
	}
	csvClient, err := client.NewClusterServiceVersionClient(kubeconfig)
	if err != nil {
		return nil, err
	}
	queueOperator, err := queueinformer.NewOperator(kubeconfig)
	if err != nil {
		return nil, err
	}
	namespaceAnnotator := annotator.NewAnnotator(queueOperator.OpClient, annotations)

	op := &ALMOperator{
		queueOperator,
		csvClient,
		&install.StrategyResolver{},
		namespaceAnnotator,
	}

	// if watching all namespaces, set up a watch to annotate new namespaces
	if len(namespaces) == 1 && namespaces[0] == metav1.NamespaceAll {
		log.Debug("watching all namespaces, setting up queue")
		namespaceWatcher := cache.NewListWatchFromClient(
			queueOperator.OpClient.KubernetesInterface().CoreV1().RESTClient(),
			"namespaces",
			metav1.NamespaceAll,
			fields.Everything(),
		)
		namespaceInformer := cache.NewSharedIndexInformer(
			namespaceWatcher,
			&corev1.Namespace{},
			wakeupInterval,
			cache.Indexers{},
		)
		queueInformer := queueinformer.NewInformer(
			"namespaces",
			namespaceInformer,
			op.annotateNamespace,
			nil,
		)
		op.RegisterQueueInformer(queueInformer)
	}

	// annotate namespaces that ALM operator manages
	if err := namespaceAnnotator.AnnotateNamespaces(namespaces); err != nil {
		return nil, err
	}

	// set up watch on CSVs
	for _, namespace := range namespaces {
		log.Debugf("watching for CSVs in namespace %s", namespace)
		csvWatcher := cache.NewListWatchFromClient(
			csvClient,
			"clusterserviceversion-v1s",
			namespace,
			fields.Everything(),
		)
		csvInformer := cache.NewSharedIndexInformer(
			csvWatcher,
			&v1alpha1.ClusterServiceVersion{},
			wakeupInterval,
			cache.Indexers{},
		)
		queueInformer := queueinformer.NewInformer(
			"clusterserviceversions",
			csvInformer,
			op.syncClusterServiceVersion,
			nil,
		)
		op.RegisterQueueInformer(queueInformer)
	}

	return op, nil
}

// syncClusterServiceVersion is the method that gets called when we see a CSV event in the cluster
func (a *ALMOperator) syncClusterServiceVersion(obj interface{}) (syncError error) {
	clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion)
	if !ok {
		log.Debugf("wrong type: %#v", obj)
		return fmt.Errorf("casting ClusterServiceVersion failed")
	}

	log.Infof("syncing ClusterServiceVersion: %s", clusterServiceVersion.SelfLink)

	syncError = a.transitionCSVState(clusterServiceVersion)

	// Update CSV with status of transition. Log errors if we can't write them to the status.
	if _, err := a.csvClient.UpdateCSV(clusterServiceVersion); err != nil {
		updateErr := errors.New("error updating ClusterServiceVersion status: " + err.Error())
		if syncError == nil {
			log.Info(updateErr)
			return updateErr
		}
		syncError = fmt.Errorf("error transitioning ClusterServiceVersion: %s and error updating CSV status: %s", syncError, updateErr)
		log.Info(syncError)
	}
	return
}

// transitionCSVState moves the CSV status state machine along based on the current value and the current cluster
// state.
func (a *ALMOperator) transitionCSVState(csv *v1alpha1.ClusterServiceVersion) (syncError error) {
	switch csv.Status.Phase {
	case v1alpha1.CSVPhaseNone:
		log.Infof("scheduling ClusterServiceVersion for requirement verification: %s", csv.SelfLink)
		csv.SetPhase(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsUnknown, "requirements not yet checked")
	case v1alpha1.CSVPhasePending:
		met, statuses := a.requirementStatus(csv)

		if !met {
			log.Info("requirements were not met")
			csv.SetPhase(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonRequirementsNotMet, "one or more requirements couldn't be found")
			csv.SetRequirementStatus(statuses)
			syncError = ErrRequirementsNotMet
			return
		}

		log.Infof("scheduling ClusterServiceVersion for install: %s", csv.SelfLink)
		csv.SetPhase(v1alpha1.CSVPhaseInstalling, v1alpha1.CSVReasonRequirementsMet, "all requirements found, attempting install")
		csv.SetRequirementStatus(statuses)
	case v1alpha1.CSVPhaseInstalling:
		strategy, err := a.resolver.UnmarshalStrategy(csv.Spec.InstallStrategy)
		if err != nil {
			csv.SetPhase(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonInvalidStrategy, fmt.Sprintf("install strategy invalid: %s", err))
			return
		}
		installer := a.resolver.InstallerForStrategy(strategy.GetStrategyName(), a.OpClient, csv.ObjectMeta)
		installed, err := installer.CheckInstalled(strategy)
		if err != nil {
			// TODO: add a retry count, don't give up on first failure
			csv.SetPhase(v1alpha1.CSVPhaseUnknown, v1alpha1.CSVReasonInstallCheckFailed, fmt.Sprintf("install check failed: %s", err))
			return
		}
		if installed {
			log.Infof(
				"%s install strategy successful for %s",
				csv.Spec.InstallStrategy.StrategyName,
				csv.SelfLink,
			)
			csv.SetPhase(v1alpha1.CSVPhaseSucceeded, v1alpha1.CSVReasonInstallSuccessful, "install strategy completed with no errors")
			return
		}
		// We transition to ComponentFailed if install failed, but we don't transition to succeeded here. Instead we let
		// this queue pick the object back up, and transition to Succeeded once we verify the install
		// with the install strategy's `CheckInstall`
		syncError = installer.Install(strategy)
		if syncError != nil {
			csv.SetPhase(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonComponentFailed, fmt.Sprintf("install strategy failed: %s", syncError))
			return
		} else {
			csv.SetPhase(v1alpha1.CSVPhaseSucceeded, v1alpha1.CSVReasonInstallSuccessful, "install strategy completed with no errors")
			return
		}
	case v1alpha1.CSVPhaseSucceeded:
		strategy, err := a.resolver.UnmarshalStrategy(csv.Spec.InstallStrategy)
		if err != nil {
			csv.SetPhase(v1alpha1.CSVPhaseFailed, v1alpha1.CSVReasonInvalidStrategy, fmt.Sprintf("install strategy invalid: %s", err))
			return
		}
		installer := a.resolver.InstallerForStrategy(strategy.GetStrategyName(), a.OpClient, csv.ObjectMeta)
		installed, err := installer.CheckInstalled(strategy)

		// if already installed, don't transition to pending if we can't query
		if err != nil {
			csv.SetPhase(v1alpha1.CSVPhaseSucceeded, v1alpha1.CSVReasonInstallCheckFailed, fmt.Sprintf("install check failed: %s", err))
			return
		}
		// transition back to pending
		if !installed {
			csv.SetPhase(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonComponentUnhealthy, "component unhealthy, rechecking and re-installing")
			return
		}
	}
	return
}

func (a *ALMOperator) requirementStatus(csv *v1alpha1.ClusterServiceVersion) (met bool, statuses []v1alpha1.RequirementStatus) {
	met = true
	for _, r := range csv.GetAllCRDDescriptions() {
		status := v1alpha1.RequirementStatus{
			Group:   "apiextensions.k8s.io",
			Version: "v1beta1",
			Kind:    "CustomResourceDefinition",
			Name:    r.Name,
		}
		crd, err := a.OpClient.GetCustomResourceDefinitionKind(r.Name)
		if err != nil {
			status.Status = "NotPresent"
			met = false
		} else {
			status.Status = "Present"
			status.UUID = string(crd.GetUID())
		}
		statuses = append(statuses, status)
	}
	return
}

// annotateNamespace is the method that gets called when we see a namespace event in the cluster
func (a *ALMOperator) annotateNamespace(obj interface{}) (syncError error) {
	namespace, ok := obj.(*corev1.Namespace)
	if !ok {
		log.Debugf("wrong type: %#v", obj)
		return fmt.Errorf("casting Namespace failed")
	}

	log.Infof("syncing Namespace: %s", namespace.GetName())
	if err := a.annotator.AnnotateNamespace(namespace); err != nil {
		log.Infof("error annotating namespace '%s'", namespace.GetName())
		return err
	}
	return nil
}
<|endoftext|>