Commit cf7fe401 authored by M. Rehan Abbasi's avatar M. Rehan Abbasi
Browse files

implement basic logic for meep-vis-traffic-mgr package

parent 1eecdf2c
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
module github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-vis-traffic-mgr

go 1.16

require github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger v0.0.0

replace github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger => ../../go-packages/meep-logger
+13 −0
Original line number Diff line number Diff line
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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+426 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2022  InterDigital Communications, Inc
 *
 * 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 vistrafficmgr

import (
	"database/sql"
	"errors"
	"strings"
	"time"

	log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"
)

// DB Config
const (
	DbHost              = "meep-postgis.default.svc.cluster.local"
	DbPort              = "5432"
	DbUser              = ""
	DbPassword          = ""
	DbDefault           = "postgres"
	DbMaxRetryCount int = 2
)

// Enable profiling
const profiling = false

var profilingTimers map[string]time.Time

const (
	FieldTime1 = "time1"
	FieldTime2 = "time2"
	FieldTime3 = "time3"
	FieldTime4 = "time4"
	// FieldPath      = "path"
	// FieldMode      = "mode"
	// FieldVelocity  = "velocity"
	// FieldConnected = "connected"
	// FieldPriority  = "priority"
	// FieldSubtype   = "subtype"
	// FieldRadius    = "radius"
)

// DB Table Names
const (
	TrafficTable = "traffic_patterns"
)

// Asset Types
const (
	TypePoa = "POA"
)

// POA Types
// const (
// 	PoaTypeGeneric      = "POA"
// 	PoaTypeCell4g       = "POA-4G"
// 	PoaTypeCell5g       = "POA-5G"
// 	PoaTypeWifi         = "POA-WIFI"
// 	PoaTypeDisconnected = "DISCONNECTED"
// )

// VIS Traffic Manager
type TrafficMgr struct {
	name      string
	namespace string
	user      string
	pwd       string
	host      string
	port      string
	dbName    string
	db        *sql.DB
	connected bool
	// updateCb  func(string, string)
}

type PoaTimes struct {
	Id      string
	PoaName string
	Time1   int32
	Time2   int32
	Time3   int32
	Time4   int32
}

// Profiling init
func init() {
	if profiling {
		profilingTimers = make(map[string]time.Time)
	}
}

// NewTrafficMgr - Creates and initializes a new VIS Traffic Manager
func NewTrafficMgr(name, namespace, user, pwd, host, port string) (tm *TrafficMgr, err error) {
	if name == "" {
		err = errors.New("Missing connector name")
		return nil, err
	}

	// Create new Traffic Manager
	tm = new(TrafficMgr)
	tm.name = name
	if namespace != "" {
		tm.namespace = namespace
	} else {
		tm.namespace = "default"
	}
	tm.user = user
	tm.pwd = pwd
	tm.host = host
	tm.port = port

	// Connect to Postgis DB
	for retry := 0; retry <= DbMaxRetryCount; retry++ {
		tm.db, err = tm.connectDB("", tm.user, tm.pwd, tm.host, tm.port)
		if err == nil {
			break
		}
	}
	if err != nil {
		log.Error("Failed to connect to postgis DB with err: ", err.Error())
		return nil, err
	}
	defer tm.db.Close()

	// Create sandbox DB if it does not exist
	// Use format: '<namespace>_<name>' & replace dashes with underscores
	tm.dbName = strings.ToLower(strings.Replace(namespace+"_"+name, "-", "_", -1))

	// Ignore DB creation error in case it already exists.
	// Failure will occur at DB connection if DB was not successfully created.
	_ = tm.CreateDb(tm.dbName)

	// Close connection to postgis DB
	_ = tm.db.Close()

	// Connect with sandbox-specific DB
	tm.db, err = tm.connectDB(tm.dbName, user, pwd, host, port)
	if err != nil {
		log.Error("Failed to connect to sandbox DB with err: ", err.Error())
		return nil, err
	}

	log.Info("Postgis Connector successfully created")
	tm.connected = true
	return tm, nil
}

