Skip to content

Commit

Permalink
[NET-494 / ACC-322] New free tier limits (#2495)
Browse files Browse the repository at this point in the history
* Rename var

* Rename consts and use iota

* Use switch instead of repeated else if

* Rename limits related vars

* Introduce new free tier limits

* Measure new limits and report on license validation

* Separate usage and limits, have new ones

* Don't check for hosts and clients limits, but for machines instead

* Error on egress creation @ free tier w/ internet gateways

* Remove clients and hosts limit from code

* Rename var

* Rename consts and use iota

* Use switch instead of repeated else if

* Rename limits related vars

* Introduce new free tier limits

* Measure new limits and report on license validation

* Separate usage and limits, have new ones

* Don't check for hosts and clients limits, but for machines instead

* Error on egress creation @ free tier w/ internet gateways

* Remove clients and hosts limit from code
  • Loading branch information
gabrielseibel1 authored Aug 8, 2023
1 parent 449f3f9 commit 8ce7da2
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 104 deletions.
5 changes: 3 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ type ServerConfig struct {
TurnPassword string `yaml:"turn_password"`
UseTurn bool `yaml:"use_turn"`
UsersLimit int `yaml:"user_limit"`
ClientsLimit int `yaml:"client_limit"`
NetworksLimit int `yaml:"network_limit"`
HostsLimit int `yaml:"host_limit"`
MachinesLimit int `yaml:"machines_limit"`
IngressesLimit int `yaml:"ingresses_limit"`
EgressesLimit int `yaml:"egresses_limit"`
DeployedByOperator bool `yaml:"deployed_by_operator"`
Environment string `yaml:"environment"`
}
Expand Down
2 changes: 1 addition & 1 deletion controllers/ext_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func extClientHandlers(r *mux.Router) {
r.HandleFunc("/api/extclients/{network}/{clientid}/{type}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(getExtClientConf))).Methods(http.MethodGet)
r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(updateExtClient))).Methods(http.MethodPut)
r.HandleFunc("/api/extclients/{network}/{clientid}", logic.NetUserSecurityCheck(false, true, http.HandlerFunc(deleteExtClient))).Methods(http.MethodDelete)
r.HandleFunc("/api/extclients/{network}/{nodeid}", logic.NetUserSecurityCheck(false, true, checkFreeTierLimits(clients_l, http.HandlerFunc(createExtClient)))).Methods(http.MethodPost)
r.HandleFunc("/api/extclients/{network}/{nodeid}", logic.NetUserSecurityCheck(false, true, checkFreeTierLimits(limitChoiceMachines, http.HandlerFunc(createExtClient)))).Methods(http.MethodPost)
}

