native windows support
This commit is contained in:
parent
7fe916c916
commit
7a5b6b58b1
|
|
@ -1,2 +1,4 @@
|
||||||
|
**~
|
||||||
node_modules
|
node_modules
|
||||||
dev
|
dev
|
||||||
|
/ci/bin/*dev*
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,7 @@ async function requestHandlerProxy(call, callback, serviceMethodName) {
|
||||||
|
|
||||||
// for testing purposes
|
// for testing purposes
|
||||||
//await GeneralUtils.sleep(10000);
|
//await GeneralUtils.sleep(10000);
|
||||||
|
//throw new Error("fake error");
|
||||||
|
|
||||||
// for CI/testing purposes
|
// for CI/testing purposes
|
||||||
if (["NodePublishVolume", "NodeStageVolume"].includes(serviceMethodName)) {
|
if (["NodePublishVolume", "NodeStageVolume"].includes(serviceMethodName)) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
|
||||||
|
# install deps
|
||||||
|
npm i
|
||||||
|
|
||||||
|
# tar node_modules to keep the number of files low to upload
|
||||||
|
tar -zcf node_modules.tar.gz node_modules
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#Set-StrictMode -Version Latest
|
||||||
|
#$ErrorActionPreference = "Stop"
|
||||||
|
#$PSDefaultParameterValues['*:ErrorAction'] = "Stop"
|
||||||
|
function ThrowOnNativeFailure {
|
||||||
|
if (-not $?) {
|
||||||
|
throw 'Native Failure'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function psenvsubstr($data) {
|
||||||
|
foreach($v in Get-ChildItem env:) {
|
||||||
|
$key = '${' + $v.Name + '}'
|
||||||
|
$data = $data.Replace($key, $v.Value)
|
||||||
|
}
|
||||||
|
return $data
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
if (! $PSScriptRoot) {
|
||||||
|
$PSScriptRoot = $args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
. "${PSScriptRoot}\helper.ps1"
|
||||||
|
|
||||||
|
Set-Location $env:PWD
|
||||||
|
|
||||||
|
Write-Output "launching csi-grpc-proxy"
|
||||||
|
|
||||||
|
$env:PROXY_TO = "npipe://" + $env:NPIPE_ENDPOINT
|
||||||
|
$env:BIND_TO = "unix://" + $env:CSI_ENDPOINT
|
||||||
|
|
||||||
|
# https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide
|
||||||
|
csi-grpc-proxy.exe 2>&1 | % { "$_" }
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
if (! $PSScriptRoot) {
|
||||||
|
$PSScriptRoot = $args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
. "${PSScriptRoot}\helper.ps1"
|
||||||
|
|
||||||
|
Set-Location $env:PWD
|
||||||
|
|
||||||
|
$exit_code = 0
|
||||||
|
$tmpdir = New-Item -ItemType Directory -Path ([System.IO.Path]::GetTempPath()) -Name ([System.IO.Path]::GetRandomFileName())
|
||||||
|
$env:CSI_SANITY_TEMP_DIR = $tmpdir.FullName
|
||||||
|
|
||||||
|
if (! $env:CSI_SANITY_FOCUS) {
|
||||||
|
$env:CSI_SANITY_FOCUS = "Node Service"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $env:CSI_SANITY_SKIP) {
|
||||||
|
$env:CSI_SANITY_SKIP = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# cleanse endpoint to something csi-sanity plays nicely with
|
||||||
|
$endpoint = ${env:CSI_ENDPOINT}
|
||||||
|
$endpoint = $endpoint.replace("C:\", "/")
|
||||||
|
$endpoint = $endpoint.replace("\", "/")
|
||||||
|
|
||||||
|
Write-Output "launching csi-sanity"
|
||||||
|
Write-Output "connecting to: ${endpoint}"
|
||||||
|
Write-Output "skip: ${env:CSI_SANITY_SKIP}"
|
||||||
|
Write-Output "focus: ${env:CSI_SANITY_FOCUS}"
|
||||||
|
|
||||||
|
csi-sanity.exe -"csi.endpoint" "unix://${endpoint}" `
|
||||||
|
-"ginkgo.failFast" `
|
||||||
|
-"csi.mountdir" "${env:CSI_SANITY_TEMP_DIR}\mnt" `
|
||||||
|
-"csi.stagingdir" "${env:CSI_SANITY_TEMP_DIR}\stage" `
|
||||||
|
-"csi.testvolumeexpandsize" 2147483648 `
|
||||||
|
-"csi.testvolumesize" 1073741824 `
|
||||||
|
-"ginkgo.focus" "${env:CSI_SANITY_FOCUS}"
|
||||||
|
|
||||||
|
# does not work the same as linux for some reason
|
||||||
|
#-"ginkgo.skip" "${env:CSI_SANITY_SKIP}" `
|
||||||
|
|
||||||
|
if (-not $?) {
|
||||||
|
$exit_code = $LASTEXITCODE
|
||||||
|
Write-Output "csi-sanity exit code: ${exit_code}"
|
||||||
|
$exit_code = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# remove tmp dir
|
||||||
|
Remove-Item -Path "$env:CSI_SANITY_TEMP_DIR" -Force -Recurse
|
||||||
|
|
||||||
|
#Exit $exit_code
|
||||||
|
Write-Output "exiting with exit code: ${exit_code}"
|
||||||
|
|
||||||
|
if ($exit_code -gt 0) {
|
||||||
|
throw "csi-sanity failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# these do not work for whatever reason
|
||||||
|
#Exit $exit_code
|
||||||
|
#[System.Environment]::Exit($exit_code)
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
if (! $PSScriptRoot) {
|
||||||
|
$PSScriptRoot = $args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
. "${PSScriptRoot}\helper.ps1"
|
||||||
|
|
||||||
|
Set-Location $env:PWD
|
||||||
|
Write-Output "launching server"
|
||||||
|
|
||||||
|
$env:LOG_LEVEL = "debug"
|
||||||
|
$env:CSI_VERSION = "1.5.0"
|
||||||
|
$env:CSI_NAME = "driver-test"
|
||||||
|
$env:CSI_SANITY = "1"
|
||||||
|
|
||||||
|
if (! ${env:CONFIG_FILE}) {
|
||||||
|
$env:CONFIG_FILE = $env:TEMP + "\csi-config-" + $env:CI_BUILD_KEY + ".yaml"
|
||||||
|
if ($env:TEMPLATE_CONFIG_FILE) {
|
||||||
|
$config_data = Get-Content "${env:TEMPLATE_CONFIG_FILE}" -Raw
|
||||||
|
$config_data = psenvsubstr($config_data)
|
||||||
|
$config_data | Set-Content "${env:CONFIG_FILE}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node "${PSScriptRoot}\..\..\bin\democratic-csi" `
|
||||||
|
--log-level "$env:LOG_LEVEL" `
|
||||||
|
--driver-config-file "$env:CONFIG_FILE" `
|
||||||
|
--csi-version "$env:CSI_VERSION" `
|
||||||
|
--csi-name "$env:CSI_NAME" `
|
||||||
|
--server-socket "${env:NPIPE_ENDPOINT}" 2>&1 | % { "$_" }
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
# https://stackoverflow.com/questions/2095088/error-when-calling-3rd-party-executable-from-powershell-when-using-an-ide
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# $mypath = $MyInvocation.MyCommand.Path
|
||||||
|
# Get-ChildItem env:\
|
||||||
|
# Get-Job | Where-Object -Property State -eq “Running”
|
||||||
|
# Get-Location (like pwd)
|
||||||
|
# if ($null -eq $env:FOO) { $env:FOO = 'bar' }
|
||||||
|
|
||||||
|
. "${PSScriptRoot}\helper.ps1"
|
||||||
|
|
||||||
|
function Job-Cleanup() {
|
||||||
|
Get-Job | Stop-Job
|
||||||
|
Get-Job | Remove-Job
|
||||||
|
}
|
||||||
|
|
||||||
|
# start clean
|
||||||
|
Job-Cleanup
|
||||||
|
|
||||||
|
# install from artifacts
|
||||||
|
if (Test-Path "node_modules.tar.gz") {
|
||||||
|
Write-Output "extracting node_modules.tar.gz"
|
||||||
|
tar -zxf node_modules.tar.gz
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup env
|
||||||
|
$env:PWD = (Get-Location).Path
|
||||||
|
$env:CI_BUILD_KEY = ([guid]::NewGuid() -Split "-")[0]
|
||||||
|
$env:CSI_ENDPOINT = $env:TEMP + "\csi-sanity-" + $env:CI_BUILD_KEY + ".sock"
|
||||||
|
$env:NPIPE_ENDPOINT = "//./pipe/csi-sanity-" + $env:CI_BUILD_KEY + "csi.sock"
|
||||||
|
|
||||||
|
# testing values
|
||||||
|
if (Test-Path "${PSScriptRoot}\run-dev.ps1") {
|
||||||
|
. "${PSScriptRoot}\run-dev.ps1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# launch server
|
||||||
|
$server_job = Start-Job -FilePath .\ci\bin\launch-server.ps1 -InitializationScript {} -ArgumentList $PSScriptRoot
|
||||||
|
|
||||||
|
# launch csi-grpc-proxy
|
||||||
|
$csi_grpc_proxy_job = Start-Job -FilePath .\ci\bin\launch-csi-grpc-proxy.ps1 -InitializationScript {} -ArgumentList $PSScriptRoot
|
||||||
|
|
||||||
|
# wait for socket to appear
|
||||||
|
$iter = 0
|
||||||
|
$max_iter = 60
|
||||||
|
$started = 1
|
||||||
|
while (!(Test-Path "${env:CSI_ENDPOINT}")) {
|
||||||
|
$iter++
|
||||||
|
Write-Output "Waiting for ${env:CSI_ENDPOINT} to appear"
|
||||||
|
Start-Sleep 1
|
||||||
|
Get-Job | Receive-Job
|
||||||
|
if ($iter -gt $max_iter) {
|
||||||
|
Write-Output "${env:CSI_ENDPOINT} failed to appear"
|
||||||
|
$started = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# launch csi-sanity
|
||||||
|
if ($started -eq 1) {
|
||||||
|
$csi_sanity_job = Start-Job -FilePath .\ci\bin\launch-csi-sanity.ps1 -InitializationScript {} -ArgumentList $PSScriptRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
# https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/get-job?view=powershell-7.2
|
||||||
|
# -ChildJobState
|
||||||
|
while ($csi_sanity_job -and ($csi_sanity_job.State -eq "Running" -or $csi_sanity_job.State -eq "NotStarted")) {
|
||||||
|
foreach ($job in Get-Job) {
|
||||||
|
try {
|
||||||
|
$job | Receive-Job
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
if ($job.State -ne "Failed") {
|
||||||
|
throw $_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# spew any remaining job output to the console
|
||||||
|
foreach ($job in Get-Job) {
|
||||||
|
try {
|
||||||
|
$job | Receive-Job
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# wait for good measure
|
||||||
|
if ($csi_sanity_job) {
|
||||||
|
Wait-Job -Job $csi_sanity_job
|
||||||
|
}
|
||||||
|
|
||||||
|
#Get-Job | fl
|
||||||
|
|
||||||
|
$exit_code = 0
|
||||||
|
|
||||||
|
if (! $csi_sanity_job) {
|
||||||
|
$exit_code = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($csi_sanity_job -and $csi_sanity_job.State -eq "Failed") {
|
||||||
|
$exit_code = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# cleanup after ourselves
|
||||||
|
Job-Cleanup
|
||||||
|
Exit $exit_code
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
driver: zfs-generic-iscsi
|
||||||
|
|
||||||
|
sshConnection:
|
||||||
|
host: ${SERVER_HOST}
|
||||||
|
port: 22
|
||||||
|
username: ${SERVER_USERNAME}
|
||||||
|
password: ${SERVER_PASSWORD}
|
||||||
|
|
||||||
|
zfs:
|
||||||
|
datasetParentName: tank/ci/${CI_BUILD_KEY}/v
|
||||||
|
detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s
|
||||||
|
|
||||||
|
zvolCompression:
|
||||||
|
zvolDedup:
|
||||||
|
zvolEnableReservation: false
|
||||||
|
zvolBlocksize:
|
||||||
|
|
||||||
|
iscsi:
|
||||||
|
targetPortal: ${SERVER_HOST}
|
||||||
|
interface: ""
|
||||||
|
namePrefix: "csi-ci-${CI_BUILD_KEY}"
|
||||||
|
nameSuffix: ""
|
||||||
|
shareStrategy: "targetCli"
|
||||||
|
shareStrategyTargetCli:
|
||||||
|
basename: "iqn.2003-01.org.linux-iscsi.ubuntu-19.x8664"
|
||||||
|
tpg:
|
||||||
|
attributes:
|
||||||
|
authentication: 0
|
||||||
|
generate_node_acls: 1
|
||||||
|
cache_dynamic_acls: 1
|
||||||
|
demo_mode_write_protect: 0
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
driver: zfs-generic-smb
|
||||||
|
|
||||||
|
sshConnection:
|
||||||
|
host: ${SERVER_HOST}
|
||||||
|
port: 22
|
||||||
|
username: ${SERVER_USERNAME}
|
||||||
|
password: ${SERVER_PASSWORD}
|
||||||
|
|
||||||
|
zfs:
|
||||||
|
datasetParentName: tank/ci/${CI_BUILD_KEY}/v
|
||||||
|
detachedSnapshotsDatasetParentName: tank/ci/${CI_BUILD_KEY}/s
|
||||||
|
|
||||||
|
datasetProperties:
|
||||||
|
#aclmode: restricted
|
||||||
|
#aclinherit: passthrough
|
||||||
|
#acltype: nfsv4
|
||||||
|
casesensitivity: insensitive
|
||||||
|
|
||||||
|
datasetEnableQuotas: true
|
||||||
|
datasetEnableReservation: false
|
||||||
|
datasetPermissionsMode: "0770"
|
||||||
|
datasetPermissionsUser: smbroot
|
||||||
|
datasetPermissionsGroup: smbroot
|
||||||
|
|
||||||
|
smb:
|
||||||
|
shareHost: ${SERVER_HOST}
|
||||||
|
shareStrategy: "setDatasetProperties"
|
||||||
|
shareStrategySetDatasetProperties:
|
||||||
|
properties:
|
||||||
|
sharesmb: "on"
|
||||||
|
|
||||||
|
node:
|
||||||
|
mount:
|
||||||
|
mount_flags: "username=smbroot,password=smbroot"
|
||||||
|
|
||||||
|
_private:
|
||||||
|
csi:
|
||||||
|
volume:
|
||||||
|
idHash:
|
||||||
|
strategy: crc16
|
||||||
|
|
@ -1327,7 +1327,10 @@ class ControllerZfsBaseDriver extends CsiBaseDriver {
|
||||||
await zb.zfs.destroy(datasetName, { recurse: true, force: true });
|
await zb.zfs.destroy(datasetName, { recurse: true, force: true });
|
||||||
success = true;
|
success = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.toString().includes("dataset is busy")) {
|
if (
|
||||||
|
err.toString().includes("dataset is busy") ||
|
||||||
|
err.toString().includes("target is busy")
|
||||||
|
) {
|
||||||
current_try++;
|
current_try++;
|
||||||
if (current_try > max_tries) {
|
if (current_try > max_tries) {
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ const __REGISTRY_NS__ = "CsiBaseDriver";
|
||||||
|
|
||||||
const NODE_OS_DRIVER_CSI_PROXY = "csi-proxy";
|
const NODE_OS_DRIVER_CSI_PROXY = "csi-proxy";
|
||||||
const NODE_OS_DRIVER_POSIX = "posix";
|
const NODE_OS_DRIVER_POSIX = "posix";
|
||||||
|
const NODE_OS_DRIVER_WINDOWS = "windows";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* common code shared between all drivers
|
* common code shared between all drivers
|
||||||
|
|
@ -199,10 +200,14 @@ class CsiBaseDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
__getNodeOsDriver() {
|
__getNodeOsDriver() {
|
||||||
if (this.getNodeIsWindows() || this.getCsiProxyEnabled()) {
|
if (this.getNodeIsWindows()) {
|
||||||
return NODE_OS_DRIVER_CSI_PROXY;
|
return NODE_OS_DRIVER_WINDOWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if (this.getNodeIsWindows() || this.getCsiProxyEnabled()) {
|
||||||
|
// return NODE_OS_DRIVER_CSI_PROXY;
|
||||||
|
//}
|
||||||
|
|
||||||
return NODE_OS_DRIVER_POSIX;
|
return NODE_OS_DRIVER_POSIX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1215,6 +1220,383 @@ class CsiBaseDriver {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case NODE_OS_DRIVER_WINDOWS:
|
||||||
|
// sanity check node_attach_driver
|
||||||
|
if (!["smb", "iscsi"].includes(node_attach_driver)) {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.UNIMPLEMENTED,
|
||||||
|
`csi-proxy does not work with node_attach_driver: ${node_attach_driver}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity check fs_type
|
||||||
|
if (fs_type && !["ntfs", "cifs"].includes(fs_type)) {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.UNIMPLEMENTED,
|
||||||
|
`csi-proxy does not work with fs_type: ${fs_type}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const WindowsUtils = require("../utils/windows").Windows;
|
||||||
|
const wutils = new WindowsUtils();
|
||||||
|
|
||||||
|
switch (node_attach_driver) {
|
||||||
|
case "smb":
|
||||||
|
device = `//${volume_context.server}/${volume_context.share}`;
|
||||||
|
const username = driver.getMountFlagValue(mount_flags, "username");
|
||||||
|
const password = driver.getMountFlagValue(mount_flags, "password");
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
throw new Error("username and password required");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* smb mount creates a link at this location and if the dir already exists
|
||||||
|
* it explodes
|
||||||
|
*
|
||||||
|
* if path exists but is NOT symlink delete it
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
fs.statSync(staging_target_path);
|
||||||
|
result = true;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === "ENOENT") {
|
||||||
|
result = false;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
result = fs.lstatSync(staging_target_path);
|
||||||
|
if (!result.isSymbolicLink()) {
|
||||||
|
fs.rmdirSync(staging_target_path);
|
||||||
|
} else {
|
||||||
|
result = await wutils.GetItem(staging_target_path);
|
||||||
|
// UNC\172.29.0.111\tank_k8s_test_PVC_111\
|
||||||
|
let target = _.get(result, "Target.[0]", "");
|
||||||
|
let parts = target.split("\\");
|
||||||
|
if (
|
||||||
|
parts[1] != volume_context.server &&
|
||||||
|
parts[2] != volume_context.share
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`${target} mounted already at ${staging_target_path}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// finish early, assured we have what we need
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = await wutils.GetSmbGlobalMapping(
|
||||||
|
filesystem.covertUnixSeparatorToWindowsSeparator(device)
|
||||||
|
);
|
||||||
|
if (!result) {
|
||||||
|
await wutils.NewSmbGlobalMapping(
|
||||||
|
filesystem.covertUnixSeparatorToWindowsSeparator(device),
|
||||||
|
`${volume_context.server}\\${username}`,
|
||||||
|
password
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
let details = _.get(e, "stderr", "");
|
||||||
|
if (!details.includes("0x80041001")) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await wutils.NewSmbLink(
|
||||||
|
filesystem.covertUnixSeparatorToWindowsSeparator(device),
|
||||||
|
staging_target_path
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
let details = _.get(e, "stderr", "");
|
||||||
|
if (!details.includes("ResourceExists")) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
result = fs.lstatSync(staging_target_path);
|
||||||
|
if (!result.isSymbolicLink()) {
|
||||||
|
throw new Error("staging path exists but is not symlink");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "iscsi":
|
||||||
|
switch (access_type) {
|
||||||
|
case "mount":
|
||||||
|
let portals = [];
|
||||||
|
if (volume_context.portal) {
|
||||||
|
portals.push(volume_context.portal.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volume_context.portals) {
|
||||||
|
volume_context.portals.split(",").forEach((portal) => {
|
||||||
|
portals.push(portal.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure full portal value
|
||||||
|
portals = portals.map((value) => {
|
||||||
|
if (!value.includes(":")) {
|
||||||
|
value += ":3260";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ensure unique entries only
|
||||||
|
portals = [...new Set(portals)];
|
||||||
|
|
||||||
|
// stores configuration of targets/iqn/luns to connect to
|
||||||
|
let iscsiConnections = [];
|
||||||
|
for (let portal of portals) {
|
||||||
|
iscsiConnections.push({
|
||||||
|
portal,
|
||||||
|
iqn: volume_context.iqn,
|
||||||
|
lun: volume_context.lun,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let successful_logins = 0;
|
||||||
|
let multipath = iscsiConnections.length > 1;
|
||||||
|
|
||||||
|
// no multipath support yet
|
||||||
|
// https://github.com/kubernetes-csi/csi-proxy/pull/99
|
||||||
|
for (let iscsiConnection of iscsiConnections) {
|
||||||
|
// add target portal
|
||||||
|
let parts = iscsiConnection.portal.split(":");
|
||||||
|
let target_address = parts[0];
|
||||||
|
let target_port = parts[1] || "3260";
|
||||||
|
|
||||||
|
// this is idempotent
|
||||||
|
try {
|
||||||
|
await wutils.NewIscsiTargetPortal(
|
||||||
|
target_address,
|
||||||
|
target_port
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
driver.ctx.logger.warn(
|
||||||
|
`failed adding target portal: ${JSON.stringify(
|
||||||
|
iscsiConnection
|
||||||
|
)}: ${e.stderr}`
|
||||||
|
);
|
||||||
|
if (!multipath) {
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// login
|
||||||
|
try {
|
||||||
|
let auth_type = "NONE";
|
||||||
|
let chap_username = "";
|
||||||
|
let chap_secret = "";
|
||||||
|
if (
|
||||||
|
normalizedSecrets[
|
||||||
|
"node-db.node.session.auth.authmethod"
|
||||||
|
] == "CHAP"
|
||||||
|
) {
|
||||||
|
// set auth_type
|
||||||
|
if (
|
||||||
|
normalizedSecrets[
|
||||||
|
"node-db.node.session.auth.username"
|
||||||
|
] &&
|
||||||
|
normalizedSecrets[
|
||||||
|
"node-db.node.session.auth.password"
|
||||||
|
] &&
|
||||||
|
normalizedSecrets[
|
||||||
|
"node-db.node.session.auth.username_in"
|
||||||
|
] &&
|
||||||
|
normalizedSecrets[
|
||||||
|
"node-db.node.session.auth.password_in"
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
auth_type = "MUTUAL_CHAP";
|
||||||
|
} else if (
|
||||||
|
normalizedSecrets[
|
||||||
|
"node-db.node.session.auth.username"
|
||||||
|
] &&
|
||||||
|
normalizedSecrets["node-db.node.session.auth.password"]
|
||||||
|
) {
|
||||||
|
auth_type = "ONE_WAY_CHAP";
|
||||||
|
}
|
||||||
|
|
||||||
|
// set credentials
|
||||||
|
if (
|
||||||
|
normalizedSecrets[
|
||||||
|
"node-db.node.session.auth.username"
|
||||||
|
] &&
|
||||||
|
normalizedSecrets["node-db.node.session.auth.password"]
|
||||||
|
) {
|
||||||
|
chap_username =
|
||||||
|
normalizedSecrets[
|
||||||
|
"node-db.node.session.auth.username"
|
||||||
|
];
|
||||||
|
|
||||||
|
chap_secret =
|
||||||
|
normalizedSecrets[
|
||||||
|
"node-db.node.session.auth.password"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await wutils.ConnectIscsiTarget(
|
||||||
|
target_address,
|
||||||
|
target_port,
|
||||||
|
iscsiConnection.iqn,
|
||||||
|
auth_type,
|
||||||
|
chap_username,
|
||||||
|
chap_secret,
|
||||||
|
multipath
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
let details = _.get(e, "stderr", "");
|
||||||
|
if (
|
||||||
|
!details.includes(
|
||||||
|
"The target has already been logged in via an iSCSI session"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
driver.ctx.logger.warn(
|
||||||
|
`failed connection to ${JSON.stringify(
|
||||||
|
iscsiConnection
|
||||||
|
)}: ${e.stderr}`
|
||||||
|
);
|
||||||
|
if (!multipath) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// discover?
|
||||||
|
//await csiProxyClient.executeRPC("iscsi", "DiscoverTargetPortal", {
|
||||||
|
// target_portal,
|
||||||
|
//});
|
||||||
|
successful_logins++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iscsiConnections.length != successful_logins) {
|
||||||
|
driver.ctx.logger.warn(
|
||||||
|
`failed to login to all portals: total - ${iscsiConnections.length}, logins - ${successful_logins}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// let things settle
|
||||||
|
// this will help in dm scenarios
|
||||||
|
await GeneralUtils.sleep(2000);
|
||||||
|
|
||||||
|
// rescan
|
||||||
|
await wutils.UpdateHostStorageCache();
|
||||||
|
|
||||||
|
// get device
|
||||||
|
let disks = await wutils.GetTargetDisksByIqnLun(
|
||||||
|
volume_context.iqn,
|
||||||
|
volume_context.lun
|
||||||
|
);
|
||||||
|
let disk;
|
||||||
|
|
||||||
|
if (disks.length == 0) {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.UNAVAILABLE,
|
||||||
|
`0 disks created by ${successful_logins} successful logins`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disks.length > 1) {
|
||||||
|
if (multipath) {
|
||||||
|
let disk_number_set = new Set();
|
||||||
|
disks.forEach((i_disk) => {
|
||||||
|
disk_number_set.add(i_disk.DiskNumber);
|
||||||
|
});
|
||||||
|
if (disk_number_set.length > 1) {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.FAILED_PRECONDITION,
|
||||||
|
"using multipath but mpio is not properly configured (multiple disk numbers with same iqn/lun)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// find first disk that is online
|
||||||
|
disk = disks.find((i_disk) => {
|
||||||
|
return i_disk.OperationalStatus == "Online";
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!disk) {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.FAILED_PRECONDITION,
|
||||||
|
"using multipath but mpio is not properly configured (failed to detect an online disk)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.FAILED_PRECONDITION,
|
||||||
|
`not using multipath but discovered ${disks.length} disks (multiple disks with same iqn/lun)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
disk = disks[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multipath && !disk.Path.startsWith("\\\\?\\mpio#")) {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.FAILED_PRECONDITION,
|
||||||
|
"using multipath but mpio is not properly configured (discover disk is not an mpio disk)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// needs to be initialized
|
||||||
|
await wutils.PartitionDisk(disk.DiskNumber);
|
||||||
|
|
||||||
|
let partition = await wutils.GetLastPartitionByDiskNumber(
|
||||||
|
disk.DiskNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
let volume = await wutils.GetVolumeByDiskNumberPartitionNumber(
|
||||||
|
disk.DiskNumber,
|
||||||
|
partition.PartitionNumber
|
||||||
|
);
|
||||||
|
if (!volume) {
|
||||||
|
throw new Error("failed to create/discover volume for disk");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await wutils.VolumeIsFormatted(volume.UniqueId);
|
||||||
|
if (!result) {
|
||||||
|
// format device
|
||||||
|
await wutils.FormatVolume(volume.UniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await wutils.GetItem(staging_target_path);
|
||||||
|
if (!result) {
|
||||||
|
fs.mkdirSync(staging_target_path, {
|
||||||
|
recursive: true,
|
||||||
|
mode: "755",
|
||||||
|
});
|
||||||
|
result = await wutils.GetItem(staging_target_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!volume.UniqueId.includes(result.Target[0])) {
|
||||||
|
// mount up!
|
||||||
|
await wutils.MountVolume(
|
||||||
|
volume.UniqueId,
|
||||||
|
staging_target_path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "block":
|
||||||
|
default:
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.UNIMPLEMENTED,
|
||||||
|
`access_type ${access_type} unsupported`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.INVALID_ARGUMENT,
|
||||||
|
`unknown/unsupported node_attach_driver: ${node_attach_driver}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case NODE_OS_DRIVER_CSI_PROXY:
|
case NODE_OS_DRIVER_CSI_PROXY:
|
||||||
// sanity check node_attach_driver
|
// sanity check node_attach_driver
|
||||||
if (!["smb", "iscsi"].includes(node_attach_driver)) {
|
if (!["smb", "iscsi"].includes(node_attach_driver)) {
|
||||||
|
|
@ -1546,9 +1928,7 @@ class CsiBaseDriver {
|
||||||
grpc.status.INVALID_ARGUMENT,
|
grpc.status.INVALID_ARGUMENT,
|
||||||
`unknown/unsupported node_attach_driver: ${node_attach_driver}`
|
`unknown/unsupported node_attach_driver: ${node_attach_driver}`
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new GrpcError(
|
throw new GrpcError(
|
||||||
|
|
@ -1798,6 +2178,89 @@ class CsiBaseDriver {
|
||||||
result = await filesystem.rmdir(staging_target_path);
|
result = await filesystem.rmdir(staging_target_path);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case NODE_OS_DRIVER_WINDOWS: {
|
||||||
|
const WindowsUtils = require("../utils/windows").Windows;
|
||||||
|
const wutils = new WindowsUtils();
|
||||||
|
|
||||||
|
async function removePath(p) {
|
||||||
|
// remove staging path
|
||||||
|
try {
|
||||||
|
fs.rmdirSync(p);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== "ENOENT") {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let node_attach_driver;
|
||||||
|
let win_volume_id;
|
||||||
|
|
||||||
|
result = await wutils.GetItem(normalized_staging_path);
|
||||||
|
if (result) {
|
||||||
|
let target = _.get(result, "Target.[0]", "");
|
||||||
|
if (target.startsWith("UNC")) {
|
||||||
|
node_attach_driver = "smb";
|
||||||
|
}
|
||||||
|
if (target.startsWith("Volume")) {
|
||||||
|
win_volume_id = `\\\\?\\${target}`;
|
||||||
|
if (await wutils.VolumeIsIscsi(win_volume_id)) {
|
||||||
|
node_attach_driver = "iscsi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node_attach_driver) {
|
||||||
|
// nothing we care about
|
||||||
|
node_attach_driver = "bypass";
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (node_attach_driver) {
|
||||||
|
case "smb":
|
||||||
|
let parts = target.split("\\");
|
||||||
|
await wutils.RemoveSmbGlobalMapping(
|
||||||
|
`\\\\${parts[1]}\\${parts[2]}`
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "iscsi":
|
||||||
|
// write volume cache
|
||||||
|
await wutils.WriteVolumeCache(win_volume_id);
|
||||||
|
|
||||||
|
// unmount volume
|
||||||
|
await wutils.UnmountVolume(
|
||||||
|
win_volume_id,
|
||||||
|
normalized_staging_path
|
||||||
|
);
|
||||||
|
|
||||||
|
// find sessions associated with volume/disks
|
||||||
|
let sessions = await wutils.GetIscsiSessionsByVolumeId(
|
||||||
|
win_volume_id
|
||||||
|
);
|
||||||
|
|
||||||
|
// logout of sessions
|
||||||
|
for (let session of sessions) {
|
||||||
|
await wutils.DisconnectIscsiTargetByNodeAddress(
|
||||||
|
session.TargetNodeAddress
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete target/target portal/etc
|
||||||
|
// do NOT do this now as removing the portal will remove all targets associated with it
|
||||||
|
break;
|
||||||
|
case "bypass":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.INVALID_ARGUMENT,
|
||||||
|
`unknown/unsupported node_attach_driver: ${node_attach_driver}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove staging path
|
||||||
|
await removePath(normalized_staging_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case NODE_OS_DRIVER_CSI_PROXY:
|
case NODE_OS_DRIVER_CSI_PROXY:
|
||||||
// load up the client instance
|
// load up the client instance
|
||||||
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
||||||
|
|
@ -1891,6 +2354,9 @@ class CsiBaseDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do NOT remove target portal etc, windows handles this quite differently than
|
||||||
|
// linux and removing the portal would remove all the targets/etc
|
||||||
|
/*
|
||||||
try {
|
try {
|
||||||
await csiProxyClient.executeRPC("iscsi", "RemoveTargetPortal", {
|
await csiProxyClient.executeRPC("iscsi", "RemoveTargetPortal", {
|
||||||
target_portal,
|
target_portal,
|
||||||
|
|
@ -1901,6 +2367,7 @@ class CsiBaseDriver {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -2077,6 +2544,79 @@ class CsiBaseDriver {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case NODE_OS_DRIVER_WINDOWS:
|
||||||
|
const WindowsUtils = require("../utils/windows").Windows;
|
||||||
|
const wutils = new WindowsUtils();
|
||||||
|
|
||||||
|
switch (node_attach_driver) {
|
||||||
|
//case "nfs":
|
||||||
|
case "smb":
|
||||||
|
//case "lustre":
|
||||||
|
//case "oneclient":
|
||||||
|
//case "hostpath":
|
||||||
|
case "iscsi":
|
||||||
|
//case "zfs-local":
|
||||||
|
// ensure appropriate directories/files
|
||||||
|
switch (access_type) {
|
||||||
|
case "mount":
|
||||||
|
break;
|
||||||
|
case "block":
|
||||||
|
default:
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.INVALID_ARGUMENT,
|
||||||
|
`unsupported/unknown access_type ${access_type}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure bind mount
|
||||||
|
if (staging_target_path) {
|
||||||
|
let normalized_staging_path;
|
||||||
|
|
||||||
|
if (access_type == "block") {
|
||||||
|
normalized_staging_path = staging_target_path + "/block_device";
|
||||||
|
} else {
|
||||||
|
normalized_staging_path = staging_target_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// source path
|
||||||
|
result = await wutils.GetItem(normalized_staging_path);
|
||||||
|
if (!result) {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.FAILED_PRECONDITION,
|
||||||
|
`staging path is not mounted: ${normalized_staging_path}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// target path
|
||||||
|
result = await wutils.GetItem(target_path);
|
||||||
|
// already published
|
||||||
|
if (result) {
|
||||||
|
if (_.get(result, "LinkType") != "SymbolicLink") {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.FAILED_PRECONDITION,
|
||||||
|
`target path exists but is not a symlink as it should be: ${target_path}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// create symlink
|
||||||
|
fs.symlinkSync(normalized_staging_path, target_path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsupported filesystem
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.FAILED_PRECONDITION,
|
||||||
|
`only staged configurations are valid`
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.INVALID_ARGUMENT,
|
||||||
|
`unknown/unsupported node_attach_driver: ${node_attach_driver}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case NODE_OS_DRIVER_CSI_PROXY:
|
case NODE_OS_DRIVER_CSI_PROXY:
|
||||||
switch (node_attach_driver) {
|
switch (node_attach_driver) {
|
||||||
//case "nfs":
|
//case "nfs":
|
||||||
|
|
@ -2242,6 +2782,24 @@ class CsiBaseDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case NODE_OS_DRIVER_WINDOWS:
|
||||||
|
const WindowsUtils = require("../utils/windows").Windows;
|
||||||
|
const wutils = new WindowsUtils();
|
||||||
|
|
||||||
|
result = await wutils.GetItem(target_path);
|
||||||
|
if (!result) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_.get(result, "LinkType") != "SymbolicLink") {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.FAILED_PRECONDITION,
|
||||||
|
`target path is not a symlink ${target_path}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.rmdirSync(target_path);
|
||||||
break;
|
break;
|
||||||
case NODE_OS_DRIVER_CSI_PROXY:
|
case NODE_OS_DRIVER_CSI_PROXY:
|
||||||
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
||||||
|
|
@ -2368,6 +2926,61 @@ class CsiBaseDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case NODE_OS_DRIVER_WINDOWS: {
|
||||||
|
const WindowsUtils = require("../utils/windows").Windows;
|
||||||
|
const wutils = new WindowsUtils();
|
||||||
|
|
||||||
|
// ensure path is mounted
|
||||||
|
result = await wutils.GetItem(volume_path);
|
||||||
|
if (!result) {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.NOT_FOUND,
|
||||||
|
`volume_path ${volume_path} is not currently mounted`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let node_attach_driver;
|
||||||
|
|
||||||
|
let target = await wutils.GetRealTarget(volume_path);
|
||||||
|
if (target.startsWith("\\\\")) {
|
||||||
|
node_attach_driver = "smb";
|
||||||
|
}
|
||||||
|
if (target.startsWith("\\\\?\\Volume")) {
|
||||||
|
if (await wutils.VolumeIsIscsi(target)) {
|
||||||
|
node_attach_driver = "iscsi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node_attach_driver) {
|
||||||
|
// nothing we care about
|
||||||
|
node_attach_driver = "bypass";
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (node_attach_driver) {
|
||||||
|
case "smb":
|
||||||
|
res.usage = [{ total: 0, unit: "BYTES" }];
|
||||||
|
break;
|
||||||
|
case "iscsi":
|
||||||
|
let node_volume = await wutils.GetVolumeByVolumeId(target);
|
||||||
|
res.usage = [
|
||||||
|
{
|
||||||
|
available: node_volume.SizeRemaining,
|
||||||
|
total: node_volume.Size,
|
||||||
|
used: node_volume.Size - node_volume.SizeRemaining,
|
||||||
|
unit: "BYTES",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case "bypass":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.INVALID_ARGUMENT,
|
||||||
|
`unknown/unsupported node_attach_driver: ${node_attach_driver}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case NODE_OS_DRIVER_CSI_PROXY:
|
case NODE_OS_DRIVER_CSI_PROXY:
|
||||||
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
||||||
const volume_context = await driver.getDerivedVolumeContext(call);
|
const volume_context = await driver.getDerivedVolumeContext(call);
|
||||||
|
|
@ -2562,6 +3175,56 @@ class CsiBaseDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case NODE_OS_DRIVER_WINDOWS: {
|
||||||
|
const WindowsUtils = require("../utils/windows").Windows;
|
||||||
|
const wutils = new WindowsUtils();
|
||||||
|
|
||||||
|
let node_attach_driver;
|
||||||
|
|
||||||
|
// ensure path is mounted
|
||||||
|
result = await wutils.GetItem(volume_path);
|
||||||
|
if (!result) {
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.NOT_FOUND,
|
||||||
|
`volume_path ${volume_path} is not currently mounted`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = await wutils.GetRealTarget(volume_path);
|
||||||
|
if (target.startsWith("\\\\")) {
|
||||||
|
node_attach_driver = "smb";
|
||||||
|
}
|
||||||
|
if (target.startsWith("\\\\?\\Volume")) {
|
||||||
|
if (await wutils.VolumeIsIscsi(target)) {
|
||||||
|
node_attach_driver = "iscsi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node_attach_driver) {
|
||||||
|
// nothing we care about
|
||||||
|
node_attach_driver = "bypass";
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (node_attach_driver) {
|
||||||
|
case "smb":
|
||||||
|
// noop
|
||||||
|
break;
|
||||||
|
case "iscsi":
|
||||||
|
// rescan devices
|
||||||
|
await wutils.UpdateHostStorageCache();
|
||||||
|
await wutils.ResizeVolume(target);
|
||||||
|
break;
|
||||||
|
case "bypass":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new GrpcError(
|
||||||
|
grpc.status.INVALID_ARGUMENT,
|
||||||
|
`unknown/unsupported node_attach_driver: ${node_attach_driver}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case NODE_OS_DRIVER_CSI_PROXY:
|
case NODE_OS_DRIVER_CSI_PROXY:
|
||||||
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
||||||
const volume_context = await driver.getDerivedVolumeContext(call);
|
const volume_context = await driver.getDerivedVolumeContext(call);
|
||||||
|
|
@ -2606,7 +3269,7 @@ class CsiBaseDriver {
|
||||||
try {
|
try {
|
||||||
await csiProxyClient.executeRPC("volume", "ResizeVolume", {
|
await csiProxyClient.executeRPC("volume", "ResizeVolume", {
|
||||||
volume_id: node_volume_id,
|
volume_id: node_volume_id,
|
||||||
resize_bytes: required_bytes,
|
resize_bytes: 0,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let details = _.get(e, "details", "");
|
let details = _.get(e, "details", "");
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
const _ = require("lodash");
|
const _ = require("lodash");
|
||||||
const grpc = require("./grpc").grpc;
|
const grpc = require("./grpc").grpc;
|
||||||
|
const path = require("path");
|
||||||
const protoLoader = require("@grpc/proto-loader");
|
const protoLoader = require("@grpc/proto-loader");
|
||||||
|
|
||||||
const PROTO_BASE_PATH = __dirname + "/../../csi_proxy_proto";
|
const PROTO_BASE_PATH =
|
||||||
|
path.dirname(path.dirname(__dirname)) + path.sep + "csi_proxy_proto";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* leave connection null as by default the named pipe is derrived
|
* leave connection null as by default the named pipe is derrived
|
||||||
|
|
@ -38,10 +40,20 @@ class CsiProxyClient {
|
||||||
const serviceVersion =
|
const serviceVersion =
|
||||||
service.version || DEFAULT_SERVICES[serviceName].version;
|
service.version || DEFAULT_SERVICES[serviceName].version;
|
||||||
const serviceConnection =
|
const serviceConnection =
|
||||||
|
// HANGS
|
||||||
|
// Http2Session client (38) nghttp2 has 13 bytes to send directly
|
||||||
|
// Http2Session client (38) wants read? 1
|
||||||
|
// Then pipe closes after 60 seconds-ish
|
||||||
service.connection ||
|
service.connection ||
|
||||||
`\\\\.\\\\pipe\\\\${pipePrefix}-${serviceName}-${serviceVersion}`;
|
`unix:////./pipe/${pipePrefix}-${serviceName}-${serviceVersion}`;
|
||||||
|
// EACCESS
|
||||||
|
//service.connection ||
|
||||||
|
//`unix:///csi/${pipePrefix}-${serviceName}-${serviceVersion}`;
|
||||||
|
//service.connection ||
|
||||||
|
//`unix:///csi/csi.sock.internal`;
|
||||||
|
|
||||||
|
const PROTO_PATH = `${PROTO_BASE_PATH}\\${serviceName}\\${serviceVersion}\\api.proto`;
|
||||||
|
|
||||||
const PROTO_PATH = `/${PROTO_BASE_PATH}/${serviceName}/${serviceVersion}/api.proto`;
|
|
||||||
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
|
||||||
keepCase: true,
|
keepCase: true,
|
||||||
longs: String,
|
longs: String,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
const cp = require("child_process");
|
||||||
|
|
||||||
|
class Powershell {
|
||||||
|
async exec(command, options = {}) {
|
||||||
|
if (!options.hasOwnProperty("timeout")) {
|
||||||
|
// TODO: cannot use this as fsck etc are too risky to kill
|
||||||
|
//options.timeout = DEFAULT_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
//cmd := exec.Command("powershell", "-Mta", "-NoProfile", "-Command", command)
|
||||||
|
|
||||||
|
let stdin;
|
||||||
|
if (options.stdin) {
|
||||||
|
stdin = options.stdin;
|
||||||
|
delete options.stdin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/kubernetes-csi/csi-proxy/blob/master/pkg/utils/utils.go
|
||||||
|
const _command = "powershell";
|
||||||
|
const args = [
|
||||||
|
"-Mta",
|
||||||
|
"-NoProfile",
|
||||||
|
"-Command",
|
||||||
|
command
|
||||||
|
];
|
||||||
|
|
||||||
|
let command_log = `${_command} ${args.join(" ")}`.trim();
|
||||||
|
if (stdin) {
|
||||||
|
command_log = `echo '${stdin}' | ${command_log}`
|
||||||
|
.trim()
|
||||||
|
.replace(/\n/, "\\n");
|
||||||
|
}
|
||||||
|
console.log("executing powershell command: %s", command_log);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = cp.spawn(_command, args, options);
|
||||||
|
let stdout = "";
|
||||||
|
let stderr = "";
|
||||||
|
|
||||||
|
child.on("spawn", function () {
|
||||||
|
if (stdin) {
|
||||||
|
child.stdin.setEncoding("utf-8");
|
||||||
|
child.stdin.write(stdin);
|
||||||
|
child.stdin.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdout.on("data", function (data) {
|
||||||
|
stdout = stdout + data;
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on("data", function (data) {
|
||||||
|
stderr = stderr + data;
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("close", function (code) {
|
||||||
|
const result = { code, stdout, stderr, timeout: false };
|
||||||
|
|
||||||
|
// timeout scenario
|
||||||
|
if (code === null) {
|
||||||
|
result.timeout = true;
|
||||||
|
reject(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
console.log(
|
||||||
|
"failed to execute powershell command: %s, response: %j",
|
||||||
|
command_log,
|
||||||
|
result
|
||||||
|
);
|
||||||
|
reject(result);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
result.parsed = JSON.parse(result.stdout);
|
||||||
|
} catch (err) { };
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports.Powershell = Powershell;
|
||||||
|
|
@ -0,0 +1,729 @@
|
||||||
|
const { result } = require("lodash");
|
||||||
|
const _ = require("lodash");
|
||||||
|
const Powershell = require("./powershell").Powershell;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://kubernetes.io/blog/2021/08/16/windows-hostprocess-containers/
|
||||||
|
* https://github.com/kubernetes-csi/csi-proxy/tree/master/pkg/os
|
||||||
|
*
|
||||||
|
* multipath notes:
|
||||||
|
* - http://scst.sourceforge.net/mc_s.html
|
||||||
|
* - https://github.com/kubernetes-csi/csi-proxy/pull/99
|
||||||
|
* - https://docs.microsoft.com/en-us/azure/storsimple/storsimple-8000-configure-mpio-windows-server
|
||||||
|
* - https://support.purestorage.com/Legacy_Documentation/Setting_the_MPIO_Policy
|
||||||
|
* - https://docs.microsoft.com/en-us/powershell/module/mpio/?view=windowsserver2022-ps
|
||||||
|
*
|
||||||
|
* Get-WindowsFeature -Name 'Multipath-IO'
|
||||||
|
* Add-WindowsFeature -Name 'Multipath-IO'
|
||||||
|
*
|
||||||
|
* Enable-MSDSMAutomaticClaim -BusType "iSCSI"
|
||||||
|
* Disable-MSDSMAutomaticClaim -BusType "iSCSI"
|
||||||
|
*
|
||||||
|
* Get-MSDSMGlobalDefaultLoadBalancePolicy
|
||||||
|
* Set-MSDSMGlobalLoadBalancePolicy -Policy RR
|
||||||
|
*
|
||||||
|
* synology woes:
|
||||||
|
* - https://community.spiceworks.com/topic/2279882-synology-iscsi-will-not-disconnect-using-powershell-commands
|
||||||
|
* - https://support.hpe.com/hpesc/public/docDisplay?docId=c01880810&docLocale=en_US
|
||||||
|
* - https://askubuntu.com/questions/1159103/why-is-iscsi-trying-to-connect-on-ipv6-at-boot
|
||||||
|
*/
|
||||||
|
class Windows {
|
||||||
|
constructor() {
|
||||||
|
this.ps = new Powershell();
|
||||||
|
}
|
||||||
|
|
||||||
|
resultToArray(result) {
|
||||||
|
if (!result.parsed) {
|
||||||
|
result.parsed = [];
|
||||||
|
}
|
||||||
|
if (!Array.isArray(result.parsed)) {
|
||||||
|
result.parsed = [result.parsed];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetRealTarget(path) {
|
||||||
|
let item;
|
||||||
|
let target;
|
||||||
|
|
||||||
|
do {
|
||||||
|
item = await this.GetItem(path);
|
||||||
|
path = null;
|
||||||
|
|
||||||
|
target = _.get(item, "Target.[0]", "");
|
||||||
|
if (target.startsWith("UNC")) {
|
||||||
|
let parts = target.split("\\", 3);
|
||||||
|
return `\\\\${parts[1]}\\${parts[2]}`;
|
||||||
|
} else if (target.startsWith("Volume")) {
|
||||||
|
return `\\\\?\\${target}`;
|
||||||
|
} else {
|
||||||
|
path = target;
|
||||||
|
}
|
||||||
|
} while (path);
|
||||||
|
}
|
||||||
|
async GetItem(localPath) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
command = 'Get-Item "$Env:localpath" | ConvertTo-Json';
|
||||||
|
try {
|
||||||
|
result = await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
localpath: localPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return result.parsed;
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetSmbGlobalMapping(remotePath) {
|
||||||
|
let command;
|
||||||
|
command =
|
||||||
|
"Get-SmbGlobalMapping -RemotePath $Env:smbremotepath | ConvertTo-Json";
|
||||||
|
try {
|
||||||
|
return await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
smbremotepath: remotePath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async NewSmbGlobalMapping(remotePath, username, password) {
|
||||||
|
let command;
|
||||||
|
command =
|
||||||
|
"$PWord = ConvertTo-SecureString -String $Env:smbpassword -AsPlainText -Force;$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Env:smbuser, $PWord;New-SmbGlobalMapping -RemotePath $Env:smbremotepath -Credential $Credential -RequirePrivacy $true";
|
||||||
|
|
||||||
|
await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
smbuser: username,
|
||||||
|
smbpassword: password,
|
||||||
|
smbremotepath: remotePath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async RemoveSmbGlobalMapping(remotePath) {
|
||||||
|
let result;
|
||||||
|
let command;
|
||||||
|
command = "Remove-SmbGlobalMapping -RemotePath $Env:smbremotepath -Force";
|
||||||
|
|
||||||
|
do {
|
||||||
|
result = await this.GetSmbGlobalMapping(remotePath);
|
||||||
|
if (result) {
|
||||||
|
await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
smbremotepath: remotePath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} while (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
async NewSmbLink(remotePath, localPath) {
|
||||||
|
let command;
|
||||||
|
if (!remotePath.endsWith("\\")) {
|
||||||
|
remotePath = `${remotePath}\\`;
|
||||||
|
}
|
||||||
|
|
||||||
|
command =
|
||||||
|
"New-Item -ItemType SymbolicLink $Env:smblocalPath -Target $Env:smbremotepath";
|
||||||
|
await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
smblocalpath: localPath,
|
||||||
|
smbremotepath: remotePath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async NewIscsiTargetPortal(address, port) {
|
||||||
|
let command;
|
||||||
|
command =
|
||||||
|
"New-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port}";
|
||||||
|
await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
iscsi_tp_address: address,
|
||||||
|
iscsi_tp_port: port,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async RemoveIscsiTargetPortalByTargetPortalAddress(targetPortalAddress) {
|
||||||
|
let command;
|
||||||
|
command = `Remove-IscsiTargetPortal -TargetPortalAddress ${targetPortalAddress} -Confirm:$false`;
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async RemoveIscsiTargetPortalByTargetPortalAddressTargetPortalPort(
|
||||||
|
targetPortalAddress,
|
||||||
|
targetPortalPort
|
||||||
|
) {
|
||||||
|
let command;
|
||||||
|
command = `Get-IscsiTargetPortal -TargetPortalAddress ${targetPortalAddress} -TargetPortalPortNumber ${targetPortalPort} | Remove-IscsiTargetPortal -Confirm:$false`;
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async IscsiTargetIsConnectedByPortalAddressPortalPort(address, port, iqn) {
|
||||||
|
let sessions = await this.GetIscsiSessionsByTargetNodeAddress(iqn);
|
||||||
|
for (let session of sessions) {
|
||||||
|
let connections = await this.GetIscsiConnectionsByIscsiSessionIdentifier(
|
||||||
|
session.SessionIdentifier
|
||||||
|
);
|
||||||
|
for (let connection of connections) {
|
||||||
|
if (
|
||||||
|
connection.TargetAddress == address &&
|
||||||
|
connection.TargetPortNumber == port
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//process.exit(1);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -IsMultipathEnabled
|
||||||
|
*
|
||||||
|
* @param {*} address
|
||||||
|
* @param {*} port
|
||||||
|
* @param {*} iqn
|
||||||
|
* @param {*} authType
|
||||||
|
* @param {*} chapUser
|
||||||
|
* @param {*} chapSecret
|
||||||
|
*/
|
||||||
|
async ConnectIscsiTarget(
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
iqn,
|
||||||
|
authType,
|
||||||
|
chapUser,
|
||||||
|
chapSecret,
|
||||||
|
multipath = false
|
||||||
|
) {
|
||||||
|
let is_connected = await this.IscsiTargetIsConnectedByPortalAddressPortalPort(address, port, iqn);
|
||||||
|
if (is_connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let command;
|
||||||
|
// -IsMultipathEnabled $([System.Convert]::ToBoolean(${Env:iscsi_is_multipath}))
|
||||||
|
// -InitiatorPortalAddress
|
||||||
|
command =
|
||||||
|
"Connect-IscsiTarget -TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port} -NodeAddress ${Env:iscsi_target_iqn} -AuthenticationType ${Env:iscsi_auth_type}";
|
||||||
|
|
||||||
|
if (chapUser) {
|
||||||
|
command += " -ChapUsername ${Env:iscsi_chap_user}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chapSecret) {
|
||||||
|
command += " -ChapSecret ${Env:iscsi_chap_secret}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multipath) {
|
||||||
|
command +=
|
||||||
|
" -IsMultipathEnabled $([System.Convert]::ToBoolean(${Env:iscsi_is_multipath}))";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
iscsi_tp_address: address,
|
||||||
|
iscsi_tp_port: port,
|
||||||
|
iscsi_target_iqn: iqn,
|
||||||
|
iscsi_auth_type: authType,
|
||||||
|
iscsi_chap_user: chapUser,
|
||||||
|
iscsi_chap_secret: chapSecret,
|
||||||
|
iscsi_is_multipath: String(multipath),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
let details = _.get(err, "stderr", "");
|
||||||
|
if (
|
||||||
|
!details.includes(
|
||||||
|
"The target has already been logged in via an iSCSI session"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetIscsiTargetsByTargetPortalAddressTargetPortalPort(address, port) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command =
|
||||||
|
"Get-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port} | Get-IscsiTarget | ConvertTo-Json";
|
||||||
|
result = await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
iscsi_tp_address: address,
|
||||||
|
iscsi_tp_port: port,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This disconnects *all* sessions from the target
|
||||||
|
*
|
||||||
|
* @param {*} nodeAddress
|
||||||
|
*/
|
||||||
|
async DisconnectIscsiTargetByNodeAddress(nodeAddress) {
|
||||||
|
let command;
|
||||||
|
|
||||||
|
command = `Disconnect-IscsiTarget -NodeAddress ${nodeAddress.toLowerCase()} -Confirm:$false`;
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetIscsiConnectionsByIscsiSessionIdentifier(iscsiSessionIdentifier) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-IscsiSession -SessionIdentifier ${iscsiSessionIdentifier} | Get-IscsiConnection | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetIscsiSessions() {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-IscsiSession | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetIscsiSessionsByDiskNumber(diskNumber) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-Disk -Number ${diskNumber} | Get-IscsiSession | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetIscsiSessionsByVolumeId(volumeId) {
|
||||||
|
let sessions = [];
|
||||||
|
let disks = await this.GetDisksByVolumeId(volumeId);
|
||||||
|
for (let disk of disks) {
|
||||||
|
let i_sessions = await this.GetIscsiSessionsByDiskNumber(disk.DiskNumber);
|
||||||
|
sessions.push(...i_sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetIscsiSessionsByTargetNodeAddress(targetNodeAddress) {
|
||||||
|
let sessions = await this.GetIscsiSessions();
|
||||||
|
let r_sessions = [];
|
||||||
|
// Where-Object { $_.TargetNodeAddress -eq ${targetNodeAddress} }
|
||||||
|
for (let session of sessions) {
|
||||||
|
if (session.TargetNodeAddress == targetNodeAddress) {
|
||||||
|
r_sessions.push(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r_sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetIscsiSessionByIscsiConnectionIdentifier(iscsiConnectionIdentifier) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-IscsiConnection -ConnectionIdentifier ${iscsiConnectionIdentifier} | Get-IscsiSession | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetIscsiTargetPortalBySessionId(sessionId) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-IscsiSession -SessionIdentifier ${sessionId} | Get-IscsiTargetPortal | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async UpdateHostStorageCache() {
|
||||||
|
let command;
|
||||||
|
command = "Update-HostStorageCache";
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetIscsiDisks() {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = "Get-iSCSISession | Get-Disk | ConvertTo-Json";
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetWin32DiskDrives() {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = "Get-WmiObject Win32_DiskDrive | ConvertTo-Json";
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetDiskLunByDiskNumber(diskNumber) {
|
||||||
|
let result;
|
||||||
|
result = await this.GetWin32DiskDrives();
|
||||||
|
for (let drive of result) {
|
||||||
|
if (drive.Index == diskNumber) {
|
||||||
|
return drive.SCSILogicalUnit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetTargetDisks(address, port, iqn) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
// this fails for synology for some reason
|
||||||
|
//command =
|
||||||
|
// '$ErrorActionPreference = "Stop"; $tp = Get-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port}; $t = $tp | Get-IscsiTarget | Where-Object { $_.NodeAddress -eq ${Env:iscsi_target_iqn} }; $s = Get-iSCSISession -IscsiTarget $t; $s | Get-Disk | ConvertTo-Json';
|
||||||
|
|
||||||
|
command =
|
||||||
|
'$ErrorActionPreference = "Stop"; $s = Get-iSCSISession | Where-Object { $_.TargetNodeAddress -eq ${Env:iscsi_target_iqn} }; $s | Get-Disk | ConvertTo-Json';
|
||||||
|
|
||||||
|
result = await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
iscsi_tp_address: address,
|
||||||
|
iscsi_tp_port: port,
|
||||||
|
iscsi_target_iqn: iqn,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetTargetDisksByIqn(iqn) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command =
|
||||||
|
'$ErrorActionPreference = "Stop"; $s = Get-iSCSISession | Where-Object { $_.TargetNodeAddress -eq ${Env:iscsi_target_iqn} }; $s | Get-Disk | ConvertTo-Json';
|
||||||
|
|
||||||
|
result = await this.ps.exec(command, {
|
||||||
|
env: {
|
||||||
|
iscsi_target_iqn: iqn,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This can be multiple when mpio is not configured properly and each
|
||||||
|
* session creates a new disk
|
||||||
|
*
|
||||||
|
* @param {*} iqn
|
||||||
|
* @param {*} lun
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async GetTargetDisksByIqnLun(iqn, lun) {
|
||||||
|
let result;
|
||||||
|
let dlun;
|
||||||
|
let disks = [];
|
||||||
|
|
||||||
|
result = await this.GetTargetDisksByIqn(iqn);
|
||||||
|
for (let disk of result) {
|
||||||
|
dlun = await this.GetDiskLunByDiskNumber(disk.DiskNumber);
|
||||||
|
if (dlun == lun) {
|
||||||
|
disks.push(disk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return disks;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetDiskByDiskNumber(diskNumber) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-Disk -Number ${diskNumber} | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetDisks() {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = "Get-Disk | ConvertTo-Json";
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetPartitions() {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = "Get-Partition | ConvertTo-Json";
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetPartitionsByDiskNumber(diskNumber) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-Disk -Number ${diskNumber} | Get-Partition | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async DiskIsInitialized(diskNumber) {
|
||||||
|
let disk = await this.GetDiskByDiskNumber(diskNumber);
|
||||||
|
|
||||||
|
return disk.PartitionStyle != "RAW";
|
||||||
|
}
|
||||||
|
|
||||||
|
async InitializeDisk(diskNumber) {
|
||||||
|
let command;
|
||||||
|
|
||||||
|
command = `Initialize-Disk -Number ${diskNumber} -PartitionStyle GPT`;
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async DiskHasBasicPartition(diskNumber) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-Partition | Where DiskNumber -eq ${diskNumber} | Where Type -ne Reserved | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async NewPartition(diskNumber) {
|
||||||
|
let command;
|
||||||
|
|
||||||
|
command = `New-Partition -DiskNumber ${diskNumber} -UseMaximumSize`;
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async PartitionDisk(diskNumber) {
|
||||||
|
let is_intialized;
|
||||||
|
let has_basic_partition;
|
||||||
|
|
||||||
|
is_intialized = await this.DiskIsInitialized(diskNumber);
|
||||||
|
if (!is_intialized) {
|
||||||
|
await this.InitializeDisk(diskNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
has_basic_partition = await this.DiskHasBasicPartition(diskNumber);
|
||||||
|
if (!has_basic_partition) {
|
||||||
|
await this.NewPartition(diskNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetLastPartitionByDiskNumber(diskNumber) {
|
||||||
|
let partitions = await this.GetPartitionsByDiskNumber(diskNumber);
|
||||||
|
let p;
|
||||||
|
for (let partition of partitions) {
|
||||||
|
if (!p) {
|
||||||
|
p = partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partition.PartitionNumber > p.PartitionNumber) {
|
||||||
|
p = partition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetVolumesByDiskNumber(diskNumber) {
|
||||||
|
let command;
|
||||||
|
command = `Get-Disk -Number ${diskNumber} | Get-Partition | Get-Volume | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
this.resultToArray(result);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetVolumeByDiskNumberPartitionNumber(diskNumber, partitionNumber) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-Disk -Number ${diskNumber} | Get-Partition -PartitionNumber ${partitionNumber} | Get-Volume | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetVolumeByVolumeId(volumeId) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-Volume -UniqueId \"${volumeId}\" -ErrorAction Stop | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
|
||||||
|
return result.parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetPartitionsByVolumeId(volumeId) {
|
||||||
|
let partitions = await this.GetPartitions();
|
||||||
|
let p = [];
|
||||||
|
for (let partition of partitions) {
|
||||||
|
let paths = _.get(partition, "AccessPaths", []);
|
||||||
|
if (paths === null) {
|
||||||
|
paths = [];
|
||||||
|
}
|
||||||
|
if (!Array.isArray(paths)) {
|
||||||
|
paths = [];
|
||||||
|
}
|
||||||
|
if (paths.includes(volumeId)) {
|
||||||
|
p.push(partition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetDisksByVolumeId(volumeId) {
|
||||||
|
let partitions = await this.GetPartitionsByVolumeId(volumeId);
|
||||||
|
let diskNumbers = new Set();
|
||||||
|
for (let parition of partitions) {
|
||||||
|
diskNumbers.add(parition.DiskNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
let disks = [];
|
||||||
|
let disk;
|
||||||
|
for (let diskNumber of diskNumbers) {
|
||||||
|
disk = await this.GetDiskByDiskNumber(diskNumber);
|
||||||
|
if (disk) {
|
||||||
|
disks.push(disk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return disks;
|
||||||
|
}
|
||||||
|
|
||||||
|
async VolumeIsFormatted(volumeId) {
|
||||||
|
let volume = await this.GetVolumeByVolumeId(volumeId);
|
||||||
|
let type = volume.FileSystemType || "";
|
||||||
|
type = type.toLowerCase().trim();
|
||||||
|
if (!type || type == "unknown") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async VolumeIsIscsi(volumeId) {
|
||||||
|
let disks = await this.GetDisksByVolumeId(volumeId);
|
||||||
|
for (let disk of disks) {
|
||||||
|
if (_.get(disk, "BusType", "").toLowerCase() == "iscsi") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async FormatVolume(volumeId) {
|
||||||
|
let command;
|
||||||
|
command = `Get-Volume -UniqueId \"${volumeId}\" | Format-Volume -FileSystem ntfs -Confirm:$false`;
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ResizeVolume(volumeId, size = 0) {
|
||||||
|
let command;
|
||||||
|
let final_size;
|
||||||
|
|
||||||
|
if (!size) {
|
||||||
|
final_size = await this.GetVolumeMaxSize(volumeId);
|
||||||
|
} else {
|
||||||
|
final_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_size = await this.GetVolumeSize(volumeId);
|
||||||
|
if (current_size >= final_size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
command = `Get-Volume -UniqueId \"${volumeId}\" | Get-Partition | Resize-Partition -Size ${final_size}`;
|
||||||
|
try {
|
||||||
|
await this.ps.exec(command);
|
||||||
|
} catch (err) {
|
||||||
|
let details = _.get(err, "stderr", "");
|
||||||
|
if (
|
||||||
|
!details.includes(
|
||||||
|
"The size of the extent is less than the minimum of 1MB"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetVolumeMaxSize(volumeId) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-Volume -UniqueId \"${volumeId}\" | Get-partition | Get-PartitionSupportedSize | Select SizeMax | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
return result.parsed.SizeMax;
|
||||||
|
}
|
||||||
|
async GetVolumeSize(volumeId) {
|
||||||
|
let command;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
command = `Get-Volume -UniqueId \"${volumeId}\" | Get-partition | ConvertTo-Json`;
|
||||||
|
result = await this.ps.exec(command);
|
||||||
|
|
||||||
|
return result.parsed.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
async MountVolume(volumeId, path) {
|
||||||
|
let command;
|
||||||
|
command = `Get-Volume -UniqueId \"${volumeId}\" | Get-Partition | Add-PartitionAccessPath -AccessPath ${path}`;
|
||||||
|
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async UnmountVolume(volumeId, path) {
|
||||||
|
let command;
|
||||||
|
command = `Get-Volume -UniqueId \"${volumeId}\" | Get-Partition | Remove-PartitionAccessPath -AccessPath ${path}`;
|
||||||
|
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
async WriteVolumeCache(volumeId) {
|
||||||
|
let command;
|
||||||
|
command = `Get-Volume -UniqueId \"${volumeId}\" | Write-Volumecache`;
|
||||||
|
|
||||||
|
await this.ps.exec(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.Windows = Windows;
|
||||||
Loading…
Reference in New Issue