func (tm *TrafficMgr) connectDB(dbName, user, pwd, host, port string) (db *sql.DB, err error) {
	// Set default values if none provided
	if dbName == "" {
		dbName = DbDefault
	}
	if host == "" {
		host = DbHost
	}
	if port == "" {
		port = DbPort
	}
	log.Debug("Connecting to Postgis DB [", dbName, "] at addr [", host, ":", port, "]")

	// Open postgis DB
	connStr := "user=" + user + " password=" + pwd + " dbname=" + dbName + " host=" + host + " port=" + port + " sslmode=disable"
	db, err = sql.Open("postgres", connStr)
	if err != nil {
		log.Warn("Failed to connect to Postgis DB with error: ", err.Error())
		return nil, err
	}

	// Make sure connection is up
	err = db.Ping()
	if err != nil {
		log.Warn("Failed to ping Postgis DB with error: ", err.Error())
		db.Close()
		return nil, err
	}

	log.Info("Connected to Postgis DB [", dbName, "]")
	return db, nil
}

// func (tm *TrafficMgr) SetListener(listener func(string, string)) error {
// 	tm.updateCb = listener
// 	return nil
// }

// func (tm *TrafficMgr) notifyListener(cbType string, assetName string) {
// 	if tm.updateCb != nil {
// 		go tm.updateCb(cbType, assetName)
// 	}
// }

// DeleteTrafficMgr -
func (tm *TrafficMgr) DeleteTrafficMgr() (err error) {

	if tm.db == nil {
		err = errors.New("Traffic Manager database not initialized")
		log.Error(err.Error())
		return err
	}

	// Close connection to sandbox-specific DB
	_ = tm.db.Close()

	// Connect to Postgis DB
	tm.db, err = tm.connectDB("", tm.user, tm.pwd, tm.host, tm.port)
	if err != nil {
		log.Error("Failed to connect to postgis DB with err: ", err.Error())
		return err
	}
	defer tm.db.Close()

	// Destroy sandbox database
	_ = tm.DestroyDb(tm.dbName)

	return nil
}

// CreateDb -- Create new DB with provided name
func (tm *TrafficMgr) CreateDb(name string) (err error) {
	_, err = tm.db.Exec("CREATE DATABASE " + name)
	if err != nil {
		log.Error(err.Error())
		return err
	}

	log.Info("Created database: " + name)
	return nil
}

// DestroyDb -- Destroy DB with provided name
func (tm *TrafficMgr) DestroyDb(name string) (err error) {
	_, err = tm.db.Exec("DROP DATABASE " + name)
	if err != nil {
		log.Error(err.Error())
		return err
	}

	log.Info("Destroyed database: " + name)
	return nil
}

func (tm *TrafficMgr) CreateTable() (err error) {
	_, err = tm.db.Exec("CREATE EXTENSION IF NOT EXISTS postgis")
	if err != nil {
		log.Error(err.Error())
		return err
	}

	_, err = tm.db.Exec(`CREATE TABLE ` + TrafficTable + ` (
		id              varchar(36)             NOT NULL,
		poaName				  varchar(100)						NOT NULL UNIQUE,
		time1						integer									NOT NULL DEFAULT '0',
		time2						integer									NOT NULL DEFAULT '0',
		time3						integer									NOT NULL DEFAULT '0',
		time4						integer									NOT NULL DEFAULT '0',
		PRIMARY KEY (id)
	)`)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Created Traffic table: ", TrafficTable)

	return nil
}

// DeleteTables - Delete all postgis traffic tables
func (tm *TrafficMgr) DeleteTables() (err error) {
	err = tm.DeleteTable(TrafficTable)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	return nil
}

// DeleteTable - Delete postgis table with provided name
func (tm *TrafficMgr) DeleteTable(tableName string) (err error) {
	_, err = tm.db.Exec("DROP TABLE IF EXISTS " + tableName)
	if err != nil {
		log.Error(err.Error())
		return err
	}
	log.Info("Deleted table: " + tableName)
	return nil
}