func checkIngressExists(nodeID string) bool {
Expand Down
56 changes: 40 additions & 16 deletions controllers/limits.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,60 @@ import (

// limit consts
const (
node_l = 0
networks_l = 1
users_l = 2
clients_l = 3
limitChoiceNetworks = iota
limitChoiceUsers
limitChoiceMachines
limitChoiceIngress
limitChoiceEgress
)

func checkFreeTierLimits(limit_choice int, next http.Handler) http.HandlerFunc {
func checkFreeTierLimits(limitChoice int, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var errorResponse = models.ErrorResponse{
Code: http.StatusForbidden, Message: "free tier limits exceeded on networks",
Code: http.StatusForbidden, Message: "free tier limits exceeded on ",
}

if logic.Free_Tier { // check that free tier limits not exceeded
if limit_choice == networks_l {
if logic.FreeTier { // check that free tier limits not exceeded
switch limitChoice {
case limitChoiceNetworks:
currentNetworks, err := logic.GetNetworks()
if (err != nil && !database.IsEmptyRecord(err)) || len(currentNetworks) >= logic.Networks_Limit {
if (err != nil && !database.IsEmptyRecord(err)) ||
len(currentNetworks) >= logic.NetworksLimit {
errorResponse.Message += "networks"
logic.ReturnErrorResponse(w, r, errorResponse)
return
}
} else if limit_choice == users_l {
case limitChoiceUsers:
users, err := logic.GetUsers()
if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= logic.Users_Limit {
errorResponse.Message = "free tier limits exceeded on users"
if (err != nil && !database.IsEmptyRecord(err)) ||
len(users) >= logic.UsersLimit {
errorResponse.Message += "users"
logic.ReturnErrorResponse(w, r, errorResponse)
return
}
} else if limit_choice == clients_l {
clients, err := logic.GetAllExtClients()
if (err != nil && !database.IsEmptyRecord(err)) || len(clients) >= logic.Clients_Limit {
errorResponse.Message = "free tier limits exceeded on external clients"
case limitChoiceMachines:
hosts, hErr := logic.GetAllHosts()
clients, cErr := logic.GetAllExtClients()
if (hErr != nil && !database.IsEmptyRecord(hErr)) ||
(cErr != nil && !database.IsEmptyRecord(cErr)) ||
len(hosts)+len(clients) >= logic.MachinesLimit {
errorResponse.Message += "machines"
logic.ReturnErrorResponse(w, r, errorResponse)
return
}
case limitChoiceIngress:
ingresses, err := logic.GetAllIngresses()
if (err != nil && !database.IsEmptyRecord(err)) ||
len(ingresses) >= logic.IngressesLimit {
errorResponse.Message += "ingresses"
logic.ReturnErrorResponse(w, r, errorResponse)
return
}
case limitChoiceEgress:
egresses, err := logic.GetAllEgresses()
if (err != nil && !database.IsEmptyRecord(err)) ||
len(egresses) >= logic.EgressesLimit {
errorResponse.Message += "egresses"
logic.ReturnErrorResponse(w, r, errorResponse)
return
}
Expand Down
2 changes: 1 addition & 1 deletion controllers/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (

func networkHandlers(r *mux.Router) {
r.HandleFunc("/api/networks", logic.SecurityCheck(false, http.HandlerFunc(getNetworks))).Methods(http.MethodGet)
r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(networks_l, http.HandlerFunc(createNetwork)))).Methods(http.MethodPost)
r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceNetworks, http.HandlerFunc(createNetwork)))).Methods(http.MethodPost)
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(getNetwork))).Methods(http.MethodGet)
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).Methods(http.MethodDelete)
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
Expand Down
4 changes: 2 additions & 2 deletions controllers/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ func nodeHandlers(r *mux.Router) {
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet)
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut)
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", Authorize(false, true, "user", http.HandlerFunc(createEgressGateway))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/{network}/{nodeid}/creategateway", Authorize(false, true, "user", checkFreeTierLimits(limitChoiceEgress, http.HandlerFunc(createEgressGateway)))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/{network}/{nodeid}/deletegateway", Authorize(false, true, "user", http.HandlerFunc(deleteEgressGateway))).Methods(http.MethodDelete)
r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(false, http.HandlerFunc(createIngressGateway))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(false, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createIngressGateway)))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
r.HandleFunc("/api/nodes/{network}/{nodeid}", Authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost)
r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
Expand Down
20 changes: 16 additions & 4 deletions controllers/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ func serverHandlers(r *mux.Router) {
r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
}

// TODO move to EE package? there is a function and a type there for that already
func getUsage(w http.ResponseWriter, r *http.Request) {
type usage struct {
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
Users int `json:"users"`
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
Users int `json:"users"`
Ingresses int `json:"ingresses"`
Egresses int `json:"egresses"`
}
var serverUsage usage
hosts, err := logic.GetAllHosts()
Expand All @@ -48,6 +52,14 @@ func getUsage(w http.ResponseWriter, r *http.Request) {
if err == nil {
serverUsage.Networks = len(networks)
}
ingresses, err := logic.GetAllIngresses()
if err == nil {
serverUsage.Ingresses = len(ingresses)
}
egresses, err := logic.GetAllEgresses()
if err == nil {
serverUsage.Egresses = len(egresses)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.SuccessResponse{
Code: http.StatusOK,
Expand Down
2 changes: 1 addition & 1 deletion controllers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func userHandlers(r *mux.Router) {
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(updateUser)))).Methods(http.MethodPut)
r.HandleFunc("/api/users/networks/{username}", logic.SecurityCheck(true, http.HandlerFunc(updateUserNetworks))).Methods(http.MethodPut)
r.HandleFunc("/api/users/{username}/adm", logic.SecurityCheck(true, http.HandlerFunc(updateUserAdm))).Methods(http.MethodPut)
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, checkFreeTierLimits(users_l, http.HandlerFunc(createUser)))).Methods(http.MethodPost)
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceUsers, http.HandlerFunc(createUser)))).Methods(http.MethodPost)
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(true, http.HandlerFunc(deleteUser))).Methods(http.MethodDelete)
r.HandleFunc("/api/users/{username}", logic.SecurityCheck(false, logic.ContinueIfUserMatch(http.HandlerFunc(getUser)))).Methods(http.MethodGet)
r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet)
Expand Down
2 changes: 1 addition & 1 deletion ee/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func ValidateLicense() (err error) {

licenseSecret := LicenseSecret{
AssociatedID: netmakerTenantID,
Limits: getCurrentServerLimit(),
Usage: getCurrentServerUsage(),
}

secretData, err := json.Marshal(&licenseSecret)
Expand Down
46 changes: 26 additions & 20 deletions ee/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ var errValidation = fmt.Errorf(license_validation_err_msg)

// LicenseKey - the license key struct representation with associated data
type LicenseKey struct {
LicenseValue string `json:"license_value"` // actual (public) key and the unique value for the key
Expiration int64 `json:"expiration"`
LimitServers int `json:"limit_servers"`
LimitUsers int `json:"limit_users"`
LimitHosts int `json:"limit_hosts"`
LimitNetworks int `json:"limit_networks"`
LimitClients int `json:"limit_clients"`
Metadata string `json:"metadata"`
IsActive bool `json:"is_active"` // yes if active
LicenseValue string `json:"license_value"` // actual (public) key and the unique value for the key
Expiration int64 `json:"expiration"`
UsageServers int `json:"usage_servers"`
UsageUsers int `json:"usage_users"`
UsageClients int `json:"usage_clients"`
UsageHosts int `json:"usage_hosts"`
UsageNetworks int `json:"usage_networks"`
UsageIngresses int `json:"usage_ingresses"`
UsageEgresses int `json:"usage_egresses"`
Metadata string `json:"metadata"`
IsActive bool `json:"is_active"` // yes if active
}

// ValidatedLicense - the validated license struct
Expand All @@ -43,26 +45,30 @@ type ValidatedLicense struct {

// LicenseSecret - the encrypted struct for sending user-id
type LicenseSecret struct {
AssociatedID string `json:"associated_id" binding:"required"` // UUID for user foreign key to User table
Limits LicenseLimits `json:"limits" binding:"required"`
AssociatedID string `json:"associated_id" binding:"required"` // UUID for user foreign key to User table
Usage Usage `json:"usage" binding:"required"`
}

// LicenseLimits - struct license limits
type LicenseLimits struct {
Servers int `json:"servers"`
Users int `json:"users"`
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
// Usage - struct for license usage
type Usage struct {
Servers int `json:"servers"`
Users int `json:"users"`
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
Ingresses int `json:"ingresses"`
Egresses int `json:"egresses"`
}

// LicenseLimits.SetDefaults - sets the default values for limits
func (l *LicenseLimits) SetDefaults() {
// Usage.SetDefaults - sets the default values for usage
func (l *Usage) SetDefaults() {
l.Clients = 0
l.Servers = 1
l.Hosts = 0
l.Users = 1
l.Networks = 0
l.Ingresses = 0
l.Egresses = 0
}

// ValidateLicenseRequest - used for request to validate license endpoint
Expand Down
19 changes: 14 additions & 5 deletions ee/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ func base64decode(input string) []byte {

return bytes
}
func getCurrentServerLimit() (limits LicenseLimits) {

func getCurrentServerUsage() (limits Usage) {
limits.SetDefaults()
hosts, err := logic.GetAllHosts()
if err == nil {
hosts, hErr := logic.GetAllHosts()
if hErr == nil {
limits.Hosts = len(hosts)
}
clients, err := logic.GetAllExtClients()
if err == nil {
clients, cErr := logic.GetAllExtClients()
if cErr == nil {
limits.Clients = len(clients)
}
users, err := logic.GetUsers()
Expand All @@ -48,5 +49,13 @@ func getCurrentServerLimit() (limits LicenseLimits) {
if err == nil {
limits.Networks = len(networks)
}
ingresses, err := logic.GetAllIngresses()
if err == nil {
limits.Ingresses = len(ingresses)
}
egresses, err := logic.GetAllEgresses()
if err == nil {
limits.Egresses = len(egresses)
}
return
}
48 changes: 36 additions & 12 deletions logic/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,36 @@ import (
"github.com/gravitl/netmaker/servercfg"
)

// GetAllIngresses - gets all the hosts that are ingresses
func GetAllIngresses() ([]models.Node, error) {
nodes, err := GetAllNodes()
if err != nil {
return nil, err
}
ingresses := make([]models.Node, 0)
for _, node := range nodes {
if node.IsIngressGateway {
ingresses = append(ingresses, node)
}
}
return ingresses, nil
}

// GetAllEgresses - gets all the hosts that are egresses
func GetAllEgresses() ([]models.Node, error) {
nodes, err := GetAllNodes()
if err != nil {
return nil, err
}
egresses := make([]models.Node, 0)
for _, node := range nodes {
if node.IsEgressGateway {
egresses = append(egresses, node)
}
}
return egresses, nil
}

// CreateEgressGateway - creates an egress gateway
func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, error) {
node, err := GetNodeByID(gateway.NodeID)
Expand All @@ -28,10 +58,13 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
return models.Node{}, errors.New("firewall is not supported for egress gateways")
}
for i := len(gateway.Ranges) - 1; i >= 0; i-- {
// check if internet gateway IPv4
if gateway.Ranges[i] == "0.0.0.0/0" && FreeTier {
return models.Node{}, fmt.Errorf("currently IPv4 internet gateways are not supported on the free tier: %s", gateway.Ranges[i])
}
// check if internet gateway IPv6
if gateway.Ranges[i] == "::/0" {
logger.Log(0, "currently IPv6 internet gateways are not supported", gateway.Ranges[i])
gateway.Ranges = append(gateway.Ranges[:i], gateway.Ranges[i+1:]...)
continue
return models.Node{}, fmt.Errorf("currently IPv6 internet gateways are not supported: %s", gateway.Ranges[i])
}
normalized, err := NormalizeCIDR(gateway.Ranges[i])
if err != nil {
Expand Down Expand Up @@ -150,15 +183,6 @@ func DeleteIngressGateway(nodeid string) (models.Node, bool, []models.ExtClient,
node.IsIngressGateway = false
node.IngressGatewayRange = ""
node.Failover = false

//logger.Log(3, "deleting ingress gateway firewall in use is '", host.FirewallInUse, "' and isEgressGateway is", node.IsEgressGateway)
if node.EgressGatewayRequest.NodeID != "" {
_, err := CreateEgressGateway(node.EgressGatewayRequest)
if err != nil {
logger.Log(0, fmt.Sprintf("failed to create egress gateway on node [%s] on network [%s]: %v",
node.EgressGatewayRequest.NodeID, node.EgressGatewayRequest.NetID, err))
}
}
err = UpsertNode(&node)
if err != nil {
return models.Node{}, wasFailover, removedClients, err
Expand Down
14 changes: 7 additions & 7 deletions logic/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,14 @@ func GetHost(hostid string) (*models.Host, error) {

// CreateHost - creates a host if not exist
func CreateHost(h *models.Host) error {
hosts, err := GetAllHosts()
if err != nil && !database.IsEmptyRecord(err) {
return err
hosts, hErr := GetAllHosts()
clients, cErr := GetAllExtClients()
if (hErr != nil && !database.IsEmptyRecord(hErr)) ||
(cErr != nil && !database.IsEmptyRecord(cErr)) ||
len(hosts)+len(clients) >= MachinesLimit {
return errors.New("free tier limits exceeded on machines")
}
if len(hosts) >= Hosts_Limit {
return errors.New("free tier limits exceeded on hosts")
}
_, err = GetHost(h.ID.String())
_, err := GetHost(h.ID.String())
if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
return ErrHostExists
}
Expand Down
Loading

0 comments on commit 8ce7da2

Please sign in to comment.