SEBA-258 - Added MONGODB backup

Change-Id: I2542e7dfc894dfa5a1269c6be99cfa8036b9911c
diff --git a/api/handler.go b/api/handler.go
index ce5c7a1..bd9d2f0 100644
--- a/api/handler.go
+++ b/api/handler.go
@@ -25,11 +25,15 @@
 	"net/http"
 	"os"
 	"strings"
+	"sync"
 
 	"gerrit.opencord.org/abstract-olt/internal/pkg/settings"
 	"gerrit.opencord.org/abstract-olt/models"
 	"gerrit.opencord.org/abstract-olt/models/abstract"
 	"gerrit.opencord.org/abstract-olt/models/physical"
+	"github.com/mongodb/mongo-go-driver/bson"
+	"github.com/mongodb/mongo-go-driver/mongo"
+	"github.com/mongodb/mongo-go-driver/mongo/updateopt"
 	context "golang.org/x/net/context"
 )
 
@@ -39,10 +43,30 @@
 type Server struct {
 }
 
+var syncChan chan bool
+var isDirty bool
+
+var runOnce sync.Once
+
+func getSyncChannel() chan bool {
+	runOnce.Do(func() {
+		syncChan = make(chan bool, 1)
+		syncChan <- true
+	})
+
+	return syncChan
+}
+func done(myChan chan bool, done bool) {
+	myChan <- done
+}
+
 /*
 Echo - Tester function which just returns same string sent to it
 */
 func (s *Server) Echo(ctx context.Context, in *EchoMessage) (*EchoReplyMessage, error) {
+	myChan := getSyncChannel()
+	<-myChan
+	defer done(myChan, true)
 	fmt.Println("HELLO WTF")
 	ping := in.GetPing()
 	pong := EchoReplyMessage{Pong: ping}
@@ -53,6 +77,10 @@
 CreateChassis - allocates a new Chassis struct and stores it in chassisMap
 */
 func (s *Server) CreateChassis(ctx context.Context, in *AddChassisMessage) (*AddChassisReturn, error) {
+	myChan := getSyncChannel()
+	fmt.Println("after getSyncChannel")
+	<-myChan
+	defer done(myChan, true)
 	chassisMap := models.GetChassisMap()
 	clli := in.GetCLLI()
 
@@ -92,6 +120,7 @@
 		log.Printf("new chassis %s\n", formatted)
 	}
 	(*chassisMap)[clli] = chassisHolder
+	isDirty = true
 	return &AddChassisReturn{DeviceID: clli}, nil
 }
 
@@ -99,6 +128,9 @@
 ChangeXOSUserPassword - allows update of xos credentials
 */
 func (s *Server) ChangeXOSUserPassword(ctx context.Context, in *ChangeXOSUserPasswordMessage) (*ChangeXOSUserPasswordReturn, error) {
+	myChan := getSyncChannel()
+	<-myChan
+	defer done(myChan, true)
 	clli := in.GetCLLI()
 	xosUser := in.GetXOSUser()
 	xosPassword := in.GetXOSPassword()
@@ -120,6 +152,7 @@
 
 	chassisHolder.PhysicalChassis.XOSUser = xosUser
 	chassisHolder.PhysicalChassis.XOSPassword = xosPassword
+	isDirty = true
 	return &ChangeXOSUserPasswordReturn{Success: true}, nil
 
 }
@@ -128,6 +161,9 @@
 CreateOLTChassis adds an OLT chassis/line card to the Physical chassis
 */
 func (s *Server) CreateOLTChassis(ctx context.Context, in *AddOLTChassisMessage) (*AddOLTChassisReturn, error) {
+	myChan := getSyncChannel()
+	<-myChan
+	defer done(myChan, true)
 	fmt.Printf(" CreateOLTChassis %v \n", *in)
 	chassisMap := models.GetChassisMap()
 	clli := in.GetCLLI()
@@ -154,6 +190,7 @@
 		absPort.PhysPort = &ports[i]
 		//AssignTraits(&ports[i], absPort)
 	}
+	isDirty = true
 	return &AddOLTChassisReturn{DeviceID: in.GetHostname(), ChassisDeviceID: clli}, nil
 
 }
@@ -162,6 +199,9 @@
 ProvisionOnt provisions an ONT on a specific Chassis/LineCard/Port
 */
 func (s *Server) ProvisionOnt(ctx context.Context, in *AddOntMessage) (*AddOntReturn, error) {
+	myChan := getSyncChannel()
+	<-myChan
+	defer done(myChan, true)
 	chassisMap := models.GetChassisMap()
 	clli := in.GetCLLI()
 	chassisHolder := (*chassisMap)[clli]
@@ -174,6 +214,7 @@
 	if err != nil {
 		return nil, err
 	}
+	isDirty = true
 	return &AddOntReturn{Success: true}, nil
 }
 
