Skip to content
Draft
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 @@ -166,4 +166,5 @@ enum State {
Long getEtcdNodeCount();
Long getCniConfigId();
String getCniConfigDetails();
boolean isCsiEnabled();
}
2 changes: 2 additions & 0 deletions api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public class ApiConstants {
public static final String CNI_CONFIG_ID = "cniconfigurationid";
public static final String CNI_CONFIG_DETAILS = "cniconfigdetails";
public static final String CNI_CONFIG_NAME = "cniconfigname";
public static final String CSI_ENABLED = "csienabled";
public static final String COMPONENT = "component";
public static final String CPU = "CPU";
public static final String CPU_CORE_PER_SOCKET = "cpucorepersocket";
Expand Down Expand Up @@ -211,6 +212,7 @@ public class ApiConstants {
public static final String DURATION = "duration";
public static final String ELIGIBLE = "eligible";
public static final String EMAIL = "email";
public static final String ENABLE_CSI = "enablecsi";
public static final String END_ASN = "endasn";
public static final String END_DATE = "enddate";
public static final String END_IP = "endip";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -757,3 +757,6 @@ SET `cs`.`domain_id` = (

-- Re-apply VPC: update default network offering for vpc tier to conserve_mode=1 (#8309)
UPDATE `cloud`.`network_offerings` SET conserve_mode = 1 WHERE name = 'DefaultIsolatedNetworkOfferingForVpcNetworks';

-- Add csi_enabled column to kubernetes_cluster table - Move to 4.22
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster', 'csi_enabled', 'TINYINT(1) unsigned NOT NULL DEFAULT 0 COMMENT "true if kubernetes cluster is using csi, false otherwise" ');
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,16 @@
import org.apache.cloudstack.api.command.user.network.DeleteNetworkACLCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworksCmd;
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotCmd;
import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMsCmd;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DeleteVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.ListVolumesCmd;
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
import org.apache.cloudstack.api.response.KubernetesClusterConfigResponse;
import org.apache.cloudstack.api.response.KubernetesClusterResponse;
import org.apache.cloudstack.api.response.KubernetesUserVmResponse;
Expand Down Expand Up @@ -252,7 +261,16 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
private static final List<Class<?>> PROJECT_KUBERNETES_ACCOUNT_ROLE_ALLOWED_APIS = Arrays.asList(
QueryAsyncJobResultCmd.class,
ListVMsCmd.class,
ListVolumesCmd.class,
CreateVolumeCmd.class,
DeleteVolumeCmd.class,
AttachVolumeCmd.class,
DetachVolumeCmd.class,
ResizeVolumeCmd.class,
ListNetworksCmd.class,
CreateSnapshotCmd.class,
ListSnapshotsCmd.class,
DeleteSnapshotCmd.class,
ListPublicIpAddressesCmd.class,
AssociateIPAddrCmd.class,
DisassociateIPAddrCmd.class,
Expand Down Expand Up @@ -873,6 +891,7 @@ public KubernetesClusterResponse createKubernetesClusterResponse(long kubernetes
response.setMinSize(kubernetesCluster.getMinSize());
response.setMaxSize(kubernetesCluster.getMaxSize());
response.setClusterType(kubernetesCluster.getClusterType());
response.setCsiEnabled(kubernetesCluster.isCsiEnabled());
response.setCreated(kubernetesCluster.getCreated());

return response;
Expand Down Expand Up @@ -1598,6 +1617,7 @@ public KubernetesClusterVO doInTransaction(TransactionStatus status) {
if (zone.isSecurityGroupEnabled()) {
newCluster.setSecurityGroupId(finalSecurityGroup.getId());
}
newCluster.setCsiEnabled(cmd.getEnableCsi());
kubernetesClusterDao.persist(newCluster);
addKubernetesClusterDetails(newCluster, defaultNetwork, cmd);
return newCluster;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ public class KubernetesClusterVO implements KubernetesCluster {
@Column(name = "cni_config_details", updatable = true, length = 4096)
private String cniConfigDetails;

@Column(name = "csi_enabled")
private boolean csiEnabled;

@Override
public long getId() {
return id;
Expand Down Expand Up @@ -389,6 +392,14 @@ public void setClusterType(ClusterType clusterType) {
this.clusterType = clusterType;
}

public boolean isCsiEnabled() {
return csiEnabled;
}

public void setCsiEnabled(boolean csiEnabled) {
this.csiEnabled = csiEnabled;
}

public KubernetesClusterVO() {
this.uuid = UUID.randomUUID().toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,17 @@ public class KubernetesClusterActionWorker {

protected final String deploySecretsScriptFilename = "deploy-cloudstack-secret";
protected final String deployProviderScriptFilename = "deploy-provider";
protected final String deployCsiDriverScriptFilename = "deploy-csi-driver";
protected final String deletePvScriptFilename = "delete-pv-reclaimpolicy-delete";
protected final String autoscaleScriptFilename = "autoscale-kube-cluster";
protected final String validateNodeScript = "validate-cks-node";
protected final String removeNodeFromClusterScript = "remove-node-from-cluster";
protected final String scriptPath = "/opt/bin/";
protected File deploySecretsScriptFile;
protected File deployProviderScriptFile;
protected File deployCsiDriverScriptFile;
protected File autoscaleScriptFile;
protected File deletePvScriptFile;
protected KubernetesClusterManagerImpl manager;
protected String[] keys;

Expand Down Expand Up @@ -715,12 +719,16 @@ protected File retrieveScriptFile(String filename) {
protected void retrieveScriptFiles() {
deploySecretsScriptFile = retrieveScriptFile(deploySecretsScriptFilename);
deployProviderScriptFile = retrieveScriptFile(deployProviderScriptFilename);
deployCsiDriverScriptFile = retrieveScriptFile(deployCsiDriverScriptFilename);
deletePvScriptFile = retrieveScriptFile(deletePvScriptFilename);
autoscaleScriptFile = retrieveScriptFile(autoscaleScriptFilename);
}

protected void copyScripts(String nodeAddress, final int sshPort) {
copyScriptFile(nodeAddress, sshPort, deploySecretsScriptFile, deploySecretsScriptFilename);
copyScriptFile(nodeAddress, sshPort, deployProviderScriptFile, deployProviderScriptFilename);
copyScriptFile(nodeAddress, sshPort, deployCsiDriverScriptFile, deployCsiDriverScriptFilename);
copyScriptFile(nodeAddress, sshPort, deletePvScriptFile, deletePvScriptFilename);
copyScriptFile(nodeAddress, sshPort, autoscaleScriptFile, autoscaleScriptFilename);
}

Expand Down Expand Up @@ -821,6 +829,43 @@ protected boolean deployProvider() {
}
}

protected boolean deployCsiDriver() {
File pkFile = getManagementServerSshPublicKeyFile();
Pair<String, Integer> publicIpSshPort = getKubernetesClusterServerIpSshPort(null);
publicIpAddress = publicIpSshPort.first();
sshPort = publicIpSshPort.second();

try {
String command = String.format("sudo %s/%s", scriptPath, deployCsiDriverScriptFilename);
Pair<Boolean, String> result = SshHelper.sshExecute(publicIpAddress, sshPort, getControlNodeLoginUser(),
pkFile, null, command, 10000, 10000, 60000);

// Maybe the file isn't present. Try and copy it
if (!result.first()) {
logMessage(Level.INFO, "CSI files missing. Adding them now", null);
retrieveScriptFiles();
copyScripts(publicIpAddress, sshPort);

if (!createCloudStackSecret(keys)) {
logTransitStateAndThrow(Level.ERROR, String.format("Failed to setup keys for Kubernetes cluster %s",
kubernetesCluster.getName()), kubernetesCluster.getId(), KubernetesCluster.Event.OperationFailed);
}

// If at first you don't succeed ...
result = SshHelper.sshExecute(publicIpAddress, sshPort, getControlNodeLoginUser(),
pkFile, null, command, 10000, 10000, 60000);
if (!result.first()) {
throw new CloudRuntimeException(result.second());
}
}
return true;
} catch (Exception e) {
String msg = String.format("Failed to deploy kubernetes provider: %s : %s", kubernetesCluster.getName(), e.getMessage());
logAndThrow(Level.ERROR, msg);
return false;
}
}

public void setKeys(String[] keys) {
this.keys = keys;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ private boolean destroyClusterVMs() {
ApiCommandResourceType.VirtualMachine);
vmContext.setEventResourceId(vmID);
try {
if (clusterVM.isControlNode() && kubernetesCluster.isCsiEnabled()) {
deletePVsWithReclaimPolicyDelete();
}
UserVm vm = userVmService.destroyVm(vmID, true);
if (!userVmManager.expunge(userVM)) {
logger.warn("Unable to expunge VM {}, destroying Kubernetes cluster will probably fail", vm);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -944,4 +944,47 @@ protected boolean autoscaleCluster(boolean enable, Long minSize, Long maxSize) {
protected List<DedicatedResourceVO> listDedicatedHostsInDomain(Long domainId) {
return dedicatedResourceDao.listByDomainId(domainId);
}

public boolean deletePVsWithReclaimPolicyDelete() {
File pkFile = getManagementServerSshPublicKeyFile();
Pair<String, Integer> publicIpSshPort = getKubernetesClusterServerIpSshPort(null);
publicIpAddress = publicIpSshPort.first();
sshPort = publicIpSshPort.second();
try {
String command = String.format("sudo %s/%s", scriptPath, deletePvScriptFilename);
logMessage(Level.INFO, "Starting PV deletion script for cluster: " + kubernetesCluster.getName(), null);
Pair<Boolean, String> result = SshHelper.sshExecute(publicIpAddress, sshPort, getControlNodeLoginUser(),
pkFile, null, command, 10000, 10000, 600000); // 10 minute timeout
if (Boolean.FALSE.equals(result.first())) {
logMessage(Level.INFO, "PV delete script missing. Adding it now", null);
retrieveScriptFiles();
if (deletePvScriptFile != null) {
copyScriptFile(publicIpAddress, sshPort, deletePvScriptFile, deletePvScriptFilename);
logMessage(Level.INFO, "Executing PV deletion script (this may take several minutes)...", null);
result = SshHelper.sshExecute(publicIpAddress, sshPort, getControlNodeLoginUser(),
pkFile, null, command, 10000, 10000, 600000); // 10 minute timeout
if (Boolean.FALSE.equals(result.first())) {
logMessage(Level.ERROR, "PV deletion script failed: " + result.second(), null);
throw new CloudRuntimeException(result.second());
}
logMessage(Level.INFO, "PV deletion script completed successfully", null);
} else {
logMessage(Level.WARN, "PV delete script file not found in resources, skipping PV deletion", null);
return false;
}
} else {
logMessage(Level.INFO, "PV deletion script completed successfully", null);
}

if (result.second() != null && !result.second().trim().isEmpty()) {
logMessage(Level.INFO, "PV deletion script output: " + result.second(), null);
}

return true;
} catch (Exception e) {
String msg = String.format("Failed to delete PVs with reclaimPolicy=Delete: %s : %s", kubernetesCluster.getName(), e.getMessage());
logMessage(Level.WARN, msg, e);
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private boolean isKubernetesVersionSupportsHA() {

private Pair<String, String> getKubernetesControlNodeConfig(final String controlNodeIp, final String serverIp,
final List<Network.IpAddresses> etcdIps, final String hostName, final boolean haSupported,
final boolean ejectIso, final boolean externalCni) throws IOException {
final boolean ejectIso, final boolean externalCni, final boolean setupCsi) throws IOException {
String k8sControlNodeConfig = readK8sConfigFile("/conf/k8s-control-node.yml");
final String apiServerCert = "{{ k8s_control_node.apiserver.crt }}";
final String apiServerKey = "{{ k8s_control_node.apiserver.key }}";
Expand All @@ -161,6 +161,7 @@ private Pair<String, String> getKubernetesControlNodeConfig(final String control
final String certSans = "{{ k8s_control.server_ips }}";
final String k8sCertificate = "{{ k8s_control.certificate_key }}";
final String externalCniPlugin = "{{ k8s.external.cni.plugin }}";
final String setupCsiDriver = "{{ k8s.setup.csi.driver }}";

final List<String> addresses = new ArrayList<>();
addresses.add(controlNodeIp);
Expand Down Expand Up @@ -212,6 +213,7 @@ private Pair<String, String> getKubernetesControlNodeConfig(final String control
k8sControlNodeConfig = k8sControlNodeConfig.replace(certSans, String.format("- %s", serverIp));
k8sControlNodeConfig = k8sControlNodeConfig.replace(k8sCertificate, KubernetesClusterUtil.generateClusterHACertificateKey(kubernetesCluster));
k8sControlNodeConfig = k8sControlNodeConfig.replace(externalCniPlugin, String.valueOf(externalCni));
k8sControlNodeConfig = k8sControlNodeConfig.replace(setupCsiDriver, String.valueOf(setupCsi));

k8sControlNodeConfig = updateKubeConfigWithRegistryDetails(k8sControlNodeConfig);

Expand Down Expand Up @@ -246,7 +248,7 @@ private Pair<UserVm,String> createKubernetesControlNode(final Network network, S
Long userDataId = kubernetesCluster.getCniConfigId();
Pair<String, String> k8sControlNodeConfigAndControlIp = new Pair<>(null, null);
try {
k8sControlNodeConfigAndControlIp = getKubernetesControlNodeConfig(controlNodeIp, serverIp, etcdIps, hostName, haSupported, Hypervisor.HypervisorType.VMware.equals(clusterTemplate.getHypervisorType()), Objects.nonNull(userDataId));
k8sControlNodeConfigAndControlIp = getKubernetesControlNodeConfig(controlNodeIp, serverIp, etcdIps, hostName, haSupported, Hypervisor.HypervisorType.VMware.equals(clusterTemplate.getHypervisorType()), Objects.nonNull(userDataId), kubernetesCluster.isCsiEnabled());
} catch (IOException e) {
logAndThrow(Level.ERROR, "Failed to read Kubernetes control node configuration file", e);
}
Expand Down Expand Up @@ -858,6 +860,9 @@ public boolean startKubernetesClusterOnCreate(Long domainId, Long accountId, Lon
}
taintControlNodes();
deployProvider();
if (kubernetesCluster.isCsiEnabled()) {
deployCsiDriver();
}
updateLoginUserDetails(clusterVMs.stream().map(InternalIdentity::getId).collect(Collectors.toList()));
stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.OperationSucceeded);
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd {
since = "4.21.0")
private Map cniConfigDetails;

@Parameter(name = ApiConstants.ENABLE_CSI, type = CommandType.BOOLEAN, description = "if true, setups up CloudStack CSI driver", since = "4.21.0")
private Boolean enableCsi;

@Parameter(name=ApiConstants.AS_NUMBER, type=CommandType.LONG, description="the AS Number of the network")
private Long asNumber;

Expand Down Expand Up @@ -371,6 +374,10 @@ public Long getCniConfigId() {
return cniConfigId;
}

public boolean getEnableCsi() {
return Objects.nonNull(enableCsi) ? enableCsi : Boolean.FALSE;
}

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ public class KubernetesClusterResponse extends BaseResponseWithAnnotations imple
@Param(description = "Maximum size of the cluster")
private Long maxSize;

@SerializedName(ApiConstants.CSI_ENABLED)
@Param(description = "Indicates if the CloudStack CSI driver has been setup in the cluster")
private Boolean isCsiEnabled;

@SerializedName(ApiConstants.CLUSTER_TYPE)
@Param(description = "the type of the cluster")
private KubernetesCluster.ClusterType clusterType;
Expand Down Expand Up @@ -515,4 +519,8 @@ public void setCniConfigId(String cniConfigId) {
public void setCniConfigName(String cniConfigName) {
this.cniConfigName = cniConfigName;
}

public void setCsiEnabled(Boolean csiEnabled) {
isCsiEnabled = csiEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ write_files:
mkdir -p /opt/provider
cp "${BINARIES_DIR}/provider.yaml" /opt/provider/provider.yaml
fi
if [ -e "${BINARIES_DIR}/snapshot-crds.yaml" ]; then
mkdir -p /opt/csi
cp "${BINARIES_DIR}/snapshot-crds.yaml" /opt/csi/snapshot-crds.yaml
cp "${BINARIES_DIR}/manifest.yaml" /opt/csi/manifest.yaml
fi

PAUSE_IMAGE=`ctr -n k8s.io images ls -q | grep "pause" | sort | tail -n 1`
echo $PAUSE_IMAGE
Expand Down
Loading