Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
client.go 6.97 KiB
package ciena

import (
	"bytes"
	"code.fbi.h-da.de/cocsn/gosdn/database"
	"code.fbi.h-da.de/cocsn/gosdn/log"
	"code.fbi.h-da.de/cocsn/gosdn/nucleus/interfaces"
	"code.fbi.h-da.de/cocsn/gosdn/sbi/restconf/util"
	apiclient "code.fbi.h-da.de/cocsn/swagger/apis/mcp/client"
	"code.fbi.h-da.de/cocsn/yang-modules/generated/tapi"
	"crypto/tls"
	"encoding/json"
	"github.com/go-openapi/runtime"
	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
	"github.com/openconfig/ygot/ygot"
	"github.com/tidwall/gjson"
	"net/http"
	"strings"
)

//MCPClient handles requests to a Ciena MCP RESTCONF endpoint
type MCPClient struct {
	transport *httptransport.Runtime
	client    *apiclient.ServiceTopologyTAPI
	database  *database.Database
	buffer    *bytes.Buffer
	config    *interfaces.ClientConfig
	device    ygot.GoStruct
}

// GetConfig returns a ClientConfig struct containing
// the current configuration stat of the Ciena SBI client
func (c MCPClient) GetConfig() interfaces.ClientConfig {
	return *c.config
}

//NewMCPClient creates a Ciena flavores TAPI client
func NewMCPClient(endpoint, username, password string, database *database.Database, config *interfaces.ClientConfig) *MCPClient {
	// create the transport
	transport := httptransport.New(endpoint, "/", nil)
	transport.Transport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	// create the API client, with the transport
	basicAuth := httptransport.BasicAuth(username, password)
	// authenticate client
	transport.DefaultAuthentication = basicAuth
	client := apiclient.New(transport, strfmt.Default)

	buffer := new(bytes.Buffer)
	transport.Consumers[runtime.JSONMime] = util.YANGConsumer{Data: buffer}

	return &MCPClient{
		transport: transport,
		client:    client,
		database:  database,
		buffer:    buffer,
		config:    config,
		device:    &tapi.Device{},
	}
}

// GetConnections implements the TAPI Connectivity GetConnections call with a grain of
// Ciena salt. The response is written to the client's buffer and passed to the database
func (c *MCPClient) GetConnections() error {
	defer c.buffer.Reset()
	_, err := c.client.TapiConnectivityCore.GetTapiCoreContextConnection(nil)
	if err != nil {
		return err
	}
	json := preformatJSON(c.buffer.String(), c.config.GjsonConnectionsPath)

	for _, jsonEntry := range json.Array() {
		dest := &tapi.TapiCommon_Context_ConnectivityContext_Connection{}
		if err := tapi.Unmarshal([]byte(jsonEntry.String()), dest); err != nil {
			//TODO: think about a way how to handle this.
			//logging every error is kinda ugly, since ciena tapi throws a
			//lot of them.
			log.Info(err)
		}
		log.Info(*dest.Uuid)
	}

	// c.database.StoreConnections(c.buffer.String())
	// log.Debug(c.buffer.Next(25))
	return err
}

// GetLinks implements the TAPI Topology GetLinks call with a grain of
// Ciena salt. The response is written to the client's buffer and passed to the database
func (c *MCPClient) GetLinks() error {
	defer c.buffer.Reset()
	_, err := c.client.TapiTopologyCore.GetTapiCoreContextTopologyMcpBaseTopologyLink(nil)
	if err != nil {
		return err
	}

	json := preformatJSON(c.buffer.String(), c.config.GjsonDefaultPath)

	for _, jsonEntry := range json.Array() {
		dest := &tapi.TapiCommon_Context_TopologyContext_Topology_Link{}
		if err := tapi.Unmarshal([]byte(jsonEntry.String()), dest); err != nil {
			//TODO: think about a way how to handle this.
			//logging every error is kinda ugly, since ciena tapi throws a
			//lot of them.
			log.Info(err)
		}
		log.Info(*dest.Uuid)
	}

	// c.database.StoreLinks(c.buffer.String())
	// log.Debug(c.buffer.Next(25))
	return err
}