@@ -181,6 +222,9 @@
 DeleteOnt - deletes a previously provision ont
 */
 func (s *Server) DeleteOnt(ctx context.Context, in *DeleteOntMessage) (*DeleteOntReturn, error) {
+	myChan := getSyncChannel()
+	<-myChan
+	defer done(myChan, true)
 	chassisMap := models.GetChassisMap()
 	clli := in.GetCLLI()
 	chassisHolder := (*chassisMap)[clli]
@@ -188,19 +232,70 @@
 	if err != nil {
 		return nil, err
 	}
+	isDirty = true
 	return &DeleteOntReturn{Success: true}, nil
 }
 
 func (s *Server) Output(ctx context.Context, in *OutputMessage) (*OutputReturn, error) {
-	chassisMap := models.GetChassisMap()
-	for clli, chassisHolder := range *chassisMap {
+	return DoOutput()
 
-		json, _ := (chassisHolder).Serialize()
-		backupFile := fmt.Sprintf("backup/%s", clli)
-		f, _ := os.Create(backupFile)
-		defer f.Close()
-		_, _ = f.WriteString(string(json))
-		f.Sync()
+}
+func DoOutput() (*OutputReturn, error) {
+	if isDirty {
+		myChan := getSyncChannel()
+		<-myChan
+		defer done(myChan, true)
+		chassisMap := models.GetChassisMap()
+		if settings.GetMongo() {
+			client, err := mongo.NewClient(settings.GetMongodb())
+			client.Connect(context.Background())
+			if err != nil {
+				log.Printf("client connect to mongo db @%s failed with %v\n", settings.GetMongodb(), err)
+			}
+			defer client.Disconnect(context.Background())
+			for clli, chassisHolder := range *chassisMap {
+				json, _ := (chassisHolder).Serialize()
+				collection := client.Database("AbstractOLT").Collection("backup")
+				doc := bson.NewDocument(bson.EC.String("_id", clli))
+				filter := bson.NewDocument(bson.EC.String("_id", clli))
+				doc.Append(bson.EC.Binary("body", json))
+
+				updateDoc := bson.NewDocument(bson.EC.SubDocument("$set", doc))
+				//update or insert if not existent
+				res, err := collection.UpdateOne(context.Background(), filter, updateDoc, updateopt.Upsert(true))
+				if err != nil {
+					log.Printf("collection.UpdateOne failed with %v\n", err)
+				} else {
+					id := res.UpsertedID
+					if settings.GetDebug() {
+						log.Printf("Update Succeeded with id %v\n", id)
+					}
+				}
+			}
+		} else {
+			for clli, chassisHolder := range *chassisMap {
+
+				json, _ := (chassisHolder).Serialize()
+				if settings.GetMongo() {
+
+				} else {
+					//TODO parameterize dump location
+					backupFile := fmt.Sprintf("backup/%s", clli)
+					f, _ := os.Create(backupFile)
+
+					defer f.Close()
+
+					_, _ = f.WriteString(string(json))
+					f.Sync()
+				}
+			}
+		}
+		isDirty = false
+	} else {
+		if settings.GetDebug() {
+			log.Print("Not dirty not dumping config")
+		}
+
 	}
 	return &OutputReturn{Success: true}, nil
 
diff --git a/cmd/AbstractOLT/AbstractOLT.go b/cmd/AbstractOLT/AbstractOLT.go
index 869c2c0..3e50828 100644
--- a/cmd/AbstractOLT/AbstractOLT.go
+++ b/cmd/AbstractOLT/AbstractOLT.go
@@ -25,11 +25,14 @@
 	"net/http"
 	"os"
 	"strings"
+	"time"
 
 	"gerrit.opencord.org/abstract-olt/api"
 	"gerrit.opencord.org/abstract-olt/internal/pkg/settings"
 	"gerrit.opencord.org/abstract-olt/models"
 	"github.com/grpc-ecosystem/grpc-gateway/runtime"
+	"github.com/mongodb/mongo-go-driver/bson"
+	"github.com/mongodb/mongo-go-driver/mongo"
 	"golang.org/x/net/context"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
@@ -186,6 +189,9 @@
 	help := flag.Bool("help", false, "Show usage")
 	dummy := flag.Bool("dummy", false, "Run in dummy mode where YAML is not sent to XOS")
 
+	useMongo := flag.Bool("useMongo", false, "use mongo db for backup/restore")
+	mongodb := flag.String("mongodb", "mongodb://foundry:foundry@localhost:27017", "connect string for mongodb backup/restore")
+
 	flag.Parse()
 	settings.SetDummy(*dummy)
 
@@ -196,12 +202,17 @@
       -a [default false] : Run in Authentication mode currently very basic
       -listenAddress IP_ADDRESS [default localhost] -grpc_port [default 7777] PORT1 -rest_port [default 7778] PORT2: Listen for grpc on IP_ADDRESS:PORT1 and rest on IP_ADDRESS:PORT2
       -log_file [default $WORKING_DIR/AbstractOLT.log] LOG_FILE
+      -mongo [default false] use mongodb for backup restore
+      -mongodb [default mongodb://foundry:foundry@localhost:27017] connect string for mongodb - Required if mongo == true
       -h(elp) print this usage
+
 `
 		fmt.Println(usage)
 		return
 	}
 	settings.SetDebug(*debugPtr)
+	settings.SetMongo(*useMongo)
+	settings.SetMongodb(*mongodb)
 	fmt.Println("Startup Params: debug:", *debugPtr, " Authentication:", *useAuthentication, " SSL:", *useSsl, "Cert Directory", *certDirectory,
 		"ListenAddress:", *listenAddress, " grpc port:", *grpcPort, " rest port:", *restPort, "Logging to ", *logFile)
 
@@ -244,29 +255,70 @@
 	}()
 
 	// infinite loop
-	files, err := ioutil.ReadDir("backup")
-	if err != nil {
-		log.Fatal(err)
-	}
-	for _, file := range files {
-		fmt.Println(file.Name())
-		chassisHolder := models.ChassisHolder{}
-		if file.Name() != "BackupPlaceHolder" {
-			fileName := fmt.Sprintf("backup/%s", file.Name())
-			json, _ := ioutil.ReadFile(fileName)
-			err := chassisHolder.Deserialize([]byte(json))
+	if *useMongo {
+		client, err := mongo.NewClient(*mongodb)
+		client.Connect(context.Background())
+		defer client.Disconnect(context.Background())
+		if err != nil {
+			log.Fatalf("unable to connect to mongodb with %v\n", err)
+		}
+		collection := client.Database("AbstractOLT").Collection("backup")
+		cur, err := collection.Find(context.Background(), nil)
+		if err != nil {
+			log.Fatalf("Unable to connect to collection with %v\n", err)
+		}
+		defer cur.Close(context.Background())
+		for cur.Next(context.Background()) {
+			elem := bson.NewDocument()
+			err := cur.Decode(elem)
 			if err != nil {
-				fmt.Printf("Deserialize threw an error %v\n", err)
+				log.Fatal(err)
 			}
-			chassisMap := models.GetChassisMap()
-			(*chassisMap)[file.Name()] = &chassisHolder
-		} else {
-			fmt.Println("Ignoring BackupPlaceHolder")
+			clli := elem.LookupElement("_id").Value()
+			body := elem.LookupElement("body").Value()
+			_, bodyBin := (*body).Binary()
+
+			chassisHolder := models.ChassisHolder{}
+			err = chassisHolder.Deserialize(bodyBin)
+			if err != nil {
+				log.Printf("Deserialize threw an error for clli %s %v\n", (*clli).StringValue(), err)
+			} else {
+				chassisMap := models.GetChassisMap()
+				(*chassisMap)[(*clli).StringValue()] = &chassisHolder
+
+			}
+		}
+	} else {
+		files, err := ioutil.ReadDir("backup")
+		if err != nil {
+			log.Fatal(err)
+		}
+		for _, file := range files {
+			chassisHolder := models.ChassisHolder{}
+			if file.Name() != "BackupPlaceHolder" {
+				fileName := fmt.Sprintf("backup/%s", file.Name())
+				json, _ := ioutil.ReadFile(fileName)
+				err := chassisHolder.Deserialize([]byte(json))
+				if err != nil {
+					fmt.Printf("Deserialize threw an error %v\n", err)
+				}
+				chassisMap := models.GetChassisMap()
+				(*chassisMap)[file.Name()] = &chassisHolder
+			} else {
+				fmt.Println("Ignoring BackupPlaceHolder")
+			}
 		}
 	}
 
 	log.Printf("Entering infinite loop")
-	select {}
+	var ticker = time.NewTicker(60 * time.Second)
+	for {
+		select {
+		case <-ticker.C:
+			api.DoOutput()
+		}
+	}
+
 	//TODO publish periodic stats etc
 	fmt.Println("AbstractOLT")
 }
diff --git a/internal/pkg/settings/Settings.go b/internal/pkg/settings/Settings.go
index af61a28..b4eb128 100644
--- a/internal/pkg/settings/Settings.go
+++ b/internal/pkg/settings/Settings.go
@@ -18,6 +18,8 @@
 
 var debug = false
 var dummy = false
+var mongo = false
+var mongodb = ""
 
 /*
 SetDebug - sets debug setting
@@ -46,3 +48,15 @@
 func GetDummy() bool {
 	return dummy
 }
+func SetMongo(useMongo bool) {
+	mongo = useMongo
+}
+func GetMongo() bool {
+	return mongo
+}
+func SetMongodb(connectString string) {
+	mongodb = connectString
+}
+func GetMongodb() string {
+	return mongodb
+}
diff --git a/models/physical/olt.go b/models/physical/olt.go
index 721e731..c800ad5 100644
--- a/models/physical/olt.go
+++ b/models/physical/olt.go
@@ -45,7 +45,7 @@
 	Number         int
 	Ports          []PONPort
 	Active         bool
-	Parent         *Chassis `json:"-"`
+	Parent         *Chassis `json:"-" bson:"-"`
 	DataSwitchPort int
 }
 
diff --git a/models/physical/ont.go b/models/physical/ont.go
index 79c21ee..21fbbae 100644
--- a/models/physical/ont.go
+++ b/models/physical/ont.go
@@ -20,12 +20,12 @@
 Ont represents a single ont/onu connect to a splitter on a Port
 */
 type Ont struct {
-	Number       int
-	Svlan        int
-	Cvlan        int
-	SerialNumber string
-	Parent       *PONPort `json:"-"`
-	Active       bool
-	NasPortID    string
-	CircuitID    string
+	Number       int      `json:",omitempty"`
+	Svlan        int      `,json:",omitempty"`
+	Cvlan        int      `,json:",omitempty"`
+	SerialNumber string   `,json:",omitempty"`
+	Parent       *PONPort `json:"-" bson:"-"`
+	Active       bool     `json:",omitempty"`
+	NasPortID    string   `json:",omitempty"`
+	CircuitID    string   `json:",omitempty"`
 }
diff --git a/models/physical/ponport.go b/models/physical/ponport.go
index 683489e..9c626c3 100644
--- a/models/physical/ponport.go
+++ b/models/physical/ponport.go
@@ -27,7 +27,7 @@
 	Number   int
 	DeviceID string
 	Onts     [64]Ont
-	Parent   *SimpleOLT `json:"-"`
+	Parent   *SimpleOLT `json:"-" bson:"-"`
 }
 
 /*
diff --git a/models/serialize.go b/models/serialize.go
index a0c2a01..fbcf460 100644
--- a/models/serialize.go
+++ b/models/serialize.go
@@ -18,24 +18,32 @@
 
 import (
 	"encoding/json"
+	"log"
+
+	"gerrit.opencord.org/abstract-olt/internal/pkg/settings"
+	"gerrit.opencord.org/abstract-olt/models/abstract"
+	"gerrit.opencord.org/abstract-olt/models/physical"
 )
 
 func (chassisHolder ChassisHolder) Serialize() ([]byte, error) {
-	return json.Marshal(chassisHolder)
+	return json.Marshal(chassisHolder.PhysicalChassis)
 
 }
 
 func (chassisHolder *ChassisHolder) Deserialize(jsonData []byte) error {
-	err := json.Unmarshal(jsonData, chassisHolder)
+	physicalChassis := physical.Chassis{}
+	err := json.Unmarshal(jsonData, &physicalChassis)
 	if err != nil {
 		return err
 	}
-	physicalChassis := &chassisHolder.PhysicalChassis
-	abstractChassis := &chassisHolder.AbstractChassis
+	abstractChassis := abstract.GenerateChassis(physicalChassis.CLLI, 1, 1)
+	chassisHolder.AbstractChassis = abstractChassis
+	chassisHolder.PhysicalChassis = physicalChassis
+
 	//first handle abstract parent pointers
 	for i := 0; i < len(abstractChassis.Slots); i++ {
 		slot := &abstractChassis.Slots[i]
-		slot.Parent = abstractChassis
+		slot.Parent = &abstractChassis
 		for j := 0; j < len(slot.Ports); j++ {
 			port := &slot.Ports[j]
 			port.Parent = slot
@@ -47,7 +55,7 @@
 	//second handle physical parent pointers
 	for i := 0; i < len(physicalChassis.Linecards); i++ {
 		slot := physicalChassis.Linecards[i]
-		slot.Parent = physicalChassis
+		slot.Parent = &physicalChassis
 		for j := 0; j < len(slot.Ports); j++ {
 			port := &slot.Ports[j]
 			port.Parent = &slot
@@ -65,5 +73,8 @@
 			absPort.PhysPort = &slot.Ports[j]
 		}
 	}
+	if settings.GetDebug() {
+		log.Printf("created chassis %v\n", abstractChassis)
+	}
 	return nil
 }