[NET-494 / ACC-322] New free tier limits (#2495)

* 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
This commit is contained in:
Gabriel de Souza Seibel 2023-08-08 14:47:49 -03:00 committed by GitHub
parent 449f3f947b
commit 8ce7da2ce9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 185 additions and 104 deletions

View file

@ -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"`
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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)

View file

@ -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)

View file

@ -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()
@ -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,

View file

@ -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)

View file

@ -81,7 +81,7 @@ func ValidateLicense() (err error) {
licenseSecret := LicenseSecret{
AssociatedID: netmakerTenantID,
Limits: getCurrentServerLimit(),
Usage: getCurrentServerUsage(),
}
secretData, err := json.Marshal(&licenseSecret)

View file

@ -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
@ -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

View file

@ -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()
@ -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
}

View file

@ -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)
@ -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 {
@ -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

View file

@ -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
}

View file

@ -2,22 +2,23 @@ package logic
import (
"encoding/json"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/servercfg"
)
var (
// Networks_Limit - dummy var for community
Networks_Limit = 1000000000
// Users_Limit - dummy var for community
Users_Limit = 1000000000
// Clients_Limit - dummy var for community
Clients_Limit = 1000000000
// Hosts_Limit - dummy var for community
Hosts_Limit = 1000000000
// Free_Tier - specifies if free tier
Free_Tier = false
// NetworksLimit - dummy var for community
NetworksLimit = 1000000000
// UsersLimit - dummy var for community
UsersLimit = 1000000000
// MachinesLimit - dummy var for community
MachinesLimit = 1000000000
// IngressesLimit - dummy var for community
IngressesLimit = 1000000000
// EgressesLimit - dummy var for community
EgressesLimit = 1000000000
// FreeTier - specifies if free tier
FreeTier = false
)
type serverData struct {
@ -87,10 +88,12 @@ func StoreJWTSecret(privateKey string) error {
return database.Insert("nm-jwt-secret", string(data), database.SERVERCONF_TABLE_NAME)
}
// SetFreeTierLimits - sets limits for free tier
func SetFreeTierLimits() {
Free_Tier = true
Users_Limit = servercfg.GetUserLimit()
Clients_Limit = servercfg.GetClientLimit()
Networks_Limit = servercfg.GetNetworkLimit()
Hosts_Limit = servercfg.GetHostLimit()
FreeTier = true
UsersLimit = servercfg.GetUserLimit()
NetworksLimit = servercfg.GetNetworkLimit()
MachinesLimit = servercfg.GetMachinesLimit()
IngressesLimit = servercfg.GetIngressLimit()
EgressesLimit = servercfg.GetEgressLimit()
}

View file

@ -753,26 +753,28 @@ func GetNetworkLimit() int {
return networkslimit
}
// GetClientLimit - fetches free tier limits on ext. clients
func GetClientLimit() int {
var clientsLimit int
if os.Getenv("CLIENTS_LIMIT") != "" {
clientsLimit, _ = strconv.Atoi(os.Getenv("CLIENTS_LIMIT"))
} else {
clientsLimit = config.Config.Server.ClientsLimit
// GetMachinesLimit - fetches free tier limits on machines (clients + hosts)
func GetMachinesLimit() int {
if l, err := strconv.Atoi(os.Getenv("MACHINES_LIMIT")); err == nil {
return l
}
return clientsLimit
return config.Config.Server.MachinesLimit
}
// GetHostLimit - fetches free tier limits on hosts
func GetHostLimit() int {
var hostsLimit int
if os.Getenv("HOSTS_LIMIT") != "" {
hostsLimit, _ = strconv.Atoi(os.Getenv("HOSTS_LIMIT"))
} else {
hostsLimit = config.Config.Server.HostsLimit
// GetIngressLimit - fetches free tier limits on ingresses
func GetIngressLimit() int {
if l, err := strconv.Atoi(os.Getenv("INGRESSES_LIMIT")); err == nil {
return l
}
return hostsLimit
return config.Config.Server.IngressesLimit
}
// GetEgressLimit - fetches free tier limits on egresses
func GetEgressLimit() int {
if l, err := strconv.Atoi(os.Getenv("EGRESSES_LIMIT")); err == nil {
return l
}
return config.Config.Server.EgressesLimit
}
// DeployedByOperator - returns true if the instance is deployed by netmaker operator