Browse Source

refactored nodes model

afeiszli 4 years ago
parent
commit
fd223e3d4a

+ 1 - 164
controllers/common.go

@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/models"
@@ -70,143 +69,6 @@ func GetExtPeersList(networkName string, macaddress string) ([]models.ExtPeersRe
 	return peers, err
 }
 
-func ValidateNodeCreate(networkName string, node models.Node) error {
-	v := validator.New()
-	_ = v.RegisterValidation("macaddress_unique", func(fl validator.FieldLevel) bool {
-		isFieldUnique, _ := functions.IsMacAddressUnique(node.MacAddress, networkName)
-		return isFieldUnique
-	})
-	_ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool {
-		_, err := node.GetNetwork()
-		return err == nil
-	})
-	_ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool {
-		isgood := functions.NameInNodeCharSet(node.Name)
-		return isgood
-	})
-	err := v.Struct(node)
-
-	if err != nil {
-		for _, e := range err.(validator.ValidationErrors) {
-			fmt.Println(e)
-		}
-	}
-	return err
-}
-
-func ValidateNodeUpdate(networkName string, node models.NodeUpdate) error {
-	v := validator.New()
-	_ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool {
-		_, err := functions.GetParentNetwork(networkName)
-		return err == nil
-	})
-	_ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool {
-		isgood := functions.NameInNodeCharSet(node.Name)
-		return isgood
-	})
-	err := v.Struct(node)
-	if err != nil {
-		for _, e := range err.(validator.ValidationErrors) {
-			fmt.Println(e)
-		}
-	}
-	return err
-}
-
-func UpdateNode(nodechange models.NodeUpdate, node models.Node) (models.Node, error) {
-	//Question: Is there a better way  of doing  this than a bunch of "if" statements? probably...
-	//Eventually, lets have a better way to check if any of the fields are filled out...
-	oldkey, err := functions.GetRecordKey(node.MacAddress, node.Network)
-	if err != nil {
-		return node, err
-	}
-
-	notifynetwork := false
-
-	if nodechange.Address != "" {
-		node.Address = nodechange.Address
-		notifynetwork = true
-	}
-	if nodechange.Address6 != "" {
-		node.Address6 = nodechange.Address6
-		notifynetwork = true
-	}
-	if nodechange.Name != "" {
-		node.Name = nodechange.Name
-	}
-	if nodechange.LocalAddress != "" {
-		node.LocalAddress = nodechange.LocalAddress
-	}
-	if nodechange.ListenPort != 0 {
-		node.ListenPort = nodechange.ListenPort
-	}
-	if nodechange.ExpirationDateTime != 0 {
-		node.ExpirationDateTime = nodechange.ExpirationDateTime
-	}
-	if nodechange.PostDown != "" {
-		node.PostDown = nodechange.PostDown
-	}
-	if nodechange.Interface != "" {
-		node.Interface = nodechange.Interface
-	}
-	if nodechange.PostUp != "" {
-		node.PostUp = nodechange.PostUp
-	}
-	if nodechange.AccessKey != "" {
-		node.AccessKey = nodechange.AccessKey
-	}
-	if nodechange.Endpoint != "" {
-		node.Endpoint = nodechange.Endpoint
-		notifynetwork = true
-	}
-	if nodechange.SaveConfig != nil {
-		node.SaveConfig = nodechange.SaveConfig
-	}
-	if nodechange.PersistentKeepalive != 0 {
-		node.PersistentKeepalive = nodechange.PersistentKeepalive
-	}
-	if nodechange.Password != "" {
-		err := bcrypt.CompareHashAndPassword([]byte(nodechange.Password), []byte(node.Password))
-		if err != nil && nodechange.Password != node.Password {
-			hash, err := bcrypt.GenerateFromPassword([]byte(nodechange.Password), 5)
-			if err != nil {
-				return node, err
-			}
-			nodechange.Password = string(hash)
-			node.Password = nodechange.Password
-		}
-	}
-	if nodechange.MacAddress != "" {
-		node.MacAddress = nodechange.MacAddress
-	}
-	if nodechange.PublicKey != "" {
-		node.PublicKey = nodechange.PublicKey
-		node.KeyUpdateTimeStamp = time.Now().Unix()
-		notifynetwork = true
-	}
-	newkey, err := functions.GetRecordKey(node.MacAddress, node.Network)
-	if err != nil {
-		return node, err
-	}
-	if oldkey != newkey {
-		if err := database.DeleteRecord(database.NODES_TABLE_NAME, oldkey); err != nil {
-			return models.Node{}, err
-		}
-	}
-	value, err := json.Marshal(&node)
-	if err != nil {
-		return models.Node{}, err
-	}
-	err = database.Insert(newkey, string(value), database.NODES_TABLE_NAME)
-	if notifynetwork {
-		err = SetNetworkNodesLastModified(node.Network)
-	}
-	if servercfg.IsDNSMode() {
-		err = SetDNS()
-	}
-	return node, err
-}
-
 func DeleteNode(macaddress string, network string) error {
 
 	key, err := functions.GetRecordKey(macaddress, network)
@@ -270,7 +132,7 @@ func GetIntClient(clientid string) (models.IntClient, error) {
 
 func CreateNode(node models.Node, networkName string) (models.Node, error) {
 
-	//encrypt that password so we never see it again
+	//encrypt that password so we never see it
 	hash, err := bcrypt.GenerateFromPassword([]byte(node.Password), 5)
 
 	if err != nil {
@@ -281,37 +143,17 @@ func CreateNode(node models.Node, networkName string) (models.Node, error) {
 
 	node.Network = networkName
 
-	//node.SetDefaults()
-	//Umm, why am I doing this again?
-	//TODO: Why am I using a local function instead of the struct function? I really dont know.
-	//I think I thought it didn't work but uhhh...idk
 	node.SetDefaults()
-
-	//Another DB call here...Inefficient
-	//Anyways, this scrolls through all the IP Addresses in the network range and checks against nodes
-	//until one is open and then returns it
 	node.Address, err = functions.UniqueAddress(networkName)
 	if err != nil {
 		return node, err
 	}
-
 	node.Address6, err = functions.UniqueAddress6(networkName)
-
 	if err != nil {
 		return node, err
 	}
-
-	//IDK why these aren't a part of "set defaults. Pretty dumb.
-	//TODO: This is dumb. Consolidate and fix.
-	node.SetLastModified()
-	node.SetDefaultName()
-	node.SetLastCheckIn()
-	node.SetLastPeerUpdate()
-	node.KeyUpdateTimeStamp = time.Now().Unix()
-
 	//Create a JWT for the node
 	tokenString, _ := functions.CreateJWT(node.MacAddress, networkName)
-
 	if tokenString == "" {
 		//returnErrorResponse(w, r, errorResponse)
 		return node, err
@@ -324,18 +166,13 @@ func CreateNode(node models.Node, networkName string) (models.Node, error) {
 	if err != nil {
 		return node, err
 	}
-
 	err = database.Insert(key, string(nodebytes), database.NODES_TABLE_NAME)
 	if err != nil {
 		return node, err
 	}
-	//return response for if node  is pending
 	if !node.IsPending {
-
 		functions.DecrimentKey(node.Network, node.AccessKey)
-
 	}
-
 	SetNetworkNodesLastModified(node.Network)
 	if servercfg.IsDNSMode() {
 		err = SetDNS()

+ 1 - 3
controllers/networkHttpController.go

@@ -275,14 +275,12 @@ func updateNetwork(w http.ResponseWriter, r *http.Request) {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
-
 	var newNetwork models.Network
 	err = json.NewDecoder(r.Body).Decode(&newNetwork)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
 	}
-
 	rangeupdate, localrangeupdate, err := network.Update(&newNetwork)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
@@ -405,7 +403,7 @@ func CreateNetwork(network models.Network) error {
 	network.SetNetworkLastModified()
 	network.KeyUpdateTimeStamp = time.Now().Unix()
 
-	err := network.Validate()
+	err := network.Validate(false)
 	if err != nil {
 		//returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return err

+ 6 - 6
controllers/nodeGrpcController.go

@@ -139,7 +139,7 @@ func (s *NodeServiceServer) CreateNode(ctx context.Context, req *nodepb.CreateNo
 		ListenPort:          data.GetListenport(),
 	}
 
-	err := ValidateNodeCreate(node.Network, node)
+	err := node.Validate(false)
 
 	if err != nil {
 		// return internal gRPC error to be handled later
@@ -257,7 +257,7 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.UpdateNo
 	// Get the node data from the request
 	data := req.GetNode()
 	// Now we have to convert this into a NodeItem type to convert into BSON
-	nodechange := models.NodeUpdate{
+	newnode := models.Node{
 		// ID:       primitive.NilObjectID,
 		MacAddress:          data.GetMacaddress(),
 		Name:                data.GetName(),
@@ -277,11 +277,11 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.UpdateNo
 	}
 
 	// Convert the Id string to a MongoDB ObjectId
-	macaddress := nodechange.MacAddress
-	networkName := nodechange.Network
+	macaddress := newnode.MacAddress
+	networkName := newnode.Network
 	network, _ := functions.GetParentNetwork(networkName)
 
-	err := ValidateNodeUpdate(networkName, nodechange)
+	err := newnode.Validate(true)
 	if err != nil {
 		return nil, err
 	}
@@ -294,7 +294,7 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.UpdateNo
 		)
 	}
 
-	newnode, err := UpdateNode(nodechange, node)
+	err = node.Update(&newnode)
 
 	if err != nil {
 		return nil, status.Errorf(

+ 35 - 32
controllers/nodeHttpController.go

@@ -3,6 +3,7 @@ package controller
 import (
 	"encoding/json"
 	"errors"
+	"fmt"
 	"log"
 	"net/http"
 	"strings"
@@ -12,6 +13,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/functions"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/bcrypt"
 )
 
@@ -310,23 +312,34 @@ func GetNetworkNodes(network string) ([]models.Node, error) {
 //Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not
 func getAllNodes(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
-	nodes, err := getUsersNodes(r.Header.Get("user"))
+	user, err := functions.GetUser(r.Header.Get("user"))
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
+	var nodes []models.Node
+	if user.IsAdmin {
+		nodes, err = models.GetAllNodes()
+		if err != nil {
+			returnErrorResponse(w, r, formatError(err, "internal"))
+			return
+		}
+	} else {
+		nodes, err = getUsersNodes(user)
+		if err != nil {
+			returnErrorResponse(w, r, formatError(err, "internal"))
+			return
+		}
+	}
 	//Return all the nodes in JSON format
 	functions.PrintUserLog(r.Header.Get("user"), "fetched nodes", 2)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(nodes)
 }
 
-func getUsersNodes(username string) ([]models.Node, error) {
+func getUsersNodes(user models.User) ([]models.Node, error) {
 	var nodes []models.Node
-	user, err := functions.GetUser(username)
-	if err != nil {
-		return nodes, err
-	}
+	var err error
 	for _, networkName := range user.Networks {
 		tmpNodes, err := GetNetworkNodes(networkName)
 		if err != nil {
@@ -416,9 +429,6 @@ func getLastModified(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(network.NodesLastModified)
 }
 
-//This one's a doozy
-//To create a node
-//Must have valid key and be unique
 func createNode(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
@@ -427,12 +437,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 	var errorResponse = models.ErrorResponse{
 		Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
 	}
-
 	networkName := params["network"]
-
-	//Check if network exists  first
-	//TODO: This is inefficient. Let's find a better way.
-	//Just a few rows down we grab the network anyway
 	networkexists, err := functions.NetworkExists(networkName)
 
 	if err != nil {
@@ -481,7 +486,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	err = ValidateNodeCreate(networkName, node)
+	err = node.Validate(false)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
@@ -759,16 +764,11 @@ func DeleteIngressGateway(network, macaddress string) (models.Node, error) {
 }
 
 func updateNode(w http.ResponseWriter, r *http.Request) {
-	log.Println("update reached.")
 	w.Header().Set("Content-Type", "application/json")
 
 	var params = mux.Vars(r)
 
-	//Get id from parameters
-	//id, _ := primitive.ObjectIDFromHex(params["id"])
-
 	var node models.Node
-	log.Println("Called", params["network"], params["macaddress"])
 	//start here
 	node, err := functions.GetNodeByMacAddress(params["network"], params["macaddress"])
 	if err != nil {
@@ -776,29 +776,32 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	var nodechange models.NodeUpdate
-
+	var newNode models.Node
 	// we decode our body request params
-	_ = json.NewDecoder(r.Body).Decode(&nodechange)
-	if nodechange.Network == "" {
-		nodechange.Network = node.Network
-	}
-	if nodechange.MacAddress == "" {
-		nodechange.MacAddress = node.MacAddress
-	}
-	err = ValidateNodeUpdate(params["network"], nodechange)
+	err = json.NewDecoder(r.Body).Decode(&newNode)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
 	}
+	err = node.Update(&newNode)
+	if err != nil {
+		returnErrorResponse(w, r, formatError(err, "internal"))
+		return
+	}
 
-	node, err = UpdateNode(nodechange, node)
+	if err = SetNetworkNodesLastModified(node.Network); err != nil {
+		fmt.Println(err)
+	}
+	if servercfg.IsDNSMode() {
+		err = SetDNS()
+	}
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "internal"))
 		return
 	}
+	functions.PrintUserLog(r.Header.Get("user"), "updated node "+node.MacAddress+" on network "+node.Network, 1)
 	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(node)
+	json.NewEncoder(w).Encode(newNode)
 }
 
 //Delete a node

+ 0 - 20
functions/helpers.go

@@ -732,23 +732,3 @@ func Inc(ip net.IP) {
 		}
 	}
 }
-
-func GetAllNodes() ([]models.Node, error) {
-	var nodes []models.Node
-
-	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
-	if err != nil {
-		return []models.Node{}, err
-	}
-
-	for _, value := range collection {
-		var node models.Node
-		if err := json.Unmarshal([]byte(value), &node); err != nil {
-			return []models.Node{}, err
-		}
-		// add node to our array
-		nodes = append(nodes, node)
-	}
-
-	return nodes, nil
-}

+ 73 - 36
models/network.go

@@ -3,6 +3,8 @@ package models
 import (
 	"encoding/json"
 	"errors"
+	"fmt"
+	"reflect"
 	"strings"
 	"time"
 
@@ -26,49 +28,56 @@ type Network struct {
 	DefaultPostDown        string      `json:"defaultpostdown" bson:"defaultpostdown"`
 	KeyUpdateTimeStamp     int64       `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"`
 	DefaultKeepalive       int32       `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"`
-	DefaultSaveConfig      string      `json:"defaultsaveconfig" bson:"defaultsaveconfig" validate:"regexp=^(yes|no)$"`
+	DefaultSaveConfig      string      `json:"defaultsaveconfig" bson:"defaultsaveconfig" validate:"checkyesorno"`
 	AccessKeys             []AccessKey `json:"accesskeys" bson:"accesskeys"`
-	AllowManualSignUp      string      `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"regexp=^(yes|no)$"`
-	IsLocal                string      `json:"islocal" bson:"islocal" validate:"regexp=^(yes|no)$"`
-	IsDualStack            string      `json:"isdualstack" bson:"isdualstack" validate:"regexp=^(yes|no)$"`
-	IsIPv4                 string      `json:"isipv4" bson:"isipv4" validate:"regexp=^(yes|no)$"`
-	IsIPv6                 string      `json:"isipv6" bson:"isipv6" validate:"regexp=^(yes|no)$"`
-	IsGRPCHub              string      `json:"isgrpchub" bson:"isgrpchub" validate:"regexp=^(yes|no)$"`
+	AllowManualSignUp      string      `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
+	IsLocal                string      `json:"islocal" bson:"islocal" validate:"checkyesorno"`
+	IsDualStack            string      `json:"isdualstack" bson:"isdualstack" validate:"checkyesorno"`
+	IsIPv4                 string      `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
+	IsIPv6                 string      `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
+	IsGRPCHub              string      `json:"isgrpchub" bson:"isgrpchub" validate:"checkyesorno"`
 	LocalRange             string      `json:"localrange" bson:"localrange" validate:"omitempty,cidr"`
 	DefaultCheckInInterval int32       `json:"checkininterval,omitempty" bson:"checkininterval,omitempty" validate:"omitempty,numeric,min=2,max=100000"`
-	DefaultUDPHolePunch    string      `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"regexp=^(yes|no)$"`
+	DefaultUDPHolePunch    string      `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
 }
 
 type SaveData struct { // put sensitive fields here
 	NetID string `json:"netid" bson:"netid" validate:"required,min=1,max=12,netid_valid"`
 }
 
+const STRING_FIELD_TYPE = "string"
+const INT64_FIELD_TYPE = "int64"
+const INT32_FIELD_TYPE = "int32"
+const ACCESS_KEY_TYPE = "[]AccessKey"
+
+var FIELD_TYPES = []string{STRING_FIELD_TYPE, INT64_FIELD_TYPE, INT32_FIELD_TYPE, ACCESS_KEY_TYPE}
+
 var FIELDS = map[string][]string{
 	// "id":                  {"ID", "string"},
-	"addressrange":        {"AddressRange", "string"},
-	"addressrange6":       {"AddressRange6", "string"},
-	"displayname":         {"DisplayName", "string"},
-	"netid":               {"NetID", "string"},
-	"nodeslastmodified":   {"NodesLastModified", "int64"},
-	"networklastmodified": {"NetworkLastModified", "int64"},
-	"defaultinterface":    {"DefaultInterface", "string"},
-	"defaultlistenport":   {"DefaultListenPort", "int32"},
-	"nodelimit":           {"NodeLimit", "int32"},
-	"defaultpostup":       {"DefaultPostUp", "string"},
-	"defaultpostdown":     {"DefaultPostDown", "string"},
-	"keyupdatetimestamp":  {"KeyUpdateTimeStamp", "int64"},
-	"defaultkeepalive":    {"DefaultKeepalive", "int32"},
-	"defaultsaveconfig":   {"DefaultSaveConfig", "string"},
-	"accesskeys":          {"AccessKeys", "[]AccessKey"},
-	"allowmanualsignup":   {"AllowManualSignUp", "string"},
-	"islocal":             {"IsLocal", "string"},
-	"isdualstack":         {"IsDualStack", "string"},
-	"isipv4":              {"IsIPv4", "string"},
-	"isipv6":              {"IsIPv6", "string"},
-	"isgrpchub":           {"IsGRPCHub", "string"},
-	"localrange":          {"LocalRange", "string"},
-	"checkininterval":     {"DefaultCheckInInterval", "int32"},
-	"defaultudpholepunch": {"DefaultUDPHolePunch", "string"},
+	"addressrange":        {"AddressRange", STRING_FIELD_TYPE},
+	"addressrange6":       {"AddressRange6", STRING_FIELD_TYPE},
+	"displayname":         {"DisplayName", STRING_FIELD_TYPE},
+	"netid":               {"NetID", STRING_FIELD_TYPE},
+	"nodeslastmodified":   {"NodesLastModified", INT64_FIELD_TYPE},
+	"networklastmodified": {"NetworkLastModified", INT64_FIELD_TYPE},
+	"defaultinterface":    {"DefaultInterface", STRING_FIELD_TYPE},
+	"defaultlistenport":   {"DefaultListenPort", INT32_FIELD_TYPE},
+	"nodelimit":           {"NodeLimit", INT32_FIELD_TYPE},
+	"defaultpostup":       {"DefaultPostUp", STRING_FIELD_TYPE},
+	"defaultpostdown":     {"DefaultPostDown", STRING_FIELD_TYPE},
+	"keyupdatetimestamp":  {"KeyUpdateTimeStamp", INT64_FIELD_TYPE},
+	"defaultkeepalive":    {"DefaultKeepalive", INT32_FIELD_TYPE},
+	"defaultsaveconfig":   {"DefaultSaveConfig", STRING_FIELD_TYPE},
+	"accesskeys":          {"AccessKeys", ACCESS_KEY_TYPE},
+	"allowmanualsignup":   {"AllowManualSignUp", STRING_FIELD_TYPE},
+	"islocal":             {"IsLocal", STRING_FIELD_TYPE},
+	"isdualstack":         {"IsDualStack", STRING_FIELD_TYPE},
+	"isipv4":              {"IsIPv4", STRING_FIELD_TYPE},
+	"isipv6":              {"IsIPv6", STRING_FIELD_TYPE},
+	"isgrpchub":           {"IsGRPCHub", STRING_FIELD_TYPE},
+	"localrange":          {"LocalRange", STRING_FIELD_TYPE},
+	"checkininterval":     {"DefaultCheckInInterval", INT32_FIELD_TYPE},
+	"defaultudpholepunch": {"DefaultUDPHolePunch", STRING_FIELD_TYPE},
 }
 
 func (network *Network) FieldExists(field string) bool {
@@ -162,21 +171,35 @@ func (network *Network) IsNetworkNameUnique() (bool, error) {
 	return isunique, nil
 }
 
-func (network *Network) Validate() error {
+func (network *Network) Validate(isUpdate bool) error {
 	v := validator.New()
 	_ = v.RegisterValidation("netid_valid", func(fl validator.FieldLevel) bool {
-		isFieldUnique, _ := network.IsNetworkNameUnique()
 		inCharSet := network.NetIDInNetworkCharSet()
+		if isUpdate {
+			return inCharSet
+		}
+		isFieldUnique, _ := network.IsNetworkNameUnique()
 		return isFieldUnique && inCharSet
 	})
 	//
 	_ = v.RegisterValidation("displayname_valid", func(fl validator.FieldLevel) bool {
 		isFieldUnique, _ := network.IsNetworkDisplayNameUnique()
 		inCharSet := network.DisplayNameInNetworkCharSet()
+		if isUpdate {
+			return inCharSet
+		}
 		return isFieldUnique && inCharSet
 	})
-
+	_ = v.RegisterValidation("checkyesorno", func(fl validator.FieldLevel) bool {
+		return CheckYesOrNo(fl)
+	})
 	err := v.Struct(network)
+	if err != nil {
+		for _, e := range err.(validator.ValidationErrors) {
+			fmt.Println(e)
+		}
+	}
+
 	return err
 }
 
@@ -241,8 +264,22 @@ func (network *Network) SetDefaults() {
 	}
 }
 
+func (network *Network) CopyValues(newNetwork *Network, fieldName string) {
+	reflection := reflect.ValueOf(newNetwork)
+	value := reflect.Indirect(reflection).FieldByName(FIELDS[fieldName][0])
+	if value.IsValid() && len(FIELDS[fieldName]) == 2 {
+		fieldData := FIELDS[fieldName]
+		for _, indexVal := range FIELD_TYPES {
+			if indexVal == fieldData[1] {
+				currentReflection := reflect.ValueOf(network)
+				reflect.Indirect(currentReflection).FieldByName(FIELDS[fieldName][0]).Set(value)
+			}
+		}
+	}
+}
+
 func (currentNetwork *Network) Update(newNetwork *Network) (bool, bool, error) {
-	if err := newNetwork.Validate(); err != nil {
+	if err := newNetwork.Validate(true); err != nil {
 		return false, false, err
 	}
 	if newNetwork.NetID == currentNetwork.NetID {

+ 134 - 79
models/node.go

@@ -2,12 +2,14 @@ package models
 
 import (
 	"encoding/json"
+	"errors"
 	"math/rand"
 	"net"
+	"strings"
 	"time"
 
+	"github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
-	"go.mongodb.org/mongo-driver/bson/primitive"
 )
 
 const charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
@@ -17,74 +19,39 @@ var seededRand *rand.Rand = rand.New(
 
 //node struct
 type Node struct {
-	ID                  primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
-	Address             string             `json:"address" bson:"address" validate:"omitempty,ipv4"`
-	Address6            string             `json:"address6" bson:"address6" validate:"omitempty,ipv6"`
-	LocalAddress        string             `json:"localaddress" bson:"localaddress" validate:"omitempty,ip"`
-	Name                string             `json:"name" bson:"name" validate:"omitempty,max=12,in_charset"`
-	ListenPort          int32              `json:"listenport" bson:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
-	PublicKey           string             `json:"publickey" bson:"publickey" validate:"required,base64"`
-	Endpoint            string             `json:"endpoint" bson:"endpoint" validate:"required,ip"`
-	PostUp              string             `json:"postup" bson:"postup"`
-	PostDown            string             `json:"postdown" bson:"postdown"`
-	AllowedIPs          []string           `json:"allowedips" bson:"allowedips"`
-	PersistentKeepalive int32              `json:"persistentkeepalive" bson:"persistentkeepalive" validate:"omitempty,numeric,max=1000"`
-	SaveConfig          *bool              `json:"saveconfig" bson:"saveconfig"`
-	AccessKey           string             `json:"accesskey" bson:"accesskey"`
-	Interface           string             `json:"interface" bson:"interface"`
-	LastModified        int64              `json:"lastmodified" bson:"lastmodified"`
-	KeyUpdateTimeStamp  int64              `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"`
-	ExpirationDateTime  int64              `json:"expdatetime" bson:"expdatetime"`
-	LastPeerUpdate      int64              `json:"lastpeerupdate" bson:"lastpeerupdate"`
-	LastCheckIn         int64              `json:"lastcheckin" bson:"lastcheckin"`
-	MacAddress          string             `json:"macaddress" bson:"macaddress" validate:"required,mac,macaddress_unique"`
-	CheckInInterval     int32              `json:"checkininterval" bson:"checkininterval"`
-	Password            string             `json:"password" bson:"password" validate:"required,min=6"`
-	Network             string             `json:"network" bson:"network" validate:"network_exists"`
-	IsPending           bool               `json:"ispending" bson:"ispending"`
-	IsEgressGateway     bool               `json:"isegressgateway" bson:"isegressgateway"`
-	IsIngressGateway    bool               `json:"isingressgateway" bson:"isingressgateway"`
-	EgressGatewayRanges []string           `json:"egressgatewayranges" bson:"egressgatewayranges"`
-	IngressGatewayRange string             `json:"ingressgatewayrange" bson:"ingressgatewayrange"`
-	PostChanges         string             `json:"postchanges" bson:"postchanges"`
-	StaticIP            string             `json:"staticip" bson:"staticip"`
-	StaticPubKey        string             `json:"staticpubkey" bson:"staticpubkey"`
-}
-
-//node update struct --- only validations are different
-type NodeUpdate struct {
-	ID                  primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
-	Address             string             `json:"address" bson:"address" validate:"omitempty,ip"`
-	Address6            string             `json:"address6" bson:"address6" validate:"omitempty,ipv6"`
-	LocalAddress        string             `json:"localaddress" bson:"localaddress" validate:"omitempty,ip"`
-	Name                string             `json:"name" bson:"name" validate:"omitempty,max=12,in_charset"`
-	ListenPort          int32              `json:"listenport" bson:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
-	PublicKey           string             `json:"publickey" bson:"publickey" validate:"omitempty,base64"`
-	Endpoint            string             `json:"endpoint" bson:"endpoint" validate:"omitempty,ip"`
-	PostUp              string             `json:"postup" bson:"postup"`
-	PostDown            string             `json:"postdown" bson:"postdown"`
-	AllowedIPs          []string           `json:"allowedips" bson:"allowedips"`
-	PersistentKeepalive int32              `json:"persistentkeepalive" bson:"persistentkeepalive" validate:"omitempty,numeric,max=1000"`
-	SaveConfig          *bool              `json:"saveconfig" bson:"saveconfig"`
-	AccessKey           string             `json:"accesskey" bson:"accesskey"`
-	Interface           string             `json:"interface" bson:"interface"`
-	LastModified        int64              `json:"lastmodified" bson:"lastmodified"`
-	KeyUpdateTimeStamp  int64              `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"`
-	ExpirationDateTime  int64              `json:"expdatetime" bson:"expdatetime"`
-	LastPeerUpdate      int64              `json:"lastpeerupdate" bson:"lastpeerupdate"`
-	LastCheckIn         int64              `json:"lastcheckin" bson:"lastcheckin"`
-	MacAddress          string             `json:"macaddress" bson:"macaddress" validate:"required,mac"`
-	CheckInInterval     int32              `json:"checkininterval" bson:"checkininterval"`
-	Password            string             `json:"password" bson:"password" validate:"omitempty,min=5"`
-	Network             string             `json:"network" bson:"network" validate:"network_exists"`
-	IsPending           bool               `json:"ispending" bson:"ispending"`
-	IsIngressGateway    bool               `json:"isingressgateway" bson:"isingressgateway"`
-	IsEgressGateway     bool               `json:"isegressgateway" bson:"isegressgateway"`
-	IngressGatewayRange string             `json:"ingressgatewayrange" bson:"ingressgatewayrange"`
-	EgressGatewayRanges []string           `json:"egressgatewayranges" bson:"egressgatewayranges"`
-	PostChanges         string             `json:"postchanges" bson:"postchanges"`
-	StaticIP            string             `json:"staticip" bson:"staticip"`
-	StaticPubKey        string             `json:"staticpubkey" bson:"staticpubkey"`
+	ID                  string   `json:"id,omitempty" bson:"id,omitempty"`
+	Address             string   `json:"address" bson:"address" validate:"omitempty,ipv4"`
+	Address6            string   `json:"address6" bson:"address6" validate:"omitempty,ipv6"`
+	LocalAddress        string   `json:"localaddress" bson:"localaddress" validate:"omitempty,ip"`
+	Name                string   `json:"name" bson:"name" validate:"omitempty,max=12,in_charset"`
+	ListenPort          int32    `json:"listenport" bson:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
+	PublicKey           string   `json:"publickey" bson:"publickey" validate:"required,base64"`
+	Endpoint            string   `json:"endpoint" bson:"endpoint" validate:"required,ip"`
+	PostUp              string   `json:"postup" bson:"postup"`
+	PostDown            string   `json:"postdown" bson:"postdown"`
+	AllowedIPs          []string `json:"allowedips" bson:"allowedips"`
+	PersistentKeepalive int32    `json:"persistentkeepalive" bson:"persistentkeepalive" validate:"omitempty,numeric,max=1000"`
+	SaveConfig          string   `json:"saveconfig" bson:"saveconfig" validate:"checkyesorno"`
+	AccessKey           string   `json:"accesskey" bson:"accesskey"`
+	Interface           string   `json:"interface" bson:"interface"`
+	LastModified        int64    `json:"lastmodified" bson:"lastmodified"`
+	KeyUpdateTimeStamp  int64    `json:"keyupdatetimestamp" bson:"keyupdatetimestamp"`
+	ExpirationDateTime  int64    `json:"expdatetime" bson:"expdatetime"`
+	LastPeerUpdate      int64    `json:"lastpeerupdate" bson:"lastpeerupdate"`
+	LastCheckIn         int64    `json:"lastcheckin" bson:"lastcheckin"`
+	MacAddress          string   `json:"macaddress" bson:"macaddress" validate:"required,mac,macaddress_unique"`
+	CheckInInterval     int32    `json:"checkininterval" bson:"checkininterval"`
+	Password            string   `json:"password" bson:"password" validate:"required,min=6"`
+	Network             string   `json:"network" bson:"network" validate:"network_exists"`
+	IsPending           bool     `json:"ispending" bson:"ispending"`
+	IsEgressGateway     bool     `json:"isegressgateway" bson:"isegressgateway"`
+	IsIngressGateway    bool     `json:"isingressgateway" bson:"isingressgateway"`
+	EgressGatewayRanges []string `json:"egressgatewayranges" bson:"egressgatewayranges"`
+	IngressGatewayRange string   `json:"ingressgatewayrange" bson:"ingressgatewayrange"`
+	PostChanges         string   `json:"postchanges" bson:"postchanges"`
+	StaticIP            string   `json:"staticip" bson:"staticip"`
+	StaticPubKey        string   `json:"staticpubkey" bson:"staticpubkey"`
+	UDPHolePunch        string   `json:"udpholepunch" bson:"udpholepunch" validate:"checkyesorno"`
 }
 
 //TODO:
@@ -101,6 +68,10 @@ func (node *Node) SetLastPeerUpdate() {
 	node.LastPeerUpdate = time.Now().Unix()
 }
 
+func (node *Node) SetID() {
+	node.ID = node.MacAddress + "###" + node.Network
+}
+
 func (node *Node) SetExpirationDateTime() {
 	node.ExpirationDateTime = time.Unix(33174902665, 0).Unix()
 }
@@ -138,16 +109,9 @@ func (node *Node) SetDefaults() {
 	if node.ListenPort == 0 {
 		node.ListenPort = parentNetwork.DefaultListenPort
 	}
-	if node.PostDown == "" {
-		//Empty because we dont set it
-		//may want to set it to something in the future
-	}
-	//TODO: This is dumb and doesn't work
-	//Need to change
-	if node.SaveConfig == nil {
+	if node.SaveConfig == "" {
 		if parentNetwork.DefaultSaveConfig != "" {
-			defaultsave := parentNetwork.DefaultSaveConfig == "yes"
-			node.SaveConfig = &defaultsave
+			node.SaveConfig = parentNetwork.DefaultSaveConfig
 		}
 	}
 	if node.Interface == "" {
@@ -166,9 +130,35 @@ func (node *Node) SetDefaults() {
 	if node.StaticPubKey == "" {
 		node.StaticPubKey = "no"
 	}
-
+	if node.UDPHolePunch == "" {
+		node.UDPHolePunch = parentNetwork.DefaultUDPHolePunch
+	}
 	node.CheckInInterval = parentNetwork.DefaultCheckInInterval
 
+	node.SetLastModified()
+	node.SetDefaultName()
+	node.SetLastCheckIn()
+	node.SetLastPeerUpdate()
+	node.SetID()
+	node.KeyUpdateTimeStamp = time.Now().Unix()
+}
+
+func (currentNode *Node) Update(newNode *Node) error {
+	if err := newNode.Validate(true); err != nil {
+		return err
+	}
+	newNode.SetID()
+	if newNode.ID == currentNode.ID {
+		if data, err := json.Marshal(newNode); err != nil {
+			return err
+		} else {
+			newNode.SetLastModified()
+			err = database.Insert(newNode.ID, string(data), database.NODES_TABLE_NAME)
+			return err
+		}
+	}
+	// copy values
+	return errors.New("failed to update node " + newNode.MacAddress + ", cannot change macaddress.")
 }
 
 func StringWithCharset(length int, charset string) string {
@@ -185,3 +175,68 @@ func StringWithCharset(length int, charset string) string {
 func IsIpv4Net(host string) bool {
 	return net.ParseIP(host) != nil
 }
+
+func (node *Node) Validate(isUpdate bool) error {
+	v := validator.New()
+	_ = v.RegisterValidation("macaddress_unique", func(fl validator.FieldLevel) bool {
+		if isUpdate {
+			return true
+		}
+		isFieldUnique, _ := node.IsIDUnique()
+		return isFieldUnique
+	})
+	_ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool {
+		_, err := node.GetNetwork()
+		return err == nil
+	})
+	_ = v.RegisterValidation("in_charset", func(fl validator.FieldLevel) bool {
+		isgood := node.NameInNodeCharSet()
+		return isgood
+	})
+	_ = v.RegisterValidation("checkyesorno", func(fl validator.FieldLevel) bool {
+		return CheckYesOrNo(fl)
+	})
+	err := v.Struct(node)
+
+	return err
+}
+
+func (node *Node) IsIDUnique() (bool, error) {
+	record, err := database.FetchRecord(database.NODES_TABLE_NAME, node.ID)
+	if err != nil {
+		return false, err
+	}
+	return record == "", err
+}
+
+func (node *Node) NameInNodeCharSet() bool {
+
+	charset := "abcdefghijklmnopqrstuvwxyz1234567890-"
+
+	for _, char := range node.Name {
+		if !strings.Contains(charset, strings.ToLower(string(char))) {
+			return false
+		}
+	}
+	return true
+}
+
+func GetAllNodes() ([]Node, error) {
+	var nodes []Node
+
+	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
+	if err != nil {
+		return []Node{}, err
+	}
+
+	for _, value := range collection {
+		var node Node
+		if err := json.Unmarshal([]byte(value), &node); err != nil {
+			return []Node{}, err
+		}
+		// add node to our array
+		nodes = append(nodes, node)
+	}
+
+	return nodes, nil
+}

+ 16 - 0
models/validation.go

@@ -0,0 +1,16 @@
+package models
+
+import (
+	"regexp"
+
+	"github.com/go-playground/validator/v10"
+)
+
+func CheckYesOrNo(fl validator.FieldLevel) bool {
+	return fl.Field().String() == "yes" || fl.Field().String() == "no"
+}
+
+func CheckRegex(fl validator.FieldLevel) bool {
+	re := regexp.MustCompile(fl.Param())
+	return re.MatchString(fl.Field().String())
+}

+ 2 - 2
test/nodecreate.sh

@@ -15,12 +15,12 @@ generate_post_json ()
   "macaddress": "$MACADDRESS",
   "password": "$PASSWORD",
   "localaddress": "172.123.123.3",
-  "accesskey": "$ACCESSKEY"
+  "accesskey": "HskQMAoHVplC3l7E"
 }
 EOF
 }
 
 POST_JSON=$(generate_post_json)
 
-curl --max-time 5.0 -d "$POST_JSON" -H 'Content-Type: application/json' -H "authorization: Bearer secretkey" localhost:8081/api/nodes/my-net
+curl --max-time 5.0 -d "$POST_JSON" -H 'Content-Type: application/json' -H "authorization: Bearer secretkey" localhost:8081/api/nodes/network