// CreatePoaLoad - Create new POA
func (tm *TrafficMgr) CreatePoaLoad(id string, poaName string, data map[string]interface{}) (err error) {
	if profiling {
		profilingTimers["CreatePoaLoad"] = time.Now()
	}

	var time1 int32
	var time2 int32
	var time3 int32
	var time4 int32
	var ok bool

	// Validate input
	if id == "" {
		return errors.New("Missing ID")
	}
	if poaName == "" {
		return errors.New("Missing POA Name")
	}

	// Get time1
	if dataTime1, found := data[FieldTime1]; !found {
		return errors.New("Missing subtype")
	} else if time1, ok = dataTime1.(int32); !ok {
		return errors.New("Invalid subtype data type")
	} else if time1 == 0 {
		return errors.New("Invalid time1")
	}

	// Get time2
	if dataTime2, found := data[FieldTime2]; !found {
		return errors.New("Missing subtype")
	} else if time2, ok = dataTime2.(int32); !ok {
		return errors.New("Invalid subtype data type")
	} else if time2 == 0 {
		return errors.New("Invalid time2")
	}

	// Get time3
	if dataTime3, found := data[FieldTime3]; !found {
		return errors.New("Missing subtype")
	} else if time3, ok = dataTime3.(int32); !ok {
		return errors.New("Invalid subtype data type")
	} else if time3 == 0 {
		return errors.New("Invalid time3")
	}

	// Get time4
	if dataTime4, found := data[FieldTime4]; !found {
		return errors.New("Missing subtype")
	} else if time4, ok = dataTime4.(int32); !ok {
		return errors.New("Invalid subtype data type")
	} else if time4 == 0 {
		return errors.New("Invalid time4")
	}

	// Create Traffic Load entry
	query := `INSERT INTO ` + TrafficTable + ` (id, poaName, time1, time2, time3, time4)
		VALUES ($1, $2, $3, $4, $5, $6)`
	_, err = tm.db.Exec(query, id, poaName, time1, time2, time3, time4)
	if err != nil {
		log.Error(err.Error())
		return err
	}

	// Notify listener
	// tm.notifyListener(TypePoa, name)

	if profiling {
		now := time.Now()
		log.Debug("CreatePoaLoad: ", now.Sub(profilingTimers["CreatePoaLoad"]))
	}
	return nil
}

// GetPoaLoad - Get POA Load information
func (tm *TrafficMgr) GetPoaLoad(poaName string) (poaTimes *PoaTimes, err error) {
	if profiling {
		profilingTimers["GetPoaLoad"] = time.Now()
	}

	// Validate input
	if poaName == "" {
		err = errors.New("Missing POA Name")
		return nil, err
	}

	// Get Poa entry
	var rows *sql.Rows
	rows, err = tm.db.Query(`
		SELECT id, name, time1, time2, time3, time4
		FROM `+TrafficTable+`
		WHERE name = ($1)`, poaName)
	if err != nil {
		log.Error(err.Error())
		return nil, err
	}
	defer rows.Close()

	// Scan result
	for rows.Next() {
		poaTimes = new(PoaTimes)
		err = rows.Scan(&poaTimes.Id, &poaTimes.PoaName, &poaTimes.Time1, &poaTimes.Time2, &poaTimes.Time3, &poaTimes.Time4)
		if err != nil {
			log.Error(err.Error())
			return nil, err
		}
	}
	err = rows.Err()
	if err != nil {
		log.Error(err)
	}

	// Return error if not found
	if poaTimes == nil {
		err = errors.New("POA not found: " + poaName)
		return nil, err
	}

	if profiling {
		now := time.Now()
		log.Debug("GetPoaLoad: ", now.Sub(profilingTimers["GetPoaLoad"]))
	}
	return poaTimes, nil
}