native windows support
This commit is contained in:
parent
7fe916c916
commit
7a5b6b58b1
|
|
@ -1,2 +1,4 @@
|
|||
**~
|
||||
node_modules
|
||||
dev
|
||||
/ci/bin/*dev*
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ async function requestHandlerProxy(call, callback, serviceMethodName) {
|
|||
|
||||
// for testing purposes
|
||||
//await GeneralUtils.sleep(10000);
|
||||
//throw new Error("fake error");
|
||||
|
||||
// for CI/testing purposes
|
||||
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 });
|
||||
success = true;
|
||||
} catch (err) {
|
||||
if (err.toString().includes("dataset is busy")) {
|
||||
if (
|
||||
err.toString().includes("dataset is busy") ||
|
||||
err.toString().includes("target is busy")
|
||||
) {
|
||||
current_try++;
|
||||
if (current_try > max_tries) {
|
||||
throw err;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const __REGISTRY_NS__ = "CsiBaseDriver";
|
|||
|
||||
const NODE_OS_DRIVER_CSI_PROXY = "csi-proxy";
|
||||
const NODE_OS_DRIVER_POSIX = "posix";
|
||||
const NODE_OS_DRIVER_WINDOWS = "windows";
|
||||
|
||||
/**
|
||||
* common code shared between all drivers
|
||||
|
|
@ -199,10 +200,14 @@ class CsiBaseDriver {
|
|||
}
|
||||
|
||||
__getNodeOsDriver() {
|
||||
if (this.getNodeIsWindows() || this.getCsiProxyEnabled()) {
|
||||
return NODE_OS_DRIVER_CSI_PROXY;
|
||||
if (this.getNodeIsWindows()) {
|
||||
return NODE_OS_DRIVER_WINDOWS;
|
||||
}
|
||||
|
||||
//if (this.getNodeIsWindows() || this.getCsiProxyEnabled()) {
|
||||
// return NODE_OS_DRIVER_CSI_PROXY;
|
||||
//}
|
||||
|
||||
return NODE_OS_DRIVER_POSIX;
|
||||
}
|
||||
|
||||
|
|
@ -1215,6 +1220,383 @@ class CsiBaseDriver {
|
|||
);
|
||||
}
|
||||
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:
|
||||
// sanity check node_attach_driver
|
||||
if (!["smb", "iscsi"].includes(node_attach_driver)) {
|
||||
|
|
@ -1546,9 +1928,7 @@ class CsiBaseDriver {
|
|||
grpc.status.INVALID_ARGUMENT,
|
||||
`unknown/unsupported node_attach_driver: ${node_attach_driver}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new GrpcError(
|
||||
|
|
@ -1798,6 +2178,89 @@ class CsiBaseDriver {
|
|||
result = await filesystem.rmdir(staging_target_path);
|
||||
}
|
||||
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:
|
||||
// load up the client instance
|
||||
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 {
|
||||
await csiProxyClient.executeRPC("iscsi", "RemoveTargetPortal", {
|
||||
target_portal,
|
||||
|
|
@ -1901,6 +2367,7 @@ class CsiBaseDriver {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
break;
|
||||
default:
|
||||
|
|
@ -2077,6 +2544,79 @@ class CsiBaseDriver {
|
|||
);
|
||||
}
|
||||
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:
|
||||
switch (node_attach_driver) {
|
||||
//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;
|
||||
case NODE_OS_DRIVER_CSI_PROXY:
|
||||
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
||||
|
|
@ -2368,6 +2926,61 @@ class CsiBaseDriver {
|
|||
}
|
||||
|
||||
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:
|
||||
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
||||
const volume_context = await driver.getDerivedVolumeContext(call);
|
||||
|
|
@ -2562,6 +3175,56 @@ class CsiBaseDriver {
|
|||
}
|
||||
|
||||
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:
|
||||
const csiProxyClient = driver.getDefaultCsiProxyClientInstance();
|
||||
const volume_context = await driver.getDerivedVolumeContext(call);
|
||||
|
|
@ -2606,7 +3269,7 @@ class CsiBaseDriver {
|
|||
try {
|
||||
await csiProxyClient.executeRPC("volume", "ResizeVolume", {
|
||||
volume_id: node_volume_id,
|
||||
resize_bytes: required_bytes,
|
||||
resize_bytes: 0,
|
||||
});
|
||||
} catch (e) {
|
||||
let details = _.get(e, "details", "");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
const _ = require("lodash");
|
||||
const grpc = require("./grpc").grpc;
|
||||
const path = require("path");
|
||||
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
|
||||
|
|
@ -38,10 +40,20 @@ class CsiProxyClient {
|
|||
const serviceVersion =
|
||||
service.version || DEFAULT_SERVICES[serviceName].version;
|
||||
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 ||
|
||||
`\\\\.\\\\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, {
|
||||
keepCase: true,
|
||||
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