Added CPU size selector & add tenant cleanup (#1326)

This commit is contained in:
Alex
2021-12-17 12:29:10 -06:00
committed by GitHub
parent 725d6f9691
commit 21158e6c7a
19 changed files with 1489 additions and 370 deletions

View File

@@ -0,0 +1,165 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// AllocatableResourcesResponse allocatable resources response
//
// swagger:model allocatableResourcesResponse
type AllocatableResourcesResponse struct {
// cpu priority
CPUPriority *NodeMaxAllocatableResources `json:"cpu_priority,omitempty"`
// mem priority
MemPriority *NodeMaxAllocatableResources `json:"mem_priority,omitempty"`
// min allocatable cpu
MinAllocatableCPU int64 `json:"min_allocatable_cpu,omitempty"`
// min allocatable mem
MinAllocatableMem int64 `json:"min_allocatable_mem,omitempty"`
}
// Validate validates this allocatable resources response
func (m *AllocatableResourcesResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateCPUPriority(formats); err != nil {
res = append(res, err)
}
if err := m.validateMemPriority(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *AllocatableResourcesResponse) validateCPUPriority(formats strfmt.Registry) error {
if swag.IsZero(m.CPUPriority) { // not required
return nil
}
if m.CPUPriority != nil {
if err := m.CPUPriority.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("cpu_priority")
}
return err
}
}
return nil
}
func (m *AllocatableResourcesResponse) validateMemPriority(formats strfmt.Registry) error {
if swag.IsZero(m.MemPriority) { // not required
return nil
}
if m.MemPriority != nil {
if err := m.MemPriority.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("mem_priority")
}
return err
}
}
return nil
}
// ContextValidate validate this allocatable resources response based on the context it is used
func (m *AllocatableResourcesResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateCPUPriority(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateMemPriority(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *AllocatableResourcesResponse) contextValidateCPUPriority(ctx context.Context, formats strfmt.Registry) error {
if m.CPUPriority != nil {
if err := m.CPUPriority.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("cpu_priority")
}
return err
}
}
return nil
}
func (m *AllocatableResourcesResponse) contextValidateMemPriority(ctx context.Context, formats strfmt.Registry) error {
if m.MemPriority != nil {
if err := m.MemPriority.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("mem_priority")
}
return err
}
}
return nil
}
// MarshalBinary interface implementation
func (m *AllocatableResourcesResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *AllocatableResourcesResponse) UnmarshalBinary(b []byte) error {
var res AllocatableResourcesResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,70 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// NodeMaxAllocatableResources node max allocatable resources
//
// swagger:model nodeMaxAllocatableResources
type NodeMaxAllocatableResources struct {
// max allocatable cpu
MaxAllocatableCPU int64 `json:"max_allocatable_cpu,omitempty"`
// max allocatable mem
MaxAllocatableMem int64 `json:"max_allocatable_mem,omitempty"`
}
// Validate validates this node max allocatable resources
func (m *NodeMaxAllocatableResources) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this node max allocatable resources based on context it is used
func (m *NodeMaxAllocatableResources) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *NodeMaxAllocatableResources) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *NodeMaxAllocatableResources) UnmarshalBinary(b []byte) error {
var res NodeMaxAllocatableResources
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -52,6 +52,39 @@ func init() {
},
"basePath": "/api/v1",
"paths": {
"/cluster/allocatable-resources": {
"get": {
"tags": [
"OperatorAPI"
],
"summary": "Get allocatable cpu and memory for given number of nodes",
"operationId": "GetAllocatableResources",
"parameters": [
{
"minimum": 1,
"type": "integer",
"format": "int32",
"name": "num_nodes",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/allocatableResourcesResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/cluster/max-allocatable-memory": {
"get": {
"tags": [
@@ -1382,6 +1415,25 @@ func init() {
}
},
"definitions": {
"allocatableResourcesResponse": {
"type": "object",
"properties": {
"cpu_priority": {
"$ref": "#/definitions/nodeMaxAllocatableResources"
},
"mem_priority": {
"$ref": "#/definitions/nodeMaxAllocatableResources"
},
"min_allocatable_cpu": {
"type": "integer",
"format": "int64"
},
"min_allocatable_mem": {
"type": "integer",
"format": "int64"
}
}
},
"awsConfiguration": {
"type": "object",
"required": [
@@ -2229,6 +2281,19 @@ func init() {
}
}
},
"nodeMaxAllocatableResources": {
"type": "object",
"properties": {
"max_allocatable_cpu": {
"type": "integer",
"format": "int64"
},
"max_allocatable_mem": {
"type": "integer",
"format": "int64"
}
}
},
"nodeSelectorTerm": {
"description": "A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.",
"type": "object",
@@ -3187,6 +3252,39 @@ func init() {
},
"basePath": "/api/v1",
"paths": {
"/cluster/allocatable-resources": {
"get": {
"tags": [
"OperatorAPI"
],
"summary": "Get allocatable cpu and memory for given number of nodes",
"operationId": "GetAllocatableResources",
"parameters": [
{
"minimum": 1,
"type": "integer",
"format": "int32",
"name": "num_nodes",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/allocatableResourcesResponse"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/cluster/max-allocatable-memory": {
"get": {
"tags": [
@@ -5242,6 +5340,25 @@ func init() {
}
}
},
"allocatableResourcesResponse": {
"type": "object",
"properties": {
"cpu_priority": {
"$ref": "#/definitions/nodeMaxAllocatableResources"
},
"mem_priority": {
"$ref": "#/definitions/nodeMaxAllocatableResources"
},
"min_allocatable_cpu": {
"type": "integer",
"format": "int64"
},
"min_allocatable_mem": {
"type": "integer",
"format": "int64"
}
}
},
"awsConfiguration": {
"type": "object",
"required": [
@@ -6077,6 +6194,19 @@ func init() {
}
}
},
"nodeMaxAllocatableResources": {
"type": "object",
"properties": {
"max_allocatable_cpu": {
"type": "integer",
"format": "int64"
},
"max_allocatable_mem": {
"type": "integer",
"format": "int64"
}
}
},
"nodeSelectorTerm": {
"description": "A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.",
"type": "object",

View File

@@ -78,6 +78,9 @@ func NewOperatorAPI(spec *loads.Document) *OperatorAPI {
OperatorAPIDirectCSIFormatDriveHandler: operator_api.DirectCSIFormatDriveHandlerFunc(func(params operator_api.DirectCSIFormatDriveParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.DirectCSIFormatDrive has not yet been implemented")
}),
OperatorAPIGetAllocatableResourcesHandler: operator_api.GetAllocatableResourcesHandlerFunc(func(params operator_api.GetAllocatableResourcesParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.GetAllocatableResources has not yet been implemented")
}),
OperatorAPIGetDirectCSIDriveListHandler: operator_api.GetDirectCSIDriveListHandlerFunc(func(params operator_api.GetDirectCSIDriveListParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.GetDirectCSIDriveList has not yet been implemented")
}),
@@ -236,6 +239,8 @@ type OperatorAPI struct {
OperatorAPIDeleteTenantHandler operator_api.DeleteTenantHandler
// OperatorAPIDirectCSIFormatDriveHandler sets the operation handler for the direct c s i format drive operation
OperatorAPIDirectCSIFormatDriveHandler operator_api.DirectCSIFormatDriveHandler
// OperatorAPIGetAllocatableResourcesHandler sets the operation handler for the get allocatable resources operation
OperatorAPIGetAllocatableResourcesHandler operator_api.GetAllocatableResourcesHandler
// OperatorAPIGetDirectCSIDriveListHandler sets the operation handler for the get direct c s i drive list operation
OperatorAPIGetDirectCSIDriveListHandler operator_api.GetDirectCSIDriveListHandler
// OperatorAPIGetDirectCSIVolumeListHandler sets the operation handler for the get direct c s i volume list operation
@@ -398,6 +403,9 @@ func (o *OperatorAPI) Validate() error {
if o.OperatorAPIDirectCSIFormatDriveHandler == nil {
unregistered = append(unregistered, "operator_api.DirectCSIFormatDriveHandler")
}
if o.OperatorAPIGetAllocatableResourcesHandler == nil {
unregistered = append(unregistered, "operator_api.GetAllocatableResourcesHandler")
}
if o.OperatorAPIGetDirectCSIDriveListHandler == nil {
unregistered = append(unregistered, "operator_api.GetDirectCSIDriveListHandler")
}
@@ -618,6 +626,10 @@ func (o *OperatorAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/cluster/allocatable-resources"] = operator_api.NewGetAllocatableResources(o.context, o.OperatorAPIGetAllocatableResourcesHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/direct-csi/drives"] = operator_api.NewGetDirectCSIDriveList(o.context, o.OperatorAPIGetDirectCSIDriveListHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)

View File

@@ -0,0 +1,88 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// GetAllocatableResourcesHandlerFunc turns a function with the right signature into a get allocatable resources handler
type GetAllocatableResourcesHandlerFunc func(GetAllocatableResourcesParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn GetAllocatableResourcesHandlerFunc) Handle(params GetAllocatableResourcesParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// GetAllocatableResourcesHandler interface for that can handle valid get allocatable resources params
type GetAllocatableResourcesHandler interface {
Handle(GetAllocatableResourcesParams, *models.Principal) middleware.Responder
}
// NewGetAllocatableResources creates a new http.Handler for the get allocatable resources operation
func NewGetAllocatableResources(ctx *middleware.Context, handler GetAllocatableResourcesHandler) *GetAllocatableResources {
return &GetAllocatableResources{Context: ctx, Handler: handler}
}
/* GetAllocatableResources swagger:route GET /cluster/allocatable-resources OperatorAPI getAllocatableResources
Get allocatable cpu and memory for given number of nodes
*/
type GetAllocatableResources struct {
Context *middleware.Context
Handler GetAllocatableResourcesHandler
}
func (o *GetAllocatableResources) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewGetAllocatableResourcesParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,120 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// NewGetAllocatableResourcesParams creates a new GetAllocatableResourcesParams object
//
// There are no default values defined in the spec.
func NewGetAllocatableResourcesParams() GetAllocatableResourcesParams {
return GetAllocatableResourcesParams{}
}
// GetAllocatableResourcesParams contains all the bound params for the get allocatable resources operation
// typically these are obtained from a http.Request
//
// swagger:parameters GetAllocatableResources
type GetAllocatableResourcesParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
Minimum: 1
In: query
*/
NumNodes int32
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewGetAllocatableResourcesParams() beforehand.
func (o *GetAllocatableResourcesParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
qs := runtime.Values(r.URL.Query())
qNumNodes, qhkNumNodes, _ := qs.GetOK("num_nodes")
if err := o.bindNumNodes(qNumNodes, qhkNumNodes, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindNumNodes binds and validates parameter NumNodes from query.
func (o *GetAllocatableResourcesParams) bindNumNodes(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("num_nodes", "query", rawData)
}
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// AllowEmptyValue: false
if err := validate.RequiredString("num_nodes", "query", raw); err != nil {
return err
}
value, err := swag.ConvertInt32(raw)
if err != nil {
return errors.InvalidType("num_nodes", "query", "int32", raw)
}
o.NumNodes = value
if err := o.validateNumNodes(formats); err != nil {
return err
}
return nil
}
// validateNumNodes carries on validations for parameter NumNodes
func (o *GetAllocatableResourcesParams) validateNumNodes(formats strfmt.Registry) error {
if err := validate.MinimumInt("num_nodes", "query", int64(o.NumNodes), 1, false); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,133 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// GetAllocatableResourcesOKCode is the HTTP code returned for type GetAllocatableResourcesOK
const GetAllocatableResourcesOKCode int = 200
/*GetAllocatableResourcesOK A successful response.
swagger:response getAllocatableResourcesOK
*/
type GetAllocatableResourcesOK struct {
/*
In: Body
*/
Payload *models.AllocatableResourcesResponse `json:"body,omitempty"`
}
// NewGetAllocatableResourcesOK creates GetAllocatableResourcesOK with default headers values
func NewGetAllocatableResourcesOK() *GetAllocatableResourcesOK {
return &GetAllocatableResourcesOK{}
}
// WithPayload adds the payload to the get allocatable resources o k response
func (o *GetAllocatableResourcesOK) WithPayload(payload *models.AllocatableResourcesResponse) *GetAllocatableResourcesOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get allocatable resources o k response
func (o *GetAllocatableResourcesOK) SetPayload(payload *models.AllocatableResourcesResponse) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetAllocatableResourcesOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}
/*GetAllocatableResourcesDefault Generic error response.
swagger:response getAllocatableResourcesDefault
*/
type GetAllocatableResourcesDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewGetAllocatableResourcesDefault creates GetAllocatableResourcesDefault with default headers values
func NewGetAllocatableResourcesDefault(code int) *GetAllocatableResourcesDefault {
if code <= 0 {
code = 500
}
return &GetAllocatableResourcesDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get allocatable resources default response
func (o *GetAllocatableResourcesDefault) WithStatusCode(code int) *GetAllocatableResourcesDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get allocatable resources default response
func (o *GetAllocatableResourcesDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get allocatable resources default response
func (o *GetAllocatableResourcesDefault) WithPayload(payload *models.Error) *GetAllocatableResourcesDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get allocatable resources default response
func (o *GetAllocatableResourcesDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetAllocatableResourcesDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,119 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"github.com/go-openapi/swag"
)
// GetAllocatableResourcesURL generates an URL for the get allocatable resources operation
type GetAllocatableResourcesURL struct {
NumNodes int32
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetAllocatableResourcesURL) WithBasePath(bp string) *GetAllocatableResourcesURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetAllocatableResourcesURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *GetAllocatableResourcesURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/cluster/allocatable-resources"
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
qs := make(url.Values)
numNodesQ := swag.FormatInt32(o.NumNodes)
if numNodesQ != "" {
qs.Set("num_nodes", numNodesQ)
}
_result.RawQuery = qs.Encode()
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *GetAllocatableResourcesURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *GetAllocatableResourcesURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *GetAllocatableResourcesURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on GetAllocatableResourcesURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on GetAllocatableResourcesURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *GetAllocatableResourcesURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -18,6 +18,7 @@ package operatorapi
import (
"context"
"errors"
"sort"
"github.com/minio/minio-go/v7/pkg/set"
@@ -50,6 +51,20 @@ func registerNodesHandlers(api *operations.OperatorAPI) {
}
return operator_api.NewListNodeLabelsOK().WithPayload(*resp)
})
api.OperatorAPIGetAllocatableResourcesHandler = operator_api.GetAllocatableResourcesHandlerFunc(func(params operator_api.GetAllocatableResourcesParams, principal *models.Principal) middleware.Responder {
resp, err := getAllocatableResourcesResponse(params.NumNodes, principal)
if err != nil {
return operator_api.NewGetAllocatableResourcesDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewGetAllocatableResourcesOK().WithPayload(resp)
})
}
type NodeResourceInfo struct {
Name string
AllocatableMemory int64
AllocatableCPU int64
}
// getMaxAllocatableMemory get max allocatable memory given a desired number of nodes
@@ -211,3 +226,147 @@ func getNodeLabelsResponse(ctx context.Context, session *models.Principal) (*mod
}
return clusterResources, nil
}
func getClusterResourcesInfo(numNodes int32, inNodesResources []NodeResourceInfo) *models.AllocatableResourcesResponse {
// purge any nodes with 0 cpu
var nodesResources []NodeResourceInfo
for _, n := range inNodesResources {
if n.AllocatableCPU > 0 {
nodesResources = append(nodesResources, n)
}
}
if int32(len(nodesResources)) < numNodes || numNodes == 0 {
return &models.AllocatableResourcesResponse{
CPUPriority: &models.NodeMaxAllocatableResources{
MaxAllocatableCPU: 0,
MaxAllocatableMem: 0,
},
MemPriority: &models.NodeMaxAllocatableResources{
MaxAllocatableCPU: 0,
MaxAllocatableMem: 0,
},
MinAllocatableCPU: 0,
MinAllocatableMem: 0,
}
}
allocatableResources := &models.AllocatableResourcesResponse{}
// sort nodesResources giving CPU priority
sort.Slice(nodesResources, func(i, j int) bool { return nodesResources[i].AllocatableCPU < nodesResources[j].AllocatableCPU })
maxCPUNodesNeeded := len(nodesResources) - int(numNodes)
maxMemNodesNeeded := maxCPUNodesNeeded
maxAllocatableCPU := nodesResources[maxCPUNodesNeeded].AllocatableCPU
minAllocatableCPU := nodesResources[maxCPUNodesNeeded].AllocatableCPU
minAllocatableMem := nodesResources[maxMemNodesNeeded].AllocatableMemory
availableMemsForMaxCPU := []int64{}
for _, info := range nodesResources {
if info.AllocatableCPU >= maxAllocatableCPU {
availableMemsForMaxCPU = append(availableMemsForMaxCPU, info.AllocatableMemory)
}
// min allocatable resources overall
minAllocatableCPU = min(minAllocatableCPU, info.AllocatableCPU)
minAllocatableMem = min(minAllocatableMem, info.AllocatableMemory)
}
sort.Slice(availableMemsForMaxCPU, func(i, j int) bool { return availableMemsForMaxCPU[i] < availableMemsForMaxCPU[j] })
maxAllocatableMem := availableMemsForMaxCPU[len(availableMemsForMaxCPU)-int(numNodes)]
allocatableResources.MinAllocatableCPU = minAllocatableCPU
allocatableResources.MinAllocatableMem = minAllocatableMem
allocatableResources.CPUPriority = &models.NodeMaxAllocatableResources{
MaxAllocatableCPU: maxAllocatableCPU,
MaxAllocatableMem: maxAllocatableMem,
}
// sort nodesResources giving Mem priority
sort.Slice(nodesResources, func(i, j int) bool { return nodesResources[i].AllocatableMemory < nodesResources[j].AllocatableMemory })
maxMemNodesNeeded = len(nodesResources) - int(numNodes)
maxAllocatableMem = nodesResources[maxMemNodesNeeded].AllocatableMemory
availableCPUsForMaxMem := []int64{}
for _, info := range nodesResources {
if info.AllocatableMemory >= maxAllocatableMem {
availableCPUsForMaxMem = append(availableCPUsForMaxMem, info.AllocatableCPU)
}
}
sort.Slice(availableCPUsForMaxMem, func(i, j int) bool { return availableCPUsForMaxMem[i] < availableCPUsForMaxMem[j] })
maxAllocatableCPU = availableCPUsForMaxMem[len(availableCPUsForMaxMem)-int(numNodes)]
allocatableResources.MemPriority = &models.NodeMaxAllocatableResources{
MaxAllocatableCPU: maxAllocatableCPU,
MaxAllocatableMem: maxAllocatableMem,
}
return allocatableResources
}
// getAllocatableResources get max allocatable memory given a desired number of nodes
func getAllocatableResources(ctx context.Context, clientset v1.CoreV1Interface, numNodes int32) (*models.AllocatableResourcesResponse, error) {
if numNodes == 0 {
return nil, errors.New("error NumNodes must be greated than 0")
}
// get all nodes from cluster
nodes, err := clientset.Nodes().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
nodesInfo := []NodeResourceInfo{}
OUTER:
for _, n := range nodes.Items {
// Don't consider node if it has a NoSchedule or NoExecute Taint
for _, t := range n.Spec.Taints {
switch t.Effect {
case corev1.TaintEffectNoSchedule:
continue OUTER
case corev1.TaintEffectNoExecute:
continue OUTER
default:
continue
}
}
var nodeMemory int64
var nodeCPU int64
if quantity, ok := n.Status.Allocatable[corev1.ResourceMemory]; ok {
// availableMemSizes = append(availableMemSizes, quantity.Value())
nodeMemory = quantity.Value()
}
// we assume all nodes have allocatable cpu resource
if quantity, ok := n.Status.Allocatable[corev1.ResourceCPU]; ok {
// availableCPU = append(availableCPU, quantity.Value())
nodeCPU = quantity.Value()
}
nodeInfo := NodeResourceInfo{
Name: n.Name,
AllocatableCPU: nodeCPU,
AllocatableMemory: nodeMemory,
}
nodesInfo = append(nodesInfo, nodeInfo)
}
res := getClusterResourcesInfo(numNodes, nodesInfo)
return res, nil
}
// Get allocatable resources response
func getAllocatableResourcesResponse(numNodes int32, session *models.Principal) (*models.AllocatableResourcesResponse, *models.Error) {
ctx := context.Background()
client, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
clusterResources, err := getAllocatableResources(ctx, client.CoreV1(), numNodes)
if err != nil {
return nil, prepareError(err)
}
return clusterResources, nil
}

View File

@@ -153,10 +153,12 @@ export interface IResourceModel {
export interface IResourceRequests {
memory: number;
cpu: number;
}
export interface IResourceLimits {
memory: number;
cpu: number;
}
export interface ITLSTenantConfiguration {

View File

@@ -15,8 +15,10 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import storage from "local-storage-fallback";
import get from "lodash/get";
import { ICapacity, IErasureCodeCalc, IStorageFactors } from "./types";
import { IPool } from "../screens/Console/Tenants/ListTenants/types";
import { AllocableResourcesResponse } from "../screens/Console/Tenants/types";
const minStReq = 1073741824; // Minimal Space required for MinIO
const minMemReq = 2147483648; // Minimal Memory required for MinIO in bytes
@@ -594,3 +596,113 @@ export const decodeFileName = (text: string) => {
return text;
}
};
export const setResourcesValidation = (
memorySize: number,
cpusSelected: number,
maxAllocatableResources: AllocableResourcesResponse
) => {
const requestedSizeBytes = getBytes(memorySize.toString(10), "GB");
const memReqSize = parseInt(requestedSizeBytes, 10);
const minimalRequiredMemory = 2147483648; // Minimal required memory, 2Gi
const memoryExists = get(
maxAllocatableResources,
"min_allocatable_mem",
false
);
const cpuExists = get(maxAllocatableResources, "min_allocatable_cpu", false);
if (memoryExists === false) {
return {
error:
"No available memory for the selected number of nodes. Please try another combination.",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (cpuExists === false) {
return {
error:
"No available CPUs for the selected number of nodes. Please try another combination",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (memReqSize < minimalRequiredMemory) {
return {
error: "Memory size is set bellow minimum required",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (cpusSelected < 1) {
return {
error: "CPU amount is set bellow minimum available",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (
memReqSize <= maxAllocatableResources.mem_priority.max_allocatable_mem &&
cpusSelected > maxAllocatableResources.mem_priority.max_allocatable_cpu
) {
return {
error:
"It is not possible to allocate this amount of memory in all the CPUs",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (
cpusSelected <= maxAllocatableResources.cpu_priority.max_allocatable_cpu &&
memReqSize > maxAllocatableResources.cpu_priority.max_allocatable_mem
) {
return {
error:
"It is not possible to allocate this amount of CPUs with the available memory",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
if (
cpusSelected > maxAllocatableResources.cpu_priority.max_allocatable_cpu ||
memReqSize > maxAllocatableResources.mem_priority.max_allocatable_mem
) {
return {
error: "CPUs or Memory selected is beyond bounds",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
};
}
return {
error: "",
memoryRequest: memReqSize,
memoryLimit: maxAllocatableResources.mem_priority.max_allocatable_mem,
cpuRequest: cpusSelected,
cpuLimit: maxAllocatableResources.cpu_priority.max_allocatable_cpu,
};
};

View File

@@ -58,6 +58,7 @@ interface InputBoxProps {
overlayObject?: any;
extraInputProps?: StandardInputProps["inputProps"];
noLabelMinWidth?: boolean;
pattern?: string;
}
const styles = (theme: Theme) =>
@@ -121,6 +122,7 @@ const InputBoxWrapper = ({
extraInputProps = {},
overlayAction,
noLabelMinWidth = false,
pattern="",
classes,
}: InputBoxProps) => {
let inputProps: any = { "data-index": index, ...extraInputProps };
@@ -133,6 +135,10 @@ const InputBoxWrapper = ({
inputProps["max"] = max;
}
if(pattern !== "") {
inputProps["pattern"] = pattern;
}
return (
<React.Fragment>
<Grid

View File

@@ -174,7 +174,7 @@ const AddTenant = ({
const enableTLS = fields.security.enableTLS;
const ecParity = fields.tenantSize.ecParity;
const distribution = fields.tenantSize.distribution;
const memorySize = fields.tenantSize.memorySize;
const resourcesSize = fields.tenantSize.resourcesSize;
const tenantCustom = fields.configure.tenantCustom;
const logSearchCustom = fields.configure.logSearchCustom;
const prometheusCustom = fields.configure.prometheusCustom;
@@ -255,10 +255,12 @@ const AddTenant = ({
},
resources: {
requests: {
memory: memorySize.request,
memory: resourcesSize.memoryRequest,
cpu: resourcesSize.cpuRequest,
},
limits: {
memory: memorySize.limit,
memory: resourcesSize.memoryRequest,
cpu: resourcesSize.cpuRequest,
},
},
securityContext: tenantCustom ? tenantSecurityContext : null,

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import React, { Fragment } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
@@ -29,22 +29,11 @@ import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import {
calculateDistribution,
erasureCodeCalc,
getBytes,
niceBytes,
setMemoryResource,
} from "../../../../../common/utils";
import { ecListTransform, Opts } from "../../ListTenants/utils";
import { IMemorySize } from "../../ListTenants/types";
import {
ErrorResponseHandler,
ICapacity,
IErasureCodeCalc,
} from "../../../../../common/types";
import { commonFormValidation } from "../../../../../utils/validationFunctions";
import api from "../../../../../common/api";
import { niceBytes } from "../../../../../common/utils";
import { Opts } from "../../ListTenants/utils";
import { IResourcesSize } from "../../ListTenants/types";
import { IErasureCodeCalc } from "../../../../../common/types";
import { Divider } from "@mui/material";
interface ISizePreviewProps {
@@ -61,11 +50,12 @@ interface ISizePreviewProps {
ecParityChoices: Opts[];
cleanECChoices: string[];
maxAllocableMemo: number;
memorySize: IMemorySize;
resourcesSize: IResourcesSize;
distribution: any;
ecParityCalc: IErasureCodeCalc;
limitSize: any;
selectedStorageClass: string;
cpuToUse: string;
}
const styles = (theme: Theme) =>
@@ -96,201 +86,17 @@ const SizePreview = ({
ecParityChoices,
cleanECChoices,
maxAllocableMemo,
memorySize,
resourcesSize,
distribution,
ecParityCalc,
limitSize,
selectedStorageClass,
cpuToUse,
}: ISizePreviewProps) => {
const [errorFlag, setErrorFlag] = useState<boolean>(false);
const [nodeError, setNodeError] = useState<string>("");
const usableInformation = ecParityCalc.storageFactors.find(
(element) => element.erasureCode === ecParity
);
// Common
const updateField = useCallback(
(field: string, value: any) => {
updateAddField("tenantSize", field, value);
},
[updateAddField]
);
/*Debounce functions*/
// Storage Quotas
const validateMemorySize = useCallback(() => {
const memSize = parseInt(memoryNode) || 0;
const clusterSize = volumeSize || 0;
const maxMemSize = maxAllocableMemo || 0;
const clusterSizeFactor = sizeFactor;
const clusterSizeBytes = getBytes(
clusterSize.toString(10),
clusterSizeFactor
);
const memoSize = setMemoryResource(memSize, clusterSizeBytes, maxMemSize);
updateField("memorySize", memoSize);
}, [maxAllocableMemo, memoryNode, sizeFactor, updateField, volumeSize]);
const getMaxAllocableMemory = (nodes: string) => {
if (nodes !== "" && !isNaN(parseInt(nodes))) {
setNodeError("");
api
.invoke(
"GET",
`/api/v1/cluster/max-allocatable-memory?num_nodes=${nodes}`
)
.then((res: { max_memory: number }) => {
const maxMemory = res.max_memory ? res.max_memory : 0;
updateField("maxAllocableMemo", maxMemory);
})
.catch((err: ErrorResponseHandler) => {
setErrorFlag(true);
setNodeError(err.errorMessage);
});
}
};
useEffect(() => {
validateMemorySize();
}, [memoryNode, validateMemorySize]);
useEffect(() => {
validateMemorySize();
}, [maxAllocableMemo, validateMemorySize]);
useEffect(() => {
if (ecParityChoices.length > 0 && distribution.error === "") {
const ecCodeValidated = erasureCodeCalc(
cleanECChoices,
distribution.persistentVolumes,
distribution.pvSize,
distribution.nodes
);
updateField("ecParityCalc", ecCodeValidated);
updateField("ecParity", ecCodeValidated.defaultEC);
}
}, [ecParityChoices.length, distribution, cleanECChoices, updateField]);
/*End debounce functions*/
/*Calculate Allocation*/
useEffect(() => {
validateClusterSize();
getECValue();
getMaxAllocableMemory(nodes);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, volumeSize, sizeFactor, drivesPerServer]);
const validateClusterSize = () => {
const size = volumeSize;
const factor = sizeFactor;
const limitSize = getBytes("12", "Ti", true);
const clusterCapacity: ICapacity = {
unit: factor,
value: size.toString(),
};
const distrCalculate = calculateDistribution(
clusterCapacity,
parseInt(nodes),
parseInt(limitSize),
parseInt(drivesPerServer)
);
updateField("distribution", distrCalculate);
};
const getECValue = () => {
updateField("ecParity", "");
if (nodes.trim() !== "" && drivesPerServer.trim() !== "") {
api
.invoke("GET", `/api/v1/get-parity/${nodes}/${drivesPerServer}`)
.then((ecList: string[]) => {
updateField("ecParityChoices", ecListTransform(ecList));
updateField("cleanECChoices", ecList);
})
.catch((err: ErrorResponseHandler) => {
updateField("ecparityChoices", []);
isPageValid("tenantSize", false);
updateField("ecParity", "");
});
}
};
/*Calculate Allocation End*/
/* Validations of pages */
useEffect(() => {
const parsedSize = getBytes(volumeSize, sizeFactor, true);
const commonValidation = commonFormValidation([
{
fieldKey: "nodes",
required: true,
value: nodes,
customValidation: errorFlag,
customValidationMessage: nodeError,
},
{
fieldKey: "volume_size",
required: true,
value: volumeSize,
customValidation:
parseInt(parsedSize) < 1073741824 ||
parseInt(parsedSize) > limitSize[selectedStorageClass],
customValidationMessage: `Volume size must be greater than 1Gi and less than ${niceBytes(
limitSize[selectedStorageClass],
true
)}`,
},
{
fieldKey: "memory_per_node",
required: true,
value: memoryNode,
customValidation: parseInt(memoryNode) < 2,
customValidationMessage: "Memory size must be greater than 2Gi",
},
{
fieldKey: "drivesps",
required: true,
value: drivesPerServer,
customValidation: parseInt(drivesPerServer) < 1,
customValidationMessage: "There must be at least one drive",
},
]);
isPageValid(
"tenantSize",
!("nodes" in commonValidation) &&
!("volume_size" in commonValidation) &&
!("memory_per_node" in commonValidation) &&
!("drivesps" in commonValidation) &&
distribution.error === "" &&
ecParityCalc.error === 0 &&
memorySize.error === ""
);
}, [
nodes,
volumeSize,
sizeFactor,
memoryNode,
distribution,
drivesPerServer,
ecParityCalc,
memorySize,
limitSize,
selectedStorageClass,
isPageValid,
errorFlag,
nodeError,
]);
/* End Validation of pages */
return (
<div className={classes.root}>
<h4>Resource Allocation</h4>
@@ -327,6 +133,10 @@ const SizePreview = ({
<TableCell align="right">{memoryNode} Gi</TableCell>
</TableRow>
)}
<TableRow>
<TableCell scope="row">CPU Selection</TableCell>
<TableCell align="right">{cpuToUse}</TableCell>
</TableRow>
</TableBody>
</Table>
{ecParityCalc.error === 0 && usableInformation && (
@@ -390,12 +200,13 @@ const mapState = (state: AppState) => ({
cleanECChoices: state.tenants.createTenant.fields.tenantSize.cleanECChoices,
maxAllocableMemo:
state.tenants.createTenant.fields.tenantSize.maxAllocableMemo,
memorySize: state.tenants.createTenant.fields.tenantSize.memorySize,
resourcesSize: state.tenants.createTenant.fields.tenantSize.resourcesSize,
distribution: state.tenants.createTenant.fields.tenantSize.distribution,
ecParityCalc: state.tenants.createTenant.fields.tenantSize.ecParityCalc,
limitSize: state.tenants.createTenant.fields.tenantSize.limitSize,
selectedStorageClass:
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
cpuToUse: state.tenants.createTenant.fields.tenantSize.cpuToUse,
});
const connector = connect(mapState, {

View File

@@ -17,8 +17,10 @@
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import { SelectChangeEvent } from "@mui/material";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import get from "lodash/get";
import { AppState } from "../../../../../store";
import { isPageValid, updateAddField } from "../../actions";
import {
@@ -33,21 +35,17 @@ import {
getBytes,
k8sfactorForDropdown,
niceBytes,
setMemoryResource,
setResourcesValidation,
} from "../../../../../common/utils";
import { clearValidationError } from "../../utils";
import { ecListTransform, Opts } from "../../ListTenants/utils";
import { IMemorySize } from "../../ListTenants/types";
import {
ErrorResponseHandler,
ICapacity,
IErasureCodeCalc,
} from "../../../../../common/types";
import { IResourcesSize } from "../../ListTenants/types";
import { AllocableResourcesResponse } from "../../types";
import { ICapacity, IErasureCodeCalc } from "../../../../../common/types";
import { commonFormValidation } from "../../../../../utils/validationFunctions";
import api from "../../../../../common/api";
import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper";
import { SelectChangeEvent } from "@mui/material";
interface ITenantSizeProps {
classes: any;
@@ -63,11 +61,15 @@ interface ITenantSizeProps {
ecParityChoices: Opts[];
cleanECChoices: string[];
maxAllocableMemo: number;
memorySize: IMemorySize;
resourcesSize: IResourcesSize;
distribution: any;
ecParityCalc: IErasureCodeCalc;
limitSize: any;
selectedStorageClass: string;
cpuToUse: string;
maxAllocatableResources: AllocableResourcesResponse;
maxCPUsUse: string;
maxMemorySize: string;
}
const styles = (theme: Theme) =>
@@ -106,11 +108,15 @@ const TenantSize = ({
ecParityChoices,
cleanECChoices,
maxAllocableMemo,
memorySize,
resourcesSize,
distribution,
ecParityCalc,
limitSize,
cpuToUse,
selectedStorageClass,
maxAllocatableResources,
maxCPUsUse,
maxMemorySize,
}: ITenantSizeProps) => {
const [validationErrors, setValidationErrors] = useState<any>({});
const [errorFlag, setErrorFlag] = useState<boolean>(false);
@@ -132,46 +138,22 @@ const TenantSize = ({
// Storage Quotas
const validateMemorySize = useCallback(() => {
const memSize = parseInt(memoryNode) || 0;
const clusterSize = volumeSize || 0;
const maxMemSize = maxAllocableMemo || 0;
const clusterSizeFactor = sizeFactor;
const validateResourcesSize = useCallback(() => {
const memSize = memoryNode || "0";
const cpusSelected = cpuToUse;
const clusterSizeBytes = getBytes(
clusterSize.toString(10),
clusterSizeFactor
const resourcesSize = setResourcesValidation(
parseInt(memSize),
parseInt(cpusSelected),
maxAllocatableResources
);
const memoSize = setMemoryResource(memSize, clusterSizeBytes, maxMemSize);
updateField("memorySize", memoSize);
}, [maxAllocableMemo, memoryNode, sizeFactor, updateField, volumeSize]);
const getMaxAllocableMemory = (nodes: string) => {
if (nodes !== "" && !isNaN(parseInt(nodes))) {
setNodeError("");
api
.invoke(
"GET",
`/api/v1/cluster/max-allocatable-memory?num_nodes=${nodes}`
)
.then((res: { max_memory: number }) => {
const maxMemory = res.max_memory ? res.max_memory : 0;
updateField("maxAllocableMemo", maxMemory);
})
.catch((err: ErrorResponseHandler) => {
setErrorFlag(true);
setNodeError(err.errorMessage);
});
}
};
updateField("resourcesSize", resourcesSize);
}, [memoryNode, cpuToUse, maxAllocatableResources, updateField]);
useEffect(() => {
validateMemorySize();
}, [memoryNode, validateMemorySize]);
useEffect(() => {
validateMemorySize();
}, [maxAllocableMemo, validateMemorySize]);
validateResourcesSize();
}, [memoryNode, cpuToUse, validateResourcesSize]);
useEffect(() => {
if (ecParityChoices.length > 0 && distribution.error === "") {
@@ -190,13 +172,7 @@ const TenantSize = ({
/*Calculate Allocation*/
useEffect(() => {
validateClusterSize();
getECValue();
getMaxAllocableMemory(nodes);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, volumeSize, sizeFactor, drivesPerServer]);
const validateClusterSize = () => {
//Validate Cluster Size
const size = volumeSize;
const factor = sizeFactor;
const limitSize = getBytes("12", "Ti", true);
@@ -214,31 +190,84 @@ const TenantSize = ({
);
updateField("distribution", distrCalculate);
};
setErrorFlag(false);
setNodeError("");
const getECValue = () => {
updateField("ecParity", "");
// Get allocatable Resources
api
.invoke("GET", `api/v1/cluster/allocatable-resources?num_nodes=${nodes}`)
.then((res: AllocableResourcesResponse) => {
updateField("maxAllocatableResources", res);
const maxAllocatableResources = res;
const memoryExists = get(
maxAllocatableResources,
"min_allocatable_mem",
false
);
const cpuExists = get(
maxAllocatableResources,
"min_allocatable_cpu",
false
);
if (memoryExists === false || cpuExists === false) {
updateField("cpuToUse", 0);
updateField("maxMemorySize", "0");
updateField("maxCPUsUse", "0");
validateResourcesSize();
return;
}
// We default to Best CPU Configuration
updateField(
"maxMemorySize",
res.mem_priority.max_allocatable_mem.toString()
);
updateField(
"maxCPUsUse",
res.cpu_priority.max_allocatable_cpu.toString()
);
updateField("maxAllocableMemo", res.mem_priority.max_allocatable_mem);
const cpuInt = parseInt(cpuToUse);
const maxAlocatableCPU = get(
maxAllocatableResources,
"cpu_priority.max_allocatable_cpu",
0
);
if (cpuInt === 0 && cpuInt !== maxAlocatableCPU) {
updateField("cpuToUse", maxAlocatableCPU);
} else if (cpuInt > maxAlocatableCPU) {
updateField("cpuToUse", maxAlocatableCPU);
}
// We reset error states
validateResourcesSize();
})
.catch((err: any) => {
updateField("maxAllocableMemo", 0);
updateField("cpuToUse", "0");
setErrorFlag(true);
setNodeError(err.errorMessage);
console.error(err);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, volumeSize, sizeFactor, updateField]);
if (nodes.trim() !== "" && drivesPerServer.trim() !== "") {
api
.invoke("GET", `/api/v1/get-parity/${nodes}/${drivesPerServer}`)
.then((ecList: string[]) => {
updateField("ecParityChoices", ecListTransform(ecList));
updateField("cleanECChoices", ecList);
})
.catch((err: ErrorResponseHandler) => {
updateField("ecparityChoices", []);
isPageValid("tenantSize", false);
updateField("ecParity", "");
});
}
};
/*Calculate Allocation End*/
/* Validations of pages */
useEffect(() => {
const parsedSize = getBytes(volumeSize, sizeFactor, true);
const commonValidation = commonFormValidation([
{
fieldKey: "nodes",
@@ -283,7 +312,10 @@ const TenantSize = ({
!("drivesps" in commonValidation) &&
distribution.error === "" &&
ecParityCalc.error === 0 &&
memorySize.error === ""
resourcesSize.error === "" &&
parseInt(cpuToUse) <= parseInt(maxCPUsUse) &&
parseInt(cpuToUse) > 0 &&
ecParity !== ""
);
setValidationErrors(commonValidation);
@@ -293,16 +325,40 @@ const TenantSize = ({
sizeFactor,
memoryNode,
distribution,
drivesPerServer,
ecParityCalc,
memorySize,
resourcesSize,
limitSize,
selectedStorageClass,
cpuToUse,
maxCPUsUse,
isPageValid,
errorFlag,
nodeError,
drivesPerServer,
ecParity,
]);
useEffect(() => {
if (distribution.error === "") {
// Get EC Value
updateField("ecParity", "");
if (nodes.trim() !== "" && distribution.disks !== 0) {
api
.invoke("GET", `api/v1/get-parity/${nodes}/${distribution.disks}`)
.then((ecList: string[]) => {
updateField("ecParityChoices", ecListTransform(ecList));
updateField("cleanECChoices", ecList);
})
.catch((err: any) => {
updateField("ecparityChoices", []);
isPageValid("tenantSize", false);
updateField("ecParity", "");
});
}
}
}, [distribution, isPageValid, updateField, nodes]);
/* End Validation of pages */
return (
@@ -320,19 +376,20 @@ const TenantSize = ({
<div className={classes.error}>{distribution.error}</div>
</Grid>
)}
{memorySize.error !== "" && (
{resourcesSize.error !== "" && (
<Grid item xs={12}>
<div className={classes.error}>{memorySize.error}</div>
<div className={classes.error}>{resourcesSize.error}</div>
</Grid>
)}
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="nodes"
name="nodes"
type="number"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
updateField("nodes", e.target.value);
cleanValidation("nodes");
if (e.target.validity.valid) {
updateField("nodes", e.target.value);
cleanValidation("nodes");
}
}}
label="Number of Servers"
disabled={selectedStorageClass === ""}
@@ -340,16 +397,18 @@ const TenantSize = ({
min="4"
required
error={validationErrors["nodes"] || ""}
pattern={"[0-9]*"}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
id="drivesps"
name="drivesps"
type="number"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
updateField("drivesPerServer", e.target.value);
cleanValidation("drivesps");
if (e.target.validity.valid) {
updateField("drivesPerServer", e.target.value);
cleanValidation("drivesps");
}
}}
label="Number of Drives per Server"
value={drivesPerServer}
@@ -357,6 +416,7 @@ const TenantSize = ({
min="1"
required
error={validationErrors["drivesps"] || ""}
pattern={"[0-9]*"}
/>
</Grid>
<Grid item xs={12}>
@@ -396,42 +456,66 @@ const TenantSize = ({
</div>
</Grid>
<Fragment>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
type="number"
id="memory_per_node"
name="memory_per_node"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
updateField("memoryNode", e.target.value);
cleanValidation("memory_per_node");
}}
label="Memory per Node [Gi]"
value={memoryNode}
disabled={selectedStorageClass === ""}
required
error={validationErrors["memory_per_node"] || ""}
min="2"
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<SelectWrapper
id="ec_parity"
name="ec_parity"
onChange={(e: SelectChangeEvent<string>) => {
updateField("ecParity", e.target.value as string);
}}
label="Erasure Code Parity"
disabled={selectedStorageClass === ""}
value={ecParity}
options={ecParityChoices}
/>
<span className={classes.descriptionText}>
Please select the desired parity. This setting will change the max
usable capacity in the cluster
</span>
</Grid>
</Fragment>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
label={"CPU Selection"}
id={"cpuToUse"}
name={"cpuToUse"}
onChange={(e) => {
if (e.target.validity.valid) {
updateField("cpuToUse", e.target.value);
}
}}
value={cpuToUse}
disabled={selectedStorageClass === ""}
min="1"
max={maxCPUsUse}
error={
parseInt(cpuToUse) > parseInt(maxCPUsUse) ||
parseInt(cpuToUse) <= 0 ||
isNaN(parseInt(cpuToUse))
? "Invalid CPU Configuration"
: ""
}
pattern={"[0-9]*"}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<InputBoxWrapper
type="number"
id="memory_per_node"
name="memory_per_node"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
updateField("memoryNode", e.target.value);
cleanValidation("memory_per_node");
}}
label="Memory per Node [Gi]"
value={memoryNode}
disabled={selectedStorageClass === ""}
required
error={validationErrors["memory_per_node"] || ""}
min="2"
max={maxMemorySize}
/>
</Grid>
<Grid item xs={12} className={classes.formFieldRow}>
<SelectWrapper
id="ec_parity"
name="ec_parity"
onChange={(e: SelectChangeEvent<string>) => {
updateField("ecParity", e.target.value as string);
}}
label="Erasure Code Parity"
disabled={selectedStorageClass === ""}
value={ecParity}
options={ecParityChoices}
/>
<span className={classes.descriptionText}>
Please select the desired parity. This setting will change the max
usable capacity in the cluster
</span>
</Grid>
</Fragment>
);
};
@@ -448,12 +532,17 @@ const mapState = (state: AppState) => ({
cleanECChoices: state.tenants.createTenant.fields.tenantSize.cleanECChoices,
maxAllocableMemo:
state.tenants.createTenant.fields.tenantSize.maxAllocableMemo,
memorySize: state.tenants.createTenant.fields.tenantSize.memorySize,
resourcesSize: state.tenants.createTenant.fields.tenantSize.resourcesSize,
distribution: state.tenants.createTenant.fields.tenantSize.distribution,
ecParityCalc: state.tenants.createTenant.fields.tenantSize.ecParityCalc,
limitSize: state.tenants.createTenant.fields.tenantSize.limitSize,
limitSize: state.tenants.createTenant.limitSize,
selectedStorageClass:
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
cpuToUse: state.tenants.createTenant.fields.tenantSize.cpuToUse,
maxAllocatableResources:
state.tenants.createTenant.fields.tenantSize.maxAllocatableResources,
maxCPUsUse: state.tenants.createTenant.fields.tenantSize.maxCPUsUse,
maxMemorySize: state.tenants.createTenant.fields.tenantSize.maxMemorySize,
});
const connector = connect(mapState, {

View File

@@ -122,8 +122,10 @@ export interface ITenantsResponse {
tenants: ITenant[];
}
export interface IMemorySize {
export interface IResourcesSize {
error: string;
limit: number;
request: number;
memoryRequest: number;
memoryLimit: number;
cpuRequest: number;
cpuLimit: number;
}

View File

@@ -39,6 +39,7 @@ import {
ADD_TENANT_ENCRYPTION_VAULT_CA,
ADD_TENANT_ENCRYPTION_GEMALTO_CA,
ADD_TENANT_RESET_FORM,
ADD_TENANT_SET_LIMIT_SIZE,
TENANT_DETAILS_SET_LOADING,
TENANT_DETAILS_SET_CURRENT_TENANT,
TENANT_DETAILS_SET_TENANT,
@@ -200,10 +201,13 @@ const initialState: ITenantState = {
ecParityChoices: [],
cleanECChoices: [],
maxAllocableMemo: 0,
memorySize: {
cpuToUse: "0",
resourcesSize: {
error: "",
limit: 0,
request: 0,
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
},
distribution: {
error: "",
@@ -221,6 +225,20 @@ const initialState: ITenantState = {
storageFactors: [],
},
limitSize: {},
maxAllocatableResources: {
min_allocatable_mem: 0,
min_allocatable_cpu: 0,
cpu_priority: {
max_allocatable_cpu: 0,
max_allocatable_mem: 0,
},
mem_priority: {
max_allocatable_cpu: 0,
max_allocatable_mem: 0,
},
},
maxCPUsUse: "0",
maxMemorySize: "0"
},
affinity: {
nodeSelectorLabels: "",
@@ -371,6 +389,13 @@ export function tenantsReducer(
},
};
return { ...changeCL };
case ADD_TENANT_SET_LIMIT_SIZE:
const changeSizeLimit = {
...state,
createTenant: { ...state.createTenant, limitSize: action.limitSize },
};
return { ...changeSizeLimit };
case ADD_TENANT_ADD_MINIO_KEYPAIR:
const minioCerts = [
...state.createTenant.certificates.minioCertificates,
@@ -714,11 +739,6 @@ export function tenantsReducer(
ecParityChoices: [],
cleanECChoices: [],
maxAllocableMemo: 0,
memorySize: {
error: "",
limit: 0,
request: 0,
},
distribution: {
error: "",
nodes: 0,
@@ -735,6 +755,28 @@ export function tenantsReducer(
storageFactors: [],
},
limitSize: {},
cpuToUse: "0",
resourcesSize: {
error: "",
memoryRequest: 0,
memoryLimit: 0,
cpuRequest: 0,
cpuLimit: 0,
},
maxAllocatableResources: {
min_allocatable_mem: 0,
min_allocatable_cpu: 0,
cpu_priority: {
max_allocatable_cpu: 0,
max_allocatable_mem: 0,
},
mem_priority: {
max_allocatable_cpu: 0,
max_allocatable_mem: 0,
},
},
maxCPUsUse: "0",
maxMemorySize: "0"
},
affinity: {
nodeSelectorLabels: "",

View File

@@ -15,7 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { IErasureCodeCalc } from "../../../common/types";
import { IMemorySize, ITenant } from "./ListTenants/types";
import { ITenant, IResourcesSize } from "./ListTenants/types";
import { KeyPair, Opts } from "./ListTenants/utils";
export const ADD_TENANT_SET_CURRENT_PAGE = "ADD_TENANT/SET_CURRENT_PAGE";
@@ -238,10 +238,14 @@ export interface ITenantSizeFields {
ecParityChoices: Opts[];
cleanECChoices: string[];
maxAllocableMemo: number;
memorySize: IMemorySize;
resourcesSize: IResourcesSize;
distribution: any;
ecParityCalc: IErasureCodeCalc;
cpuToUse: string;
limitSize: any;
maxAllocatableResources: AllocableResourcesResponse;
maxCPUsUse: string;
maxMemorySize: string;
}
export interface ITenantAffinity {
@@ -268,6 +272,18 @@ export interface ILabelKeyPair {
labelValue: string;
}
export interface AllocableResourcesResponse {
min_allocatable_mem?: number;
min_allocatable_cpu?: number;
cpu_priority: NodeMaxAllocatableResources;
mem_priority: NodeMaxAllocatableResources;
}
export interface NodeMaxAllocatableResources {
max_allocatable_cpu: number;
max_allocatable_mem: number;
}
interface SetTenantWizardPage {
type: typeof ADD_TENANT_SET_CURRENT_PAGE;
page: number;

View File

@@ -19,7 +19,7 @@ securityDefinitions:
tokenUrl: http://min.io
# Apply the key security definition to all APIs
security:
- key: [ ]
- key: []
paths:
/login:
get:
@@ -35,7 +35,7 @@ paths:
schema:
$ref: "#/definitions/error"
# Exclude this API from the authentication requirement
security: [ ]
security: []
tags:
- UserAPI
/login/operator:
@@ -55,7 +55,7 @@ paths:
description: Generic error response.
schema:
$ref: "#/definitions/error"
security: [ ]
security: []
tags:
- UserAPI
@@ -76,7 +76,7 @@ paths:
description: Generic error response.
schema:
$ref: "#/definitions/error"
security: [ ]
security: []
tags:
- UserAPI
@@ -110,7 +110,6 @@ paths:
tags:
- UserAPI
/subscription/info:
get:
summary: Subscription info
@@ -188,7 +187,6 @@ paths:
tags:
- OperatorAPI
/tenants:
get:
summary: List Tenant of All Namespaces
@@ -794,6 +792,29 @@ paths:
tags:
- OperatorAPI
/cluster/allocatable-resources:
get:
summary: Get allocatable cpu and memory for given number of nodes
operationId: GetAllocatableResources
parameters:
- name: num_nodes
in: query
required: true
type: integer
format: int32
minimum: 1
responses:
200:
description: A successful response.
schema:
$ref: "#/definitions/allocatableResourcesResponse"
default:
description: Generic error response.
schema:
$ref: "#/definitions/error"
tags:
- OperatorAPI
/get-parity/{nodes}/{disksPerNode}:
get:
summary: Gets parity by sending number of nodes & number of disks
@@ -926,7 +947,6 @@ paths:
- OperatorAPI
definitions:
error:
type: object
required:
@@ -945,7 +965,7 @@ definitions:
properties:
loginStrategy:
type: string
enum: [ form, redirect, service-account ]
enum: [form, redirect, service-account]
redirect:
type: string
loginRequest:
@@ -980,7 +1000,7 @@ definitions:
properties:
status:
type: string
enum: [ ok ]
enum: [ok]
operator:
type: boolean
permissions:
@@ -1325,10 +1345,10 @@ definitions:
type: string
securityContext:
type: object
$ref: '#/definitions/securityContext'
$ref: "#/definitions/securityContext"
postgres_securityContext:
type: object
$ref: '#/definitions/securityContext'
$ref: "#/definitions/securityContext"
postgres_image:
type: string
postgres_init_image:
@@ -1350,7 +1370,7 @@ definitions:
type: string
securityContext:
type: object
$ref: '#/definitions/securityContext'
$ref: "#/definitions/securityContext"
idpConfiguration:
type: object
properties:
@@ -1454,7 +1474,7 @@ definitions:
$ref: "#/definitions/azureConfiguration"
securityContext:
type: object
$ref: '#/definitions/securityContext'
$ref: "#/definitions/securityContext"
vaultConfiguration:
type: object
@@ -1701,7 +1721,7 @@ definitions:
$ref: "#/definitions/poolTolerations"
securityContext:
type: object
$ref: '#/definitions/securityContext'
$ref: "#/definitions/securityContext"
poolTolerations:
description: Tolerations allows users to set entries like effect,
@@ -2150,7 +2170,6 @@ definitions:
items:
type: string
subscriptionValidateRequest:
type: object
properties:
@@ -2226,7 +2245,6 @@ definitions:
drive:
type: string
csiFormatErrorResponse:
type: object
properties:
@@ -2330,19 +2348,42 @@ definitions:
force:
type: boolean
securityContext:
type: object
required:
- runAsUser
- runAsGroup
- runAsNonRoot
- fsGroup
properties:
runAsUser:
type: string
runAsGroup:
type: string
runAsNonRoot:
type: boolean
fsGroup:
type: string
type: object
required:
- runAsUser
- runAsGroup
- runAsNonRoot
- fsGroup
properties:
runAsUser:
type: string
runAsGroup:
type: string
runAsNonRoot:
type: boolean
fsGroup:
type: string
allocatableResourcesResponse:
type: object
properties:
min_allocatable_mem:
type: integer
format: int64
min_allocatable_cpu:
type: integer
format: int64
cpu_priority:
$ref: "#/definitions/nodeMaxAllocatableResources"
mem_priority:
$ref: "#/definitions/nodeMaxAllocatableResources"
nodeMaxAllocatableResources:
type: object
properties:
max_allocatable_cpu:
type: integer
format: int64
max_allocatable_mem:
type: integer
format: int64