pax_global_header00006660000000000000000000000064150167150070014514gustar00rootroot0000000000000052 comment=86bdbba4c0457d7d61ff6d5c4175d2c9943815dd golang-github-linbit-golinstor-0.55.0/000077500000000000000000000000001501671500700176275ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/.gitlab-ci.yml000066400000000000000000000012561501671500700222670ustar00rootroot00000000000000# Use the same image version as the minimum version on go.mod image: golang:1.20 test: stage: test cache: paths: - .mygo/pkg/mod variables: GOPATH: $CI_PROJECT_DIR/.mygo script: - go mod tidy - git diff --exit-code go.mod go.sum || (echo "Run go mod tidy!" >&2 ; exit 1) - go test -v -coverprofile .testCoverage.txt -covermode count ./client 2>&1 | go run github.com/jstemmer/go-junit-report@latest -set-exit-code > test.xml - go run github.com/boumenot/gocover-cobertura@latest < .testCoverage.txt > coverage.xml artifacts: reports: coverage_report: coverage_format: cobertura path: coverage.xml junit: test.xml golang-github-linbit-golinstor-0.55.0/.gitlab/000077500000000000000000000000001501671500700211475ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/.gitlab/dependabot.yml000066400000000000000000000002621501671500700237770ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" assignees: - dependabot reviewers: - cboehmwalder golang-github-linbit-golinstor-0.55.0/.gitmodules000066400000000000000000000001441501671500700220030ustar00rootroot00000000000000[submodule "linstor-common"] path = linstor-common url = https://github.com/LINBIT/linstor-common golang-github-linbit-golinstor-0.55.0/LICENSE000066400000000000000000000261351501671500700206430ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-github-linbit-golinstor-0.55.0/Makefile000066400000000000000000000003521501671500700212670ustar00rootroot00000000000000all: echo "The only meaningful target is 'apiconsts'" gitclean: git diff-index --quiet --exit-code HEAD .PHONY: apiconsts apiconsts: gitclean git submodule update --remote go generate git commit -am "apiconsts: submodule pull" golang-github-linbit-golinstor-0.55.0/README.md000066400000000000000000000003331501671500700211050ustar00rootroot00000000000000# golinstor This library aims to be a simple go interface for [Linstor](https://github.com/LINBIT/linstor-server) # Documentation [godocs](https://pkg.go.dev/github.com/LINBIT/golinstor/client) # License Apache-2.0 golang-github-linbit-golinstor-0.55.0/apiconsts.go000066400000000000000000001036151501671500700221670ustar00rootroot00000000000000// This file was autogenerated by genconsts.py // LINSTOR - management of distributed storage/DRBD9 resources // Copyright (C) 2017 - 2025 LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Robert Altnoeder, Roland Kammerer, Gabor Hernadi, Rene Peinthor // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package linstor // ## Bits 62 - 63 (most significant 2) are reserved for the message type masks (error, warning, info) // Bits 25 - 26 are reserved for the operation type masks (create, modify, delete) // Bits 18 - 22 are reserved for the object type masks (node, resource, resource definition, ...) // Bits 0 - 14 are reserved for codes ### const MaskBitsType = 0xC000000000000000 const MaskError = 0xC000000000000000 const MaskWarn = 0x8000000000000000 const MaskInfo = 0x4000000000000000 const MaskSuccess = 0x0000000000000000 // ## Operation type masks ### const MaskBitsOp = 0x0000000003000000 const MaskCrt = 0x0000000001000000 const MaskMod = 0x0000000002000000 const MaskDel = 0x0000000003000000 // ## Type masks (Node, ResDfn, Res, VolDfn, Vol, NetInterface, ...) ### const MaskBitsObj = 0x00000000007C0000 const MaskSchedule = 0x0000000000540000 const MaskExtFiles = 0x0000000000500000 const MaskPhysicalDevice = 0x00000000004C0000 const MaskVlmGrp = 0x0000000000480000 const MaskRscGrp = 0x0000000000440000 const MaskKvs = 0x0000000000400000 const MaskNode = 0x00000000003C0000 const MaskRscDfn = 0x0000000000380000 const MaskRsc = 0x0000000000340000 const MaskVlmDfn = 0x0000000000300000 const MaskVlm = 0x00000000002C0000 const MaskNodeConn = 0x0000000000280000 const MaskRscConn = 0x0000000000240000 const MaskVlmConn = 0x0000000000200000 const MaskNetIf = 0x00000000001C0000 const MaskStorPoolDfn = 0x0000000000180000 const MaskStorPool = 0x0000000000140000 const MaskCtrlConf = 0x0000000000100000 const MaskSnapshot = 0x00000000000C0000 const MaskBackup = 0x0000000000080000 const MaskRemote = 0x0000000000040000 // ## Codes ### const MaskBitsCode = 0x0000000000007FFF // ## Codes 1-9: success ### const Created = (1 | MaskSuccess) const Deleted = (2 | MaskSuccess) const Modified = (3 | MaskSuccess) const PassphraseAccepted = (4 | MaskSuccess) // ## Codes 100 - 999: failures ### // ## Codes 100 - 199: sql failures ### const FailSql = (100 | MaskError) const FailSqlRollback = (101 | MaskError) // ## Codes 200-299: invalid * failures ### const FailInvldNodeName = (200 | MaskError) const FailInvldNodeType = (201 | MaskError) const FailInvldRscName = (202 | MaskError) const FailInvldRscPort = (203 | MaskError) const FailInvldNodeId = (204 | MaskError) const FailInvldVlmNr = (205 | MaskError) const FailInvldVlmSize = (206 | MaskError) const FailInvldMinorNr = (207 | MaskError) const FailInvldStorPoolName = (208 | MaskError) const FailInvldNetName = (209 | MaskError) const FailInvldNetAddr = (210 | MaskError) const FailInvldNetPort = (211 | MaskError) const FailInvldNetType = (212 | MaskError) const FailInvldProp = (213 | MaskError) const FailInvldTransportType = (214 | MaskError) const FailInvldTcpPort = (215 | MaskError) const FailInvldCryptPassphrase = (216 | MaskError) const FailInvldEncryptType = (217 | MaskError) const FailInvldSnapshotName = (218 | MaskError) const FailInvldPlaceCount = (219 | MaskError) const FailInvldFreeSpaceMgrName = (220 | MaskError) const FailInvldStorDriver = (221 | MaskError) const FailInvldDrbdProxyCompressionType = (222 | MaskError) const FailInvldKvsName = (223 | MaskError) const FailInvldLayerKind = (224 | MaskError) const FailInvldLayerStack = (225 | MaskError) const FailInvldExtName = (226 | MaskError) const FailInvldProvider = (227 | MaskError) const FailInvldVlmSizes = (228 | MaskError) const FailInvldVlmCount = (229 | MaskError) const FailInvldConf = (230 | MaskError) const FailInvldSnapshotShippingSource = (231 | MaskError) const FailInvldSnapshotShippingTarget = (232 | MaskError) const FailNodeHasUsedRsc = (233 | MaskError) const FailInvldRequest = (234 | MaskError) const FailInvldExtFileName = (235 | MaskError) const FailInvldExtFile = (236 | MaskError) const FailInvldRscGrpName = (237 | MaskError) const FailInvldBackupConfig = (238 | MaskError) const FailInvldRemoteName = (239 | MaskError) const FailInvldTimeParam = (240 | MaskError) const FailInvldScheduleName = (241 | MaskError) const FailInvldDbExportFile = (242 | MaskError) // ## Codes 300-399: dependency not found failures ### const FailNotFoundNode = (300 | MaskError) const FailNotFoundRscDfn = (301 | MaskError) const FailNotFoundRsc = (302 | MaskError) const FailNotFoundVlmDfn = (303 | MaskError) const FailNotFoundVlm = (304 | MaskError) const FailNotFoundNetIf = (305 | MaskError) const FailNotFoundNodeConn = (306 | MaskError) const FailNotFoundRscConn = (307 | MaskError) const FailNotFoundVlmConn = (308 | MaskError) const FailNotFoundStorPoolDfn = (309 | MaskError) const FailNotFoundStorPool = (310 | MaskError) const FailNotFoundDfltStorPool = (311 | MaskError) const FailNotFoundCryptKey = (312 | MaskError) const FailNotFoundSnapshotDfn = (313 | MaskError) const FailNotFoundSnapshotVlmDfn = (314 | MaskError) const FailNotFoundSnapshot = (315 | MaskError) const FailNotFoundKvs = (316 | MaskError) const FailNotFoundRscGrp = (317 | MaskError) const FailNotFoundVlmGrp = (318 | MaskError) const FailNotFoundExosEnclosure = (319 | MaskError) const FailNotFoundExtFile = (320 | MaskError) const FailNotFoundRemote = (321 | MaskError) const FailNotFoundBackup = (322 | MaskError) const FailNotFoundSchedule = (323 | MaskError) // ## Codes 400-499: access denied failures ### const FailAccDeniedNode = (400 | MaskError) const FailAccDeniedRscDfn = (401 | MaskError) const FailAccDeniedRsc = (402 | MaskError) const FailAccDeniedVlmDfn = (403 | MaskError) const FailAccDeniedVlm = (404 | MaskError) const FailAccDeniedStorPoolDfn = (405 | MaskError) const FailAccDeniedStorPool = (406 | MaskError) const FailAccDeniedNodeConn = (407 | MaskError) const FailAccDeniedRscConn = (408 | MaskError) const FailAccDeniedVlmConn = (409 | MaskError) const FailAccDeniedStltConn = (410 | MaskError) const FailAccDeniedCtrlCfg = (411 | MaskError) const FailAccDeniedCommand = (412 | MaskError) const FailAccDeniedWatch = (413 | MaskError) const FailAccDeniedSnapshotDfn = (414 | MaskError) const FailAccDeniedSnapshot = (415 | MaskError) const FailAccDeniedSnapshotVlmDfn = (416 | MaskError) const FailAccDeniedFreeSpaceMgr = (417 | MaskError) const FailAccDeniedKvs = (418 | MaskError) const FailAccDeniedRscGrp = (419 | MaskError) const FailAccDeniedVlmGrp = (420 | MaskError) const FailAccDeniedSnapDfn = (421 | MaskError) const FailAccDeniedExtFile = (422 | MaskError) const FailAccDeniedRemote = (423 | MaskError) const FailAccDeniedSchedule = (424 | MaskError) // ## Codes 500-599: data already exists failures ### const FailExistsNode = (500 | MaskError) const FailExistsRscDfn = (501 | MaskError) const FailExistsRsc = (502 | MaskError) const FailExistsVlmDfn = (503 | MaskError) const FailExistsVlm = (504 | MaskError) const FailExistsNetIf = (505 | MaskError) const FailExistsNodeConn = (506 | MaskError) const FailExistsRscConn = (507 | MaskError) const FailExistsVlmConn = (508 | MaskError) const FailExistsStorPoolDfn = (509 | MaskError) const FailExistsStorPool = (510 | MaskError) const FailExistsStltConn = (511 | MaskError) const FailExistsCryptPassphrase = (512 | MaskError) const FailExistsWatch = (513 | MaskError) const FailExistsSnapshotDfn = (514 | MaskError) const FailExistsSnapshot = (516 | MaskError) const FailExistsExtName = (517 | MaskError) const FailExistsNvmeTargetPerRscDfn = (518 | MaskError) const FailExistsNvmeInitiatorPerRscDfn = (519 | MaskError) const FailLostStorPool = (521 | MaskError) const FailExistsRscGrp = (522 | MaskError) const FailExistsVlmGrp = (523 | MaskError) const FailExistsSnapshotShipping = (525 | MaskError) const FailExistsExosEnclosure = (526 | MaskError) const FailExistsRemote = (527 | MaskError) const FailExistsSchedule = (528 | MaskError) // ## Codes 600-699: data missing failures ### const FailMissingProps = (600 | MaskError) const FailMissingPropsNetcomType = (601 | MaskError) const FailMissingPropsNetcomPort = (602 | MaskError) const FailMissingNetcom = (603 | MaskError) const FailMissingPropsNetifName = (604 | MaskError) const FailMissingStltConn = (605 | MaskError) const FailMissingExtName = (606 | MaskError) const FailMissingNvmeTarget = (608 | MaskError) const FailNoStltConnDefined = (609 | MaskError) const FailMissingEbsTarget = (611 | MaskError) // ## Codes 700-799: uuid mismatch failures ### const FailUuidNode = (700 | MaskError) const FailUuidRscDfn = (701 | MaskError) const FailUuidRsc = (702 | MaskError) const FailUuidVlmDfn = (703 | MaskError) const FailUuidVlm = (704 | MaskError) const FailUuidNetIf = (705 | MaskError) const FailUuidNodeConn = (706 | MaskError) const FailUuidRscConn = (707 | MaskError) const FailUuidVlmConn = (708 | MaskError) const FailUuidStorPoolDfn = (709 | MaskError) const FailUuidStorPool = (710 | MaskError) const FailUuidKvs = (711 | MaskError) // ## Codes 800-899: number pools exhausted ### const FailPoolExhaustedVlmNr = (800 | MaskError) const FailPoolExhaustedMinorNr = (801 | MaskError) const FailPoolExhaustedTcpPort = (802 | MaskError) const FailPoolExhaustedNodeId = (803 | MaskError) const FailPoolExhaustedRscLayerId = (804 | MaskError) const FailPoolExhaustedSpecialSatellteTcpPort = (805 | MaskError) const FailPoolExhaustedSnapshotShippingTcpPort = (806 | MaskError) const FailPoolExhaustedBackupShippingTcpPort = (807 | MaskError) // ## Other failures ### const FailSnapshotRollbackInProgress = (971 | MaskError) const FailSpMixingNotAllowed = (972 | MaskError) const FailEbsCooldown = (973 | MaskError) const FailNotAllUptodate = (974 | MaskError) const FailEvacuating = (975 | MaskError) const FailBackupUnknownCluster = (976 | MaskError) const FailDependendBackup = (977 | MaskError) const FailBackupIncompatibleVersion = (978 | MaskError) const FailSnapshotNotUptodate = (979 | MaskError) const FailNotEnoughFreeSpace = (980 | MaskError) const FailOnlyOneActRscPerSharedStorPoolAllowed = (981 | MaskError) const FailCryptInit = (982 | MaskError) const FailSnapshotShippingNotSupported = (983 | MaskError) const FailSnapshotShippingInProgress = (984 | MaskError) const FailUndecidableAutoplacment = (985 | MaskError) const FailPreSelectScriptDidNotTerminate = (986 | MaskError) const FailLinstorManagedSatelliteDidNotStartProperly = (987 | MaskError) const FailStltDoesNotSupportLayer = (988 | MaskError) const FailStltDoesNotSupportProvider = (989 | MaskError) const FailStorPoolConfigurationError = (990 | MaskError) const FailInsufficientReplicaCount = (991 | MaskError) const FailRscBusy = (992 | MaskError) const FailInsufficientPeerSlots = (993 | MaskError) const FailSnapshotsNotSupported = (994 | MaskError) const FailNotConnected = (995 | MaskError) const FailNotEnoughNodes = (996 | MaskError) const FailInUse = (997 | MaskError) const FailUnknownError = (998 | MaskError) const FailImplError = (999 | MaskError) // ## Codes 1000-9999: warnings ### const WarnInvldOptPropNetcomEnabled = (1001 | MaskWarn) const WarnNotConnected = (1002 | MaskWarn) const WarnStltNotUpdated = (1003 | MaskWarn) const WarnNoStltConnDefined = (1004 | MaskWarn) const WarnDelUnsetProp = (1005 | MaskWarn) const WarnRscAlreadyDeployed = (1006 | MaskWarn) const WarnRscAlreadyHasDisk = (1007 | MaskWarn) const WarnRscAlreadyDiskless = (1008 | MaskWarn) const WarnAllDiskless = (1009 | MaskWarn) const WarnStorageError = (1010 | MaskWarn) const WarnNotFoundCryptKey = (1011 | MaskWarn) const WarnStorageKindAdded = (1012 | MaskWarn) const WarnNotEnoughNodesForTieBreaker = (1013 | MaskWarn) const WarnMixedPmemAndNonPmem = (1014 | MaskWarn) const WarnUneffectiveProp = (1015 | MaskWarn) const WarnInvldSnapshotShippingPrefix = (1016 | MaskWarn) const WarnNodeEvicted = (1017 | MaskWarn) const WarnRscDeactivated = (1018 | MaskWarn) const WarnBackupDlOnly = (1019 | MaskWarn) const WarnNotEvacuating = (1020 | MaskWarn) const WarnInvldConf = (1021 | MaskWarn) const WarnStorpoolRenameNotAllowed = (1022 | MaskWarn) const WarnNotFound = (3000 | MaskWarn) const WarnDeprecated = (4000 | MaskWarn) // ## Codes 10000-19999: info ### const InfoNoRscSpawned = (10000 | MaskInfo) const InfoNodeNameMismatch = (10001 | MaskInfo) const InfoPropSet = (10002 | MaskInfo) const InfoTieBreakerCreated = (10003 | MaskInfo) const InfoTieBreakerDeleting = (10004 | MaskInfo) const InfoTieBreakerTakeover = (10006 | MaskInfo) const InfoPropRemoved = (10005 | MaskInfo) const InfoAutoDrbdProxyCreated = (10007 | MaskInfo) const InfoNoop = (10007 | MaskInfo) const InfoRscAlreadyExists = (10008 | MaskInfo) const InfoAbortedFailedSnapshotRollback = (10009 | MaskInfo) const InfoRecoveringFailedSnapshotRollback = (10010 | MaskInfo) // ## Special codes ### const UnknownApiCall = (0x0FFFFFFFFFFFFFFF | MaskError) const ApiCallAuthReq = (0x0FFFFFFFFFFFFFFE | MaskError) const ApiCallParseError = (0x0FFFFFFFFFFFFFFD | MaskError) // ## SignIn codes ### const SuccessSignIn = (10000 | MaskSuccess) const FailSignIn = (10000 | MaskError) const FailSignInMissingCredentials = (10001 | MaskError) // ## Special answer message content types ### // Textual MsgApiCallResponse responses const ApiReply = "Reply" // Indicates that the immediate answers to the API call are complete const ApiEndOfImmediateAnswers = "EndOfImmediateAnswers" // ## Create object APIs ### const ApiCrtNode = "CrtNode" const ApiCrtRsc = "CrtRsc" const ApiCrtRscDfn = "CrtRscDfn" const ApiCrtNetIf = "CrtNetIf" const ApiCrtVlmDfn = "CrtVlmDfn" const ApiCrtSnapshot = "CrtSnapshot" const ApiCrtSnapshotMulti = "CrtSnapshotMulti" const ApiCrtBackup = "CrtBackup" const ApiCrtStorPoolDfn = "CrtStorPoolDfn" const ApiCrtStorPool = "CrtStorPool" const ApiCrtNodeConn = "CrtNodeConn" const ApiCrtRscConn = "CrtRscConn" const ApiCrtVlmConn = "CrtVlmConn" const ApiAutoPlaceRsc = "AutoPlaceRsc" const ApiCloneRscdfn = "CloneRscDfn" const ApiCloneRscdfnStatus = "CloneRscDfnStatus" const ApiCrtCryptPass = "CrtCryptPass" const ApiRestoreVlmDfn = "RestoreVlmDfn" const ApiRestoreSnapshot = "RestoreSnapshot" const ApiCrtRscGrp = "CrtRscGrp" const ApiCrtVlmGrp = "CrtVlmGrp" const ApiSpawnRscDfn = "SpawnRscDfn" const ApiAdjustRscGrp = "AdjustRscGrp" const ApiCreateDevicePool = "CreateDevicePool" const ApiMakeRscAvail = "MakeRscAvail" const ApiCrtExosEnclosure = "CrtExosEnclosure" const ApiSetRemote = "SetRemote" const ApiRestoreBackup = "RstBackup" const ApiAbortBackup = "AbortBackup" const ApiShipBackup = "ShipBackup" const ApiCrtSchedule = "CrtSchedule" // ## Modify object APIs ### const ApiModNode = "ModNode" const ApiModNodeConn = "ModNodeConn" const ApiModRsc = "ModRsc" const ApiToggleDisk = "ToggleDisk" const ApiModRscConn = "ModRscConn" const ApiModRscDfn = "ModRscDfn" const ApiModNetIf = "ModNetIf" const ApiModStorPool = "ModStorPool" const ApiModStorPoolDfn = "ModStorPoolDfn" const ApiModVlmDfn = "ModVlmDfn" const ApiModVlmDfnPass = "ModVlmDfnPassphrase" const ApiModVlm = "ModVlm" const ApiModVlmConn = "ModVlmConn" const ApiModSnapshot = "ModSnapshot" const ApiModCryptPass = "ModCryptPass" const ApiEnableDrbdProxy = "EnableDrbdProxy" const ApiDisableDrbdProxy = "DisableDrbdProxy" const ApiModDrbdProxy = "ModifyDrbdProxy" const ApiRollbackSnapshot = "RollbackSnapshot" const ApiShipSnapshot = "ShipSnapshot" const ApiModKvs = "ModifyKvs" const ApiModRscGrp = "ModifyRscGrp" const ApiModVlmGrp = "ModifyVlmGrp" const ApiActivateRsc = "ActivateRsc" const ApiDeactivateRsc = "DeactivateRsc" const ApiModExosDflts = "ModifyExosDefaults" const ApiModExosEnclosure = "ModExosEnclosure" const ApiModSchedule = "ModSchedule" // ## Delete object APIs ### const ApiDelNode = "DelNode" const ApiDelRsc = "DelRsc" const ApiDelRscDfn = "DelRscDfn" const ApiDelNetIf = "DelNetIf" const ApiDelVlmDfn = "DelVlmDfn" const ApiDelStorPoolDfn = "DelStorPoolDfn" const ApiDelStorPool = "DelStorPool" const ApiDelNodeConn = "DelNodeConn" const ApiDelRscConn = "DelRscConn" const ApiDelVlmConn = "DelVlmConn" const ApiDelSnapshot = "DelSnapshot" const ApiDelKvs = "DelKvs" const ApiDelRscGrp = "DelRscGrp" const ApiDelVlmGrp = "DelVlmGrp" const ApiDelBackup = "DelBackup" const ApiLostNode = "LostNode" const ApiLostStorPool = "LostStorPool" const ApiDelExosEnclosure = "DelExosEnclosure" const ApiDelSchedule = "DelSchedule" // ## Authentication APIs ### const ApiSignIn = "SignIn" const ApiVersion = "Version" // ## Debug APIs ### const ApiCrtDbgCnsl = "CrtDbgCnsl" const ApiDstrDbgCnsl = "DstrDbgCnsl" // ## Command APIs ### const ApiControlCtrl = "ControlCtrl" const ApiCmdShutdown = "Shutdown" const ApiNodeReconnect = "NodeReconnect" const ApiNodeRestore = "NodeRestore" const ApiNodeEvict = "NodeEvict" const ApiNodeEvacuate = "NodeEvacuate" // ## List object APIs ### const ApiLstNode = "LstNode" const ApiLstNodeConn = "LstNodeConn" const ApiLstRsc = "LstRsc" const ApiLstRscDfn = "LstRscDfn" const ApiLstNetIf = "LstNetIf" const ApiLstVlmDfn = "LstVlmDfn" const ApiLstVlm = "LstVlm" const ApiLstSnapshotDfn = "LstSnapshotDfn" const ApiLstStorPool = "LstStorPool" const ApiLstStorPoolDfn = "LstStorPoolDfn" const ApiLstErrorReports = "LstErrorReports" const ApiReqErrorReports = "ReqErrorReports" const ApiDelErrorReport = "DelErrorReport" const ApiDelErrorReports = "DelErrorReports" const ApiReqSosReport = "ReqSosReport" const ApiReqRscConnList = "ReqRscConnList" const ApiLstRscConn = "LstRscConn" const ApiLstKvs = "LstKvs" const ApiLstRscGrp = "LstRscGrp" const ApiLstVlmGrp = "LstVlmGrp" const ApiLstPhysStor = "LstPhysicalStorage" const ApiLstSnapshotShippings = "LstSnapShips" const ApiLstBackups = "LstBackups" const ApiBackupInfo = "BackupInfo" const ApiLstPropsInfo = "LstPropsInfo" const ApiLstRemote = "LstRemote" const ApiLstExosDflts = "LstExosDefaults" const ApiLstExosEnclosures = "LstExosEnclosures" const ApiExosEnclosureEvents = "ExosEvents" const ApiExosExec = "ExosExec" const ApiExosMap = "ExosMap" const ApiLstExtFiles = "LstExtFiles" const ApiNodeStats = "NodeStats" const ApiRscGrpStats = "RscGrpStats" const ApiRscDfnStats = "RscDfnStats" const ApiRscStats = "RscStats" const ApiStorPoolStats = "StorPoolStats" const ApiErrReportStats = "ErrReportStats" const ApiLstSchedule = "LstSchedule" const ApiLstQueue = "LstQueue" // ## Query APIs ### const ApiQryMaxVlmSize = "QryMaxVlmSize" const ApiRspMaxVlmSize = "RspMaxVlmSize" const ApiQrySizeInfo = "QrySizeInfo" const ApiQryAllSizeInfo = "QryAllSizeInfo" const ApiRscdfnSyncStatus = "RscDfnSyncStatus" const ApiCheckExtFile = "CheckExtFile" // ## Event APIs ### const ApiCrtWatch = "CrtWatch" const ApiDelWatch = "DelWatch" const ApiEvent = "Event" const ApiRptSpc = "RptSpc" const ApiPing = "Ping" const ApiPong = "Pong" const ApiModInf = "ModInf" const ApiVsnInf = "VsnInf" const ApiSetCtrlProp = "SetCtrlProp" const ApiDelCtrlProp = "DelCtrlProp" const ApiLstCtrlProps = "LstCtrlProps" // ## Encryption APIs ### const ApiEnterCryptPass = "EnterCryptPass" // ## External files APIs ### const ApiSetExtFile = "SetExtFile" const ApiDelExtFile = "DeleteExtFile" const ApiDeployExtFile = "DeployExtFile" const ApiUndeployExtFile = "UndeployExtFile" // ## Database APIs ### const ApiDbExport = "DbExport" // ## DRBD property keys ### const KeyUuid = "UUID" const KeyDrbdCurrentGi = "DrbdCurrentGi" const KeyDmstats = "DMStats" const KeyDrbdAutoQuorum = "auto-quorum" const KeyDrbdAutoAddQuorumTiebreaker = "auto-add-quorum-tiebreaker" const KeyMinorNrAutoRange = "MinorNrAutoRange" const KeyDrbdAutoDiskful = "auto-diskful" const KeyDrbdAutoDiskfulAllowCleanup = "auto-diskful-allow-cleanup" const KeyDrbdDisableAutoResyncAfter = "auto-resync-after-disable" const KeyDrbdDisableAutoVerifyAlgo = "auto-verify-algo-disable" const KeyDrbdAutoVerifyAlgoAllowedUser = "auto-verify-algo-allowed-user-list" const KeyDrbdAutoRsDiscardGranularity = "auto-rs-discard-granularity" const KeyForceInitialSync = "ForceInitialSync" const KeyDrbdSkipDisk = "SkipDisk" const KeyDrbdExactSize = "ExactSize" // ## Node property keys ### const KeyNode = "Node" const Key1StNode = "FirstNode" const Key2NdNode = "SecondNode" const KeyCurStltConnName = "CurStltConnName" // ## Volume-definition property keys ### const KeyPassphrase = "Passphrase" // ## Resource property keys ### const KeyRscDfn = "RscDfn" const KeyRscGrp = "RscGrp" const KeyTcpPortAutoRange = "TcpPortAutoRange" const KeyPeerSlotsNewResource = "PeerSlotsNewResource" const KeyPeerSlots = "PeerSlots" const KeyRscRollbackTarget = "RollbackTarget" const KeyRscMigrateFrom = "MigrateFrom" const KeyRscAllowMixingDeviceKind = "AllowMixingStoragePoolDriver" const KeyRscDiskfulBy = "DiskfulBy" const KeyBalanceResourcesEnabled = "BalanceResourcesEnabled" const KeyBalanceResourcesInterval = "BalanceResourcesInterval" const KeyBalanceResourcesGracePeriod = "BalanceResourcesGracePeriod" // ## Volume property keys ### const KeyVlmGrp = "VlmGrp" const KeyVlmNr = "VlmNr" const KeyVlmRestoreFromResource = "RestoreFromResource" const KeyVlmRestoreFromSnapshot = "RestoreFromSnapshot" // ## ldap property keys ### const KeySearchDomain = "SearchDomain" // ## nvme property keys ### const KeyTrType = "TRType" // ## Snapshot property keys ### const KeySnapshot = "Snapshot" const KeySnapshotDfnSequenceNumber = "SequenceNumber" // ## Network Interface property keys ### const KeyPort = "Port" const KeyDisableHttpMetrics = "disable-http-metrics" // ## Writecache property keys ### const KeyWritecacheBlocksize = "Blocksize" const KeyWritecachePoolName = "PoolName" const KeyWritecacheSize = "Size" const KeyWritecacheOptionHighWatermark = "HighWatermark" const KeyWritecacheOptionLowWatermark = "LowWatermark" const KeyWritecacheOptionStartSector = "StartSector" const KeyWritecacheOptionWritebackJobs = "WritebackJobs" const KeyWritecacheOptionAutocommitBlocks = "AutocommitBlocks" const KeyWritecacheOptionAutocommitTime = "AutocommitTime" const KeyWritecacheOptionFua = "Fua" const KeyWritecacheOptionAdditional = "Additional" // ## Cache property keys ### const KeyCacheOperatingMode = "OpMode" const KeyCacheMetaPoolName = "MetaPool" const KeyCacheCachePoolName = "CachePool" const KeyCacheMetaSize = "Metasize" const KeyCacheCacheSize = "Cachesize" const KeyCacheBlockSize = "Blocksize" const KeyCachePolicy = "Policy" const KeyUpdateCacheInterval = "UpdateCacheInterval" // ## BCache property keys ### const KeyBcachePoolName = "PoolName" const KeyBcacheSize = "Size" const KeyBcacheBlocksize = "Blocksize" const KeyBcacheBucketsize = "Bucketsize" const KeyBcacheDataOffset = "DataOffset" const KeyBcacheWriteback = "Writeback" const KeyBcacheDiscard = "Discard" const KeyBcacheCacheReplacementPolicy = "CacheReplacementPolicy" // ## Autoplace property keys ### const KeyAutoplaceStratWeightMaxFreespace = "MaxFreeSpace" const KeyAutoplaceStratWeightMinReservedSpace = "MinReservedSpace" const KeyAutoplaceStratWeightMinRscCount = "MinRscCount" const KeyAutoplacePreSelectFileName = "PreSelectScript" const KeyAutoplacePreSelectScriptTimeout = "PreSelectScriptTimeout" const KeyAutoplaceMaxThroughput = "MaxThroughput" const KeySite = "Site" const KeyAutoplaceAllowTarget = "AutoplaceTarget" // ## Auto-Evict property keys ### const KeyAutoEvictMinReplicaCount = "AutoEvictMinReplicaCount" const KeyAutoEvictAfterTime = "AutoEvictAfterTime" const KeyAutoEvictMaxDisconnectedNodes = "AutoEvictMaxDisconnectedNodes" const KeyAutoEvictAllowEviction = "AutoEvictAllowEviction" // ## Snapshot shipping property keys ### const KeySnapshotShippingPrefix = "SnapshotShippingPrefix" const KeyTargetNode = "TargetNode" const KeySourceNode = "SourceNode" const KeyRunEvery = "RunEvery" const KeyAutoSnapshotPrefix = "Prefix" const KeyKeep = "Keep" const KeyAutoSnapshotNextId = "NextAutoId" const KeyTcpPortRange = "TcpPortRange" // ## Backup shipping property keys ### const KeyBackupTimeout = "BackupTimeout" const KeyBackupS3Suffix = "S3KeySuffix" const KeyRemote = "Remote" const KeyMaxConcurrentBackupsPerNode = "MaxConcurrentBackupsPerNode" const ValNodeUndecided = "" const KeyAllowForceRestore = "AllowForceRestore" const KeyRecvTimeoutInMs = "L2LReceiveStartedTimeout" const KeyBackupL2LSkipWaitForStart = "L2LSkipWaitForStart" // ## Clone property keys ### const KeyUseZfsClone = "UseZFSClone" // ## NetCom namespaces ### const KeyNetcomEnabled = "Enabled" const KeyNetcomBindAddress = "BindAddress" const KeyNetcomKeyPasswd = "KeyPasswd" const KeyNetcomKeyStore = "KeyStore" const KeyNetcomKeyStorePasswd = "KeyStorePasswd" const KeyNetcomPort = "Port" const KeyNetcomSslProtocol = "SslProtocol" const KeyNetcomTrustStore = "TrustStore" const KeyNetcomTrustStorePasswd = "TrustStorePasswd" const KeyNetcomType = "Type" // ## Property namespaces ### const NamespcNetcom = "NetCom" const NamespcDflt = "Default" const NamespcLogging = "Logging" const NamespcAlloc = "Allocation" const NamespcNetif = "NetIf" const NamespcStlt = "Satellite" const NamespcNode = "Node" const NamespcStorageDriver = "StorDriver" const NamespcDrbdProxy = "DrbdProxy" const NamespcAuxiliary = "Aux" const NamespcDrbdOptions = "DrbdOptions" const NamespcDrbdNetOptions = "DrbdOptions/Net" const NamespcDrbdDiskOptions = "DrbdOptions/Disk" const NamespcDrbdResourceOptions = "DrbdOptions/Resource" const NamespcDrbdPeerDeviceOptions = "DrbdOptions/PeerDevice" const NamespcDrbdProxyOptions = "DrbdOptions/Proxy" const NamespcDrbdProxyCompressionOptions = "DrbdOptions/ProxyCompression" const NamespcDrbdHandlerOptions = "DrbdOptions/Handlers" const NamespcConnectionPaths = "Paths" const NamespcRest = "REST" const NamespcFilesystem = "FileSystem" const NamespcNvme = "NVMe" const NamespcSysFs = "sys/fs" const NamespcWritecache = "Writecache" const NamespcWritecacheOptions = "Writecache/Options" const NamespcCache = "Cache" const NamespcCacheFeatures = "Cache/Features" const NamespcCachePolicyArgs = "Cache/Policy" const NamespcBcache = "BCache" const NamespcAutoplacer = "Autoplacer" const NamespcAutoplacerWeights = "Autoplacer/Weights" const NamespcSnapshotShipping = "SnapshotShipping" const NamespcAutoSnapshot = "AutoSnapshot" const NamespcStltDevSymlinks = "Satellite/Device/Symlinks" const NamespcExos = "StorDriver/Exos" const NamespcBackupShipping = "BackupShipping" const NamespcCluster = "Cluster" const NamespcClusterRemote = "Cluster/Remote" const NamespcSed = "SED" const NamespcEbs = "EBS" const NamespcTags = "Tags" const NamespcEncryption = "Encryption" // ## Storage pool property keys ### const KeyStorPoolDfn = "StorPoolDfn" const KeyStorPoolName = "StorPoolName" const KeyStorPoolDrbdMetaName = "StorPoolNameDrbdMeta" const KeyStorPoolVolumeGroup = "LvmVg" const KeyStorPoolLvcreateType = "LvcreateType" const KeyStorPoolLvcreateOptions = "LvcreateOptions" const KeyStorPoolLvcreateSnapshotOptions = "LvcreateSnapshotOptions" const KeyStorPoolThinPool = "ThinPool" const KeyStorPoolLvmSizesCacheTime = "SizesCacheTime" const KeyStorPoolZpool = "ZPool" const KeyStorPoolZpoolthin = "ZPoolThin" const KeyStorPoolZfsCreateOptions = "ZfscreateOptions" const KeyStorPoolZfsSnapshotOptions = "ZfsSnapshotOptions" const KeyStorPoolFileDirectory = "FileDir" const KeyStorPoolPrefNic = "PrefNic" const KeyStorPoolCryptPasswd = "CryptPasswd" const KeyStorPoolOverrideVlmId = "OverrideVlmId" const KeyStorPoolMaxOversubscriptionRatio = "MaxOversubscriptionRatio" const KeyStorPoolMaxFreeCapacityOversubscriptionRatio = "MaxFreeCapacityOversubscriptionRatio" const KeyStorPoolMaxTotalCapacityOversubscriptionRatio = "MaxTotalCapacityOversubscriptionRatio" const KeyStorPoolWaitTimeoutAfterCreate = "WaitTimeoutAfterCreate" const KeySpecStltPortAutoRange = "SpecStltPortAutoRange" const KeyStorPoolExosApiIp = "IP" const KeyStorPoolExosApiIpEnv = "IPEnv" const KeyStorPoolExosApiPort = "Port" const KeyStorPoolExosApiUser = "Username" const KeyStorPoolExosApiUserEnv = "UsernameEnv" const KeyStorPoolExosApiPassword = "Password" const KeyStorPoolExosApiPasswordEnv = "PasswordEnv" const KeyStorPoolExosVlmType = "VolumeType" const KeyStorPoolExosCreateVolumeOptions = "CreateVolumeOptions" const KeyStorPoolExosEnclosure = "Enclosure" const KeyStorPoolExosPoolSn = "PoolSN" const KeyStorPoolRemoteSpdkApiHost = "RemoteSpdk/ApiHost" const KeyStorPoolRemoteSpdkApiPort = "RemoteSpdk/ApiPort" const KeyStorPoolRemoteSpdkApiUserName = "RemoteSpdk/UserName" const KeyStorPoolRemoteSpdkApiUserPw = "RemoteSpdk/UserPassword" const KeyStorPoolRemoteSpdkApiUserNameEnv = "RemoteSpdk/UserNameEnv" const KeyStorPoolRemoteSpdkApiUserPwEnv = "RemoteSpdk/UserPasswordEnv" const KeyEbsVolumeType = "EBS/VolumeType" const KeyPrefNic = "PrefNic" // ## Storage pool traits keys ### const KeyStorPoolSupportsSnapshots = "SupportsSnapshots" const KeyStorPoolProvisioning = "Provisioning" // Unit of smallest allocation. The size in KiB as a decimal number. const KeyStorPoolAllocationUnit = "AllocationUnit" // ## Storage pool traits values ### const ValStorPoolProvisioningFat = "Fat" const ValStorPoolProvisioningThin = "Thin" const ValStorPoolDrbdMetaInternal = ".internal" const ValStorPoolSpaceNotFound = -1 const ValStorPoolSpaceEnough = 9223372036854775807 // ## DRBD Proxy keys (other than 'options') ### const KeyDrbdProxyCompressionType = "CompressionType" const KeyDrbdProxyAutoEnable = "AutoEnable" // ## File system property keys ### const KeyFsType = "Type" const KeyFsMkfsparameters = "MkfsParams" const KeyFsUser = "User" const KeyFsGroup = "Group" const ValFsTypeExt4 = "ext4" const ValFsTypeXfs = "xfs" // ## sys/fs property keys ### const KeySysFsBlkioThrottleRead = "blkio_throttle_read" const KeySysFsBlkioThrottleWrite = "blkio_throttle_write" const KeySysFsBlkioThrottleReadIops = "blkio_throttle_read_iops" const KeySysFsBlkioThrottleWriteIops = "blkio_throttle_write_iops" // ## Property values ### const ValNetcomTypeSsl = "SSL" const ValNetcomTypePlain = "Plain" const ValSslProtoTlsv1 = "TLSv1" // ## DRBD related property values ### const ValDrbdProxyCompressionNone = "none" const ValDrbdProxyCompressionZstd = "zstd" const ValDrbdProxyCompressionZlib = "zlib" const ValDrbdProxyCompressionLzma = "lzma" const ValDrbdProxyCompressionLz4 = "lz4" const ValDrbdAutoQuorumDisabled = "disabled" const ValDrbdAutoQuorumIoError = "io-error" const ValDrbdAutoQuorumSuspendIo = "suspend-io" // ## Node Type values ### const ValNodeTypeCtrl = "Controller" const ValNodeTypeStlt = "Satellite" const ValNodeTypeCmbd = "Combined" const ValNodeTypeAux = "Auxiliary" const ValNodeTypeRemoteSpdk = "Remote_Spdk" const ValNodeTypeExosTarget = "Exos_Target" // ## Writecache option values ### const ValWritecacheFuaOn = "On" const ValWritecacheFuaOff = "Off" // ## Net interface Type values ### const ValNetifTypeIp = "IP" const ValNetifTypeRdma = "RDMA" const ValNetifTypeRoce = "RoCE" // ## Authentication keys ### const KeySecIdentity = "SecIdentity" const KeySecRole = "SecRole" const KeySecType = "SecType" const KeySecDomain = "SecDomain" const KeySecPassword = "SecPassword" const KeyPoolName = "PoolName" // ## External commands keys ### const KeyExtCmdWaitTo = "ExtCmdWaitTimeout" // ## External files keys ### const KeyExtFile = "ExtFile" // ## Default ports ### const DfltCtrlPortSsl = 3371 const DfltCtrlPortPlain = 3370 const DfltStltPortSsl = 3367 const DfltStltPortPlain = 3366 // ## Boolean values ### const ValTrue = "True" const ValFalse = "False" const ValYes = "Yes" const ValNo = "No" // ## Snapshot-shipping values ### const ValSnapShipName = "SnapshotShipping" // enum generated in package -> "golinstor/snapshotshipstatus" // snapshotshipstatus.Running = "Running" // snapshotshipstatus.Complete = "Complete" // ## Flag string values ### const FlagClean = "CLEAN" const FlagEvicted = "EVICTED" const FlagEvacuate = "EVACUATE" const FlagDelete = "DELETE" const FlagDrbdDelete = "DRBD_DELETE" const FlagDiskless = "DISKLESS" const FlagQignore = "QIGNORE" const FlagEncrypted = "ENCRYPTED" const FlagGrossSize = "GROSS_SIZE" const FlagSuccessful = "SUCCESSFUL" const FlagShipping = "SHIPPING" const FlagShippingCleanup = "SHIPPING_CLEANUP" const FlagShippingAbort = "SHIPPING_ABORT" const FlagShipped = "SHIPPED" const FlagAutoSnapshot = "AUTO_SNAPSHOT" const FlagBackup = "BACKUP" const FlagRestoreBackupOnSuccess = "RESTORE_BACKUP_ON_SUCCESS" const FlagBackupTarget = "BACKUP_TARGET" const FlagBackupSource = "BACKUP_SOURCE" const FlagFailedDeployment = "FAILED_DEPLOYMENT" const FlagFailedDisconnect = "FAILED_DISCONNECT" const FlagResize = "RESIZE" const FlagDiskAdding = "DISK_ADDING" const FlagDiskAddRequested = "DISK_ADD_REQUESTED" const FlagDiskRemoving = "DISK_REMOVING" const FlagDiskRemoveRequested = "DISK_REMOVE_REQUESTED" const FlagTieBreaker = "TIE_BREAKER" const FlagDrbdDiskless = "DRBD_DISKLESS" const FlagNvmeInitiator = "NVME_INITIATOR" const FlagRscInactive = "INACTIVE" const FlagEbsInitiator = "EBS_INITIATOR" // ## Device layer kinds ### // enum generated in package -> "golinstor/devicelayerkind" // devicelayerkind.Drbd = "DRBD" // devicelayerkind.Luks = "LUKS" // devicelayerkind.Storage = "STORAGE" // devicelayerkind.Nvme = "NVME" // devicelayerkind.Exos = "EXOS" // devicelayerkind.Writecache = "WRITECACHE" // devicelayerkind.Cache = "CACHE" // devicelayerkind.Bcache = "BCACHE" // ## Satellite connection statuses ### // enum generated in package -> "golinstor/connectionstatus" // connectionstatus.Offline = 0 // connectionstatus.Connected = 1 // connectionstatus.Online = 2 // connectionstatus.VersionMismatch = 3 // connectionstatus.FullSyncFailed = 4 // connectionstatus.AuthenticationError = 5 // connectionstatus.Unknown = 6 // connectionstatus.HostnameMismatch = 7 // connectionstatus.OtherController = 8 // connectionstatus.Authenticated = 9 // connectionstatus.NoStltConn = 10 // connectionstatus.MissingExtTools = 11 // ## Clone states ### // enum generated in package -> "golinstor/clonestatus" // clonestatus.Failed = "FAILED" // clonestatus.Cloning = "CLONING" // clonestatus.Complete = "COMPLETE" // ## Default names ### const DefaultNetif = "default" const DfltSnapshotShippingPrefix = "ship" // ## Default values ### const DfltAutoSnapshotKeep = "10" const DfltShippedSnapshotKeep = "10" golang-github-linbit-golinstor-0.55.0/cache/000077500000000000000000000000001501671500700206725ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/cache/cache.go000066400000000000000000000126011501671500700222640ustar00rootroot00000000000000// Package cache // // Implement client side caching for client.Client. This is useful for burst-happy applications that will try to query // a lot of the same information in small chunks. // // For example, an application could try to check the state of nodes, but do so using one request per node. This is // obviously not ideal in larger cluster, where it would be more efficient to request the state of all nodes at once. // Depending on the application, this may not be possible, however. // // This package contains ready-to-use client side caches with configurable duration and automatic invalidation under the // assumption that modifications are made from the same client. package cache import ( "fmt" "strings" "sync" "time" log "github.com/sirupsen/logrus" "github.com/LINBIT/golinstor/client" ) var ( yes = true cacheOpt = &client.ListOpts{Cached: &yes} ) type Cache interface { apply(c *client.Client) } // WithCaches sets up the given caches on the client.Client. func WithCaches(caches ...Cache) client.Option { return func(cl *client.Client) error { for _, ca := range caches { ca.apply(cl) } return nil } } type cache struct { mu sync.Mutex lastUpdate time.Time cache any } // Invalidate forcefully resets the cache. // The next call to Get will always invoke the provided function. func (c *cache) Invalidate() { c.mu.Lock() c.lastUpdate = time.Time{} c.cache = nil c.mu.Unlock() } // Get returns a cached response or the result of the provided update function. // // If the cache is current, it will return the last successful cached response. // If the cache is outdated, it will run the provided function to retrieve a result. A successful response // is cached for later use. func (c *cache) Get(timeout time.Duration, updateFunc func() (any, error)) (any, error) { c.mu.Lock() defer c.mu.Unlock() now := time.Now() if timeout != 0 && c.lastUpdate.Add(timeout).Before(now) { result, err := updateFunc() if err != nil { return nil, err } c.cache = result c.lastUpdate = now } return c.cache, nil } // filterNodeAndPoolOpts filters generic items based on the provided client.ListOpts // This tries to mimic the behaviour of LINSTOR when using the node and storage pool query parameters. func filterNodeAndPoolOpts[T Filterable](items []T, opts ...*client.ListOpts) []T { filterNodes := make(map[string]struct{}) filterPools := make(map[string]struct{}) var filterProps []string if hasUnsupportedOpts(opts...) { log.WithField("opts", opts).Warn("unsupported filter opts in cache") } for _, o := range opts { for _, n := range o.Node { filterNodes[n] = struct{}{} } for _, sp := range o.StoragePool { filterPools[sp] = struct{}{} } filterProps = append(filterProps, o.Prop...) } var result []T outer: for i := range items { if len(filterNodes) > 0 { if !anyMatches(filterNodes, nodes(&items[i])) { continue } } if len(filterPools) > 0 { if !anyMatches(filterPools, pools(&items[i])) { continue } } itemProps := props(&items[i]) for _, filterProp := range filterProps { key, val, found := strings.Cut(filterProp, "=") itemVal, ok := itemProps[key] if !ok { continue outer } if found && val != itemVal { continue outer } } result = append(result, items[i]) } return result } func hasUnsupportedOpts(opts ...*client.ListOpts) bool { for _, opt := range opts { if len(opt.Snapshots) > 0 || len(opt.Resource) > 0 || opt.Limit != 0 || opt.Offset != 0 { return true } } return false } type Filterable interface { client.Node | client.StoragePool | client.ResourceWithVolumes | client.Snapshot | client.PhysicalStorageViewItem } func anyMatches(haystack map[string]struct{}, items []string) bool { for _, item := range items { if _, ok := haystack[item]; ok { return true } } return false } func nodes(item any) []string { switch item.(type) { case *client.Node: return []string{item.(*client.Node).Name} case *client.StoragePool: return []string{item.(*client.StoragePool).NodeName} case *client.ResourceWithVolumes: return []string{item.(*client.ResourceWithVolumes).NodeName} case *client.Snapshot: return item.(*client.Snapshot).Nodes case *client.PhysicalStorageViewItem: var result []string for k := range item.(*client.PhysicalStorageViewItem).Nodes { result = append(result, k) } return result default: panic(fmt.Sprintf("unsupported item type: %T", item)) } } func pools(item any) []string { switch item.(type) { case *client.Node: return nil case *client.StoragePool: return []string{item.(*client.StoragePool).StoragePoolName} case *client.ResourceWithVolumes: var result []string for _, vol := range item.(*client.ResourceWithVolumes).Volumes { result = append(result, vol.StoragePoolName) } return result case *client.Snapshot: return nil case *client.PhysicalStorageViewItem: return nil default: panic(fmt.Sprintf("unsupported item type: %T", item)) } } func props(item any) map[string]string { switch item.(type) { case *client.Node: return item.(*client.Node).Props case *client.StoragePool: return item.(*client.StoragePool).Props case *client.ResourceWithVolumes: return item.(*client.ResourceWithVolumes).Props case *client.Snapshot: return item.(*client.Snapshot).Props case *client.PhysicalStorageViewItem: return nil default: panic(fmt.Sprintf("unsupported item type: %T", item)) } } golang-github-linbit-golinstor-0.55.0/cache/cache_test.go000066400000000000000000000103271501671500700233260ustar00rootroot00000000000000package cache_test import ( "context" "encoding/json" "net/http" "net/http/httptest" "net/url" "testing" "time" "github.com/stretchr/testify/assert" linstor "github.com/LINBIT/golinstor" "github.com/LINBIT/golinstor/cache" "github.com/LINBIT/golinstor/client" ) type TestResponse struct { Code int Body any } type TestServer struct { counter map[string]int responses map[string]TestResponse } func (t *TestServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) { req := request.Method + " " + request.URL.String() t.counter[req]++ resp, ok := t.responses[req] if !ok { writer.WriteHeader(http.StatusInternalServerError) } else { writer.WriteHeader(resp.Code) _ = json.NewEncoder(writer).Encode(resp.Body) } } const ( NodesRequest = "GET /v1/nodes?cached=true&limit=0&offset=0" ReconnectRequest = "PUT /v1/nodes/node1/reconnect" ) var ( Node1 = client.Node{ Name: "node1", Props: map[string]string{ "Aux/key1": "val1", "Aux/key2": "val2", }, Type: linstor.ValNodeTypeStlt, } Node2 = client.Node{ Name: "node2", Props: map[string]string{ "Aux/key2": "", "Aux/key3": "val3", }, Type: linstor.ValNodeTypeCmbd, } AllNodes = []client.Node{Node1, Node2} ) func TestNodeCache(t *testing.T) { testSrv := TestServer{ counter: make(map[string]int), responses: map[string]TestResponse{ NodesRequest: {Code: http.StatusOK, Body: AllNodes}, ReconnectRequest: {Code: http.StatusOK}, }, } srv := httptest.NewServer(&testSrv) defer srv.Close() u, err := url.Parse(srv.URL) assert.NoError(t, err) cl, err := client.NewClient( client.HTTPClient(srv.Client()), client.BaseURL(u), cache.WithCaches(&cache.NodeCache{Timeout: 1 * time.Second}), ) assert.NoError(t, err) nodes, err := cl.Nodes.GetAll(context.Background()) assert.NoError(t, err) assert.Equal(t, AllNodes, nodes) node1, err := cl.Nodes.Get(context.Background(), "node1") assert.NoError(t, err) assert.Equal(t, Node1, node1) _, err = cl.Nodes.Get(context.Background(), "node3") assert.Equal(t, client.NotFoundError, err) // Assert that the request was only sent once assert.Equal(t, 1, testSrv.counter[NodesRequest]) // Invalidate cache err = cl.Nodes.Reconnect(context.Background(), "node1") assert.NoError(t, err) node1, err = cl.Nodes.Get(context.Background(), "node1") assert.NoError(t, err) assert.Equal(t, Node1, node1) assert.Equal(t, 2, testSrv.counter[NodesRequest]) // Wait for cache time out to be reached time.Sleep(1*time.Second + 100*time.Millisecond) node1, err = cl.Nodes.Get(context.Background(), "node1") assert.NoError(t, err) assert.Equal(t, Node1, node1) assert.Equal(t, 3, testSrv.counter[NodesRequest]) } func TestNodeCachePropsFiltering(t *testing.T) { testSrv := TestServer{ counter: make(map[string]int), responses: map[string]TestResponse{ NodesRequest: {Code: http.StatusOK, Body: AllNodes}, ReconnectRequest: {Code: http.StatusOK}, }, } srv := httptest.NewServer(&testSrv) defer srv.Close() u, err := url.Parse(srv.URL) assert.NoError(t, err) cl, err := client.NewClient( client.HTTPClient(srv.Client()), client.BaseURL(u), cache.WithCaches(&cache.NodeCache{Timeout: 1 * time.Second}), ) assert.NoError(t, err) // Filtering by presence of key nodes, err := cl.Nodes.GetAll(context.Background(), &client.ListOpts{Prop: []string{"Aux/key2"}}) assert.NoError(t, err) assert.Equal(t, AllNodes, nodes) // Filtering by presence of key only on one node nodes, err = cl.Nodes.GetAll(context.Background(), &client.ListOpts{Prop: []string{"Aux/key3"}}) assert.NoError(t, err) assert.Equal(t, []client.Node{Node2}, nodes) // Filtering by presence of key on no node nodes, err = cl.Nodes.GetAll(context.Background(), &client.ListOpts{Prop: []string{"Aux/other"}}) assert.NoError(t, err) assert.Empty(t, nodes) // Filtering by presence of specific key=value nodes, err = cl.Nodes.GetAll(context.Background(), &client.ListOpts{Prop: []string{"Aux/key2=val2"}}) assert.NoError(t, err) assert.Equal(t, []client.Node{Node1}, nodes) // Filtering by presence of specific key="" nodes, err = cl.Nodes.GetAll(context.Background(), &client.ListOpts{Prop: []string{"Aux/key2="}}) assert.NoError(t, err) assert.Equal(t, []client.Node{Node2}, nodes) } golang-github-linbit-golinstor-0.55.0/cache/node.go000066400000000000000000000163511501671500700221540ustar00rootroot00000000000000package cache import ( "context" "time" "github.com/LINBIT/golinstor/client" ) // NodeCache caches respones from a client.NodeProvider. type NodeCache struct { // Timeout for the cached responses. Timeout time.Duration nodeCache cache storagePoolCache cache physicalStorageCache cache } func (n *NodeCache) apply(c *client.Client) { c.Nodes = &nodeCacheProvider{ cl: c.Nodes, cache: n, } } type nodeCacheProvider struct { cl client.NodeProvider cache *NodeCache } var _ client.NodeProvider = &nodeCacheProvider{} func (n *nodeCacheProvider) GetAll(ctx context.Context, opts ...*client.ListOpts) ([]client.Node, error) { c, err := n.cache.nodeCache.Get(n.cache.Timeout, func() (any, error) { return n.cl.GetAll(ctx, cacheOpt) }) if err != nil { return nil, err } return filterNodeAndPoolOpts(c.([]client.Node), opts...), nil } func (n *nodeCacheProvider) Get(ctx context.Context, nodeName string, opts ...*client.ListOpts) (client.Node, error) { nodes, err := n.GetAll(ctx, opts...) if err != nil { return client.Node{}, err } for i := range nodes { if nodes[i].Name == nodeName { return nodes[i], nil } } return client.Node{}, client.NotFoundError } func (n *nodeCacheProvider) Create(ctx context.Context, node client.Node) error { n.cache.nodeCache.Invalidate() return n.cl.Create(ctx, node) } func (n *nodeCacheProvider) CreateEbsNode(ctx context.Context, name string, remoteName string) error { n.cache.nodeCache.Invalidate() return n.cl.CreateEbsNode(ctx, name, remoteName) } func (n *nodeCacheProvider) Modify(ctx context.Context, nodeName string, props client.NodeModify) error { n.cache.nodeCache.Invalidate() return n.cl.Modify(ctx, nodeName, props) } func (n *nodeCacheProvider) Delete(ctx context.Context, nodeName string) error { n.cache.nodeCache.Invalidate() return n.cl.Delete(ctx, nodeName) } func (n *nodeCacheProvider) Lost(ctx context.Context, nodeName string) error { n.cache.nodeCache.Invalidate() return n.cl.Lost(ctx, nodeName) } func (n *nodeCacheProvider) Reconnect(ctx context.Context, nodeName string) error { n.cache.nodeCache.Invalidate() return n.cl.Reconnect(ctx, nodeName) } func (n *nodeCacheProvider) GetNetInterfaces(ctx context.Context, nodeName string, opts ...*client.ListOpts) ([]client.NetInterface, error) { node, err := n.Get(ctx, nodeName, opts...) if err != nil { return nil, err } return node.NetInterfaces, nil } func (n *nodeCacheProvider) GetNetInterface(ctx context.Context, nodeName, nifName string, opts ...*client.ListOpts) (client.NetInterface, error) { interfaces, err := n.GetNetInterfaces(ctx, nodeName, opts...) if err != nil { return client.NetInterface{}, err } for i := range interfaces { if interfaces[i].Name == nifName { return interfaces[i], nil } } return client.NetInterface{}, client.NotFoundError } func (n *nodeCacheProvider) CreateNetInterface(ctx context.Context, nodeName string, nif client.NetInterface) error { n.cache.nodeCache.Invalidate() return n.cl.CreateNetInterface(ctx, nodeName, nif) } func (n *nodeCacheProvider) ModifyNetInterface(ctx context.Context, nodeName, nifName string, nif client.NetInterface) error { n.cache.nodeCache.Invalidate() return n.cl.ModifyNetInterface(ctx, nodeName, nifName, nif) } func (n *nodeCacheProvider) DeleteNetinterface(ctx context.Context, nodeName, nifName string) error { n.cache.nodeCache.Invalidate() return n.cl.DeleteNetinterface(ctx, nodeName, nifName) } func (n *nodeCacheProvider) Evict(ctx context.Context, nodeName string) error { n.cache.nodeCache.Invalidate() return n.cl.Evict(ctx, nodeName) } func (n *nodeCacheProvider) Restore(ctx context.Context, nodeName string, restore client.NodeRestore) error { n.cache.nodeCache.Invalidate() return n.cl.Restore(ctx, nodeName, restore) } func (n *nodeCacheProvider) Evacuate(ctx context.Context, nodeName string) error { n.cache.nodeCache.Invalidate() return n.cl.Evacuate(ctx, nodeName) } func (n *nodeCacheProvider) GetStoragePoolView(ctx context.Context, opts ...*client.ListOpts) ([]client.StoragePool, error) { result, err := n.cache.storagePoolCache.Get(n.cache.Timeout, func() (any, error) { return n.cl.GetStoragePoolView(ctx, cacheOpt) }) if err != nil { return nil, err } return filterNodeAndPoolOpts(result.([]client.StoragePool), opts...), nil } func (n *nodeCacheProvider) GetStoragePools(ctx context.Context, nodeName string, opts ...*client.ListOpts) ([]client.StoragePool, error) { allPools, err := n.GetStoragePoolView(ctx, opts...) if err != nil { return nil, err } var pools []client.StoragePool for i := range allPools { if allPools[i].NodeName == nodeName { pools = append(pools, allPools[i]) } } return pools, nil } func (n *nodeCacheProvider) GetStoragePool(ctx context.Context, nodeName, spName string, opts ...*client.ListOpts) (client.StoragePool, error) { pools, err := n.GetStoragePools(ctx, nodeName, opts...) if err != nil { return client.StoragePool{}, err } for i := range pools { if pools[i].StoragePoolName == spName { return pools[i], nil } } return client.StoragePool{}, client.NotFoundError } func (n *nodeCacheProvider) CreateStoragePool(ctx context.Context, nodeName string, sp client.StoragePool) error { n.cache.storagePoolCache.Invalidate() return n.cl.CreateStoragePool(ctx, nodeName, sp) } func (n *nodeCacheProvider) ModifyStoragePool(ctx context.Context, nodeName, spName string, genericProps client.GenericPropsModify) error { n.cache.storagePoolCache.Invalidate() return n.cl.ModifyStoragePool(ctx, nodeName, spName, genericProps) } func (n *nodeCacheProvider) DeleteStoragePool(ctx context.Context, nodeName, spName string) error { n.cache.storagePoolCache.Invalidate() return n.cl.DeleteStoragePool(ctx, nodeName, spName) } func (n *nodeCacheProvider) GetPhysicalStorageView(ctx context.Context, opts ...*client.ListOpts) ([]client.PhysicalStorageViewItem, error) { result, err := n.cache.physicalStorageCache.Get(n.cache.Timeout, func() (any, error) { return n.cl.GetPhysicalStorageView(ctx, cacheOpt) }) if err != nil { return nil, err } return filterNodeAndPoolOpts(result.([]client.PhysicalStorageViewItem), opts...), nil } func (n *nodeCacheProvider) GetPhysicalStorage(ctx context.Context, nodeName string) ([]client.PhysicalStorageNode, error) { view, err := n.GetPhysicalStorageView(ctx) if err != nil { return nil, err } var result []client.PhysicalStorageNode for i := range view { for j := range view[i].Nodes[nodeName] { result = append(result, client.PhysicalStorageNode{ Rotational: view[i].Rotational, Size: view[i].Size, PhysicalStorageDevice: view[i].Nodes[nodeName][j], }) } } return result, nil } func (n *nodeCacheProvider) CreateDevicePool(ctx context.Context, nodeName string, psc client.PhysicalStorageCreate) error { n.cache.physicalStorageCache.Invalidate() return n.cl.CreateDevicePool(ctx, nodeName, psc) } func (n *nodeCacheProvider) GetStoragePoolPropsInfos(ctx context.Context, nodeName string, opts ...*client.ListOpts) ([]client.PropsInfo, error) { return n.cl.GetStoragePoolPropsInfos(ctx, nodeName, opts...) } func (n *nodeCacheProvider) GetPropsInfos(ctx context.Context, opts ...*client.ListOpts) ([]client.PropsInfo, error) { return n.cl.GetPropsInfos(ctx, opts...) } golang-github-linbit-golinstor-0.55.0/cache/resource.go000066400000000000000000000250231501671500700230520ustar00rootroot00000000000000package cache import ( "context" "time" "github.com/LINBIT/golinstor/client" ) // ResourceCache caches responses from a client.ResourceProvider. type ResourceCache struct { // Timeout for the cached responses. Timeout time.Duration resourceCache cache snapshotCache cache } // backupShim hooks into the backup provider and invalidates the resource cache on certain operations. type backupShim struct { client.BackupProvider resourceCache *cache snapshotCache *cache } func (r *ResourceCache) apply(c *client.Client) { c.Resources = &resourceCacheProvider{ cl: c.Resources, cache: r, } c.Backup = backupShim{ BackupProvider: c.Backup, resourceCache: &r.resourceCache, snapshotCache: &r.snapshotCache, } } type resourceCacheProvider struct { cl client.ResourceProvider cache *ResourceCache } var _ client.ResourceProvider = &resourceCacheProvider{} var _ client.BackupProvider = backupShim{} func (r *resourceCacheProvider) GetResourceView(ctx context.Context, opts ...*client.ListOpts) ([]client.ResourceWithVolumes, error) { result, err := r.cache.resourceCache.Get(r.cache.Timeout, func() (any, error) { return r.cl.GetResourceView(ctx, cacheOpt) }) if err != nil { return nil, err } return filterNodeAndPoolOpts(result.([]client.ResourceWithVolumes), opts...), nil } func (r *resourceCacheProvider) GetAll(ctx context.Context, resName string, opts ...*client.ListOpts) ([]client.Resource, error) { ress, err := r.GetResourceView(ctx, opts...) if err != nil { return nil, err } var result []client.Resource for i := range ress { if ress[i].Name == resName { result = append(result, ress[i].Resource) } } return result, nil } func (r *resourceCacheProvider) Get(ctx context.Context, resName, nodeName string, opts ...*client.ListOpts) (client.Resource, error) { ress, err := r.GetAll(ctx, resName, opts...) if err != nil { return client.Resource{}, err } for i := range ress { if ress[i].NodeName == nodeName { return ress[i], nil } } return client.Resource{}, client.NotFoundError } func (r *resourceCacheProvider) GetVolumes(ctx context.Context, resName, nodeName string, opts ...*client.ListOpts) ([]client.Volume, error) { ress, err := r.GetResourceView(ctx, opts...) if err != nil { return nil, err } var result []client.Volume for i := range ress { if ress[i].NodeName != nodeName || ress[i].Name != resName { continue } for j := range ress[i].Volumes { result = append(result, ress[i].Volumes[j]) } } return result, nil } func (r *resourceCacheProvider) GetVolume(ctx context.Context, resName, nodeName string, volNr int, opts ...*client.ListOpts) (client.Volume, error) { volumes, err := r.GetVolumes(ctx, resName, nodeName, opts...) if err != nil { return client.Volume{}, err } for i := range volumes { if int(volumes[i].VolumeNumber) == volNr { return volumes[i], nil } } return client.Volume{}, client.NotFoundError } func (r *resourceCacheProvider) Create(ctx context.Context, res client.ResourceCreate) error { r.cache.resourceCache.Invalidate() return r.cl.Create(ctx, res) } func (r *resourceCacheProvider) Modify(ctx context.Context, resName, nodeName string, props client.GenericPropsModify) error { r.cache.resourceCache.Invalidate() return r.cl.Modify(ctx, resName, nodeName, props) } func (r *resourceCacheProvider) Delete(ctx context.Context, resName, nodeName string) error { r.cache.resourceCache.Invalidate() return r.cl.Delete(ctx, resName, nodeName) } func (r *resourceCacheProvider) ModifyVolume(ctx context.Context, resName, nodeName string, volNr int, props client.GenericPropsModify) error { r.cache.resourceCache.Invalidate() return r.cl.ModifyVolume(ctx, resName, nodeName, volNr, props) } func (r *resourceCacheProvider) Diskless(ctx context.Context, resName, nodeName, disklessPoolName string) error { r.cache.resourceCache.Invalidate() return r.cl.Diskless(ctx, resName, nodeName, disklessPoolName) } func (r *resourceCacheProvider) Diskful(ctx context.Context, resName, nodeName, storagePoolName string, props *client.ToggleDiskDiskfulProps) error { r.cache.resourceCache.Invalidate() return r.cl.Diskful(ctx, resName, nodeName, storagePoolName, props) } func (r *resourceCacheProvider) Migrate(ctx context.Context, resName, fromNodeName, toNodeName, storagePoolName string) error { r.cache.resourceCache.Invalidate() return r.cl.Migrate(ctx, resName, fromNodeName, toNodeName, storagePoolName) } func (r *resourceCacheProvider) Autoplace(ctx context.Context, resName string, apr client.AutoPlaceRequest) error { r.cache.resourceCache.Invalidate() return r.cl.Autoplace(ctx, resName, apr) } func (r *resourceCacheProvider) Activate(ctx context.Context, resName string, nodeName string) error { r.cache.resourceCache.Invalidate() return r.cl.Activate(ctx, resName, nodeName) } func (r *resourceCacheProvider) Deactivate(ctx context.Context, resName string, nodeName string) error { r.cache.resourceCache.Invalidate() return r.cl.Deactivate(ctx, resName, nodeName) } func (r *resourceCacheProvider) MakeAvailable(ctx context.Context, resName, nodeName string, makeAvailable client.ResourceMakeAvailable) error { r.cache.resourceCache.Invalidate() return r.cl.MakeAvailable(ctx, resName, nodeName, makeAvailable) } func (r *resourceCacheProvider) GetSnapshotView(ctx context.Context, opts ...*client.ListOpts) ([]client.Snapshot, error) { result, err := r.cache.snapshotCache.Get(r.cache.Timeout, func() (any, error) { return r.cl.GetSnapshotView(ctx, cacheOpt) }) if err != nil { return nil, err } return filterNodeAndPoolOpts(result.([]client.Snapshot), opts...), nil } func (r *resourceCacheProvider) GetSnapshots(ctx context.Context, resName string, opts ...*client.ListOpts) ([]client.Snapshot, error) { snaps, err := r.GetSnapshotView(ctx, opts...) if err != nil { return nil, err } var result []client.Snapshot for i := range snaps { if snaps[i].ResourceName == resName { result = append(result, snaps[i]) } } return result, nil } func (r *resourceCacheProvider) GetSnapshot(ctx context.Context, resName, snapName string, opts ...*client.ListOpts) (client.Snapshot, error) { snaps, err := r.GetSnapshots(ctx, resName, opts...) if err != nil { return client.Snapshot{}, err } for i := range snaps { if snaps[i].Name == snapName { return snaps[i], nil } } return client.Snapshot{}, client.NotFoundError } func (r *resourceCacheProvider) CreateSnapshot(ctx context.Context, snapshot client.Snapshot) error { r.cache.snapshotCache.Invalidate() return r.cl.CreateSnapshot(ctx, snapshot) } func (r *resourceCacheProvider) DeleteSnapshot(ctx context.Context, resName, snapName string, nodes ...string) error { r.cache.snapshotCache.Invalidate() return r.cl.DeleteSnapshot(ctx, resName, snapName, nodes...) } func (r *resourceCacheProvider) RestoreSnapshot(ctx context.Context, origResName, snapName string, snapRestoreConf client.SnapshotRestore) error { // This will create new resources, not touch snapshots r.cache.resourceCache.Invalidate() return r.cl.RestoreSnapshot(ctx, origResName, snapName, snapRestoreConf) } func (r *resourceCacheProvider) RestoreVolumeDefinitionSnapshot(ctx context.Context, origResName, snapName string, snapRestoreConf client.SnapshotRestore) error { // This will create new resources, not touch snapshots r.cache.resourceCache.Invalidate() return r.cl.RestoreVolumeDefinitionSnapshot(ctx, origResName, snapName, snapRestoreConf) } func (r *resourceCacheProvider) RollbackSnapshot(ctx context.Context, resName, snapName string) error { return r.cl.RollbackSnapshot(ctx, resName, snapName) } func (r *resourceCacheProvider) GetConnections(ctx context.Context, resName, nodeAName, nodeBName string, opts ...*client.ListOpts) ([]client.ResourceConnection, error) { return r.cl.GetConnections(ctx, resName, nodeAName, nodeBName, opts...) } func (r *resourceCacheProvider) ModifyConnection(ctx context.Context, resName, nodeAName, nodeBName string, props client.GenericPropsModify) error { return r.cl.ModifyConnection(ctx, resName, nodeAName, nodeBName, props) } func (r *resourceCacheProvider) EnableSnapshotShipping(ctx context.Context, resName string, ship client.SnapshotShipping) error { return r.cl.EnableSnapshotShipping(ctx, resName, ship) } func (r *resourceCacheProvider) ModifyDRBDProxy(ctx context.Context, resName string, props client.DrbdProxyModify) error { return r.cl.ModifyDRBDProxy(ctx, resName, props) } func (r *resourceCacheProvider) EnableDRBDProxy(ctx context.Context, resName, nodeAName, nodeBName string) error { return r.cl.EnableDRBDProxy(ctx, resName, nodeAName, nodeBName) } func (r *resourceCacheProvider) DisableDRBDProxy(ctx context.Context, resName, nodeAName, nodeBName string) error { return r.cl.DisableDRBDProxy(ctx, resName, nodeAName, nodeBName) } func (r *resourceCacheProvider) QueryMaxVolumeSize(ctx context.Context, filter client.AutoSelectFilter) (client.MaxVolumeSizes, error) { return r.cl.QueryMaxVolumeSize(ctx, filter) } func (r *resourceCacheProvider) GetSnapshotShippings(ctx context.Context, opts ...*client.ListOpts) ([]client.SnapshotShippingStatus, error) { return r.cl.GetSnapshotShippings(ctx, opts...) } func (r *resourceCacheProvider) GetPropsInfos(ctx context.Context, resName string, opts ...*client.ListOpts) ([]client.PropsInfo, error) { return r.cl.GetPropsInfos(ctx, resName, opts...) } func (r *resourceCacheProvider) GetVolumeDefinitionPropsInfos(ctx context.Context, resName string, opts ...*client.ListOpts) ([]client.PropsInfo, error) { return r.cl.GetVolumeDefinitionPropsInfos(ctx, resName, opts...) } func (r *resourceCacheProvider) GetVolumePropsInfos(ctx context.Context, resName, nodeName string, opts ...*client.ListOpts) ([]client.PropsInfo, error) { return r.cl.GetVolumePropsInfos(ctx, resName, nodeName, opts...) } func (r *resourceCacheProvider) GetConnectionPropsInfos(ctx context.Context, resName string, opts ...*client.ListOpts) ([]client.PropsInfo, error) { return r.cl.GetConnectionPropsInfos(ctx, resName, opts...) } func (b backupShim) Restore(ctx context.Context, remoteName string, request client.BackupRestoreRequest) error { b.snapshotCache.Invalidate() b.resourceCache.Invalidate() return b.BackupProvider.Restore(ctx, remoteName, request) } func (b backupShim) Create(ctx context.Context, remoteName string, request client.BackupCreate) (string, error) { b.snapshotCache.Invalidate() return b.BackupProvider.Create(ctx, remoteName, request) } func (b backupShim) Ship(ctx context.Context, remoteName string, request client.BackupShipRequest) (string, error) { b.snapshotCache.Invalidate() return b.BackupProvider.Ship(ctx, remoteName, request) } golang-github-linbit-golinstor-0.55.0/client/000077500000000000000000000000001501671500700211055ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/client/apicallerror.go000066400000000000000000000020601501671500700241110ustar00rootroot00000000000000package client import ( "strings" ) type ApiCallError []ApiCallRc func (e ApiCallError) Error() string { var finalErr string for i, r := range e { finalErr += strings.TrimSpace(r.String()) if i < len(e)-1 { finalErr += " next error: " } } return finalErr } // Is is a shorthand for checking all ApiCallRcs of an ApiCallError against // a given mask. func (e ApiCallError) Is(mask uint64) bool { for _, r := range e { if r.Is(mask) { return true } } return false } // Is can be used to check the return code against a given mask. Since LINSTOR // return codes are designed to be machine readable, this can be used to check // for a very specific type of error. // Refer to package apiconsts.go in package linstor for a list of possible // mask values. func (r ApiCallRc) Is(mask uint64) bool { return (uint64(r.RetCode) & mask) == mask } // IsApiCallError checks if an error is a specific type of LINSTOR error. func IsApiCallError(err error, mask uint64) bool { e, ok := err.(ApiCallError) if !ok { return false } return e.Is(mask) } golang-github-linbit-golinstor-0.55.0/client/apicallerror_test.go000066400000000000000000000017451501671500700251610ustar00rootroot00000000000000package client import ( "testing" linstor "github.com/LINBIT/golinstor" ) func TestRcIs(t *testing.T) { cases := []struct { descr string rc int64 mask uint64 expectIs bool }{{ descr: "exact match", rc: -4611686018406153244, mask: linstor.FailNotEnoughNodes, expectIs: true, }, { descr: "wrong value", rc: -4611686018406153244, mask: linstor.FailInvldNodeName, expectIs: false, }} for _, c := range cases { r := ApiCallRc{RetCode: c.rc} is := r.Is(c.mask) if is && !c.expectIs { t.Errorf("Case \"%s\": mask unexpectedly matches", c.descr) t.Errorf("RetCode: %064b", uint64(c.rc)) t.Errorf("Mask: %064b", c.mask) t.Errorf("And: %064b", uint64(c.rc)&c.mask) } if !is && c.expectIs { t.Errorf("Case \"%s\": mask unexpectedly does not match", c.descr) t.Errorf("RetCode: %064b", uint64(c.rc)) t.Errorf("Mask: %064b", c.mask) t.Errorf("And: %064b", uint64(c.rc)&c.mask) } } } golang-github-linbit-golinstor-0.55.0/client/backup.go000066400000000000000000000226761501671500700227160ustar00rootroot00000000000000package client import ( "context" "errors" "fmt" "net/http" "github.com/google/go-querystring/query" "github.com/LINBIT/golinstor/devicelayerkind" ) type Backup struct { Id string `json:"id"` StartTime string `json:"start_time,omitempty"` StartTimestamp *TimeStampMs `json:"start_timestamp,omitempty"` FinishedTime string `json:"finished_time,omitempty"` FinishedTimestamp *TimeStampMs `json:"finished_timestamp,omitempty"` OriginRsc string `json:"origin_rsc"` OriginSnap string `json:"origin_snap"` OriginNode string `json:"origin_node,omitempty"` FailMessages string `json:"fail_messages,omitempty"` Vlms []BackupVolumes `json:"vlms"` Success bool `json:"success,omitempty"` Shipping bool `json:"shipping,omitempty"` Restorable bool `json:"restorable,omitempty"` S3 BackupS3 `json:"s3,omitempty"` BasedOnId string `json:"based_on_id,omitempty"` } type BackupInfo struct { Rsc string `json:"rsc"` Snap string `json:"snap"` Full string `json:"full"` Latest string `json:"latest"` Count int32 `json:"count,omitempty"` DlSizeKib int64 `json:"dl_size_kib"` AllocSizeKib int64 `json:"alloc_size_kib"` Storpools []BackupInfoStorPool `json:"storpools"` } type BackupInfoRequest struct { SrcRscName string `json:"src_rsc_name,omitempty"` SrcSnapName string `json:"src_snap_name,omitempty"` LastBackup string `json:"last_backup,omitempty"` StorPoolMap map[string]string `json:"stor_pool_map,omitempty"` NodeName string `json:"node_name,omitempty"` } type BackupInfoStorPool struct { Name string `json:"name"` ProviderKind ProviderKind `json:"provider_kind,omitempty"` TargetName string `json:"target_name,omitempty"` RemainingSpaceKib int64 `json:"remaining_space_kib,omitempty"` Vlms []BackupInfoVolume `json:"vlms"` } type BackupInfoVolume struct { Name string `json:"name,omitempty"` LayerType devicelayerkind.DeviceLayerKind `json:"layer_type"` DlSizeKib int64 `json:"dl_size_kib,omitempty"` AllocSizeKib int64 `json:"alloc_size_kib"` UsableSizeKib int64 `json:"usable_size_kib,omitempty"` } type BackupList struct { // Linstor is a map of all entries found that could be parsed as LINSTOR backups. Linstor map[string]Backup `json:"linstor,omitempty"` // Other are files that could not be parsed as LINSTOR backups. Other BackupOther `json:"other,omitempty"` } type BackupOther struct { Files *[]string `json:"files,omitempty"` } type BackupRestoreRequest struct { SrcRscName string `json:"src_rsc_name,omitempty"` SrcSnapName string `json:"src_snap_name,omitempty"` LastBackup string `json:"last_backup,omitempty"` StorPoolMap map[string]string `json:"stor_pool_map,omitempty"` TargetRscName string `json:"target_rsc_name"` Passphrase string `json:"passphrase,omitempty"` NodeName string `json:"node_name"` DownloadOnly bool `json:"download_only,omitempty"` } type BackupS3 struct { MetaName string `json:"meta_name,omitempty"` } type BackupAbortRequest struct { RscName string `json:"rsc_name"` Restore *bool `json:"restore,omitempty"` Create *bool `json:"create,omitempty"` } type BackupCreate struct { RscName string `json:"rsc_name"` SnapName string `json:"snap_name,omitempty"` NodeName string `json:"node_name,omitempty"` Incremental bool `json:"incremental,omitempty"` } type BackupShipRequest struct { SrcNodeName string `json:"src_node_name,omitempty"` SrcRscName string `json:"src_rsc_name"` DstRscName string `json:"dst_rsc_name"` DstNodeName string `json:"dst_node_name,omitempty"` DstNetIfName string `json:"dst_net_if_name,omitempty"` DstStorPool string `json:"dst_stor_pool,omitempty"` StorPoolRename map[string]string `json:"stor_pool_rename,omitempty"` DownloadOnly *bool `json:"download_only,omitempty"` } type BackupVolumes struct { VlmNr int64 `json:"vlm_nr"` FinishedTime *string `json:"finished_time,omitempty"` FinishedTimestamp *TimeStampMs `json:"finished_timestamp,omitempty"` S3 *BackupVolumesS3 `json:"s3,omitempty"` } type BackupVolumesS3 struct { Key *string `json:"key,omitempty"` } type BackupDeleteOpts struct { ID string `url:"id,omitempty"` IDPrefix string `url:"id_prefix,omitempty"` Cascading bool `url:"cascading,omitempty"` Timestamp *TimeStampMs `url:"timestamp,omitempty"` ResourceName string `url:"resource_name,omitempty"` NodeName string `url:"node_name,omitempty"` AllLocalCluster bool `url:"all_local_cluster,omitempty"` All bool `url:"all,omitempty"` S3Key string `url:"s3key,omitempty"` S3KeyForce string `url:"s3key_force,omitempty"` DryRun bool `url:"dryrun,omitempty"` } type BackupProvider interface { // GetAll fetches information on all backups stored at the given remote. Optionally limited to the given // resource names. GetAll(ctx context.Context, remoteName string, rscName string, snapName string) (*BackupList, error) // DeleteAll backups that fit the given criteria. DeleteAll(ctx context.Context, remoteName string, filter BackupDeleteOpts) error // Create a new backup operation. Create(ctx context.Context, remoteName string, request BackupCreate) (string, error) // Info retrieves information about a specific backup instance. Info(ctx context.Context, remoteName string, request BackupInfoRequest) (*BackupInfo, error) // Abort all running backup operations of a resource. Abort(ctx context.Context, remoteName string, request BackupAbortRequest) error // Ship ships a backup from one LINSTOR cluster to another. Ship(ctx context.Context, remoteName string, request BackupShipRequest) (string, error) // Restore starts to restore a resource from a backup. Restore(ctx context.Context, remoteName string, request BackupRestoreRequest) error } var _ BackupProvider = &BackupService{} type BackupService struct { client *Client } func (b *BackupService) GetAll(ctx context.Context, remoteName string, rscName string, snapName string) (*BackupList, error) { vals, err := query.Values(struct { ResourceName string `url:"rsc_name,omitempty"` SnapshotName string `url:"snap_name,omitempty"` }{ResourceName: rscName, SnapshotName: snapName}) if err != nil { return nil, fmt.Errorf("failed to encode resource names: %w", err) } var list BackupList _, err = b.client.doGET(ctx, "/v1/remotes/"+remoteName+"/backups?"+vals.Encode(), &list) if err != nil { return nil, err } return &list, err } func (b *BackupService) DeleteAll(ctx context.Context, remoteName string, filter BackupDeleteOpts) error { vals, err := query.Values(filter) if err != nil { return fmt.Errorf("failed to encode filter options: %w", err) } _, err = b.client.doDELETE(ctx, "/v1/remotes/"+remoteName+"/backups?"+vals.Encode(), nil) return err } func (b *BackupService) Create(ctx context.Context, remoteName string, request BackupCreate) (string, error) { req, err := b.client.newRequest(http.MethodPost, "/v1/remotes/"+remoteName+"/backups", request) if err != nil { return "", err } var resp []ApiCallRc _, err = b.client.doJSON(ctx, req, &resp) if err != nil { return "", err } for _, rc := range resp { if s, ok := rc.ObjRefs["Snapshot"]; ok { return s, nil } } return "", errors.New("missing snapshot reference") } func (b *BackupService) Info(ctx context.Context, remoteName string, request BackupInfoRequest) (*BackupInfo, error) { req, err := b.client.newRequest(http.MethodPost, "/v1/remotes/"+remoteName+"/backups/info", request) if err != nil { return nil, err } var resp BackupInfo _, err = b.client.doJSON(ctx, req, &resp) if err != nil { return nil, err } return &resp, nil } func (b *BackupService) Abort(ctx context.Context, remoteName string, request BackupAbortRequest) error { _, err := b.client.doPOST(ctx, "/v1/remotes/"+remoteName+"/backups/abort", request) return err } func (b *BackupService) Ship(ctx context.Context, remoteName string, request BackupShipRequest) (string, error) { req, err := b.client.newRequest(http.MethodPost, "/v1/remotes/"+remoteName+"/backups/ship", request) if err != nil { return "", err } var resp []ApiCallRc _, err = b.client.doJSON(ctx, req, &resp) if err != nil { return "", err } for _, rc := range resp { // LINSTOR will report the name of the created (local) snapshot in one of the messages. // There may be multiple such references, but the name of the snapshot stays the same. if s, ok := rc.ObjRefs["Snapshot"]; ok { return s, nil } } return "", errors.New("missing snapshot reference") } func (b *BackupService) Restore(ctx context.Context, remoteName string, request BackupRestoreRequest) error { _, err := b.client.doPOST(ctx, "/v1/remotes/"+remoteName+"/backups/restore", request) return err } golang-github-linbit-golinstor-0.55.0/client/cache.go000066400000000000000000000012531501671500700225000ustar00rootroot00000000000000package client type CacheResource struct { CacheVolumes []CacheVolume `json:"cache_volumes,omitempty"` } type CacheVolume struct { VolumeNumber int32 `json:"volume_number,omitempty"` // block device path DevicePath string `json:"device_path,omitempty"` // block device path used as cache device DevicePathCache string `json:"device_path_cache,omitempty"` // block device path used as meta device DeviceMetaCache string `json:"device_meta_cache,omitempty"` AllocatedSizeKib int64 `json:"allocated_size_kib,omitempty"` UsableSizeKib int64 `json:"usable_size_kib,omitempty"` // String describing current volume state DiskState string `json:"disk_state,omitempty"` } golang-github-linbit-golinstor-0.55.0/client/client.go000066400000000000000000000477761501671500700227370ustar00rootroot00000000000000// A REST client to interact with LINSTOR's REST API // Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client import ( "bytes" "context" "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "io" "log" "net" "net/http" "net/url" "os" "strings" "sync" "time" "github.com/donovanhide/eventsource" "golang.org/x/time/rate" "moul.io/http2curl/v2" ) // Client is a struct representing a LINSTOR REST client. type Client struct { httpClient *http.Client basicAuth *BasicAuthCfg bearerToken string userAgent string controllersMu sync.Mutex controllers []*url.URL lim *rate.Limiter log interface{} // must be either Logger or LeveledLogger Nodes NodeProvider ResourceDefinitions ResourceDefinitionProvider Resources ResourceProvider ResourceGroups ResourceGroupProvider StoragePoolDefinitions StoragePoolDefinitionProvider Encryption EncryptionProvider Controller ControllerProvider Events EventProvider Vendor VendorProvider Remote RemoteProvider Backup BackupProvider KeyValueStore KeyValueStoreProvider Connections ConnectionProvider } // Logger represents a standard logger interface type Logger interface { Printf(string, ...interface{}) } // LeveledLogger interface implements the basic methods that a logger library needs type LeveledLogger interface { Errorf(string, ...interface{}) Infof(string, ...interface{}) Debugf(string, ...interface{}) Warnf(string, ...interface{}) } type BasicAuthCfg struct { Username, Password string } // const errors as in https://dave.cheney.net/2016/04/07/constant-errors type clientError string func (e clientError) Error() string { return string(e) } const ( // NotFoundError is the error type returned in case of a 404 error. This is required to test for this kind of error. NotFoundError = clientError("404 Not Found") // Name of the environment variable that stores the certificate used for TLS client authentication UserCertEnv = "LS_USER_CERTIFICATE" // Name of the environment variable that stores the key used for TLS client authentication UserKeyEnv = "LS_USER_KEY" // Name of the environment variable that stores the certificate authority for the LINSTOR HTTPS API RootCAEnv = "LS_ROOT_CA" // Name of the environment variable that holds the URL(s) of LINSTOR controllers ControllerUrlEnv = "LS_CONTROLLERS" // Name of the environment variable that holds the username for authentication UsernameEnv = "LS_USERNAME" // Name of the environment variable that holds the password for authentication PasswordEnv = "LS_PASSWORD" // Name of the environment variable that points to the file containing the token for authentication BearerTokenFileEnv = "LS_BEARER_TOKEN_FILE" ) // For example: // u, _ := url.Parse("http://somehost:3370") // c, _ := linstor.NewClient(linstor.BaseURL(u)) // Option configures a LINSTOR Client type Option func(*Client) error // BaseURL is a client's option to set the baseURL of the REST client. // // If multiple URLs are provided, each is tried in turn. func BaseURL(urls ...*url.URL) Option { return func(c *Client) error { c.controllers = urls return nil } } // BasicAuth is a client's option to set username and password for the REST client. func BasicAuth(basicauth *BasicAuthCfg) Option { return func(c *Client) error { c.basicAuth = basicauth return nil } } // HTTPClient is a client's option to set a specific http.Client. func HTTPClient(httpClient *http.Client) Option { return func(c *Client) error { c.httpClient = httpClient return nil } } // Log is a client's option to set a Logger func Log(logger interface{}) Option { return func(c *Client) error { switch logger.(type) { case Logger, LeveledLogger, nil: c.log = logger default: return errors.New("Invalid logger type, expected Logger or LeveledLogger") } return nil } } // Limiter to use when making queries. // Mutually exclusive with Limit, last applied option wins. func Limiter(limiter *rate.Limiter) Option { return func(c *Client) error { if limiter.Burst() == 0 && limiter.Limit() != rate.Inf { return fmt.Errorf("invalid rate limit, burst must not be zero for non-unlimited rates") } c.lim = limiter return nil } } // Limit is the client's option to set number of requests per second and // max number of bursts. // Mutually exclusive with Limiter, last applied option wins. // Deprecated: Use Limiter instead. func Limit(r rate.Limit, b int) Option { return Limiter(rate.NewLimiter(r, b)) } func Controllers(controllers []string) Option { return func(c *Client) error { var err error c.controllers, err = parseURLs(controllers) return err } } // BearerToken configures authentication via the given token send in the Authorization Header. // If set, this will override any authentication happening via Basic Authentication. func BearerToken(token string) Option { return func(c *Client) error { c.bearerToken = token return nil } } // UserAgent sets the User-Agent header for every request to the given string. func UserAgent(ua string) Option { return func(c *Client) error { c.userAgent = ua return nil } } // buildHttpClient constructs an HTTP client which will be used to connect to // the LINSTOR controller. It recongnizes some environment variables which can // be used to configure the HTTP client at runtime. If an invalid key or // certificate is passed, an error is returned. // If none or not all of the environment variables are passed, the default // client is used as a fallback. func buildHttpClient() (*http.Client, error) { certPEM, cert := os.LookupEnv(UserCertEnv) keyPEM, key := os.LookupEnv(UserKeyEnv) caPEM, ca := os.LookupEnv(RootCAEnv) if key != cert { return nil, fmt.Errorf("'%s', '%s': specify both or none", UserKeyEnv, UserCertEnv) } if !cert && !key && !ca { // Non of the special variables was set -> if TLS is used, default configuration can be used return http.DefaultClient, nil } tlsConfig := &tls.Config{} if ca { caPool := x509.NewCertPool() ok := caPool.AppendCertsFromPEM([]byte(caPEM)) if !ok { return nil, fmt.Errorf("failed to get a valid certificate from '%s'", RootCAEnv) } tlsConfig.RootCAs = caPool } if key && cert { keyPair, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM)) if err != nil { return nil, fmt.Errorf("failed to load keys: %w", err) } tlsConfig.Certificates = append(tlsConfig.Certificates, keyPair) } return &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsConfig, }, }, nil } // Return the default scheme to access linstor // If one of the HTTPS environment variables is set, will return "https". // If not, will return "http" func defaultScheme() string { _, ca := os.LookupEnv(RootCAEnv) _, cert := os.LookupEnv(UserCertEnv) _, key := os.LookupEnv(UserKeyEnv) if ca || cert || key { return "https" } return "http" } const defaultHost = "localhost" // Return the default port to access linstor. // Defaults are: // "https": 3371 // "http": 3370 func defaultPort(scheme string) string { if scheme == "https" { return "3371" } return "3370" } func parseBaseURL(urlString string) (*url.URL, error) { // Check scheme urlSplit := strings.Split(urlString, "://") if len(urlSplit) == 1 { if urlSplit[0] == "" { urlSplit[0] = defaultHost } urlSplit = []string{defaultScheme(), urlSplit[0]} } if len(urlSplit) != 2 { return nil, fmt.Errorf("URL with multiple scheme separators. parts: %v", urlSplit) } scheme, endpoint := urlSplit[0], urlSplit[1] switch scheme { case "linstor": scheme = defaultScheme() case "linstor+ssl": scheme = "https" } // Check port endpointSplit := strings.Split(endpoint, ":") if len(endpointSplit) == 1 { endpointSplit = []string{endpointSplit[0], defaultPort(scheme)} } if len(endpointSplit) != 2 { return nil, fmt.Errorf("URL with multiple port separators. parts: %v", endpointSplit) } host, port := endpointSplit[0], endpointSplit[1] return url.Parse(fmt.Sprintf("%s://%s:%s", scheme, host, port)) } func parseURLs(urls []string) ([]*url.URL, error) { var result []*url.URL for _, controller := range urls { url, err := parseBaseURL(controller) if err != nil { return nil, err } result = append(result, url) } return result, nil } // NewClient takes an arbitrary number of options and returns a Client or an error. // It recognizes several environment variables which can be used to configure // the client at runtime: // // - LS_CONTROLLERS: a comma-separated list of LINSTOR controllers to connect to. // // - LS_USERNAME, LS_PASSWORD: can be used to authenticate against the LINSTOR // controller using HTTP basic authentication. // // - LS_USER_CERTIFICATE, LS_USER_KEY, LS_ROOT_CA: can be used to enable TLS on // the HTTP client, enabling encrypted communication with the LINSTOR controller. // // - LS_BEARER_TOKEN_FILE: can be set to a file containing the bearer token used // for authentication. // // Options passed to NewClient take precedence over options passed in via // environment variables. func NewClient(options ...Option) (*Client, error) { httpClient, err := buildHttpClient() if err != nil { return nil, fmt.Errorf("failed to build http client: %w", err) } c := &Client{ httpClient: httpClient, basicAuth: &BasicAuthCfg{ Username: os.Getenv(UsernameEnv), Password: os.Getenv(PasswordEnv), }, lim: rate.NewLimiter(rate.Inf, 0), log: log.New(os.Stderr, "", 0), } c.Nodes = &NodeService{client: c} c.ResourceDefinitions = &ResourceDefinitionService{client: c} c.Resources = &ResourceService{client: c} c.Encryption = &EncryptionService{client: c} c.ResourceGroups = &ResourceGroupService{client: c} c.StoragePoolDefinitions = &StoragePoolDefinitionService{client: c} c.Controller = &ControllerService{client: c} c.Events = &EventService{client: c} c.Vendor = &VendorService{client: c} c.Remote = &RemoteService{client: c} c.Backup = &BackupService{client: c} c.KeyValueStore = &KeyValueStoreService{client: c} c.Connections = &ConnectionService{client: c} if path, ok := os.LookupEnv(BearerTokenFileEnv); ok { token, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read token from file: %w", err) } c.bearerToken = string(token) } for _, opt := range options { if err := opt(c); err != nil { return nil, err } } if len(c.controllers) == 0 { // if not already set by option, get from environment... controllersStr := os.Getenv(ControllerUrlEnv) if controllersStr == "" { // ... or fall back to defaults controllersStr = fmt.Sprintf("%v://%v:%v", defaultScheme(), defaultHost, defaultPort(defaultScheme())) } c.controllers, err = parseURLs(strings.Split(controllersStr, ",")) if err != nil { return nil, fmt.Errorf("failed to parse controller URLs: %w", err) } } return c, nil } // BaseURL returns the current controllers URL. func (c *Client) BaseURL() *url.URL { c.controllersMu.Lock() defer c.controllersMu.Unlock() return c.controllers[0] } func (c *Client) newRequest(method, path string, body interface{}) (*http.Request, error) { rel, err := url.Parse(path) if err != nil { return nil, err } u := c.BaseURL().ResolveReference(rel) var buf io.ReadWriter if body != nil { // Use json.Marshal instead of encoding to the buffer directly; json.Encoder.Encode() adds a newline // at the end, which is annoying for logging. jsonBuf, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("failed to marshal JSON body: %w", err) } buf = bytes.NewBuffer(jsonBuf) } req, err := http.NewRequest(method, u.String(), buf) if err != nil { return nil, err } if body != nil { req.Header.Set("Content-Type", "application/json") } if c.userAgent != "" { req.Header.Set("User-Agent", c.userAgent) } username := c.basicAuth.Username if username != "" { req.SetBasicAuth(username, c.basicAuth.Password) } if c.bearerToken != "" { req.Header.Set("Authorization", "Bearer "+c.bearerToken) } return req, nil } func (c *Client) curlify(req *http.Request) (string, error) { cc, err := http2curl.GetCurlCommand(req) if err != nil { return "", err } return cc.String(), nil } // findRespondingController scans the list of controllers for a working LINSTOR controller. // // After this returns successfully, the first controllers entry will point to the working controller. // // If no controller could be reached, an error combining all attempts is returned. func (c *Client) findRespondingController() error { if len(c.controllers) <= 1 { return nil } c.controllersMu.Lock() defer c.controllersMu.Unlock() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) errs := make([]error, len(c.controllers)) var wg sync.WaitGroup wg.Add(len(c.controllers)) for i := range c.controllers { i := i go func() { defer wg.Done() conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", c.controllers[i].Host) if err != nil { errs[i] = fmt.Errorf("failed to dial '%s': %w", c.controllers[i].Host, err) return } _ = conn.Close() // Success -> we can cancel the other goroutines cancel() }() } wg.Wait() cancel() // just to make linters happy for i := range errs { if errs[i] == nil { tmp := c.controllers[i] c.controllers[0] = c.controllers[i] c.controllers[i] = tmp return nil } } return fmt.Errorf("could not connect to any controller: %w", errors.Join(errs...)) } func (c *Client) logCurlify(req *http.Request) { var msg string if curl, err := c.curlify(req); err != nil { msg = err.Error() } else { msg = curl } switch l := c.log.(type) { case LeveledLogger: l.Debugf("%s", msg) case Logger: l.Printf("[DEBUG] %s", msg) } } func (c *Client) retry(origErr error, req *http.Request) (*http.Response, error) { // only retry on network errors and if we even have another controller to choose from var netError net.Error if !errors.As(origErr, &netError) || len(c.controllers) <= 1 { return nil, origErr } e := c.findRespondingController() // if findRespondingController failed, or we just got the same base URL, don't bother retrying if e != nil { return nil, origErr } req.URL.Host = c.BaseURL().Host req.URL.Scheme = c.BaseURL().Scheme return c.httpClient.Do(req) } // do sends a prepared http.Request and returns the http.Response. If an HTTP error occurs, the parsed error is // returned. Otherwise, the response is returned as-is. The caller is responsible for closing the response body in // the non-error case. func (c *Client) do(ctx context.Context, req *http.Request) (*http.Response, error) { if err := c.lim.Wait(ctx); err != nil { return nil, err } req = req.WithContext(ctx) c.logCurlify(req) resp, err := c.httpClient.Do(req) if err != nil { select { case <-ctx.Done(): return nil, ctx.Err() default: } // if this was a connectivity issue, attempt a retry resp, err = c.retry(err, req) if err != nil { return nil, err } } if resp.StatusCode < 200 || resp.StatusCode >= 400 { // If we get an error, we handle the body ourselves, so we also have to close it. defer resp.Body.Close() msg := fmt.Sprintf("Status code not within 200 to 400, but %d (%s)\n", resp.StatusCode, http.StatusText(resp.StatusCode)) switch l := c.log.(type) { case LeveledLogger: l.Debugf("%s", msg) case Logger: l.Printf("[DEBUG] %s", msg) } if resp.StatusCode == 404 { return nil, NotFoundError } var rets ApiCallError if err = json.NewDecoder(resp.Body).Decode(&rets); err != nil { return nil, err } return nil, rets } return resp, err } // doJSON sends a prepared http.Request and returns the http.Response. If out is provided, the response body is // JSON-decoded into out. func (c *Client) doJSON(ctx context.Context, req *http.Request, out any) (*http.Response, error) { req.Header.Set("Accept", "application/json") resp, err := c.do(ctx, req) if err != nil { return nil, err } defer resp.Body.Close() if out != nil { err = json.NewDecoder(resp.Body).Decode(out) } return resp, err } // Higer Leve Abstractions func (c *Client) doGET(ctx context.Context, url string, ret interface{}, opts ...*ListOpts) (*http.Response, error) { u, err := addOptions(url, genOptions(opts...)) if err != nil { return nil, err } req, err := c.newRequest("GET", u, nil) if err != nil { return nil, err } return c.doJSON(ctx, req, ret) } func (c *Client) doEvent(ctx context.Context, url, lastEventId string) (*eventsource.Stream, error) { req, err := c.newRequest("GET", url, nil) if err != nil { return nil, err } req.Header.Set("Accept", "text/event-stream") req = req.WithContext(ctx) stream, err := eventsource.SubscribeWith(lastEventId, c.httpClient, req) if err != nil { return nil, err } return stream, nil } func (c *Client) doPOST(ctx context.Context, url string, body interface{}) (*http.Response, error) { req, err := c.newRequest("POST", url, body) if err != nil { return nil, err } return c.doJSON(ctx, req, nil) } func (c *Client) doPUT(ctx context.Context, url string, body interface{}) (*http.Response, error) { req, err := c.newRequest("PUT", url, body) if err != nil { return nil, err } return c.doJSON(ctx, req, nil) } func (c *Client) doPATCH(ctx context.Context, url string, body interface{}) (*http.Response, error) { req, err := c.newRequest("PATCH", url, body) if err != nil { return nil, err } return c.doJSON(ctx, req, nil) } func (c *Client) doDELETE(ctx context.Context, url string, body interface{}) (*http.Response, error) { req, err := c.newRequest("DELETE", url, body) if err != nil { return nil, err } return c.doJSON(ctx, req, nil) } func (c *Client) doOPTIONS(ctx context.Context, url string, ret interface{}, body interface{}) (*http.Response, error) { req, err := c.newRequest("OPTIONS", url, body) if err != nil { return nil, err } return c.doJSON(ctx, req, ret) } // ApiCallRc represents the struct returned by LINSTOR, when accessing its REST API. type ApiCallRc struct { // A masked error number RetCode int64 `json:"ret_code"` Message string `json:"message"` // Cause of the error Cause string `json:"cause,omitempty"` // Details to the error message Details string `json:"details,omitempty"` // Possible correction options Correction string `json:"correction,omitempty"` // List of error report ids related to this api call return code. ErrorReportIds []string `json:"error_report_ids,omitempty"` // Map of objection that have been involved by the operation. ObjRefs map[string]string `json:"obj_refs,omitempty"` } func (rc *ApiCallRc) String() string { s := fmt.Sprintf("Message: '%s'", rc.Message) if rc.Cause != "" { s += fmt.Sprintf("; Cause: '%s'", rc.Cause) } if rc.Details != "" { s += fmt.Sprintf("; Details: '%s'", rc.Details) } if rc.Correction != "" { s += fmt.Sprintf("; Correction: '%s'", rc.Correction) } if len(rc.ErrorReportIds) > 0 { s += fmt.Sprintf("; Reports: '[%s]'", strings.Join(rc.ErrorReportIds, ",")) } return s } // DeleteProps is a slice of properties to delete. type DeleteProps []string // OverrideProps is a map of properties to modify (key/value pairs) type OverrideProps map[string]string // Namespaces to delete type DeleteNamespaces []string // GenericPropsModify is a struct combining DeleteProps and OverrideProps type GenericPropsModify struct { DeleteProps DeleteProps `json:"delete_props,omitempty"` OverrideProps OverrideProps `json:"override_props,omitempty"` DeleteNamespaces DeleteNamespaces `json:"delete_namespaces,omitempty"` } golang-github-linbit-golinstor-0.55.0/client/client_test.go000066400000000000000000000150161501671500700237540ustar00rootroot00000000000000package client import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "net/url" "os" "testing" "github.com/stretchr/testify/assert" ) const TestCaCert = `-----BEGIN CERTIFICATE----- MIIB1DCCAXqgAwIBAgIUZJyvXb6pJ9tHxKOlVkjTaqjpAAIwCgYIKoZIzj0EAwIw SDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp c2NvMRQwEgYDVQQDEwtleGFtcGxlLm5ldDAeFw0yMDA1MDgxMjUyMDBaFw0yNTA1 MDcxMjUyMDBaMEgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMN U2FuIEZyYW5jaXNjbzEUMBIGA1UEAxMLZXhhbXBsZS5uZXQwWTATBgcqhkjOPQIB BggqhkjOPQMBBwNCAAT7uWd8BeFIcw64pRJheVh6tKrsqSLF4z9LAQKEaH5pg34+ 06T2Ed7hKUSca3R8zEuP9EZcHNpYXKoeuF1QjZt5o0IwQDAOBgNVHQ8BAf8EBAMC AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyK850E3ZE9Jb3JPDq3BtttXd wE0wCgYIKoZIzj0EAwIDSAAwRQIge+3tsEmO/WbjxmQoA+kDoSpOQDnkDckqvEXD 1H939HUCIQDHp0oAvI1sMM0ksAl7D0Bpxjtha2kpzbqsAf4yDy9rWw== -----END CERTIFICATE----- ` func TestNewClient_ViaEnv(t *testing.T) { testcases := []struct { name string env map[string]string expectedUrl string hasError bool }{ { name: "default", expectedUrl: "http://localhost:3370", }, { name: "just-domain-name", env: map[string]string{"LS_CONTROLLERS": "just.domain"}, expectedUrl: "http://just.domain:3370", }, { name: "linstor-protocol", env: map[string]string{"LS_CONTROLLERS": "linstor://just.domain"}, expectedUrl: "http://just.domain:3370", }, { name: "linstor-ssl-protocol", env: map[string]string{"LS_CONTROLLERS": "linstor+ssl://just.domain"}, expectedUrl: "https://just.domain:3371", }, { name: "just-domain-with-port", env: map[string]string{"LS_CONTROLLERS": "just.domain:4000"}, expectedUrl: "http://just.domain:4000", }, { name: "domain-with-protocol", env: map[string]string{"LS_CONTROLLERS": "http://just.domain"}, expectedUrl: "http://just.domain:3370", }, { name: "just-domain-with-https-protocol", env: map[string]string{"LS_CONTROLLERS": "https://just.domain"}, expectedUrl: "https://just.domain:3371", }, { name: "just-domain-with-client-secrets", env: map[string]string{"LS_CONTROLLERS": "just.domain", "LS_ROOT_CA": TestCaCert}, expectedUrl: "https://just.domain:3371", }, { name: "just-domain-with-client-secrets-and-port", env: map[string]string{"LS_CONTROLLERS": "just.domain:4000", "LS_ROOT_CA": TestCaCert}, expectedUrl: "https://just.domain:4000", }, { name: "parse-error-multi-scheme", env: map[string]string{"LS_CONTROLLERS": "https://http://just.domain:4000"}, hasError: true, }, { name: "parse-error-multi-port", env: map[string]string{"LS_CONTROLLERS": "https://just.domain:4000:5000"}, hasError: true, }, { name: "parse-error-inconsistent-env", env: map[string]string{"LS_CONTROLLERS": "https://just.domain:4000", "LS_USER_CERTIFICATE": "stuff"}, hasError: true, }, { name: "parse-error-inconsistent-env-other", env: map[string]string{"LS_CONTROLLERS": "https://just.domain:4000", "LS_USER_KEY": "stuff"}, hasError: true, }, } for _, item := range testcases { test := item t.Run(test.name, func(t *testing.T) { os.Clearenv() defer os.Clearenv() for k, v := range test.env { _ = os.Setenv(k, v) } actual, err := NewClient() if actual == nil { if !test.hasError { t.Errorf("expected no error, got error: %v", err) } return } if test.expectedUrl != actual.BaseURL().String() { t.Errorf("expected url: %v, got url: %v", test.expectedUrl, actual.BaseURL().String()) } }) } } func fakeVersionHandler(version string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = fmt.Fprintf(w, `{"version":"%s"}`, version) }) } func TestBaseURLFailover(t *testing.T) { first := httptest.NewServer(fakeVersionHandler("first")) second := httptest.NewServer(fakeVersionHandler("second")) third := httptest.NewTLSServer(fakeVersionHandler("third")) defer first.Close() defer second.Close() defer third.Close() firstUrl := url.URL{ Scheme: "http", Host: first.Listener.Addr().String(), } secondUrl := url.URL{ Scheme: "http", Host: second.Listener.Addr().String(), } thirdUrl := url.URL{ Scheme: "https", Host: third.Listener.Addr().String(), } client, err := NewClient( BaseURL(&firstUrl, &secondUrl, &thirdUrl), HTTPClient(third.Client()), ) assert.NoError(t, err) // Take the first URL as is. version, err := client.Controller.GetVersion(context.Background()) assert.NoError(t, err) assert.Equal(t, "first", version.Version) assert.Equal(t, &firstUrl, client.BaseURL()) // Stop the first server -> should fail-over to second or third. first.Close() version, err = client.Controller.GetVersion(context.Background()) assert.NoError(t, err) switch version.Version { case "second": second.Close() assert.Equal(t, &secondUrl, client.BaseURL()) version, err = client.Controller.GetVersion(context.Background()) assert.NoError(t, err) assert.Equal(t, "third", version.Version) assert.Equal(t, &thirdUrl, client.BaseURL()) third.Close() case "third": third.Close() assert.Equal(t, &thirdUrl, client.BaseURL()) version, err = client.Controller.GetVersion(context.Background()) assert.NoError(t, err) assert.Equal(t, "second", version.Version) assert.Equal(t, &secondUrl, client.BaseURL()) second.Close() default: t.Fatalf("unexpected version: %s", version.Version) } _, err = client.Controller.GetVersion(context.Background()) assert.Error(t, err) } func TestBearerTokenOpt(t *testing.T) { const Token = "AbCdEfg1234567890" var FakeVersion = ControllerVersion{ BuildTime: "#buildtime", GitHash: "#git", RestApiVersion: "#rest", Version: "#v1", } fakeHttpServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { if request.Header.Get("Authorization") != "Bearer "+Token { writer.WriteHeader(http.StatusUnauthorized) } else { writer.WriteHeader(http.StatusOK) enc := json.NewEncoder(writer) _ = enc.Encode(&FakeVersion) } })) defer fakeHttpServer.Close() u, err := url.Parse(fakeHttpServer.URL) assert.NoError(t, err) cl, err := NewClient(BearerToken(Token), HTTPClient(fakeHttpServer.Client()), BaseURL(u)) assert.NoError(t, err) actualVersion, err := cl.Controller.GetVersion(context.Background()) assert.NoError(t, err) assert.Equal(t, FakeVersion, actualVersion) } golang-github-linbit-golinstor-0.55.0/client/connection.go000066400000000000000000000066461501671500700236070ustar00rootroot00000000000000package client import ( "context" "fmt" "github.com/google/go-querystring/query" ) type Connection struct { NodeA string `json:"node_a,omitempty"` NodeB string `json:"node_b,omitempty"` Props map[string]string `json:"props,omitempty"` Flags []string `json:"flags,omitempty"` Port *int32 `json:"port,omitempty"` } // ConnectionProvider acts as an abstraction for a ConnectionService. It can be swapped out // for another ConnectionService implementation, for example for testing. type ConnectionProvider interface { // GetNodeConnections lists all node connections, optionally limites to nodes A and B, if not empty. GetNodeConnections(ctx context.Context, nodeA, nodeB string) ([]Connection, error) // GetResourceConnections returns all connections of the given resource. GetResourceConnections(ctx context.Context, resource string) ([]Connection, error) // GetResourceConnection returns the connection between node A and B for the given resource. GetResourceConnection(ctx context.Context, resource, nodeA, nodeB string) (*Connection, error) // SetNodeConnection sets or updates the node connection between node A and B. SetNodeConnection(ctx context.Context, nodeA, nodeB string, props GenericPropsModify) error // SetResourceConnection sets or updates the connection between node A and B for a resource. SetResourceConnection(ctx context.Context, resource, nodeA, nodeB string, props GenericPropsModify) error } // ConnectionService is the service that deals with connection related tasks. type ConnectionService struct { client *Client } func (c *ConnectionService) GetNodeConnections(ctx context.Context, nodeA, nodeB string) ([]Connection, error) { nodeA, nodeB = sortNodes(nodeA, nodeB) vals, err := query.Values(struct { NodeA string `url:"node_a,omitempty"` NodeB string `url:"node_b,omitempty"` }{NodeA: nodeA, NodeB: nodeB}) if err != nil { return nil, fmt.Errorf("failed to encode node names: %w", err) } var conns []Connection _, err = c.client.doGET(ctx, "/v1/node-connections?"+vals.Encode(), &conns) return conns, err } func (c *ConnectionService) GetResourceConnections(ctx context.Context, resource string) ([]Connection, error) { var conns []Connection _, err := c.client.doGET(ctx, "/v1/resource-definitions/"+resource+"/resource-connections", &conns) return conns, err } func (c *ConnectionService) GetResourceConnection(ctx context.Context, resource, nodeA, nodeB string) (*Connection, error) { nodeA, nodeB = sortNodes(nodeA, nodeB) var conn Connection _, err := c.client.doGET(ctx, "/v1/resource-definitions/"+resource+"/resource-connections/"+nodeA+"/"+nodeB, &conn) if err != nil { return nil, err } return &conn, err } func (c *ConnectionService) SetNodeConnection(ctx context.Context, nodeA, nodeB string, props GenericPropsModify) error { nodeA, nodeB = sortNodes(nodeA, nodeB) _, err := c.client.doPUT(ctx, "/v1/node-connections/"+nodeA+"/"+nodeB, &props) return err } func (c *ConnectionService) SetResourceConnection(ctx context.Context, resource, nodeA, nodeB string, props GenericPropsModify) error { nodeA, nodeB = sortNodes(nodeA, nodeB) _, err := c.client.doPUT(ctx, "/v1/resource-definitions/"+resource+"/resource-connections/"+nodeA+"/"+nodeB, &props) return err } // Sort node parameters: LINSTOR is (sometimes) order-dependent with parameters. func sortNodes(a, b string) (string, string) { if a < b { return a, b } else { return b, a } } golang-github-linbit-golinstor-0.55.0/client/controller.go000066400000000000000000000330621501671500700236230ustar00rootroot00000000000000// Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client import ( "context" "encoding/base64" "encoding/json" "fmt" "io" "net/url" "strconv" "time" ) // copy & paste from generated code // ControllerVersion represents version information of the LINSTOR controller type ControllerVersion struct { Version string `json:"version,omitempty"` GitHash string `json:"git_hash,omitempty"` BuildTime string `json:"build_time,omitempty"` RestApiVersion string `json:"rest_api_version,omitempty"` } // ErrorReport struct for ErrorReport type ErrorReport struct { NodeName string `json:"node_name,omitempty"` ErrorTime TimeStampMs `json:"error_time"` // Filename of the error report on the server. Format is: ```ErrorReport-{instanceid}-{nodeid}-{sequencenumber}.log``` Filename string `json:"filename,omitempty"` // Contains the full text of the error report file. Text string `json:"text,omitempty"` // Which module this error occurred. Module string `json:"module,omitempty"` // Linstor version this error report was created. Version string `json:"version,omitempty"` // Peer client that was involved. Peer string `json:"peer,omitempty"` // Exception that occurred Exception string `json:"exception,omitempty"` // Exception message ExceptionMessage string `json:"exception_message,omitempty"` // Origin file of the exception OriginFile string `json:"origin_file,omitempty"` // Origin method where the exception occurred OriginMethod string `json:"origin_method,omitempty"` // Origin line number OriginLine int32 `json:"origin_line,omitempty"` } type ErrorReportDelete struct { Since *TimeStampMs `json:"since,omitempty"` To *TimeStampMs `json:"to,omitempty"` // on which nodes to delete error-reports, if empty/null all nodes Nodes []string `json:"nodes,omitempty"` // delete all error reports with the given exception Exception string `json:"exception,omitempty"` // delete all error reports from the given version Version string `json:"version,omitempty"` // error report ids to delete Ids []string `json:"ids,omitempty"` } type PropsInfo struct { Info string `json:"info,omitempty"` PropType string `json:"prop_type,omitempty"` Value string `json:"value,omitempty"` Dflt string `json:"dflt,omitempty"` Unit string `json:"unit,omitempty"` } // ExternalFile is an external file which can be configured to be deployed by Linstor type ExternalFile struct { Path string Content []byte } // externalFileBase64 is a golinstor-internal type which represents an external // file as it is handled by the LINSTOR API. The API expects files to come in // base64 encoding, and also returns files in base64 encoding. To make golinstor // easier to use, we only present the ExternalFile type to our users an // transparently handle the base64 encoding/decoding. type externalFileBase64 struct { Path string `json:"path,omitempty"` ContentBase64 string `json:"content,omitempty"` } func (e *ExternalFile) UnmarshalJSON(text []byte) error { v := externalFileBase64{} err := json.Unmarshal(text, &v) if err != nil { return err } e.Path = v.Path e.Content, err = base64.StdEncoding.DecodeString(v.ContentBase64) if err != nil { return err } return nil } func (e *ExternalFile) MarshalJSON() ([]byte, error) { v := externalFileBase64{ Path: e.Path, ContentBase64: base64.StdEncoding.EncodeToString(e.Content), } return json.Marshal(v) } // custom code // ControllerProvider acts as an abstraction for a ControllerService. It can be // swapped out for another ControllerService implementation, for example for // testing. type ControllerProvider interface { // GetVersion queries version information for the controller. GetVersion(ctx context.Context, opts ...*ListOpts) (ControllerVersion, error) // GetConfig queries the configuration of a controller GetConfig(ctx context.Context, opts ...*ListOpts) (ControllerConfig, error) // Modify modifies the controller node and sets/deletes the given properties. Modify(ctx context.Context, props GenericPropsModify) error // GetProps gets all properties of a controller GetProps(ctx context.Context, opts ...*ListOpts) (ControllerProps, error) // DeleteProp deletes the given property/key from the controller object. DeleteProp(ctx context.Context, prop string) error // GetErrorReports returns all error reports. The Text field is not populated, // use GetErrorReport to get the text of an error report. GetErrorReports(ctx context.Context, opts ...*ListOpts) ([]ErrorReport, error) // DeleteErrorReports deletes error reports as specified by the ErrorReportDelete struct. DeleteErrorReports(ctx context.Context, del ErrorReportDelete) error // GetErrorReportsSince returns all error reports created after a certain point in time. GetErrorReportsSince(ctx context.Context, since time.Time, opts ...*ListOpts) ([]ErrorReport, error) // GetErrorReport returns a specific error report, including its text. GetErrorReport(ctx context.Context, id string, opts ...*ListOpts) (ErrorReport, error) // CreateSOSReport creates an SOS report in the log directory of the controller CreateSOSReport(ctx context.Context, opts ...*ListOpts) error // DownloadSOSReport creates and downloads an SOS report. The report is written to w as a tar.gz file. DownloadSOSReport(ctx context.Context, w io.WriteCloser, opts ...*ListOpts) error GetSatelliteConfig(ctx context.Context, node string) (SatelliteConfig, error) ModifySatelliteConfig(ctx context.Context, node string, cfg SatelliteConfig) error // GetPropsInfos gets meta information about the properties that can be // set on a controller. GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) // GetPropsInfosAll gets meta information about all properties that can // be set on a controller and all entities it contains (nodes, resource // definitions, ...). GetPropsInfosAll(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) // GetExternalFiles gets a list of previously registered external files. GetExternalFiles(ctx context.Context, opts ...*ListOpts) ([]ExternalFile, error) // GetExternalFile gets the requested external file including its content GetExternalFile(ctx context.Context, name string) (ExternalFile, error) // ModifyExternalFile registers or modifies a previously registered // external file ModifyExternalFile(ctx context.Context, name string, file ExternalFile) error // DeleteExternalFile deletes the given external file. This effectively // also deletes the file on all satellites DeleteExternalFile(ctx context.Context, name string) error } // ControllerService is the service that deals with the LINSTOR controller. type ControllerService struct { client *Client } // GetVersion queries version information for the controller. func (s *ControllerService) GetVersion(ctx context.Context, opts ...*ListOpts) (ControllerVersion, error) { var vers ControllerVersion _, err := s.client.doGET(ctx, "/v1/controller/version", &vers, opts...) return vers, err } // GetConfig queries the configuration of a controller func (s *ControllerService) GetConfig(ctx context.Context, opts ...*ListOpts) (ControllerConfig, error) { var cfg ControllerConfig _, err := s.client.doGET(ctx, "/v1/controller/config", &cfg, opts...) return cfg, err } // Modify modifies the controller node and sets/deletes the given properties. func (s *ControllerService) Modify(ctx context.Context, props GenericPropsModify) error { _, err := s.client.doPOST(ctx, "/v1/controller/properties", props) return err } type ControllerProps map[string]string // GetProps gets all properties of a controller func (s *ControllerService) GetProps(ctx context.Context, opts ...*ListOpts) (ControllerProps, error) { var props ControllerProps _, err := s.client.doGET(ctx, "/v1/controller/properties", &props, opts...) return props, err } // DeleteProp deletes the given property/key from the controller object. func (s *ControllerService) DeleteProp(ctx context.Context, prop string) error { _, err := s.client.doDELETE(ctx, "/v1/controller/properties/"+prop, nil) return err } // GetPropsInfos gets meta information about the properties that can be set on // a controller. func (s *ControllerService) GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := s.client.doGET(ctx, "/v1/controller/properties/info", &infos, opts...) return infos, err } // GetPropsInfosAll gets meta information about all properties that can be set // on a controller and all entities it contains (nodes, resource definitions, ...). func (s *ControllerService) GetPropsInfosAll(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := s.client.doGET(ctx, "/v1/controller/properties/info/all", &infos, opts...) return infos, err } // GetErrorReports returns all error reports. The Text field is not populated, // use GetErrorReport to get the text of an error report. func (s *ControllerService) GetErrorReports(ctx context.Context, opts ...*ListOpts) ([]ErrorReport, error) { var reports []ErrorReport _, err := s.client.doGET(ctx, "/v1/error-reports", &reports, opts...) return reports, err } // DeleteErrorReports deletes error reports as specified by the ErrorReportDelete struct. func (s *ControllerService) DeleteErrorReports(ctx context.Context, del ErrorReportDelete) error { // Yes, this is using PATCH. don't ask me why, its just implemented that way... _, err := s.client.doPATCH(ctx, "/v1/error-reports", del) return err } // unixMilli returns t formatted as milliseconds since Unix epoch func unixMilli(t time.Time) int64 { return t.UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)) } // GetErrorReportsSince returns all error reports created after a certain point in time. func (s *ControllerService) GetErrorReportsSince(ctx context.Context, since time.Time, opts ...*ListOpts) ([]ErrorReport, error) { var reports []ErrorReport v := url.Values{} v.Set("since", strconv.FormatInt(unixMilli(since), 10)) _, err := s.client.doGET(ctx, "/v1/error-reports/?"+v.Encode(), &reports, opts...) return reports, err } // GetErrorReport returns a specific error report, including its text. func (s *ControllerService) GetErrorReport(ctx context.Context, id string, opts ...*ListOpts) (ErrorReport, error) { var report []ErrorReport _, err := s.client.doGET(ctx, "/v1/error-reports/"+id, &report, opts...) return report[0], err } // CreateSOSReport creates an SOS report in the log directory of the controller func (s *ControllerService) CreateSOSReport(ctx context.Context, opts ...*ListOpts) error { _, err := s.client.doGET(ctx, "/v1/sos-report", nil, opts...) return err } // DownloadSOSReport creates and downloads an SOS report. The report is written to w as a tar.gz file. func (s *ControllerService) DownloadSOSReport(ctx context.Context, w io.WriteCloser, opts ...*ListOpts) error { reqUrl := "/v1/sos-report/download" u, err := addOptions(reqUrl, genOptions(opts...)) if err != nil { return err } req, err := s.client.newRequest("GET", u, nil) if err != nil { return err } req.Header.Set("Accept", "application/octet-stream") resp, err := s.client.do(ctx, req) if err != nil { return err } defer resp.Body.Close() _, err = io.Copy(w, resp.Body) return err } func (s *ControllerService) GetSatelliteConfig(ctx context.Context, node string) (SatelliteConfig, error) { var cfg SatelliteConfig _, err := s.client.doGET(ctx, "/v1/nodes/"+node+"/config", &cfg) return cfg, err } func (s *ControllerService) ModifySatelliteConfig(ctx context.Context, node string, cfg SatelliteConfig) error { _, err := s.client.doPUT(ctx, "/v1/nodes/"+node+"/config", &cfg) return err } // GetExternalFiles get a list of previously registered external files. // File contents are not included, unless ListOpts.Content is true. func (s *ControllerService) GetExternalFiles(ctx context.Context, opts ...*ListOpts) ([]ExternalFile, error) { var files []ExternalFile _, err := s.client.doGET(ctx, "/v1/files", &files, opts...) return files, err } // GetExternalFile gets the requested external file including its content func (s *ControllerService) GetExternalFile(ctx context.Context, name string) (ExternalFile, error) { file := ExternalFile{} _, err := s.client.doGET(ctx, "/v1/files/"+url.QueryEscape(name), &file) if err != nil { return ExternalFile{}, fmt.Errorf("request failed: %w", err) } return file, nil } // ModifyExternalFile registers or modifies a previously registered external // file func (s *ControllerService) ModifyExternalFile(ctx context.Context, name string, file ExternalFile) error { b64file := externalFileBase64{ Path: file.Path, ContentBase64: base64.StdEncoding.EncodeToString(file.Content), } _, err := s.client.doPUT(ctx, "/v1/files/"+url.QueryEscape(name), b64file) return err } // DeleteExternalFile deletes the given external file. This effectively also // deletes the file on all satellites func (s *ControllerService) DeleteExternalFile(ctx context.Context, name string) error { _, err := s.client.doDELETE(ctx, "/v1/files/"+url.QueryEscape(name), nil) return err } golang-github-linbit-golinstor-0.55.0/client/controllerconfig.go000066400000000000000000000117511501671500700250120ustar00rootroot00000000000000// Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client type ControllerConfig struct { Config ControllerConfigConfig `json:"config,omitempty"` Debug ControllerConfigDebug `json:"debug,omitempty"` Log ControllerConfigLog `json:"log,omitempty"` Db ControllerConfigDb `json:"db,omitempty"` Http ControllerConfigHttp `json:"http,omitempty"` Https ControllerConfigHttps `json:"https,omitempty"` Ldap ControllerConfigLdap `json:"ldap,omitempty"` } type ControllerConfigConfig struct { Dir string `json:"dir,omitempty"` } type ControllerConfigDbEtcd struct { OperationsPerTransaction int32 `json:"operations_per_transaction,omitempty"` Prefix string `json:"prefix,omitempty"` } type ControllerConfigDb struct { ConnectionUrl string `json:"connection_url,omitempty"` CaCertificate string `json:"ca_certificate,omitempty"` ClientCertificate string `json:"client_certificate,omitempty"` ClientKeyPkcs8Pem string `json:"client_key_pkcs8_pem,omitempty"` InMemory string `json:"in_memory,omitempty"` VersionCheckDisabled bool `json:"version_check_disabled,omitempty"` Etcd ControllerConfigDbEtcd `json:"etcd,omitempty"` } type ControllerConfigDebug struct { ConsoleEnabled bool `json:"console_enabled,omitempty"` } type ControllerConfigHttp struct { Enabled bool `json:"enabled,omitempty"` ListenAddress string `json:"listen_address,omitempty"` Port int32 `json:"port,omitempty"` } type ControllerConfigHttps struct { Enabled bool `json:"enabled,omitempty"` ListenAddress string `json:"listen_address,omitempty"` Port int32 `json:"port,omitempty"` Keystore string `json:"keystore,omitempty"` KeystorePassword string `json:"keystore_password,omitempty"` Truststore string `json:"truststore,omitempty"` TruststorePassword string `json:"truststore_password,omitempty"` } type ControllerConfigLdap struct { Enabled bool `json:"enabled,omitempty"` PublicAccessAllowed bool `json:"public_access_allowed,omitempty"` Uri string `json:"uri,omitempty"` Dn string `json:"dn,omitempty"` SearchBase string `json:"search_base,omitempty"` SearchFilter string `json:"search_filter,omitempty"` } type ControllerConfigLog struct { PrintStackTrace bool `json:"print_stack_trace,omitempty"` Directory string `json:"directory,omitempty"` Level LogLevel `json:"level,omitempty"` LevelGlobal LogLevel `json:"level_global,omitempty"` LevelLinstor LogLevel `json:"level_linstor,omitempty"` LevelLinstorGlobal LogLevel `json:"level_linstor_global,omitempty"` RestAccessLogPath string `json:"rest_access_log_path,omitempty"` RestAccessMode string `json:"rest_access_mode,omitempty"` } // SatelliteConfig struct for SatelliteConfig type SatelliteConfig struct { Config ControllerConfigConfig `json:"config,omitempty"` Debug ControllerConfigDebug `json:"debug,omitempty"` Log SatelliteConfigLog `json:"log,omitempty"` StltOverrideNodeName string `json:"stlt_override_node_name,omitempty"` RemoteSpdk bool `json:"remote_spdk,omitempty"` Ebs bool `json:"ebs,omitempty"` SpecialSatellite bool `json:"special_satellite,omitempty"` DrbdKeepResPattern string `json:"drbd_keep_res_pattern,omitempty"` Net SatelliteConfigNet `json:"net,omitempty"` } // SatelliteConfigLog struct for SatelliteConfigLog type SatelliteConfigLog struct { PrintStackTrace bool `json:"print_stack_trace,omitempty"` Directory string `json:"directory,omitempty"` Level LogLevel `json:"level,omitempty"` LevelLinstor LogLevel `json:"level_linstor,omitempty"` } // SatelliteConfigNet struct for SatelliteConfigNet type SatelliteConfigNet struct { BindAddress string `json:"bind_address,omitempty"` Port int32 `json:"port,omitempty"` ComType string `json:"com_type,omitempty"` } type LogLevel string // List of LogLevel const ( ERROR LogLevel = "ERROR" WARN LogLevel = "WARN" INFO LogLevel = "INFO" DEBUG LogLevel = "DEBUG" TRACE LogLevel = "TRACE" ) golang-github-linbit-golinstor-0.55.0/client/encryption.go000066400000000000000000000044311501671500700236300ustar00rootroot00000000000000// A REST client to interact with LINSTOR's REST API // Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client import "context" // custom code // EncryptionProvider acts as an abstraction for an EncryptionService. It can be // swapped out for another EncryptionService implementation, for example for // testing. type EncryptionProvider interface { // Create creates an encryption with the given passphrase Create(ctx context.Context, passphrase Passphrase) error // Modify modifies an existing passphrase Modify(ctx context.Context, passphrase Passphrase) error // Enter is used to enter a password so that content can be decrypted Enter(ctx context.Context, password string) error } // EncryptionService is the service that deals with encyrption related tasks. type EncryptionService struct { client *Client } // Passphrase represents a LINSTOR passphrase type Passphrase struct { NewPassphrase string `json:"new_passphrase,omitempty"` OldPassphrase string `json:"old_passphrase,omitempty"` } // Create creates an encryption with the given passphrase func (n *EncryptionService) Create(ctx context.Context, passphrase Passphrase) error { _, err := n.client.doPOST(ctx, "/v1/encryption/passphrase", passphrase) return err } // Modify modifies an existing passphrase func (n *EncryptionService) Modify(ctx context.Context, passphrase Passphrase) error { _, err := n.client.doPUT(ctx, "/v1/encryption/passphrase", passphrase) return err } // Enter is used to enter a password so that content can be decrypted func (n *EncryptionService) Enter(ctx context.Context, password string) error { _, err := n.client.doPATCH(ctx, "/v1/encryption/passphrase", password) return err } golang-github-linbit-golinstor-0.55.0/client/example_test.go000066400000000000000000000074751501671500700241430ustar00rootroot00000000000000package client_test import ( "context" "crypto/tls" "fmt" "net/http" "net/url" "github.com/LINBIT/golinstor/client" log "github.com/sirupsen/logrus" ) func Example_simple() { ctx := context.TODO() u, err := url.Parse("http://controller:3370") if err != nil { log.Fatal(err) } c, err := client.NewClient(client.BaseURL(u), client.Log(log.StandardLogger())) if err != nil { log.Fatal(err) } rs, err := c.Resources.GetAll(ctx, "foo") if err != nil { log.Fatal(err) } for _, r := range rs { fmt.Printf("Resource with name '%s' on node with name '%s'\n", r.Name, r.NodeName) } } func Example_https() { ctx := context.TODO() u, err := url.Parse("https://controller:3371") if err != nil { log.Fatal(err) } // Be careful if that is really what you want! trSkipVerify := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } httpClient := &http.Client{ Transport: trSkipVerify, } c, err := client.NewClient(client.BaseURL(u), client.HTTPClient(httpClient)) if err != nil { log.Fatal(err) } rs, err := c.Resources.GetAll(ctx, "foo") if err != nil { log.Fatal(err) } for _, r := range rs { fmt.Printf("Resource with name '%s' on node with name '%s'\n", r.Name, r.NodeName) } } func Example_httpsauth() { ctx := context.TODO() u, err := url.Parse("https://controller:3371") if err != nil { log.Fatal(err) } // Be careful if that is really what you want! trSkipVerify := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } httpClient := &http.Client{ Transport: trSkipVerify, } c, err := client.NewClient(client.BaseURL(u), client.HTTPClient(httpClient), client.BasicAuth(&client.BasicAuthCfg{Username: "Username", Password: "Password"})) if err != nil { log.Fatal(err) } rs, err := c.Resources.GetAll(ctx, "foo") if err != nil { log.Fatal(err) } for _, r := range rs { fmt.Printf("Resource with name '%s' on node with name '%s'\n", r.Name, r.NodeName) } } func Example_error() { ctx := context.TODO() u, err := url.Parse("http://controller:3370") if err != nil { log.Fatal(err) } c, err := client.NewClient(client.BaseURL(u), client.Log(log.StandardLogger())) if err != nil { log.Fatal(err) } rs, err := c.Resources.GetAll(ctx, "foo") if errs, ok := err.(client.ApiCallError); ok { log.Error("A LINSTOR API error occurred:") for i, e := range errs { log.Errorf(" Message #%d:", i) log.Errorf(" Code: %d", e.RetCode) log.Errorf(" Message: %s", e.Message) log.Errorf(" Cause: %s", e.Cause) log.Errorf(" Details: %s", e.Details) log.Errorf(" Correction: %s", e.Correction) log.Errorf(" Error Reports: %v", e.ErrorReportIds) } return } if err != nil { log.Fatalf("Some other error occurred: %s", err.Error()) } for _, r := range rs { fmt.Printf("Resource with name '%s' on node with name '%s'\n", r.Name, r.NodeName) } } func Example_events() { ctx := context.TODO() u, err := url.Parse("http://controller:3370") if err != nil { log.Fatal(err) } c, err := client.NewClient(client.BaseURL(u)) if err != nil { log.Fatal(err) } mayPromoteStream, err := c.Events.DRBDPromotion(ctx, "") if err != nil { log.Fatal(err) } defer mayPromoteStream.Close() for ev := range mayPromoteStream.Events { fmt.Printf("Resource '%s' on node with name '%s' may promote: %t\n", ev.ResourceName, ev.NodeName, ev.MayPromote) } } func Example_multipleControllers() { ctx := context.TODO() controllers := []string{"alfa:3370", "bravo:3370", "charlie:3370"} c, err := client.NewClient(client.Controllers(controllers)) if err != nil { log.Fatal(err) } // This call will try each of the controllers and use the first one that responds version, err := c.Controller.GetVersion(ctx) if err != nil { log.Fatal(err) } fmt.Printf("Controller Version: %v", version) } golang-github-linbit-golinstor-0.55.0/client/keyvaluestore.go000066400000000000000000000026551501671500700243460ustar00rootroot00000000000000package client import ( "context" "fmt" ) type KV struct { Name string `json:"name"` Props map[string]string `json:"props"` } type KeyValueStoreProvider interface { List(ctx context.Context) ([]KV, error) Get(ctx context.Context, kv string) (*KV, error) CreateOrModify(ctx context.Context, kv string, modify GenericPropsModify) error Delete(ctx context.Context, kv string) error } var _ KeyValueStoreProvider = &KeyValueStoreService{} type KeyValueStoreService struct { client *Client } // List returns the name of key-value stores and their values func (k *KeyValueStoreService) List(ctx context.Context) ([]KV, error) { var ret []KV _, err := k.client.doGET(ctx, "/v1/key-value-store", &ret) if err != nil { return nil, err } return ret, nil } func (k *KeyValueStoreService) Get(ctx context.Context, kv string) (*KV, error) { var ret []KV _, err := k.client.doGET(ctx, "/v1/key-value-store/"+kv, &ret) if err != nil { return nil, err } if len(ret) != 1 { return nil, fmt.Errorf("expected exactly one KV, got %d", len(ret)) } return &ret[0], nil } func (k *KeyValueStoreService) CreateOrModify(ctx context.Context, kv string, modify GenericPropsModify) error { _, err := k.client.doPUT(ctx, "/v1/key-value-store/"+kv, modify) return err } func (k *KeyValueStoreService) Delete(ctx context.Context, kv string) error { _, err := k.client.doDELETE(ctx, "/v1/key-value-store/"+kv, nil) return err } golang-github-linbit-golinstor-0.55.0/client/node.go000066400000000000000000000364141501671500700223710ustar00rootroot00000000000000// Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client import ( "context" "net" "github.com/LINBIT/golinstor/devicelayerkind" ) // copy & paste from generated code // Node represents a node in LINSTOR type Node struct { Name string `json:"name"` Type string `json:"type"` Flags []string `json:"flags,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` NetInterfaces []NetInterface `json:"net_interfaces,omitempty"` // Enum describing the current connection status. ConnectionStatus string `json:"connection_status,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` StorageProviders []ProviderKind `json:"storage_providers,omitempty"` ResourceLayers []devicelayerkind.DeviceLayerKind `json:"resource_layers,omitempty"` UnsupportedProviders map[ProviderKind][]string `json:"unsupported_providers,omitempty"` UnsupportedLayers map[devicelayerkind.DeviceLayerKind][]string `json:"unsupported_layers,omitempty"` // milliseconds since unix epoch in UTC EvictionTimestamp *TimeStampMs `json:"eviction_timestamp,omitempty"` } type NodeModify struct { NodeType string `json:"node_type,omitempty"` // A string to string property map. GenericPropsModify } type NodeRestore struct { DeleteResources *bool `json:"delete_resources,omitempty"` DeleteSnapshots *bool `json:"delete_snapshots,omitempty"` } // NetInterface represents a node's network interface. type NetInterface struct { Name string `json:"name"` Address net.IP `json:"address"` SatellitePort int32 `json:"satellite_port,omitempty"` SatelliteEncryptionType string `json:"satellite_encryption_type,omitempty"` // Defines if this netinterface should be used for the satellite connection IsActive bool `json:"is_active,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` } // StoragePool represents a nodes storage pool as defined in LINSTOR. type StoragePool struct { StoragePoolName string `json:"storage_pool_name"` NodeName string `json:"node_name,omitempty"` ProviderKind ProviderKind `json:"provider_kind"` // A string to string property map. Props map[string]string `json:"props,omitempty"` // read only map of static storage pool traits StaticTraits map[string]string `json:"static_traits,omitempty"` // Kibi - read only FreeCapacity int64 `json:"free_capacity,omitempty"` // Kibi - read only TotalCapacity int64 `json:"total_capacity,omitempty"` // read only FreeSpaceMgrName string `json:"free_space_mgr_name,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` // Currently known report messages for this storage pool Reports []ApiCallRc `json:"reports,omitempty"` // true if the storage pool supports snapshots. false otherwise SupportsSnapshots bool `json:"supports_snapshots,omitempty"` // name of the shared space or null if none given SharedSpace string `json:"shared_space,omitempty"` // true if a shared storage pool uses linstor-external locking, like cLVM ExternalLocking bool `json:"external_locking,omitempty"` } // ProviderKind is a type that represents various types of storage. type ProviderKind string // List of ProviderKind const ( DISKLESS ProviderKind = "DISKLESS" LVM ProviderKind = "LVM" LVM_THIN ProviderKind = "LVM_THIN" ZFS ProviderKind = "ZFS" ZFS_THIN ProviderKind = "ZFS_THIN" FILE ProviderKind = "FILE" FILE_THIN ProviderKind = "FILE_THIN" SPDK ProviderKind = "SPDK" EBS_TARGET ProviderKind = "EBS_TARGET" EBS_INIT ProviderKind = "EBS_INIT" ) // custom code // NodeProvider acts as an abstraction for a NodeService. It can be swapped out // for another NodeService implementation, for example for testing. type NodeProvider interface { // GetAll gets information for all registered nodes. GetAll(ctx context.Context, opts ...*ListOpts) ([]Node, error) // Get gets information for a particular node. Get(ctx context.Context, nodeName string, opts ...*ListOpts) (Node, error) // Create creates a new node object. Create(ctx context.Context, node Node) error // CreateEbsNode creates a special virtual satellite for interacting with EBS. CreateEbsNode(ctx context.Context, name string, remoteName string) error // Modify modifies the given node and sets/deletes the given properties. Modify(ctx context.Context, nodeName string, props NodeModify) error // Delete deletes the given node. Delete(ctx context.Context, nodeName string) error // Lost marks the given node as lost to delete an unrecoverable node. Lost(ctx context.Context, nodeName string) error // Reconnect reconnects a node to the controller. Reconnect(ctx context.Context, nodeName string) error // GetNetInterfaces gets information about all network interfaces of a given node. GetNetInterfaces(ctx context.Context, nodeName string, opts ...*ListOpts) ([]NetInterface, error) // GetNetInterface gets information about a particular network interface on a given node. GetNetInterface(ctx context.Context, nodeName, nifName string, opts ...*ListOpts) (NetInterface, error) // CreateNetInterface creates the given network interface on a given node. CreateNetInterface(ctx context.Context, nodeName string, nif NetInterface) error // ModifyNetInterface modifies the given network interface on a given node. ModifyNetInterface(ctx context.Context, nodeName, nifName string, nif NetInterface) error // DeleteNetinterface deletes the given network interface on a given node. DeleteNetinterface(ctx context.Context, nodeName, nifName string) error // GetStoragePoolView gets information about all storage pools in the cluster. GetStoragePoolView(ctx context.Context, opts ...*ListOpts) ([]StoragePool, error) // GetStoragePools gets information about all storage pools on a given node. GetStoragePools(ctx context.Context, nodeName string, opts ...*ListOpts) ([]StoragePool, error) // GetStoragePool gets information about a specific storage pool on a given node. GetStoragePool(ctx context.Context, nodeName, spName string, opts ...*ListOpts) (StoragePool, error) // CreateStoragePool creates a storage pool on a given node. CreateStoragePool(ctx context.Context, nodeName string, sp StoragePool) error // ModifyStoragePool modifies a storage pool on a given node. ModifyStoragePool(ctx context.Context, nodeName, spName string, genericProps GenericPropsModify) error // DeleteStoragePool deletes a storage pool on a given node. DeleteStoragePool(ctx context.Context, nodeName, spName string) error // CreateDevicePool creates an LVM, LVM-thin or ZFS pool, optional VDO under it on a given node. CreateDevicePool(ctx context.Context, nodeName string, psc PhysicalStorageCreate) error // GetPhysicalStorageView gets a grouped list of physical storage that can be turned into a LINSTOR storage-pool GetPhysicalStorageView(ctx context.Context, opts ...*ListOpts) ([]PhysicalStorageViewItem, error) // GetPhysicalStorage gets a list of unconfigured physical storage on a node. GetPhysicalStorage(ctx context.Context, nodeName string) ([]PhysicalStorageNode, error) // GetStoragePoolPropsInfos gets meta information about the properties // that can be set on a storage pool on a particular node. GetStoragePoolPropsInfos(ctx context.Context, nodeName string, opts ...*ListOpts) ([]PropsInfo, error) // GetPropsInfos gets meta information about the properties that can be // set on a node. GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) // Evict the given node, migrating resources to the remaining nodes, if possible. This is meant for offline nodes. Evict(ctx context.Context, nodeName string) error // Restore an evicted node, optionally keeping existing resources. Restore(ctx context.Context, nodeName string, restore NodeRestore) error // Evacuate the given node, migrating resources to remaining nodes. While Evict works only on offline nodes, this // is meant for online nodes. Evacuate(ctx context.Context, nodeName string) error } // NodeService is the service that deals with node related tasks. type NodeService struct { client *Client } var _ NodeProvider = &NodeService{} // GetAll gets information for all registered nodes. func (n *NodeService) GetAll(ctx context.Context, opts ...*ListOpts) ([]Node, error) { var nodes []Node _, err := n.client.doGET(ctx, "/v1/nodes", &nodes, opts...) return nodes, err } // Get gets information for a particular node. func (n *NodeService) Get(ctx context.Context, nodeName string, opts ...*ListOpts) (Node, error) { var node Node _, err := n.client.doGET(ctx, "/v1/nodes/"+nodeName, &node, opts...) return node, err } // Create creates a new node object. func (n *NodeService) Create(ctx context.Context, node Node) error { _, err := n.client.doPOST(ctx, "/v1/nodes", node) return err } func (n *NodeService) CreateEbsNode(ctx context.Context, name, remoteName string) error { _, err := n.client.doPOST(ctx, "/v1/nodes/ebs", struct { Name string `json:"name"` EbsRemoteName string `json:"ebs_remote_name"` }{ Name: name, EbsRemoteName: remoteName, }) return err } // Modify modifies the given node and sets/deletes the given properties. func (n *NodeService) Modify(ctx context.Context, nodeName string, props NodeModify) error { _, err := n.client.doPUT(ctx, "/v1/nodes/"+nodeName, props) return err } // Delete deletes the given node. func (n *NodeService) Delete(ctx context.Context, nodeName string) error { _, err := n.client.doDELETE(ctx, "/v1/nodes/"+nodeName, nil) return err } // Lost marks the given node as lost to delete an unrecoverable node. func (n *NodeService) Lost(ctx context.Context, nodeName string) error { _, err := n.client.doDELETE(ctx, "/v1/nodes/"+nodeName+"/lost", nil) return err } // Reconnect reconnects a node to the controller. func (n *NodeService) Reconnect(ctx context.Context, nodeName string) error { _, err := n.client.doPUT(ctx, "/v1/nodes/"+nodeName+"/reconnect", nil) return err } // GetNetInterfaces gets information about all network interfaces of a given node. func (n *NodeService) GetNetInterfaces(ctx context.Context, nodeName string, opts ...*ListOpts) ([]NetInterface, error) { var nifs []NetInterface _, err := n.client.doGET(ctx, "/v1/nodes/"+nodeName+"/net-interfaces", &nifs, opts...) return nifs, err } // GetNetInterface gets information about a particular network interface on a given node. func (n *NodeService) GetNetInterface(ctx context.Context, nodeName, nifName string, opts ...*ListOpts) (NetInterface, error) { var nif NetInterface _, err := n.client.doGET(ctx, "/v1/nodes/"+nodeName+"/net-interfaces/"+nifName, &nif, opts...) return nif, err } // CreateNetInterface creates the given network interface on a given node. func (n *NodeService) CreateNetInterface(ctx context.Context, nodeName string, nif NetInterface) error { _, err := n.client.doPOST(ctx, "/v1/nodes/"+nodeName+"/net-interfaces", nif) return err } // ModifyNetInterface modifies the given network interface on a given node. func (n *NodeService) ModifyNetInterface(ctx context.Context, nodeName, nifName string, nif NetInterface) error { _, err := n.client.doPUT(ctx, "/v1/nodes/"+nodeName+"/net-interfaces/"+nifName, nif) return err } // DeleteNetinterface deletes the given network interface on a given node. func (n *NodeService) DeleteNetinterface(ctx context.Context, nodeName, nifName string) error { _, err := n.client.doDELETE(ctx, "/v1/nodes/"+nodeName+"/net-interfaces/"+nifName, nil) return err } // GetStoragePoolView gets information about all storage pools in the cluster. func (n *NodeService) GetStoragePoolView(ctx context.Context, opts ...*ListOpts) ([]StoragePool, error) { var sps []StoragePool _, err := n.client.doGET(ctx, "/v1/view/storage-pools", &sps, opts...) return sps, err } // GetStoragePools gets information about all storage pools on a given node. func (n *NodeService) GetStoragePools(ctx context.Context, nodeName string, opts ...*ListOpts) ([]StoragePool, error) { var sps []StoragePool _, err := n.client.doGET(ctx, "/v1/nodes/"+nodeName+"/storage-pools", &sps, opts...) return sps, err } // GetStoragePool gets information about a specific storage pool on a given node. func (n *NodeService) GetStoragePool(ctx context.Context, nodeName, spName string, opts ...*ListOpts) (StoragePool, error) { var sp StoragePool _, err := n.client.doGET(ctx, "/v1/nodes/"+nodeName+"/storage-pools/"+spName, &sp, opts...) return sp, err } // CreateStoragePool creates a storage pool on a given node. func (n *NodeService) CreateStoragePool(ctx context.Context, nodeName string, sp StoragePool) error { _, err := n.client.doPOST(ctx, "/v1/nodes/"+nodeName+"/storage-pools", sp) return err } // ModifyStoragePool modifies a storage pool on a given node. func (n *NodeService) ModifyStoragePool(ctx context.Context, nodeName, spName string, genericProps GenericPropsModify) error { _, err := n.client.doPUT(ctx, "/v1/nodes/"+nodeName+"/storage-pools/"+spName, genericProps) return err } // DeleteStoragePool deletes a storage pool on a given node. func (n *NodeService) DeleteStoragePool(ctx context.Context, nodeName, spName string) error { _, err := n.client.doDELETE(ctx, "/v1/nodes/"+nodeName+"/storage-pools/"+spName, nil) return err } // GetStoragePoolPropsInfos gets meta information about the properties that can // be set on a storage pool on a particular node. func (n *NodeService) GetStoragePoolPropsInfos(ctx context.Context, nodeName string, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/nodes/"+nodeName+"/storage-pools/properties/info", &infos, opts...) return infos, err } // GetPropsInfos gets meta information about the properties that can be set on // a node. func (n *NodeService) GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/nodes/properties/info", &infos, opts...) return infos, err } // Evict the given node, migrating resources to the remaining nodes, if possible. func (n NodeService) Evict(ctx context.Context, nodeName string) error { _, err := n.client.doPUT(ctx, "/v1/nodes/"+nodeName+"/evict", nil) return err } // Evacuate the given node, migrating resources to remaining nodes. While Evict works only on offline nodes, this // is meant for online nodes. func (n NodeService) Evacuate(ctx context.Context, nodeName string) error { _, err := n.client.doPUT(ctx, "/v1/nodes/"+nodeName+"/evacuate", nil) return err } // Restore an evicted node, optionally keeping existing resources. func (n *NodeService) Restore(ctx context.Context, nodeName string, restore NodeRestore) error { _, err := n.client.doPUT(ctx, "/v1/nodes/"+nodeName+"/restore", restore) return err } golang-github-linbit-golinstor-0.55.0/client/option.go000066400000000000000000000040401501671500700227420ustar00rootroot00000000000000// A REST client to interact with LINSTOR's REST API // Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client import ( "net/url" "reflect" "github.com/google/go-querystring/query" ) // ListOpts is a struct primarily used to define parameters used for pagination. It is also used for filtering (e.g., the /view/ calls) type ListOpts struct { // Number of items to skip. Only used if Limit is a positive value Offset int `url:"offset"` // Maximum number of items to retrieve Limit int `url:"limit"` // Some responses can be cached controller side, such as snapshot lists Cached *bool `url:"cached,omitempty"` StoragePool []string `url:"storage_pools"` Resource []string `url:"resources"` Node []string `url:"nodes"` Prop []string `url:"props"` Snapshots []string `url:"snapshots"` Status string `url:"status,omitempty"` // Content is used in the files API. If true, fetching files will include the content. Content bool `url:"content,omitempty"` } func genOptions(opts ...*ListOpts) *ListOpts { if opts == nil || len(opts) == 0 { return nil } return opts[0] } func addOptions(s string, opt interface{}) (string, error) { v := reflect.ValueOf(opt) if v.Kind() == reflect.Ptr && v.IsNil() { return s, nil } u, err := url.Parse(s) if err != nil { return s, err } vs, err := query.Values(opt) if err != nil { return s, err } u.RawQuery = vs.Encode() return u.String(), nil } golang-github-linbit-golinstor-0.55.0/client/physicalstorage.go000066400000000000000000000073001501671500700246350ustar00rootroot00000000000000// Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client import "context" // copy & paste from generated code // PhysicalStorageStoragePoolCreate is used for create physical-storage type PhysicalStorageStoragePoolCreate struct { // Name of the linstor storage pool Name string `json:"name,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` // Name of the shared space SharedSpace string `json:"shared_space,omitempty"` // true if a shared storage pool uses linstor-external locking, like cLVM ExternalLocking bool `json:"external_locking,omitempty"` } // PhysicalStorageCreate is a configuration struct used to represent pysical storage on a given node. // If with_storage_pool is set a linstor storage pool will also be created using this device pool type PhysicalStorageCreate struct { ProviderKind ProviderKind `json:"provider_kind"` DevicePaths []string `json:"device_paths"` // RAID level to use for pool. RaidLevel string `json:"raid_level,omitempty"` PoolName string `json:"pool_name,omitempty"` VdoEnable bool `json:"vdo_enable,omitempty"` VdoSlabSizeKib int64 `json:"vdo_slab_size_kib,omitempty"` VdoLogicalSizeKib int64 `json:"vdo_logical_size_kib,omitempty"` WithStoragePool PhysicalStorageStoragePoolCreate `json:"with_storage_pool,omitempty"` } type PhysicalStorageNode struct { PhysicalStorageDevice Size int64 `json:"size,omitempty"` Rotational bool `json:"rotational,omitempty"` } // PhysicalStorageDevice represents a physical storage device on a a node. type PhysicalStorageDevice struct { Device string `json:"device,omitempty"` Model string `json:"model,omitempty"` Serial string `json:"serial,omitempty"` Wwn string `json:"wwn,omitempty"` } // PhysicalStorageViewItem is a view on a physical storage on multiple nodes. type PhysicalStorageViewItem struct { Size int64 `json:"size,omitempty"` Rotational bool `json:"rotational,omitempty"` Nodes map[string][]PhysicalStorageDevice `json:"nodes,omitempty"` } // GetPhysicalStorageView gets a grouped list of physical storage that can be turned into a LINSTOR storage-pool func (n *NodeService) GetPhysicalStorageView(ctx context.Context, opts ...*ListOpts) ([]PhysicalStorageViewItem, error) { var ps []PhysicalStorageViewItem _, err := n.client.doGET(ctx, "/v1/physical-storage/", &ps, opts...) return ps, err } func (n *NodeService) GetPhysicalStorage(ctx context.Context, nodeName string) ([]PhysicalStorageNode, error) { var ps []PhysicalStorageNode _, err := n.client.doGET(ctx, "/v1/physical-storage/"+nodeName, &ps) return ps, err } // CreateDevicePool creates an LVM, LVM-thin or ZFS pool, optional VDO under it on a given node. func (n *NodeService) CreateDevicePool(ctx context.Context, nodeName string, psc PhysicalStorageCreate) error { _, err := n.client.doPOST(ctx, "/v1/physical-storage/"+nodeName, psc) return err } golang-github-linbit-golinstor-0.55.0/client/remote.go000066400000000000000000000113601501671500700227300ustar00rootroot00000000000000package client import ( "context" "fmt" "github.com/google/go-querystring/query" ) type LinstorRemote struct { RemoteName string `json:"remote_name,omitempty"` Url string `json:"url,omitempty"` Passphrase string `json:"passphrase,omitempty"` ClusterId string `json:"cluster_id,omitempty"` } type RemoteList struct { S3Remotes []S3Remote `json:"s3_remotes,omitempty"` LinstorRemotes []LinstorRemote `json:"linstor_remotes,omitempty"` EbsRemotes []EbsRemote `json:"ebs_remotes,omitempty"` } type S3Remote struct { RemoteName string `json:"remote_name,omitempty"` Endpoint string `json:"endpoint,omitempty"` Bucket string `json:"bucket,omitempty"` Region string `json:"region,omitempty"` AccessKey string `json:"access_key,omitempty"` SecretKey string `json:"secret_key,omitempty"` UsePathStyle bool `json:"use_path_style,omitempty"` } type EbsRemote struct { RemoteName string `json:"remote_name,omitempty"` Endpoint string `json:"endpoint,omitempty"` Region string `json:"region,omitempty"` AvailabilityZone string `json:"availability_zone,omitempty"` AccessKey string `json:"access_key,omitempty"` SecretKey string `json:"secret_key,omitempty"` } type RemoteProvider interface { // GetAll returns the list of all registered remotes. GetAll(ctx context.Context, opts ...*ListOpts) (RemoteList, error) // GetAllLinstor returns the list of LINSTOR remotes. GetAllLinstor(ctx context.Context, opts ...*ListOpts) ([]LinstorRemote, error) // GetAllS3 returns the list of S3 remotes. GetAllS3(ctx context.Context, opts ...*ListOpts) ([]S3Remote, error) // GetAllEbs returns the list of EBS remotes. GetAllEbs(ctx context.Context, opts ...*ListOpts) ([]EbsRemote, error) // CreateLinstor creates a new LINSTOR remote. CreateLinstor(ctx context.Context, create LinstorRemote) error // CreateS3 creates a new S3 remote. CreateS3(ctx context.Context, create S3Remote) error // CreateEbs creates a new EBS remote. CreateEbs(ctx context.Context, create EbsRemote) error // Delete a named remote. Delete(ctx context.Context, remoteName string) error // ModifyLinstor modifies the given LINSTOR remote. ModifyLinstor(ctx context.Context, remoteName string, modify LinstorRemote) error // ModifyS3 modifies the given S3 remote. ModifyS3(ctx context.Context, remoteName string, modify S3Remote) error // ModifyEbs modifies the given EBS remote. ModifyEbs(ctx context.Context, remoteName string, modify EbsRemote) error } var _ RemoteProvider = &RemoteService{} type RemoteService struct { client *Client } func (r *RemoteService) GetAll(ctx context.Context, opts ...*ListOpts) (RemoteList, error) { var list RemoteList _, err := r.client.doGET(ctx, "/v1/remotes", &list, opts...) return list, err } func (r *RemoteService) GetAllLinstor(ctx context.Context, opts ...*ListOpts) ([]LinstorRemote, error) { var list []LinstorRemote _, err := r.client.doGET(ctx, "/v1/remotes/linstor", &list, opts...) return list, err } func (r *RemoteService) GetAllS3(ctx context.Context, opts ...*ListOpts) ([]S3Remote, error) { var list []S3Remote _, err := r.client.doGET(ctx, "/v1/remotes/s3", &list, opts...) return list, err } func (r *RemoteService) GetAllEbs(ctx context.Context, opts ...*ListOpts) ([]EbsRemote, error) { var list []EbsRemote _, err := r.client.doGET(ctx, "/v1/remotes/ebs", &list, opts...) return list, err } func (r *RemoteService) CreateLinstor(ctx context.Context, create LinstorRemote) error { _, err := r.client.doPOST(ctx, "/v1/remotes/linstor", create) return err } func (r *RemoteService) CreateS3(ctx context.Context, create S3Remote) error { _, err := r.client.doPOST(ctx, "/v1/remotes/s3", create) return err } func (r *RemoteService) CreateEbs(ctx context.Context, create EbsRemote) error { _, err := r.client.doPOST(ctx, "/v1/remotes/ebs", create) return err } func (r *RemoteService) Delete(ctx context.Context, remoteName string) error { vals, err := query.Values(&struct { RemoteName string `url:"remote_name"` }{RemoteName: remoteName}) if err != nil { return fmt.Errorf("failed to encode remote name: %w", err) } _, err = r.client.doDELETE(ctx, "/v1/remotes?"+vals.Encode(), nil) return err } func (r *RemoteService) ModifyLinstor(ctx context.Context, remoteName string, modify LinstorRemote) error { _, err := r.client.doPUT(ctx, "/v1/remotes/linstor/"+remoteName, modify) return err } func (r *RemoteService) ModifyS3(ctx context.Context, remoteName string, modify S3Remote) error { _, err := r.client.doPUT(ctx, "/v1/remotes/s3/"+remoteName, modify) return err } func (r *RemoteService) ModifyEbs(ctx context.Context, remoteName string, modify EbsRemote) error { _, err := r.client.doPUT(ctx, "/v1/remotes/ebs/"+remoteName, modify) return err } golang-github-linbit-golinstor-0.55.0/client/resource.go000066400000000000000000001137631501671500700232760ustar00rootroot00000000000000// A REST client to interact with LINSTOR's REST API // Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client import ( "context" "encoding/json" "fmt" "strconv" "github.com/google/go-querystring/query" "github.com/LINBIT/golinstor/devicelayerkind" "github.com/LINBIT/golinstor/snapshotshipstatus" ) // ResourceService is a struct which contains the pointer of the client type ResourceService struct { client *Client } // copy & paste from generated code // Resource is a struct which holds the information of a resource type Resource struct { Name string `json:"name,omitempty"` NodeName string `json:"node_name,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` Flags []string `json:"flags,omitempty"` LayerObject *ResourceLayer `json:"layer_object,omitempty"` State *ResourceState `json:"state,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` // milliseconds since unix epoch in UTC CreateTimestamp *TimeStampMs `json:"create_timestamp,omitempty"` } type ResourceWithVolumes struct { Resource // milliseconds since unix epoch in UTC CreateTimestamp *TimeStampMs `json:"create_timestamp,omitempty"` Volumes []Volume `json:"volumes,omitempty"` // shared space name of the data storage pool of the first volume of // the resource or empty if data storage pool is not shared SharedName string `json:"shared_name,omitempty"` } type ResourceDefinitionModify struct { // drbd port for resources DrbdPort int32 `json:"drbd_port,omitempty"` // drbd peer slot number DrbdPeerSlots int32 `json:"drbd_peer_slots,omitempty"` LayerStack []devicelayerkind.DeviceLayerKind `json:"layer_stack,omitempty"` // change resource group to the given group name ResourceGroup string `json:"resource_group,omitempty"` GenericPropsModify } // ResourceCreate is a struct where the properties of a resource are stored to create it type ResourceCreate struct { Resource Resource `json:"resource,omitempty"` LayerList []devicelayerkind.DeviceLayerKind `json:"layer_list,omitempty"` DrbdNodeId int32 `json:"drbd_node_id,omitempty"` } // ResourceLayer is a struct to store layer-information abour a resource type ResourceLayer struct { Children []ResourceLayer `json:"children,omitempty"` ResourceNameSuffix string `json:"resource_name_suffix,omitempty"` Type devicelayerkind.DeviceLayerKind `json:"type,omitempty"` Drbd *DrbdResource `json:"drbd,omitempty"` Luks *LuksResource `json:"luks,omitempty"` Storage *StorageResource `json:"storage,omitempty"` Nvme *NvmeResource `json:"nvme,omitempty"` Writecache *WritecacheResource `json:"writecache,omitempty"` Cache *CacheResource `json:"cache,omitempty"` BCache *BCacheResource `json:"bcache,omitempty"` } type WritecacheResource struct { WritecacheVolumes []WritecacheVolume `json:"writecache_volumes,omitempty"` } type WritecacheVolume struct { VolumeNumber int32 `json:"volume_number,omitempty"` // block device path DevicePath string `json:"device_path,omitempty"` // block device path used as cache device DevicePathCache string `json:"device_path_cache,omitempty"` AllocatedSizeKib int64 `json:"allocated_size_kib,omitempty"` UsableSizeKib int64 `json:"usable_size_kib,omitempty"` // String describing current volume state DiskState string `json:"disk_state,omitempty"` } type BCacheResource struct { BCacheVolumes []BCacheVolume `json:"bcache_volumes,omitempty"` } type BCacheVolume struct { VolumeNumber int32 `json:"volume_number,omitempty"` // block device path DevicePath string `json:"device_path,omitempty"` // block device path used as cache device DevicePathCache string `json:"device_path_cache,omitempty"` AllocatedSizeKib int64 `json:"allocated_size_kib,omitempty"` UsableSizeKib int64 `json:"usable_size_kib,omitempty"` // String describing current volume state DiskState string `json:"disk_state,omitempty"` } // DrbdResource is a struct used to give linstor drbd properties for a resource type DrbdResource struct { DrbdResourceDefinition DrbdResourceDefinitionLayer `json:"drbd_resource_definition,omitempty"` NodeId int32 `json:"node_id,omitempty"` PeerSlots int32 `json:"peer_slots,omitempty"` AlStripes int32 `json:"al_stripes,omitempty"` AlSize int64 `json:"al_size,omitempty"` Flags []string `json:"flags,omitempty"` DrbdVolumes []DrbdVolume `json:"drbd_volumes,omitempty"` Connections map[string]DrbdConnection `json:"connections,omitempty"` PromotionScore int32 `json:"promotion_score,omitempty"` MayPromote bool `json:"may_promote,omitempty"` } // DrbdConnection is a struct representing the DRBD connection status type DrbdConnection struct { Connected bool `json:"connected,omitempty"` // DRBD connection status Message string `json:"message,omitempty"` } // DrbdVolume is a struct for linstor to get inormation about a drbd-volume type DrbdVolume struct { DrbdVolumeDefinition DrbdVolumeDefinition `json:"drbd_volume_definition,omitempty"` // drbd device path e.g. '/dev/drbd1000' DevicePath string `json:"device_path,omitempty"` // block device used by drbd BackingDevice string `json:"backing_device,omitempty"` MetaDisk string `json:"meta_disk,omitempty"` AllocatedSizeKib int64 `json:"allocated_size_kib,omitempty"` UsableSizeKib int64 `json:"usable_size_kib,omitempty"` // String describing current volume state DiskState string `json:"disk_state,omitempty"` // Storage pool name used for external meta data; null for internal ExtMetaStorPool string `json:"ext_meta_stor_pool,omitempty"` } // LuksResource is a struct to store storage-volumes for a luks-resource type LuksResource struct { StorageVolumes []LuksVolume `json:"storage_volumes,omitempty"` } // LuksVolume is a struct used for information about a luks-volume type LuksVolume struct { VolumeNumber int32 `json:"volume_number,omitempty"` // block device path DevicePath string `json:"device_path,omitempty"` // block device used by luks BackingDevice string `json:"backing_device,omitempty"` AllocatedSizeKib int64 `json:"allocated_size_kib,omitempty"` UsableSizeKib int64 `json:"usable_size_kib,omitempty"` // String describing current volume state DiskState string `json:"disk_state,omitempty"` Opened bool `json:"opened,omitempty"` } // StorageResource is a struct which contains the storage-volumes for a storage-resource type StorageResource struct { StorageVolumes []StorageVolume `json:"storage_volumes,omitempty"` } // StorageVolume is a struct to store standard poperties of a Volume type StorageVolume struct { VolumeNumber int32 `json:"volume_number,omitempty"` // block device path DevicePath string `json:"device_path,omitempty"` AllocatedSizeKib int64 `json:"allocated_size_kib,omitempty"` UsableSizeKib int64 `json:"usable_size_kib,omitempty"` // String describing current volume state DiskState string `json:"disk_state,omitempty"` } type NvmeResource struct { NvmeVolumes []NvmeVolume `json:"nvme_volumes,omitempty"` } type NvmeVolume struct { VolumeNumber int32 `json:"volume_number,omitempty"` // block device path DevicePath string `json:"device_path,omitempty"` // block device used by nvme BackingDevice string `json:"backing_device,omitempty"` AllocatedSizeKib int64 `json:"allocated_size_kib,omitempty"` UsableSizeKib int64 `json:"usable_size_kib,omitempty"` // String describing current volume state DiskState string `json:"disk_state,omitempty"` } // ResourceState is a struct for getting the status of a resource type ResourceState struct { InUse *bool `json:"in_use,omitempty"` } // Volume is a struct which holds the information about a linstor-volume type Volume struct { VolumeNumber int32 `json:"volume_number,omitempty"` StoragePoolName string `json:"storage_pool_name,omitempty"` ProviderKind ProviderKind `json:"provider_kind,omitempty"` DevicePath string `json:"device_path,omitempty"` AllocatedSizeKib int64 `json:"allocated_size_kib,omitempty"` UsableSizeKib int64 `json:"usable_size_kib,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` Flags []string `json:"flags,omitempty"` State VolumeState `json:"state,omitempty"` LayerDataList []VolumeLayer `json:"layer_data_list,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` Reports []ApiCallRc `json:"reports,omitempty"` } // VolumeLayer is a struct for storing the layer-properties of a linstor-volume type VolumeLayer struct { Type devicelayerkind.DeviceLayerKind `json:"type,omitempty"` Data OneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolumeBCacheVolume `json:"data,omitempty"` } // VolumeState is a struct which contains the disk-state for volume type VolumeState struct { DiskState string `json:"disk_state,omitempty"` } // AutoPlaceRequest is a struct to store the paramters for the linstor auto-place command type AutoPlaceRequest struct { DisklessOnRemaining bool `json:"diskless_on_remaining,omitempty"` SelectFilter AutoSelectFilter `json:"select_filter,omitempty"` LayerList []devicelayerkind.DeviceLayerKind `json:"layer_list,omitempty"` } // AutoSelectFilter is a struct used to have information about the auto-select function type AutoSelectFilter struct { PlaceCount int32 `json:"place_count,omitempty"` AdditionalPlaceCount int32 `json:"additional_place_count,omitempty"` NodeNameList []string `json:"node_name_list,omitempty"` StoragePool string `json:"storage_pool,omitempty"` StoragePoolList []string `json:"storage_pool_list,omitempty"` StoragePoolDisklessList []string `json:"storage_pool_diskless_list,omitempty"` NotPlaceWithRsc []string `json:"not_place_with_rsc,omitempty"` NotPlaceWithRscRegex string `json:"not_place_with_rsc_regex,omitempty"` ReplicasOnSame []string `json:"replicas_on_same,omitempty"` ReplicasOnDifferent []string `json:"replicas_on_different,omitempty"` XReplicasOnDifferent map[string]int `json:"x_replicas_on_different_map,omitempty"` LayerStack []string `json:"layer_stack,omitempty"` ProviderList []string `json:"provider_list,omitempty"` DisklessOnRemaining bool `json:"diskless_on_remaining,omitempty"` DisklessType string `json:"diskless_type,omitempty"` Overprovision *float64 `json:"overprovision,omitempty"` } // ResourceConnection is a struct which holds information about a connection between to nodes type ResourceConnection struct { // source node of the connection NodeA string `json:"node_a,omitempty"` // target node of the connection NodeB string `json:"node_b,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` Flags []string `json:"flags,omitempty"` Port int32 `json:"port,omitempty"` } // Snapshot is a struct for information about a snapshot type Snapshot struct { Name string `json:"name,omitempty"` ResourceName string `json:"resource_name,omitempty"` Nodes []string `json:"nodes,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` Flags []string `json:"flags,omitempty"` VolumeDefinitions []SnapshotVolumeDefinition `json:"volume_definitions,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` Snapshots []SnapshotNode `json:"snapshots,omitempty"` } // SnapshotNode Actual snapshot data from a node type SnapshotNode struct { // Snapshot name this snapshots belongs to SnapshotName string `json:"snapshot_name,omitempty"` // Node name where this snapshot was taken NodeName string `json:"node_name,omitempty"` // milliseconds since unix epoch in UTC CreateTimestamp *TimeStampMs `json:"create_timestamp,omitempty"` Flags []string `json:"flags,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` // SnapshotVolumes holds per-volume information about snapshots on this node. SnapshotVolumes []SnapshotVolumeNode `json:"snapshot_volumes,omitempty"` } type SnapshotVolumeNode struct { // unique object id Uuid string `json:"uuid,omitempty"` // Volume number of the snapshot VlmNr int32 `json:"vlm_nr,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` // Optional state for the given snapshot State string `json:"state,omitempty"` } // SnapshotShipping struct for SnapshotShipping type SnapshotShipping struct { // Node where to ship the snapshot from FromNode string `json:"from_node"` // NetInterface of the source node FromNic string `json:"from_nic,omitempty"` // Node where to ship the snapshot ToNode string `json:"to_node"` // NetInterface of the destination node ToNic string `json:"to_nic,omitempty"` } // SnapshotShippingStatus struct for SnapshotShippingStatus type SnapshotShippingStatus struct { Snapshot Snapshot `json:"snapshot,omitempty"` FromNodeName string `json:"from_node_name,omitempty"` ToNodeName string `json:"to_node_name,omitempty"` Status snapshotshipstatus.SnapshotShipStatus `json:"status,omitempty"` } // SnapshotVolumeDefinition is a struct to store the properties of a volume from a snapshot type SnapshotVolumeDefinition struct { VolumeNumber int32 `json:"volume_number,omitempty"` // Volume size in KiB SizeKib uint64 `json:"size_kib,omitempty"` } // SnapshotRestore is a struct used to hold the information about where a Snapshot has to be restored type SnapshotRestore struct { // Resource where to restore the snapshot ToResource string `json:"to_resource"` // List of nodes where to place the restored snapshot Nodes []string `json:"nodes,omitempty"` } type DrbdProxyModify struct { // Compression type used by the proxy. CompressionType string `json:"compression_type,omitempty"` // A string to string property map. CompressionProps map[string]string `json:"compression_props,omitempty"` GenericPropsModify } // Candidate struct for Candidate type Candidate struct { StoragePool string `json:"storage_pool,omitempty"` // maximum size in KiB MaxVolumeSizeKib int64 `json:"max_volume_size_kib,omitempty"` NodeNames []string `json:"node_names,omitempty"` AllThin bool `json:"all_thin,omitempty"` } // MaxVolumeSizes struct for MaxVolumeSizes type MaxVolumeSizes struct { Candidates []Candidate `json:"candidates,omitempty"` DefaultMaxOversubscriptionRatio float64 `json:"default_max_oversubscription_ratio,omitempty"` } type ResourceMakeAvailable struct { LayerList []devicelayerkind.DeviceLayerKind `json:"layer_list,omitempty"` // if true resource will be created as diskful even if diskless would be possible Diskful bool `json:"diskful,omitempty"` } type ToggleDiskDiskfulProps struct { LayerList []devicelayerkind.DeviceLayerKind `json:"layer_list,omitempty"` } // custom code // ResourceProvider acts as an abstraction for an ResourceService. It can be // swapped out for another ResourceService implementation, for example for // testing. type ResourceProvider interface { // GetResourceView returns all resources in the cluster. Filters can be set via ListOpts. GetResourceView(ctx context.Context, opts ...*ListOpts) ([]ResourceWithVolumes, error) // GetAll returns all resources for a resource-definition GetAll(ctx context.Context, resName string, opts ...*ListOpts) ([]Resource, error) // Get returns information about a resource on a specific node Get(ctx context.Context, resName, nodeName string, opts ...*ListOpts) (Resource, error) // Create is used to create a resource on a node Create(ctx context.Context, res ResourceCreate) error // Modify gives the ability to modify a resource on a node Modify(ctx context.Context, resName, nodeName string, props GenericPropsModify) error // Delete deletes a resource on a specific node Delete(ctx context.Context, resName, nodeName string) error // GetVolumes lists als volumes of a resource GetVolumes(ctx context.Context, resName, nodeName string, opts ...*ListOpts) ([]Volume, error) // GetVolume returns information about a specific volume defined by it resource,node and volume-number GetVolume(ctx context.Context, resName, nodeName string, volNr int, opts ...*ListOpts) (Volume, error) // ModifyVolume modifies an existing volume with the given props ModifyVolume(ctx context.Context, resName, nodeName string, volNr int, props GenericPropsModify) error // Diskless toggles a resource on a node to diskless - the parameter disklesspool can be set if its needed Diskless(ctx context.Context, resName, nodeName, disklessPoolName string) error // Diskful toggles a resource to diskful - the parameter storagepool can be set if its needed Diskful(ctx context.Context, resName, nodeName, storagePoolName string, props *ToggleDiskDiskfulProps) error // Migrate mirgates a resource from one node to another node Migrate(ctx context.Context, resName, fromNodeName, toNodeName, storagePoolName string) error // Autoplace places a resource on your nodes autmatically Autoplace(ctx context.Context, resName string, apr AutoPlaceRequest) error // GetConnections lists all resource connections if no node-names are given- if two node-names are given it shows the connection between them GetConnections(ctx context.Context, resName, nodeAName, nodeBName string, opts ...*ListOpts) ([]ResourceConnection, error) // ModifyConnection allows to modify the connection between two nodes ModifyConnection(ctx context.Context, resName, nodeAName, nodeBName string, props GenericPropsModify) error // GetSnapshots lists all snapshots of a resource GetSnapshots(ctx context.Context, resName string, opts ...*ListOpts) ([]Snapshot, error) // GetSnapshotView gets information about all snapshots GetSnapshotView(ctx context.Context, opts ...*ListOpts) ([]Snapshot, error) // GetSnapshot returns information about a specific Snapshot by its name GetSnapshot(ctx context.Context, resName, snapName string, opts ...*ListOpts) (Snapshot, error) // CreateSnapshot creates a snapshot of a resource CreateSnapshot(ctx context.Context, snapshot Snapshot) error // DeleteSnapshot deletes a snapshot by its name. Specify nodes to only delete snapshots on specific nodes. DeleteSnapshot(ctx context.Context, resName, snapName string, nodes ...string) error // RestoreSnapshot restores a snapshot on a resource RestoreSnapshot(ctx context.Context, origResName, snapName string, snapRestoreConf SnapshotRestore) error // RestoreVolumeDefinitionSnapshot restores a volume-definition-snapshot on a resource RestoreVolumeDefinitionSnapshot(ctx context.Context, origResName, snapName string, snapRestoreConf SnapshotRestore) error // RollbackSnapshot rolls back a snapshot from a specific resource RollbackSnapshot(ctx context.Context, resName, snapName string) error // EnableSnapshotShipping enables snapshot shipping for a resource EnableSnapshotShipping(ctx context.Context, resName string, ship SnapshotShipping) error // ModifyDRBDProxy is used to modify drbd-proxy properties ModifyDRBDProxy(ctx context.Context, resName string, props DrbdProxyModify) error // EnableDRBDProxy is used to enable drbd-proxy with the rest-api call from the function enableDisableDRBDProxy EnableDRBDProxy(ctx context.Context, resName, nodeAName, nodeBName string) error // DisableDRBDProxy is used to disable drbd-proxy with the rest-api call from the function enableDisableDRBDProxy DisableDRBDProxy(ctx context.Context, resName, nodeAName, nodeBName string) error // QueryMaxVolumeSize finds the maximum size of a volume for a given filter QueryMaxVolumeSize(ctx context.Context, filter AutoSelectFilter) (MaxVolumeSizes, error) // GetSnapshotShippings gets a view of all snapshot shippings GetSnapshotShippings(ctx context.Context, opts ...*ListOpts) ([]SnapshotShippingStatus, error) // GetPropsInfos gets meta information about the properties that can be // set on a resource. GetPropsInfos(ctx context.Context, resName string, opts ...*ListOpts) ([]PropsInfo, error) // GetVolumeDefinitionPropsInfos gets meta information about the // properties that can be set on a volume definition. GetVolumeDefinitionPropsInfos(ctx context.Context, resName string, opts ...*ListOpts) ([]PropsInfo, error) // GetVolumePropsInfos gets meta information about the properties that // can be set on a volume. GetVolumePropsInfos(ctx context.Context, resName, nodeName string, opts ...*ListOpts) ([]PropsInfo, error) // GetConnectionPropsInfos gets meta information about the properties // that can be set on a connection. GetConnectionPropsInfos(ctx context.Context, resName string, opts ...*ListOpts) ([]PropsInfo, error) // Activate starts an inactive resource on a given node. Activate(ctx context.Context, resName string, nodeName string) error // Deactivate stops an active resource on given node. Deactivate(ctx context.Context, resName string, nodeName string) error // MakeAvailable adds a resource on a node if not already deployed. // To use a specific storage pool add the StorPoolName property and use // the storage pool name as value. If the StorPoolName property is not // set, a storage pool will be chosen automatically using the // auto-placer. // To create a diskless resource you have to set the "DISKLESS" flag in // the flags list. MakeAvailable(ctx context.Context, resName, nodeName string, makeAvailable ResourceMakeAvailable) error } var _ ResourceProvider = &ResourceService{} // volumeLayerIn is a struct for volume-layers type volumeLayerIn struct { Type devicelayerkind.DeviceLayerKind `json:"type,omitempty"` Data json.RawMessage `json:"data,omitempty"` } // UnmarshalJSON fulfills the unmarshal interface for the VolumeLayer type func (v *VolumeLayer) UnmarshalJSON(b []byte) error { var vIn volumeLayerIn if err := json.Unmarshal(b, &vIn); err != nil { return err } v.Type = vIn.Type switch v.Type { case devicelayerkind.Drbd: dst := new(DrbdVolume) if vIn.Data != nil { if err := json.Unmarshal(vIn.Data, &dst); err != nil { return err } } v.Data = dst case devicelayerkind.Luks: dst := new(LuksVolume) if vIn.Data != nil { if err := json.Unmarshal(vIn.Data, &dst); err != nil { return err } } v.Data = dst case devicelayerkind.Storage: dst := new(StorageVolume) if vIn.Data != nil { if err := json.Unmarshal(vIn.Data, &dst); err != nil { return err } } v.Data = dst case devicelayerkind.Nvme: dst := new(NvmeVolume) if vIn.Data != nil { if err := json.Unmarshal(vIn.Data, &dst); err != nil { return err } } v.Data = dst case devicelayerkind.Writecache: dst := new(WritecacheVolume) if vIn.Data != nil { if err := json.Unmarshal(vIn.Data, &dst); err != nil { return err } } v.Data = dst case devicelayerkind.Cache: case devicelayerkind.Exos: default: return fmt.Errorf("'%+v' is not a valid type to Unmarshal", v.Type) } return nil } // OneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolume is used to prevent that other types than drbd- luks- and storage-volume are used for a VolumeLayer type OneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolumeBCacheVolume interface { isOneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolumeBCacheVolume() } // Functions which are used if type is a correct VolumeLayer func (d *DrbdVolume) isOneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolumeBCacheVolume() { } func (d *LuksVolume) isOneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolumeBCacheVolume() { } func (d *StorageVolume) isOneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolumeBCacheVolume() { } func (d *NvmeVolume) isOneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolumeBCacheVolume() { } func (d *WritecacheVolume) isOneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolumeBCacheVolume() { } func (d *CacheVolume) isOneOfDrbdVolumeLuksVolumeStorageVolumeNvmeVolumeWritecacheVolumeCacheVolumeBCacheVolume() { } // GetResourceView returns all resources in the cluster. Filters can be set via ListOpts. func (n *ResourceService) GetResourceView(ctx context.Context, opts ...*ListOpts) ([]ResourceWithVolumes, error) { var reses []ResourceWithVolumes _, err := n.client.doGET(ctx, "/v1/view/resources", &reses, opts...) return reses, err } // GetAll returns all resources for a resource-definition func (n *ResourceService) GetAll(ctx context.Context, resName string, opts ...*ListOpts) ([]Resource, error) { var reses []Resource _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/resources", &reses, opts...) return reses, err } // Get returns information about a resource on a specific node func (n *ResourceService) Get(ctx context.Context, resName, nodeName string, opts ...*ListOpts) (Resource, error) { var res Resource _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/resources/"+nodeName, &res, opts...) return res, err } // Create is used to create a resource on a node func (n *ResourceService) Create(ctx context.Context, res ResourceCreate) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+res.Resource.Name+"/resources/"+res.Resource.NodeName, res) return err } // Modify gives the ability to modify a resource on a node func (n *ResourceService) Modify(ctx context.Context, resName, nodeName string, props GenericPropsModify) error { _, err := n.client.doPUT(ctx, "/v1/resource-definitions/"+resName+"/resources/"+nodeName, props) return err } // Delete deletes a resource on a specific node func (n *ResourceService) Delete(ctx context.Context, resName, nodeName string) error { _, err := n.client.doDELETE(ctx, "/v1/resource-definitions/"+resName+"/resources/"+nodeName, nil) return err } func (n *ResourceService) Activate(ctx context.Context, resName, nodeName string) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+resName+"/resources/"+nodeName+"/activate", nil) return err } func (n *ResourceService) Deactivate(ctx context.Context, resName, nodeName string) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+resName+"/resources/"+nodeName+"/deactivate", nil) return err } // GetVolumes lists als volumes of a resource func (n *ResourceService) GetVolumes(ctx context.Context, resName, nodeName string, opts ...*ListOpts) ([]Volume, error) { var vols []Volume _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/resources/"+nodeName+"/volumes", &vols, opts...) return vols, err } // GetVolume returns information about a specific volume defined by it resource,node and volume-number func (n *ResourceService) GetVolume(ctx context.Context, resName, nodeName string, volNr int, opts ...*ListOpts) (Volume, error) { var vol Volume _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/resources/"+nodeName+"/volumes/"+strconv.Itoa(volNr), &vol, opts...) return vol, err } // ModifyVolume modifies an existing volume with the given props func (n *ResourceService) ModifyVolume(ctx context.Context, resName, nodeName string, volNr int, props GenericPropsModify) error { u := fmt.Sprintf("/v1/resource-definitions/%s/resources/%s/volumes/%d", resName, nodeName, volNr) _, err := n.client.doPUT(ctx, u, props) return err } // Diskless toggles a resource on a node to diskless - the parameter disklesspool can be set if its needed func (n *ResourceService) Diskless(ctx context.Context, resName, nodeName, disklessPoolName string) error { u := "/v1/resource-definitions/" + resName + "/resources/" + nodeName + "/toggle-disk/diskless" if disklessPoolName != "" { u += "/" + disklessPoolName } _, err := n.client.doPUT(ctx, u, nil) return err } // Diskful toggles a resource to diskful - the parameter storagepool can be set if its needed func (n *ResourceService) Diskful(ctx context.Context, resName, nodeName, storagePoolName string, props *ToggleDiskDiskfulProps) error { u := "/v1/resource-definitions/" + resName + "/resources/" + nodeName + "/toggle-disk/diskful" if storagePoolName != "" { u += "/" + storagePoolName } _, err := n.client.doPUT(ctx, u, props) return err } // Migrate mirgates a resource from one node to another node func (n *ResourceService) Migrate(ctx context.Context, resName, fromNodeName, toNodeName, storagePoolName string) error { u := "/v1/resource-definitions/" + resName + "/resources/" + toNodeName + "/migrate-disk/" + fromNodeName if storagePoolName != "" { u += "/" + storagePoolName } _, err := n.client.doPUT(ctx, u, nil) return err } // Autoplace places a resource on your nodes autmatically func (n *ResourceService) Autoplace(ctx context.Context, resName string, apr AutoPlaceRequest) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+resName+"/autoplace", apr) return err } // GetConnections lists all resource connections if no node-names are given- if two node-names are given it shows the connection between them func (n *ResourceService) GetConnections(ctx context.Context, resName, nodeAName, nodeBName string, opts ...*ListOpts) ([]ResourceConnection, error) { var resConns []ResourceConnection u := "/v1/resource-definitions/" + resName + "/resources-connections" if nodeAName != "" && nodeBName != "" { u += fmt.Sprintf("/%s/%s", nodeAName, nodeBName) } _, err := n.client.doGET(ctx, u, &resConns, opts...) return resConns, err } // ModifyConnection allows to modify the connection between two nodes func (n *ResourceService) ModifyConnection(ctx context.Context, resName, nodeAName, nodeBName string, props GenericPropsModify) error { u := fmt.Sprintf("/v1/resource-definitions/%s/resource-connections/%s/%s", resName, nodeAName, nodeBName) _, err := n.client.doPUT(ctx, u, props) return err } // GetSnapshots lists all snapshots of a resource func (n *ResourceService) GetSnapshots(ctx context.Context, resName string, opts ...*ListOpts) ([]Snapshot, error) { var snaps []Snapshot _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/snapshots", &snaps, opts...) return snaps, err } // GetSnapshotView gets information about all snapshots func (r *ResourceService) GetSnapshotView(ctx context.Context, opts ...*ListOpts) ([]Snapshot, error) { var snaps []Snapshot _, err := r.client.doGET(ctx, "/v1/view/snapshots", &snaps, opts...) return snaps, err } // GetSnapshot returns information about a specific Snapshot by its name func (n *ResourceService) GetSnapshot(ctx context.Context, resName, snapName string, opts ...*ListOpts) (Snapshot, error) { var snap Snapshot _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/snapshots/"+snapName, &snap, opts...) return snap, err } // CreateSnapshot creates a snapshot of a resource func (n *ResourceService) CreateSnapshot(ctx context.Context, snapshot Snapshot) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+snapshot.ResourceName+"/snapshots", snapshot) return err } // DeleteSnapshot deletes a snapshot by its name. Specify nodes to only delete snapshots on specific nodes. func (n *ResourceService) DeleteSnapshot(ctx context.Context, resName, snapName string, nodes ...string) error { vals, err := query.Values(struct { Nodes []string `url:"nodes"` }{Nodes: nodes}) if err != nil { return fmt.Errorf("failed to encode node names: %w", err) } _, err = n.client.doDELETE(ctx, "/v1/resource-definitions/"+resName+"/snapshots/"+snapName+"?"+vals.Encode(), nil) return err } // RestoreSnapshot restores a snapshot on a resource func (n *ResourceService) RestoreSnapshot(ctx context.Context, origResName, snapName string, snapRestoreConf SnapshotRestore) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+origResName+"/snapshot-restore-resource/"+snapName, snapRestoreConf) return err } // RestoreVolumeDefinitionSnapshot restores a volume-definition-snapshot on a resource func (n *ResourceService) RestoreVolumeDefinitionSnapshot(ctx context.Context, origResName, snapName string, snapRestoreConf SnapshotRestore) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+origResName+"/snapshot-restore-volume-definition/"+snapName, snapRestoreConf) return err } // RollbackSnapshot rolls back a snapshot from a specific resource func (n *ResourceService) RollbackSnapshot(ctx context.Context, resName, snapName string) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+resName+"/snapshot-rollback/"+snapName, nil) return err } // EnableSnapshotShipping enables snapshot shipping for a resource func (n *ResourceService) EnableSnapshotShipping(ctx context.Context, resName string, ship SnapshotShipping) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+resName+"/snapshot-shipping", ship) return err } // ModifyDRBDProxy is used to modify drbd-proxy properties func (n *ResourceService) ModifyDRBDProxy(ctx context.Context, resName string, props DrbdProxyModify) error { _, err := n.client.doPUT(ctx, "/v1/resource-definitions/"+resName+"/drbd-proxy", props) return err } // enableDisableDRBDProxy enables or disables drbd-proxy between two nodes func (n *ResourceService) enableDisableDRBDProxy(ctx context.Context, what, resName, nodeAName, nodeBName string) error { u := fmt.Sprintf("/v1/resource-definitions/%s/drbd-proxy/%s/%s/%s", resName, what, nodeAName, nodeBName) _, err := n.client.doPOST(ctx, u, nil) return err } // EnableDRBDProxy is used to enable drbd-proxy with the rest-api call from the function enableDisableDRBDProxy func (n *ResourceService) EnableDRBDProxy(ctx context.Context, resName, nodeAName, nodeBName string) error { return n.enableDisableDRBDProxy(ctx, "enable", resName, nodeAName, nodeBName) } // DisableDRBDProxy is used to disable drbd-proxy with the rest-api call from the function enableDisableDRBDProxy func (n *ResourceService) DisableDRBDProxy(ctx context.Context, resName, nodeAName, nodeBName string) error { return n.enableDisableDRBDProxy(ctx, "disable", resName, nodeAName, nodeBName) } // QueryMaxVolumeSize finds the maximum size of a volume for a given filter func (n *ResourceService) QueryMaxVolumeSize(ctx context.Context, filter AutoSelectFilter) (MaxVolumeSizes, error) { var sizes MaxVolumeSizes _, err := n.client.doOPTIONS(ctx, "/v1/query-max-volume-size", &sizes, filter) return sizes, err } // GetSnapshotShippings gets a view of all snapshot shippings func (n *ResourceService) GetSnapshotShippings(ctx context.Context, opts ...*ListOpts) ([]SnapshotShippingStatus, error) { var shippings []SnapshotShippingStatus _, err := n.client.doGET(ctx, "/v1/view/snapshot-shippings", &shippings, opts...) return shippings, err } // GetPropsInfos gets meta information about the properties that can be set on // a resource. func (n *ResourceService) GetPropsInfos(ctx context.Context, resName string, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/resources/properties/info", &infos, opts...) return infos, err } // GetVolumeDefinitionPropsInfos gets meta information about the properties // that can be set on a volume definition. func (n *ResourceService) GetVolumeDefinitionPropsInfos(ctx context.Context, resName string, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/volume-definitions/properties/info", &infos, opts...) return infos, err } // GetVolumePropsInfos gets meta information about the properties that can be // set on a volume. func (n *ResourceService) GetVolumePropsInfos(ctx context.Context, resName, nodeName string, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/resources/"+nodeName+"/volumes/properties/info", &infos, opts...) return infos, err } // GetConnectionPropsInfos gets meta information about the properties that can // be set on a connection. func (n *ResourceService) GetConnectionPropsInfos(ctx context.Context, resName string, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resName+"/resource-connections/properties/info", &infos, opts...) return infos, err } // MakeAvailable adds a resource on a node if not already deployed. // To use a specific storage pool add the StorPoolName property and use the // storage pool name as value. If the StorPoolName property is not set, a // storage pool will be chosen automatically using the auto-placer. // To create a diskless resource you have to set the "DISKLESS" flag in the // flags list. func (n *ResourceService) MakeAvailable(ctx context.Context, resName, nodeName string, makeAvailable ResourceMakeAvailable) error { u := fmt.Sprintf("/v1/resource-definitions/%s/resources/%s/make-available", resName, nodeName) _, err := n.client.doPOST(ctx, u, makeAvailable) return err } golang-github-linbit-golinstor-0.55.0/client/resource_test.go000066400000000000000000000257121501671500700243310ustar00rootroot00000000000000package client_test import ( "encoding/json" "reflect" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/LINBIT/golinstor/client" "github.com/LINBIT/golinstor/devicelayerkind" ) func TestParse(t *testing.T) { no := false testcases := []struct { response string actual interface{} expected interface{} }{ { response: `{"name":"pvc-b5be6893-9892-4278-b2da-51a060fc4624","node_name":"demo1.linstor-days.at.linbit.com","props":{"StorPoolName":"thinpool"},"layer_object":{"children":[{"type":"STORAGE","storage":{"storage_volumes":[{"volume_number":0,"device_path":"/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000","allocated_size_kib":516096,"usable_size_kib":516096,"disk_state":"[]"}]}}],"type":"DRBD","drbd":{"drbd_resource_definition":{"peer_slots":7,"al_stripes":1,"al_stripe_size_kib":32,"port":7000,"transport_type":"IP","secret":"bNvYcSbPFPpbHZ9Gtq00","down":false},"node_id":0,"peer_slots":7,"al_stripes":1,"al_size":32,"drbd_volumes":[{"drbd_volume_definition":{"volume_number":0,"minor_number":1000},"device_path":"/dev/drbd1000","backing_device":"/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000","allocated_size_kib":512148,"usable_size_kib":512000}],"connections":{"demo2.linstor-days.at.linbit.com":{"connected":true,"message":"Connected"},"demo3.linstor-days.at.linbit.com":{"connected":false,"message":"Connecting"}},"promotion_score":10101,"may_promote":true}},"state":{"in_use":false},"uuid":"78f0d7fe-2b4d-4d5b-afb4-e1b1450c70cb","create_timestamp":1622636098831}`, actual: &client.Resource{}, expected: &client.Resource{ Name: "pvc-b5be6893-9892-4278-b2da-51a060fc4624", NodeName: "demo1.linstor-days.at.linbit.com", Props: map[string]string{ "StorPoolName": "thinpool", }, LayerObject: &client.ResourceLayer{ Children: []client.ResourceLayer{ { Type: devicelayerkind.Storage, Storage: &client.StorageResource{ StorageVolumes: []client.StorageVolume{ { VolumeNumber: 0, DevicePath: "/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000", AllocatedSizeKib: 516096, UsableSizeKib: 516096, DiskState: "[]", }, }, }, }, }, Type: devicelayerkind.Drbd, Drbd: &client.DrbdResource{ DrbdResourceDefinition: client.DrbdResourceDefinitionLayer{ PeerSlots: 7, AlStripes: 1, Port: 7000, TransportType: "IP", Secret: "bNvYcSbPFPpbHZ9Gtq00", }, DrbdVolumes: []client.DrbdVolume{ { DrbdVolumeDefinition: client.DrbdVolumeDefinition{ ResourceNameSuffix: "", VolumeNumber: 0, MinorNumber: 1000, }, DevicePath: "/dev/drbd1000", BackingDevice: "/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000", MetaDisk: "", AllocatedSizeKib: 512148, UsableSizeKib: 512000, DiskState: "", ExtMetaStorPool: "", }, }, Connections: map[string]client.DrbdConnection{ "demo2.linstor-days.at.linbit.com": { Connected: true, Message: "Connected", }, "demo3.linstor-days.at.linbit.com": { Connected: false, Message: "Connecting", }, }, PeerSlots: 7, AlStripes: 1, AlSize: 32, PromotionScore: 10101, MayPromote: true, }, }, State: &client.ResourceState{ InUse: &no, }, Uuid: "78f0d7fe-2b4d-4d5b-afb4-e1b1450c70cb", CreateTimestamp: &client.TimeStampMs{Time: time.Unix(1622636098, 831_000_000)}, }, }, { response: `{"name":"pvc-b5be6893-9892-4278-b2da-51a060fc4624","node_name":"demo1.linstor-days.at.linbit.com","props":{"StorPoolName":"thinpool"},"layer_object":{"children":[{"type":"STORAGE","storage":{"storage_volumes":[{"volume_number":0,"device_path":"/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000","allocated_size_kib":516096,"usable_size_kib":516096,"disk_state":"[]"}]}}],"type":"DRBD","drbd":{"drbd_resource_definition":{"peer_slots":7,"al_stripes":1,"al_stripe_size_kib":32,"port":7000,"transport_type":"IP","secret":"bNvYcSbPFPpbHZ9Gtq00","down":false},"node_id":0,"peer_slots":7,"al_stripes":1,"al_size":32,"drbd_volumes":[{"drbd_volume_definition":{"volume_number":0,"minor_number":1000},"device_path":"/dev/drbd1000","backing_device":"/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000","allocated_size_kib":512148,"usable_size_kib":512000}],"connections":{"demo2.linstor-days.at.linbit.com":{"connected":true,"message":"Connected"},"demo3.linstor-days.at.linbit.com":{"connected":false,"message":"Connecting"}},"promotion_score":10101,"may_promote":true}},"uuid":"78f0d7fe-2b4d-4d5b-afb4-e1b1450c70cb","create_timestamp":1622636098831,"volumes":[{"volume_number":0,"storage_pool_name":"thinpool","provider_kind":"LVM_THIN","device_path":"/dev/drbd1000","allocated_size_kib":206,"state":{"disk_state":"UpToDate"},"layer_data_list":[{"type":"DRBD","data":{"drbd_volume_definition":{"volume_number":0,"minor_number":1000},"device_path":"/dev/drbd1000","backing_device":"/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000","allocated_size_kib":512148,"usable_size_kib":512000}},{"type":"STORAGE","data":{"volume_number":0,"device_path":"/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000","allocated_size_kib":516096,"usable_size_kib":516096,"disk_state":"[]"}}],"uuid":"03b8ffd6-dbef-4745-87a0-46b4f8459e1e"}]}`, actual: &client.ResourceWithVolumes{}, expected: &client.ResourceWithVolumes{ Resource: client.Resource{ Name: "pvc-b5be6893-9892-4278-b2da-51a060fc4624", NodeName: "demo1.linstor-days.at.linbit.com", Props: map[string]string{ "StorPoolName": "thinpool", }, LayerObject: &client.ResourceLayer{ Children: []client.ResourceLayer{ { Type: devicelayerkind.Storage, Storage: &client.StorageResource{ StorageVolumes: []client.StorageVolume{ { VolumeNumber: 0, DevicePath: "/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000", AllocatedSizeKib: 516096, UsableSizeKib: 516096, DiskState: "[]", }, }, }, }, }, Type: devicelayerkind.Drbd, Drbd: &client.DrbdResource{ DrbdResourceDefinition: client.DrbdResourceDefinitionLayer{ PeerSlots: 7, AlStripes: 1, Port: 7000, TransportType: "IP", Secret: "bNvYcSbPFPpbHZ9Gtq00", }, DrbdVolumes: []client.DrbdVolume{ { DrbdVolumeDefinition: client.DrbdVolumeDefinition{ ResourceNameSuffix: "", VolumeNumber: 0, MinorNumber: 1000, }, DevicePath: "/dev/drbd1000", BackingDevice: "/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000", MetaDisk: "", AllocatedSizeKib: 512148, UsableSizeKib: 512000, DiskState: "", ExtMetaStorPool: "", }, }, Connections: map[string]client.DrbdConnection{ "demo2.linstor-days.at.linbit.com": { Connected: true, Message: "Connected", }, "demo3.linstor-days.at.linbit.com": { Connected: false, Message: "Connecting", }, }, PeerSlots: 7, AlStripes: 1, AlSize: 32, PromotionScore: 10101, MayPromote: true, }, }, Uuid: "78f0d7fe-2b4d-4d5b-afb4-e1b1450c70cb", }, CreateTimestamp: &client.TimeStampMs{Time: time.Unix(1622636098, 831_000_000)}, Volumes: []client.Volume{ { StoragePoolName: "thinpool", ProviderKind: client.LVM_THIN, DevicePath: "/dev/drbd1000", AllocatedSizeKib: 206, State: client.VolumeState{ DiskState: "UpToDate", }, LayerDataList: []client.VolumeLayer{ { Type: devicelayerkind.Drbd, Data: &client.DrbdVolume{ DrbdVolumeDefinition: client.DrbdVolumeDefinition{ MinorNumber: 1000, }, DevicePath: "/dev/drbd1000", BackingDevice: "/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000", AllocatedSizeKib: 512148, UsableSizeKib: 512000, }, }, { Type: devicelayerkind.Storage, Data: &client.StorageVolume{ DevicePath: "/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000", AllocatedSizeKib: 516096, UsableSizeKib: 516096, DiskState: "[]", }, }, }, Uuid: "03b8ffd6-dbef-4745-87a0-46b4f8459e1e", }, }, }, }, { response: `{"volume_number":0,"storage_pool_name":"thinpool","provider_kind":"LVM_THIN","device_path":"/dev/drbd1000","allocated_size_kib":206,"state":{"disk_state":"UpToDate"},"layer_data_list":[{"type":"DRBD","data":{"drbd_volume_definition":{"volume_number":0,"minor_number":1000},"device_path":"/dev/drbd1000","backing_device":"/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000","allocated_size_kib":512148,"usable_size_kib":512000}},{"type":"STORAGE","data":{"volume_number":0,"device_path":"/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000","allocated_size_kib":516096,"usable_size_kib":516096,"disk_state":"[]"}}],"uuid":"03b8ffd6-dbef-4745-87a0-46b4f8459e1e"}`, actual: &client.Volume{}, expected: &client.Volume{ StoragePoolName: "thinpool", ProviderKind: client.LVM_THIN, DevicePath: "/dev/drbd1000", AllocatedSizeKib: 206, State: client.VolumeState{ DiskState: "UpToDate", }, LayerDataList: []client.VolumeLayer{ { Type: devicelayerkind.Drbd, Data: &client.DrbdVolume{ DrbdVolumeDefinition: client.DrbdVolumeDefinition{ MinorNumber: 1000, }, DevicePath: "/dev/drbd1000", BackingDevice: "/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000", AllocatedSizeKib: 512148, UsableSizeKib: 512000, }, }, { Type: devicelayerkind.Storage, Data: &client.StorageVolume{ DevicePath: "/dev/linstor_thinpool/pvc-b5be6893-9892-4278-b2da-51a060fc4624_00000", AllocatedSizeKib: 516096, UsableSizeKib: 516096, DiskState: "[]", }, }, }, Uuid: "03b8ffd6-dbef-4745-87a0-46b4f8459e1e", }, }, } t.Parallel() for i := range testcases { tcase := &testcases[i] t.Run(reflect.TypeOf(tcase.expected).Name(), func(t *testing.T) { err := json.NewDecoder(strings.NewReader(tcase.response)).Decode(tcase.actual) if !assert.NoError(t, err) { t.FailNow() } assert.Equal(t, tcase.expected, tcase.actual) }) } } golang-github-linbit-golinstor-0.55.0/client/resourcedefinition.go000066400000000000000000000441641501671500700253450ustar00rootroot00000000000000// A REST client to interact with LINSTOR's REST API // Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client import ( "context" "encoding/json" "fmt" "net/url" "strconv" "github.com/google/go-querystring/query" "github.com/LINBIT/golinstor/clonestatus" "github.com/LINBIT/golinstor/devicelayerkind" ) // ResourceDefinitionService is a struct for the client pointer type ResourceDefinitionService struct { client *Client } // ResourceDefinition is a struct to store the information about a resource-definition type ResourceDefinition struct { Name string `json:"name,omitempty"` // External name can be used to have native resource names. If you need to store a non Linstor compatible resource name use this field and Linstor will generate a compatible name. ExternalName string `json:"external_name,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` Flags []string `json:"flags,omitempty"` LayerData []ResourceDefinitionLayer `json:"layer_data,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` // name of the linked resource group, if there is a link ResourceGroupName string `json:"resource_group_name,omitempty"` } // ResourceDefinitionCreate is a struct for holding the data needed to create a resource-defintion type ResourceDefinitionCreate struct { // drbd port for resources DrbdPort int32 `json:"drbd_port,omitempty"` // drbd resource secret DrbdSecret string `json:"drbd_secret,omitempty"` DrbdTransportType string `json:"drbd_transport_type,omitempty"` ResourceDefinition ResourceDefinition `json:"resource_definition"` } // ResourceDefinitionLayer is a struct for the storing the layertype of a resource-defintion type ResourceDefinitionLayer struct { Type devicelayerkind.DeviceLayerKind `json:"type,omitempty"` Data *DrbdResourceDefinitionLayer `json:"data,omitempty"` } // DrbdResourceDefinitionLayer is a struct which contains the information about the layertype of a resource-definition on drbd level type DrbdResourceDefinitionLayer struct { ResourceNameSuffix string `json:"resource_name_suffix,omitempty"` PeerSlots int32 `json:"peer_slots,omitempty"` AlStripes int64 `json:"al_stripes,omitempty"` // used drbd port for this resource Port int32 `json:"port,omitempty"` TransportType string `json:"transport_type,omitempty"` // drbd resource secret Secret string `json:"secret,omitempty"` Down bool `json:"down,omitempty"` } // VolumeDefinitionCreate is a struct used for creating volume-definitions type VolumeDefinitionCreate struct { VolumeDefinition VolumeDefinition `json:"volume_definition"` DrbdMinorNumber int32 `json:"drbd_minor_number,omitempty"` } // VolumeDefinition is a struct which is used to store volume-definition properties type VolumeDefinition struct { VolumeNumber *int32 `json:"volume_number,omitempty"` // Size of the volume in Kibi. SizeKib uint64 `json:"size_kib"` // A string to string property map. Props map[string]string `json:"props,omitempty"` Flags []string `json:"flags,omitempty"` LayerData []VolumeDefinitionLayer `json:"layer_data,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` } type VolumeDefinitionModify struct { SizeKib uint64 `json:"size_kib,omitempty"` GenericPropsModify // To add a flag just specify the flag name, to remove a flag prepend it with a '-'. Flags: * GROSS_SIZE Flags []string `json:"flags,omitempty"` } // VolumeDefinitionLayer is a struct for the layer-type of a volume-definition type VolumeDefinitionLayer struct { Type devicelayerkind.DeviceLayerKind `json:"type"` Data OneOfDrbdVolumeDefinition `json:"data,omitempty"` } // DrbdVolumeDefinition is a struct containing volume-definition on drbd level type DrbdVolumeDefinition struct { ResourceNameSuffix string `json:"resource_name_suffix,omitempty"` VolumeNumber int32 `json:"volume_number,omitempty"` MinorNumber int32 `json:"minor_number,omitempty"` } type ResourceDefinitionCloneRequest struct { Name string `json:"name,omitempty"` ExternalName string `json:"external_name,omitempty"` UseZfsClone bool `json:"use_zfs_clone,omitempty"` LayerList []devicelayerkind.DeviceLayerKind `json:"layer_list,omitempty"` VolumePassphrases []string `json:"volume_passphrases,omitempty"` ResourceGroup string `json:"resource_group,omitempty"` } type ResourceDefinitionCloneStarted struct { // Path for clone status Location string `json:"location"` // name of the source resource SourceName string `json:"source_name"` // name of the clone resource CloneName string `json:"clone_name"` Messages *[]ApiCallRc `json:"messages,omitempty"` } type ResourceDefinitionCloneStatus struct { Status clonestatus.CloneStatus `json:"status"` } type ResourceDefinitionSyncStatus struct { SyncedOnAll bool `json:"synced_on_all"` } // custom code type ResourceDefinitionWithVolumeDefinition struct { ResourceDefinition VolumeDefinitions []VolumeDefinition `json:"volume_definitions,omitempty"` } type RDGetAllRequest struct { // ResourceDefinitions filters the returned resource definitions by the given names ResourceDefinitions []string `url:"resource_definitions,omitempty"` // Props filters the returned resource definitions on their property values (uses key=value syntax) Props []string `url:"props,omitempty"` Offset int `url:"offset,omitempty"` Limit int `url:"offset,omitempty"` // WithVolumeDefinitions, if set to true, LINSTOR will also include volume definitions in the response. WithVolumeDefinitions bool `url:"with_volume_definitions,omitempty"` } // ResourceDefinitionProvider acts as an abstraction for a // ResourceDefinitionService. It can be swapped out for another // ResourceDefinitionService implementation, for example for testing. type ResourceDefinitionProvider interface { // GetAll lists all resource-definitions GetAll(ctx context.Context, request RDGetAllRequest) ([]ResourceDefinitionWithVolumeDefinition, error) // Get return information about a resource-defintion Get(ctx context.Context, resDefName string, opts ...*ListOpts) (ResourceDefinition, error) // Create adds a new resource-definition Create(ctx context.Context, resDef ResourceDefinitionCreate) error // Modify allows to modify a resource-definition Modify(ctx context.Context, resDefName string, props GenericPropsModify) error // Delete completely deletes a resource-definition Delete(ctx context.Context, resDefName string) error // GetVolumeDefinitions returns all volume-definitions of a resource-definition GetVolumeDefinitions(ctx context.Context, resDefName string, opts ...*ListOpts) ([]VolumeDefinition, error) // GetVolumeDefinition shows the properties of a specific volume-definition GetVolumeDefinition(ctx context.Context, resDefName string, volNr int, opts ...*ListOpts) (VolumeDefinition, error) // CreateVolumeDefinition adds a volume-definition to a resource-definition. Only the size is required. CreateVolumeDefinition(ctx context.Context, resDefName string, volDef VolumeDefinitionCreate) error // ModifyVolumeDefinition give the abilty to modify a specific volume-definition ModifyVolumeDefinition(ctx context.Context, resDefName string, volNr int, props VolumeDefinitionModify) error // DeleteVolumeDefinition deletes a specific volume-definition DeleteVolumeDefinition(ctx context.Context, resDefName string, volNr int) error // GetPropsInfos gets meta information about the properties that can be // set on a resource definition. GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) // GetDRBDProxyPropsInfos gets meta information about the properties // that can be set on a resource definition for drbd proxy. GetDRBDProxyPropsInfos(ctx context.Context, resDefName string, opts ...*ListOpts) ([]PropsInfo, error) // AttachExternalFile adds an external file to the resource definition. This // means that the file will be deployed to every node the resource is deployed on. AttachExternalFile(ctx context.Context, resDefName string, filePath string) error // DetachExternalFile removes a binding between an external file and a resource definition. // This means that the file will no longer be deployed on every node the resource // is deployed on. DetachExternalFile(ctx context.Context, resDefName string, filePath string) error // Clone starts cloning a resource definition and all resources using a method optimized for the storage driver. Clone(ctx context.Context, srcResDef string, request ResourceDefinitionCloneRequest) (ResourceDefinitionCloneStarted, error) // CloneStatus fetches the current status of a clone operation started by Clone. CloneStatus(ctx context.Context, srcResDef, targetResDef string) (ResourceDefinitionCloneStatus, error) // SyncStatus checks if a resource is currently in sync on all nodes SyncStatus(ctx context.Context, resDef string) (ResourceDefinitionSyncStatus, error) } var _ ResourceDefinitionProvider = &ResourceDefinitionService{} // resourceDefinitionLayerIn is a struct for resource-definitions type resourceDefinitionLayerIn struct { Type devicelayerkind.DeviceLayerKind `json:"type,omitempty"` Data json.RawMessage `json:"data,omitempty"` } // UnmarshalJSON is needed for the unmarshal interface for ResourceDefinitionLayer types func (rd *ResourceDefinitionLayer) UnmarshalJSON(b []byte) error { var rdIn resourceDefinitionLayerIn if err := json.Unmarshal(b, &rdIn); err != nil { return err } rd.Type = rdIn.Type switch rd.Type { case devicelayerkind.Drbd: dst := new(DrbdResourceDefinitionLayer) if rdIn.Data != nil { if err := json.Unmarshal(rdIn.Data, &dst); err != nil { return err } } rd.Data = dst case devicelayerkind.Luks, devicelayerkind.Storage, devicelayerkind.Nvme, devicelayerkind.Writecache, devicelayerkind.Cache, devicelayerkind.Exos: // valid types, but do not set data default: return fmt.Errorf("'%+v' is not a valid type to Unmarshal", rd.Type) } return nil } // volumeDefinitionLayerIn is a struct for volume-defintion-layers type volumeDefinitionLayerIn struct { Type devicelayerkind.DeviceLayerKind `json:"type,omitempty"` Data json.RawMessage `json:"data,omitempty"` } // UnmarshalJSON is needed for the unmarshal interface for VolumeDefinitionLayer types func (vd *VolumeDefinitionLayer) UnmarshalJSON(b []byte) error { var vdIn volumeDefinitionLayerIn if err := json.Unmarshal(b, &vdIn); err != nil { return err } vd.Type = vdIn.Type switch vd.Type { case devicelayerkind.Drbd: dst := new(DrbdVolumeDefinition) if vdIn.Data != nil { if err := json.Unmarshal(vdIn.Data, &dst); err != nil { return err } } vd.Data = dst case devicelayerkind.Luks, devicelayerkind.Storage, devicelayerkind.Nvme, devicelayerkind.Writecache, devicelayerkind.Cache, devicelayerkind.Exos: // valid types, but do not set data default: return fmt.Errorf("'%+v' is not a valid type to Unmarshal", vd.Type) } return nil } // OneOfDrbdVolumeDefinition is used to prevent other layertypes than drbd-volume-defintion type OneOfDrbdVolumeDefinition interface { isOneOfDrbdVolumeDefinition() } // Function used if volume-defintion-layertype is correct func (d *DrbdVolumeDefinition) isOneOfDrbdVolumeDefinition() {} // GetAll lists all resource-definitions func (n *ResourceDefinitionService) GetAll(ctx context.Context, request RDGetAllRequest) ([]ResourceDefinitionWithVolumeDefinition, error) { val, err := query.Values(request) if err != nil { return nil, err } var resDefs []ResourceDefinitionWithVolumeDefinition _, err = n.client.doGET(ctx, "/v1/resource-definitions?"+val.Encode(), &resDefs) return resDefs, err } // Get return information about a resource-defintion func (n *ResourceDefinitionService) Get(ctx context.Context, resDefName string, opts ...*ListOpts) (ResourceDefinition, error) { var resDef ResourceDefinition _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resDefName, &resDef, opts...) return resDef, err } // Create adds a new resource-definition func (n *ResourceDefinitionService) Create(ctx context.Context, resDef ResourceDefinitionCreate) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions", resDef) return err } // Modify allows to modify a resource-definition func (n *ResourceDefinitionService) Modify(ctx context.Context, resDefName string, props GenericPropsModify) error { _, err := n.client.doPUT(ctx, "/v1/resource-definitions/"+resDefName, props) return err } // Delete completely deletes a resource-definition func (n *ResourceDefinitionService) Delete(ctx context.Context, resDefName string) error { _, err := n.client.doDELETE(ctx, "/v1/resource-definitions/"+resDefName, nil) return err } // GetVolumeDefinitions returns all volume-definitions of a resource-definition func (n *ResourceDefinitionService) GetVolumeDefinitions(ctx context.Context, resDefName string, opts ...*ListOpts) ([]VolumeDefinition, error) { var volDefs []VolumeDefinition _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resDefName+"/volume-definitions", &volDefs, opts...) return volDefs, err } // GetVolumeDefinition shows the properties of a specific volume-definition func (n *ResourceDefinitionService) GetVolumeDefinition(ctx context.Context, resDefName string, volNr int, opts ...*ListOpts) (VolumeDefinition, error) { var volDef VolumeDefinition _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resDefName+"/volume-definitions/"+strconv.Itoa(volNr), &volDef, opts...) return volDef, err } // CreateVolumeDefinition adds a volume-definition to a resource-definition. Only the size is required. func (n *ResourceDefinitionService) CreateVolumeDefinition(ctx context.Context, resDefName string, volDef VolumeDefinitionCreate) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+resDefName+"/volume-definitions", volDef) return err } // ModifyVolumeDefinition give the abilty to modify a specific volume-definition func (n *ResourceDefinitionService) ModifyVolumeDefinition(ctx context.Context, resDefName string, volNr int, props VolumeDefinitionModify) error { _, err := n.client.doPUT(ctx, "/v1/resource-definitions/"+resDefName+"/volume-definitions/"+strconv.Itoa(volNr), props) return err } // DeleteVolumeDefinition deletes a specific volume-definition func (n *ResourceDefinitionService) DeleteVolumeDefinition(ctx context.Context, resDefName string, volNr int) error { _, err := n.client.doDELETE(ctx, "/v1/resource-definitions/"+resDefName+"/volume-definitions/"+strconv.Itoa(volNr), nil) return err } // GetPropsInfos gets meta information about the properties that can be set on // a resource definition. func (n *ResourceDefinitionService) GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/resource-definitions/properties/info", &infos, opts...) return infos, err } // GetDRBDProxyPropsInfos gets meta information about the properties that can // be set on a resource definition for drbd proxy. func (n *ResourceDefinitionService) GetDRBDProxyPropsInfos(ctx context.Context, resDefName string, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resDefName+"/drbd-proxy/properties/info", &infos, opts...) return infos, err } // AttachExternalFile adds an external file to the resource definition. This // means that the file will be deployed to every node the resource is deployed on. func (n *ResourceDefinitionService) AttachExternalFile(ctx context.Context, resDefName string, filePath string) error { _, err := n.client.doPOST(ctx, "/v1/resource-definitions/"+resDefName+"/files/"+url.QueryEscape(filePath), nil) return err } // DetachExternalFile removes a binding between an external file and a resource definition. // This means that the file will no longer be deployed on every node the resource // is deployed on. func (n *ResourceDefinitionService) DetachExternalFile(ctx context.Context, resDefName string, filePath string) error { _, err := n.client.doDELETE(ctx, "/v1/resource-definitions/"+resDefName+"/files/"+url.QueryEscape(filePath), nil) return err } // Clone starts cloning a resource definition and all resources using a method optimized for the storage driver. func (n *ResourceDefinitionService) Clone(ctx context.Context, srcResDef string, request ResourceDefinitionCloneRequest) (ResourceDefinitionCloneStarted, error) { var resp ResourceDefinitionCloneStarted req, err := n.client.newRequest("POST", "/v1/resource-definitions/"+srcResDef+"/clone", request) if err != nil { return ResourceDefinitionCloneStarted{}, err } _, err = n.client.doJSON(ctx, req, &resp) if err != nil { return ResourceDefinitionCloneStarted{}, err } return resp, nil } // CloneStatus fetches the current status of a clone operation started by Clone. func (n *ResourceDefinitionService) CloneStatus(ctx context.Context, srcResDef, targetResDef string) (ResourceDefinitionCloneStatus, error) { var status ResourceDefinitionCloneStatus _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+srcResDef+"/clone/"+targetResDef, &status) return status, err } // SyncStatus checks if a resource is currently in sync on all nodes func (n *ResourceDefinitionService) SyncStatus(ctx context.Context, resDef string) (ResourceDefinitionSyncStatus, error) { var status ResourceDefinitionSyncStatus _, err := n.client.doGET(ctx, "/v1/resource-definitions/"+resDef+"/sync-status", &status) return status, err } golang-github-linbit-golinstor-0.55.0/client/resourcegroup.go000066400000000000000000000304741501671500700243500ustar00rootroot00000000000000// A REST client to interact with LINSTOR's REST API // Copyright (C) LINBIT HA-Solutions GmbH // All Rights Reserved. // Author: Roland Kammerer // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package client import ( "context" "net/http" "strconv" ) type ResourceGroup struct { Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` SelectFilter AutoSelectFilter `json:"select_filter,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` } type ResourceGroupModify struct { Description string `json:"description,omitempty"` // A string to string property map. OverrideProps map[string]string `json:"override_props,omitempty"` DeleteProps []string `json:"delete_props,omitempty"` DeleteNamespaces []string `json:"delete_namespaces,omitempty"` SelectFilter AutoSelectFilter `json:"select_filter,omitempty"` } type ResourceGroupSpawn struct { // name of the resulting resource-definition ResourceDefinitionName string `json:"resource_definition_name"` // External name can be used to have native resource names. If you need to store a non Linstor compatible resource name use this field and Linstor will generate a compatible name. ResourceDefinitionExternalName string `json:"resource_definition_external_name,omitempty"` // sizes (in kib) of the resulting volume-definitions VolumeSizes []int64 `json:"volume_sizes,omitempty"` SelectFilter AutoSelectFilter `json:"select_filter,omitempty"` // If false, the length of the vlm_sizes has to match the number of volume-groups or an error is returned. If true and there are more vlm_sizes than volume-groups, the additional volume-definitions will simply have no pre-set properties (i.e. \"empty\" volume-definitions) If true and there are less vlm_sizes than volume-groups, the additional volume-groups won't be used. If the count of vlm_sizes matches the number of volume-groups, this \"partial\" parameter has no effect. Partial bool `json:"partial,omitempty"` // If true, the spawn command will only create the resource-definition with the volume-definitions but will not perform an auto-place, even if it is configured. DefinitionsOnly bool `json:"definitions_only,omitempty"` } type ResourceGroupAdjust struct { SelectFilter *AutoSelectFilter `json:"select_filter,omitempty"` } type VolumeGroup struct { VolumeNumber int32 `json:"volume_number,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` // unique object id Uuid string `json:"uuid,omitempty"` Flags []string `json:"flags,omitempty"` } type VolumeGroupModify struct { // A string to string property map. OverrideProps map[string]string `json:"override_props,omitempty"` // To add a flag just specify the flag name, to remove a flag prepend it with a '-'. Flags: * GROSS_SIZE Flags []string `json:"flags,omitempty"` DeleteProps []string `json:"delete_props,omitempty"` DeleteNamespaces []string `json:"delete_namespaces,omitempty"` } // QuerySizeInfoResponseSpaceInfo contains information returned from the QuerySizeInfo API call type QuerySizeInfoResponseSpaceInfo struct { MaxVlmSizeInKib int64 `json:"max_vlm_size_in_kib"` AvailableSizeInKib *int64 `json:"available_size_in_kib,omitempty"` CapacityInKib *int64 `json:"capacity_in_kib,omitempty"` DefaultMaxOversubscriptionRatio *float64 `json:"default_max_oversubscription_ratio,omitempty"` NextSpawnResult []QuerySizeInfoSpawnResult `json:"next_spawn_result,omitempty"` } // QuerySizeInfoSpawnResult describes the result of a potential spawn operation type QuerySizeInfoSpawnResult struct { NodeName string `json:"node_name"` StorPoolName string `json:"stor_pool_name"` } // QuerySizeInfoRequest is the request object for the QuerySizeInfo API call type QuerySizeInfoRequest struct { SelectFilter *AutoSelectFilter `json:"select_filter,omitempty"` } type QuerySizeInfoResponse struct { SpaceInfo *QuerySizeInfoResponseSpaceInfo `json:"space_info,omitempty"` Reports []ApiCallRc `json:"reports,omitempty"` } // custom code // ResourceGroupProvider acts as an abstraction for a // ResourceGroupService. It can be swapped out for another // ResourceGroupService implementation, for example for testing. type ResourceGroupProvider interface { // GetAll lists all resource-groups GetAll(ctx context.Context, opts ...*ListOpts) ([]ResourceGroup, error) // Get return information about a resource-defintion Get(ctx context.Context, resGrpName string, opts ...*ListOpts) (ResourceGroup, error) // Create adds a new resource-group Create(ctx context.Context, resGrp ResourceGroup) error // Modify allows to modify a resource-group Modify(ctx context.Context, resGrpName string, props ResourceGroupModify) error // Delete deletes a resource-group Delete(ctx context.Context, resGrpName string) error // Spawn creates a new resource-definition and auto-deploys if configured to do so Spawn(ctx context.Context, resGrpName string, resGrpSpwn ResourceGroupSpawn) error // GetVolumeGroups lists all volume-groups for a resource-group GetVolumeGroups(ctx context.Context, resGrpName string, opts ...*ListOpts) ([]VolumeGroup, error) // GetVolumeGroup lists a volume-group for a resource-group GetVolumeGroup(ctx context.Context, resGrpName string, volNr int, opts ...*ListOpts) (VolumeGroup, error) // Create adds a new volume-group to a resource-group CreateVolumeGroup(ctx context.Context, resGrpName string, volGrp VolumeGroup) error // Modify allows to modify a volume-group of a resource-group ModifyVolumeGroup(ctx context.Context, resGrpName string, volNr int, props VolumeGroupModify) error DeleteVolumeGroup(ctx context.Context, resGrpName string, volNr int) error // GetPropsInfos gets meta information about the properties that can be // set on a resource group. GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) // GetVolumeGroupPropsInfos gets meta information about the properties // that can be set on a resource group. GetVolumeGroupPropsInfos(ctx context.Context, resGrpName string, opts ...*ListOpts) ([]PropsInfo, error) // Adjust all resource-definitions (calls autoplace for) of the given resource-group Adjust(ctx context.Context, resGrpName string, adjust ResourceGroupAdjust) error // AdjustAll adjusts all resource-definitions (calls autoplace) according to their associated resource group. AdjustAll(ctx context.Context, adjust ResourceGroupAdjust) error // QuerySizeInfo returns information about the space available in a resource group QuerySizeInfo(ctx context.Context, resGrpName string, req QuerySizeInfoRequest) (QuerySizeInfoResponse, error) } var _ ResourceGroupProvider = &ResourceGroupService{} // ResourceGroupService is the service that deals with resource group related tasks. type ResourceGroupService struct { client *Client } // GetAll lists all resource-groups func (n *ResourceGroupService) GetAll(ctx context.Context, opts ...*ListOpts) ([]ResourceGroup, error) { var resGrps []ResourceGroup _, err := n.client.doGET(ctx, "/v1/resource-groups", &resGrps, opts...) return resGrps, err } // Get return information about a resource-defintion func (n *ResourceGroupService) Get(ctx context.Context, resGrpName string, opts ...*ListOpts) (ResourceGroup, error) { var resGrp ResourceGroup _, err := n.client.doGET(ctx, "/v1/resource-groups/"+resGrpName, &resGrp, opts...) return resGrp, err } // Create adds a new resource-group func (n *ResourceGroupService) Create(ctx context.Context, resGrp ResourceGroup) error { _, err := n.client.doPOST(ctx, "/v1/resource-groups", resGrp) return err } // Modify allows to modify a resource-group func (n *ResourceGroupService) Modify(ctx context.Context, resGrpName string, props ResourceGroupModify) error { _, err := n.client.doPUT(ctx, "/v1/resource-groups/"+resGrpName, props) return err } // Delete deletes a resource-group func (n *ResourceGroupService) Delete(ctx context.Context, resGrpName string) error { _, err := n.client.doDELETE(ctx, "/v1/resource-groups/"+resGrpName, nil) return err } // Spawn creates a new resource-definition and auto-deploys if configured to do so func (n *ResourceGroupService) Spawn(ctx context.Context, resGrpName string, resGrpSpwn ResourceGroupSpawn) error { _, err := n.client.doPOST(ctx, "/v1/resource-groups/"+resGrpName+"/spawn", resGrpSpwn) return err } // GetVolumeGroups lists all volume-groups for a resource-group func (n *ResourceGroupService) GetVolumeGroups(ctx context.Context, resGrpName string, opts ...*ListOpts) ([]VolumeGroup, error) { var volGrps []VolumeGroup _, err := n.client.doGET(ctx, "/v1/resource-groups/"+resGrpName+"/volume-groups", &volGrps, opts...) return volGrps, err } // GetVolumeGroup lists a volume-group for a resource-group func (n *ResourceGroupService) GetVolumeGroup(ctx context.Context, resGrpName string, volNr int, opts ...*ListOpts) (VolumeGroup, error) { var volGrp VolumeGroup _, err := n.client.doGET(ctx, "/v1/resource-groups/"+resGrpName+"/volume-groups/"+strconv.Itoa(volNr), &volGrp, opts...) return volGrp, err } // Create adds a new volume-group to a resource-group func (n *ResourceGroupService) CreateVolumeGroup(ctx context.Context, resGrpName string, volGrp VolumeGroup) error { _, err := n.client.doPOST(ctx, "/v1/resource-groups/"+resGrpName+"/volume-groups", volGrp) return err } // Modify allows to modify a volume-group of a resource-group func (n *ResourceGroupService) ModifyVolumeGroup(ctx context.Context, resGrpName string, volNr int, props VolumeGroupModify) error { _, err := n.client.doPUT(ctx, "/v1/resource-groups/"+resGrpName+"/volume-groups/"+strconv.Itoa(volNr), props) return err } func (n *ResourceGroupService) DeleteVolumeGroup(ctx context.Context, resGrpName string, volNr int) error { _, err := n.client.doDELETE(ctx, "/v1/resource-groups/"+resGrpName+"/volume-groups/"+strconv.Itoa(volNr), nil) return err } // GetPropsInfos gets meta information about the properties that can be set on // a resource group. func (n *ResourceGroupService) GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/resource-groups/properties/info", &infos, opts...) return infos, err } // GetVolumeGroupPropsInfos gets meta information about the properties that can // be set on a resource group. func (n *ResourceGroupService) GetVolumeGroupPropsInfos(ctx context.Context, resGrpName string, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := n.client.doGET(ctx, "/v1/resource-groups/"+resGrpName+"/volume-groups/properties/info", &infos, opts...) return infos, err } // Adjust all resource-definitions (calls autoplace for) of the given resource-group func (n *ResourceGroupService) Adjust(ctx context.Context, resGrpName string, adjust ResourceGroupAdjust) error { _, err := n.client.doPOST(ctx, "/v1/resource-groups/"+resGrpName+"/adjust", adjust) return err } // AdjustAll adjusts all resource-definitions (calls autoplace) according to their associated resource group. func (n *ResourceGroupService) AdjustAll(ctx context.Context, adjust ResourceGroupAdjust) error { _, err := n.client.doPOST(ctx, "/v1/resource-groups/adjustall", adjust) return err } // QuerySizeInfo returns information about the space available in a resource group func (n *ResourceGroupService) QuerySizeInfo(ctx context.Context, resGrpName string, req QuerySizeInfoRequest) (QuerySizeInfoResponse, error) { var resp QuerySizeInfoResponse httpReq, err := n.client.newRequest(http.MethodPost, "/v1/resource-groups/"+resGrpName+"/query-size-info", req) if err != nil { return resp, err } _, err = n.client.doJSON(ctx, httpReq, &resp) return resp, err } golang-github-linbit-golinstor-0.55.0/client/sse.go000066400000000000000000000050261501671500700222310ustar00rootroot00000000000000package client import ( "context" "encoding/json" "github.com/donovanhide/eventsource" ) type EventMayPromoteChange struct { ResourceName string `json:"resource_name,omitempty"` NodeName string `json:"node_name,omitempty"` MayPromote bool `json:"may_promote,omitempty"` } // custom code // EventProvider acts as an abstraction for an EventService. It can be swapped // out for another EventService implementation, for example for testing. type EventProvider interface { // DRBDPromotion is used to subscribe to LINSTOR DRBD Promotion events DRBDPromotion(ctx context.Context, lastEventId string) (*DRBDMayPromoteStream, error) } const mayPromoteChange = "may-promote-change" // EventService is the service that deals with LINSTOR server side event streams. type EventService struct { client *Client } // DRBDMayPromoteStream is a struct that contains a channel of EventMayPromoteChange events // It has a Close() method that needs to be called/defered. type DRBDMayPromoteStream struct { Events chan EventMayPromoteChange stream *eventsource.Stream } // Close is used to close the underlying stream and all Go routines func (dmp *DRBDMayPromoteStream) Close() { dmp.stream.Close() } // suscribe handles stream creation, event splitting, and context cancelation func (e *EventService) subscribe(ctx context.Context, url, event, lastEventId string) (*eventsource.Stream, chan interface{}, error) { stream, err := e.client.doEvent(ctx, url, lastEventId) if err != nil { return nil, nil, err } ch := make(chan interface{}) go func() { defer close(ch) for { select { case ev, ok := <-stream.Events: if !ok { // most likely someone called Close() return } if ev.Event() == event { switch event { case mayPromoteChange: var empc EventMayPromoteChange if err := json.Unmarshal([]byte(ev.Data()), &empc); err == nil { ch <- empc } } } case <-ctx.Done(): return } } }() return stream, ch, nil } // DRBDPromotion is used to subscribe to LINSTOR DRBD Promotion events func (e *EventService) DRBDPromotion(ctx context.Context, lastEventId string) (*DRBDMayPromoteStream, error) { stream, ch, err := e.subscribe(ctx, "/v1/events/drbd/promotion", mayPromoteChange, lastEventId) if err != nil { return nil, err } empch := make(chan EventMayPromoteChange) go func() { defer close(empch) for ev := range ch { if e, ok := ev.(EventMayPromoteChange); ok { empch <- e } } }() return &DRBDMayPromoteStream{ Events: empch, stream: stream, }, nil } golang-github-linbit-golinstor-0.55.0/client/storagepooldefinition.go000066400000000000000000000071301501671500700260440ustar00rootroot00000000000000package client import "context" // StoragePoolDefinition represents a storage pool definition in LINSTOR type StoragePoolDefinition struct { StoragePoolName string `json:"storage_pool_name,omitempty"` // A string to string property map. Props map[string]string `json:"props,omitempty"` } // StoragePoolDefinitionModify holds properties of a storage pool definition to modify such a definition. type StoragePoolDefinitionModify struct { GenericPropsModify } // custom code // StoragePoolDefinitionProvider acts as an abstraction for a // StoragePoolDefinitionService. It can be swapped out for another // StoragePoolDefinitionService implementation, for example for testing. type StoragePoolDefinitionProvider interface { // GetAll gets information for all existing storage pool definitions. GetAll(ctx context.Context, opts ...*ListOpts) ([]StoragePoolDefinition, error) // Get gets information for a particular storage pool definition. Get(ctx context.Context, spdName string, opts ...*ListOpts) (StoragePoolDefinition, error) // Create creates a new storage pool definition Create(ctx context.Context, spd StoragePoolDefinition) error // Modify modifies the given storage pool definition and sets/deletes the given properties. Modify(ctx context.Context, spdName string, props StoragePoolDefinitionModify) error // Delete deletes the given storage pool definition. Delete(ctx context.Context, spdName string) error // GetPropsInfos gets meta information about the properties that can be // set on a storage pool definition. GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) } var _ StoragePoolDefinitionProvider = &StoragePoolDefinitionService{} // StoragePoolDefinitionService is the service that deals with storage pool definition related tasks. type StoragePoolDefinitionService struct { client *Client } // GetAll gets information for all existing storage pool definitions. func (s *StoragePoolDefinitionService) GetAll(ctx context.Context, opts ...*ListOpts) ([]StoragePoolDefinition, error) { var spds []StoragePoolDefinition _, err := s.client.doGET(ctx, "/v1/storage-pool-definitions", &spds, opts...) return spds, err } // Get gets information for a particular storage pool definition. func (s *StoragePoolDefinitionService) Get(ctx context.Context, spdName string, opts ...*ListOpts) (StoragePoolDefinition, error) { var spd StoragePoolDefinition _, err := s.client.doGET(ctx, "/v1/storage-pool-definitions/"+spdName, &spd, opts...) return spd, err } // Create creates a new storage pool definition func (s *StoragePoolDefinitionService) Create(ctx context.Context, spd StoragePoolDefinition) error { _, err := s.client.doPOST(ctx, "/v1/storage-pool-definitions", spd) return err } // Modify modifies the given storage pool definition and sets/deletes the given properties. func (s *StoragePoolDefinitionService) Modify(ctx context.Context, spdName string, props StoragePoolDefinitionModify) error { _, err := s.client.doPUT(ctx, "/v1/storage-pool-definitions/"+spdName, props) return err } // Delete deletes the given storage pool definition. func (s *StoragePoolDefinitionService) Delete(ctx context.Context, spdName string) error { _, err := s.client.doDELETE(ctx, "/v1/storage-pool-definitions/"+spdName, nil) return err } // GetPropsInfos gets meta information about the properties that can be set on // a storage pool definition. func (s *StoragePoolDefinitionService) GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) { var infos []PropsInfo _, err := s.client.doGET(ctx, "/v1/storage-pool-definitions/properties/info", &infos, opts...) return infos, err } golang-github-linbit-golinstor-0.55.0/client/timestamp.go000066400000000000000000000010451501671500700234370ustar00rootroot00000000000000package client import ( "encoding/json" "net/url" "strconv" "time" ) type TimeStampMs struct { time.Time } func (t *TimeStampMs) UnmarshalJSON(s []byte) (err error) { r := string(s) q, err := strconv.ParseInt(r, 10, 64) if err != nil { return err } t.Time = time.Unix(q/1000, (q%1000)*1_000_000) return nil } func (t TimeStampMs) MarshalJSON() ([]byte, error) { return json.Marshal(t.Time.Unix() * 1000) } func (t TimeStampMs) EncodeValues(key string, v *url.Values) error { v.Add(key, t.Format("20060102_150405")) return nil } golang-github-linbit-golinstor-0.55.0/client/vendor.go000066400000000000000000000140101501671500700227250ustar00rootroot00000000000000package client import ( "context" "fmt" ) // copy & paste from generated code // ExosConnectionMap struct for ExosConnectionMap type ExosConnectionMap struct { NodeName string `json:"node_name,omitempty"` EnclosureName string `json:"enclosure_name,omitempty"` Connections []string `json:"connections,omitempty"` } // ExosDefaults Default settings for EXOS enclosures type ExosDefaults struct { Username string `json:"username,omitempty"` UsernameEnv string `json:"username_env,omitempty"` Password string `json:"password,omitempty"` PasswordEnv string `json:"password_env,omitempty"` } // ExosDefaultsModifyAllOf struct for ExosDefaultsModifyAllOf type ExosDefaultsModifyAllOf struct { // A list of keys to unset. The keys have to exist in ExosDefaults UnsetKeys []string `json:"unset_keys,omitempty"` } // ExosDefaultsModify struct for ExosDefaultsModify type ExosDefaultsModify struct { Username string `json:"username,omitempty"` UsernameEnv string `json:"username_env,omitempty"` Password string `json:"password,omitempty"` PasswordEnv string `json:"password_env,omitempty"` // A list of keys to unset. The keys have to exist in ExosDefaults UnsetKeys []string `json:"unset_keys,omitempty"` } // ExosEnclosureEvent EXOS event type ExosEnclosureEvent struct { Severity string `json:"severity,omitempty"` EventId string `json:"event_id,omitempty"` Controller string `json:"controller,omitempty"` TimeStamp string `json:"time_stamp,omitempty"` TimeStampNumeric int64 `json:"time_stamp_numeric,omitempty"` Message string `json:"message,omitempty"` AdditionalInformation string `json:"additional_information,omitempty"` RecommendedAction string `json:"recommended_action,omitempty"` } // ExosEnclosure EXOS enclosure type ExosEnclosure struct { Name string `json:"name,omitempty"` CtrlAIp string `json:"ctrl_a_ip,omitempty"` CtrlBIp string `json:"ctrl_b_ip,omitempty"` Username string `json:"username,omitempty"` UsernameEnv string `json:"username_env,omitempty"` Password string `json:"password,omitempty"` PasswordEnv string `json:"password_env,omitempty"` } // ExosEnclosureHealth EXOS enclosure name, controller IPs and health status type ExosEnclosureHealth struct { Name string `json:"name,omitempty"` CtrlAIp string `json:"ctrl_a_ip,omitempty"` CtrlBIp string `json:"ctrl_b_ip,omitempty"` Health string `json:"health,omitempty"` HealthReason string `json:"health_reason,omitempty"` } // custom code type VendorProvider interface { // GetExosDefaults lists default settings for all EXOS enclosures GetExosDefaults(ctx context.Context) (ExosDefaults, error) // ModifyExosDefaults sets or modifies default username / password for EXOS enclosures ModifyExosDefaults(ctx context.Context, defaults ExosDefaultsModify) error // GetExosEnclosures lists EXOS enclosures including controller IP and health status GetExosEnclosures(ctx context.Context, noCache bool) ([]ExosEnclosure, error) // CreateExosEnclosure creates a new enclosure unless it already exists CreateExosEnclosure(ctx context.Context, enclosure ExosEnclosure) error // ModifyExosEnclosure modifies an existing enclosure ModifyExosEnclosure(ctx context.Context, name string, enclosure ExosEnclosure) error // DeleteExosEnclosure deletes an existing enclosure DeleteExosEnclosure(ctx context.Context, name string) error // GetExosEvents lists the most current "count" events GetExosEvents(ctx context.Context, name string, count int32) ([]ExosEnclosureEvent, error) // GetExosConnectionMap lists the connection-mesh of EXOS Ports to LINSTOR Nodes GetExosConnectionMap(ctx context.Context) (ExosConnectionMap, error) } type VendorService struct { client *Client } // GetExosDefaults lists default settings for all EXOS enclosures func (s *VendorService) GetExosDefaults(ctx context.Context) (ExosDefaults, error) { var defaults ExosDefaults _, err := s.client.doGET(ctx, "/v1/vendor/seagate/exos/defaults", &defaults) return defaults, err } // ModifyExosDefaults sets or modifies default username / password for EXOS enclosures func (s *VendorService) ModifyExosDefaults(ctx context.Context, defaults ExosDefaultsModify) error { _, err := s.client.doPUT(ctx, "/v1/vendor/seagate/exos/defaults", defaults) return err } // GetExosEnclosures lists EXOS enclosures including controller IP and health status func (s *VendorService) GetExosEnclosures(ctx context.Context, noCache bool) ([]ExosEnclosure, error) { var enclosures []ExosEnclosure _, err := s.client.doGET(ctx, fmt.Sprintf("/v1/vendor/seagate/exos/enclosures?nocache=%t", noCache), &enclosures) return enclosures, err } // CreateExosEnclosure creates a new enclosure unless it already exists func (s *VendorService) CreateExosEnclosure(ctx context.Context, enclosure ExosEnclosure) error { _, err := s.client.doPOST(ctx, "/v1/vendor/seagate/exos/enclosures", enclosure) return err } // ModifyExosEnclosure modifies an existing enclosure func (s *VendorService) ModifyExosEnclosure(ctx context.Context, name string, enclosure ExosEnclosure) error { _, err := s.client.doPUT(ctx, "/v1/vendor/seagate/exos/enclosures/"+name, enclosure) return err } // DeleteExosEnclosure deletes an existing enclosure func (s *VendorService) DeleteExosEnclosure(ctx context.Context, name string) error { _, err := s.client.doDELETE(ctx, "/v1/vendor/seagate/exos/enclosures/"+name, nil) return err } // GetExosEvents lists the most current "count" events func (s *VendorService) GetExosEvents(ctx context.Context, name string, count int32) ([]ExosEnclosureEvent, error) { var events []ExosEnclosureEvent _, err := s.client.doGET(ctx, "/v1/vendor/seagate/exos/enclosures/"+name+"/events", &events) return events, err } // GetExosConnectionMap lists the connection-mesh of EXOS Ports to LINSTOR Nodes func (s *VendorService) GetExosConnectionMap(ctx context.Context) (ExosConnectionMap, error) { var connMap ExosConnectionMap _, err := s.client.doGET(ctx, "/v1/vendor/seagate/exos/map", &connMap) return connMap, err } golang-github-linbit-golinstor-0.55.0/clonestatus/000077500000000000000000000000001501671500700221735ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/clonestatus/clonestatus.go000066400000000000000000000002361501671500700250670ustar00rootroot00000000000000package clonestatus type CloneStatus string const ( Failed CloneStatus = "FAILED" Cloning CloneStatus = "CLONING" Complete CloneStatus = "COMPLETE" ) golang-github-linbit-golinstor-0.55.0/connectionstatus/000077500000000000000000000000001501671500700232325ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/connectionstatus/connectionstatus.go000066400000000000000000000010711501671500700271630ustar00rootroot00000000000000package connectionstatus type ConnectionStatus int const ( Offline ConnectionStatus = 0 Connected ConnectionStatus = 1 Online ConnectionStatus = 2 VersionMismatch ConnectionStatus = 3 FullSyncFailed ConnectionStatus = 4 AuthenticationError ConnectionStatus = 5 Unknown ConnectionStatus = 6 HostnameMismatch ConnectionStatus = 7 OtherController ConnectionStatus = 8 Authenticated ConnectionStatus = 9 NoStltConn ConnectionStatus = 10 MissingExtTools ConnectionStatus = 11 ) golang-github-linbit-golinstor-0.55.0/devicelayerkind/000077500000000000000000000000001501671500700227715ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/devicelayerkind/devicelayerkind.go000066400000000000000000000005641501671500700264670ustar00rootroot00000000000000package devicelayerkind type DeviceLayerKind string const ( Drbd DeviceLayerKind = "DRBD" Luks DeviceLayerKind = "LUKS" Storage DeviceLayerKind = "STORAGE" Nvme DeviceLayerKind = "NVME" Exos DeviceLayerKind = "EXOS" Writecache DeviceLayerKind = "WRITECACHE" Cache DeviceLayerKind = "CACHE" Bcache DeviceLayerKind = "BCACHE" ) golang-github-linbit-golinstor-0.55.0/go.mod000066400000000000000000000010611501671500700207330ustar00rootroot00000000000000module github.com/LINBIT/golinstor go 1.20 require ( github.com/BurntSushi/toml v1.4.0 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/google/go-querystring v1.1.0 github.com/sirupsen/logrus v1.9.2 github.com/stretchr/testify v1.10.0 golang.org/x/time v0.10.0 moul.io/http2curl/v2 v2.3.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-linbit-golinstor-0.55.0/go.sum000066400000000000000000000131041501671500700207610ustar00rootroot00000000000000github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 h1:C7t6eeMaEQVy6e8CarIhscYQlNmw5e3G36y7l7Y21Ao= github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0/go.mod h1:56wL82FO0bfMU5RvfXoIwSOP2ggqqxT+tAfNEIyxuHw= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= golang-github-linbit-golinstor-0.55.0/linstortoml/000077500000000000000000000000001501671500700222155ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/linstortoml/controller.go000066400000000000000000000051231501671500700247300ustar00rootroot00000000000000package linstortoml import lapi "github.com/LINBIT/golinstor/client" type Controller struct { Http *ControllerHttp `toml:"http,omitempty"` Https *ControllerHttps `toml:"https,omitempty"` Ldap *ControllerLdap `toml:"ldap,omitempty"` Db *ControllerDb `toml:"db,omitempty"` Logging *ControllerLogging `toml:"logging,omitempty"` Encrypt *ControllerEncrypt `toml:"encrypt,omitempty"` WebUi *ControllerWebUi `toml:"webUi,omitempty"` } type ControllerHttp struct { Enabled *bool `toml:"enabled,omitempty"` ListenAddr string `toml:"listen_addr,omitempty"` Port int `toml:"port,omitzero"` } type ControllerHttps struct { Enabled *bool `toml:"enabled,omitempty"` ListenAddr string `toml:"listen_addr,omitempty"` Port int `toml:"port,omitzero"` Keystore string `toml:"keystore,omitempty"` KeystorePassword string `toml:"keystore_password,omitempty"` Truststore string `toml:"truststore,omitempty"` TruststorePassword string `toml:"truststore_password,omitempty"` } type ControllerLdap struct { Enabled *bool `toml:"enabled,omitempty"` AllowPublicAccess *bool `toml:"allow_pubic_access,omitempty"` Uri string `toml:"uri,omitempty"` Dn string `toml:"dn,omitempty"` SearchBase string `toml:"search_base,omitempty"` SearchFilter string `toml:"search_filter,omitempty"` } type ControllerDb struct { User string `toml:"user,omitempty"` Password string `toml:"password,omitempty"` ConnectionUrl string `toml:"connection_url,omitempty"` CaCertificate string `toml:"ca_certificate,omitempty"` ClientCertificate string `toml:"client_certificate,omitempty"` ClientKeyPkcs8Pem string `toml:"client_key_pkcs8_pem,omitempty"` ClientKeyPassword string `toml:"client_key_password,omitempty"` Etcd *ControllerDbEtcd `toml:"etcd,omitempty"` } type ControllerDbEtcd struct { OpsPerTransaction int `toml:"ops_per_transaction,omitzero"` Prefix string `toml:"prefix,omitempty"` } type ControllerLogging struct { Level lapi.LogLevel `toml:"level,omitempty"` LinstorLevel lapi.LogLevel `toml:"linstor_level,omitempty"` RestAccessLogPath string `toml:"rest_access_log_path,omitempty"` RestAccessLogMode string `toml:"rest_access_log_mode,omitempty"` } type ControllerEncrypt struct { Passphrase string `toml:"passphrase,omitempty"` } type ControllerWebUi struct { Directory string `toml:"directory,omitempty"` } golang-github-linbit-golinstor-0.55.0/linstortoml/controller_test.go000066400000000000000000000020501501671500700257630ustar00rootroot00000000000000package linstortoml_test import ( "strings" "testing" "github.com/BurntSushi/toml" "github.com/LINBIT/golinstor/linstortoml" ) func TestController_Write(t *testing.T) { cases := []struct { config linstortoml.Controller expectedToml string }{ { config: linstortoml.Controller{}, expectedToml: "", }, { config: linstortoml.Controller{ Db: &linstortoml.ControllerDb{ConnectionUrl: "https://127.0.0.1:2379", CaCertificate: "/path/to/bundle.pem"}, Http: &linstortoml.ControllerHttp{Port: 5}, }, expectedToml: "[http]\nport = 5\n\n[db]\nconnection_url = \"https://127.0.0.1:2379\"\nca_certificate = \"/path/to/bundle.pem\"\n", }, } for _, test := range cases { builder := strings.Builder{} enc := toml.NewEncoder(&builder) enc.Indent = "" err := enc.Encode(test.config) if err != nil { t.Fatalf("Could not write config: %v", err) } result := builder.String() if result != test.expectedToml { t.Fatalf("Mismatched config, expected '%+v', got '%+v'", test.expectedToml, result) } } } golang-github-linbit-golinstor-0.55.0/linstortoml/satellite.go000066400000000000000000000020631501671500700245330ustar00rootroot00000000000000package linstortoml import lapi "github.com/LINBIT/golinstor/client" type Satellite struct { NetCom *SatelliteNetCom `toml:"netcom,omitempty"` Logging *SatelliteLogging `toml:"logging,omitempty"` Files *SatelliteFiles `toml:"files,omitempty"` } type SatelliteNetCom struct { Type string `toml:"type,omitempty"` BindAddress string `toml:"bind_address,omitempty"` Port int `toml:"port,omitzero"` ServerCertificate string `toml:"server_certificate,omitempty"` KeyPassword string `toml:"key_password,omitempty"` KeystorePassword string `toml:"keystore_password,omitempty"` TrustedCertificates string `toml:"trusted_certificates,omitempty"` TruststorePassword string `toml:"truststore_password,omitempty"` SslProtocol string `toml:"ssl_protocol,omitempty"` } type SatelliteLogging struct { Level lapi.LogLevel `toml:"level,omitempty"` LinstorLevel lapi.LogLevel `toml:"linstor_level,omitempty"` } type SatelliteFiles struct { AllowExtFiles []string `toml:"allowExtFiles,omitempty"` } golang-github-linbit-golinstor-0.55.0/monitor/000077500000000000000000000000001501671500700213165ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/monitor/lostresourceuser.go000066400000000000000000000127311501671500700253010ustar00rootroot00000000000000package monitor import ( "context" "sync" "time" "github.com/LINBIT/golinstor/client" "github.com/LINBIT/golinstor/devicelayerkind" ) type resourceState struct { hasQuorum bool isWatched bool } type haResources struct { resources map[string]resourceState sync.Mutex } // LostResourceUser is a struct that exposes the "may promote" state of a DRBD resource // If a resource may be promoted (i.e., may be switched to Primary) after some grace period, this usually means that its user (that had the resource promoted) failed. It could also happen that the user just terminated/gets rescheduled,... It is up to the user of this API to decide. // This also means that the user (e.g., some k8s pod) needs to be restarted/rescheduled. // The LostResourceUser is generic, it sends the names of resources that lost their user on the channel C. type LostResourceUser struct { ctx context.Context cancel context.CancelFunc client *client.Client mayPromoteStream *client.DRBDMayPromoteStream haResources haResources initialDelay time.Duration existingDelay time.Duration C chan string // DRBD resource names of resources that may be promoted. } const ( INITIAL_DELAY_DEFAULT = 1 * time.Minute EXISTING_DELAY_DEFAULT = 45 * time.Second ) // Option represents a configuration option of the LostResourceUser type Option func(*LostResourceUser) error // WithDelay sets the "initial delay" (for not yet seen resources) and the "existing delay" (for already known resources). func WithDelay(initial, existing time.Duration) Option { return func(ha *LostResourceUser) error { ha.initialDelay = initial ha.existingDelay = existing return nil } } // NewLostResourceUser creates a new LostResourceUser. It takes a context, a Go LINSTOR client, and options as its input. func NewLostResourceUser(ctx context.Context, client *client.Client, options ...Option) (*LostResourceUser, error) { // TODO: we only add to the map, we should have a GC that iterates over all the non-watched(?) and rms them. mayPromoteStream, err := client.Events.DRBDPromotion(ctx, "current") if err != nil { return nil, err } ctx, cancel := context.WithCancel(ctx) lr := &LostResourceUser{ ctx: ctx, cancel: cancel, client: client, mayPromoteStream: mayPromoteStream, // haResources: haResources, haResources: haResources{ resources: make(map[string]resourceState), }, initialDelay: INITIAL_DELAY_DEFAULT, existingDelay: EXISTING_DELAY_DEFAULT, C: make(chan string), } for _, opt := range options { if err := opt(lr); err != nil { return nil, err } } go func() { for { select { case ev, ok := <-lr.mayPromoteStream.Events: if !ok { lr.Stop() close(lr.C) return } if !ev.MayPromote { continue } resName := ev.ResourceName watch, dur := lr.resShouldWatch(resName) if !watch { continue } go lr.watch(resName, dur) case <-lr.ctx.Done(): lr.mayPromoteStream.Close() // would now receive in the event case, so close(lr.C) return } } }() return lr, nil } // Stop terminates all helper Go routines and closes the connection to the events stream. func (rl *LostResourceUser) Stop() { rl.cancel() } func (lr *LostResourceUser) watch(resName string, dur time.Duration) { ticker := time.NewTicker(dur) defer ticker.Stop() select { case <-ticker.C: break case <-lr.ctx.Done(): return } // reevaluate the current state ress, err := lr.client.Resources.GetAll(lr.ctx, resName) // here we might delete it, or reset isWatched lr.haResources.Lock() defer lr.haResources.Unlock() if err == client.NotFoundError { // looks like it got deleted. but anyways, nothing we can do, rm it from our dict delete(lr.haResources.resources, resName) return } else if err != nil { lr.Stop() return } oneMayPromote := false for _, r := range ress { if r.LayerObject.Type != devicelayerkind.Drbd { delete(lr.haResources.resources, resName) return } if r.LayerObject.Drbd.MayPromote { oneMayPromote = true break } } if oneMayPromote { lr.C <- resName } res := lr.haResources.resources[resName] // if we introduce a GC we need to check for ok here ^^ // but currently all the deletes are here under this lock res.isWatched = false lr.haResources.resources[resName] = res } func (ha *LostResourceUser) resHasQuorum(resName string) (bool, error) { rd, err := ha.client.ResourceDefinitions.Get(ha.ctx, resName) if err != nil { return false, err } val, ok := rd.Props["DrbdOptions/Resource/quorum"] if !ok || val == "off" { return false, nil } return true, nil } func (ha *LostResourceUser) resShouldWatch(resName string) (bool, time.Duration) { long, short := ha.initialDelay, ha.existingDelay ha.haResources.Lock() defer ha.haResources.Unlock() res, ok := ha.haResources.resources[resName] if ok { // existing resource if res.isWatched { return false, 0 } if !res.hasQuorum { return false, 0 } res.isWatched = true ha.haResources.resources[resName] = res return true, short } // new resource hasQuorum, err := ha.resHasQuorum(resName) if err != nil { // hope for better times... return false, 0 } // create the map entry ha.haResources.resources[resName] = resourceState{ hasQuorum: hasQuorum, isWatched: hasQuorum, // not a typo, if it hasQuorum, we will watch it } if !hasQuorum { return false, 0 } // new one with quorum, give it some time... return true, long } golang-github-linbit-golinstor-0.55.0/snapshotshipstatus/000077500000000000000000000000001501671500700236165ustar00rootroot00000000000000golang-github-linbit-golinstor-0.55.0/snapshotshipstatus/snapshotshipstatus.go000066400000000000000000000002311501671500700301300ustar00rootroot00000000000000package snapshotshipstatus type SnapshotShipStatus string const ( Running SnapshotShipStatus = "Running" Complete SnapshotShipStatus = "Complete" )