Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #3794: [API] Support cloud instances in 'Cluster state' panel #3798

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.epam.pipeline.entity.cluster.AllowedInstanceAndPriceTypes;
import com.epam.pipeline.entity.cluster.FilterPodsRequest;
import com.epam.pipeline.entity.cluster.InstanceType;
import com.epam.pipeline.entity.cluster.MachineType;
import com.epam.pipeline.entity.cluster.MasterNode;
import com.epam.pipeline.entity.cluster.NodeDisk;
import com.epam.pipeline.entity.cluster.NodeInstance;
Expand All @@ -49,6 +50,7 @@

import static com.epam.pipeline.security.acl.AclExpressions.ADMIN_ONLY;
import static com.epam.pipeline.security.acl.AclExpressions.ADMIN_OR_GENERAL_USER;
import static com.epam.pipeline.security.acl.AclExpressions.CLOUD_NODE_READ;
import static com.epam.pipeline.security.acl.AclExpressions.NODE_READ;
import static com.epam.pipeline.security.acl.AclExpressions.NODE_READ_FILTER;
import static com.epam.pipeline.security.acl.AclExpressions.NODE_STOP;
Expand All @@ -65,19 +67,19 @@ public class ClusterApiService {
private final PodsManager podsManager;

@PostFilter(NODE_READ_FILTER)
public List<NodeInstance> getNodes() {
return nodesManager.getNodes();
public List<NodeInstance> getNodes(final MachineType machineType) {
return nodesManager.getNodes(machineType);
}

@PostFilter(NODE_READ_FILTER)
public List<NodeInstance> filterNodes(final FilterNodesVO filterNodesVO) {
return nodesManager.filterNodes(filterNodesVO);
}

@PreAuthorize(NODE_READ)
@PreAuthorize(CLOUD_NODE_READ)
@AclMask
public NodeInstance getNode(final String name) {
return nodesManager.getNode(name);
public NodeInstance getNode(final String name, final MachineType machineType, final Long regionId) {
return nodesManager.getKubeOrCloudNode(name, machineType, regionId);
}

@PreAuthorize(NODE_READ)
Expand All @@ -93,8 +95,8 @@ public NodeInstance getNode(final String name, final FilterPodsRequest request)

@PreAuthorize(NODE_STOP)
@AclMask
public NodeInstance terminateNode(final String name) {
return nodesManager.terminateNode(name);
public NodeInstance terminateNode(final String name, final MachineType machineType, final Long regionId) {
return nodesManager.terminateKubeOrCloudNode(name, machineType, regionId);
}

@PreAuthorize(ADMIN_OR_GENERAL_USER)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.epam.pipeline.entity.cluster.AllowedInstanceAndPriceTypes;
import com.epam.pipeline.entity.cluster.FilterPodsRequest;
import com.epam.pipeline.entity.cluster.InstanceType;
import com.epam.pipeline.entity.cluster.MachineType;
import com.epam.pipeline.entity.cluster.MasterNode;
import com.epam.pipeline.entity.cluster.NodeDisk;
import com.epam.pipeline.entity.cluster.NodeInstance;
Expand All @@ -43,6 +44,7 @@
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand Down Expand Up @@ -74,6 +76,7 @@ public class ClusterController extends AbstractRestController {
private static final String REPORT_NAME_TEMPLATE = "%s_%s-%s-%s.%s";
private static final char TIME_SEPARATION_CHAR = ':';
private static final char UNDERSCORE = '_';
private static final String KUBE = "KUBE";

private final ClusterApiService clusterApiService;
private final InfrastructureApiService infrastructureApiService;
Expand Down Expand Up @@ -103,18 +106,19 @@ public Result<String> buildEdgeExternalUrl(@RequestParam(required = false) final
return Result.success(clusterApiService.buildEdgeExternalUrl(region));
}

@RequestMapping(value = "/cluster/node/loadAll", method = RequestMethod.GET)
@GetMapping(value = "/cluster/node/loadAll")
@ResponseBody
@ApiOperation(
value = "Returns all ec2 nodes used in cluster",
notes = "Returns all ec2 nodes used in cluster",
value = "Returns all nodes used in cluster",
notes = "Returns all nodes used in cluster",
produces = MediaType.APPLICATION_JSON_VALUE
)
@ApiResponses(
value = {@ApiResponse(code = HTTP_STATUS_OK, message = API_STATUS_DESCRIPTION)}
)
public Result<List<NodeInstance>> loadNodes() {
return Result.success(clusterApiService.getNodes());
public Result<List<NodeInstance>> loadNodes(
@RequestParam(required = false, defaultValue = KUBE) final MachineType machineType) {
return Result.success(clusterApiService.getNodes(machineType));
}

@RequestMapping(value = "/cluster/node/filter", method = RequestMethod.POST)
Expand All @@ -131,18 +135,21 @@ public Result<List<NodeInstance>> filterNodes(@RequestBody FilterNodesVO filterN
return Result.success(clusterApiService.filterNodes(filterNodesVO));
}

@RequestMapping(value = "/cluster/node/{name}/load", method = RequestMethod.GET)
@GetMapping(value = "/cluster/node/{name}/load")
@ResponseBody
@ApiOperation(
value = "Returns an ec2 node, specified by name.",
notes = "Returns an ec2 node, specified by name.",
value = "Returns a node, specified by name.",
notes = "Returns a node, specified by name.",
produces = MediaType.APPLICATION_JSON_VALUE
)
@ApiResponses(
value = {@ApiResponse(code = HTTP_STATUS_OK, message = API_STATUS_DESCRIPTION)
})
public Result<NodeInstance> loadNode(@PathVariable(value = NAME) final String name) {
return Result.success(clusterApiService.getNode(name));
public Result<NodeInstance> loadNode(@PathVariable(value = NAME) final String name,
@RequestParam(required = false, defaultValue = KUBE)
final MachineType machineType,
@RequestParam(required = false) final Long regionId) {
return Result.success(clusterApiService.getNode(name, machineType, regionId));
}

@RequestMapping(value = "/cluster/node/{name}/run", method = RequestMethod.GET)
Expand Down Expand Up @@ -174,18 +181,21 @@ public Result<NodeInstance> loadNodeFiltered(@PathVariable(value = NAME) final S
return Result.success(clusterApiService.getNode(name, request));
}

@RequestMapping(value = "/cluster/node/{name}", method = RequestMethod.DELETE)
@DeleteMapping(value = "/cluster/node/{name}")
@ResponseBody
@ApiOperation(
value = "Terminates an ec2 node, specified by name.",
notes = "Terminates an ec2 node, specified by name.",
value = "Terminates a node, specified by name.",
notes = "Terminates a node, specified by name.",
produces = MediaType.APPLICATION_JSON_VALUE
)
@ApiResponses(
value = {@ApiResponse(code = HTTP_STATUS_OK, message = API_STATUS_DESCRIPTION)
})
public Result<NodeInstance> terminateNode(@PathVariable(value = NAME) final String name) {
return Result.success(clusterApiService.terminateNode(name));
public Result<NodeInstance> terminateNode(@PathVariable(value = NAME) final String name,
@RequestParam(required = false, defaultValue = KUBE)
final MachineType machineType,
@RequestParam(required = false) final Long regionId) {
return Result.success(clusterApiService.terminateNode(name, machineType, regionId));
}

@RequestMapping(value = "/cluster/instance/loadAll", method = RequestMethod.GET)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.epam.pipeline.controller.vo.region;

import com.epam.pipeline.entity.region.CloudProvider;
import com.epam.pipeline.entity.region.ClusterStateRegionProperties;
import com.epam.pipeline.entity.region.MountStorageRule;
import com.epam.pipeline.entity.region.RunRegionShiftPolicy;
import com.epam.pipeline.entity.region.StorageLifecycleServiceProperties;
Expand Down Expand Up @@ -58,6 +59,11 @@ public abstract class AbstractCloudRegionDTO {
private MountStorageRule mountCredentialsRule = MountStorageRule.NONE;
private StorageLifecycleServiceProperties storageLifecycleServiceProperties;
private RunRegionShiftPolicy runShiftPolicy;
/**
* Indicates that region instances shall be included into cluster state panel
*/
private boolean clusterInclude;
private ClusterStateRegionProperties clusterStateRegionProperties;

public abstract CloudProvider getProvider();
public abstract void setProvider(CloudProvider provider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.epam.pipeline.config.JsonMapper;
import com.epam.pipeline.entity.region.AbstractCloudRegion;
import com.epam.pipeline.entity.region.AbstractCloudRegionCredentials;
import com.epam.pipeline.entity.region.ClusterStateRegionProperties;
import com.epam.pipeline.entity.region.MountStorageRule;
import com.epam.pipeline.entity.region.RunRegionShiftPolicy;
import com.epam.pipeline.entity.region.StorageLifecycleServiceProperties;
Expand Down Expand Up @@ -56,6 +57,18 @@ public MapSqlParameterSource getParameters(final R region, final C credentials)
Optional.ofNullable(region.getRunShiftPolicy())
.map(RunRegionShiftPolicy::isShiftEnabled)
.orElse(Boolean.FALSE));
params.addValue(CloudRegionParameters.CLUSTER_INCLUDE.name(), region.isClusterInclude());

final String clusterSateRegionProperties = Optional.ofNullable(region.getClusterStateRegionProperties())
.map(clusterStateProperties -> {
if (MapUtils.isNotEmpty(clusterStateProperties.getTagsFilter())) {
return JsonMapper.convertDataToJsonStringForQuery(
region.getClusterStateRegionProperties());
} else {
return null;
}
}).orElse(null);
params.addValue(CloudRegionParameters.CLUSTER_STATE_PROPERTIES.name(), clusterSateRegionProperties);

final String slsPropertiesJson = Optional.ofNullable(region.getStorageLifecycleServiceProperties())
.map(props -> {
Expand Down Expand Up @@ -110,5 +123,13 @@ void fillCommonCloudRegionFields(final R region, final ResultSet rs) {
JsonMapper.parseData(slsJsonString, new TypeReference<StorageLifecycleServiceProperties>() {})
);
}

region.setClusterInclude(rs.getBoolean(CloudRegionParameters.CLUSTER_INCLUDE.name()));
final String clusterStateProperties = rs.getString(CloudRegionParameters.CLUSTER_STATE_PROPERTIES.name());
if (!rs.wasNull()) {
region.setClusterStateRegionProperties(
JsonMapper.parseData(clusterStateProperties, new TypeReference<ClusterStateRegionProperties>() {})
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,7 @@ enum CloudRegionParameters {
AWS_KEY_ID,
AWS_ACCESS_KEY,
USER_NAME,
USER_PASSWORD
USER_PASSWORD,
CLUSTER_INCLUDE,
CLUSTER_STATE_PROPERTIES
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2024 EPAM Systems, Inc. (https://www.epam.com/)
*
* Licensed 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 com.epam.pipeline.entity.cluster;

public enum MachineType {
ALL, KUBE, CLOUD
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public class NodeInstance extends AbstractSecuredEntity {
private PipelineRun pipelineRun;
private String region;
private CloudProvider provider;
private MachineType machineType;

public NodeInstance() {
this.pods = new ArrayList<>();
Expand Down Expand Up @@ -105,6 +106,7 @@ public NodeInstance(Node node) {
this.setAllocatable(QuantitiesConverter.convertQuantityMap(status.getAllocatable()));
this.setCapacity(QuantitiesConverter.convertQuantityMap(status.getCapacity()));
}
this.machineType = MachineType.KUBE;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.epam.pipeline.entity.cluster.InstanceImage;
import com.epam.pipeline.entity.cluster.InstanceOffer;
import com.epam.pipeline.entity.cluster.InstanceType;
import com.epam.pipeline.entity.cluster.NodeInstance;
import com.epam.pipeline.entity.cluster.NodeRegionLabels;
import com.epam.pipeline.entity.cluster.pool.NodePool;
import com.epam.pipeline.entity.pipeline.DiskAttachRequest;
Expand Down Expand Up @@ -114,4 +115,8 @@ RunInstance scaleUpNode(Long runId, RunInstance instance, Map<String, String> ru
boolean instanceScalingSupported(Long cloudRegionId);

void deleteInstanceTags(Long regionId, String runId, Set<String> tagNames);

List<NodeInstance> getCloudNodes(Long regionId);

Optional<NodeInstance> findCloudNode(Long regionId, String instanceId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.epam.pipeline.entity.cluster.InstanceImage;
import com.epam.pipeline.entity.cluster.InstanceOffer;
import com.epam.pipeline.entity.cluster.InstanceType;
import com.epam.pipeline.entity.cluster.NodeInstance;
import com.epam.pipeline.entity.cluster.NodeRegionLabels;
import com.epam.pipeline.entity.cluster.pool.NodePool;
import com.epam.pipeline.entity.pipeline.DiskAttachRequest;
Expand Down Expand Up @@ -323,6 +324,18 @@ public boolean instanceScalingSupported(final Long regionId) {
return region.getProvider() != CloudProvider.LOCAL;
}

@Override
public List<NodeInstance> getCloudNodes(final Long regionId) {
final AbstractCloudRegion region = regionManager.loadOrDefault(regionId);
return getInstanceService(region).getCloudNodes(region);
}

@Override
public Optional<NodeInstance> findCloudNode(final Long regionId, final String instanceId) {
final AbstractCloudRegion region = regionManager.loadOrDefault(regionId);
return getInstanceService(region).findCloudNode(region, instanceId);
}

public void deleteInstanceTags(final Long regionId, final String runId, final Set<String> tagNames) {
final AbstractCloudRegion region = regionManager.loadOrDefault(regionId);
getInstanceService(region).deleteInstanceTags(region, runId, tagNames);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.epam.pipeline.entity.cloud.CloudInstanceOperationResult;
import com.epam.pipeline.entity.cluster.InstanceDisk;
import com.epam.pipeline.entity.cluster.InstanceImage;
import com.epam.pipeline.entity.cluster.NodeInstance;
import com.epam.pipeline.entity.cluster.pool.NodePool;
import com.epam.pipeline.entity.pipeline.DiskAttachRequest;
import com.epam.pipeline.entity.pipeline.RunInstance;
Expand Down Expand Up @@ -213,4 +214,20 @@ default boolean isNodeExpired(T region, Long runId, Integer keepAliveMinutes) {
void adjustOfferRequest(InstanceOfferRequestVO requestVO);

void deleteInstanceTags(T region, String runId, Set<String> tagNames);

/**
* Loads all cloud instances available for specified region. Filter by tags can be applied.
*
* @param region region to load
* @return loaded instances
*/
List<NodeInstance> getCloudNodes(T region);

/**
* Finds cloud instance with specified instance ID in requested region. Empty if no instance found.
* @param region - requested region
* @param instanceId - cloud instance identifier
* @return instance if exists
*/
Optional<NodeInstance> findCloudNode(T region, String instanceId);
}
Loading