// GetNodes implements the TAPI Topology GetNodes call with a grain of
// Ciena salt. The response is written to the client's buffer and passed to the database
func (c *MCPClient) GetNodes() error {
	defer c.buffer.Reset()
	_, err := c.client.TapiTopologyCore.GetTapiCoreContextTopologyMcpBaseTopologyNode(nil)
	if err != nil {
		return err
	}

	json := preformatJSON(c.buffer.String(), c.config.GjsonDefaultPath)

	for _, jsonEntry := range json.Array() {
		dest := &tapi.TapiCommon_Context_TopologyContext_Topology_Node{}
		if err := tapi.Unmarshal([]byte(jsonEntry.String()), dest); err != nil {
			//TODO: think about a way how to handle this.
			//logging every error is kinda ugly, since ciena tapi throws a
			//lot of them.
			log.Info(err)
		}
		log.Info(*dest.Uuid)
	}

	// c.database.StoreNodes(c.buffer.String())
	// log.Debug(c.buffer.Next(25))
	return err
}

// GetNodeEdgePoints implements the TAPI Topology GetNodeEdgePoints call with a grain of
// Ciena salt. The response is written to the client's buffer and passed to the database
func (c *MCPClient) GetNodeEdgePoints() error {
	defer c.buffer.Reset()
	_, err := c.client.TapiTopologyCore.GetTapiCoreContextTopologyMcpBaseTopologyNodeEdgePoint(nil)
	if err != nil {
		return err
	}
	//TODO: there is no tapi ygot struct that fits the ciena node-edge-point
	dest := &tapi.TapiCommon_Context_TopologyContext_Topology_Link_NodeEdgePoint{}
	if err := tapi.Unmarshal(c.buffer.Bytes(), dest); err != nil {
		return err
	}
	c.database.StoreNodeEdgePoints(c.buffer.String())
	log.Debug(c.buffer.Next(25))
	return err
}

//preformatJSON preformats the recieved JSON for further processing
func preformatJSON(jsn string, path string) *gjson.Result {
	//TODO: move this!
	modifierName := "uppercase"
	gjson.AddModifier(modifierName, func(jsonString, arg string) string {
		var jsonMap interface{}
		err := json.Unmarshal([]byte(jsonString), &jsonMap)
		if err != nil {
			log.Info("failed unmarshal for JSON")
		}

		jsonMap = uppercaseJSONValues(jsonMap)
		result, err := json.Marshal(jsonMap)
		if err != nil {
			log.Info("failed marshal for JSON")
		}
		return string(result)
	})

	formattedJSON := gjson.Get(jsn, path+"|@"+modifierName)
	return &formattedJSON
}

//uppercaseJSONValues takes a interface{} created with json.Unmarshal() and changes the containing
//string values to uppercase
//returns a interface{} which can be changed back into a JSON string via
//json.Marshal()
func uppercaseJSONValues(jsonMap interface{}) interface{} {
	switch jsonMap := jsonMap.(type) {
	//check if []interface{} and go through every entry recursively
	case []interface{}:
		for i := range jsonMap {
			jsonMap[i] = uppercaseJSONValues(jsonMap[i])
		}
		return jsonMap
	//check if map[string]interface, handle ciena tapi json specific fixes
	//and go on recursively
	case map[string]interface{}:
		tmpInterfaceMap := make(map[string]interface{}, len(jsonMap))
		for k, v := range jsonMap {
			//TODO: maybe we can uppercase them too, but for now:
			//DO NOT uppercase uuid's
			if strings.Contains(k, "uuid") {
				tmpInterfaceMap[k] = v
			} else {
				tmpInterfaceMap[k] = uppercaseJSONValues(v)
			}
		}
		return tmpInterfaceMap
	//ygot: requires enums in uppercase and since CIENA TAPI does sometimes
	//provide faulty JSON values we need to uppercase them before we process
	//them further
	case string:
		return strings.ToUpper(jsonMap)
	//default: do nothing (like for bool or other stuff)
	default:
		return jsonMap
	}
}