pkg/api/deploymentapi/depresourceapi/elasticsearch_topology.go (113 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package depresourceapi
import (
"encoding/json"
"errors"
"fmt"
"github.com/elastic/cloud-sdk-go/pkg/models"
"github.com/elastic/cloud-sdk-go/pkg/multierror"
"github.com/elastic/cloud-sdk-go/pkg/util/ec"
)
const (
// DataNode identifies the node type which stores data.
DataNode = "data"
// MasterNode identifies the node type which is master elegible.
MasterNode = "master"
// MLNode identifies the node type performing the Machine Learning.
MLNode = "ml"
)
var (
// DefaultTopology will be used when no topology is specified.
DefaultTopology = []ElasticsearchTopologyElement{
DefaultTopologyElement,
}
// DefaultTopologyElement defines the element used in DefaultTopology
DefaultTopologyElement = ElasticsearchTopologyElement{
NodeType: DataNode,
Size: DefaultDataSize,
ZoneCount: DefaultDataZoneCount,
}
)
// BuildElasticsearchTopologyParams is consumed by BuildElasticsearchTopology.
type BuildElasticsearchTopologyParams struct {
// Deployment Template ID from which the ClusterTopology comes from.
TemplateID string
// API-obtained slice of the Elasticsearch Deployment topology.
ClusterTopology []*models.ElasticsearchClusterTopologyElement
// User specified desired topology from which a new topology will be built.
Topology []ElasticsearchTopologyElement
}
// ElasticsearchTopologyElement is a single cluster topology element, meaning
// a number of instances (controlled by ZoneCount) for a single NodeType.
type ElasticsearchTopologyElement struct {
// NodeType can be one of "data", "master" or "ml".
NodeType string `json:"node_type"`
// Number of zones to span the cluster on.
ZoneCount int32 `json:"zone_count,omitempty"`
// Memory size of the cluster node.
Size int32 `json:"size"`
}
// Sets ZoneCount to a DefaultDataZoneCount.
func (element *ElasticsearchTopologyElement) fillDefaults() {
if element.ZoneCount == 0 {
element.ZoneCount = DefaultDataZoneCount
}
}
// Validate ensures the parameters are usable by the consuming function.
func (element *ElasticsearchTopologyElement) Validate() error {
var merr = multierror.NewPrefixed("elasticsearch topology")
if element.NodeType == "" {
merr = merr.Append(errors.New("node_type cannot be empty"))
}
if element.Size == 0 {
merr = merr.Append(errors.New("size cannot be empty"))
}
return merr.ErrorOrNil()
}
// NewElasticsearchTopology creates a []ElasticsearchTopologyElement from a
// slice of raw strings which are then unmarshaled into the desired slice type.
// If any of the topology elements is not usable, an error is returned.
func NewElasticsearchTopology(topology []string) ([]ElasticsearchTopologyElement, error) {
var t = make([]ElasticsearchTopologyElement, 0, len(topology))
for _, rawElement := range topology {
var element ElasticsearchTopologyElement
element.fillDefaults()
if err := json.Unmarshal([]byte(rawElement), &element); err != nil {
return nil, fmt.Errorf("depresourceapi: failed unpacking raw topology: %s", err)
}
if err := element.Validate(); err != nil {
return nil, err
}
t = append(t, element)
}
return t, nil
}
// NewElasticsearchTopologyElement creates a new topology element given a zone
// count and node size. Using DefaultTopologyElement as the blueprint.
func NewElasticsearchTopologyElement(size, zoneCount int32) ElasticsearchTopologyElement {
var element = DefaultTopologyElement
if size > 0 {
element.Size = size
}
if zoneCount > 0 {
element.ZoneCount = zoneCount
}
return element
}
// BuildElasticsearchTopology receives an ElasticsearchCluster topology from
// the deployment template definition and what the user has specified through
// a simplified version of the deployment topology, iterating over the template
// topology and triying to match the types that the user can specify: ["ml",
// "data", "master"], with their instance configuration ID. the matchNodeType
// function takes care of the matching.
// Once the NodeType has beeen matched, any overrides coming from the user-
// specified topology settings are set, overriding the default deployment
// template defined defaults.
func BuildElasticsearchTopology(params BuildElasticsearchTopologyParams) ([]*models.ElasticsearchClusterTopologyElement, error) {
var topologyList []*models.ElasticsearchClusterTopologyElement
for _, desired := range params.Topology {
for _, t := range params.ClusterTopology {
if matchNodeType(*t.NodeType, desired) {
// Override the desired topology if values are non zero
if desired.Size > 0 {
t.Size.Value = ec.Int32(desired.Size)
}
if desired.ZoneCount > 0 {
t.ZoneCount = desired.ZoneCount
}
topologyList = append(topologyList, t)
}
}
}
if len(topologyList) == 0 {
return nil, fmt.Errorf(
"deployment topology: failed to obtain desired topology names (%+v) in deployment template id \"%s\"",
params.Topology, params.TemplateID,
)
}
return topologyList, nil
}
// matchNodeType compares ElasticsearchTopologyElement name (NodeType) to the
// actual NodeTypes specified in a deployment template cluster topology. The
// NodeType field can be ["data", "master", "ml"].
func matchNodeType(got models.ElasticsearchNodeType, want ElasticsearchTopologyElement) bool {
if want.NodeType == DataNode {
return got.Data != nil && *got.Data
}
if want.NodeType == MasterNode {
var dataFalse = (got.Data != nil && !*got.Data) || got.Data == nil
var masterTrue = got.Master != nil && *got.Master
return dataFalse && masterTrue
}
if want.NodeType == MLNode {
var dataFalse = (got.Data != nil && !*got.Data) || got.Data == nil
var mlTrue = got.Ml != nil && *got.Ml
return dataFalse && mlTrue
}
return false
}