diff --git a/examples/zfs-generic-iscsi.yaml b/examples/zfs-generic-iscsi.yaml index af5df37..f8aa270 100644 --- a/examples/zfs-generic-iscsi.yaml +++ b/examples/zfs-generic-iscsi.yaml @@ -44,6 +44,7 @@ zfs: iscsi: shareStrategy: "targetCli" + #shareStrategy: "pcs" # https://kifarunix.com/how-to-install-and-configure-iscsi-storage-server-on-ubuntu-18-04/ # https://kifarunix.com/how-install-and-configure-iscsi-storage-server-on-centos-7/ @@ -75,6 +76,17 @@ iscsi: attributes: # set to 1 to enable Thin Provisioning Unmap emulate_tpu: 0 + + shareStrategyPcs: + #sudoEnabled: true + pcs_group: "group-nas" + basename: "iqn.2003-01.org.linux-iscsi.ubuntu-19.x8664" + auth: + enabled: 0 + # CHAP – incoming only, mutual not supported by the Pacemaker resource agent + incoming_username: "foo" + incoming_password: "bar" + targetPortal: "server[:port]" # for multipath targetPortals: [] # [ "server[:port]", "server[:port]", ... ] diff --git a/src/driver/controller-zfs-generic/index.js b/src/driver/controller-zfs-generic/index.js index d3080ae..380b674 100644 --- a/src/driver/controller-zfs-generic/index.js +++ b/src/driver/controller-zfs-generic/index.js @@ -298,8 +298,70 @@ create /backstores/block/${assetName} } ); break; + case "pcs": + basename = this.options.iscsi.shareStrategyPcs.basename; + pcs_group = this.options.iscsi.shareStrategyPcs.pcs_group; + + let groupText = `group ${pcs_group}`; + let createTargetText = [ + `resource create --future target-${assetName} iSCSITarget`, + 'implementation="lio-t"', + `iqn="${basename}:${assetName}"` + ]; + + if (this.options.iscsi.shareStrategyPcs.auth.enabled) { + createTargetText.push(`incoming_username="${this.options.iscsi.shareStrategyPcs.auth.incoming_username}"`); + createTargetText.push(`incoming_password="${this.options.iscsi.shareStrategyPcs.auth.incoming_password}"`); + } + + createTargetText.push(groupText); + + await GeneralUtils.retry( + 3, + 2000, + async () => { + await this.pcsCommand(createTargetText); + }, + { + retryCondition: (err) => { + if (err.stdout && err.stdout.includes("Timed Out")) { + return true; + } + return false; + }, + } + ); + + let createLunText = [ + `resource create --future lun-${assetName} iSCSILogicalUnit`, + 'implementation="lio-t"', + `target_iqn="${basename}:${assetName}" lun="1"`, + `path="/dev/${extentDiskName}"`, + groupText + ]; + + await GeneralUtils.retry( + 3, + 2000, + async () => { + await this.pcsCommand(createLunText); + }, + { + retryCondition: (err) => { + if (err.stdout && err.stdout.includes("Timed Out")) { + return true; + } + return false; + }, + } + ); + break; + default: - break; + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `invalid configuration: unknown shareStrategy ${this.options.iscsi.shareStrategy}` + ); } // iqn = target @@ -690,8 +752,54 @@ delete ${assetName} ); break; - default: + case "pcs": + let deleteLunText = [ + `resource delete lun-${assetName}` + ]; + + await GeneralUtils.retry( + 3, + 2000, + async () => { + await this.pcsCommand(deleteLunText); + }, + { + retryCondition: (err) => { + if (err.stdout && err.stdout.includes("Timed Out")) { + return true; + } + return false; + }, + } + ); + + let deleteTargetText = [ + `resource delete target-${assetName}` + ]; + + await GeneralUtils.retry( + 3, + 2000, + async () => { + await this.pcsCommand(deleteTargetText); + }, + { + retryCondition: (err) => { + if (err.stdout && err.stdout.includes("Timed Out")) { + return true; + } + return false; + }, + } + ); + break; + + default: + throw new GrpcError( + grpc.status.FAILED_PRECONDITION, + `invalid configuration: unknown shareStrategy ${this.options.iscsi.shareStrategy}` + ); } break; } @@ -841,6 +949,9 @@ save_config filename=${this.options.nvmeof.shareStrategySpdkCli.configPath} case "targetCli": // nothing required, just need to rescan on the node break; + case "pcs": + // nothing required, just need to rescan on the node + break; default: break; } @@ -851,6 +962,56 @@ save_config filename=${this.options.nvmeof.shareStrategySpdkCli.configPath} } } + async pcsCommand(commandLines) { + const execClient = this.getExecClient(); + const driver = this; + + let command = "sh"; + let args = ["-c"]; + + let cliArgs = ["pcs"]; + if ( + _.get(this.options, "iscsi.shareStrategyPcs.sudoEnabled", false) + ) { + cliArgs.unshift("sudo"); + } + + let cliCommand = []; + cliCommand.push(cliArgs.join(" ")); + cliCommand.push(commandLines.join(" ")); + args.push("'" + cliCommand.join(" ") + "'"); + + let logCommandTmp = command + " " + args.join(" "); + let logCommand = ""; + + logCommandTmp.split(" ").forEach((term) => { + logCommand += " "; + + if (term.startsWith("incoming_password=")) { + logCommand += "incoming_password="; + } else { + logCommand += term; + } + }); + + driver.ctx.logger.verbose("Pcs command:" + logCommand); + + let options = { + pty: true, + }; + let response = await execClient.exec( + execClient.buildCommand(command, args), + options + ); + driver.ctx.logger.verbose( + "Pcs response: " + JSON.stringify(response) + ); + if (response.code != 0) { + throw response; + } + return response; + } + async targetCliCommand(data) { const execClient = this.getExecClient(); const driver = this;