diff --git a/COPYING-GPLV2 b/COPYING-GPLV2 deleted file mode 100644 index d159169d10..0000000000 --- a/COPYING-GPLV2 +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/COPYING-LGPLV3 b/COPYING-LGPLV3 deleted file mode 100644 index 65c5ca88a6..0000000000 --- a/COPYING-LGPLV3 +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/LICENSE b/LICENSE index 3f16cfc819..ad410e1130 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,201 @@ -Heketi code is released under various licenses: +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -The REST API client code (in go and python) is released -under a dual license of Apache 2.0 or LGPLv3+. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -The other parts of heketi (server, cli, tests, ...) are released -under a dual license of LGPLv3+ or GPLv2. + 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. \ No newline at end of file diff --git a/LICENSE-APACHE2 b/LICENSE-APACHE2 deleted file mode 100644 index ad410e1130..0000000000 --- a/LICENSE-APACHE2 +++ /dev/null @@ -1,201 +0,0 @@ -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. \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index a4e117cde7..0000000000 --- a/Makefile +++ /dev/null @@ -1,138 +0,0 @@ -# -# Based on http://chrismckenzie.io/post/deploying-with-golang/ -# - -.PHONY: version all run dist clean - -APP_NAME := heketi -CLIENT_PKG_NAME := heketi-client -SHA := $(shell git rev-parse --short HEAD) -BRANCH := $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) -VER := $(shell git describe) -ARCH := $(shell go env GOARCH) -GOOS := $(shell go env GOOS) -GLIDEPATH := $(shell command -v glide 2> /dev/null) -DIR=. - -ifdef APP_SUFFIX - VERSION = $(VER)-$(subst /,-,$(APP_SUFFIX)) -else -ifeq (master,$(BRANCH)) - VERSION = $(VER) -else - VERSION = $(VER)-$(BRANCH) -endif -endif - -# Go setup -GO=go - -# Sources and Targets -EXECUTABLES :=$(APP_NAME) -# Build Binaries setting main.version and main.build vars -LDFLAGS :=-ldflags "-X main.HEKETI_VERSION=$(VERSION) -extldflags '-z relro -z now'" -# Package target -PACKAGE :=$(DIR)/dist/$(APP_NAME)-$(VERSION).$(GOOS).$(ARCH).tar.gz -CLIENT_PACKAGE :=$(DIR)/dist/$(APP_NAME)-client-$(VERSION).$(GOOS).$(ARCH).tar.gz -GOFILES=$(shell go list ./... | grep -v vendor) - -.DEFAULT: all - -all: server client - -# print the version -version: - @echo $(VERSION) - -# print the name of the app -name: - @echo $(APP_NAME) - -# print the package path -package: - @echo $(PACKAGE) - -heketi: glide.lock vendor - go build $(LDFLAGS) -o $(APP_NAME) - -server: heketi - -vendor: -ifndef GLIDEPATH - $(info Please install glide.) - $(info Install it using your package manager or) - $(info by running: curl https://glide.sh/get | sh.) - $(info ) - $(error glide is required to continue) -endif - echo "Installing vendor directory" - glide install -v - - echo "Building dependencies to make builds faster" - go install github.com/heketi/heketi - -glide.lock: glide.yaml - echo "Glide.yaml has changed, updating glide.lock" - glide update -v - -client: glide.lock vendor - @$(MAKE) -C client/cli/go - -run: server - ./$(APP_NAME) - -test: glide.lock vendor - go test $(GOFILES) - -clean: - @echo Cleaning Workspace... - rm -rf $(APP_NAME) - rm -rf dist - @$(MAKE) -C client/cli/go clean - -$(PACKAGE): all - @echo Packaging Binaries... - @mkdir -p tmp/$(APP_NAME) - @cp $(APP_NAME) tmp/$(APP_NAME)/ - @cp client/cli/go/heketi-cli tmp/$(APP_NAME)/ - @cp etc/heketi.json tmp/$(APP_NAME)/ - @mkdir -p $(DIR)/dist/ - tar -czf $@ -C tmp $(APP_NAME); - @rm -rf tmp - @echo - @echo Package $@ saved in dist directory - -$(CLIENT_PACKAGE): all - @echo Packaging client Binaries... - @mkdir -p tmp/$(CLIENT_PKG_NAME)/bin - @mkdir -p tmp/$(CLIENT_PKG_NAME)/share/heketi/openshift/templates - @mkdir -p tmp/$(CLIENT_PKG_NAME)/share/heketi/kubernetes - @cp client/cli/go/topology-sample.json tmp/$(CLIENT_PKG_NAME)/share/heketi - @cp client/cli/go/heketi-cli tmp/$(CLIENT_PKG_NAME)/bin - @cp extras/openshift/templates/* tmp/$(CLIENT_PKG_NAME)/share/heketi/openshift/templates - @cp extras/kubernetes/* tmp/$(CLIENT_PKG_NAME)/share/heketi/kubernetes - @mkdir -p $(DIR)/dist/ - tar -czf $@ -C tmp $(CLIENT_PKG_NAME); - @rm -rf tmp - @echo - @echo Package $@ saved in dist directory - -dist: $(PACKAGE) $(CLIENT_PACKAGE) - -linux_amd64_dist: - GOOS=linux GOARCH=amd64 $(MAKE) dist - -linux_arm_dist: - GOOS=linux GOARCH=arm $(MAKE) dist - -linux_arm64_dist: - GOOS=linux GOARCH=arm64 $(MAKE) dist - -darwin_amd64_dist: - GOOS=darwin GOARCH=amd64 $(MAKE) dist - -release: darwin_amd64_dist linux_arm64_dist linux_arm_dist linux_amd64_dist - -.PHONY: server client test clean name run version release \ - darwin_amd64_dist linux_arm_dist linux_amd64_dist linux_arm64_dist \ - heketi diff --git a/README.md b/README.md deleted file mode 100644 index 00b5df3f4f..0000000000 --- a/README.md +++ /dev/null @@ -1,37 +0,0 @@ -[![Stories in Ready](https://badge.waffle.io/heketi/heketi.png?label=in%20progress&title=In%20Progress)](https://waffle.io/heketi/heketi) -[![Build Status](https://travis-ci.org/heketi/heketi.svg?branch=master)](https://travis-ci.org/heketi/heketi) -[![Coverage Status](https://coveralls.io/repos/heketi/heketi/badge.svg)](https://coveralls.io/r/heketi/heketi) -[![Go Report Card](https://goreportcard.com/badge/github.com/heketi/heketi)](https://goreportcard.com/report/github.com/heketi/heketi) -[![Join the chat at https://gitter.im/heketi/heketi](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/heketi/heketi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -# Heketi -Heketi provides a RESTful management interface which can be used to manage the life cycle of GlusterFS volumes. With Heketi, cloud services like OpenStack Manila, Kubernetes, and OpenShift can dynamically provision GlusterFS volumes with any of the supported durability types. Heketi will automatically determine the location for bricks across the cluster, making sure to place bricks and its replicas across different failure domains. Heketi also supports any number of GlusterFS clusters, allowing cloud services to provide network file storage without being limited to a single GlusterFS cluster. - -# Workflow -When a request is received to create a volume, Heketi will first allocate the appropriate storage in a cluster, making sure to place brick replicas across failure domains. It will then format, then mount the storage to create bricks for the volume requested. Once all bricks have been automatically created, Heketi will finally satisfy the request by creating, then starting the newly created GlusterFS volume. - -# Downloads -Please go to the [wiki/Installation](https://github.com/heketi/heketi/wiki/Installation) for more information - -# Documentation -Please visit the [WIKI](http://github.com/heketi/heketi/wiki) for project documentation and demo information - -# Demo -Please visit [Vagrant-Heketi](https://github.com/heketi/vagrant-heketi) to try out the demo. - -# Community - -* Mailing list: [Join our mailing list](http://lists.gluster.org/mailman/listinfo/heketi-devel) -* IRC: #heketi on Freenode - -# Talks - -* DevNation 2016 - -[![image](https://img.youtube.com/vi/gmEUnOmDziQ/3.jpg)](https://youtu.be/gmEUnOmDziQ) -[Slides](http://bit.ly/29avBJX) - -* Devconf.cz 2016: - -[![image](https://img.youtube.com/vi/jpkG4wciy4U/3.jpg)](https://www.youtube.com/watch?v=jpkG4wciy4U) [Slides](https://github.com/lpabon/go-slides) - diff --git a/apps/app.go b/apps/app.go deleted file mode 100644 index 628052168d..0000000000 --- a/apps/app.go +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package apps - -import ( - "github.com/gorilla/mux" - "net/http" -) - -type Application interface { - SetRoutes(router *mux.Router) error - Close() - Auth(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) -} diff --git a/apps/doc.go b/apps/doc.go deleted file mode 100644 index 830e2458a4..0000000000 --- a/apps/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Location for applications for Heketi -package apps diff --git a/apps/glusterfs/allocator.go b/apps/glusterfs/allocator.go deleted file mode 100644 index 0bedf06925..0000000000 --- a/apps/glusterfs/allocator.go +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -type Allocator interface { - - // Inform the brick allocator to include device - AddDevice(c *ClusterEntry, n *NodeEntry, d *DeviceEntry) error - - // Inform the brick allocator to not use the specified device - RemoveDevice(c *ClusterEntry, n *NodeEntry, d *DeviceEntry) error - - // Remove cluster information from allocator - RemoveCluster(clusterId string) error - - // Returns a generator, done, and error channel. - // The generator returns the location for the brick, then the possible locations - // of its replicas. The caller must close() the done channel when it no longer - // needs to read from the generator. - GetNodes(clusterId, brickId string) (<-chan string, - chan<- struct{}, <-chan error) -} diff --git a/apps/glusterfs/allocator_mock.go b/apps/glusterfs/allocator_mock.go deleted file mode 100644 index 8cdd1efc37..0000000000 --- a/apps/glusterfs/allocator_mock.go +++ /dev/null @@ -1,161 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/boltdb/bolt" - "github.com/heketi/heketi/pkg/utils" - "sort" - "sync" -) - -type MockAllocator struct { - clustermap map[string]sort.StringSlice - lock sync.Mutex - db bolt.DB -} - -func NewMockAllocator(db *bolt.DB) *MockAllocator { - d := &MockAllocator{} - d.clustermap = make(map[string]sort.StringSlice) - - var clusters []string - err := db.View(func(tx *bolt.Tx) error { - var err error - clusters, err = ClusterList(tx) - if err != nil { - return err - } - - for _, cluster := range clusters { - err := d.addDevicesFromDb(tx, cluster) - if err != nil { - return err - } - } - - return nil - }) - if err != nil { - return nil - } - - return d -} - -func (d *MockAllocator) AddDevice(cluster *ClusterEntry, - node *NodeEntry, - device *DeviceEntry) error { - - d.lock.Lock() - defer d.lock.Unlock() - - clusterId := cluster.Info.Id - deviceId := device.Info.Id - - if devicelist, ok := d.clustermap[clusterId]; ok { - devicelist = append(devicelist, deviceId) - devicelist.Sort() - d.clustermap[clusterId] = devicelist - } else { - d.clustermap[clusterId] = sort.StringSlice{deviceId} - } - - return nil -} - -func (d *MockAllocator) RemoveDevice(cluster *ClusterEntry, - node *NodeEntry, - device *DeviceEntry) error { - - d.lock.Lock() - defer d.lock.Unlock() - - clusterId := cluster.Info.Id - deviceId := device.Info.Id - - d.clustermap[clusterId] = utils.SortedStringsDelete(d.clustermap[clusterId], deviceId) - - return nil -} - -func (d *MockAllocator) RemoveCluster(clusterId string) error { - // Save in the object - d.lock.Lock() - defer d.lock.Unlock() - - delete(d.clustermap, clusterId) - - return nil -} - -func (d *MockAllocator) GetNodes(clusterId, brickId string) (<-chan string, - chan<- struct{}, <-chan error) { - - // Initialize channels - device, done := make(chan string), make(chan struct{}) - - // Make sure to make a buffered channel for the error, so we can - // set it and return - errc := make(chan error, 1) - - d.lock.Lock() - devicelist := d.clustermap[clusterId] - d.lock.Unlock() - - // Start generator in a new goroutine - go func() { - defer func() { - errc <- nil - close(device) - }() - - for _, id := range devicelist { - select { - case device <- id: - case <-done: - return - } - } - - }() - - return device, done, errc -} - -func (d *MockAllocator) addDevicesFromDb(tx *bolt.Tx, clusterId string) error { - // Get data from the DB - devicelist := make(sort.StringSlice, 0) - - // Get cluster info - cluster, err := NewClusterEntryFromId(tx, clusterId) - if err != nil { - return err - } - - for _, nodeId := range cluster.Info.Nodes { - node, err := NewNodeEntryFromId(tx, nodeId) - if err != nil { - return err - } - - devicelist = append(devicelist, node.Devices...) - } - - // We have to sort the list so that later we can search and delete an entry - devicelist.Sort() - - // Save in the object - d.lock.Lock() - defer d.lock.Unlock() - - d.clustermap[clusterId] = devicelist - return nil -} diff --git a/apps/glusterfs/allocator_simple.go b/apps/glusterfs/allocator_simple.go deleted file mode 100644 index 2a445528a3..0000000000 --- a/apps/glusterfs/allocator_simple.go +++ /dev/null @@ -1,206 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/boltdb/bolt" - "sync" -) - -// Simple allocator contains a map to rings of clusters -type SimpleAllocator struct { - rings map[string]*SimpleAllocatorRing - lock sync.Mutex -} - -// Create a new simple allocator -func NewSimpleAllocator() *SimpleAllocator { - s := &SimpleAllocator{} - s.rings = make(map[string]*SimpleAllocatorRing) - return s -} - -// Create a new simple allocator and initialize it with data from the db -func NewSimpleAllocatorFromDb(db *bolt.DB) *SimpleAllocator { - - s := NewSimpleAllocator() - - err := db.View(func(tx *bolt.Tx) error { - clusters, err := ClusterList(tx) - if err != nil { - return err - } - - for _, clusterId := range clusters { - cluster, err := NewClusterEntryFromId(tx, clusterId) - if err != nil { - return err - } - - for _, nodeId := range cluster.Info.Nodes { - node, err := NewNodeEntryFromId(tx, nodeId) - if err != nil { - return err - } - - // Check node is online - if !node.isOnline() { - continue - } - - for _, deviceId := range node.Devices { - device, err := NewDeviceEntryFromId(tx, deviceId) - if err != nil { - return err - } - - // Check device is online - if !device.isOnline() { - continue - } - - // Add device to ring - err = s.AddDevice(cluster, node, device) - if err != nil { - return err - } - - } - } - } - return nil - }) - if err != nil { - return nil - } - - return s - -} - -func (s *SimpleAllocator) AddDevice(cluster *ClusterEntry, - node *NodeEntry, - device *DeviceEntry) error { - - s.lock.Lock() - defer s.lock.Unlock() - - // Create a new cluster id if one is not available - clusterId := cluster.Info.Id - if _, ok := s.rings[clusterId]; !ok { - s.rings[clusterId] = NewSimpleAllocatorRing() - } - - s.rings[clusterId].Add(&SimpleDevice{ - zone: node.Info.Zone, - nodeId: node.Info.Id, - deviceId: device.Info.Id, - }) - - return nil - -} - -func (s *SimpleAllocator) RemoveDevice(cluster *ClusterEntry, - node *NodeEntry, - device *DeviceEntry) error { - - s.lock.Lock() - defer s.lock.Unlock() - - // Check the cluster id is in the map - clusterId := cluster.Info.Id - if _, ok := s.rings[clusterId]; !ok { - logger.LogError("Unknown cluster id requested: %v", clusterId) - return ErrNotFound - } - - // Remove device from ring - s.rings[clusterId].Remove(&SimpleDevice{ - zone: node.Info.Zone, - nodeId: node.Info.Id, - deviceId: device.Info.Id, - }) - - return nil -} - -func (s *SimpleAllocator) RemoveCluster(clusterId string) error { - - s.lock.Lock() - defer s.lock.Unlock() - - // Check the cluster id is in the map - if _, ok := s.rings[clusterId]; !ok { - logger.LogError("Unknown cluster id requested: %v", clusterId) - return ErrNotFound - } - - // Remove cluster from map - delete(s.rings, clusterId) - - return nil -} - -func (s *SimpleAllocator) getDeviceList(clusterId, brickId string) (SimpleDevices, error) { - s.lock.Lock() - defer s.lock.Unlock() - - if _, ok := s.rings[clusterId]; !ok { - logger.LogError("Unknown cluster id requested: %v", clusterId) - return nil, ErrNotFound - } - - ring := s.rings[clusterId] - ring.Rebalance() - devicelist := ring.GetDeviceList(brickId) - - return devicelist, nil - -} - -func (s *SimpleAllocator) GetNodes(clusterId, brickId string) (<-chan string, - chan<- struct{}, <-chan error) { - - // Initialize channels - device, done := make(chan string), make(chan struct{}) - - // Make sure to make a buffered channel for the error, so we can - // set it and return - errc := make(chan error, 1) - - // Get the list of devices for this brick id - devicelist, err := s.getDeviceList(clusterId, brickId) - - if err != nil { - errc <- err - close(device) - return device, done, errc - } - - // Start generator in a new goroutine - go func() { - defer func() { - errc <- nil - close(device) - }() - - for _, d := range devicelist { - select { - case device <- d.deviceId: - case <-done: - return - } - } - - }() - - return device, done, errc -} diff --git a/apps/glusterfs/allocator_simple_ring.go b/apps/glusterfs/allocator_simple_ring.go deleted file mode 100644 index f2d25238d3..0000000000 --- a/apps/glusterfs/allocator_simple_ring.go +++ /dev/null @@ -1,192 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "fmt" - "strconv" -) - -// Elements in the balanced list -type SimpleDevice struct { - zone int - nodeId, deviceId string -} - -// Pretty pring a SimpleDevice -func (s *SimpleDevice) String() string { - return fmt.Sprintf("{Z:%v N:%v D:%v}", - s.zone, - s.nodeId, - s.deviceId) -} - -// Simple Devices so that we have no pointers and no race conditions -type SimpleDevices []SimpleDevice - -// A node is a collection of devices -type SimpleNode []*SimpleDevice - -// A zone is a collection of nodes -type SimpleZone []SimpleNode - -// The allocation ring will contain a map composed of all -// the devices available in the cluster. Call Rebalance() -// for it to create a balanced list. -type SimpleAllocatorRing struct { - - // Map [zone] to [node] to slice of SimpleDevices - ring map[int]map[string][]*SimpleDevice - balancedList SimpleDevices -} - -// Create a new simple ring -func NewSimpleAllocatorRing() *SimpleAllocatorRing { - s := &SimpleAllocatorRing{} - s.ring = make(map[int]map[string][]*SimpleDevice) - - return s -} - -// Convert the ring map into a consumable list of lists. -// This allows the rebalancer to go through the lists and remove -// elements as it balances -func (s *SimpleAllocatorRing) createZoneLists() []SimpleZone { - zones := make([]SimpleZone, 0) - - for _, n := range s.ring { - - zone := make([]SimpleNode, 0) - for _, d := range n { - zone = append(zone, d) - } - zones = append(zones, zone) - } - - return zones -} - -// Add a device to the ring map -func (s *SimpleAllocatorRing) Add(d *SimpleDevice) { - - if nodes, ok := s.ring[d.zone]; ok { - if _, ok := nodes[d.nodeId]; ok { - nodes[d.nodeId] = append(nodes[d.nodeId], d) - } else { - nodes[d.nodeId] = []*SimpleDevice{d} - } - } else { - s.ring[d.zone] = make(map[string][]*SimpleDevice) - s.ring[d.zone][d.nodeId] = []*SimpleDevice{d} - } - - s.balancedList = nil -} - -// Remove device from the ring map -func (s *SimpleAllocatorRing) Remove(d *SimpleDevice) { - - if nodes, ok := s.ring[d.zone]; ok { - if devices, ok := nodes[d.nodeId]; ok { - for index, device := range devices { - if device.deviceId == d.deviceId { - // Found device, now delete it from the ring map - nodes[d.nodeId] = append(nodes[d.nodeId][:index], nodes[d.nodeId][index+1:]...) - - if len(nodes[d.nodeId]) == 0 { - delete(nodes, d.nodeId) - } - if len(s.ring[d.zone]) == 0 { - delete(s.ring, d.zone) - } - } - } - } - } - - s.balancedList = nil -} - -// Rebalance the ring and place the rebalanced list -// into balancedList. -// The idea is to setup an array/slice where each continguous SimpleDevice -// is from either a different zone, or node. -func (s *SimpleAllocatorRing) Rebalance() { - - if s.balancedList != nil { - return - } - - // Copy map data to slices - zones := s.createZoneLists() - - // Create a list - list := make(SimpleDevices, 0) - - // Populate the list - var device *SimpleDevice - for i := 0; len(zones) != 0; i++ { - zone := i % len(zones) - nodes := zones[zone] - node := i % len(nodes) - devices := nodes[node] - - // pop device - device = devices[len(devices)-1] - devices = devices[:len(devices)-1] - nodes[node] = devices - - list = append(list, *device) - - if len(devices) == 0 { - // delete node - nodes = append(nodes[:node], nodes[node+1:]...) - zones[zone] = nodes - } - - if len(nodes) == 0 { - // delete zone - zones = append(zones[:zone], zones[zone+1:]...) - } - } - - s.balancedList = list -} - -// Use a uuid to point at a position in the ring. Return a list of devices -// from that point in the ring. -func (s *SimpleAllocatorRing) GetDeviceList(uuid string) SimpleDevices { - - if s.balancedList == nil { - s.Rebalance() - } - if len(s.balancedList) == 0 { - return SimpleDevices{} - } - - // Create a new list to avoid race conditions - devices := make(SimpleDevices, len(s.balancedList)) - copy(devices, s.balancedList) - - // Instead of using 8 characters to convert to a int32, use 7 which avoids - // negative numbers - index64, err := strconv.ParseInt(uuid[:7], 16, 32) - if err != nil { - logger.Err(err) - return devices - } - - // Point to a position on the ring - index := int(index64) % len(s.balancedList) - - // Return a list according to the position in the list - return append(devices[index:], devices[:index]...) - -} diff --git a/apps/glusterfs/allocator_simple_ring_test.go b/apps/glusterfs/allocator_simple_ring_test.go deleted file mode 100644 index 0c350aa009..0000000000 --- a/apps/glusterfs/allocator_simple_ring_test.go +++ /dev/null @@ -1,258 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" - "reflect" - "testing" -) - -func TestNewSimpleAllocatorRing(t *testing.T) { - r := NewSimpleAllocatorRing() - tests.Assert(t, r != nil) - tests.Assert(t, r.ring != nil) -} - -func TestSimpleAllocatorRingAddRemove(t *testing.T) { - r := NewSimpleAllocatorRing() - tests.Assert(t, r != nil) - - // Add one device - sd := &SimpleDevice{ - zone: 100, - nodeId: "abc", - deviceId: "def", - } - - r.Add(sd) - tests.Assert(t, r.ring[sd.zone][sd.nodeId][0] == sd) - - // Add new zone - sd2 := &SimpleDevice{ - zone: 10, - nodeId: "ace", - deviceId: "dea", - } - r.Add(sd2) - tests.Assert(t, r.ring[sd.zone][sd.nodeId][0] == sd) - tests.Assert(t, r.ring[sd2.zone][sd2.nodeId][0] == sd2) - tests.Assert(t, r.balancedList == nil) - - // Add new node to same zone - sd3 := &SimpleDevice{ - zone: 10, - nodeId: "aed", - deviceId: "daa", - } - r.Add(sd3) - tests.Assert(t, r.ring[sd.zone][sd.nodeId][0] == sd) - tests.Assert(t, r.ring[sd2.zone][sd2.nodeId][0] == sd2) - tests.Assert(t, r.ring[sd2.zone][sd3.nodeId][0] == sd3) - tests.Assert(t, r.balancedList == nil) - - // Add new disk to same node - sd4 := &SimpleDevice{ - zone: 10, - nodeId: "aed", - deviceId: "dbb", - } - r.Add(sd4) - tests.Assert(t, r.ring[sd.zone][sd.nodeId][0] == sd) - tests.Assert(t, r.ring[sd2.zone][sd2.nodeId][0] == sd2) - tests.Assert(t, r.ring[sd2.zone][sd3.nodeId][0] == sd3) - tests.Assert(t, r.ring[sd2.zone][sd3.nodeId][1] == sd4) - tests.Assert(t, len(r.ring[sd2.zone][sd3.nodeId]) == 2) - tests.Assert(t, r.balancedList == nil) - - // Remove sd4 - r.Remove(sd4) - tests.Assert(t, r.ring[sd.zone][sd.nodeId][0] == sd) - tests.Assert(t, r.ring[sd2.zone][sd2.nodeId][0] == sd2) - tests.Assert(t, r.ring[sd2.zone][sd3.nodeId][0] == sd3) - tests.Assert(t, len(r.ring[sd2.zone][sd3.nodeId]) == 1) - tests.Assert(t, len(r.ring[sd2.zone]) == 2) - tests.Assert(t, r.balancedList == nil) - - // Remove sd3 - r.Remove(sd3) - tests.Assert(t, r.ring[sd.zone][sd.nodeId][0] == sd) - tests.Assert(t, r.ring[sd2.zone][sd2.nodeId][0] == sd2) - tests.Assert(t, len(r.ring[sd2.zone]) == 1) - tests.Assert(t, len(r.ring) == 2) - tests.Assert(t, r.balancedList == nil) - - // Remove sd2 - r.Remove(sd2) - tests.Assert(t, r.ring[sd.zone][sd.nodeId][0] == sd) - tests.Assert(t, len(r.ring) == 1) - tests.Assert(t, r.balancedList == nil) - - // Remove sd - r.Remove(sd) - tests.Assert(t, len(r.ring) == 0) - tests.Assert(t, r.balancedList == nil) - -} - -func TestSimpleAllocatorCreateZoneLists(t *testing.T) { - r := NewSimpleAllocatorRing() - tests.Assert(t, r != nil) - - // Add one device - sd := &SimpleDevice{ - zone: 100, - nodeId: "abc", - deviceId: "def", - } - - // Add new zone - sd2 := &SimpleDevice{ - zone: 10, - nodeId: "ace", - deviceId: "dea", - } - - // Add new node to same zone - sd3 := &SimpleDevice{ - zone: 10, - nodeId: "aed", - deviceId: "daa", - } - - // Add new disk to same node - sd4 := &SimpleDevice{ - zone: 10, - nodeId: "aed", - deviceId: "dbb", - } - - r.Add(sd) - r.Add(sd2) - r.Add(sd3) - r.Add(sd4) - - // Get a - zones := r.createZoneLists() - tests.Assert(t, zones != nil) - tests.Assert(t, len(zones) == 2) // two zones - tests.Assert(t, len(zones[0]) == 1 || len(zones[0]) == 2) - - // Check the two zones - for i := 0; i < 2; i++ { - if len(zones[i]) == 1 { - tests.Assert(t, zones[i][0][0] == sd) - } else { - tests.Assert(t, len(zones[i]) == 2) - - // Check the two nodes - tests.Assert(t, len(zones[i][0]) == 1 || len(zones[i][0]) == 2) - for j := 0; j < 2; j++ { - if len(zones[i][j]) == 1 { - tests.Assert(t, zones[i][j][0] == sd2) - } else { - tests.Assert(t, zones[i][j][0] == sd3 || zones[i][j][0] == sd4) - } - } - } - } -} - -func TestSimpleAllocatorRingRebalance(t *testing.T) { - r := NewSimpleAllocatorRing() - tests.Assert(t, r != nil) - - zones, nodes, drives := 10, 100, 48 - - // Add 10*100*48 devices to the ring - for z := 0; z < zones; z++ { - - // Generate nodes for this zone - for n := 0; n < nodes; n++ { - nid := utils.GenUUID() - - // Generate drives for this node - for d := 0; d < drives; d++ { - did := utils.GenUUID() - - // Setup simple device - dev := &SimpleDevice{ - zone: z, - deviceId: did, - nodeId: nid, - } - r.Add(dev) - } - } - } - tests.Assert(t, r.balancedList == nil) - - // Rebalance - r.Rebalance() - tests.Assert(t, r.balancedList != nil) - tests.Assert(t, len(r.balancedList) == zones*nodes*drives) - - // Check balance - // 1. No zones should be next to eachother in the list - // 2. Every other element should not have the same node - for i := range r.balancedList[:len(r.balancedList)-1] { - tests.Assert(t, r.balancedList[i].zone != r.balancedList[i+1].zone) - } - for i := range r.balancedList[:len(r.balancedList)-2] { - tests.Assert(t, r.balancedList[i].nodeId != r.balancedList[i+2].nodeId) - } -} - -func TestSimpleAllocatorGetDeviceList(t *testing.T) { - r := NewSimpleAllocatorRing() - tests.Assert(t, r != nil) - - zones, nodes, drives := 1, 2, 4 - - // Create ring - for z := 0; z < zones; z++ { - - // Generate nodes for this zone - for n := 0; n < nodes; n++ { - nid := utils.GenUUID() - - // Generate drives for this node - for d := 0; d < drives; d++ { - did := utils.GenUUID() - - // Setup simple device - dev := &SimpleDevice{ - zone: z, - deviceId: did, - nodeId: nid, - } - r.Add(dev) - } - } - } - tests.Assert(t, r.balancedList == nil) - - // Rebalance - r.Rebalance() - tests.Assert(t, r.balancedList != nil) - tests.Assert(t, len(r.balancedList) == zones*nodes*drives) - - // Get a list for a brick with "00000" id - // It should return a list equal to balancedList - tests.Assert(t, - reflect.DeepEqual(r.GetDeviceList("0000000"), r.balancedList)) - tests.Assert(t, - reflect.DeepEqual(r.GetDeviceList("0000001"), append(r.balancedList[1:], r.balancedList[0]))) - - // 14 is larger than 1*2*4, 8.. So the index is 14%8 = 6 - tests.Assert(t, - reflect.DeepEqual(r.GetDeviceList("000000e"), append(r.balancedList[6:], r.balancedList[:6]...))) -} diff --git a/apps/glusterfs/allocator_simple_test.go b/apps/glusterfs/allocator_simple_test.go deleted file mode 100644 index f1f0f7a2c3..0000000000 --- a/apps/glusterfs/allocator_simple_test.go +++ /dev/null @@ -1,211 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "os" - "testing" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func TestNewSimpleAllocator(t *testing.T) { - - a := NewSimpleAllocator() - tests.Assert(t, a != nil) - tests.Assert(t, a.rings != nil) - -} - -func TestSimpleAllocatorEmpty(t *testing.T) { - a := NewSimpleAllocator() - tests.Assert(t, a != nil) - - err := a.RemoveDevice(createSampleClusterEntry(), - createSampleNodeEntry(), - createSampleDeviceEntry("aaa", 10)) - tests.Assert(t, err == ErrNotFound) - - err = a.RemoveCluster("aaa") - tests.Assert(t, err == ErrNotFound) - - ch, _, errc := a.GetNodes(utils.GenUUID(), utils.GenUUID()) - for d := range ch { - tests.Assert(t, false, d) - } - err = <-errc - tests.Assert(t, err == ErrNotFound) -} - -func TestSimpleAllocatorAddRemoveDevice(t *testing.T) { - a := NewSimpleAllocator() - tests.Assert(t, a != nil) - - cluster := createSampleClusterEntry() - node := createSampleNodeEntry() - node.Info.ClusterId = cluster.Info.Id - device := createSampleDeviceEntry(node.Info.Id, 10000) - - tests.Assert(t, len(a.rings) == 0) - err := a.AddDevice(cluster, node, device) - tests.Assert(t, err == nil) - tests.Assert(t, len(a.rings) == 1) - tests.Assert(t, a.rings[cluster.Info.Id] != nil) - - // Get the nodes from the ring - ch, _, errc := a.GetNodes(cluster.Info.Id, utils.GenUUID()) - - var devices int - for d := range ch { - devices++ - tests.Assert(t, d == device.Info.Id) - } - err = <-errc - tests.Assert(t, devices == 1) - tests.Assert(t, err == nil) - - // Now remove the device - err = a.RemoveDevice(cluster, node, device) - tests.Assert(t, err == nil) - tests.Assert(t, len(a.rings) == 1) - - // Get the nodes from the ring - ch, _, errc = a.GetNodes(cluster.Info.Id, utils.GenUUID()) - - devices = 0 - for d := range ch { - devices++ - tests.Assert(t, false, d) - } - err = <-errc - tests.Assert(t, devices == 0) - tests.Assert(t, err == nil) - -} - -func TestSimpleAllocatorInitFromDb(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Setup database - app := NewTestApp(tmpfile) - defer app.Close() - - // Create large cluster - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 20, // devices_per_node, - 600*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Get the cluster list - var clusterId string - err = app.db.View(func(tx *bolt.Tx) error { - clusters, err := ClusterList(tx) - if err != nil { - return err - } - tests.Assert(t, len(clusters) == 1) - clusterId = clusters[0] - - return nil - }) - tests.Assert(t, err == nil) - - // Create an allocator and initialize it from the DB - a := NewSimpleAllocatorFromDb(app.db) - tests.Assert(t, a != nil) - - // Get the nodes from the ring - ch, _, errc := a.GetNodes(clusterId, utils.GenUUID()) - - var devices int - for d := range ch { - devices++ - tests.Assert(t, d != "") - } - err = <-errc - tests.Assert(t, devices == 10*20) - tests.Assert(t, err == nil) - -} - -func TestSimpleAllocatorInitFromDbWithOfflineDevices(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Setup database - app := NewTestApp(tmpfile) - defer app.Close() - - // Create large cluster - err := setupSampleDbWithTopology(app, - 1, // clusters - 2, // nodes_per_cluster - 4, // devices_per_node, - 600*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Get the cluster list - var clusterId, nodeId string - err = app.db.Update(func(tx *bolt.Tx) error { - clusters, err := ClusterList(tx) - if err != nil { - return err - } - tests.Assert(t, len(clusters) == 1) - clusterId = clusters[0] - - cluster, err := NewClusterEntryFromId(tx, clusterId) - tests.Assert(t, err == nil) - - // make one node offline, which will mean none of its - // devices are added to the ring - node, err := cluster.NodeEntryFromClusterIndex(tx, 0) - tests.Assert(t, err == nil) - nodeId = node.Info.Id - node.State = api.EntryStateOffline - node.Save(tx) - - // Make only one device offline in the other node - node, err = cluster.NodeEntryFromClusterIndex(tx, 1) - device, err := NewDeviceEntryFromId(tx, node.Devices[0]) - device.State = api.EntryStateOffline - device.Save(tx) - - return nil - }) - tests.Assert(t, err == nil) - - // Create an allocator and initialize it from the DB - a := NewSimpleAllocatorFromDb(app.db) - tests.Assert(t, a != nil) - - // Get the nodes from the ring - ch, _, errc := a.GetNodes(clusterId, utils.GenUUID()) - - var devices int - for d := range ch { - devices++ - tests.Assert(t, d != "") - } - err = <-errc - tests.Assert(t, err == nil) - - // Only three online devices should be in the list - tests.Assert(t, devices == 3, devices) - -} diff --git a/apps/glusterfs/app.go b/apps/glusterfs/app.go deleted file mode 100644 index 83654b5ef1..0000000000 --- a/apps/glusterfs/app.go +++ /dev/null @@ -1,413 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "io" - "net/http" - "strconv" - "time" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/executors/kubeexec" - "github.com/heketi/heketi/executors/mockexec" - "github.com/heketi/heketi/executors/sshexec" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/rest" -) - -const ( - ASYNC_ROUTE = "/queue" - BOLTDB_BUCKET_CLUSTER = "CLUSTER" - BOLTDB_BUCKET_NODE = "NODE" - BOLTDB_BUCKET_VOLUME = "VOLUME" - BOLTDB_BUCKET_DEVICE = "DEVICE" - BOLTDB_BUCKET_BRICK = "BRICK" -) - -var ( - logger = utils.NewLogger("[heketi]", utils.LEVEL_INFO) - dbfilename = "heketi.db" -) - -type App struct { - asyncManager *rest.AsyncHttpManager - db *bolt.DB - dbReadOnly bool - executor executors.Executor - allocator Allocator - conf *GlusterFSConfig - - // For testing only. Keep access to the object - // not through the interface - xo *mockexec.MockExecutor -} - -// Use for tests only -func NewApp(configIo io.Reader) *App { - app := &App{} - - // Load configuration file - app.conf = loadConfiguration(configIo) - if app.conf == nil { - return nil - } - - // Setup loglevel - app.setLogLevel(app.conf.Loglevel) - - // Setup asynchronous manager - app.asyncManager = rest.NewAsyncHttpManager(ASYNC_ROUTE) - - // Setup executor - var err error - switch { - case app.conf.Executor == "mock": - app.xo, err = mockexec.NewMockExecutor() - app.executor = app.xo - case app.conf.Executor == "kube" || app.conf.Executor == "kubernetes": - app.executor, err = kubeexec.NewKubeExecutor(&app.conf.KubeConfig) - case app.conf.Executor == "ssh" || app.conf.Executor == "": - app.executor, err = sshexec.NewSshExecutor(&app.conf.SshConfig) - default: - return nil - } - if err != nil { - logger.Err(err) - return nil - } - logger.Info("Loaded %v executor", app.conf.Executor) - - // Set db is set in the configuration file - if app.conf.DBfile != "" { - dbfilename = app.conf.DBfile - } - - // Setup BoltDB database - app.db, err = bolt.Open(dbfilename, 0600, &bolt.Options{Timeout: 3 * time.Second}) - if err != nil { - logger.Warning("Unable to open database. Retrying using read only mode") - - // Try opening as read-only - app.db, err = bolt.Open(dbfilename, 0666, &bolt.Options{ - ReadOnly: true, - }) - if err != nil { - logger.LogError("Unable to open database: %v", err) - return nil - } - app.dbReadOnly = true - } else { - err = app.db.Update(func(tx *bolt.Tx) error { - // Create Cluster Bucket - _, err := tx.CreateBucketIfNotExists([]byte(BOLTDB_BUCKET_CLUSTER)) - if err != nil { - logger.LogError("Unable to create cluster bucket in DB") - return err - } - - // Create Node Bucket - _, err = tx.CreateBucketIfNotExists([]byte(BOLTDB_BUCKET_NODE)) - if err != nil { - logger.LogError("Unable to create node bucket in DB") - return err - } - - // Create Volume Bucket - _, err = tx.CreateBucketIfNotExists([]byte(BOLTDB_BUCKET_VOLUME)) - if err != nil { - logger.LogError("Unable to create volume bucket in DB") - return err - } - - // Create Device Bucket - _, err = tx.CreateBucketIfNotExists([]byte(BOLTDB_BUCKET_DEVICE)) - if err != nil { - logger.LogError("Unable to create device bucket in DB") - return err - } - - // Create Brick Bucket - _, err = tx.CreateBucketIfNotExists([]byte(BOLTDB_BUCKET_BRICK)) - if err != nil { - logger.LogError("Unable to create brick bucket in DB") - return err - } - - // Handle Upgrade Changes - err = app.Upgrade(tx) - if err != nil { - logger.LogError("Unable to Upgrade Changes") - return err - } - - return nil - - }) - if err != nil { - logger.Err(err) - return nil - } - } - - // Set advanced settings - app.setAdvSettings() - - // Setup allocator - switch { - case app.conf.Allocator == "mock": - app.allocator = NewMockAllocator(app.db) - case app.conf.Allocator == "simple" || app.conf.Allocator == "": - app.conf.Allocator = "simple" - app.allocator = NewSimpleAllocatorFromDb(app.db) - default: - return nil - } - logger.Info("Loaded %v allocator", app.conf.Allocator) - - // Show application has loaded - logger.Info("GlusterFS Application Loaded") - - return app -} - -func (a *App) setLogLevel(level string) { - switch level { - case "none": - logger.SetLevel(utils.LEVEL_NOLOG) - case "critical": - logger.SetLevel(utils.LEVEL_CRITICAL) - case "error": - logger.SetLevel(utils.LEVEL_ERROR) - case "warning": - logger.SetLevel(utils.LEVEL_WARNING) - case "info": - logger.SetLevel(utils.LEVEL_INFO) - case "debug": - logger.SetLevel(utils.LEVEL_DEBUG) - } -} - -// Upgrade Path to update all the values for new API entries -func (a *App) Upgrade(tx *bolt.Tx) error { - - err := ClusterEntryUpgrade(tx) - if err != nil { - logger.LogError("Failed to upgrade db for cluster entries") - return err - } - - err = NodeEntryUpgrade(tx) - if err != nil { - logger.LogError("Failed to upgrade db for node entries") - return err - } - - err = VolumeEntryUpgrade(tx) - if err != nil { - logger.LogError("Failed to upgrade db for volume entries") - return err - } - - err = DeviceEntryUpgrade(tx) - if err != nil { - logger.LogError("Failed to upgrade db for device entries") - return err - } - - err = BrickEntryUpgrade(tx) - if err != nil { - logger.LogError("Failed to upgrade db for brick entries: %v", err) - return err - } - - return nil -} - -func (a *App) setAdvSettings() { - if a.conf.BrickMaxNum != 0 { - logger.Info("Adv: Max bricks per volume set to %v", a.conf.BrickMaxNum) - - // From volume_entry.go - BrickMaxNum = a.conf.BrickMaxNum - } - if a.conf.BrickMaxSize != 0 { - logger.Info("Adv: Max brick size %v GB", a.conf.BrickMaxSize) - - // From volume_entry.go - // Convert to KB - BrickMaxSize = uint64(a.conf.BrickMaxSize) * 1024 * 1024 - } - if a.conf.BrickMinSize != 0 { - logger.Info("Adv: Min brick size %v GB", a.conf.BrickMinSize) - - // From volume_entry.go - // Convert to KB - BrickMinSize = uint64(a.conf.BrickMinSize) * 1024 * 1024 - } -} - -// Register Routes -func (a *App) SetRoutes(router *mux.Router) error { - - routes := rest.Routes{ - - // Asynchronous Manager - rest.Route{ - Name: "Async", - Method: "GET", - Pattern: ASYNC_ROUTE + "/{id:[A-Fa-f0-9]+}", - HandlerFunc: a.asyncManager.HandlerStatus}, - - // Cluster - rest.Route{ - Name: "ClusterCreate", - Method: "POST", - Pattern: "/clusters", - HandlerFunc: a.ClusterCreate}, - rest.Route{ - Name: "ClusterInfo", - Method: "GET", - Pattern: "/clusters/{id:[A-Fa-f0-9]+}", - HandlerFunc: a.ClusterInfo}, - rest.Route{ - Name: "ClusterList", - Method: "GET", - Pattern: "/clusters", - HandlerFunc: a.ClusterList}, - rest.Route{ - Name: "ClusterDelete", - Method: "DELETE", - Pattern: "/clusters/{id:[A-Fa-f0-9]+}", - HandlerFunc: a.ClusterDelete}, - - // Node - rest.Route{ - Name: "NodeAdd", - Method: "POST", - Pattern: "/nodes", - HandlerFunc: a.NodeAdd}, - rest.Route{ - Name: "NodeInfo", - Method: "GET", - Pattern: "/nodes/{id:[A-Fa-f0-9]+}", - HandlerFunc: a.NodeInfo}, - rest.Route{ - Name: "NodeDelete", - Method: "DELETE", - Pattern: "/nodes/{id:[A-Fa-f0-9]+}", - HandlerFunc: a.NodeDelete}, - rest.Route{ - Name: "NodeSetState", - Method: "POST", - Pattern: "/nodes/{id:[A-Fa-f0-9]+}/state", - HandlerFunc: a.NodeSetState}, - - // Devices - rest.Route{ - Name: "DeviceAdd", - Method: "POST", - Pattern: "/devices", - HandlerFunc: a.DeviceAdd}, - rest.Route{ - Name: "DeviceInfo", - Method: "GET", - Pattern: "/devices/{id:[A-Fa-f0-9]+}", - HandlerFunc: a.DeviceInfo}, - rest.Route{ - Name: "DeviceDelete", - Method: "DELETE", - Pattern: "/devices/{id:[A-Fa-f0-9]+}", - HandlerFunc: a.DeviceDelete}, - rest.Route{ - Name: "DeviceSetState", - Method: "POST", - Pattern: "/devices/{id:[A-Fa-f0-9]+}/state", - HandlerFunc: a.DeviceSetState}, - - // Volume - rest.Route{ - Name: "VolumeCreate", - Method: "POST", - Pattern: "/volumes", - HandlerFunc: a.VolumeCreate}, - rest.Route{ - Name: "VolumeInfo", - Method: "GET", - Pattern: "/volumes/{id:[A-Fa-f0-9]+}", - HandlerFunc: a.VolumeInfo}, - rest.Route{ - Name: "VolumeExpand", - Method: "POST", - Pattern: "/volumes/{id:[A-Fa-f0-9]+}/expand", - HandlerFunc: a.VolumeExpand}, - rest.Route{ - Name: "VolumeDelete", - Method: "DELETE", - Pattern: "/volumes/{id:[A-Fa-f0-9]+}", - HandlerFunc: a.VolumeDelete}, - rest.Route{ - Name: "VolumeList", - Method: "GET", - Pattern: "/volumes", - HandlerFunc: a.VolumeList}, - - // Backup - rest.Route{ - Name: "Backup", - Method: "GET", - Pattern: "/backup/db", - HandlerFunc: a.Backup}, - } - - // Register all routes from the App - for _, route := range routes { - - // Add routes from the table - router. - Methods(route.Method). - Path(route.Pattern). - Name(route.Name). - Handler(route.HandlerFunc) - - } - - // Set default error handler - router.NotFoundHandler = http.HandlerFunc(a.NotFoundHandler) - - return nil -} - -func (a *App) Close() { - - // Close the DB - a.db.Close() - logger.Info("Closed") -} - -func (a *App) Backup(w http.ResponseWriter, r *http.Request) { - err := a.db.View(func(tx *bolt.Tx) error { - w.Header().Set("Content-Type", "application/octet-stream") - w.Header().Set("Content-Disposition", `attachment; filename="heketi.db"`) - w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) - _, err := tx.WriteTo(w) - return err - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} - -func (a *App) NotFoundHandler(w http.ResponseWriter, r *http.Request) { - logger.Warning("Invalid path or request %v", r.URL.Path) - http.Error(w, "Invalid path or request", http.StatusNotFound) -} diff --git a/apps/glusterfs/app_cluster.go b/apps/glusterfs/app_cluster.go deleted file mode 100644 index b1e31db438..0000000000 --- a/apps/glusterfs/app_cluster.go +++ /dev/null @@ -1,163 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "encoding/json" - "net/http" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - "github.com/heketi/heketi/pkg/glusterfs/api" -) - -func (a *App) ClusterCreate(w http.ResponseWriter, r *http.Request) { - - // Create a new ClusterInfo - entry := NewClusterEntryFromRequest() - - // Add cluster to db - err := a.db.Update(func(tx *bolt.Tx) error { - err := entry.Save(tx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - return nil - - }) - if err != nil { - return - } - - // Send back we created it (as long as we did not fail) - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusCreated) - if err := json.NewEncoder(w).Encode(entry.Info); err != nil { - panic(err) - } -} - -func (a *App) ClusterList(w http.ResponseWriter, r *http.Request) { - - var list api.ClusterListResponse - - // Get all the cluster ids from the DB - err := a.db.View(func(tx *bolt.Tx) error { - var err error - - list.Clusters, err = ClusterList(tx) - if err != nil { - return err - } - - return nil - }) - - if err != nil { - logger.Err(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Send list back - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(list); err != nil { - panic(err) - } -} - -func (a *App) ClusterInfo(w http.ResponseWriter, r *http.Request) { - - // Get the id from the URL - vars := mux.Vars(r) - id := vars["id"] - - // Get info from db - var info *api.ClusterInfoResponse - err := a.db.View(func(tx *bolt.Tx) error { - - // Create a db entry from the id - entry, err := NewClusterEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, err.Error(), http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - // Create a response from the db entry - info, err = entry.NewClusterInfoResponse(tx) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return - } - - // Write msg - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(info); err != nil { - panic(err) - } - -} - -func (a *App) ClusterDelete(w http.ResponseWriter, r *http.Request) { - - // Get the id from the URL - vars := mux.Vars(r) - id := vars["id"] - - // Delete cluster from db - err := a.db.Update(func(tx *bolt.Tx) error { - - // Access cluster entry - entry, err := NewClusterEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, err.Error(), http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return logger.Err(err) - } - - err = entry.Delete(tx) - if err != nil { - if err == ErrConflict { - http.Error(w, entry.ConflictString(), http.StatusConflict) - } else { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - return err - } - - return nil - }) - if err != nil { - return - } - - // Update allocator hat the cluster has been removed - a.allocator.RemoveCluster(id) - - // Show that the key has been deleted - logger.Info("Deleted cluster [%s]", id) - - // Write msg - w.WriteHeader(http.StatusOK) -} diff --git a/apps/glusterfs/app_cluster_test.go b/apps/glusterfs/app_cluster_test.go deleted file mode 100644 index 1624b08bfc..0000000000 --- a/apps/glusterfs/app_cluster_test.go +++ /dev/null @@ -1,381 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "errors" - "fmt" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func init() { - // turn off logging - logger.SetLevel(utils.LEVEL_NOLOG) -} - -func TestClusterCreate(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // ClusterCreate JSON Request - request := []byte(`{ - }`) - - // Post nothing - r, err := http.Post(ts.URL+"/clusters", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusCreated) - - // Read JSON - var msg api.ClusterInfoResponse - err = utils.GetJsonFromResponse(r, &msg) - tests.Assert(t, err == nil) - - // Test JSON - tests.Assert(t, len(msg.Nodes) == 0) - tests.Assert(t, len(msg.Volumes) == 0) - - // Check that the data on the database is recorded correctly - var entry ClusterEntry - err = app.db.View(func(tx *bolt.Tx) error { - return entry.Unmarshal( - tx.Bucket([]byte(BOLTDB_BUCKET_CLUSTER)). - Get([]byte(msg.Id))) - }) - tests.Assert(t, err == nil) - - // Make sure they entries are euqal - tests.Assert(t, entry.Info.Id == msg.Id) - tests.Assert(t, len(entry.Info.Volumes) == 0) - tests.Assert(t, len(entry.Info.Nodes) == 0) -} - -func TestClusterList(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Save some objects in the database - numclusters := 5 - err := app.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(BOLTDB_BUCKET_CLUSTER)) - if b == nil { - return errors.New("Unable to open bucket") - } - - for i := 0; i < numclusters; i++ { - var entry ClusterEntry - - entry.Info.Id = fmt.Sprintf("%v", 5000+i) - buffer, err := entry.Marshal() - if err != nil { - return err - } - - err = b.Put([]byte(entry.Info.Id), buffer) - if err != nil { - return err - } - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Now that we have some data in the database, we can - // make a request for the clutser list - r, err := http.Get(ts.URL + "/clusters") - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, err == nil) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - // Read response - var msg api.ClusterListResponse - err = utils.GetJsonFromResponse(r, &msg) - tests.Assert(t, err == nil) - - // Thanks to BoltDB they come back in order - mockid := 5000 // This is the mock id value we set above - for _, id := range msg.Clusters { - tests.Assert(t, id == fmt.Sprintf("%v", mockid)) - mockid++ - } -} - -func TestClusterInfoIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Now that we have some data in the database, we can - // make a request for the clutser list - r, err := http.Get(ts.URL + "/clusters/12345") - tests.Assert(t, r.StatusCode == http.StatusNotFound) - tests.Assert(t, err == nil) -} - -func TestClusterInfo(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a new ClusterInfo - entry := NewClusterEntry() - entry.Info.Id = "123" - for _, node := range []string{"a1", "a2", "a3"} { - entry.NodeAdd(node) - } - for _, vol := range []string{"b1", "b2", "b3"} { - entry.VolumeAdd(vol) - } - - // Save the info in the database - err := app.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(BOLTDB_BUCKET_CLUSTER)) - if b == nil { - return errors.New("Unable to open bucket") - } - - buffer, err := entry.Marshal() - if err != nil { - return err - } - - err = b.Put([]byte(entry.Info.Id), buffer) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Now that we have some data in the database, we can - // make a request for the clutser list - r, err := http.Get(ts.URL + "/clusters/" + "123") - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, err == nil) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - // Read response - var msg api.ClusterInfoResponse - err = utils.GetJsonFromResponse(r, &msg) - tests.Assert(t, err == nil) - - // Check values are equal - tests.Assert(t, entry.Info.Id == msg.Id) - tests.Assert(t, entry.Info.Volumes[0] == msg.Volumes[0]) - tests.Assert(t, entry.Info.Volumes[1] == msg.Volumes[1]) - tests.Assert(t, entry.Info.Volumes[2] == msg.Volumes[2]) - tests.Assert(t, entry.Info.Nodes[0] == msg.Nodes[0]) - tests.Assert(t, entry.Info.Nodes[1] == msg.Nodes[1]) - tests.Assert(t, entry.Info.Nodes[2] == msg.Nodes[2]) -} - -func TestClusterDeleteBadId(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Delete cluster with no elements - req, err := http.NewRequest("DELETE", ts.URL+"/clusters/12345", nil) - tests.Assert(t, err == nil) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusNotFound) -} - -func TestClusterDelete(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create an entry with volumes and nodes - entries := make([]*ClusterEntry, 0) - entry := NewClusterEntry() - entry.Info.Id = "a1" - for _, node := range []string{"a1", "a2", "a3"} { - entry.NodeAdd(node) - } - for _, vol := range []string{"b1", "b2", "b3"} { - entry.VolumeAdd(vol) - } - entries = append(entries, entry) - - // Create an entry with only volumes - entry = NewClusterEntry() - entry.Info.Id = "a2" - for _, vol := range []string{"b1", "b2", "b3"} { - entry.VolumeAdd(vol) - } - entries = append(entries, entry) - - // Create an entry with only nodes - entry = NewClusterEntry() - entry.Info.Id = "a3" - for _, node := range []string{"a1", "a2", "a3"} { - entry.NodeAdd(node) - } - entries = append(entries, entry) - - // Create an empty entry - entry = NewClusterEntry() - entry.Info.Id = "000" - entries = append(entries, entry) - - // Save the info in the database - err := app.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(BOLTDB_BUCKET_CLUSTER)) - if b == nil { - return errors.New("Unable to open bucket") - } - - for _, entry := range entries { - buffer, err := entry.Marshal() - if err != nil { - return err - } - - err = b.Put([]byte(entry.Info.Id), buffer) - if err != nil { - return err - } - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Check that we cannot delete a cluster with elements - req, err := http.NewRequest("DELETE", ts.URL+"/clusters/"+"a1", nil) - tests.Assert(t, err == nil) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusConflict) - tests.Assert(t, utils.GetErrorFromResponse(r).Error() == entries[0].ConflictString()) - - // Check that we cannot delete a cluster with volumes - req, err = http.NewRequest("DELETE", ts.URL+"/clusters/"+"a2", nil) - tests.Assert(t, err == nil) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusConflict) - tests.Assert(t, utils.GetErrorFromResponse(r).Error() == entries[1].ConflictString()) - - // Check that we cannot delete a cluster with nodes - req, err = http.NewRequest("DELETE", ts.URL+"/clusters/"+"a3", nil) - tests.Assert(t, err == nil) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusConflict) - tests.Assert(t, utils.GetErrorFromResponse(r).Error() == entries[2].ConflictString()) - - // Delete cluster with no elements - req, err = http.NewRequest("DELETE", ts.URL+"/clusters/"+"000", nil) - tests.Assert(t, err == nil) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - - // Check database still has a1,a2, and a3, but not '000' - err = app.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(BOLTDB_BUCKET_CLUSTER)) - if b == nil { - return errors.New("Unable to open bucket") - } - - // Check that the ids are still in the database - for _, id := range []string{"a1", "a2", "a3"} { - buffer := b.Get([]byte(id)) - if buffer == nil { - return errors.New(fmt.Sprintf("Id %v not found", id)) - } - } - - // Check that the id 000 is no longer in the database - buffer := b.Get([]byte("000")) - if buffer != nil { - return errors.New(fmt.Sprintf("Id 000 still in database and was deleted")) - } - - return nil - - }) - tests.Assert(t, err == nil, err) - -} diff --git a/apps/glusterfs/app_config.go b/apps/glusterfs/app_config.go deleted file mode 100644 index db75c1ddde..0000000000 --- a/apps/glusterfs/app_config.go +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "encoding/json" - "io" - "os" - - "github.com/heketi/heketi/executors/kubeexec" - "github.com/heketi/heketi/executors/sshexec" -) - -type GlusterFSConfig struct { - DBfile string `json:"db"` - Executor string `json:"executor"` - Allocator string `json:"allocator"` - SshConfig sshexec.SshConfig `json:"sshexec"` - KubeConfig kubeexec.KubeConfig `json:"kubeexec"` - Loglevel string `json:"loglevel"` - - // advanced settings - BrickMaxSize int `json:"brick_max_size_gb"` - BrickMinSize int `json:"brick_min_size_gb"` - BrickMaxNum int `json:"max_bricks_per_volume"` -} - -type ConfigFile struct { - GlusterFS GlusterFSConfig `json:"glusterfs"` -} - -func loadConfiguration(configIo io.Reader) *GlusterFSConfig { - configParser := json.NewDecoder(configIo) - - var config ConfigFile - if err := configParser.Decode(&config); err != nil { - logger.LogError("Unable to parse config file: %v\n", - err.Error()) - return nil - } - - // Set environment variable to override configuration file - env := os.Getenv("HEKETI_EXECUTOR") - if env != "" { - config.GlusterFS.Executor = env - } - - return &config.GlusterFS -} diff --git a/apps/glusterfs/app_device.go b/apps/glusterfs/app_device.go deleted file mode 100644 index 7603cecc2d..0000000000 --- a/apps/glusterfs/app_device.go +++ /dev/null @@ -1,352 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "encoding/json" - "net/http" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" -) - -func (a *App) DeviceAdd(w http.ResponseWriter, r *http.Request) { - - var msg api.DeviceAddRequest - err := utils.GetJsonFromRequest(r, &msg) - if err != nil { - http.Error(w, "request unable to be parsed", 422) - return - } - - // Check the message has devices - if msg.Name == "" { - http.Error(w, "no devices added", http.StatusBadRequest) - return - } - - // Create device entry - device := NewDeviceEntryFromRequest(&msg) - - // Check the node is in the db - var node *NodeEntry - err = a.db.Update(func(tx *bolt.Tx) error { - var err error - node, err = NewNodeEntryFromId(tx, msg.NodeId) - if err == ErrNotFound { - http.Error(w, "Node id does not exist", http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - // Register device - err = device.Register(tx) - if err != nil { - http.Error(w, err.Error(), http.StatusConflict) - return err - } - - return nil - }) - if err != nil { - return - } - - // Log the devices are being added - logger.Info("Adding device %v to node %v", msg.Name, msg.NodeId) - - // Add device in an asynchronous function - a.asyncManager.AsyncHttpRedirectFunc(w, r, func() (seeOtherUrl string, e error) { - - defer func() { - if e != nil { - a.db.Update(func(tx *bolt.Tx) error { - err := device.Deregister(tx) - if err != nil { - logger.Err(err) - return err - } - - return nil - }) - } - }() - - // Setup device on node - info, err := a.executor.DeviceSetup(node.ManageHostName(), - device.Info.Name, device.Info.Id) - if err != nil { - return "", err - } - - // Create an entry for the device and set the size - device.StorageSet(info.Size) - device.SetExtentSize(info.ExtentSize) - - // Setup garbage collector on error - defer func() { - if e != nil { - a.executor.DeviceTeardown(node.ManageHostName(), - device.Info.Name, - device.Info.Id) - } - }() - - // Save on db - err = a.db.Update(func(tx *bolt.Tx) error { - - nodeEntry, err := NewNodeEntryFromId(tx, msg.NodeId) - if err != nil { - return err - } - - // Add device to node - nodeEntry.DeviceAdd(device.Info.Id) - - clusterEntry, err := NewClusterEntryFromId(tx, nodeEntry.Info.ClusterId) - if err != nil { - return err - } - - // Commit - err = nodeEntry.Save(tx) - if err != nil { - return err - } - - // Save drive - err = device.Save(tx) - if err != nil { - return err - } - - // Add to allocator - err = a.allocator.AddDevice(clusterEntry, nodeEntry, device) - if err != nil { - return err - } - - return nil - - }) - if err != nil { - return "", err - } - - logger.Info("Added device %v", msg.Name) - - // Done - // Returning a null string instructs the async manager - // to return http status of 204 (No Content) - return "", nil - }) - -} - -func (a *App) DeviceInfo(w http.ResponseWriter, r *http.Request) { - - // Get device id from URL - vars := mux.Vars(r) - id := vars["id"] - - // Get device information - var info *api.DeviceInfoResponse - err := a.db.View(func(tx *bolt.Tx) error { - entry, err := NewDeviceEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, "Id not found", http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - info, err = entry.NewInfoResponse(tx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - return nil - }) - if err != nil { - return - } - - // Write msg - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(info); err != nil { - panic(err) - } - -} - -func (a *App) DeviceDelete(w http.ResponseWriter, r *http.Request) { - // Get the id from the URL - vars := mux.Vars(r) - id := vars["id"] - - // Check request - var ( - device *DeviceEntry - node *NodeEntry - cluster *ClusterEntry - ) - err := a.db.View(func(tx *bolt.Tx) error { - var err error - // Access device entry - device, err = NewDeviceEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, err.Error(), http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return logger.Err(err) - } - - // Check if we can delete the device - if !device.IsDeleteOk() { - http.Error(w, device.ConflictString(), http.StatusConflict) - logger.LogError(device.ConflictString()) - return ErrConflict - } - - // Access node entry - node, err = NewNodeEntryFromId(tx, device.NodeId) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return logger.Err(err) - } - - // Save cluster to update allocator - cluster, err = NewClusterEntryFromId(tx, node.Info.ClusterId) - if err != nil { - return logger.Err(err) - } - - return nil - }) - if err != nil { - return - } - - // Delete device - logger.Info("Deleting device %v on node %v", device.Info.Id, device.NodeId) - a.asyncManager.AsyncHttpRedirectFunc(w, r, func() (string, error) { - - // Teardown device - err := a.executor.DeviceTeardown(node.ManageHostName(), - device.Info.Name, device.Info.Id) - if err != nil { - return "", err - } - - // Remove device from allocator - err = a.allocator.RemoveDevice(cluster, node, device) - if err != nil { - return "", err - } - - // Get info from db - err = a.db.Update(func(tx *bolt.Tx) error { - - // Access node entry - node, err := NewNodeEntryFromId(tx, device.NodeId) - if err == ErrNotFound { - logger.Critical( - "Node id %v pointed to by device %v, but it is not in the db", - device.NodeId, - device.Info.Id) - return err - } else if err != nil { - logger.Err(err) - return err - } - - // Delete device from node - node.DeviceDelete(device.Info.Id) - - // Save node - node.Save(tx) - - // Delete device from db - err = device.Delete(tx) - if err != nil { - logger.Err(err) - return err - } - - // Deregister device - err = device.Deregister(tx) - if err != nil { - logger.Err(err) - return err - } - - return nil - - }) - if err != nil { - return "", err - } - - // Show that the key has been deleted - logger.Info("Deleted node [%s]", id) - - return "", nil - }) - -} - -func (a *App) DeviceSetState(w http.ResponseWriter, r *http.Request) { - // Get the id from the URL - vars := mux.Vars(r) - id := vars["id"] - var device *DeviceEntry - - // Unmarshal JSON - var msg api.StateRequest - err := utils.GetJsonFromRequest(r, &msg) - if err != nil { - http.Error(w, "request unable to be parsed", 422) - return - } - - // Check for valid id, return immediately if not valid - err = a.db.View(func(tx *bolt.Tx) error { - device, err = NewDeviceEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, "Id not found", http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - return nil - }) - if err != nil { - return - } - - // Set state - a.asyncManager.AsyncHttpRedirectFunc(w, r, func() (string, error) { - err = device.SetState(a.db, a.executor, a.allocator, msg.State) - if err != nil { - return "", err - } - return "", nil - }) -} diff --git a/apps/glusterfs/app_device_test.go b/apps/glusterfs/app_device_test.go deleted file mode 100644 index 3f7160de53..0000000000 --- a/apps/glusterfs/app_device_test.go +++ /dev/null @@ -1,726 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "net/http" - "net/http/httptest" - "os" - "sort" - "testing" - "time" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func init() { - // turn off logging - logger.SetLevel(utils.LEVEL_NOLOG) -} - -func TestDeviceAddBadRequests(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // ClusterCreate JSON Request - request := []byte(`{ - bad json - }`) - - // Post bad JSON - r, err := http.Post(ts.URL+"/devices", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == 422) - - // Make a request with no device - request = []byte(`{ - "node" : "123" - }`) - - // Post bad JSON - r, err = http.Post(ts.URL+"/devices", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - - // Make a request with unknown node - request = []byte(`{ - "node" : "123", - "name" : "/dev/fake" - }`) - - // Post unknown node - r, err = http.Post(ts.URL+"/devices", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusNotFound) - -} - -func TestDeviceAddDelete(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Add Cluster then a Node on the cluster - // node - cluster := NewClusterEntryFromRequest() - nodereq := &api.NodeAddRequest{ - ClusterId: cluster.Info.Id, - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - node := NewNodeEntryFromRequest(nodereq) - cluster.NodeAdd(node.Info.Id) - - // Save information in the db - err := app.db.Update(func(tx *bolt.Tx) error { - err := cluster.Save(tx) - if err != nil { - return err - } - - err = node.Save(tx) - if err != nil { - return err - } - return nil - }) - tests.Assert(t, err == nil) - - // Create a request to a device - request := []byte(`{ - "node" : "` + node.Info.Id + `", - "name" : "/dev/fake1" - }`) - - // Add device using POST - r, err := http.Post(ts.URL+"/devices", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Add the same device. It should conflict - r, err = http.Post(ts.URL+"/devices", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusConflict) - - // Add a second device - request = []byte(`{ - "node" : "` + node.Info.Id + `", - "name" : "/dev/fake2" - }`) - - // Add device using POST - r, err = http.Post(ts.URL+"/devices", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check db to make sure devices where added - devicemap := make(map[string]*DeviceEntry) - err = app.db.View(func(tx *bolt.Tx) error { - node, err = NewNodeEntryFromId(tx, node.Info.Id) - if err != nil { - return err - } - - for _, id := range node.Devices { - device, err := NewDeviceEntryFromId(tx, id) - if err != nil { - return err - } - devicemap[device.Info.Name] = device - } - - return nil - }) - tests.Assert(t, err == nil) - - val, ok := devicemap["/dev/fake1"] - tests.Assert(t, ok) - tests.Assert(t, val.Info.Name == "/dev/fake1") - tests.Assert(t, len(val.Bricks) == 0) - - val, ok = devicemap["/dev/fake2"] - tests.Assert(t, ok) - tests.Assert(t, val.Info.Name == "/dev/fake2") - tests.Assert(t, len(val.Bricks) == 0) - - // Add some bricks to check if delete conflicts works - fakeid := devicemap["/dev/fake1"].Info.Id - err = app.db.Update(func(tx *bolt.Tx) error { - device, err := NewDeviceEntryFromId(tx, fakeid) - if err != nil { - return err - } - - device.BrickAdd("123") - device.BrickAdd("456") - return device.Save(tx) - }) - tests.Assert(t, err == nil) - - // Now delete device and check for conflict - req, err := http.NewRequest("DELETE", ts.URL+"/devices/"+fakeid, nil) - tests.Assert(t, err == nil) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusConflict) - tests.Assert(t, utils.GetErrorFromResponse(r).Error() == devicemap["/dev/fake1"].ConflictString()) - - // Check the db is still intact - err = app.db.View(func(tx *bolt.Tx) error { - device, err := NewDeviceEntryFromId(tx, fakeid) - if err != nil { - return err - } - - node, err = NewNodeEntryFromId(tx, device.NodeId) - if err != nil { - return err - } - - return nil - }) - tests.Assert(t, err == nil) - tests.Assert(t, utils.SortedStringHas(node.Devices, fakeid)) - - // Node delete bricks from the device - err = app.db.Update(func(tx *bolt.Tx) error { - device, err := NewDeviceEntryFromId(tx, fakeid) - if err != nil { - return err - } - - device.BrickDelete("123") - device.BrickDelete("456") - return device.Save(tx) - }) - tests.Assert(t, err == nil) - - // Delete device - req, err = http.NewRequest("DELETE", ts.URL+"/devices/"+fakeid, nil) - tests.Assert(t, err == nil) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Wait for deletion - for { - r, err := http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - continue - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check db - err = app.db.View(func(tx *bolt.Tx) error { - _, err := NewDeviceEntryFromId(tx, fakeid) - return err - }) - tests.Assert(t, err == ErrNotFound) - - // Check node does not have the device - err = app.db.View(func(tx *bolt.Tx) error { - node, err = NewNodeEntryFromId(tx, node.Info.Id) - return err - }) - tests.Assert(t, err == nil) - tests.Assert(t, !utils.SortedStringHas(node.Devices, fakeid)) - - // Check the registration of the device has been removed, - // and the device can be added again - request = []byte(`{ - "node" : "` + node.Info.Id + `", - "name" : "/dev/fake1" - }`) - r, err = http.Post(ts.URL+"/devices", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } -} - -func TestDeviceAddCleansUp(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Add Cluster then a Node on the cluster - // node - cluster := NewClusterEntryFromRequest() - nodereq := &api.NodeAddRequest{ - ClusterId: cluster.Info.Id, - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - node := NewNodeEntryFromRequest(nodereq) - cluster.NodeAdd(node.Info.Id) - - // Save information in the db - err := app.db.Update(func(tx *bolt.Tx) error { - err := cluster.Save(tx) - if err != nil { - return err - } - - err = node.Save(tx) - if err != nil { - return err - } - return nil - }) - tests.Assert(t, err == nil) - - // Mock the device setup to return an error, which will - // cause the cleanup. - deviceSetupFn := app.xo.MockDeviceSetup - app.xo.MockDeviceSetup = func(host, device, vgid string) (*executors.DeviceInfo, error) { - return nil, ErrDbAccess - } - - // Create a request to a device - request := []byte(`{ - "node" : "` + node.Info.Id + `", - "name" : "/dev/fake1" - }`) - - // Add device using POST - r, err := http.Post(ts.URL+"/devices", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode != http.StatusNoContent) - break - } - } - - // Let's reset the mocked function - app.xo.MockDeviceSetup = deviceSetupFn - - // Now it should work - // Add device using POST - r, err = http.Post(ts.URL+"/devices", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } -} - -func TestDeviceInfoIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Get unknown device id - r, err := http.Get(ts.URL + "/devices/123456789") - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusNotFound) - -} - -func TestDeviceInfo(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a device to save in the db - device := NewDeviceEntry() - device.Info.Id = "abc" - device.Info.Name = "/dev/fake1" - device.NodeId = "def" - device.StorageSet(10000) - device.StorageAllocate(1000) - - // Save device in the db - err := app.db.Update(func(tx *bolt.Tx) error { - return device.Save(tx) - }) - tests.Assert(t, err == nil) - - // Get device information - r, err := http.Get(ts.URL + "/devices/" + device.Info.Id) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - var info api.DeviceInfoResponse - err = utils.GetJsonFromResponse(r, &info) - tests.Assert(t, info.Id == device.Info.Id) - tests.Assert(t, info.Name == device.Info.Name) - tests.Assert(t, info.State == "online") - tests.Assert(t, info.Storage.Free == device.Info.Storage.Free) - tests.Assert(t, info.Storage.Used == device.Info.Storage.Used) - tests.Assert(t, info.Storage.Total == device.Info.Storage.Total) - -} - -func TestDeviceDeleteErrors(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a device to save in the db - device := NewDeviceEntry() - device.Info.Id = "abc" - device.Info.Name = "/dev/fake1" - device.NodeId = "def" - device.StorageSet(10000) - device.StorageAllocate(1000) - - // Save device in the db - err := app.db.Update(func(tx *bolt.Tx) error { - return device.Save(tx) - }) - tests.Assert(t, err == nil) - - // Delete unknown id - req, err := http.NewRequest("DELETE", ts.URL+"/devices/123", nil) - tests.Assert(t, err == nil) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusNotFound) - - // Delete device without a node there.. that's probably a really - // bad situation - req, err = http.NewRequest("DELETE", ts.URL+"/devices/"+device.Info.Id, nil) - tests.Assert(t, err == nil) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusInternalServerError) -} - -func TestDeviceState(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create mock allocator - mockAllocator := NewMockAllocator(app.db) - app.allocator = mockAllocator - - // Create a client - c := client.NewClientNoAuth(ts.URL) - tests.Assert(t, c != nil) - - // Create Cluster - cluster, err := c.ClusterCreate() - tests.Assert(t, err == nil) - - // Create Node - nodeReq := &api.NodeAddRequest{ - Zone: 1, - ClusterId: cluster.Id, - } - nodeReq.Hostnames.Manage = sort.StringSlice{"manage.host"} - nodeReq.Hostnames.Storage = sort.StringSlice{"storage.host"} - node, err := c.NodeAdd(nodeReq) - tests.Assert(t, err == nil) - - // Add device - deviceReq := &api.DeviceAddRequest{} - deviceReq.Name = "/dev/fake1" - deviceReq.NodeId = node.Id - - err = c.DeviceAdd(deviceReq) - tests.Assert(t, err == nil) - - // Get node information again - node, err = c.NodeInfo(node.Id) - tests.Assert(t, err == nil) - - // Get device information - deviceId := node.DevicesInfo[0].Id - device, err := c.DeviceInfo(deviceId) - tests.Assert(t, err == nil) - - // Get info - deviceInfo, err := c.DeviceInfo(device.Id) - tests.Assert(t, err == nil) - tests.Assert(t, deviceInfo.State == "online") - - // Check that the device is in the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[cluster.Id][0] == device.Id) - - // Set offline - request := []byte(`{ - "state" : "offline" - }`) - r, err := http.Post(ts.URL+"/devices/"+device.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - // Check it was removed from the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 0) - - // Get Device Info - r, err = http.Get(ts.URL + "/devices/" + device.Id) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - var info api.DeviceInfoResponse - err = utils.GetJsonFromResponse(r, &info) - tests.Assert(t, info.Id == device.Id) - tests.Assert(t, info.Name == device.Name) - tests.Assert(t, info.State == "offline") - tests.Assert(t, info.Storage.Free == device.Storage.Free) - tests.Assert(t, info.Storage.Used == device.Storage.Used) - tests.Assert(t, info.Storage.Total == device.Storage.Total) - - // Set online again - request = []byte(`{ - "state" : "online" - }`) - r, err = http.Post(ts.URL+"/devices/"+device.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check that the device is in the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[cluster.Id][0] == device.Id) - - // Get Device Info - r, err = http.Get(ts.URL + "/devices/" + device.Id) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - err = utils.GetJsonFromResponse(r, &info) - tests.Assert(t, info.Id == device.Id) - tests.Assert(t, info.Name == device.Name) - tests.Assert(t, info.State == "online") - tests.Assert(t, info.Storage.Free == device.Storage.Free) - tests.Assert(t, info.Storage.Used == device.Storage.Used) - tests.Assert(t, info.Storage.Total == device.Storage.Total) - - // Set to unknown state - request = []byte(`{ - "state" : "blah" - }`) - r, err = http.Post(ts.URL+"/devices/"+device.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusInternalServerError) - break - } - } - - // Check that the device is still in the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[cluster.Id][0] == device.Id) - - // Make sure the state did not change - r, err = http.Get(ts.URL + "/devices/" + device.Id) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - err = utils.GetJsonFromResponse(r, &info) - tests.Assert(t, info.Id == device.Id) - tests.Assert(t, info.Name == device.Name) - tests.Assert(t, info.State == "online") - tests.Assert(t, info.Storage.Free == device.Storage.Free) - tests.Assert(t, info.Storage.Used == device.Storage.Used) - tests.Assert(t, info.Storage.Total == device.Storage.Total) -} diff --git a/apps/glusterfs/app_middleware.go b/apps/glusterfs/app_middleware.go deleted file mode 100644 index 199139b743..0000000000 --- a/apps/glusterfs/app_middleware.go +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (c) 2017 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "net/http" - "strings" - - jwt "github.com/dgrijalva/jwt-go" - "github.com/gorilla/context" - "github.com/urfave/negroni" - - "github.com/heketi/heketi/pkg/kubernetes" -) - -var ( - kubeBackupDbToSecret = kubernetes.KubeBackupDbToSecret -) - -// Authorization function -func (a *App) Auth(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - - // Value saved by the JWT middleware. - data := context.Get(r, "jwt") - - // Need to change from interface{} to the jwt.Token type - token := data.(*jwt.Token) - claims := token.Claims.(jwt.MapClaims) - - // Check access - if "user" == claims["iss"] && r.URL.Path != "/volumes" { - http.Error(w, "Administrator access required", http.StatusUnauthorized) - return - } - - // Everything is clean - next(w, r) -} - -// Backup database to a secret -func (a *App) BackupToKubernetesSecret( - w http.ResponseWriter, - r *http.Request, - next http.HandlerFunc) { - - // Call the next middleware first - // Wrap it in a negroni ResponseWriter because for some reason - // the Golang http ResponseWriter does not provide access to - // the HttpStatus. - responsew := negroni.NewResponseWriter(w) - next(responsew, r) - - // Backup for everything except GET methods which do not - // provide information on asynchronous completion request - if !a.isAsyncDone(responsew, r) && r.Method == http.MethodGet { - return - } - - // Backup database - err := kubeBackupDbToSecret(a.db) - if err != nil { - logger.Err(err) - } else { - logger.Info("Backup successful") - } -} - -func (a *App) isAsyncDone( - w negroni.ResponseWriter, - r *http.Request) bool { - - return r.Method == http.MethodGet && - strings.HasPrefix(r.URL.Path, ASYNC_ROUTE) && - (w.Status() == http.StatusNoContent || - w.Status() == http.StatusSeeOther) -} diff --git a/apps/glusterfs/app_middleware_test.go b/apps/glusterfs/app_middleware_test.go deleted file mode 100644 index 1342ca5bfd..0000000000 --- a/apps/glusterfs/app_middleware_test.go +++ /dev/null @@ -1,194 +0,0 @@ -// -// Copyright (c) 2017 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "compress/gzip" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func init() { - logger.SetLevel(utils.LEVEL_NOLOG) -} - -func TestBackupToKubeSecretMaxSize(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - - // Create a Max cluster size when using Secrets - // Max secret size is 1024M in Kubernetes - // Max = 12000 Drives - err := setupSampleDbWithTopology(app, - 1, // clusters - 120, // nodes_per_cluster - 100, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - app.Close() - - // Gzip database - var b bytes.Buffer - gz := gzip.NewWriter(&b) - dbData, err := ioutil.ReadFile(tmpfile) - tests.Assert(t, err == nil) - _, err = gz.Write(dbData) - tests.Assert(t, err == nil) - err = gz.Close() - tests.Assert(t, err == nil) - tests.Assert(t, b.Len() < 1024*MB) -} - -func TestBackupToKubeSecretMaxSizeFailure(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - - // Create a Max cluster size when using Secrets - // Max secret size is 1024M in Kubernetes - // Max = 12000 Drives - // So here pick 24000 Drives in two clusters - err := setupSampleDbWithTopology(app, - 2, // clusters - 120, // nodes_per_cluster - 100, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - app.Close() - - // Gzip database - var b bytes.Buffer - gz := gzip.NewWriter(&b) - dbData, err := ioutil.ReadFile(tmpfile) - tests.Assert(t, err == nil) - _, err = gz.Write(dbData) - tests.Assert(t, err == nil) - err = gz.Close() - tests.Assert(t, err == nil) - tests.Assert(t, b.Len() > 1024*MB) -} - -func TestBackupToKubeSecretBackupOnNonGet(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - incluster_count := 0 - defer tests.Patch(&kubeBackupDbToSecret, func(db *bolt.DB) error { - incluster_count++ - return nil - }).Restore() - - // Backup on Post - r, err := http.NewRequest(http.MethodPost, "http://mytest.com/hello", nil) - tests.Assert(t, err == nil) - w := httptest.NewRecorder() - app.BackupToKubernetesSecret(w, r, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - tests.Assert(t, incluster_count == 1) - - // Backup on PUT - r, err = http.NewRequest(http.MethodPut, "http://mytest.com/hello", nil) - tests.Assert(t, err == nil) - w = httptest.NewRecorder() - app.BackupToKubernetesSecret(w, r, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - tests.Assert(t, incluster_count == 2) - - // Backup on DELETE - r, err = http.NewRequest(http.MethodDelete, "http://mytest.com/hello", nil) - tests.Assert(t, err == nil) - w = httptest.NewRecorder() - app.BackupToKubernetesSecret(w, r, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - tests.Assert(t, incluster_count == 3) -} - -func TestBackupToKubeSecretBackupOnGet(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - incluster_count := 0 - defer tests.Patch(&kubeBackupDbToSecret, func(db *bolt.DB) error { - incluster_count++ - return nil - }).Restore() - - // No backups on GET to non-/queue URLs - r, err := http.NewRequest(http.MethodGet, "http://mytest.com/hello", nil) - tests.Assert(t, err == nil) - w := httptest.NewRecorder() - app.BackupToKubernetesSecret(w, r, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - tests.Assert(t, incluster_count == 0) - - // No backups on GET on /queue URL where the Status is still 200 (OK) - // which means that the resource is pending - r, err = http.NewRequest(http.MethodGet, "http://mytest.com"+ASYNC_ROUTE, nil) - tests.Assert(t, err == nil) - w = httptest.NewRecorder() - app.BackupToKubernetesSecret(w, r, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - tests.Assert(t, incluster_count == 0) - - // No backups on GET on /queue URL where the Status is error - r, err = http.NewRequest(http.MethodGet, "http://mytest.com"+ASYNC_ROUTE, nil) - tests.Assert(t, err == nil) - w = httptest.NewRecorder() - app.BackupToKubernetesSecret(w, r, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - }) - tests.Assert(t, incluster_count == 0) - - // Backup when a GET on /queue gets a Done - r, err = http.NewRequest(http.MethodGet, "http://mytest.com"+ASYNC_ROUTE, nil) - tests.Assert(t, err == nil) - w = httptest.NewRecorder() - app.BackupToKubernetesSecret(w, r, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNoContent) - }) - tests.Assert(t, incluster_count == 1) - - // Backup when a GET on /queue gets a See Other - r, err = http.NewRequest(http.MethodGet, "http://mytest.com"+ASYNC_ROUTE, nil) - tests.Assert(t, err == nil) - w = httptest.NewRecorder() - app.BackupToKubernetesSecret(w, r, func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusSeeOther) - }) - tests.Assert(t, incluster_count == 2) -} diff --git a/apps/glusterfs/app_node.go b/apps/glusterfs/app_node.go deleted file mode 100644 index 5562ec12e7..0000000000 --- a/apps/glusterfs/app_node.go +++ /dev/null @@ -1,358 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "encoding/json" - "net/http" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" -) - -func (a *App) NodeAdd(w http.ResponseWriter, r *http.Request) { - var msg api.NodeAddRequest - - err := utils.GetJsonFromRequest(r, &msg) - if err != nil { - http.Error(w, "request unable to be parsed", 422) - return - } - - // Check information in JSON request - if len(msg.Hostnames.Manage) == 0 { - http.Error(w, "Manage hostname missing", http.StatusBadRequest) - return - } - if len(msg.Hostnames.Storage) == 0 { - http.Error(w, "Storage hostname missing", http.StatusBadRequest) - return - } - - // Zone value of 0 is not allowed because we do not know - // if it is because it was set to zero, or it is the default - // value used for missing 'zone' in JSON - if msg.Zone == 0 { - http.Error(w, "Zone cannot be zero or value is missing", http.StatusBadRequest) - return - } - - // Check for correct values - for _, name := range append(msg.Hostnames.Manage, msg.Hostnames.Storage...) { - if name == "" { - http.Error(w, "Hostname cannot be an empty string", http.StatusBadRequest) - return - } - } - - // Create a node entry - node := NewNodeEntryFromRequest(&msg) - - // Get cluster and peer node - var cluster *ClusterEntry - var peer_node *NodeEntry - err = a.db.Update(func(tx *bolt.Tx) error { - var err error - cluster, err = NewClusterEntryFromId(tx, msg.ClusterId) - if err == ErrNotFound { - http.Error(w, "Cluster id does not exist", http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - // Register node - err = node.Register(tx) - if err != nil { - http.Error(w, err.Error(), http.StatusConflict) - return err - } - - // Get a node in the cluster to execute the Gluster peer command - // only if there is more than one node - if len(cluster.Info.Nodes) > 0 { - peer_node, err = cluster.NodeEntryFromClusterIndex(tx, 0) - if err != nil { - logger.Err(err) - return err - } - } - - return nil - }) - if err != nil { - return - } - - // Add node - logger.Info("Adding node %v", node.ManageHostName()) - a.asyncManager.AsyncHttpRedirectFunc(w, r, func() (seeother string, e error) { - - // Cleanup in case of failure - defer func() { - if e != nil { - a.db.Update(func(tx *bolt.Tx) error { - node.Deregister(tx) - return nil - }) - } - }() - - // Peer probe if there is at least one other node - // TODO: What happens if the peer_node is not responding.. we need to choose another. - if peer_node != nil { - err := a.executor.PeerProbe(peer_node.ManageHostName(), node.StorageHostName()) - if err != nil { - return "", err - } - } - - // Add node entry into the db - err = a.db.Update(func(tx *bolt.Tx) error { - cluster, err := NewClusterEntryFromId(tx, msg.ClusterId) - if err == ErrNotFound { - http.Error(w, "Cluster id does not exist", http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - // Add node to cluster - cluster.NodeAdd(node.Info.Id) - - // Save cluster - err = cluster.Save(tx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - // Save node - err = node.Save(tx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - return nil - - }) - if err != nil { - return "", err - } - logger.Info("Added node " + node.Info.Id) - return "/nodes/" + node.Info.Id, nil - }) -} - -func (a *App) NodeInfo(w http.ResponseWriter, r *http.Request) { - - // Get node id from URL - vars := mux.Vars(r) - id := vars["id"] - - // Get Node information - var info *api.NodeInfoResponse - err := a.db.View(func(tx *bolt.Tx) error { - entry, err := NewNodeEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, "Id not found", http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - info, err = entry.NewInfoReponse(tx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - return nil - }) - if err != nil { - return - } - - // Write msg - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(info); err != nil { - panic(err) - } - -} - -func (a *App) NodeDelete(w http.ResponseWriter, r *http.Request) { - // Get the id from the URL - vars := mux.Vars(r) - id := vars["id"] - - // Get node info - var ( - peer_node, node *NodeEntry - cluster *ClusterEntry - ) - err := a.db.View(func(tx *bolt.Tx) error { - - // Access node entry - var err error - node, err = NewNodeEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, err.Error(), http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return logger.Err(err) - } - - // Check the node can be deleted - if !node.IsDeleteOk() { - http.Error(w, node.ConflictString(), http.StatusConflict) - logger.LogError(node.ConflictString()) - return ErrConflict - } - - // Access cluster information and peer node - cluster, err = NewClusterEntryFromId(tx, node.Info.ClusterId) - if err == ErrNotFound { - http.Error(w, "Cluster id does not exist", http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return logger.Err(err) - } - - // Get a node in the cluster to execute the Gluster peer command - // If it only has one in the list, then there is no need to do a - // peer detach. - if len(cluster.Info.Nodes) > 1 { - for index := range cluster.Info.Nodes { - peer_node, err = cluster.NodeEntryFromClusterIndex(tx, index) - if err != nil { - return logger.Err(err) - } - - // Cannot peer detach from the same node, we need to execute - // the command from another node - if peer_node.Info.Id != node.Info.Id { - break - } - } - } - return nil - }) - if err != nil { - return - } - - // Delete node asynchronously - logger.Info("Deleting node %v [%v]", node.ManageHostName(), node.Info.Id) - a.asyncManager.AsyncHttpRedirectFunc(w, r, func() (string, error) { - - // Remove from trusted pool - if peer_node != nil { - err := a.executor.PeerDetach(peer_node.ManageHostName(), node.StorageHostName()) - if err != nil { - return "", err - } - } - - // Remove from db - err = a.db.Update(func(tx *bolt.Tx) error { - - // Get Cluster - cluster, err := NewClusterEntryFromId(tx, node.Info.ClusterId) - if err == ErrNotFound { - logger.Critical("Cluster id %v is expected be in db. Pointed to by node %v", - node.Info.ClusterId, - node.Info.Id) - return err - } else if err != nil { - logger.Err(err) - return err - } - cluster.NodeDelete(node.Info.Id) - - // Save cluster - err = cluster.Save(tx) - if err != nil { - logger.Err(err) - return err - } - - // Remove hostnames - node.Deregister(tx) - - // Delete node from db - err = node.Delete(tx) - if err != nil { - logger.Err(err) - return err - } - - return nil - - }) - if err != nil { - return "", err - } - // Show that the key has been deleted - logger.Info("Deleted node [%s]", id) - - return "", nil - - }) -} - -func (a *App) NodeSetState(w http.ResponseWriter, r *http.Request) { - // Get the id from the URL - vars := mux.Vars(r) - id := vars["id"] - var node *NodeEntry - - // Unmarshal JSON - var msg api.StateRequest - err := utils.GetJsonFromRequest(r, &msg) - if err != nil { - http.Error(w, "request unable to be parsed", 422) - return - } - - // Check state is supported - err = a.db.View(func(tx *bolt.Tx) error { - node, err = NewNodeEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, "Id not found", http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - return nil - }) - - // Set state - a.asyncManager.AsyncHttpRedirectFunc(w, r, func() (string, error) { - err = node.SetState(a.db, a.executor, a.allocator, msg.State) - if err != nil { - return "", err - } - return "", nil - - }) - -} diff --git a/apps/glusterfs/app_node_test.go b/apps/glusterfs/app_node_test.go deleted file mode 100644 index 519315b502..0000000000 --- a/apps/glusterfs/app_node_test.go +++ /dev/null @@ -1,1212 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "errors" - "net/http" - "net/http/httptest" - "os" - "sort" - "strings" - "testing" - "time" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func init() { - // turn off logging - logger.SetLevel(utils.LEVEL_NOLOG) -} - -func TestNodeAddBadRequests(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // ClusterCreate JSON Request - request := []byte(`{ - bad json - }`) - - // Post bad JSON - r, err := http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == 422) - - // Make a request without hostnames - request = []byte(`{ - "cluster" : "123", - "hostname" : {} - }`) - - // Post bad JSON - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - - // Make a request with only manage hostname - request = []byte(`{ - "cluster" : "123", - "hostnames" : { - "manage" : [ "manage.hostname.com" ] - }, - "zone" : 10 - }`) - - // Post bad JSON - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest, *r) - - // Make a request with only storage hostname - request = []byte(`{ - "cluster" : "123", - "hostnames" : { - "storage" : [ "storage.hostname.com" ] - }, - "zone" : 10 - }`) - - // Post bad JSON - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - - // Make a request where the hostnames are empty strings - request = []byte(`{ - "cluster" : "123", - "hostnames" : { - "storage" : [ "" ], - "manage" : [ "" ] - }, - "zone" : 10 - }`) - - // Check that it returns that the cluster id is not found - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - s, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "empty string")) - - // Make a request where the zone is missing - request = []byte(`{ - "cluster" : "123", - "hostnames" : { - "storage" : [ "storage.hostname.com" ], - "manage" : [ "manage.hostname.com" ] - } - }`) - - // Check that it returns that the cluster id is not found - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - s, err = utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "Zone cannot be zero")) - - // Make a request where the cluster id does not exist - request = []byte(`{ - "cluster" : "123", - "hostnames" : { - "storage" : [ "storage.hostname.com" ], - "manage" : [ "manage.hostname.com" ] - }, - "zone" : 10 - }`) - - // Check that it returns that the cluster id is not found - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusNotFound, r.StatusCode) -} - -func TestPeerProbe(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // ClusterCreate JSON Request - request := []byte(`{ - }`) - - // Post nothing - r, err := http.Post(ts.URL+"/clusters", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusCreated) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - // Read cluster information - var clusterinfo api.ClusterInfoResponse - err = utils.GetJsonFromResponse(r, &clusterinfo) - tests.Assert(t, err == nil) - - // Override mock to check if the peer function was called - probe_called := false - app.xo.MockPeerProbe = func(exec_host, newnode string) error { - probe_called = true - return nil - } - - // Create node on this cluster - request = []byte(`{ - "cluster" : "` + clusterinfo.Id + `", - "hostnames" : { - "storage" : [ "storage0.hostname.com" ], - "manage" : [ "manage0.hostname.com" ] - }, - "zone" : 1 - }`) - - // Create node - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - if r.ContentLength <= 0 { - time.Sleep(time.Millisecond * 10) - continue - } else { - // Should have node information here - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - tests.Assert(t, err == nil) - break - } - } - tests.Assert(t, probe_called == false) - - // Now add another and check that probe was called - request = []byte(`{ - "cluster" : "` + clusterinfo.Id + `", - "hostnames" : { - "storage" : [ "storage1.hostname.com" ], - "manage" : [ "manage1.hostname.com" ] - }, - "zone" : 1 - }`) - - // Create node - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - if r.ContentLength <= 0 { - time.Sleep(time.Millisecond * 10) - continue - } else { - // Should have node information here - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - tests.Assert(t, err == nil) - break - } - } - tests.Assert(t, probe_called == true) -} - -func TestNodeAddDelete(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // ClusterCreate JSON Request - request := []byte(`{ - }`) - - // Post nothing - r, err := http.Post(ts.URL+"/clusters", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusCreated) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - // Read cluster information - var clusterinfo api.ClusterInfoResponse - err = utils.GetJsonFromResponse(r, &clusterinfo) - tests.Assert(t, err == nil) - - // Create node on this cluster - request = []byte(`{ - "cluster" : "` + clusterinfo.Id + `", - "hostnames" : { - "storage" : [ "storage.hostname.com" ], - "manage" : [ "manage.hostname.com" ] - }, - "zone" : 1 - }`) - - // Create node - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - var node api.NodeInfoResponse - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - if r.ContentLength <= 0 { - time.Sleep(time.Millisecond * 10) - continue - } else { - // Should have node information here - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - err = utils.GetJsonFromResponse(r, &node) - tests.Assert(t, err == nil) - break - } - } - tests.Assert(t, len(node.Id) > 0) - tests.Assert(t, len(node.Hostnames.Manage) == 1) - tests.Assert(t, len(node.Hostnames.Storage) == 1) - tests.Assert(t, node.Hostnames.Manage[0] == "manage.hostname.com") - tests.Assert(t, node.Hostnames.Storage[0] == "storage.hostname.com") - tests.Assert(t, node.Zone == 1) - tests.Assert(t, node.ClusterId == clusterinfo.Id) - tests.Assert(t, len(node.DevicesInfo) == 0) - - // Check that the node has registered - err = app.db.View(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte(BOLTDB_BUCKET_NODE)) - tests.Assert(t, b != nil) - - val := b.Get([]byte("STORAGE" + node.Hostnames.Storage[0])) - tests.Assert(t, string(val) == node.Id) - - val = b.Get([]byte("MANAGE" + node.Hostnames.Manage[0])) - tests.Assert(t, string(val) == node.Id) - - return nil - }) - tests.Assert(t, err == nil) - - //---- OK, now it should have been registered - // now let's add it again - // It should return a conflict - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusConflict) - - // Check Cluster has node - r, err = http.Get(ts.URL + "/clusters/" + clusterinfo.Id) - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, err == nil) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - err = utils.GetJsonFromResponse(r, &clusterinfo) - tests.Assert(t, len(clusterinfo.Nodes) == 1) - tests.Assert(t, clusterinfo.Nodes[0] == node.Id) - - // Check the data is in the database correctly - var entry *NodeEntry - err = app.db.View(func(tx *bolt.Tx) error { - entry, err = NewNodeEntryFromId(tx, node.Id) - return err - }) - tests.Assert(t, err == nil) - tests.Assert(t, entry != nil) - tests.Assert(t, entry.Info.Id == node.Id) - tests.Assert(t, len(entry.Info.Hostnames.Manage) == 1) - tests.Assert(t, len(entry.Info.Hostnames.Storage) == 1) - tests.Assert(t, entry.Info.Hostnames.Manage[0] == node.Hostnames.Manage[0]) - tests.Assert(t, entry.Info.Hostnames.Storage[0] == node.Hostnames.Storage[0]) - tests.Assert(t, len(entry.Devices) == 0) - - // Add some devices to check if delete conflict works - err = app.db.Update(func(tx *bolt.Tx) error { - entry, err = NewNodeEntryFromId(tx, node.Id) - if err != nil { - return err - } - - entry.DeviceAdd("123") - entry.DeviceAdd("456") - return entry.Save(tx) - }) - tests.Assert(t, err == nil) - - // Now delete node and check for conflict - req, err := http.NewRequest("DELETE", ts.URL+"/nodes/"+node.Id, nil) - tests.Assert(t, err == nil) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusConflict) - tests.Assert(t, utils.GetErrorFromResponse(r).Error() == entry.ConflictString()) - - // Check that nothing has changed in the db - var cluster *ClusterEntry - err = app.db.View(func(tx *bolt.Tx) error { - entry, err = NewNodeEntryFromId(tx, node.Id) - if err != nil { - return err - } - - cluster, err = NewClusterEntryFromId(tx, entry.Info.ClusterId) - if err != nil { - return err - } - - return nil - }) - tests.Assert(t, err == nil) - tests.Assert(t, utils.SortedStringHas(cluster.Info.Nodes, node.Id)) - - // Node delete the drives - err = app.db.Update(func(tx *bolt.Tx) error { - entry, err = NewNodeEntryFromId(tx, node.Id) - if err != nil { - return err - } - - entry.DeviceDelete("123") - entry.DeviceDelete("456") - return entry.Save(tx) - }) - tests.Assert(t, err == nil) - - // Now delete node - req, err = http.NewRequest("DELETE", ts.URL+"/nodes/"+node.Id, nil) - tests.Assert(t, err == nil) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Wait for deletion - for { - r, err := http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - continue - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check db to make sure key is removed - err = app.db.View(func(tx *bolt.Tx) error { - _, err = NewNodeEntryFromId(tx, node.Id) - return err - }) - tests.Assert(t, err == ErrNotFound) - - // Check the cluster does not have this node id - r, err = http.Get(ts.URL + "/clusters/" + clusterinfo.Id) - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, err == nil) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - err = utils.GetJsonFromResponse(r, &clusterinfo) - tests.Assert(t, len(clusterinfo.Nodes) == 0) - - // It should have deregistered the node - // We should be able to add it again - r, err = http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - if r.ContentLength <= 0 { - time.Sleep(time.Millisecond * 10) - continue - } else { - // Should have node information here - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - err = utils.GetJsonFromResponse(r, &node) - tests.Assert(t, err == nil) - break - } - } -} - -func TestNodeInfoIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Get unknown node id - r, err := http.Get(ts.URL + "/nodes/123456789") - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusNotFound) - -} - -func TestNodeInfo(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a node to save in the db - node := NewNodeEntry() - node.Info.Id = "abc" - node.Info.ClusterId = "123" - node.Info.Hostnames.Manage = sort.StringSlice{"manage.system"} - node.Info.Hostnames.Storage = sort.StringSlice{"storage.system"} - node.Info.Zone = 10 - - // Save node in the db - err := app.db.Update(func(tx *bolt.Tx) error { - return node.Save(tx) - }) - tests.Assert(t, err == nil) - - // Get unknown node id - r, err := http.Get(ts.URL + "/nodes/" + node.Info.Id) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - var info api.NodeInfoResponse - err = utils.GetJsonFromResponse(r, &info) - tests.Assert(t, info.Id == node.Info.Id) - tests.Assert(t, info.Hostnames.Manage[0] == node.Info.Hostnames.Manage[0]) - tests.Assert(t, len(info.Hostnames.Manage) == len(node.Info.Hostnames.Manage)) - tests.Assert(t, info.Hostnames.Storage[0] == node.Info.Hostnames.Storage[0]) - tests.Assert(t, len(info.Hostnames.Storage) == len(node.Info.Hostnames.Storage)) - tests.Assert(t, info.Zone == node.Info.Zone) - -} - -func TestNodeDeleteErrors(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a node to save in the db - node := NewNodeEntry() - node.Info.Id = "abc" - node.Info.ClusterId = "123" - node.Info.Hostnames.Manage = sort.StringSlice{"manage.system"} - node.Info.Hostnames.Storage = sort.StringSlice{"storage.system"} - node.Info.Zone = 10 - - // Save node in the db - err := app.db.Update(func(tx *bolt.Tx) error { - return node.Save(tx) - }) - tests.Assert(t, err == nil) - - // Delete unknown id - req, err := http.NewRequest("DELETE", ts.URL+"/nodes/123", nil) - tests.Assert(t, err == nil) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusNotFound) - -} - -func TestNodePeerProbeFailure(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a cluster. We at least one - // other node in the same cluster to execute a probe - err := setupSampleDbWithTopology(app, - 1, // clusters - 4, // nodes_per_cluster - 4, // devices_per_node, - 50*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Setup the mock peer probe to fail - peerprobe_called := false - peerprobe_calls := 0 - app.xo.MockPeerProbe = func(exec_host, newnode string) error { - peerprobe_calls++ - peerprobe_called = true - return errors.New("Mock") - } - - // Get cluter id - var clusterlist []string - err = app.db.View(func(tx *bolt.Tx) error { - var err error - clusterlist, err = ClusterList(tx) - return err - }) - tests.Assert(t, err == nil) - tests.Assert(t, len(clusterlist) == 1) - clusterid := clusterlist[0] - - // Create a node - storage_name := "host.hostname.com" - manage_name := "host.hostname.com" - request := []byte(`{ - "cluster" : "` + clusterid + `", - "hostnames" : { - "storage" : [ "` + storage_name + `" ], - "manage" : [ "` + manage_name + `" ] - }, - "zone" : 1 - }`) - - // Create node - r, err := http.Post(ts.URL+"/nodes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Since we forced the MockPeerProbe above to fail, the request should fail - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusInternalServerError) - s, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.TrimSpace(s) == "Mock") - tests.Assert(t, peerprobe_called == true) - tests.Assert(t, peerprobe_calls == 1) - break - } - } - - // Check that the node has not been added to the db - var nodelist []string - var cluster *ClusterEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - cluster, err = NewClusterEntryFromId(tx, clusterid) - if err != nil { - return err - } - - // Check that the node has not registered - b := tx.Bucket([]byte(BOLTDB_BUCKET_NODE)) - tests.Assert(t, b != nil) - - val := b.Get([]byte("STORAGE" + storage_name)) - tests.Assert(t, val == nil) - - val = b.Get([]byte("MANAGE" + manage_name)) - tests.Assert(t, val == nil) - - // Set nodelist - nodelist = EntryKeys(tx, BOLTDB_BUCKET_NODE) - - return nil - }) - tests.Assert(t, err == nil) - tests.Assert(t, len(nodelist) == 4) - tests.Assert(t, len(cluster.Info.Nodes) == 4) -} - -func TestNodePeerDetachFailure(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a cluster. We do not want - // any drives in the node so we can delete easily - err := setupSampleDbWithTopology(app, - 1, // clusters - 4, // nodes_per_cluster - 0, // devices_per_node, - 50*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Setup the mock peer probe to fail - peer_called := false - peer_calls := 0 - app.xo.MockPeerDetach = func(exec_host, newnode string) error { - peer_calls++ - peer_called = true - return errors.New("Mock") - } - - // Get a node id - var nodeid string - err = app.db.View(func(tx *bolt.Tx) error { - clusterlist, err := ClusterList(tx) - if err != nil { - return err - } - - cluster, err := NewClusterEntryFromId(tx, clusterlist[0]) - if err != nil { - return err - } - - nodeid = cluster.Info.Nodes[0] - - return nil - - }) - tests.Assert(t, err == nil) - tests.Assert(t, nodeid != "") - - // Delete node - req, err := http.NewRequest("DELETE", ts.URL+"/nodes/"+nodeid, nil) - tests.Assert(t, err == nil) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Since we forced the MockPeerDetach above to fail, the request should fail - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusInternalServerError) - s, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.TrimSpace(s) == "Mock") - tests.Assert(t, peer_called == true) - tests.Assert(t, peer_calls == 1) - break - } - } - - // Check that the node is still in the db - err = app.db.View(func(tx *bolt.Tx) error { - clusters, err := ClusterList(tx) - if err != nil { - return err - } - - cluster, err := NewClusterEntryFromId(tx, clusters[0]) - if err != nil { - return err - } - tests.Assert(t, utils.SortedStringHas(cluster.Info.Nodes, nodeid)) - - _, err = NewNodeEntryFromId(tx, nodeid) - return err - }) - tests.Assert(t, err == nil) -} - -func TestNodePeerDetach(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a cluster. We do not want - // any drives in the node so we can delete easily - err := setupSampleDbWithTopology(app, - 1, // clusters - 4, // nodes_per_cluster - 0, // devices_per_node, - 50*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Setup the mock peer probe to fail - peer_called := false - app.xo.MockPeerDetach = func(exec_host, newnode string) error { - peer_called = true - return nil - } - - // get list of nodes - var nodes []string - err = app.db.View(func(tx *bolt.Tx) error { - clusters, err := ClusterList(tx) - if err != nil { - return err - } - - cluster, err := NewClusterEntryFromId(tx, clusters[0]) - if err != nil { - return err - } - - nodes = cluster.Info.Nodes - return nil - }) - tests.Assert(t, err == nil) - - // Delete nodes, peer detach should be called for each except the last one - for index, node := range nodes { - peer_called = false - - // Delete node - req, err := http.NewRequest("DELETE", ts.URL+"/nodes/"+node, nil) - tests.Assert(t, err == nil) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - tests.Assert(t, err == nil) - break - } - } - - // Check if detach was called - if index == len(nodes)-1 { - tests.Assert(t, peer_called == false) - } else { - tests.Assert(t, peer_called == true) - } - } -} - -func TestNodeState(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create mock allocator - mockAllocator := NewMockAllocator(app.db) - app.allocator = mockAllocator - - // Create a client - c := client.NewClientNoAuth(ts.URL) - tests.Assert(t, c != nil) - - // Create Cluster - cluster, err := c.ClusterCreate() - tests.Assert(t, err == nil) - - // Create Node - nodeReq := &api.NodeAddRequest{ - Zone: 1, - ClusterId: cluster.Id, - } - nodeReq.Hostnames.Manage = sort.StringSlice{"manage.host"} - nodeReq.Hostnames.Storage = sort.StringSlice{"storage.host"} - node, err := c.NodeAdd(nodeReq) - tests.Assert(t, err == nil) - - // Add device - deviceReq := &api.DeviceAddRequest{} - deviceReq.Name = "/dev/fake1" - deviceReq.NodeId = node.Id - - err = c.DeviceAdd(deviceReq) - tests.Assert(t, err == nil) - - // Get node information again - node, err = c.NodeInfo(node.Id) - tests.Assert(t, err == nil) - tests.Assert(t, node.State == "online") - - // Get device information - deviceId := node.DevicesInfo[0].Id - device, err := c.DeviceInfo(deviceId) - tests.Assert(t, err == nil) - - // Get info - deviceInfo, err := c.DeviceInfo(device.Id) - tests.Assert(t, err == nil) - tests.Assert(t, deviceInfo.State == "online") - - // Check that the device is in the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[cluster.Id][0] == device.Id) - - // Set node offline - request := []byte(`{ - "state" : "offline" - }`) - r, err := http.Post(ts.URL+"/nodes/"+node.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check it was removed from the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 0) - - // Get node info - node, err = c.NodeInfo(node.Id) - tests.Assert(t, err == nil) - tests.Assert(t, node.State == "offline") - - // Set offline again, should succeed - request = []byte(`{ - "state" : "offline" - }`) - r, err = http.Post(ts.URL+"/nodes/"+node.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - - // Check it was removed from the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 0) - - // Get node info - node, err = c.NodeInfo(node.Id) - tests.Assert(t, err == nil) - tests.Assert(t, node.State == "offline") - - // Set online again - request = []byte(`{ - "state" : "online" - }`) - r, err = http.Post(ts.URL+"/nodes/"+node.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check that the device is in the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[cluster.Id][0] == device.Id) - - // Set online again, should succeed - request = []byte(`{ - "state" : "online" - }`) - r, err = http.Post(ts.URL+"/nodes/"+node.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check that the device is in the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[cluster.Id][0] == device.Id) - - // Get node info - node, err = c.NodeInfo(node.Id) - tests.Assert(t, err == nil) - tests.Assert(t, node.State == "online") - - // Set unknown state - request = []byte(`{ - "state" : "blah" - }`) - r, err = http.Post(ts.URL+"/nodes/"+node.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusInternalServerError) - break - } - } - - // Check that the device is still in the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[cluster.Id][0] == device.Id) - - // Check node is still online - node, err = c.NodeInfo(node.Id) - tests.Assert(t, err == nil) - tests.Assert(t, node.State == "online") - - // Set device offline - request = []byte(`{ - "state" : "offline" - }`) - r, err = http.Post(ts.URL+"/devices/"+device.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Wait for deletion - for { - r, err := http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - continue - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check it was removed from the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 0) - - // Set Node offline - request = []byte(`{ - "state" : "offline" - }`) - r, err = http.Post(ts.URL+"/nodes/"+node.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check it was removed from the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 0) - - // Set Node online -- Device is still offline and should not be added - request = []byte(`{ - "state" : "online" - }`) - r, err = http.Post(ts.URL+"/nodes/"+node.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - - location, err = r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Check device is not in ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 0) - - // Now make device online - request = []byte(`{ - "state" : "online" - }`) - r, err = http.Post(ts.URL+"/devices/"+device.Id+"/state", - "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err = r.Location() - tests.Assert(t, err == nil) - - // Wait for deletion - for { - r, err := http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - continue - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - break - } - } - - // Now it should be back in the ring - tests.Assert(t, len(mockAllocator.clustermap[cluster.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[cluster.Id][0] == device.Id) - -} diff --git a/apps/glusterfs/app_test.go b/apps/glusterfs/app_test.go deleted file mode 100644 index 07dfccaa33..0000000000 --- a/apps/glusterfs/app_test.go +++ /dev/null @@ -1,223 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "net/http/httptest" - "os" - "strings" - "testing" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func TestAppBadConfigData(t *testing.T) { - data := []byte(`{ bad json }`) - app := NewApp(bytes.NewBuffer(data)) - tests.Assert(t, app == nil) - - data = []byte(`{}`) - app = NewApp(bytes.NewReader(data)) - tests.Assert(t, app == nil) - - data = []byte(`{ - "glusterfs" : {} - }`) - app = NewApp(bytes.NewReader(data)) - tests.Assert(t, app == nil) -} - -func TestAppUnknownExecutorInConfig(t *testing.T) { - data := []byte(`{ - "glusterfs" : { - "executor" : "unknown value here" - } - }`) - app := NewApp(bytes.NewReader(data)) - tests.Assert(t, app == nil) -} - -func TestAppUnknownAllocatorInConfig(t *testing.T) { - data := []byte(`{ - "glusterfs" : { - "allocator" : "unknown value here" - } - }`) - app := NewApp(bytes.NewReader(data)) - tests.Assert(t, app == nil) -} - -func TestAppBadDbLocation(t *testing.T) { - data := []byte(`{ - "glusterfs" : { - "db" : "/badlocation" - } - }`) - app := NewApp(bytes.NewReader(data)) - tests.Assert(t, app == nil) -} - -func TestAppAdvsettings(t *testing.T) { - - dbfile := tests.Tempfile() - defer os.Remove(dbfile) - os.Setenv("HEKETI_EXECUTOR", "mock") - defer os.Unsetenv("HEKETI_EXECUTOR") - - data := []byte(`{ - "glusterfs" : { - "executor" : "crazyexec", - "allocator" : "simple", - "db" : "` + dbfile + `", - "brick_max_size_gb" : 1024, - "brick_min_size_gb" : 4, - "max_bricks_per_volume" : 33 - } - }`) - - bmax, bmin, bnum := BrickMaxSize, BrickMinSize, BrickMaxNum - defer func() { - BrickMaxSize, BrickMinSize, BrickMaxNum = bmax, bmin, bnum - }() - - app := NewApp(bytes.NewReader(data)) - defer app.Close() - tests.Assert(t, app != nil) - tests.Assert(t, app.conf.Executor == "mock") - tests.Assert(t, BrickMaxNum == 33) - tests.Assert(t, BrickMaxSize == 1*TB) - tests.Assert(t, BrickMinSize == 4*GB) -} - -func TestAppLogLevel(t *testing.T) { - dbfile := tests.Tempfile() - defer os.Remove(dbfile) - - levels := []string{ - "none", - "critical", - "error", - "warning", - "info", - "debug", - } - - logger.SetLevel(utils.LEVEL_DEBUG) - for _, level := range levels { - data := []byte(`{ - "glusterfs" : { - "executor" : "mock", - "allocator" : "simple", - "db" : "` + dbfile + `", - "loglevel" : "` + level + `" - } - }`) - - app := NewApp(bytes.NewReader(data)) - tests.Assert(t, app != nil, level, string(data)) - - switch level { - case "none": - tests.Assert(t, logger.Level() == utils.LEVEL_NOLOG) - case "critical": - tests.Assert(t, logger.Level() == utils.LEVEL_CRITICAL) - case "error": - tests.Assert(t, logger.Level() == utils.LEVEL_ERROR) - case "warning": - tests.Assert(t, logger.Level() == utils.LEVEL_WARNING) - case "info": - tests.Assert(t, logger.Level() == utils.LEVEL_INFO) - case "debug": - tests.Assert(t, logger.Level() == utils.LEVEL_DEBUG) - } - app.Close() - } - - // Test that an unknown value does not change the loglevel - logger.SetLevel(utils.LEVEL_NOLOG) - data := []byte(`{ - "glusterfs" : { - "executor" : "mock", - "allocator" : "simple", - "db" : "` + dbfile + `", - "loglevel" : "blah" - } - }`) - - app := NewApp(bytes.NewReader(data)) - defer app.Close() - tests.Assert(t, app != nil) - tests.Assert(t, logger.Level() == utils.LEVEL_NOLOG) -} - -func TestAppReadOnlyDb(t *testing.T) { - - dbfile := tests.Tempfile() - defer os.Remove(dbfile) - - // First, create a db - data := []byte(`{ - "glusterfs": { - "executor" : "mock", - "db" : "` + dbfile + `" - } - }`) - app := NewApp(bytes.NewReader(data)) - tests.Assert(t, app != nil) - tests.Assert(t, app.dbReadOnly == false) - app.Close() - - // Now open it again here. This will force NewApp() - // to be unable to open RW. - db, err := bolt.Open(dbfile, 0666, &bolt.Options{ - ReadOnly: true, - }) - tests.Assert(t, err == nil, err) - tests.Assert(t, db != nil) - - // Now open it again and notice how it opened - app = NewApp(bytes.NewReader(data)) - defer app.Close() - tests.Assert(t, app != nil) - tests.Assert(t, app.dbReadOnly == true) -} - -func TestAppPathNotFound(t *testing.T) { - dbfile := tests.Tempfile() - defer os.Remove(dbfile) - - app := NewTestApp(dbfile) - tests.Assert(t, app != nil) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Setup a new client - c := client.NewClientNoAuth(ts.URL) - - // Test paths which do not match the hexadecimal id - _, err := c.ClusterInfo("xxx") - tests.Assert(t, strings.Contains(err.Error(), "Invalid path or request")) - - _, err = c.NodeInfo("xxx") - tests.Assert(t, strings.Contains(err.Error(), "Invalid path or request")) - - _, err = c.VolumeInfo("xxx") - tests.Assert(t, strings.Contains(err.Error(), "Invalid path or request")) -} diff --git a/apps/glusterfs/app_volume.go b/apps/glusterfs/app_volume.go deleted file mode 100644 index dce3cb71b5..0000000000 --- a/apps/glusterfs/app_volume.go +++ /dev/null @@ -1,332 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "encoding/json" - "fmt" - "math" - "net/http" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - "github.com/heketi/heketi/pkg/db" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" -) - -const ( - VOLUME_CREATE_MAX_SNAPSHOT_FACTOR = 100 -) - -func (a *App) VolumeCreate(w http.ResponseWriter, r *http.Request) { - - var msg api.VolumeCreateRequest - err := utils.GetJsonFromRequest(r, &msg) - if err != nil { - http.Error(w, "request unable to be parsed", 422) - return - } - - switch { - case msg.Gid < 0: - http.Error(w, "Bad group id less than zero", http.StatusBadRequest) - logger.LogError("Bad group id less than zero") - return - case msg.Gid >= math.MaxInt32: - http.Error(w, "Bad group id equal or greater than 2**32", http.StatusBadRequest) - logger.LogError("Bad group id equal or greater than 2**32") - return - } - - switch msg.Durability.Type { - case api.DurabilityEC: - case api.DurabilityReplicate: - case api.DurabilityDistributeOnly: - case "": - msg.Durability.Type = api.DurabilityDistributeOnly - default: - http.Error(w, "Unknown durability type", http.StatusBadRequest) - logger.LogError("Unknown durability type") - return - } - - if msg.Size < 1 { - http.Error(w, "Invalid volume size", http.StatusBadRequest) - logger.LogError("Invalid volume size") - return - } - if msg.Snapshot.Enable { - if msg.Snapshot.Factor < 1 || msg.Snapshot.Factor > VOLUME_CREATE_MAX_SNAPSHOT_FACTOR { - http.Error(w, "Invalid snapshot factor", http.StatusBadRequest) - logger.LogError("Invalid snapshot factor") - return - } - } - - if msg.Durability.Type == api.DurabilityReplicate { - if msg.Durability.Replicate.Replica > 3 { - http.Error(w, "Invalid replica value", http.StatusBadRequest) - logger.LogError("Invalid replica value") - return - } - } - - if msg.Durability.Type == api.DurabilityEC { - d := msg.Durability.Disperse - // Place here correct combinations - switch { - case d.Data == 2 && d.Redundancy == 1: - case d.Data == 4 && d.Redundancy == 2: - case d.Data == 8 && d.Redundancy == 3: - case d.Data == 8 && d.Redundancy == 4: - default: - http.Error(w, - fmt.Sprintf("Invalid dispersion combination: %v+%v", d.Data, d.Redundancy), - http.StatusBadRequest) - logger.LogError(fmt.Sprintf("Invalid dispersion combination: %v+%v", d.Data, d.Redundancy)) - return - } - } - - // Check that the clusters requested are available - err = a.db.View(func(tx *bolt.Tx) error { - - // :TODO: All we need to do is check for one instead of gathering all keys - clusters, err := ClusterList(tx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - if len(clusters) == 0 { - http.Error(w, fmt.Sprintf("No clusters configured"), http.StatusBadRequest) - logger.LogError("No clusters configured") - return ErrNotFound - } - - // Check the clusters requested are correct - for _, clusterid := range msg.Clusters { - _, err := NewClusterEntryFromId(tx, clusterid) - if err != nil { - http.Error(w, fmt.Sprintf("Cluster id %v not found", clusterid), http.StatusBadRequest) - logger.LogError(fmt.Sprintf("Cluster id %v not found", clusterid)) - return err - } - } - - return nil - }) - if err != nil { - return - } - - vol := NewVolumeEntryFromRequest(&msg) - - if uint64(msg.Size)*GB < vol.Durability.MinVolumeSize() { - http.Error(w, fmt.Sprintf("Requested volume size (%v GB) is "+ - "smaller than the minimum supported volume size (%v)", - msg.Size, vol.Durability.MinVolumeSize()), - http.StatusBadRequest) - logger.LogError(fmt.Sprintf("Requested volume size (%v GB) is "+ - "smaller than the minimum supported volume size (%v)", - msg.Size, vol.Durability.MinVolumeSize())) - return - } - - // Add device in an asynchronous function - a.asyncManager.AsyncHttpRedirectFunc(w, r, func() (string, error) { - - logger.Info("Creating volume %v", vol.Info.Id) - err := vol.Create(a.db, a.executor, a.allocator) - if err != nil { - logger.LogError("Failed to create volume: %v", err) - return "", err - } - - logger.Info("Created volume %v", vol.Info.Id) - - // Done - return "/volumes/" + vol.Info.Id, nil - }) - -} - -func (a *App) VolumeList(w http.ResponseWriter, r *http.Request) { - - var list api.VolumeListResponse - - // Get all the cluster ids from the DB - err := a.db.View(func(tx *bolt.Tx) error { - var err error - - list.Volumes, err = VolumeList(tx) - if err != nil { - return err - } - - return nil - }) - - if err != nil { - logger.Err(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Send list back - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(list); err != nil { - panic(err) - } -} - -func (a *App) VolumeInfo(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - id := vars["id"] - - var info *api.VolumeInfoResponse - err := a.db.View(func(tx *bolt.Tx) error { - entry, err := NewVolumeEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, "Id not found", http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - info, err = entry.NewInfoResponse(tx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - return nil - }) - if err != nil { - return - } - - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(info); err != nil { - panic(err) - } - -} - -func (a *App) VolumeDelete(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - id := vars["id"] - - var volume *VolumeEntry - err := a.db.View(func(tx *bolt.Tx) error { - - var err error - volume, err = NewVolumeEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, err.Error(), http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - if volume.Info.Name == db.HeketiStorageVolumeName { - err := fmt.Errorf("Cannot delete volume containing the Heketi database") - http.Error(w, err.Error(), http.StatusConflict) - return err - } - - return nil - - }) - if err != nil { - return - } - - a.asyncManager.AsyncHttpRedirectFunc(w, r, func() (string, error) { - - // Actually destroy the Volume here - err := volume.Destroy(a.db, a.executor) - - // If it fails for some reason, we will need to add to the DB again - // or hold state on the entry "DELETING" - - // Show that the key has been deleted - if err != nil { - logger.LogError("Failed to delete volume %v: %v", volume.Info.Id, err) - return "", err - } - - logger.Info("Deleted volume [%s]", id) - return "", nil - - }) - -} - -func (a *App) VolumeExpand(w http.ResponseWriter, r *http.Request) { - logger.Debug("In VolumeExpand") - - vars := mux.Vars(r) - id := vars["id"] - - var msg api.VolumeExpandRequest - err := utils.GetJsonFromRequest(r, &msg) - if err != nil { - http.Error(w, "request unable to be parsed", 422) - return - } - logger.Debug("Msg: %v", msg) - - if msg.Size < 1 { - http.Error(w, "Invalid volume size", http.StatusBadRequest) - return - } - logger.Debug("Size: %v", msg.Size) - - var volume *VolumeEntry - err = a.db.View(func(tx *bolt.Tx) error { - - var err error - volume, err = NewVolumeEntryFromId(tx, id) - if err == ErrNotFound { - http.Error(w, err.Error(), http.StatusNotFound) - return err - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - return nil - - }) - if err != nil { - return - } - - // Expand volume in an asynchronous function - a.asyncManager.AsyncHttpRedirectFunc(w, r, func() (string, error) { - - logger.Info("Expanding volume %v", volume.Info.Id) - err := volume.Expand(a.db, a.executor, a.allocator, msg.Size) - if err != nil { - logger.LogError("Failed to expand volume %v", volume.Info.Id) - return "", err - } - - logger.Info("Expanded volume %v", volume.Info.Id) - - return "/volumes/" + volume.Info.Id, nil - }) - -} diff --git a/apps/glusterfs/app_volume_test.go b/apps/glusterfs/app_volume_test.go deleted file mode 100644 index 582b6a1978..0000000000 --- a/apps/glusterfs/app_volume_test.go +++ /dev/null @@ -1,1281 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "errors" - "fmt" - "io" - "io/ioutil" - "math" - "net/http" - "net/http/httptest" - "os" - "reflect" - "strings" - "testing" - "time" - - "github.com/boltdb/bolt" - "github.com/gorilla/mux" - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/db" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func init() { - // turn off logging - logger.SetLevel(utils.LEVEL_NOLOG) -} - -func TestVolumeCreateBadGid(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // VolumeCreate JSON Request - request := []byte(`{ - "size" : 100, - "gid" : -1 - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - - body, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, - strings.Contains(body, "Bad group id less than zero")) - - // VolumeCreate JSON Request - request = []byte(`{ - "size" : 100, - "gid" : ` + fmt.Sprintf("%v", math.MaxInt32) + ` - }`) - - // Send request - r, err = http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - - body, err = utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, - strings.Contains(body, "Bad group id equal or greater than 2**32")) - -} - -func TestVolumeCreateBadJson(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // VolumeCreate JSON Request - request := []byte(`{ - asdfsdf - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == 422) -} - -func TestVolumeCreateNoTopology(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // VolumeCreate JSON Request - request := []byte(`{ - "size" : 100 - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) -} - -func TestVolumeCreateInvalidSize(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // VolumeCreate JSON Request - request := []byte(`{ - "size" : 0 - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Invalid volume size")) -} - -func TestVolumeCreateSmallSize(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - os.Setenv("HEKETI_EXECUTOR", "mock") - defer os.Unsetenv("HEKETI_EXECUTOR") - - data := []byte(`{ - "glusterfs" : { - "db" : "` + tmpfile + `", - "brick_min_size_gb" : 4 - } - }`) - - bmin := BrickMinSize - defer func() { - BrickMinSize = bmin - }() - - app := NewApp(bytes.NewReader(data)) - defer app.Close() - - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Setup database - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // VolumeCreate JSON Request - request := []byte(`{ - "size" : 2 - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", - bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(body, "Requested volume size (2 GB) "+ - "is smaller than the minimum supported volume size"), body) -} - -func TestVolumeHeketiDbStorage(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Setup database - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // VolumeCreate using default durability - request := []byte(`{ - "size" : 100, - "name" : "` + db.HeketiStorageVolumeName + `" - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - var info api.VolumeInfoResponse - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - if r.ContentLength <= 0 { - time.Sleep(time.Millisecond * 10) - continue - } else { - // Should have node information here - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - err = utils.GetJsonFromResponse(r, &info) - tests.Assert(t, err == nil) - break - } - } - - // Delete the volume - req, err := http.NewRequest("DELETE", ts.URL+"/volumes/"+info.Id, nil) - tests.Assert(t, err == nil) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusConflict) -} - -func TestVolumeCreateDurabilityTypeInvalid(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // VolumeCreate JSON Request - request := []byte(`{ - "size" : 100, - "durability" : { - "type" : "bad type" - } - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Unknown durability type")) -} - -func TestVolumeCreateBadReplicaValues(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // VolumeCreate JSON Request - request := []byte(`{ - "size" : 100, - "durability": { - "type": "replicate", - "replicate": { - "replica": 100 - } - } - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Invalid replica value")) - - // VolumeCreate JSON Request - request = []byte(`{ - "size" : 100, - "durability": { - "type": "replicate", - "replicate": { - "replica": 4 - } - } - }`) - - // Send request - r, err = http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err = ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Invalid replica value")) -} - -func TestVolumeCreateBadDispersionValues(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // VolumeCreate JSON Request - request := []byte(`{ - "size" : 100, - "durability": { - "type": "disperse", - "disperse": { - "data" : 8, - "redundancy" : 1 - } - } - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Invalid dispersion combination")) - - // VolumeCreate JSON Request - request = []byte(`{ - "size" : 100, - "durability": { - "type": "disperse", - "disperse": { - "data" : 4, - "redundancy" : 3 - } - } - }`) - - // Send request - r, err = http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err = ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Invalid dispersion combination")) -} - -func TestVolumeCreateBadClusters(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a cluster - // Setup database - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // VolumeCreate JSON Request - request := []byte(`{ - "size" : 10, - "clusters" : [ - "bad" - ] - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Cluster id bad not found")) -} - -func TestVolumeCreateBadSnapshotFactor(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create JSON with missing factor - request := []byte(`{ - "size" : 100, - "snapshot" : { - "enable" : true - } - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Invalid snapshot factor")) - - // Create JSON with large invalid factor - request = []byte(`{ - "size" : 100, - "snapshot" : { - "enable" : true, - "factor" : 101 - } - }`) - - // Send request - r, err = http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err = ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Invalid snapshot factor")) - - // Create JSON with small invalid factor - request = []byte(`{ - "size" : 100, - "snapshot" : { - "enable" : true, - "factor" : 0.1 - } - }`) - - // Send request - r, err = http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err = ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Invalid snapshot factor")) - -} - -func TestVolumeCreate(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Setup database - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // VolumeCreate using default durability - request := []byte(`{ - "size" : 100 - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - var info api.VolumeInfoResponse - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - if r.ContentLength <= 0 { - time.Sleep(time.Millisecond * 10) - continue - } else { - // Should have node information here - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - err = utils.GetJsonFromResponse(r, &info) - tests.Assert(t, err == nil) - break - } - } - tests.Assert(t, info.Id != "") - tests.Assert(t, info.Cluster != "") - tests.Assert(t, len(info.Bricks) == 1) // Only one 100GB brick needed - tests.Assert(t, info.Bricks[0].Size == 100*GB) - tests.Assert(t, info.Name == "vol_"+info.Id) - tests.Assert(t, info.Snapshot.Enable == false) - tests.Assert(t, info.Snapshot.Factor == 1) - tests.Assert(t, info.Durability.Type == api.DurabilityDistributeOnly) -} - -func TestVolumeInfoIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Now that we have some data in the database, we can - // make a request for the clutser list - r, err := http.Get(ts.URL + "/volumes/12345") - tests.Assert(t, r.StatusCode == http.StatusNotFound) - tests.Assert(t, err == nil) -} - -func TestVolumeInfo(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Setup database - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a volume - req := &api.VolumeCreateRequest{} - req.Size = 100 - req.Durability.Type = api.DurabilityEC - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v != nil) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Now that we have some data in the database, we can - // make a request for the clutser list - r, err := http.Get(ts.URL + "/volumes/" + v.Info.Id) - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, err == nil) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - // Read response - var msg api.VolumeInfoResponse - err = utils.GetJsonFromResponse(r, &msg) - tests.Assert(t, err == nil) - - tests.Assert(t, msg.Id == v.Info.Id) - tests.Assert(t, msg.Cluster == v.Info.Cluster) - tests.Assert(t, msg.Name == v.Info.Name) - tests.Assert(t, msg.Size == v.Info.Size) - tests.Assert(t, reflect.DeepEqual(msg.Durability, v.Info.Durability)) - tests.Assert(t, reflect.DeepEqual(msg.Snapshot, v.Info.Snapshot)) - for _, brick := range msg.Bricks { - tests.Assert(t, utils.SortedStringHas(v.Bricks, brick.Id)) - } -} - -func TestVolumeListEmpty(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Get volumes, there should be none - r, err := http.Get(ts.URL + "/volumes") - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, err == nil) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - // Read response - var msg api.VolumeListResponse - err = utils.GetJsonFromResponse(r, &msg) - tests.Assert(t, err == nil) - tests.Assert(t, len(msg.Volumes) == 0) -} - -func TestVolumeList(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create some volumes - numvolumes := 1000 - err := app.db.Update(func(tx *bolt.Tx) error { - - for i := 0; i < numvolumes; i++ { - v := createSampleVolumeEntry(100) - err := v.Save(tx) - if err != nil { - return err - } - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Get volumes, there should be none - r, err := http.Get(ts.URL + "/volumes") - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, err == nil) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - // Read response - var msg api.VolumeListResponse - err = utils.GetJsonFromResponse(r, &msg) - tests.Assert(t, err == nil) - tests.Assert(t, len(msg.Volumes) == numvolumes) - - // Check that all the volumes are in the database - err = app.db.View(func(tx *bolt.Tx) error { - for _, id := range msg.Volumes { - _, err := NewVolumeEntryFromId(tx, id) - if err != nil { - return err - } - } - - return nil - }) - tests.Assert(t, err == nil) - -} - -func TestVolumeListReadOnlyDb(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - - // Create some volumes - numvolumes := 1000 - err := app.db.Update(func(tx *bolt.Tx) error { - - for i := 0; i < numvolumes; i++ { - v := createSampleVolumeEntry(100) - err := v.Save(tx) - if err != nil { - return err - } - } - - return nil - - }) - tests.Assert(t, err == nil) - app.Close() - - // Open Db here to force read only mode - db, err := bolt.Open(tmpfile, 0666, &bolt.Options{ - ReadOnly: true, - }) - tests.Assert(t, err == nil, err) - tests.Assert(t, db != nil) - - // Create the app - app = NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Get volumes, there should be none - r, err := http.Get(ts.URL + "/volumes") - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, err == nil) - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - - // Read response - var msg api.VolumeListResponse - err = utils.GetJsonFromResponse(r, &msg) - tests.Assert(t, err == nil) - tests.Assert(t, len(msg.Volumes) == numvolumes) - - // Check that all the volumes are in the database - err = app.db.View(func(tx *bolt.Tx) error { - for _, id := range msg.Volumes { - _, err := NewVolumeEntryFromId(tx, id) - if err != nil { - return err - } - } - - return nil - }) - tests.Assert(t, err == nil) - -} - -func TestVolumeDeleteIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Now that we have some data in the database, we can - // make a request for the clutser list - req, err := http.NewRequest("DELETE", ts.URL+"/volumes/12345", nil) - tests.Assert(t, err == nil) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusNotFound) -} - -func TestVolumeDelete(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Setup database - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a volume - v := createSampleVolumeEntry(100) - tests.Assert(t, v != nil) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Delete the volume - req, err := http.NewRequest("DELETE", ts.URL+"/volumes/"+v.Info.Id, nil) - tests.Assert(t, err == nil) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - if r.Header.Get("X-Pending") == "true" { - tests.Assert(t, r.StatusCode == http.StatusOK) - time.Sleep(time.Millisecond * 10) - continue - } else { - tests.Assert(t, r.StatusCode == http.StatusNoContent) - tests.Assert(t, err == nil) - break - } - } - - // Check it is not there - r, err = http.Get(ts.URL + "/volumes/" + v.Info.Id) - tests.Assert(t, r.StatusCode == http.StatusNotFound) - tests.Assert(t, err == nil) -} - -func TestVolumeExpandBadJson(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // VolumeCreate JSON Request - request := []byte(`{ - "asdfasd 0 - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == 422) -} - -func TestVolumeExpandIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // JSON Request - request := []byte(`{ - "expand_size" : 100 - }`) - - // Now that we have some data in the database, we can - // make a request for the clutser list - r, err := http.Post(ts.URL+"/volumes/12345/expand", - "application/json", - bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusNotFound, r.StatusCode) - body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Id not found")) -} - -func TestVolumeExpandSizeTooSmall(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // VolumeCreate JSON Request - request := []byte(`{ - "expand_size" : 0 - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - body, err := ioutil.ReadAll(io.LimitReader(r.Body, r.ContentLength)) - tests.Assert(t, err == nil) - r.Body.Close() - tests.Assert(t, strings.Contains(string(body), "Invalid volume size")) -} - -func TestVolumeExpand(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a cluster - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a volume - v := createSampleVolumeEntry(100) - tests.Assert(t, v != nil) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Keep a copy - vc := &VolumeEntry{} - *vc = *v - - // JSON Request - request := []byte(`{ - "expand_size" : 1000 - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes/"+v.Info.Id+"/expand", - "application/json", - bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - var info api.VolumeInfoResponse - for { - r, err := http.Get(location.String()) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - if r.Header.Get("X-Pending") == "true" { - time.Sleep(time.Millisecond * 10) - continue - } else { - err = utils.GetJsonFromResponse(r, &info) - tests.Assert(t, err == nil) - break - } - } - - tests.Assert(t, info.Size == 100+1000) - tests.Assert(t, len(vc.Bricks) < len(info.Bricks)) -} - -func TestVolumeClusterResizeByAddingDevices(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a cluster - err := setupSampleDbWithTopology(app, - 1, // clusters - 2, // nodes_per_cluster - 1, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a volume which uses the entire storage - v := createSampleVolumeEntry(495) - tests.Assert(t, v != nil) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Try to create another volume, but this should fail - v = createSampleVolumeEntry(495) - tests.Assert(t, v != nil) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == ErrNoSpace) - - // Create a client - c := client.NewClientNoAuth(ts.URL) - tests.Assert(t, c != nil) - - // Get the cluster ID - clusters, err := c.ClusterList() - tests.Assert(t, len(clusters.Clusters) == 1) - clusterId := clusters.Clusters[0] - - // Get Nodes - clusterInfo, err := c.ClusterInfo(clusterId) - tests.Assert(t, len(clusterInfo.Nodes) == 2) - - // Add two devices to the cluster - for _, nodeId := range clusterInfo.Nodes { - d := &api.DeviceAddRequest{} - d.Name = "/fake/device" - d.NodeId = nodeId - err := c.DeviceAdd(d) - tests.Assert(t, err == nil, err) - } - - // Now add a volume, and it should work - v = createSampleVolumeEntry(495) - tests.Assert(t, v != nil) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Try to create another volume, but this should fail - v = createSampleVolumeEntry(495) - tests.Assert(t, v != nil) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == ErrNoSpace) -} - -// Test for https://github.com/heketi/heketi/issues/382: -// -// A TopologyInfo request running concurrently to a -// VolumeCreate request failed with "Id not found" due -// to unsaved brick info. -func TestVolumeCreateVsTopologyInfo(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Create a cluster - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a client - c := client.NewClientNoAuth(ts.URL) - tests.Assert(t, c != nil) - - // Start Several concurrent VolumeCreate and - // TopologyInfo requests so that there is a - // chance for a TopologyInfo request to hit - // the race of unsaved // brick data. - sg := utils.NewStatusGroup() - for i := 0; i < 20; i++ { - sg.Add(1) - go func() { - defer sg.Done() - - volumeReq := &api.VolumeCreateRequest{} - volumeReq.Size = 10 - - volume, err := c.VolumeCreate(volumeReq) - sg.Err(err) - if err != nil { - return - } - - if volume.Id == "" { - sg.Err(errors.New("Empty volume Id.")) - return - } - - if volume.Size != volumeReq.Size { - sg.Err(fmt.Errorf("Unexpected Volume size "+ - "[%d] instead of [%d].", - volume.Size, volumeReq.Size)) - } - }() - - sg.Add(1) - go func() { - defer sg.Done() - - _, err := c.TopologyInfo() - if err != nil { - err = fmt.Errorf("TopologyInfo failed: %s", err) - } - sg.Err(err) - }() - } - - err = sg.Result() - tests.Assert(t, err == nil, err) -} -func TestVolumeCreateWithOptions(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - router := mux.NewRouter() - app.SetRoutes(router) - - // Setup the server - ts := httptest.NewServer(router) - defer ts.Close() - - // Setup database - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // VolumeCreate using a test option called "test-option" - request := []byte(`{ - "size" : 100, - "glustervolumeoptions" : [ - "test-option" - ] - }`) - - // Send request - r, err := http.Post(ts.URL+"/volumes", "application/json", bytes.NewBuffer(request)) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusAccepted) - location, err := r.Location() - tests.Assert(t, err == nil) - - // Query queue until finished - var info api.VolumeInfoResponse - for { - r, err = http.Get(location.String()) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - if r.ContentLength <= 0 { - time.Sleep(time.Millisecond * 10) - continue - } else { - // Should have node information here - tests.Assert(t, r.Header.Get("Content-Type") == "application/json; charset=UTF-8") - err = utils.GetJsonFromResponse(r, &info) - tests.Assert(t, err == nil) - break - } - } - - tests.Assert(t, info.Id != "") - tests.Assert(t, info.Cluster != "") - tests.Assert(t, len(info.Bricks) == 1) // Only one 100GB brick needed - tests.Assert(t, info.Bricks[0].Size == 100*GB) - tests.Assert(t, info.Name == "vol_"+info.Id) - tests.Assert(t, info.Snapshot.Enable == false) - tests.Assert(t, info.Snapshot.Factor == 1) - tests.Assert(t, info.Durability.Type == api.DurabilityDistributeOnly) - // GlusterVolumeOption should have the "test-option" - tests.Assert(t, info.GlusterVolumeOptions[0] == "test-option") - -} diff --git a/apps/glusterfs/brick_create.go b/apps/glusterfs/brick_create.go deleted file mode 100644 index 43caa13f5c..0000000000 --- a/apps/glusterfs/brick_create.go +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/boltdb/bolt" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/utils" -) - -type CreateType int - -const ( - CREATOR_CREATE CreateType = iota - CREATOR_DESTROY -) - -func createDestroyConcurrently(db *bolt.DB, - executor executors.Executor, - brick_entries []*BrickEntry, - create_type CreateType) error { - - sg := utils.NewStatusGroup() - - // Create a goroutine for each brick - for _, brick := range brick_entries { - sg.Add(1) - go func(b *BrickEntry) { - defer sg.Done() - if create_type == CREATOR_CREATE { - sg.Err(b.Create(db, executor)) - } else { - sg.Err(b.Destroy(db, executor)) - } - }(brick) - } - - // Wait here until all goroutines have returned. If - // any of errored, it would be cought here - err := sg.Result() - if err != nil { - logger.Err(err) - - // Destroy all bricks and cleanup - if create_type == CREATOR_CREATE { - createDestroyConcurrently(db, executor, brick_entries, CREATOR_DESTROY) - } - } - return err -} - -func CreateBricks(db *bolt.DB, executor executors.Executor, brick_entries []*BrickEntry) error { - return createDestroyConcurrently(db, executor, brick_entries, CREATOR_CREATE) -} - -func DestroyBricks(db *bolt.DB, executor executors.Executor, brick_entries []*BrickEntry) error { - return createDestroyConcurrently(db, executor, brick_entries, CREATOR_DESTROY) -} diff --git a/apps/glusterfs/brick_entry.go b/apps/glusterfs/brick_entry.go deleted file mode 100644 index 7709bb4247..0000000000 --- a/apps/glusterfs/brick_entry.go +++ /dev/null @@ -1,286 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "encoding/gob" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/lpabon/godbc" -) - -type BrickEntry struct { - Info api.BrickInfo - TpSize uint64 - PoolMetadataSize uint64 - gidRequested int64 -} - -func BrickList(tx *bolt.Tx) ([]string, error) { - - list := EntryKeys(tx, BOLTDB_BUCKET_BRICK) - if list == nil { - return nil, ErrAccessList - } - return list, nil -} - -func NewBrickEntry(size, tpsize, poolMetadataSize uint64, - deviceid, nodeid string, gid int64, volumeid string) *BrickEntry { - - godbc.Require(size > 0) - godbc.Require(tpsize > 0) - godbc.Require(deviceid != "") - godbc.Require(nodeid != "") - - entry := &BrickEntry{} - entry.gidRequested = gid - entry.TpSize = tpsize - entry.PoolMetadataSize = poolMetadataSize - entry.Info.Id = utils.GenUUID() - entry.Info.Size = size - entry.Info.NodeId = nodeid - entry.Info.DeviceId = deviceid - entry.Info.VolumeId = volumeid - - godbc.Ensure(entry.Info.Id != "") - godbc.Ensure(entry.TpSize == tpsize) - godbc.Ensure(entry.Info.Size == size) - godbc.Ensure(entry.Info.NodeId == nodeid) - godbc.Ensure(entry.Info.DeviceId == deviceid) - - return entry -} - -func NewBrickEntryFromId(tx *bolt.Tx, id string) (*BrickEntry, error) { - godbc.Require(tx != nil) - - entry := &BrickEntry{} - err := EntryLoad(tx, entry, id) - if err != nil { - return nil, err - } - - return entry, nil -} - -func (b *BrickEntry) BucketName() string { - return BOLTDB_BUCKET_BRICK -} - -func (b *BrickEntry) SetId(id string) { - b.Info.Id = id -} - -func (b *BrickEntry) Id() string { - return b.Info.Id -} - -func (b *BrickEntry) Save(tx *bolt.Tx) error { - godbc.Require(tx != nil) - godbc.Require(len(b.Info.Id) > 0) - - return EntrySave(tx, b, b.Info.Id) -} - -func (b *BrickEntry) Delete(tx *bolt.Tx) error { - return EntryDelete(tx, b, b.Info.Id) -} - -func (b *BrickEntry) NewInfoResponse(tx *bolt.Tx) (*api.BrickInfo, error) { - info := &api.BrickInfo{} - *info = b.Info - - return info, nil -} - -func (b *BrickEntry) Marshal() ([]byte, error) { - var buffer bytes.Buffer - enc := gob.NewEncoder(&buffer) - err := enc.Encode(*b) - - return buffer.Bytes(), err -} - -func (b *BrickEntry) Unmarshal(buffer []byte) error { - dec := gob.NewDecoder(bytes.NewReader(buffer)) - err := dec.Decode(b) - if err != nil { - return err - } - - return nil -} - -func (b *BrickEntry) Create(db *bolt.DB, executor executors.Executor) error { - godbc.Require(db != nil) - godbc.Require(b.TpSize > 0) - godbc.Require(b.Info.Size > 0) - - // Get node hostname - var host string - err := db.View(func(tx *bolt.Tx) error { - node, err := NewNodeEntryFromId(tx, b.Info.NodeId) - if err != nil { - return err - } - - host = node.ManageHostName() - godbc.Check(host != "") - return nil - }) - if err != nil { - return err - } - - // Create request - req := &executors.BrickRequest{} - req.Gid = b.gidRequested - req.Name = b.Info.Id - req.Size = b.Info.Size - req.TpSize = b.TpSize - req.VgId = b.Info.DeviceId - req.PoolMetadataSize = b.PoolMetadataSize - - // Create brick on node - logger.Info("Creating brick %v", b.Info.Id) - info, err := executor.BrickCreate(host, req) - if err != nil { - return err - } - b.Info.Path = info.Path - - godbc.Ensure(b.Info.Path != "") - - return nil -} - -func (b *BrickEntry) Destroy(db *bolt.DB, executor executors.Executor) error { - - godbc.Require(db != nil) - godbc.Require(b.TpSize > 0) - godbc.Require(b.Info.Size > 0) - - // Get node hostname - var host string - err := db.View(func(tx *bolt.Tx) error { - node, err := NewNodeEntryFromId(tx, b.Info.NodeId) - if err != nil { - return err - } - - host = node.ManageHostName() - godbc.Check(host != "") - return nil - }) - if err != nil { - return err - } - - // Create request - req := &executors.BrickRequest{} - req.Name = b.Info.Id - req.Size = b.Info.Size - req.TpSize = b.TpSize - req.VgId = b.Info.DeviceId - - // Delete brick on node - logger.Info("Deleting brick %v", b.Info.Id) - err = executor.BrickDestroy(host, req) - if err != nil { - return err - } - - return nil -} - -func (b *BrickEntry) DestroyCheck(db *bolt.DB, executor executors.Executor) error { - godbc.Require(db != nil) - godbc.Require(b.TpSize > 0) - godbc.Require(b.Info.Size > 0) - - // Get node hostname - var host string - err := db.View(func(tx *bolt.Tx) error { - node, err := NewNodeEntryFromId(tx, b.Info.NodeId) - if err != nil { - return err - } - - host = node.ManageHostName() - godbc.Check(host != "") - return nil - }) - if err != nil { - return err - } - - // Create request - req := &executors.BrickRequest{} - req.Name = b.Info.Id - req.Size = b.Info.Size - req.TpSize = b.TpSize - req.VgId = b.Info.DeviceId - - // Check brick on node - return executor.BrickDestroyCheck(host, req) -} - -// Size consumed on device -func (b *BrickEntry) TotalSize() uint64 { - return b.TpSize + b.PoolMetadataSize -} - -func BrickEntryUpgrade(tx *bolt.Tx) error { - err := addVolumeIdInBrickEntry(tx) - if err != nil { - return err - } - return nil -} - -func addVolumeIdInBrickEntry(tx *bolt.Tx) error { - clusters, err := ClusterList(tx) - if err != nil { - return err - } - for _, cluster := range clusters { - clusterEntry, err := NewClusterEntryFromId(tx, cluster) - if err != nil { - return err - } - for _, volume := range clusterEntry.Info.Volumes { - volumeEntry, err := NewVolumeEntryFromId(tx, volume) - if err != nil { - return err - } - for _, brick := range volumeEntry.Bricks { - brickEntry, err := NewBrickEntryFromId(tx, brick) - if err != nil { - return err - } - if brickEntry.Info.VolumeId == "" { - brickEntry.Info.VolumeId = volume - err = brickEntry.Save(tx) - if err != nil { - return err - } - } else { - break - } - } - } - } - return nil -} diff --git a/apps/glusterfs/brick_entry_test.go b/apps/glusterfs/brick_entry_test.go deleted file mode 100644 index 42d1d9a0da..0000000000 --- a/apps/glusterfs/brick_entry_test.go +++ /dev/null @@ -1,276 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "fmt" - "os" - "reflect" - "testing" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/tests" -) - -func TestNewBrickEntry(t *testing.T) { - - size := uint64(10) - tpsize := size * 2 - deviceid := "abc" - nodeid := "def" - ps := size - gid := int64(1) - volumeid := "ghi" - - b := NewBrickEntry(size, tpsize, ps, deviceid, nodeid, gid, volumeid) - tests.Assert(t, b.Info.Id != "") - tests.Assert(t, b.TpSize == tpsize) - tests.Assert(t, b.PoolMetadataSize == ps) - tests.Assert(t, b.Info.DeviceId == deviceid) - tests.Assert(t, b.Info.NodeId == nodeid) - tests.Assert(t, b.Info.Size == size) - tests.Assert(t, b.gidRequested == gid) - tests.Assert(t, b.Info.VolumeId == volumeid) -} - -func TestBrickEntryMarshal(t *testing.T) { - size := uint64(10) - tpsize := size * 2 - deviceid := "abc" - nodeid := "def" - ps := size - gid := int64(0) - volumeid := "ghi" - m := NewBrickEntry(size, tpsize, ps, deviceid, nodeid, gid, volumeid) - - buffer, err := m.Marshal() - tests.Assert(t, err == nil) - tests.Assert(t, buffer != nil) - tests.Assert(t, len(buffer) > 0) - - um := &BrickEntry{} - err = um.Unmarshal(buffer) - tests.Assert(t, err == nil) - - tests.Assert(t, reflect.DeepEqual(um, m)) -} - -func TestNewBrickEntryFromIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Test for ID not found - err := app.db.View(func(tx *bolt.Tx) error { - _, err := NewBrickEntryFromId(tx, "123") - return err - }) - tests.Assert(t, err == ErrNotFound) - -} - -func TestNewBrickEntryFromId(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a brick - b := NewBrickEntry(10, 20, 5, "abc", "def", 0, "ghi") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return b.Save(tx) - }) - tests.Assert(t, err == nil) - - var brick *BrickEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - brick, err = NewBrickEntryFromId(tx, b.Info.Id) - return err - }) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(brick, b)) - -} - -func TestNewBrickEntrySaveDelete(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a brick - b := NewBrickEntry(10, 20, 5, "abc", "def", 1000, "ghi") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return b.Save(tx) - }) - tests.Assert(t, err == nil) - - // Delete entry which has devices - var brick *BrickEntry - err = app.db.Update(func(tx *bolt.Tx) error { - var err error - brick, err = NewBrickEntryFromId(tx, b.Info.Id) - if err != nil { - return err - } - - err = brick.Delete(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Check brick has been deleted and is not in db - err = app.db.View(func(tx *bolt.Tx) error { - var err error - brick, err = NewBrickEntryFromId(tx, b.Info.Id) - return err - }) - tests.Assert(t, err == ErrNotFound) -} - -func TestNewBrickEntryNewInfoResponse(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a brick - b := NewBrickEntry(10, 20, 5, "abc", "def", 1000, "ghi") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return b.Save(tx) - }) - tests.Assert(t, err == nil) - - var info *api.BrickInfo - err = app.db.View(func(tx *bolt.Tx) error { - brick, err := NewBrickEntryFromId(tx, b.Id()) - if err != nil { - return err - } - - info, err = brick.NewInfoResponse(tx) - return err - }) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(*info, b.Info)) -} - -func TestBrickEntryDestroyCheck(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a brick - b := NewBrickEntry(10, 20, 5, "abc", "node", 1000, "ghi") - n := NewNodeEntry() - n.Info.Id = "node" - n.Info.Hostnames.Manage = []string{"manage"} - n.Info.Hostnames.Storage = []string{"storage"} - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - err := n.Save(tx) - tests.Assert(t, err == nil) - return b.Save(tx) - }) - tests.Assert(t, err == nil) - - app.xo.MockBrickDestroyCheck = func(host string, brick *executors.BrickRequest) error { - return fmt.Errorf("MOCK error") - } - - err = b.DestroyCheck(app.db, app.executor) - tests.Assert(t, err != nil) - - app.xo.MockBrickDestroyCheck = func(host string, brick *executors.BrickRequest) error { - return nil - } - - err = b.DestroyCheck(app.db, app.executor) - tests.Assert(t, err == nil, err) -} - -func TestBrickEntryCreate(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Set test values - size := uint64(10) - tpsize := uint64(20) - poolMetadataSize := uint64(5) - deviceid := "abc" - nodeid := "node" - gid := int64(1000) - volumeid := "ghi" - - // Create a brick - b := NewBrickEntry(size, tpsize, poolMetadataSize, - deviceid, nodeid, gid, volumeid) - n := NewNodeEntry() - n.Info.Id = nodeid - n.Info.Hostnames.Manage = []string{"manage"} - n.Info.Hostnames.Storage = []string{"storage"} - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - err := n.Save(tx) - tests.Assert(t, err == nil) - return b.Save(tx) - }) - tests.Assert(t, err == nil) - - app.xo.MockBrickCreate = func(host string, - brick *executors.BrickRequest) (*executors.BrickInfo, error) { - bInfo := &executors.BrickInfo{ - Path: "/mockpath", - } - - tests.Assert(t, brick.Gid == gid) - tests.Assert(t, brick.Name == b.Info.Id) - tests.Assert(t, brick.PoolMetadataSize == poolMetadataSize) - tests.Assert(t, brick.Size == size) - tests.Assert(t, brick.TpSize == tpsize) - tests.Assert(t, brick.VgId == deviceid) - - return bInfo, nil - } - err = b.Create(app.db, app.executor) - tests.Assert(t, err == nil) -} diff --git a/apps/glusterfs/cluster_entry.go b/apps/glusterfs/cluster_entry.go deleted file mode 100644 index 6612645279..0000000000 --- a/apps/glusterfs/cluster_entry.go +++ /dev/null @@ -1,153 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "encoding/gob" - "fmt" - "sort" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/lpabon/godbc" -) - -type ClusterEntry struct { - Info api.ClusterInfoResponse -} - -func ClusterList(tx *bolt.Tx) ([]string, error) { - - list := EntryKeys(tx, BOLTDB_BUCKET_CLUSTER) - if list == nil { - return nil, ErrAccessList - } - return list, nil -} - -func NewClusterEntry() *ClusterEntry { - entry := &ClusterEntry{} - entry.Info.Nodes = make(sort.StringSlice, 0) - entry.Info.Volumes = make(sort.StringSlice, 0) - - return entry -} - -func NewClusterEntryFromRequest() *ClusterEntry { - entry := NewClusterEntry() - entry.Info.Id = utils.GenUUID() - - return entry -} - -func NewClusterEntryFromId(tx *bolt.Tx, id string) (*ClusterEntry, error) { - - entry := NewClusterEntry() - err := EntryLoad(tx, entry, id) - if err != nil { - return nil, err - } - - return entry, nil -} - -func (c *ClusterEntry) BucketName() string { - return BOLTDB_BUCKET_CLUSTER -} - -func (c *ClusterEntry) Save(tx *bolt.Tx) error { - godbc.Require(tx != nil) - godbc.Require(len(c.Info.Id) > 0) - - return EntrySave(tx, c, c.Info.Id) -} - -func (c *ClusterEntry) ConflictString() string { - return fmt.Sprintf("Unable to delete cluster [%v] because it contains volumes and/or nodes", c.Info.Id) -} - -func (c *ClusterEntry) Delete(tx *bolt.Tx) error { - godbc.Require(tx != nil) - - // Check if the cluster still has nodes or volumes - if len(c.Info.Nodes) > 0 || len(c.Info.Volumes) > 0 { - logger.Warning(c.ConflictString()) - return ErrConflict - } - - return EntryDelete(tx, c, c.Info.Id) -} - -func (c *ClusterEntry) NewClusterInfoResponse(tx *bolt.Tx) (*api.ClusterInfoResponse, error) { - - info := &api.ClusterInfoResponse{} - *info = c.Info - - return info, nil -} - -func (c *ClusterEntry) Marshal() ([]byte, error) { - var buffer bytes.Buffer - enc := gob.NewEncoder(&buffer) - err := enc.Encode(*c) - - return buffer.Bytes(), err -} - -func (c *ClusterEntry) Unmarshal(buffer []byte) error { - dec := gob.NewDecoder(bytes.NewReader(buffer)) - err := dec.Decode(c) - if err != nil { - return err - } - - // Make sure to setup arrays if nil - if c.Info.Nodes == nil { - c.Info.Nodes = make(sort.StringSlice, 0) - } - if c.Info.Volumes == nil { - c.Info.Volumes = make(sort.StringSlice, 0) - } - - return nil -} - -func (c *ClusterEntry) NodeEntryFromClusterIndex(tx *bolt.Tx, index int) (*NodeEntry, error) { - node, err := NewNodeEntryFromId(tx, c.Info.Nodes[index]) - if err != nil { - return nil, err - } - - return node, nil -} - -func (c *ClusterEntry) NodeAdd(id string) { - c.Info.Nodes = append(c.Info.Nodes, id) - c.Info.Nodes.Sort() -} - -func (c *ClusterEntry) VolumeAdd(id string) { - c.Info.Volumes = append(c.Info.Volumes, id) - c.Info.Volumes.Sort() -} - -func (c *ClusterEntry) VolumeDelete(id string) { - c.Info.Volumes = utils.SortedStringsDelete(c.Info.Volumes, id) -} - -func (c *ClusterEntry) NodeDelete(id string) { - c.Info.Nodes = utils.SortedStringsDelete(c.Info.Nodes, id) -} - -func ClusterEntryUpgrade(tx *bolt.Tx) error { - return nil -} diff --git a/apps/glusterfs/cluster_entry_test.go b/apps/glusterfs/cluster_entry_test.go deleted file mode 100644 index 814eb9f2c8..0000000000 --- a/apps/glusterfs/cluster_entry_test.go +++ /dev/null @@ -1,357 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "os" - "reflect" - "testing" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func createSampleClusterEntry() *ClusterEntry { - return NewClusterEntryFromRequest() -} - -func TestNewClusterEntry(t *testing.T) { - c := NewClusterEntry() - tests.Assert(t, c.Info.Id == "") - tests.Assert(t, c.Info.Volumes != nil) - tests.Assert(t, c.Info.Nodes != nil) - tests.Assert(t, len(c.Info.Volumes) == 0) - tests.Assert(t, len(c.Info.Nodes) == 0) -} - -func TestNewClusterEntryFromRequest(t *testing.T) { - - c := NewClusterEntryFromRequest() - tests.Assert(t, c != nil) - tests.Assert(t, len(c.Info.Id) > 0) - tests.Assert(t, c.Info.Id != "") - tests.Assert(t, c.Info.Volumes != nil) - tests.Assert(t, c.Info.Nodes != nil) - tests.Assert(t, len(c.Info.Volumes) == 0) - tests.Assert(t, len(c.Info.Nodes) == 0) - -} - -func TestClusterEntryMarshal(t *testing.T) { - m := NewClusterEntry() - m.Info.Id = "123" - m.Info.Nodes = []string{"1", "2"} - m.Info.Volumes = []string{"3", "4", "5"} - - buffer, err := m.Marshal() - tests.Assert(t, err == nil) - tests.Assert(t, buffer != nil) - tests.Assert(t, len(buffer) > 0) - - um := NewClusterEntry() - err = um.Unmarshal(buffer) - tests.Assert(t, err == nil) - - tests.Assert(t, m.Info.Id == um.Info.Id) - tests.Assert(t, len(um.Info.Volumes) == 3) - tests.Assert(t, len(um.Info.Nodes) == 2) - tests.Assert(t, um.Info.Nodes[0] == "1") - tests.Assert(t, um.Info.Nodes[1] == "2") - tests.Assert(t, um.Info.Volumes[0] == "3") - tests.Assert(t, um.Info.Volumes[1] == "4") - tests.Assert(t, um.Info.Volumes[2] == "5") -} - -func TestClusterEntryAddDeleteElements(t *testing.T) { - c := NewClusterEntry() - - c.NodeAdd("123") - tests.Assert(t, len(c.Info.Nodes) == 1) - tests.Assert(t, len(c.Info.Volumes) == 0) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "123")) - - c.NodeAdd("456") - tests.Assert(t, len(c.Info.Nodes) == 2) - tests.Assert(t, len(c.Info.Volumes) == 0) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "123")) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "456")) - - c.VolumeAdd("aabb") - tests.Assert(t, len(c.Info.Nodes) == 2) - tests.Assert(t, len(c.Info.Volumes) == 1) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "123")) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "456")) - tests.Assert(t, utils.SortedStringHas(c.Info.Volumes, "aabb")) - - c.NodeDelete("aabb") - tests.Assert(t, len(c.Info.Nodes) == 2) - tests.Assert(t, len(c.Info.Volumes) == 1) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "123")) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "456")) - tests.Assert(t, utils.SortedStringHas(c.Info.Volumes, "aabb")) - - c.NodeDelete("456") - tests.Assert(t, len(c.Info.Nodes) == 1) - tests.Assert(t, len(c.Info.Volumes) == 1) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "123")) - tests.Assert(t, !utils.SortedStringHas(c.Info.Nodes, "456")) - tests.Assert(t, utils.SortedStringHas(c.Info.Volumes, "aabb")) - - c.NodeDelete("123") - tests.Assert(t, len(c.Info.Nodes) == 0) - tests.Assert(t, len(c.Info.Volumes) == 1) - tests.Assert(t, !utils.SortedStringHas(c.Info.Nodes, "123")) - tests.Assert(t, !utils.SortedStringHas(c.Info.Nodes, "456")) - tests.Assert(t, utils.SortedStringHas(c.Info.Volumes, "aabb")) - - c.VolumeDelete("123") - tests.Assert(t, len(c.Info.Nodes) == 0) - tests.Assert(t, len(c.Info.Volumes) == 1) - tests.Assert(t, !utils.SortedStringHas(c.Info.Nodes, "123")) - tests.Assert(t, !utils.SortedStringHas(c.Info.Nodes, "456")) - tests.Assert(t, utils.SortedStringHas(c.Info.Volumes, "aabb")) - - c.VolumeDelete("aabb") - tests.Assert(t, len(c.Info.Nodes) == 0) - tests.Assert(t, len(c.Info.Volumes) == 0) - tests.Assert(t, !utils.SortedStringHas(c.Info.Nodes, "123")) - tests.Assert(t, !utils.SortedStringHas(c.Info.Nodes, "456")) - tests.Assert(t, !utils.SortedStringHas(c.Info.Volumes, "aabb")) -} - -func TestNewClusterEntryFromIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Test for ID not found - err := app.db.View(func(tx *bolt.Tx) error { - _, err := NewClusterEntryFromId(tx, "123") - return err - }) - tests.Assert(t, err == ErrNotFound) - -} - -func TestNewClusterEntryFromId(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a cluster - c := NewClusterEntryFromRequest() - c.NodeAdd("node_abc") - c.NodeAdd("node_def") - c.VolumeAdd("vol_abc") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return c.Save(tx) - }) - tests.Assert(t, err == nil) - - var cluster *ClusterEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - cluster, err = NewClusterEntryFromId(tx, c.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == nil) - - tests.Assert(t, cluster.Info.Id == c.Info.Id) - tests.Assert(t, len(c.Info.Nodes) == 2) - tests.Assert(t, len(c.Info.Volumes) == 1) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "node_abc")) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "node_def")) - tests.Assert(t, utils.SortedStringHas(c.Info.Volumes, "vol_abc")) - -} - -func TestNewClusterEntrySaveDelete(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a cluster - c := NewClusterEntryFromRequest() - c.NodeAdd("node_abc") - c.NodeAdd("node_def") - c.VolumeAdd("vol_abc") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return c.Save(tx) - }) - tests.Assert(t, err == nil) - - var cluster *ClusterEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - cluster, err = NewClusterEntryFromId(tx, c.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == nil) - - tests.Assert(t, cluster.Info.Id == c.Info.Id) - tests.Assert(t, len(c.Info.Nodes) == 2) - tests.Assert(t, len(c.Info.Volumes) == 1) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "node_abc")) - tests.Assert(t, utils.SortedStringHas(c.Info.Nodes, "node_def")) - tests.Assert(t, utils.SortedStringHas(c.Info.Volumes, "vol_abc")) - - // Delete entry which has devices - err = app.db.Update(func(tx *bolt.Tx) error { - var err error - cluster, err = NewClusterEntryFromId(tx, c.Info.Id) - if err != nil { - return err - } - - err = cluster.Delete(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == ErrConflict) - - // Delete devices in cluster - cluster.VolumeDelete("vol_abc") - tests.Assert(t, len(cluster.Info.Volumes) == 0) - tests.Assert(t, len(cluster.Info.Nodes) == 2) - - // Save cluster - err = app.db.Update(func(tx *bolt.Tx) error { - return cluster.Save(tx) - }) - tests.Assert(t, err == nil) - - // Try do delete a cluster which still has nodes - err = app.db.Update(func(tx *bolt.Tx) error { - var err error - cluster, err = NewClusterEntryFromId(tx, c.Info.Id) - if err != nil { - return err - } - - err = cluster.Delete(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == ErrConflict) - - // Delete cluster - cluster.NodeDelete("node_abc") - cluster.NodeDelete("node_def") - tests.Assert(t, len(cluster.Info.Nodes) == 0) - - // Save cluster - err = app.db.Update(func(tx *bolt.Tx) error { - return cluster.Save(tx) - }) - tests.Assert(t, err == nil) - - // Now try to delete the cluster with no elements - err = app.db.Update(func(tx *bolt.Tx) error { - var err error - cluster, err = NewClusterEntryFromId(tx, c.Info.Id) - if err != nil { - return err - } - - err = cluster.Delete(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Check cluster has been deleted and is not in db - err = app.db.View(func(tx *bolt.Tx) error { - var err error - cluster, err = NewClusterEntryFromId(tx, c.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == ErrNotFound) -} - -func TestNewClusterEntryNewInfoResponse(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a cluster - c := NewClusterEntryFromRequest() - c.NodeAdd("node_abc") - c.NodeAdd("node_def") - c.VolumeAdd("vol_abc") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return c.Save(tx) - }) - tests.Assert(t, err == nil) - - var info *api.ClusterInfoResponse - err = app.db.View(func(tx *bolt.Tx) error { - cluster, err := NewClusterEntryFromId(tx, c.Info.Id) - if err != nil { - return err - } - - info, err = cluster.NewClusterInfoResponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - tests.Assert(t, info.Id == c.Info.Id) - tests.Assert(t, reflect.DeepEqual(info.Nodes, c.Info.Nodes)) - tests.Assert(t, reflect.DeepEqual(info.Volumes, c.Info.Volumes)) -} diff --git a/apps/glusterfs/dbentry.go b/apps/glusterfs/dbentry.go deleted file mode 100644 index 0b0121919c..0000000000 --- a/apps/glusterfs/dbentry.go +++ /dev/null @@ -1,147 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/boltdb/bolt" - "github.com/lpabon/godbc" -) - -type DbEntry interface { - BucketName() string - Marshal() ([]byte, error) - Unmarshal(buffer []byte) error -} - -// Checks if the key already exists in the database. If it does not exist, -// then it will save the key value pair in the database bucket. -func EntryRegister(tx *bolt.Tx, entry DbEntry, key string, value []byte) ([]byte, error) { - godbc.Require(tx != nil) - godbc.Require(len(key) > 0) - - // Access bucket - b := tx.Bucket([]byte(entry.BucketName())) - if b == nil { - err := ErrDbAccess - logger.Err(err) - return nil, err - } - - // Check if key exists already - val := b.Get([]byte(key)) - if val != nil { - return val, ErrKeyExists - } - - // Key does not exist. We can save it - err := b.Put([]byte(key), value) - if err != nil { - logger.Err(err) - return nil, err - } - - return nil, nil -} - -func EntryKeys(tx *bolt.Tx, bucket string) []string { - list := make([]string, 0) - - // Get all the cluster ids from the DB - b := tx.Bucket([]byte(bucket)) - if b == nil { - return nil - } - - err := b.ForEach(func(k, v []byte) error { - list = append(list, string(k)) - return nil - }) - if err != nil { - return nil - } - - return list -} - -func EntrySave(tx *bolt.Tx, entry DbEntry, key string) error { - godbc.Require(tx != nil) - godbc.Require(len(key) > 0) - - // Access bucket - b := tx.Bucket([]byte(entry.BucketName())) - if b == nil { - err := ErrDbAccess - logger.Err(err) - return err - } - - // Save device entry to db - buffer, err := entry.Marshal() - if err != nil { - logger.Err(err) - return err - } - - // Save data using the id as the key - err = b.Put([]byte(key), buffer) - if err != nil { - logger.Err(err) - return err - } - - return nil -} - -func EntryDelete(tx *bolt.Tx, entry DbEntry, key string) error { - godbc.Require(tx != nil) - godbc.Require(len(key) > 0) - - // Access bucket - b := tx.Bucket([]byte(entry.BucketName())) - if b == nil { - err := ErrDbAccess - logger.Err(err) - return err - } - - // Delete key - err := b.Delete([]byte(key)) - if err != nil { - logger.LogError("Unable to delete key [%v] in db: %v", key, err.Error()) - return err - } - - return nil -} - -func EntryLoad(tx *bolt.Tx, entry DbEntry, key string) error { - godbc.Require(tx != nil) - godbc.Require(len(key) > 0) - - b := tx.Bucket([]byte(entry.BucketName())) - if b == nil { - err := ErrDbAccess - logger.Err(err) - return err - } - - val := b.Get([]byte(key)) - if val == nil { - return ErrNotFound - } - - err := entry.Unmarshal(val) - if err != nil { - logger.Err(err) - return err - } - - return nil -} diff --git a/apps/glusterfs/dbentry_test.go b/apps/glusterfs/dbentry_test.go deleted file mode 100644 index b43e14aed6..0000000000 --- a/apps/glusterfs/dbentry_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/boltdb/bolt" - "github.com/heketi/tests" - "os" - "testing" - "time" -) - -type testDbEntry struct { -} - -func (t *testDbEntry) BucketName() string { - return "TestBucket" -} - -func (t *testDbEntry) Marshal() ([]byte, error) { - return nil, nil -} - -func (t *testDbEntry) Unmarshal(data []byte) error { - return nil -} - -func TestEntryRegister(t *testing.T) { - tmpfile := tests.Tempfile() - - // Setup BoltDB database - db, err := bolt.Open(tmpfile, 0600, &bolt.Options{Timeout: 3 * time.Second}) - tests.Assert(t, err == nil) - defer os.Remove(tmpfile) - - // Create a bucket - entry := &testDbEntry{} - err = db.Update(func(tx *bolt.Tx) error { - - // Create Cluster Bucket - _, err := tx.CreateBucketIfNotExists([]byte(entry.BucketName())) - tests.Assert(t, err == nil) - - // Register a value - _, err = EntryRegister(tx, entry, "mykey", []byte("myvalue")) - tests.Assert(t, err == nil) - - return nil - }) - tests.Assert(t, err == nil) - - // Try to write key again - err = db.Update(func(tx *bolt.Tx) error { - - // Save again, it should not work - val, err := EntryRegister(tx, entry, "mykey", []byte("myvalue")) - tests.Assert(t, err == ErrKeyExists) - tests.Assert(t, string(val) == "myvalue") - - // Remove key - err = EntryDelete(tx, entry, "mykey") - tests.Assert(t, err == nil) - - // Register again - _, err = EntryRegister(tx, entry, "mykey", []byte("myvalue")) - tests.Assert(t, err == nil) - - return nil - }) - tests.Assert(t, err == nil) - -} diff --git a/apps/glusterfs/device_entry.go b/apps/glusterfs/device_entry.go deleted file mode 100644 index dd613b166f..0000000000 --- a/apps/glusterfs/device_entry.go +++ /dev/null @@ -1,506 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "encoding/gob" - "fmt" - "sort" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/lpabon/godbc" -) - -const ( - maxPoolMetadataSizeMb = 16 * GB -) - -type DeviceEntry struct { - Entry - - Info api.DeviceInfo - Bricks sort.StringSlice - NodeId string - ExtentSize uint64 -} - -func DeviceList(tx *bolt.Tx) ([]string, error) { - - list := EntryKeys(tx, BOLTDB_BUCKET_DEVICE) - if list == nil { - return nil, ErrAccessList - } - return list, nil -} - -func NewDeviceEntry() *DeviceEntry { - entry := &DeviceEntry{} - entry.Bricks = make(sort.StringSlice, 0) - entry.SetOnline() - - // Default to 4096KB - entry.ExtentSize = 4096 - - return entry -} - -func NewDeviceEntryFromRequest(req *api.DeviceAddRequest) *DeviceEntry { - godbc.Require(req != nil) - - device := NewDeviceEntry() - device.Info.Id = utils.GenUUID() - device.Info.Name = req.Name - device.NodeId = req.NodeId - - return device -} - -func NewDeviceEntryFromId(tx *bolt.Tx, id string) (*DeviceEntry, error) { - godbc.Require(tx != nil) - - entry := NewDeviceEntry() - err := EntryLoad(tx, entry, id) - if err != nil { - return nil, err - } - - return entry, nil -} - -func (d *DeviceEntry) registerKey() string { - return "DEVICE" + d.NodeId + d.Info.Name -} - -func (d *DeviceEntry) Register(tx *bolt.Tx) error { - godbc.Require(tx != nil) - - val, err := EntryRegister(tx, - d, - d.registerKey(), - []byte(d.Id())) - if err == ErrKeyExists { - - // Now check if the node actually exists. This only happens - // when the application crashes and it doesn't clean up stale - // registrations. - conflictId := string(val) - _, err := NewDeviceEntryFromId(tx, conflictId) - if err == ErrNotFound { - // (stale) There is actually no conflict, we can allow - // the registration - return nil - } else if err != nil { - return logger.Err(err) - } - - return fmt.Errorf("Device %v is already used on node %v by device %v", - d.Info.Name, - d.NodeId, - conflictId) - - } else if err != nil { - return err - } - - return nil -} - -func (d *DeviceEntry) Deregister(tx *bolt.Tx) error { - godbc.Require(tx != nil) - - err := EntryDelete(tx, d, d.registerKey()) - if err != nil { - return err - } - - return nil -} - -func (d *DeviceEntry) SetId(id string) { - d.Info.Id = id -} - -func (d *DeviceEntry) Id() string { - return d.Info.Id -} - -func (d *DeviceEntry) BucketName() string { - return BOLTDB_BUCKET_DEVICE -} - -func (d *DeviceEntry) Save(tx *bolt.Tx) error { - godbc.Require(tx != nil) - godbc.Require(len(d.Info.Id) > 0) - - return EntrySave(tx, d, d.Info.Id) - -} - -func (d *DeviceEntry) IsDeleteOk() bool { - // Check if the nodes still has drives - if len(d.Bricks) > 0 { - return false - } - return true -} - -func (d *DeviceEntry) ConflictString() string { - return fmt.Sprintf("Unable to delete device [%v] because it contains bricks", d.Info.Id) -} - -func (d *DeviceEntry) Delete(tx *bolt.Tx) error { - godbc.Require(tx != nil) - - // Check if the devices still has drives - if !d.IsDeleteOk() { - logger.Warning(d.ConflictString()) - return ErrConflict - } - - return EntryDelete(tx, d, d.Info.Id) -} - -func (d *DeviceEntry) removeDeviceFromRing(tx *bolt.Tx, - a Allocator) error { - - node, err := NewNodeEntryFromId(tx, d.NodeId) - if err != nil { - return err - } - - cluster, err := NewClusterEntryFromId(tx, node.Info.ClusterId) - if err != nil { - return err - } - - return a.RemoveDevice(cluster, node, d) -} - -func (d *DeviceEntry) addDeviceToRing(tx *bolt.Tx, - a Allocator) error { - - node, err := NewNodeEntryFromId(tx, d.NodeId) - if err != nil { - return err - } - - cluster, err := NewClusterEntryFromId(tx, node.Info.ClusterId) - if err != nil { - return err - } - - return a.AddDevice(cluster, node, d) -} - -func (d *DeviceEntry) SetState(db *bolt.DB, - e executors.Executor, - a Allocator, - s api.EntryState) error { - - // Check current state - switch d.State { - - // Device is in removed/failed state - case api.EntryStateFailed: - switch s { - case api.EntryStateFailed: - return nil - case api.EntryStateOnline: - return fmt.Errorf("Cannot move a failed/removed device to online state") - case api.EntryStateOffline: - return fmt.Errorf("Cannot move a failed/removed device to offline state") - default: - return fmt.Errorf("Unknown state type: %v", s) - } - - // Device is in enabled/online state - case api.EntryStateOnline: - switch s { - case api.EntryStateOnline: - return nil - case api.EntryStateOffline: - // Remove disk from Ring - err := db.Update(func(tx *bolt.Tx) error { - err := d.removeDeviceFromRing(tx, a) - if err != nil { - return err - } - - // Save state - d.State = s - // Save new state - err = d.Save(tx) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - case api.EntryStateFailed: - return fmt.Errorf("Device must be offline before remove operation is performed, device:%v", d.Id()) - default: - return fmt.Errorf("Unknown state type: %v", s) - } - - // Device is in disabled/offline state - case api.EntryStateOffline: - switch s { - case api.EntryStateOffline: - return nil - case api.EntryStateOnline: - // Add disk back - err := db.Update(func(tx *bolt.Tx) error { - err := d.addDeviceToRing(tx, a) - if err != nil { - return err - } - d.State = s - err = d.Save(tx) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - case api.EntryStateFailed: - - err := d.Remove(db, e, a) - if err != nil { - if err == ErrNoReplacement { - return logger.LogError("Unable to delete device [%v] as no device was found to replace it", d.Id()) - } - return err - } - default: - return fmt.Errorf("Unknown state type: %v", s) - } - } - - return nil -} - -func (d *DeviceEntry) NewInfoResponse(tx *bolt.Tx) (*api.DeviceInfoResponse, error) { - - godbc.Require(tx != nil) - - info := &api.DeviceInfoResponse{} - info.Id = d.Info.Id - info.Name = d.Info.Name - info.Storage = d.Info.Storage - info.State = d.State - info.Bricks = make([]api.BrickInfo, 0) - - // Add each drive information - for _, id := range d.Bricks { - brick, err := NewBrickEntryFromId(tx, id) - if err != nil { - return nil, err - } - - brickinfo, err := brick.NewInfoResponse(tx) - if err != nil { - return nil, err - } - info.Bricks = append(info.Bricks, *brickinfo) - } - - return info, nil -} - -func (d *DeviceEntry) Marshal() ([]byte, error) { - var buffer bytes.Buffer - enc := gob.NewEncoder(&buffer) - err := enc.Encode(*d) - - return buffer.Bytes(), err -} - -func (d *DeviceEntry) Unmarshal(buffer []byte) error { - dec := gob.NewDecoder(bytes.NewReader(buffer)) - err := dec.Decode(d) - if err != nil { - return err - } - - // Make sure to setup arrays if nil - if d.Bricks == nil { - d.Bricks = make(sort.StringSlice, 0) - } - - return nil -} - -func (d *DeviceEntry) BrickAdd(id string) { - godbc.Require(!utils.SortedStringHas(d.Bricks, id)) - - d.Bricks = append(d.Bricks, id) - d.Bricks.Sort() -} - -func (d *DeviceEntry) BrickDelete(id string) { - d.Bricks = utils.SortedStringsDelete(d.Bricks, id) -} - -func (d *DeviceEntry) StorageSet(amount uint64) { - d.Info.Storage.Free = amount - d.Info.Storage.Total = amount -} - -func (d *DeviceEntry) StorageAllocate(amount uint64) { - d.Info.Storage.Free -= amount - d.Info.Storage.Used += amount -} - -func (d *DeviceEntry) StorageFree(amount uint64) { - d.Info.Storage.Free += amount - d.Info.Storage.Used -= amount -} - -func (d *DeviceEntry) StorageCheck(amount uint64) bool { - return d.Info.Storage.Free > amount -} - -func (d *DeviceEntry) SetExtentSize(amount uint64) { - d.ExtentSize = amount -} - -// Allocates a new brick if the space is available. It will automatically reserve -// the storage amount required from the device's used storage, but it will not add -// the brick id to the brick list. The caller is responsabile for adding the brick -// id to the list. -func (d *DeviceEntry) NewBrickEntry(amount uint64, snapFactor float64, gid int64, volumeid string) *BrickEntry { - - // :TODO: This needs unit test - - // Calculate thinpool size - tpsize := uint64(float64(amount) * snapFactor) - - // Align tpsize to extent - alignment := tpsize % d.ExtentSize - if alignment != 0 { - tpsize += d.ExtentSize - alignment - } - - // Determine if we need to allocate space for the metadata - metadataSize := d.poolMetadataSize(tpsize) - - // Align to extent - alignment = metadataSize % d.ExtentSize - if alignment != 0 { - metadataSize += d.ExtentSize - alignment - } - - // Total required size - total := tpsize + metadataSize - - logger.Debug("device %v[%v] > required size [%v] ?", - d.Id(), - d.Info.Storage.Free, total) - if !d.StorageCheck(total) { - return nil - } - - // Allocate amount from disk - d.StorageAllocate(total) - - // Create brick - return NewBrickEntry(amount, tpsize, metadataSize, d.Info.Id, d.NodeId, gid, volumeid) -} - -// Return poolmetadatasize in KB -func (d *DeviceEntry) poolMetadataSize(tpsize uint64) uint64 { - - // TP size is in KB - p := uint64(float64(tpsize) * 0.005) - if p > maxPoolMetadataSizeMb { - p = maxPoolMetadataSizeMb - } - - return p -} - -// Moves all the bricks from the device to one or more other devices -func (d *DeviceEntry) Remove(db *bolt.DB, - executor executors.Executor, - allocator Allocator) (e error) { - - // If the device has no bricks, just change the state and we are done - if d.IsDeleteOk() { - d.State = api.EntryStateFailed - // Save new state - err := db.Update(func(tx *bolt.Tx) error { - err := d.Save(tx) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - } - - for _, brickId := range d.Bricks { - var brickEntry *BrickEntry - var volumeEntry *VolumeEntry - err := db.View(func(tx *bolt.Tx) error { - var err error - brickEntry, err = NewBrickEntryFromId(tx, brickId) - if err != nil { - return err - } - volumeEntry, err = NewVolumeEntryFromId(tx, brickEntry.Info.VolumeId) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - logger.Info("Replacing brick %v on device %v on node %v", brickEntry.Id(), d.Id(), d.NodeId) - err = volumeEntry.replaceBrickInVolume(db, executor, allocator, brickEntry.Id()) - if err != nil { - return logger.Err(fmt.Errorf("Failed to remove device, error: %v", err)) - } - } - - // Set device state to failed - // Get new entry for the device because db would have changed - // replaceBrickInVolume calls functions that change device state in db - err := db.Update(func(tx *bolt.Tx) error { - newDeviceEntry, err := NewDeviceEntryFromId(tx, d.Id()) - if err != nil { - return err - } - newDeviceEntry.State = api.EntryStateFailed - err = newDeviceEntry.Save(tx) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - return nil -} - -func DeviceEntryUpgrade(tx *bolt.Tx) error { - return nil -} diff --git a/apps/glusterfs/device_entry_test.go b/apps/glusterfs/device_entry_test.go deleted file mode 100644 index b30b800248..0000000000 --- a/apps/glusterfs/device_entry_test.go +++ /dev/null @@ -1,747 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "os" - "reflect" - "sort" - "testing" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func createSampleDeviceEntry(nodeid string, disksize uint64) *DeviceEntry { - - req := &api.DeviceAddRequest{} - req.NodeId = nodeid - req.Name = "/dev/" + utils.GenUUID()[:8] - - d := NewDeviceEntryFromRequest(req) - d.StorageSet(disksize) - - return d -} - -func TestNewDeviceEntry(t *testing.T) { - - d := NewDeviceEntry() - tests.Assert(t, d != nil) - tests.Assert(t, d.Info.Id == "") - tests.Assert(t, d.Info.Name == "") - tests.Assert(t, d.Info.Storage.Free == 0) - tests.Assert(t, d.Info.Storage.Total == 0) - tests.Assert(t, d.Info.Storage.Used == 0) - tests.Assert(t, d.Bricks != nil) - tests.Assert(t, len(d.Bricks) == 0) - -} - -func TestNewDeviceEntryFromRequest(t *testing.T) { - req := &api.DeviceAddRequest{} - req.NodeId = "123" - req.Name = "/dev/" + utils.GenUUID() - - d := NewDeviceEntryFromRequest(req) - tests.Assert(t, d != nil) - tests.Assert(t, d.Info.Id != "") - tests.Assert(t, d.Info.Name == req.Name) - tests.Assert(t, d.Info.Storage.Free == 0) - tests.Assert(t, d.Info.Storage.Total == 0) - tests.Assert(t, d.Info.Storage.Used == 0) - tests.Assert(t, d.NodeId == "123") - tests.Assert(t, d.Bricks != nil) - tests.Assert(t, len(d.Bricks) == 0) - -} - -func TestNewDeviceEntryMarshal(t *testing.T) { - req := &api.DeviceAddRequest{} - req.NodeId = "abc" - req.Name = "/dev/" + utils.GenUUID() - - d := NewDeviceEntryFromRequest(req) - d.Info.Storage.Free = 10 - d.Info.Storage.Total = 100 - d.Info.Storage.Used = 1000 - d.BrickAdd("abc") - d.BrickAdd("def") - - buffer, err := d.Marshal() - tests.Assert(t, err == nil) - tests.Assert(t, buffer != nil) - tests.Assert(t, len(buffer) > 0) - - um := &DeviceEntry{} - err = um.Unmarshal(buffer) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(um, d)) - -} - -func TestDeviceEntryNewBrickEntry(t *testing.T) { - req := &api.DeviceAddRequest{} - req.NodeId = "abc" - req.Name = "/dev/" + utils.GenUUID() - - d := NewDeviceEntryFromRequest(req) - d.Info.Storage.Free = 900 - d.Info.Storage.Total = 1000 - d.Info.Storage.Used = 100 - - // Alignment - d.ExtentSize = 8 - - // Too large - brick := d.NewBrickEntry(1000000000, 1.5, 1000, "abc") - tests.Assert(t, brick == nil) - - // --- Now check with a real value --- - - // Check newly created brick - size := 201 - tpsize := uint64(float32(size) * 1.5) - - // Alignment - tpsize += d.ExtentSize - (tpsize % d.ExtentSize) - - // Calculate metadatasize - metadatasize := d.poolMetadataSize(tpsize) - - // Alignment - metadatasize += d.ExtentSize - (metadatasize % d.ExtentSize) - total := tpsize + metadatasize - - brick = d.NewBrickEntry(200, 1.5, 1000, "abc") - tests.Assert(t, brick != nil) - tests.Assert(t, brick.TpSize == tpsize) - tests.Assert(t, brick.PoolMetadataSize == metadatasize, brick.PoolMetadataSize, metadatasize) - tests.Assert(t, brick.Info.Size == 200) - tests.Assert(t, brick.gidRequested == 1000) - tests.Assert(t, brick.Info.VolumeId == "abc") - - // Check it was subtracted from device storage - tests.Assert(t, d.Info.Storage.Used == 100+total) - tests.Assert(t, d.Info.Storage.Free == 900-total) - tests.Assert(t, d.Info.Storage.Total == 1000) -} - -func TestDeviceEntryAddDeleteBricks(t *testing.T) { - d := NewDeviceEntry() - tests.Assert(t, len(d.Bricks) == 0) - - d.BrickAdd("123") - tests.Assert(t, utils.SortedStringHas(d.Bricks, "123")) - tests.Assert(t, len(d.Bricks) == 1) - d.BrickAdd("abc") - tests.Assert(t, utils.SortedStringHas(d.Bricks, "123")) - tests.Assert(t, utils.SortedStringHas(d.Bricks, "abc")) - tests.Assert(t, len(d.Bricks) == 2) - - d.BrickDelete("123") - tests.Assert(t, !utils.SortedStringHas(d.Bricks, "123")) - tests.Assert(t, utils.SortedStringHas(d.Bricks, "abc")) - tests.Assert(t, len(d.Bricks) == 1) - - d.BrickDelete("ccc") - tests.Assert(t, !utils.SortedStringHas(d.Bricks, "123")) - tests.Assert(t, utils.SortedStringHas(d.Bricks, "abc")) - tests.Assert(t, len(d.Bricks) == 1) -} - -func TestNewDeviceEntryFromIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Test for ID not found - err := app.db.View(func(tx *bolt.Tx) error { - _, err := NewDeviceEntryFromId(tx, "123") - return err - }) - tests.Assert(t, err == ErrNotFound) - -} - -func TestDeviceEntryRegister(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a device - req := &api.DeviceAddRequest{} - req.NodeId = "abc" - req.Name = "/dev/" + utils.GenUUID() - - d := NewDeviceEntryFromRequest(req) - - // Register device - err := app.db.Update(func(tx *bolt.Tx) error { - err := d.Register(tx) - tests.Assert(t, err == nil) - - return d.Save(tx) - }) - tests.Assert(t, err == nil) - - // Should not be able to register again - err = app.db.Update(func(tx *bolt.Tx) error { - err := d.Register(tx) - tests.Assert(t, err != nil) - - return err - }) - tests.Assert(t, err != nil) - - // Create another device on a different node device - req = &api.DeviceAddRequest{} - req.NodeId = "def" - req.Name = "/dev/" + utils.GenUUID() - - d2 := NewDeviceEntryFromRequest(req) - - // Same device on different node should work - err = app.db.Update(func(tx *bolt.Tx) error { - err := d2.Register(tx) - tests.Assert(t, err == nil) - - return d2.Save(tx) - }) - tests.Assert(t, err == nil) - - // Remove d - err = app.db.Update(func(tx *bolt.Tx) error { - err := d.Deregister(tx) - tests.Assert(t, err == nil) - - return d.Delete(tx) - }) - tests.Assert(t, err == nil) - - // Register d node again - err = app.db.Update(func(tx *bolt.Tx) error { - err := d.Register(tx) - tests.Assert(t, err == nil) - - return d.Save(tx) - }) - tests.Assert(t, err == nil) - -} - -func TestDeviceEntryRegisterStaleRegistration(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a device - req := &api.DeviceAddRequest{} - req.NodeId = "abc" - req.Name = "/dev/" + utils.GenUUID() - - d := NewDeviceEntryFromRequest(req) - - // Only register device but do not save it - err := app.db.Update(func(tx *bolt.Tx) error { - return d.Register(tx) - }) - tests.Assert(t, err == nil) - - // Should be able to register again - err = app.db.Update(func(tx *bolt.Tx) error { - err := d.Register(tx) - tests.Assert(t, err == nil) - - return d.Save(tx) - }) - tests.Assert(t, err == nil) - - // Should not be able to register again - err = app.db.Update(func(tx *bolt.Tx) error { - return d.Register(tx) - }) - tests.Assert(t, err != nil) - - // Remove d - err = app.db.Update(func(tx *bolt.Tx) error { - err := d.Deregister(tx) - tests.Assert(t, err == nil) - - return d.Delete(tx) - }) - tests.Assert(t, err == nil) - - // Register d node again - err = app.db.Update(func(tx *bolt.Tx) error { - err := d.Register(tx) - tests.Assert(t, err == nil) - - return d.Save(tx) - }) - tests.Assert(t, err == nil) - -} - -func TestNewDeviceEntryFromId(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a device - req := &api.DeviceAddRequest{} - req.NodeId = "abc" - req.Name = "/dev/" + utils.GenUUID() - - d := NewDeviceEntryFromRequest(req) - d.Info.Storage.Free = 10 - d.Info.Storage.Total = 100 - d.Info.Storage.Used = 1000 - d.BrickAdd("abc") - d.BrickAdd("def") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return d.Save(tx) - }) - tests.Assert(t, err == nil) - - var device *DeviceEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - device, err = NewDeviceEntryFromId(tx, d.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(device, d)) -} - -func TestNewDeviceEntrySaveDelete(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a device - req := &api.DeviceAddRequest{} - req.NodeId = "abc" - req.Name = "/dev/" + utils.GenUUID() - - d := NewDeviceEntryFromRequest(req) - d.Info.Storage.Free = 10 - d.Info.Storage.Total = 100 - d.Info.Storage.Used = 1000 - d.BrickAdd("abc") - d.BrickAdd("def") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return d.Save(tx) - }) - tests.Assert(t, err == nil) - - var device *DeviceEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - device, err = NewDeviceEntryFromId(tx, d.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(device, d)) - - // Delete entry which has devices - err = app.db.Update(func(tx *bolt.Tx) error { - var err error - device, err = NewDeviceEntryFromId(tx, d.Info.Id) - if err != nil { - return err - } - - err = device.Delete(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == ErrConflict) - - // Delete devices in device - device.BrickDelete("abc") - device.BrickDelete("def") - tests.Assert(t, len(device.Bricks) == 0) - err = app.db.Update(func(tx *bolt.Tx) error { - return device.Save(tx) - }) - tests.Assert(t, err == nil) - - // Now try to delete the device - err = app.db.Update(func(tx *bolt.Tx) error { - var err error - device, err = NewDeviceEntryFromId(tx, d.Info.Id) - if err != nil { - return err - } - - err = device.Delete(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Check device has been deleted and is not in db - err = app.db.View(func(tx *bolt.Tx) error { - var err error - device, err = NewDeviceEntryFromId(tx, d.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == ErrNotFound) -} - -func TestNewDeviceEntryNewInfoResponseBadBrickIds(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a device - req := &api.DeviceAddRequest{} - req.NodeId = "abc" - req.Name = "/dev/" + utils.GenUUID() - - // Add bad brick ids - d := NewDeviceEntryFromRequest(req) - d.Info.Storage.Free = 10 - d.Info.Storage.Total = 100 - d.Info.Storage.Used = 1000 - d.BrickAdd("abc") - d.BrickAdd("def") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return d.Save(tx) - }) - tests.Assert(t, err == nil) - - var info *api.DeviceInfoResponse - err = app.db.View(func(tx *bolt.Tx) error { - device, err := NewDeviceEntryFromId(tx, d.Info.Id) - if err != nil { - return err - } - - info, err = device.NewInfoResponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == ErrNotFound) -} - -func TestNewDeviceEntryNewInfoResponse(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a device - req := &api.DeviceAddRequest{} - req.NodeId = "abc" - req.Name = "/dev/" + utils.GenUUID() - - d := NewDeviceEntryFromRequest(req) - d.Info.Storage.Free = 10 - d.Info.Storage.Total = 100 - d.Info.Storage.Used = 1000 - - // Create a brick - b := &BrickEntry{} - b.Info.Id = "bbb" - b.Info.Size = 10 - b.Info.NodeId = "abc" - b.Info.DeviceId = d.Info.Id - b.Info.Path = "/somepath" - - // Add brick to device - d.BrickAdd("bbb") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - err := d.Save(tx) - if err != nil { - return err - } - - return b.Save(tx) - }) - tests.Assert(t, err == nil) - - var info *api.DeviceInfoResponse - err = app.db.View(func(tx *bolt.Tx) error { - device, err := NewDeviceEntryFromId(tx, d.Info.Id) - if err != nil { - return err - } - - info, err = device.NewInfoResponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - tests.Assert(t, info.Id == d.Info.Id) - tests.Assert(t, info.Name == d.Info.Name) - tests.Assert(t, reflect.DeepEqual(info.Storage, d.Info.Storage)) - tests.Assert(t, len(info.Bricks) == 1) - tests.Assert(t, info.Bricks[0].Id == "bbb") - tests.Assert(t, info.Bricks[0].Path == "/somepath") - tests.Assert(t, info.Bricks[0].NodeId == "abc") - tests.Assert(t, info.Bricks[0].DeviceId == d.Info.Id) - tests.Assert(t, info.Bricks[0].Size == 10) - -} - -func TestDeviceEntryStorage(t *testing.T) { - d := NewDeviceEntry() - - tests.Assert(t, d.Info.Storage.Free == 0) - tests.Assert(t, d.Info.Storage.Total == 0) - tests.Assert(t, d.Info.Storage.Used == 0) - - d.StorageSet(1000) - tests.Assert(t, d.Info.Storage.Free == 1000) - tests.Assert(t, d.Info.Storage.Total == 1000) - tests.Assert(t, d.Info.Storage.Used == 0) - - d.StorageSet(2000) - tests.Assert(t, d.Info.Storage.Free == 2000) - tests.Assert(t, d.Info.Storage.Total == 2000) - tests.Assert(t, d.Info.Storage.Used == 0) - - d.StorageAllocate(1000) - tests.Assert(t, d.Info.Storage.Free == 1000) - tests.Assert(t, d.Info.Storage.Total == 2000) - tests.Assert(t, d.Info.Storage.Used == 1000) - - d.StorageAllocate(500) - tests.Assert(t, d.Info.Storage.Free == 500) - tests.Assert(t, d.Info.Storage.Total == 2000) - tests.Assert(t, d.Info.Storage.Used == 1500) - - d.StorageFree(500) - tests.Assert(t, d.Info.Storage.Free == 1000) - tests.Assert(t, d.Info.Storage.Total == 2000) - tests.Assert(t, d.Info.Storage.Used == 1000) - - d.StorageFree(1000) - tests.Assert(t, d.Info.Storage.Free == 2000) - tests.Assert(t, d.Info.Storage.Total == 2000) - tests.Assert(t, d.Info.Storage.Used == 0) -} - -func TestDeviceSetStateFailed(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create allocator - mockAllocator := NewMockAllocator(app.db) - app.allocator = mockAllocator - - // Create cluster entry - c := NewClusterEntry() - c.Info.Id = "cluster" - - // Create a node - n := NewNodeEntry() - tests.Assert(t, n != nil) - tests.Assert(t, n.State == api.EntryStateOnline) - - // Initialize node - n.Info.Id = "node" - n.Info.ClusterId = "cluster" - n.Devices = sort.StringSlice{"d1"} - - // Create device entry - d := NewDeviceEntry() - d.Info.Id = "d1" - d.Info.Name = "/d1" - d.NodeId = "node" - - // Add to allocator - mockAllocator.AddDevice(c, n, d) - - // Save in db - app.db.Update(func(tx *bolt.Tx) error { - err := c.Save(tx) - tests.Assert(t, err == nil) - - err = n.Save(tx) - tests.Assert(t, err == nil) - - err = d.Save(tx) - tests.Assert(t, err == nil) - - // Check ring - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[c.Info.Id][0] == d.Info.Id) - return nil - }) - - // Set offline - err := d.SetState(app.db, app.executor, mockAllocator, api.EntryStateOffline) - tests.Assert(t, d.State == api.EntryStateOffline) - tests.Assert(t, err == nil, err) - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 0) - - // Set failed, Note: this requires the current state to be offline - err = d.SetState(app.db, app.executor, mockAllocator, api.EntryStateFailed) - tests.Assert(t, d.State == api.EntryStateFailed) - tests.Assert(t, err == nil) - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 0) - - // Set failed again - err = d.SetState(app.db, app.executor, mockAllocator, api.EntryStateFailed) - tests.Assert(t, d.State == api.EntryStateFailed) - tests.Assert(t, err == nil) - - // Set offline - err = d.SetState(app.db, app.executor, mockAllocator, api.EntryStateOffline) - tests.Assert(t, d.State == api.EntryStateFailed) - tests.Assert(t, err != nil) - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 0) - - // Set online - err = d.SetState(app.db, app.executor, mockAllocator, api.EntryStateOnline) - tests.Assert(t, d.State == api.EntryStateFailed) - tests.Assert(t, err != nil) - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 0) - -} - -func TestDeviceSetStateOfflineOnline(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create allocator - mockAllocator := NewMockAllocator(app.db) - app.allocator = mockAllocator - - // Create cluster entry - c := NewClusterEntry() - c.Info.Id = "cluster" - - // Create a node - n := NewNodeEntry() - tests.Assert(t, n != nil) - tests.Assert(t, n.State == api.EntryStateOnline) - - // Initialize node - n.Info.Id = "node" - n.Info.ClusterId = "cluster" - n.Devices = sort.StringSlice{"d1"} - - // Create device entry - d := NewDeviceEntry() - d.Info.Id = "d1" - d.Info.Name = "/d1" - d.NodeId = "node" - - // Add to allocator - mockAllocator.AddDevice(c, n, d) - - // Save in db - app.db.Update(func(tx *bolt.Tx) error { - err := c.Save(tx) - tests.Assert(t, err == nil) - - err = n.Save(tx) - tests.Assert(t, err == nil) - - err = d.Save(tx) - tests.Assert(t, err == nil) - - // Check ring - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[c.Info.Id][0] == d.Info.Id) - return nil - }) - - // Set offline - err := d.SetState(app.db, app.executor, mockAllocator, api.EntryStateOffline) - tests.Assert(t, d.State == api.EntryStateOffline) - tests.Assert(t, err == nil) - - // Check it was removed from ring - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 0) - - // Set offline again - err = d.SetState(app.db, app.executor, mockAllocator, api.EntryStateOffline) - tests.Assert(t, d.State == api.EntryStateOffline) - tests.Assert(t, err == nil) - - // Set online - err = d.SetState(app.db, app.executor, mockAllocator, api.EntryStateOnline) - tests.Assert(t, d.State == api.EntryStateOnline) - tests.Assert(t, err == nil) - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[c.Info.Id][0] == d.Info.Id) - -} diff --git a/apps/glusterfs/entry.go b/apps/glusterfs/entry.go deleted file mode 100644 index f17cf39c25..0000000000 --- a/apps/glusterfs/entry.go +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/heketi/heketi/pkg/glusterfs/api" -) - -type Entry struct { - State api.EntryState -} - -func (e *Entry) isOnline() bool { - return e.State == api.EntryStateOnline -} - -func (e *Entry) SetOnline() { - e.State = api.EntryStateOnline -} diff --git a/apps/glusterfs/entry_test.go b/apps/glusterfs/entry_test.go deleted file mode 100644 index 236ee2c896..0000000000 --- a/apps/glusterfs/entry_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "testing" - - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/tests" -) - -func TestEntryStates(t *testing.T) { - e := &Entry{} - - tests.Assert(t, e.State == api.EntryStateUnknown) - tests.Assert(t, e.isOnline() == false) - - e.State = api.EntryStateOnline - tests.Assert(t, e.isOnline()) - - e.State = api.EntryStateOffline - tests.Assert(t, e.isOnline() == false) - - e.State = api.EntryStateFailed - tests.Assert(t, e.isOnline() == false) - -} diff --git a/apps/glusterfs/errors.go b/apps/glusterfs/errors.go deleted file mode 100644 index 74cad637cf..0000000000 --- a/apps/glusterfs/errors.go +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "errors" -) - -var ( - ErrNoSpace = errors.New("No space") - ErrNotFound = errors.New("Id not found") - ErrConflict = errors.New("The target exists, contains other items, or is in use.") - ErrMaxBricks = errors.New("Maximum number of bricks reached.") - ErrMinimumBrickSize = errors.New("Minimum brick size limit reached. Out of space.") - ErrDbAccess = errors.New("Unable to access db") - ErrAccessList = errors.New("Unable to access list") - ErrKeyExists = errors.New("Key already exists in the database") - ErrNoReplacement = errors.New("No Replacement was found for resource requested to be removed") -) diff --git a/apps/glusterfs/limits.go b/apps/glusterfs/limits.go deleted file mode 100644 index fe337a89ec..0000000000 --- a/apps/glusterfs/limits.go +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -var ( - // Default limits - BrickMinSize = uint64(1 * GB) - BrickMaxSize = uint64(4 * TB) - BrickMaxNum = 32 -) diff --git a/apps/glusterfs/node_entry.go b/apps/glusterfs/node_entry.go deleted file mode 100644 index 8b04b39f71..0000000000 --- a/apps/glusterfs/node_entry.go +++ /dev/null @@ -1,474 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "encoding/gob" - "fmt" - "sort" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/lpabon/godbc" -) - -type NodeEntry struct { - Entry - - Info api.NodeInfo - Devices sort.StringSlice -} - -func NewNodeEntry() *NodeEntry { - entry := &NodeEntry{} - entry.Devices = make(sort.StringSlice, 0) - entry.SetOnline() - - return entry -} - -func NewNodeEntryFromRequest(req *api.NodeAddRequest) *NodeEntry { - godbc.Require(req != nil) - - node := NewNodeEntry() - node.Info.Id = utils.GenUUID() - node.Info.ClusterId = req.ClusterId - node.Info.Hostnames = req.Hostnames - node.Info.Zone = req.Zone - - return node -} - -func NewNodeEntryFromId(tx *bolt.Tx, id string) (*NodeEntry, error) { - godbc.Require(tx != nil) - - entry := NewNodeEntry() - err := EntryLoad(tx, entry, id) - if err != nil { - return nil, err - } - - return entry, nil -} - -func (n *NodeEntry) registerManageKey(host string) string { - return "MANAGE" + host -} - -func (n *NodeEntry) registerStorageKey(host string) string { - return "STORAGE" + host -} - -// Verify gluster process in the node and return the manage hostname of a node in the cluster -func GetVerifiedManageHostname(db *bolt.DB, e executors.Executor, clusterId string) (string, error) { - godbc.Require(clusterId != "") - var cluster *ClusterEntry - var node *NodeEntry - var err error - err = db.View(func(tx *bolt.Tx) error { - var err error - cluster, err = NewClusterEntryFromId(tx, clusterId) - return err - }) - - if err != nil { - return "", err - } - - for _, n := range cluster.Info.Nodes { - var newNode *NodeEntry - err = db.View(func(tx *bolt.Tx) error { - var err error - newNode, err = NewNodeEntryFromId(tx, n) - return err - }) - - if err != nil { - //pass on to next node - continue - } - - // Ignore if the node is not online - if !newNode.isOnline() { - continue - } - err = e.GlusterdCheck(newNode.ManageHostName()) - if err != nil { - logger.Info("Glusterd not running in %v", newNode.ManageHostName()) - continue - } - node = newNode - break - } - if node != nil { - return node.ManageHostName(), nil - } - return "", ErrNotFound -} - -func (n *NodeEntry) Register(tx *bolt.Tx) error { - - // Save manage hostnames - for _, h := range n.Info.Hostnames.Manage { - val, err := EntryRegister(tx, n, n.registerManageKey(h), []byte(n.Info.Id)) - if err == ErrKeyExists { - // Now check if the node actually exists. This only happens - // when the application crashes and it doesn't clean up stale - // registrations. - conflictId := string(val) - _, err := NewNodeEntryFromId(tx, conflictId) - if err == ErrNotFound { - // (stale) There is actually no conflict, we can allow - // the registration - return nil - } else if err != nil { - return logger.Err(err) - } - - // Return that we found a conflict - return fmt.Errorf("Hostname %v already used by node with id %v\n", - h, conflictId) - } else if err != nil { - return err - } - } - - // Save storage hostnames - for _, h := range n.Info.Hostnames.Storage { - val, err := EntryRegister(tx, n, n.registerStorageKey(h), []byte(n.Info.Id)) - if err == ErrKeyExists { - - // Check if it exists - conflictId := string(val) - _, err := NewNodeEntryFromId(tx, conflictId) - if err == ErrNotFound { - // (stale) There is actually no conflict, we can allow - // the registration - return nil - } else if err != nil { - return logger.Err(err) - } - - // Return that we found a conflict - return fmt.Errorf("Hostname %v already used by node with id %v\n", - h, conflictId) - } else if err != nil { - return err - } - } - - return nil - -} - -func (n *NodeEntry) Deregister(tx *bolt.Tx) error { - - // Remove manage hostnames from Db - for _, h := range n.Info.Hostnames.Manage { - err := EntryDelete(tx, n, n.registerManageKey(h)) - if err != nil { - return err - } - } - - // Remove storage hostnames - for _, h := range n.Info.Hostnames.Storage { - err := EntryDelete(tx, n, n.registerStorageKey(h)) - if err != nil { - return err - } - } - - return nil -} - -func (n *NodeEntry) BucketName() string { - return BOLTDB_BUCKET_NODE -} - -func (n *NodeEntry) Save(tx *bolt.Tx) error { - godbc.Require(tx != nil) - godbc.Require(len(n.Info.Id) > 0) - - return EntrySave(tx, n, n.Info.Id) - -} - -func (n *NodeEntry) ManageHostName() string { - godbc.Require(n.Info.Hostnames.Manage != nil) - godbc.Require(len(n.Info.Hostnames.Manage) > 0) - - return n.Info.Hostnames.Manage[0] -} - -func (n *NodeEntry) StorageHostName() string { - godbc.Require(n.Info.Hostnames.Storage != nil) - godbc.Require(len(n.Info.Hostnames.Storage) > 0) - - return n.Info.Hostnames.Storage[0] -} - -func (n *NodeEntry) IsDeleteOk() bool { - // Check if the nodes still has drives - if len(n.Devices) > 0 { - return false - } - return true -} - -func (n *NodeEntry) ConflictString() string { - return fmt.Sprintf("Unable to delete node [%v] because it contains devices", n.Info.Id) -} - -func (n *NodeEntry) Delete(tx *bolt.Tx) error { - godbc.Require(tx != nil) - - // Check if the nodes still has drives - if !n.IsDeleteOk() { - logger.Warning(n.ConflictString()) - return ErrConflict - } - - return EntryDelete(tx, n, n.Info.Id) -} - -func (n *NodeEntry) removeAllDisksFromRing(tx *bolt.Tx, - a Allocator) error { - - cluster, err := NewClusterEntryFromId(tx, n.Info.ClusterId) - if err != nil { - return err - } - - for _, deviceId := range n.Devices { - device, err := NewDeviceEntryFromId(tx, deviceId) - if err != nil { - return err - } - - // Remove device - err = a.RemoveDevice(cluster, n, device) - if err != nil { - return err - } - } - - return nil -} - -func (n *NodeEntry) addAllDisksToRing(tx *bolt.Tx, - a Allocator) error { - - cluster, err := NewClusterEntryFromId(tx, n.Info.ClusterId) - if err != nil { - return err - } - - // Add all devices - for _, deviceId := range n.Devices { - device, err := NewDeviceEntryFromId(tx, deviceId) - if err != nil { - return err - } - - // Add device - if device.isOnline() { - err = a.AddDevice(cluster, n, device) - if err != nil { - return err - } - } - } - - return nil -} - -func (n *NodeEntry) SetState(db *bolt.DB, e executors.Executor, - a Allocator, - s api.EntryState) error { - - // Check current state - switch n.State { - - // Node is in removed/failed state - case api.EntryStateFailed: - switch s { - case api.EntryStateFailed: - return nil - case api.EntryStateOnline: - return fmt.Errorf("Cannot move a failed/removed node to online state") - case api.EntryStateOffline: - return fmt.Errorf("Cannot move a failed/removed node to offline state") - default: - return fmt.Errorf("Unknown state type: %v", s) - } - - // Node is in enabled/online state - case api.EntryStateOnline: - switch s { - case api.EntryStateOnline: - return nil - case api.EntryStateOffline: - // Remove all disks from Ring - err := db.Update(func(tx *bolt.Tx) error { - err := n.removeAllDisksFromRing(tx, a) - if err != nil { - return err - } - - // Save state - n.State = s - // Save new state - err = n.Save(tx) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - case api.EntryStateFailed: - return fmt.Errorf("Node must be offline before remove operation is performed, node:%v", n.Info.Id) - default: - return fmt.Errorf("Unknown state type: %v", s) - } - - // Node is in disabled/offline state - case api.EntryStateOffline: - switch s { - case api.EntryStateOffline: - return nil - case api.EntryStateOnline: - // Add all disks back - err := db.Update(func(tx *bolt.Tx) error { - err := n.addAllDisksToRing(tx, a) - if err != nil { - return err - } - n.State = s - err = n.Save(tx) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - case api.EntryStateFailed: - for _, id := range n.Devices { - var d *DeviceEntry - err := db.View(func(tx *bolt.Tx) error { - var err error - d, err = NewDeviceEntryFromId(tx, id) - if err != nil { - return err - } - return nil - }) - err = d.Remove(db, e, a) - if err != nil { - if err == ErrNoReplacement { - return logger.LogError("Unable to remove node [%v] as no device was found to replace device [%v]", n.Info.Id, d.Id()) - } - return err - } - } - - // Make the state change to failed - err := db.Update(func(tx *bolt.Tx) error { - n.State = s - err := n.Save(tx) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - - default: - return fmt.Errorf("Unknown state type: %v", s) - } - } - return nil -} - -func (n *NodeEntry) NewInfoReponse(tx *bolt.Tx) (*api.NodeInfoResponse, error) { - - godbc.Require(tx != nil) - - info := &api.NodeInfoResponse{} - info.ClusterId = n.Info.ClusterId - info.Hostnames = n.Info.Hostnames - info.Id = n.Info.Id - info.Zone = n.Info.Zone - info.State = n.State - info.DevicesInfo = make([]api.DeviceInfoResponse, 0) - - // Add each drive information - for _, deviceid := range n.Devices { - device, err := NewDeviceEntryFromId(tx, deviceid) - if err != nil { - return nil, err - } - - driveinfo, err := device.NewInfoResponse(tx) - if err != nil { - return nil, err - } - info.DevicesInfo = append(info.DevicesInfo, *driveinfo) - } - - return info, nil -} - -func (n *NodeEntry) Marshal() ([]byte, error) { - var buffer bytes.Buffer - enc := gob.NewEncoder(&buffer) - err := enc.Encode(*n) - - return buffer.Bytes(), err -} - -func (n *NodeEntry) Unmarshal(buffer []byte) error { - dec := gob.NewDecoder(bytes.NewReader(buffer)) - err := dec.Decode(n) - if err != nil { - return err - } - - // Make sure to setup arrays if nil - if n.Devices == nil { - n.Devices = make(sort.StringSlice, 0) - } - - return nil -} - -func (n *NodeEntry) DeviceAdd(id string) { - godbc.Require(!utils.SortedStringHas(n.Devices, id)) - - n.Devices = append(n.Devices, id) - n.Devices.Sort() -} - -func (n *NodeEntry) DeviceDelete(id string) { - n.Devices = utils.SortedStringsDelete(n.Devices, id) -} - -func NodeEntryUpgrade(tx *bolt.Tx) error { - return nil -} diff --git a/apps/glusterfs/node_entry_test.go b/apps/glusterfs/node_entry_test.go deleted file mode 100644 index 714c63f540..0000000000 --- a/apps/glusterfs/node_entry_test.go +++ /dev/null @@ -1,698 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "os" - "reflect" - "sort" - "testing" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func createSampleNodeEntry() *NodeEntry { - req := &api.NodeAddRequest{ - ClusterId: "123", - Hostnames: api.HostAddresses{ - Manage: []string{"manage" + utils.GenUUID()[:8]}, - Storage: []string{"storage" + utils.GenUUID()[:8]}, - }, - Zone: 99, - } - - return NewNodeEntryFromRequest(req) -} - -func TestNewNodeEntry(t *testing.T) { - - n := NewNodeEntry() - tests.Assert(t, n.Info.Id == "") - tests.Assert(t, n.Info.ClusterId == "") - tests.Assert(t, len(n.Devices) == 0) - tests.Assert(t, n.Devices != nil) -} - -func TestNewNodeEntryFromRequest(t *testing.T) { - req := &api.NodeAddRequest{ - ClusterId: "123", - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - - n := NewNodeEntryFromRequest(req) - tests.Assert(t, n != nil) - tests.Assert(t, n.Info.ClusterId == req.ClusterId) - tests.Assert(t, n.Info.Zone == req.Zone) - tests.Assert(t, len(n.Info.Id) > 0) - tests.Assert(t, len(n.Info.Hostnames.Manage) == len(req.Hostnames.Manage)) - tests.Assert(t, len(n.Info.Hostnames.Storage) == len(req.Hostnames.Storage)) - tests.Assert(t, n.Info.Hostnames.Manage[0] == req.Hostnames.Manage[0]) - tests.Assert(t, n.Info.Hostnames.Storage[0] == req.Hostnames.Storage[0]) - -} - -func TestNewNodeEntryMarshal(t *testing.T) { - req := &api.NodeAddRequest{ - ClusterId: "123", - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - - n := NewNodeEntryFromRequest(req) - n.DeviceAdd("abc") - n.DeviceAdd("def") - - buffer, err := n.Marshal() - tests.Assert(t, err == nil) - tests.Assert(t, buffer != nil) - tests.Assert(t, len(buffer) > 0) - - um := &NodeEntry{} - err = um.Unmarshal(buffer) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(n, um)) - -} - -func TestNodeEntryAddDeleteDevices(t *testing.T) { - n := NewNodeEntry() - tests.Assert(t, len(n.Devices) == 0) - - n.DeviceAdd("123") - tests.Assert(t, utils.SortedStringHas(n.Devices, "123")) - tests.Assert(t, len(n.Devices) == 1) - n.DeviceAdd("abc") - tests.Assert(t, utils.SortedStringHas(n.Devices, "123")) - tests.Assert(t, utils.SortedStringHas(n.Devices, "abc")) - tests.Assert(t, len(n.Devices) == 2) - - n.DeviceDelete("123") - tests.Assert(t, !utils.SortedStringHas(n.Devices, "123")) - tests.Assert(t, utils.SortedStringHas(n.Devices, "abc")) - tests.Assert(t, len(n.Devices) == 1) - - n.DeviceDelete("ccc") - tests.Assert(t, !utils.SortedStringHas(n.Devices, "123")) - tests.Assert(t, utils.SortedStringHas(n.Devices, "abc")) - tests.Assert(t, len(n.Devices) == 1) -} - -func TestNodeEntryRegister(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a node - req := &api.NodeAddRequest{ - ClusterId: "123", - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - n := NewNodeEntryFromRequest(req) - - // Register node - err := app.db.Update(func(tx *bolt.Tx) error { - err := n.Register(tx) - tests.Assert(t, err == nil) - - return n.Save(tx) - }) - tests.Assert(t, err == nil) - - // Should not be able to register again - err = app.db.Update(func(tx *bolt.Tx) error { - err := n.Register(tx) - tests.Assert(t, err != nil) - - return err - }) - tests.Assert(t, err != nil) - - // Create a new node on *different* cluster - req = &api.NodeAddRequest{ - ClusterId: "abc", - Hostnames: api.HostAddresses{ - // Same name as previous - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - diff_cluster_n := NewNodeEntryFromRequest(req) - - // Should not be able to register diff_cluster_n - err = app.db.Update(func(tx *bolt.Tx) error { - return diff_cluster_n.Register(tx) - }) - tests.Assert(t, err != nil) - - // Add a new node - req = &api.NodeAddRequest{ - ClusterId: "3", - Hostnames: api.HostAddresses{ - Manage: []string{"manage2"}, - Storage: []string{"storage2"}, - }, - Zone: 99, - } - n2 := NewNodeEntryFromRequest(req) - - // Register n2 node - err = app.db.Update(func(tx *bolt.Tx) error { - err := n2.Register(tx) - tests.Assert(t, err == nil) - - return n2.Save(tx) - }) - tests.Assert(t, err == nil) - - // Remove n - err = app.db.Update(func(tx *bolt.Tx) error { - err := n.Deregister(tx) - tests.Assert(t, err == nil) - - return n.Delete(tx) - }) - tests.Assert(t, err == nil) - - // Register n node again - err = app.db.Update(func(tx *bolt.Tx) error { - err := n.Register(tx) - tests.Assert(t, err == nil) - - return n.Save(tx) - }) - tests.Assert(t, err == nil) -} - -func TestNodeEntryRegisterStaleRegistration(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a registration entry in the db - // but do not create an actual node entry - req := &api.NodeAddRequest{ - ClusterId: "123", - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - n := NewNodeEntryFromRequest(req) - - // Only save the registration - err := app.db.Update(func(tx *bolt.Tx) error { - return n.Register(tx) - }) - tests.Assert(t, err == nil) - - // Register node again. This should - // work because a real node entry is not saved - err = app.db.Update(func(tx *bolt.Tx) error { - err := n.Register(tx) - tests.Assert(t, err == nil) - - return n.Save(tx) - }) - tests.Assert(t, err == nil) - - // Register again. Should not work - err = app.db.Update(func(tx *bolt.Tx) error { - return n.Register(tx) - }) - tests.Assert(t, err != nil) - - // Remove n - err = app.db.Update(func(tx *bolt.Tx) error { - err := n.Deregister(tx) - tests.Assert(t, err == nil) - - return n.Delete(tx) - }) - tests.Assert(t, err == nil) - - // Register n node again - err = app.db.Update(func(tx *bolt.Tx) error { - err := n.Register(tx) - tests.Assert(t, err == nil) - - return n.Save(tx) - }) - tests.Assert(t, err == nil) - -} - -func TestNewNodeEntryFromIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Test for ID not found - err := app.db.View(func(tx *bolt.Tx) error { - _, err := NewNodeEntryFromId(tx, "123") - return err - }) - tests.Assert(t, err == ErrNotFound) - -} - -func TestNewNodeEntryFromId(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a node - req := &api.NodeAddRequest{ - ClusterId: "123", - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - - n := NewNodeEntryFromRequest(req) - n.DeviceAdd("abc") - n.DeviceAdd("def") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return n.Save(tx) - }) - tests.Assert(t, err == nil) - - var node *NodeEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - node, err = NewNodeEntryFromId(tx, n.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(node, n)) - -} - -func TestNewNodeEntrySaveDelete(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a node - req := &api.NodeAddRequest{ - ClusterId: "123", - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - - n := NewNodeEntryFromRequest(req) - n.DeviceAdd("abc") - n.DeviceAdd("def") - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return n.Save(tx) - }) - tests.Assert(t, err == nil) - - var node *NodeEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - node, err = NewNodeEntryFromId(tx, n.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(node, n)) - - // Delete entry which has devices - err = app.db.Update(func(tx *bolt.Tx) error { - var err error - node, err = NewNodeEntryFromId(tx, n.Info.Id) - if err != nil { - return err - } - - err = node.Delete(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == ErrConflict) - - // Delete devices in node - node.DeviceDelete("abc") - node.DeviceDelete("def") - tests.Assert(t, len(node.Devices) == 0) - err = app.db.Update(func(tx *bolt.Tx) error { - return node.Save(tx) - }) - tests.Assert(t, err == nil) - - // Now try to delete the node - err = app.db.Update(func(tx *bolt.Tx) error { - var err error - node, err = NewNodeEntryFromId(tx, n.Info.Id) - if err != nil { - return err - } - - err = node.Delete(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Check node has been deleted and is not in db - err = app.db.View(func(tx *bolt.Tx) error { - var err error - node, err = NewNodeEntryFromId(tx, n.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == ErrNotFound) -} - -func TestNewNodeEntryNewInfoResponse(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a node - req := &api.NodeAddRequest{ - ClusterId: "123", - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - - n := NewNodeEntryFromRequest(req) - - // Save element in database - err := app.db.Update(func(tx *bolt.Tx) error { - return n.Save(tx) - }) - tests.Assert(t, err == nil) - - var info *api.NodeInfoResponse - err = app.db.View(func(tx *bolt.Tx) error { - node, err := NewNodeEntryFromId(tx, n.Info.Id) - if err != nil { - return err - } - - info, err = node.NewInfoReponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - tests.Assert(t, info.ClusterId == n.Info.ClusterId) - tests.Assert(t, info.Id == n.Info.Id) - tests.Assert(t, info.Zone == n.Info.Zone) - tests.Assert(t, len(info.Hostnames.Manage) == 1) - tests.Assert(t, len(info.Hostnames.Storage) == 1) - tests.Assert(t, reflect.DeepEqual(info.Hostnames.Manage, n.Info.Hostnames.Manage)) - tests.Assert(t, reflect.DeepEqual(info.Hostnames.Storage, n.Info.Hostnames.Storage)) -} - -func TestNodeSetStateFailed(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create allocator - mockAllocator := NewMockAllocator(app.db) - app.allocator = mockAllocator - - // Create cluster entry - c := NewClusterEntry() - c.Info.Id = "cluster" - - // Create a node - n := NewNodeEntry() - tests.Assert(t, n != nil) - tests.Assert(t, n.State == api.EntryStateOnline) - - // Initialize node - n.Info.Id = "node" - n.Info.ClusterId = "cluster" - n.Devices = sort.StringSlice{"d1"} - - // Create device entry - d := NewDeviceEntry() - d.Info.Id = "d1" - d.Info.Name = "/d1" - d.NodeId = "node" - - // Add to allocator - mockAllocator.AddDevice(c, n, d) - - // Save in db - app.db.Update(func(tx *bolt.Tx) error { - err := c.Save(tx) - tests.Assert(t, err == nil) - - err = n.Save(tx) - tests.Assert(t, err == nil) - - err = d.Save(tx) - tests.Assert(t, err == nil) - - return nil - }) - - // Check ring - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[c.Info.Id][0] == d.Info.Id) - - // Set failed - err := n.SetState(app.db, app.executor, mockAllocator, api.EntryStateFailed) - tests.Assert(t, n.State == api.EntryStateOnline) - tests.Assert(t, err != nil) - - // Set offline - err = n.SetState(app.db, app.executor, mockAllocator, api.EntryStateOffline) - tests.Assert(t, n.State == api.EntryStateOffline) - tests.Assert(t, err == nil) - - // Check it was removed from ring - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 0) - - // Set failed - err = n.SetState(app.db, app.executor, mockAllocator, api.EntryStateFailed) - tests.Assert(t, n.State == api.EntryStateFailed) - tests.Assert(t, err == nil) - - // Set offline - err = n.SetState(app.db, app.executor, mockAllocator, api.EntryStateOffline) - tests.Assert(t, n.State == api.EntryStateFailed) - tests.Assert(t, err != nil) - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 0) - - // Set online - err = n.SetState(app.db, app.executor, mockAllocator, api.EntryStateOnline) - tests.Assert(t, n.State == api.EntryStateFailed) - tests.Assert(t, err != nil) - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 0) - -} - -func TestNodeSetStateOfflineOnline(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create allocator - mockAllocator := NewMockAllocator(app.db) - app.allocator = mockAllocator - - // Create cluster entry - c := NewClusterEntry() - c.Info.Id = "cluster" - - // Create a node - n := NewNodeEntry() - tests.Assert(t, n != nil) - tests.Assert(t, n.State == api.EntryStateOnline) - - // Initialize node - n.Info.Id = "node" - n.Info.ClusterId = "cluster" - n.Devices = sort.StringSlice{"d1"} - - // Create device entry - d := NewDeviceEntry() - d.Info.Id = "d1" - d.Info.Name = "/d1" - d.NodeId = "node" - - // Add to allocator - mockAllocator.AddDevice(c, n, d) - - // Save in db - app.db.Update(func(tx *bolt.Tx) error { - err := c.Save(tx) - tests.Assert(t, err == nil) - - err = n.Save(tx) - tests.Assert(t, err == nil) - - err = d.Save(tx) - tests.Assert(t, err == nil) - return nil - }) - - // Check ring - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[c.Info.Id][0] == d.Info.Id) - - // Set offline - err := n.SetState(app.db, app.executor, mockAllocator, api.EntryStateOffline) - tests.Assert(t, n.State == api.EntryStateOffline) - tests.Assert(t, err == nil) - - // Check it was removed from ring - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 0) - - // Set offline again - err = n.SetState(app.db, app.executor, mockAllocator, api.EntryStateOffline) - tests.Assert(t, n.State == api.EntryStateOffline) - tests.Assert(t, err == nil) - - // Set online - err = n.SetState(app.db, app.executor, mockAllocator, api.EntryStateOnline) - tests.Assert(t, n.State == api.EntryStateOnline) - tests.Assert(t, err == nil) - tests.Assert(t, len(mockAllocator.clustermap[c.Info.Id]) == 1) - tests.Assert(t, mockAllocator.clustermap[c.Info.Id][0] == d.Info.Id) -} - -func TestGetVerifiedManageHostname(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create cluster entry - c := NewClusterEntry() - c.Info.Id = "cluster" - - // Create a node - req := &api.NodeAddRequest{ - ClusterId: "cluster", - Hostnames: api.HostAddresses{ - Manage: []string{"manage"}, - Storage: []string{"storage"}, - }, - Zone: 99, - } - - n := NewNodeEntryFromRequest(req) - c.NodeAdd(n.Info.Id) - - // Save in db - app.db.Update(func(tx *bolt.Tx) error { - err := c.Save(tx) - tests.Assert(t, err == nil) - - err = n.Save(tx) - tests.Assert(t, err == nil) - - return nil - }) - - hostname, err := GetVerifiedManageHostname(app.db, app.executor, "cluster") - tests.Assert(t, hostname == "manage") - tests.Assert(t, err == nil) - tests.Assert(t, n.State == api.EntryStateOnline) - - app.db.Update(func(tx *bolt.Tx) error { - // Set offline - n.State = api.EntryStateOffline - tests.Assert(t, n.State == api.EntryStateOffline) - - err := n.Save(tx) - tests.Assert(t, err == nil) - - return nil - - }) - - hostname, err = GetVerifiedManageHostname(app.db, app.executor, "cluster") - tests.Assert(t, hostname != "manage") - tests.Assert(t, err != nil) -} diff --git a/apps/glusterfs/testapp_mock.go b/apps/glusterfs/testapp_mock.go deleted file mode 100644 index 1e5b57b99b..0000000000 --- a/apps/glusterfs/testapp_mock.go +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "github.com/lpabon/godbc" -) - -func NewTestApp(dbfile string) *App { - - // Create simple configuration for unit tests - appConfig := bytes.NewBuffer([]byte(`{ - "glusterfs" : { - "executor" : "mock", - "allocator" : "simple", - "db" : "` + dbfile + `" - } - }`)) - app := NewApp(appConfig) - godbc.Check(app != nil) - - return app -} diff --git a/apps/glusterfs/volume_durability.go b/apps/glusterfs/volume_durability.go deleted file mode 100644 index 948987cc6d..0000000000 --- a/apps/glusterfs/volume_durability.go +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/heketi/heketi/executors" -) - -type VolumeDurability interface { - BrickSizeGenerator(size uint64) func() (int, uint64, error) - MinVolumeSize() uint64 - BricksInSet() int - SetDurability() - SetExecutorVolumeRequest(v *executors.VolumeRequest) -} diff --git a/apps/glusterfs/volume_durability_ec.go b/apps/glusterfs/volume_durability_ec.go deleted file mode 100644 index 2b188e58ad..0000000000 --- a/apps/glusterfs/volume_durability_ec.go +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" -) - -type VolumeDisperseDurability struct { - api.DisperseDurability -} - -func NewVolumeDisperseDurability(d *api.DisperseDurability) *VolumeDisperseDurability { - v := &VolumeDisperseDurability{} - v.Data = d.Data - v.Redundancy = d.Redundancy - - return v -} - -func (d *VolumeDisperseDurability) SetDurability() { - if d.Data == 0 { - d.Data = DEFAULT_EC_DATA - } - if d.Redundancy == 0 { - d.Redundancy = DEFAULT_EC_REDUNDANCY - } -} - -func (d *VolumeDisperseDurability) BrickSizeGenerator(size uint64) func() (int, uint64, error) { - - sets := 1 - return func() (int, uint64, error) { - - var brick_size uint64 - var num_sets int - - for { - num_sets = sets - sets *= 2 - brick_size = size / uint64(num_sets) - - // Divide what would be the brick size for replica by the - // number of data drives in the disperse request - brick_size /= uint64(d.Data) - - if brick_size < BrickMinSize { - return 0, 0, ErrMinimumBrickSize - } else if brick_size <= BrickMaxSize { - break - } - } - - return num_sets, brick_size, nil - } -} - -func (d *VolumeDisperseDurability) MinVolumeSize() uint64 { - return BrickMinSize * uint64(d.Data) -} - -func (d *VolumeDisperseDurability) BricksInSet() int { - return d.Data + d.Redundancy -} - -func (d *VolumeDisperseDurability) SetExecutorVolumeRequest(v *executors.VolumeRequest) { - v.Type = executors.DurabilityDispersion - v.Data = d.Data - v.Redundancy = d.Redundancy -} diff --git a/apps/glusterfs/volume_durability_none.go b/apps/glusterfs/volume_durability_none.go deleted file mode 100644 index 84250b2973..0000000000 --- a/apps/glusterfs/volume_durability_none.go +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/heketi/heketi/executors" -) - -type NoneDurability struct { - VolumeReplicaDurability -} - -func NewNoneDurability() *NoneDurability { - n := &NoneDurability{} - n.Replica = 1 - - return n -} - -func (n *NoneDurability) SetDurability() { - n.Replica = 1 -} - -func (n *NoneDurability) BricksInSet() int { - return 1 -} - -func (n *NoneDurability) SetExecutorVolumeRequest(v *executors.VolumeRequest) { - v.Type = executors.DurabilityNone - v.Replica = n.Replica -} diff --git a/apps/glusterfs/volume_durability_replica.go b/apps/glusterfs/volume_durability_replica.go deleted file mode 100644 index a21f973599..0000000000 --- a/apps/glusterfs/volume_durability_replica.go +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" -) - -type VolumeReplicaDurability struct { - api.ReplicaDurability -} - -func NewVolumeReplicaDurability(r *api.ReplicaDurability) *VolumeReplicaDurability { - v := &VolumeReplicaDurability{} - v.Replica = r.Replica - - return v -} - -func (r *VolumeReplicaDurability) SetDurability() { - if r.Replica == 0 { - r.Replica = DEFAULT_REPLICA - } -} - -func (r *VolumeReplicaDurability) BrickSizeGenerator(size uint64) func() (int, uint64, error) { - - sets := 1 - return func() (int, uint64, error) { - - var brick_size uint64 - var num_sets int - - for { - num_sets = sets - sets *= 2 - brick_size = size / uint64(num_sets) - - if brick_size < BrickMinSize { - return 0, 0, ErrMinimumBrickSize - } else if brick_size <= BrickMaxSize { - break - } - } - - return num_sets, brick_size, nil - } -} - -func (r *VolumeReplicaDurability) MinVolumeSize() uint64 { - return BrickMinSize -} - -func (r *VolumeReplicaDurability) BricksInSet() int { - return r.Replica -} - -func (r *VolumeReplicaDurability) SetExecutorVolumeRequest(v *executors.VolumeRequest) { - v.Type = executors.DurabilityReplica - v.Replica = r.Replica -} diff --git a/apps/glusterfs/volume_durability_test.go b/apps/glusterfs/volume_durability_test.go deleted file mode 100644 index cc4bb3b0ee..0000000000 --- a/apps/glusterfs/volume_durability_test.go +++ /dev/null @@ -1,306 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "testing" - - "github.com/heketi/heketi/executors" - "github.com/heketi/tests" -) - -func TestNoneDurabilityDefaults(t *testing.T) { - r := &NoneDurability{} - tests.Assert(t, r.Replica == 0) - - r.SetDurability() - tests.Assert(t, r.Replica == 1) -} - -func TestDisperseDurabilityDefaults(t *testing.T) { - r := &VolumeDisperseDurability{} - tests.Assert(t, r.Data == 0) - tests.Assert(t, r.Redundancy == 0) - - r.SetDurability() - tests.Assert(t, r.Data == DEFAULT_EC_DATA) - tests.Assert(t, r.Redundancy == DEFAULT_EC_REDUNDANCY) -} - -func TestReplicaDurabilityDefaults(t *testing.T) { - r := &VolumeReplicaDurability{} - tests.Assert(t, r.Replica == 0) - - r.SetDurability() - tests.Assert(t, r.Replica == DEFAULT_REPLICA) -} - -func TestNoneDurabilitySetExecutorRequest(t *testing.T) { - r := &NoneDurability{} - r.SetDurability() - - v := &executors.VolumeRequest{} - r.SetExecutorVolumeRequest(v) - tests.Assert(t, v.Replica == 1) - tests.Assert(t, v.Type == executors.DurabilityNone) -} - -func TestDisperseDurabilitySetExecutorRequest(t *testing.T) { - r := &VolumeDisperseDurability{} - r.SetDurability() - - v := &executors.VolumeRequest{} - r.SetExecutorVolumeRequest(v) - tests.Assert(t, v.Data == r.Data) - tests.Assert(t, v.Redundancy == r.Redundancy) - tests.Assert(t, v.Type == executors.DurabilityDispersion) -} - -func TestReplicaDurabilitySetExecutorRequest(t *testing.T) { - r := &VolumeReplicaDurability{} - r.SetDurability() - - v := &executors.VolumeRequest{} - r.SetExecutorVolumeRequest(v) - tests.Assert(t, v.Replica == r.Replica) - tests.Assert(t, v.Type == executors.DurabilityReplica) -} - -func TestNoneDurability(t *testing.T) { - r := &NoneDurability{} - r.SetDurability() - - gen := r.BrickSizeGenerator(100 * GB) - - // Gen 1 - sets, brick_size, err := gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 1) - tests.Assert(t, brick_size == 100*GB) - tests.Assert(t, 1 == r.BricksInSet()) - - // Gen 2 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 2) - tests.Assert(t, brick_size == 50*GB) - tests.Assert(t, 1 == r.BricksInSet()) - - // Gen 3 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 4) - tests.Assert(t, brick_size == 25*GB) - tests.Assert(t, 1 == r.BricksInSet()) - - // Gen 4 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 8) - tests.Assert(t, brick_size == 12800*1024) - tests.Assert(t, 1 == r.BricksInSet()) - - // Gen 5 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 16) - tests.Assert(t, brick_size == 6400*1024) - tests.Assert(t, 1 == r.BricksInSet()) - - // Gen 6 - sets, brick_size, err = gen() - tests.Assert(t, err == nil, err) - tests.Assert(t, sets == 32) - tests.Assert(t, brick_size == 3200*1024) - tests.Assert(t, 1 == r.BricksInSet()) - - // Gen 7 - sets, brick_size, err = gen() - tests.Assert(t, err == nil, err) - tests.Assert(t, sets == 64) - tests.Assert(t, brick_size == 1600*1024) - tests.Assert(t, 1 == r.BricksInSet()) - - // Gen 8 - sets, brick_size, err = gen() - tests.Assert(t, err == ErrMinimumBrickSize) - tests.Assert(t, sets == 0) - tests.Assert(t, brick_size == 0) - tests.Assert(t, 1 == r.BricksInSet()) -} - -func TestDisperseDurability(t *testing.T) { - - r := &VolumeDisperseDurability{} - r.Data = 8 - r.Redundancy = 3 - - gen := r.BrickSizeGenerator(200 * GB) - - // Gen 1 - sets, brick_size, err := gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 1) - tests.Assert(t, brick_size == uint64(200*GB/8)) - tests.Assert(t, 8+3 == r.BricksInSet()) - - // Gen 2 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 2) - tests.Assert(t, brick_size == uint64(100*GB/8)) - tests.Assert(t, 8+3 == r.BricksInSet()) - - // Gen 3 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 4) - tests.Assert(t, brick_size == uint64(50*GB/8)) - tests.Assert(t, 8+3 == r.BricksInSet()) - - // Gen 4 - sets, brick_size, err = gen() - tests.Assert(t, err == nil, err) - tests.Assert(t, sets == 8) - tests.Assert(t, brick_size == uint64(25*GB/8)) - tests.Assert(t, 8+3 == r.BricksInSet()) - - // Gen 5 - sets, brick_size, err = gen() - tests.Assert(t, err == nil, err) - tests.Assert(t, sets == 16) - tests.Assert(t, brick_size == uint64(12800*1024/8)) - tests.Assert(t, 8+3 == r.BricksInSet()) - - // Gen 6 - sets, brick_size, err = gen() - tests.Assert(t, err == ErrMinimumBrickSize) - tests.Assert(t, 8+3 == r.BricksInSet()) -} - -func TestDisperseDurabilityLargeBrickGenerator(t *testing.T) { - r := &VolumeDisperseDurability{} - r.Data = 8 - r.Redundancy = 3 - - gen := r.BrickSizeGenerator(800 * TB) - - // Gen 1 - sets, brick_size, err := gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 32) - tests.Assert(t, brick_size == 3200*GB) - tests.Assert(t, 8+3 == r.BricksInSet()) -} - -func TestReplicaDurabilityGenerator(t *testing.T) { - r := &VolumeReplicaDurability{} - r.Replica = 2 - - gen := r.BrickSizeGenerator(100 * GB) - - // Gen 1 - sets, brick_size, err := gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 1) - tests.Assert(t, brick_size == 100*GB) - tests.Assert(t, 2 == r.BricksInSet()) - - // Gen 2 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 2, "sets we got:", sets) - tests.Assert(t, brick_size == 50*GB) - tests.Assert(t, 2 == r.BricksInSet()) - - // Gen 3 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 4) - tests.Assert(t, brick_size == 25*GB) - tests.Assert(t, 2 == r.BricksInSet()) - - // Gen 4 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 8) - tests.Assert(t, brick_size == 12800*1024) - tests.Assert(t, 2 == r.BricksInSet()) - - // Gen 5 - sets, brick_size, err = gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 16) - tests.Assert(t, brick_size == 6400*1024) - tests.Assert(t, 2 == r.BricksInSet()) - - // Gen 6 - sets, brick_size, err = gen() - tests.Assert(t, err == nil, err) - tests.Assert(t, sets == 32, sets) - tests.Assert(t, brick_size == 3200*1024) - tests.Assert(t, 2 == r.BricksInSet()) - - // Gen 7 - sets, brick_size, err = gen() - tests.Assert(t, err == nil, err) - tests.Assert(t, sets == 64, sets) - tests.Assert(t, brick_size == 1600*1024) - tests.Assert(t, 2 == r.BricksInSet()) - - // Gen 8 - sets, brick_size, err = gen() - tests.Assert(t, err == ErrMinimumBrickSize) - tests.Assert(t, sets == 0) - tests.Assert(t, brick_size == 0) - tests.Assert(t, 2 == r.BricksInSet()) -} - -func TestReplicaDurabilityLargeBrickGenerator(t *testing.T) { - r := &VolumeReplicaDurability{} - r.Replica = 2 - - gen := r.BrickSizeGenerator(100 * TB) - - // Gen 1 - sets, brick_size, err := gen() - tests.Assert(t, err == nil) - tests.Assert(t, sets == 32) - tests.Assert(t, brick_size == 3200*GB) - tests.Assert(t, 2 == r.BricksInSet()) -} - -func TestNoneDurabilityMinVolumeSize(t *testing.T) { - r := &NoneDurability{} - r.SetDurability() - - minvolsize := r.MinVolumeSize() - - tests.Assert(t, minvolsize == BrickMinSize) -} - -func TestReplicaDurabilityMinVolumeSize(t *testing.T) { - r := &VolumeReplicaDurability{} - r.Replica = 3 - - minvolsize := r.MinVolumeSize() - - tests.Assert(t, minvolsize == BrickMinSize) -} - -func TestDisperseDurabilityMinVolumeSize(t *testing.T) { - r := &VolumeDisperseDurability{} - r.Data = 8 - r.Redundancy = 3 - - minvolsize := r.MinVolumeSize() - - tests.Assert(t, minvolsize == BrickMinSize*8) -} diff --git a/apps/glusterfs/volume_entry.go b/apps/glusterfs/volume_entry.go deleted file mode 100644 index 9670933b0a..0000000000 --- a/apps/glusterfs/volume_entry.go +++ /dev/null @@ -1,594 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "bytes" - "encoding/gob" - "fmt" - "sort" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/lpabon/godbc" -) - -const ( - - // Byte values in KB - KB = 1 - MB = KB * 1024 - GB = MB * 1024 - TB = GB * 1024 - - // Default values - DEFAULT_REPLICA = 2 - DEFAULT_EC_DATA = 4 - DEFAULT_EC_REDUNDANCY = 2 - DEFAULT_THINP_SNAPSHOT_FACTOR = 1.5 -) - -type VolumeEntry struct { - Info api.VolumeInfo - Bricks sort.StringSlice - Durability VolumeDurability - GlusterVolumeOptions []string -} - -func VolumeList(tx *bolt.Tx) ([]string, error) { - - list := EntryKeys(tx, BOLTDB_BUCKET_VOLUME) - if list == nil { - return nil, ErrAccessList - } - return list, nil -} - -func NewVolumeEntry() *VolumeEntry { - entry := &VolumeEntry{} - entry.Bricks = make(sort.StringSlice, 0) - - gob.Register(&NoneDurability{}) - gob.Register(&VolumeReplicaDurability{}) - gob.Register(&VolumeDisperseDurability{}) - - return entry -} - -func NewVolumeEntryFromRequest(req *api.VolumeCreateRequest) *VolumeEntry { - godbc.Require(req != nil) - - vol := NewVolumeEntry() - vol.Info.Gid = req.Gid - vol.Info.Id = utils.GenUUID() - vol.Info.Durability = req.Durability - vol.Info.Snapshot = req.Snapshot - vol.Info.Size = req.Size - - // Set default durability values - durability := vol.Info.Durability.Type - switch { - - case durability == api.DurabilityReplicate: - logger.Debug("[%v] Replica %v", - vol.Info.Id, - vol.Info.Durability.Replicate.Replica) - vol.Durability = NewVolumeReplicaDurability(&vol.Info.Durability.Replicate) - - case durability == api.DurabilityEC: - logger.Debug("[%v] EC %v + %v ", - vol.Info.Id, - vol.Info.Durability.Disperse.Data, - vol.Info.Durability.Disperse.Redundancy) - vol.Durability = NewVolumeDisperseDurability(&vol.Info.Durability.Disperse) - - case durability == api.DurabilityDistributeOnly || durability == "": - logger.Debug("[%v] Distributed", vol.Info.Id) - vol.Durability = NewNoneDurability() - - default: - panic(fmt.Sprintf("BUG: Unknown type: %v\n", vol.Info.Durability)) - } - - // Set the default values accordingly - vol.Durability.SetDurability() - - // Set default name - if req.Name == "" { - vol.Info.Name = "vol_" + vol.Info.Id - } else { - vol.Info.Name = req.Name - } - - // Set default thinp factor - if vol.Info.Snapshot.Enable && vol.Info.Snapshot.Factor == 0 { - vol.Info.Snapshot.Factor = DEFAULT_THINP_SNAPSHOT_FACTOR - } else if !vol.Info.Snapshot.Enable { - vol.Info.Snapshot.Factor = 1 - } - - // If it is zero, then no volume options are set. - vol.GlusterVolumeOptions = req.GlusterVolumeOptions - - // If it is zero, then it will be assigned during volume creation - vol.Info.Clusters = req.Clusters - - return vol -} - -func NewVolumeEntryFromId(tx *bolt.Tx, id string) (*VolumeEntry, error) { - godbc.Require(tx != nil) - - entry := NewVolumeEntry() - err := EntryLoad(tx, entry, id) - if err != nil { - return nil, err - } - - return entry, nil -} - -func (v *VolumeEntry) BucketName() string { - return BOLTDB_BUCKET_VOLUME -} - -func (v *VolumeEntry) Save(tx *bolt.Tx) error { - godbc.Require(tx != nil) - godbc.Require(len(v.Info.Id) > 0) - - return EntrySave(tx, v, v.Info.Id) -} - -func (v *VolumeEntry) Delete(tx *bolt.Tx) error { - return EntryDelete(tx, v, v.Info.Id) -} - -func (v *VolumeEntry) NewInfoResponse(tx *bolt.Tx) (*api.VolumeInfoResponse, error) { - godbc.Require(tx != nil) - - info := api.NewVolumeInfoResponse() - info.Id = v.Info.Id - info.Cluster = v.Info.Cluster - info.Mount = v.Info.Mount - info.Snapshot = v.Info.Snapshot - info.Size = v.Info.Size - info.Durability = v.Info.Durability - info.Name = v.Info.Name - info.GlusterVolumeOptions = v.GlusterVolumeOptions - - for _, brickid := range v.BricksIds() { - brick, err := NewBrickEntryFromId(tx, brickid) - if err != nil { - return nil, err - } - brickinfo, err := brick.NewInfoResponse(tx) - if err != nil { - return nil, err - } - - info.Bricks = append(info.Bricks, *brickinfo) - } - - return info, nil -} - -func (v *VolumeEntry) Marshal() ([]byte, error) { - var buffer bytes.Buffer - enc := gob.NewEncoder(&buffer) - err := enc.Encode(*v) - - return buffer.Bytes(), err -} - -func (v *VolumeEntry) Unmarshal(buffer []byte) error { - dec := gob.NewDecoder(bytes.NewReader(buffer)) - err := dec.Decode(v) - if err != nil { - return err - } - - // Make sure to setup arrays if nil - if v.Bricks == nil { - v.Bricks = make(sort.StringSlice, 0) - } - - return nil -} - -func (v *VolumeEntry) BrickAdd(id string) { - godbc.Require(!utils.SortedStringHas(v.Bricks, id)) - - v.Bricks = append(v.Bricks, id) - v.Bricks.Sort() -} - -func (v *VolumeEntry) BrickDelete(id string) { - v.Bricks = utils.SortedStringsDelete(v.Bricks, id) -} - -func (v *VolumeEntry) Create(db *bolt.DB, - executor executors.Executor, - allocator Allocator) (e error) { - - // On any error, remove the volume - defer func() { - if e != nil { - db.Update(func(tx *bolt.Tx) error { - v.Delete(tx) - - return nil - }) - } - }() - - // Get list of clusters - var possibleClusters []string - if len(v.Info.Clusters) == 0 { - err := db.View(func(tx *bolt.Tx) error { - var err error - possibleClusters, err = ClusterList(tx) - return err - }) - if err != nil { - return err - } - } else { - possibleClusters = v.Info.Clusters - } - - // Check we have clusters - if len(possibleClusters) == 0 { - logger.LogError("Volume being ask to be created, but there are no clusters configured") - return ErrNoSpace - } - logger.Debug("Using the following clusters: %+v", possibleClusters) - - // Check for volume name conflict on any cluster - var clusters []string - for _, cluster := range possibleClusters { - var err error - - // Check this cluster does not have a volume with the name - err = db.View(func(tx *bolt.Tx) error { - ce, err := NewClusterEntryFromId(tx, cluster) - if err != nil { - return err - } - - for _, volumeId := range ce.Info.Volumes { - volume, err := NewVolumeEntryFromId(tx, volumeId) - if err != nil { - return err - } - if v.Info.Name == volume.Info.Name { - return fmt.Errorf("Name %v already in use in cluster %v", - v.Info.Name, cluster) - } - } - - return nil - - }) - if err != nil { - logger.Warning("%v", err.Error()) - } else { - clusters = append(clusters, cluster) - } - } - if len(clusters) == 0 { - return fmt.Errorf("Name %v is already in use in all available clusters", v.Info.Name) - } - - // For each cluster look for storage space for this volume - var brick_entries []*BrickEntry - var err error - for _, cluster := range clusters { - - // Check this cluster for space - brick_entries, err = v.allocBricksInCluster(db, allocator, cluster, v.Info.Size) - - if err == nil { - v.Info.Cluster = cluster - logger.Debug("Volume to be created on cluster %v", cluster) - break - } else if err == ErrNoSpace || - err == ErrMaxBricks || - err == ErrMinimumBrickSize { - logger.Debug("Cluster %v can not accommodate volume "+ - "(%v), trying next cluster", cluster, err) - continue - } else { - // A genuine error occurred - bail out - return logger.LogError("Error calling v.allocBricksInCluster: %v", err) - } - } - - if err != nil || brick_entries == nil { - // Map all 'valid' errors to NoSpace here: - // Only the last such error could get propagated down, - // so it does not make sense to hand the granularity on. - // But for other callers (Expand), we keep it. - return ErrNoSpace - } - - // Make sure to clean up bricks on error - defer func() { - if e != nil { - db.Update(func(tx *bolt.Tx) error { - for _, brick := range brick_entries { - v.removeBrickFromDb(tx, brick) - } - return nil - }) - } - }() - - // Create the bricks on the nodes - err = CreateBricks(db, executor, brick_entries) - if err != nil { - return err - } - - // Clean up created bricks on failure - defer func() { - if e != nil { - DestroyBricks(db, executor, brick_entries) - } - }() - - // Create GlusterFS volume - err = v.createVolume(db, executor, brick_entries) - if err != nil { - return err - } - - // Destroy volume on failure - defer func() { - if e != nil { - v.Destroy(db, executor) - } - }() - - // Save information on db - err = db.Update(func(tx *bolt.Tx) error { - - // Save brick entries - for _, brick := range brick_entries { - err := brick.Save(tx) - if err != nil { - return err - } - } - - // Save volume information - err = v.Save(tx) - if err != nil { - return err - } - - // Save cluster - cluster, err := NewClusterEntryFromId(tx, v.Info.Cluster) - if err != nil { - return err - } - cluster.VolumeAdd(v.Info.Id) - return cluster.Save(tx) - }) - if err != nil { - return err - } - - return nil - -} - -func (v *VolumeEntry) Destroy(db *bolt.DB, executor executors.Executor) error { - logger.Info("Destroying volume %v", v.Info.Id) - - // Get the entries from the database - brick_entries := make([]*BrickEntry, len(v.Bricks)) - var sshhost string - db.View(func(tx *bolt.Tx) error { - for index, id := range v.BricksIds() { - brick, err := NewBrickEntryFromId(tx, id) - if err != nil { - logger.LogError("Brick %v not found in db: %v", id, err) - continue - } - brick_entries[index] = brick - - // Set ssh host to send volume commands - if sshhost == "" { - node, err := NewNodeEntryFromId(tx, brick.Info.NodeId) - if err != nil { - logger.LogError("Unable to determine brick node: %v", err) - return err - } - sshhost = node.ManageHostName() - } - } - return nil - }) - - // Determine if we can destroy the volume - err := executor.VolumeDestroyCheck(sshhost, v.Info.Name) - if err != nil { - logger.Err(err) - return err - } - - // Determine if the bricks can be destroyed - err = v.checkBricksCanBeDestroyed(db, executor, brick_entries) - if err != nil { - logger.Err(err) - return err - } - - // :TODO: What if the host is no longer available, we may need to try others - // Stop volume - err = executor.VolumeDestroy(sshhost, v.Info.Name) - if err != nil { - logger.LogError("Unable to delete volume: %v", err) - return err - } - - // Destroy bricks - err = DestroyBricks(db, executor, brick_entries) - if err != nil { - logger.LogError("Unable to delete bricks: %v", err) - return err - } - - // Remove from entries from the db - err = db.Update(func(tx *bolt.Tx) error { - for _, brick := range brick_entries { - err = v.removeBrickFromDb(tx, brick) - if err != nil { - logger.Err(err) - // Everything is destroyed anyways, just keep deleting the others - // Do not return here - } - } - - // Remove volume from cluster - cluster, err := NewClusterEntryFromId(tx, v.Info.Cluster) - if err != nil { - logger.Err(err) - // Do not return here.. keep going - } - cluster.VolumeDelete(v.Info.Id) - - err = cluster.Save(tx) - if err != nil { - logger.Err(err) - // Do not return here.. keep going - } - - // Delete volume - v.Delete(tx) - - return nil - }) - - return err -} - -func (v *VolumeEntry) Expand(db *bolt.DB, - executor executors.Executor, - allocator Allocator, - sizeGB int) (e error) { - - // Allocate new bricks in the cluster - brick_entries, err := v.allocBricksInCluster(db, allocator, v.Info.Cluster, sizeGB) - if err != nil { - return err - } - - // Setup cleanup function - defer func() { - if e != nil { - logger.Debug("Error detected, cleaning up") - - // Remove from db - db.Update(func(tx *bolt.Tx) error { - for _, brick := range brick_entries { - v.removeBrickFromDb(tx, brick) - } - err := v.Save(tx) - godbc.Check(err == nil) - - return nil - }) - } - }() - - // Create bricks - err = CreateBricks(db, executor, brick_entries) - if err != nil { - return err - } - - // Setup cleanup function - defer func() { - if e != nil { - logger.Debug("Error detected, cleaning up") - DestroyBricks(db, executor, brick_entries) - } - }() - - // Create a volume request to send to executor - // so that it can add the new bricks - vr, host, err := v.createVolumeRequest(db, brick_entries) - if err != nil { - return err - } - - // Expand the volume - _, err = executor.VolumeExpand(host, vr) - if err != nil { - return err - } - - // Increase the recorded volume size - v.Info.Size += sizeGB - - // Save volume entry - err = db.Update(func(tx *bolt.Tx) error { - - // Save brick entries - for _, brick := range brick_entries { - err := brick.Save(tx) - if err != nil { - return err - } - } - - return v.Save(tx) - }) - - return err - -} - -func (v *VolumeEntry) BricksIds() sort.StringSlice { - ids := make(sort.StringSlice, len(v.Bricks)) - copy(ids, v.Bricks) - return ids -} - -func (v *VolumeEntry) checkBricksCanBeDestroyed(db *bolt.DB, - executor executors.Executor, - brick_entries []*BrickEntry) error { - - sg := utils.NewStatusGroup() - - // Create a goroutine for each brick - for _, brick := range brick_entries { - sg.Add(1) - go func(b *BrickEntry) { - defer sg.Done() - sg.Err(b.DestroyCheck(db, executor)) - }(brick) - } - - // Wait here until all goroutines have returned. If - // any of errored, it would be cought here - err := sg.Result() - if err != nil { - logger.Err(err) - } - return err -} - -func VolumeEntryUpgrade(tx *bolt.Tx) error { - return nil -} diff --git a/apps/glusterfs/volume_entry_allocate.go b/apps/glusterfs/volume_entry_allocate.go deleted file mode 100644 index 74947eccd3..0000000000 --- a/apps/glusterfs/volume_entry_allocate.go +++ /dev/null @@ -1,522 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "fmt" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" -) - -func (v *VolumeEntry) allocBricksInCluster(db *bolt.DB, - allocator Allocator, - cluster string, - gbsize int) ([]*BrickEntry, error) { - - size := uint64(gbsize) * GB - - // Setup a brick size generator - // Note: subsequent calls to gen need to return decreasing - // brick sizes in order for the following code to work! - gen := v.Durability.BrickSizeGenerator(size) - - // Try decreasing possible brick sizes until space is found - for { - // Determine next possible brick size - sets, brick_size, err := gen() - if err != nil { - logger.Err(err) - return nil, err - } - - num_bricks := sets * v.Durability.BricksInSet() - - logger.Debug("brick_size = %v", brick_size) - logger.Debug("sets = %v", sets) - logger.Debug("num_bricks = %v", num_bricks) - - // Check that the volume would not have too many bricks - if (num_bricks + len(v.Bricks)) > BrickMaxNum { - logger.Debug("Maximum number of bricks reached") - return nil, ErrMaxBricks - } - - // Allocate bricks in the cluster - brick_entries, err := v.allocBricks(db, allocator, cluster, sets, brick_size) - if err == ErrNoSpace { - logger.Debug("No space, re-trying with smaller brick size") - continue - } - if err != nil { - logger.Err(err) - return nil, err - } - - // We were able to allocate bricks - return brick_entries, nil - } -} - -func (v *VolumeEntry) getBrickEntryfromBrickName(db *bolt.DB, brickname string) (brickEntry *BrickEntry, e error) { - - var nodeEntry *NodeEntry - for _, brickid := range v.BricksIds() { - - err := db.View(func(tx *bolt.Tx) error { - var err error - brickEntry, err = NewBrickEntryFromId(tx, brickid) - if err != nil { - return err - } - nodeEntry, err = NewNodeEntryFromId(tx, brickEntry.Info.NodeId) - if err != nil { - return err - } - return nil - }) - if err != nil { - return nil, err - } - - if brickname == fmt.Sprintf("%v:%v", nodeEntry.Info.Hostnames.Storage[0], brickEntry.Info.Path) { - return brickEntry, nil - } - } - - return nil, ErrNotFound -} - -func (v *VolumeEntry) replaceBrickInVolume(db *bolt.DB, executor executors.Executor, - allocator Allocator, - oldBrickId string) (e error) { - - var oldBrickEntry *BrickEntry - var oldDeviceEntry *DeviceEntry - var newDeviceEntry *DeviceEntry - var oldBrickNodeEntry *NodeEntry - var newBrickNodeEntry *NodeEntry - var newBrickEntry *BrickEntry - - if api.DurabilityDistributeOnly == v.Info.Durability.Type { - return fmt.Errorf("replace brick is not supported for volume durability type %v", v.Info.Durability.Type) - } - - err := db.View(func(tx *bolt.Tx) error { - var err error - oldBrickEntry, err = NewBrickEntryFromId(tx, oldBrickId) - if err != nil { - return err - } - - oldDeviceEntry, err = NewDeviceEntryFromId(tx, oldBrickEntry.Info.DeviceId) - if err != nil { - return err - } - oldBrickNodeEntry, err = NewNodeEntryFromId(tx, oldBrickEntry.Info.NodeId) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - - node := oldBrickNodeEntry.ManageHostName() - err = executor.GlusterdCheck(node) - if err != nil { - node, err = GetVerifiedManageHostname(db, executor, oldBrickNodeEntry.Info.ClusterId) - if err != nil { - return err - } - } - - // Determine the setlist by getting data from Gluster - vinfo, err := executor.VolumeInfo(node, v.Info.Name) - var slicestartindex int - var foundbrickset bool - var brick executors.Brick - setlist := make([]*BrickEntry, 0) - // BrickList in volume info is a slice of all bricks in volume - // We loop over the slice in steps of BricksInSet() - // If brick to be replaced is found in an iteration, other bricks in that slice form the setlist - for slicestartindex = 0; slicestartindex <= len(vinfo.Bricks.BrickList)-v.Durability.BricksInSet(); slicestartindex = slicestartindex + v.Durability.BricksInSet() { - setlist = make([]*BrickEntry, 0) - for _, brick = range vinfo.Bricks.BrickList[slicestartindex : slicestartindex+v.Durability.BricksInSet()] { - brickentry, err := v.getBrickEntryfromBrickName(db, brick.Name) - if err != nil { - logger.LogError("Unable to create brick entry using brick name:%v, error: %v", brick.Name, err) - return err - } - if brickentry.Id() == oldBrickId { - foundbrickset = true - } else { - setlist = append(setlist, brickentry) - } - } - if foundbrickset { - break - } - } - if !foundbrickset { - logger.LogError("Unable to find brick set for brick %v, db is possibly corrupt", oldBrickEntry.Id()) - return ErrNotFound - } - - //Create an Id for new brick - newBrickId := utils.GenUUID() - - // Check the ring for devices to place the brick - deviceCh, done, errc := allocator.GetNodes(v.Info.Cluster, newBrickId) - defer func() { - close(done) - }() - - for deviceId := range deviceCh { - - // Get device entry - err = db.View(func(tx *bolt.Tx) error { - newDeviceEntry, err = NewDeviceEntryFromId(tx, deviceId) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - - // Skip same device - if oldDeviceEntry.Info.Id == newDeviceEntry.Info.Id { - continue - } - - // Do not allow a device from the same node to be - // in the set - deviceOk := true - for _, brickInSet := range setlist { - if brickInSet.Info.NodeId == newDeviceEntry.NodeId { - deviceOk = false - } - } - - if !deviceOk { - continue - } - - // Try to allocate a brick on this device - // NewBrickEntry would deduct storage from device entry - // which we will save to disk, hence reload the latest device - // entry to get latest storage state of device - err = db.Update(func(tx *bolt.Tx) error { - newDeviceEntry, err := NewDeviceEntryFromId(tx, deviceId) - if err != nil { - return err - } - newBrickEntry = newDeviceEntry.NewBrickEntry(oldBrickEntry.Info.Size, - float64(v.Info.Snapshot.Factor), - v.Info.Gid, v.Info.Id) - err = newDeviceEntry.Save(tx) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - - // Determine if it was successful - if newBrickEntry == nil { - continue - } - - defer func() { - if e != nil { - db.Update(func(tx *bolt.Tx) error { - newDeviceEntry, err = NewDeviceEntryFromId(tx, newBrickEntry.Info.DeviceId) - if err != nil { - return err - } - newDeviceEntry.StorageFree(newBrickEntry.TotalSize()) - newDeviceEntry.Save(tx) - return nil - }) - } - }() - - err = db.View(func(tx *bolt.Tx) error { - newBrickNodeEntry, err = NewNodeEntryFromId(tx, newBrickEntry.Info.NodeId) - if err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - - newBrickEntry.SetId(newBrickId) - var brickEntries []*BrickEntry - brickEntries = append(brickEntries, newBrickEntry) - err = CreateBricks(db, executor, brickEntries) - if err != nil { - return err - } - - defer func() { - if e != nil { - DestroyBricks(db, executor, brickEntries) - } - }() - - var oldBrick executors.BrickInfo - var newBrick executors.BrickInfo - - oldBrick.Path = oldBrickEntry.Info.Path - oldBrick.Host = oldBrickNodeEntry.StorageHostName() - newBrick.Path = newBrickEntry.Info.Path - newBrick.Host = newBrickNodeEntry.StorageHostName() - - err = executor.VolumeReplaceBrick(node, v.Info.Name, &oldBrick, &newBrick) - if err != nil { - return err - } - - // After this point we should not call any defer func() - // We don't have a *revert* of replace brick operation - - _ = oldBrickEntry.Destroy(db, executor) - - // We must read entries from db again as state on disk might - // have changed - - err = db.Update(func(tx *bolt.Tx) error { - err = newBrickEntry.Save(tx) - if err != nil { - return err - } - reReadNewDeviceEntry, err := NewDeviceEntryFromId(tx, newBrickEntry.Info.DeviceId) - if err != nil { - return err - } - reReadNewDeviceEntry.BrickAdd(newBrickEntry.Id()) - err = reReadNewDeviceEntry.Save(tx) - if err != nil { - return err - } - - reReadVolEntry, err := NewVolumeEntryFromId(tx, newBrickEntry.Info.VolumeId) - if err != nil { - return err - } - reReadVolEntry.BrickAdd(newBrickEntry.Id()) - err = reReadVolEntry.removeBrickFromDb(tx, oldBrickEntry) - if err != nil { - return err - } - err = reReadVolEntry.Save(tx) - if err != nil { - return err - } - return nil - }) - if err != nil { - logger.Err(err) - } - - logger.Info("replaced brick:%v on node:%v at path:%v with brick:%v on node:%v at path:%v", - oldBrickEntry.Id(), oldBrickEntry.Info.NodeId, oldBrickEntry.Info.Path, - newBrickEntry.Id(), newBrickEntry.Info.NodeId, newBrickEntry.Info.Path) - - return nil - } - // Check if allocator returned an error - if err := <-errc; err != nil { - return err - } - - // No device found - return ErrNoReplacement -} - -func (v *VolumeEntry) allocBricks( - db *bolt.DB, - allocator Allocator, - cluster string, - bricksets int, - brick_size uint64) (brick_entries []*BrickEntry, e error) { - - // Setup garbage collector function in case of error - defer func() { - - // Check the named return value 'err' - if e != nil { - logger.Debug("Error detected. Cleaning up volume %v: Len(%v) ", v.Info.Id, len(brick_entries)) - db.Update(func(tx *bolt.Tx) error { - for _, brick := range brick_entries { - v.removeBrickFromDb(tx, brick) - } - return nil - }) - } - }() - - // Initialize brick_entries - brick_entries = make([]*BrickEntry, 0) - - // Determine allocation for each brick required for this volume - for brick_num := 0; brick_num < bricksets; brick_num++ { - logger.Info("brick_num: %v", brick_num) - - // Create a brick set list to later make sure that the - // proposed bricks and devices are acceptable - setlist := make([]*BrickEntry, 0) - - // Generate an id for the brick - brickId := utils.GenUUID() - - // Get allocator generator - // The same generator should be used for the brick and its replicas - deviceCh, done, errc := allocator.GetNodes(cluster, brickId) - defer func() { - close(done) - }() - - // Check location has space for each brick and its replicas - for i := 0; i < v.Durability.BricksInSet(); i++ { - logger.Debug("%v / %v", i, v.Durability.BricksInSet()) - - // Do the work in the database context so that the cluster - // data does not change while determining brick location - err := db.Update(func(tx *bolt.Tx) error { - - // Check the ring for devices to place the brick - for deviceId := range deviceCh { - - // Get device entry - device, err := NewDeviceEntryFromId(tx, deviceId) - if err != nil { - return err - } - - // Do not allow a device from the same node to be - // in the set - deviceOk := true - for _, brickInSet := range setlist { - if brickInSet.Info.NodeId == device.NodeId { - deviceOk = false - } - } - - if !deviceOk { - continue - } - - // Try to allocate a brick on this device - brick := device.NewBrickEntry(brick_size, - float64(v.Info.Snapshot.Factor), - v.Info.Gid, v.Info.Id) - - // Determine if it was successful - if brick != nil { - - // If the first in the set, the reset the id - if i == 0 { - brick.SetId(brickId) - } - - // Save the brick entry to create later - brick_entries = append(brick_entries, brick) - - // Add to set list - setlist = append(setlist, brick) - - // Add brick to device - device.BrickAdd(brick.Id()) - - // Add brick to volume - v.BrickAdd(brick.Id()) - - // Save values - err := brick.Save(tx) - if err != nil { - return err - } - - err = device.Save(tx) - if err != nil { - return err - } - - return nil - } - } - - // Check if allocator returned an error - if err := <-errc; err != nil { - return err - } - - // No devices found - return ErrNoSpace - - }) - if err != nil { - return brick_entries, err - } - } - } - - return brick_entries, nil - -} - -func (v *VolumeEntry) removeBrickFromDb(tx *bolt.Tx, brick *BrickEntry) error { - - // Access device - device, err := NewDeviceEntryFromId(tx, brick.Info.DeviceId) - if err != nil { - logger.Err(err) - return err - } - - // Deallocate space on device - device.StorageFree(brick.TotalSize()) - - // Delete brick from device - device.BrickDelete(brick.Info.Id) - - // Save device - err = device.Save(tx) - if err != nil { - logger.Err(err) - return err - } - - // Delete brick entryfrom db - err = brick.Delete(tx) - if err != nil { - logger.Err(err) - return err - } - - // Delete brick from volume db - v.BrickDelete(brick.Info.Id) - if err != nil { - logger.Err(err) - return err - } - - return nil -} diff --git a/apps/glusterfs/volume_entry_create.go b/apps/glusterfs/volume_entry_create.go deleted file mode 100644 index 06a7c12911..0000000000 --- a/apps/glusterfs/volume_entry_create.go +++ /dev/null @@ -1,104 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "fmt" - "strings" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/utils" - "github.com/lpabon/godbc" -) - -func (v *VolumeEntry) createVolume(db *bolt.DB, - executor executors.Executor, - brick_entries []*BrickEntry) error { - - godbc.Require(db != nil) - godbc.Require(brick_entries != nil) - - // Create a volume request for executor with - // the bricks allocated - vr, host, err := v.createVolumeRequest(db, brick_entries) - if err != nil { - return err - } - - // Create the volume - _, err = executor.VolumeCreate(host, vr) - if err != nil { - return err - } - - // Get all brick hosts - stringset := utils.NewStringSet() - for _, brick := range vr.Bricks { - stringset.Add(brick.Host) - } - hosts := stringset.Strings() - v.Info.Mount.GlusterFS.Hosts = hosts - - // Save volume information - v.Info.Mount.GlusterFS.MountPoint = fmt.Sprintf("%v:%v", - hosts[0], vr.Name) - - // Set glusterfs mount volfile-servers options - v.Info.Mount.GlusterFS.Options = make(map[string]string) - v.Info.Mount.GlusterFS.Options["backup-volfile-servers"] = - strings.Join(hosts[1:], ",") - - godbc.Ensure(v.Info.Mount.GlusterFS.MountPoint != "") - return nil -} - -func (v *VolumeEntry) createVolumeRequest(db *bolt.DB, - brick_entries []*BrickEntry) (*executors.VolumeRequest, string, error) { - godbc.Require(db != nil) - godbc.Require(brick_entries != nil) - - // Setup list of bricks - vr := &executors.VolumeRequest{} - vr.Bricks = make([]executors.BrickInfo, len(brick_entries)) - var sshhost string - for i, b := range brick_entries { - - // Setup path - vr.Bricks[i].Path = b.Info.Path - - // Get storage host name from Node entry - err := db.View(func(tx *bolt.Tx) error { - node, err := NewNodeEntryFromId(tx, b.Info.NodeId) - if err != nil { - return err - } - - if sshhost == "" { - sshhost = node.ManageHostName() - } - vr.Bricks[i].Host = node.StorageHostName() - godbc.Check(vr.Bricks[i].Host != "") - - return nil - }) - if err != nil { - logger.Err(err) - return nil, "", err - } - } - - // Setup volume information in the request - vr.Name = v.Info.Name - v.Durability.SetExecutorVolumeRequest(vr) - vr.GlusterVolumeOptions = v.GlusterVolumeOptions - - return vr, sshhost, nil -} diff --git a/apps/glusterfs/volume_entry_test.go b/apps/glusterfs/volume_entry_test.go deleted file mode 100644 index f0ff213c16..0000000000 --- a/apps/glusterfs/volume_entry_test.go +++ /dev/null @@ -1,1721 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package glusterfs - -import ( - "errors" - "fmt" - "os" - "reflect" - "sort" - "strings" - "sync" - "testing" - - "github.com/boltdb/bolt" - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func createSampleVolumeEntry(size int) *VolumeEntry { - req := &api.VolumeCreateRequest{} - req.Size = size - req.Durability.Type = api.DurabilityReplicate - req.Durability.Replicate.Replica = 2 - - v := NewVolumeEntryFromRequest(req) - - return v -} - -func setupSampleDbWithTopology(app *App, - clusters, nodes_per_cluster, devices_per_node int, - disksize uint64) error { - - var clusterlist []string - err := app.db.Update(func(tx *bolt.Tx) error { - for c := 0; c < clusters; c++ { - cluster := createSampleClusterEntry() - - for n := 0; n < nodes_per_cluster; n++ { - node := createSampleNodeEntry() - node.Info.ClusterId = cluster.Info.Id - node.Info.Zone = n % 2 - - cluster.NodeAdd(node.Info.Id) - - for d := 0; d < devices_per_node; d++ { - device := createSampleDeviceEntry(node.Info.Id, disksize) - node.DeviceAdd(device.Id()) - - // Update allocator - err := app.allocator.AddDevice(cluster, node, device) - if err != nil { - return nil - } - - err = device.Save(tx) - if err != nil { - return err - } - } - err := node.Save(tx) - if err != nil { - return err - } - } - err := cluster.Save(tx) - if err != nil { - return err - } - } - - var err error - clusterlist, err = ClusterList(tx) - if err != nil { - return err - } - - return nil - }) - if err != nil { - return nil - } - - return nil -} - -func TestNewVolumeEntry(t *testing.T) { - v := NewVolumeEntry() - - tests.Assert(t, v.Bricks != nil) - tests.Assert(t, len(v.Info.Id) == 0) - tests.Assert(t, len(v.Info.Cluster) == 0) - tests.Assert(t, len(v.Info.Clusters) == 0) -} - -func TestNewVolumeEntryFromRequestOnlySize(t *testing.T) { - - req := &api.VolumeCreateRequest{} - req.Size = 1024 - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, len(v.Info.Clusters) == 0) - tests.Assert(t, v.Info.Snapshot.Enable == false) - tests.Assert(t, v.Info.Snapshot.Factor == 1) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, v.Info.Cluster == "") - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - tests.Assert(t, v.Info.Durability.Type == "") -} - -func TestNewVolumeEntryFromRequestReplicaDefault(t *testing.T) { - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Durability.Type = api.DurabilityReplicate - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, len(v.Info.Clusters) == 0) - tests.Assert(t, v.Info.Snapshot.Enable == false) - tests.Assert(t, v.Info.Snapshot.Factor == 1) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, v.Info.Cluster == "") - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - tests.Assert(t, v.Info.Durability.Type == api.DurabilityReplicate) - tests.Assert(t, v.Info.Durability.Replicate.Replica == 0) -} - -func TestNewVolumeEntryFromRequestReplica5(t *testing.T) { - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Durability.Type = api.DurabilityReplicate - req.Durability.Replicate.Replica = 5 - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, len(v.Info.Clusters) == 0) - tests.Assert(t, v.Info.Snapshot.Enable == false) - tests.Assert(t, v.Info.Snapshot.Factor == 1) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, v.Info.Cluster == "") - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - tests.Assert(t, v.Info.Durability.Type == api.DurabilityReplicate) - tests.Assert(t, v.Info.Durability.Replicate.Replica == 5) -} - -func TestNewVolumeEntryFromRequestDistribute(t *testing.T) { - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Durability.Type = api.DurabilityDistributeOnly - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, len(v.Info.Clusters) == 0) - tests.Assert(t, v.Info.Snapshot.Enable == false) - tests.Assert(t, v.Info.Snapshot.Factor == 1) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, v.Info.Cluster == "") - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - tests.Assert(t, v.Info.Durability.Type == api.DurabilityDistributeOnly) -} - -func TestNewVolumeEntryFromRequestDisperseDefault(t *testing.T) { - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Durability.Type = api.DurabilityEC - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, len(v.Info.Clusters) == 0) - tests.Assert(t, v.Info.Snapshot.Enable == false) - tests.Assert(t, v.Info.Snapshot.Factor == 1) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, v.Info.Cluster == "") - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - tests.Assert(t, v.Info.Durability.Type == api.DurabilityEC) - tests.Assert(t, v.Info.Durability.Disperse.Data == 0) - tests.Assert(t, v.Info.Durability.Disperse.Redundancy == 0) -} - -func TestNewVolumeEntryFromRequestDisperseDefault48(t *testing.T) { - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Durability.Type = api.DurabilityEC - req.Durability.Disperse.Data = 8 - req.Durability.Disperse.Redundancy = 4 - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, len(v.Info.Clusters) == 0) - tests.Assert(t, v.Info.Snapshot.Enable == false) - tests.Assert(t, v.Info.Snapshot.Factor == 1) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, v.Info.Cluster == "") - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - tests.Assert(t, v.Info.Durability.Type == api.DurabilityEC) - tests.Assert(t, v.Info.Durability.Disperse.Data == 8) - tests.Assert(t, v.Info.Durability.Disperse.Redundancy == 4) -} - -func TestNewVolumeEntryFromRequestClusters(t *testing.T) { - - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Clusters = []string{"abc", "def"} - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, v.Info.Snapshot.Enable == false) - tests.Assert(t, v.Info.Snapshot.Factor == 1) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, reflect.DeepEqual(req.Clusters, v.Info.Clusters)) - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - -} - -func TestNewVolumeEntryFromRequestSnapshotEnabledDefaultFactor(t *testing.T) { - - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Clusters = []string{"abc", "def"} - req.Snapshot.Enable = true - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, v.Info.Snapshot.Enable == true) - tests.Assert(t, v.Info.Snapshot.Factor == DEFAULT_THINP_SNAPSHOT_FACTOR) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, reflect.DeepEqual(req.Clusters, v.Info.Clusters)) - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - -} - -func TestNewVolumeEntryFromRequestSnapshotFactor(t *testing.T) { - - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Clusters = []string{"abc", "def"} - req.Snapshot.Enable = true - req.Snapshot.Factor = 1.3 - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, v.Info.Snapshot.Enable == true) - tests.Assert(t, v.Info.Snapshot.Factor == 1.3) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, reflect.DeepEqual(req.Clusters, v.Info.Clusters)) - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - -} - -func TestNewVolumeEntryFromRequestName(t *testing.T) { - - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Clusters = []string{"abc", "def"} - req.Snapshot.Enable = true - req.Snapshot.Factor = 1.3 - req.Name = "myvol" - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "myvol") - tests.Assert(t, v.Info.Snapshot.Enable == true) - tests.Assert(t, v.Info.Snapshot.Factor == 1.3) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, reflect.DeepEqual(req.Clusters, v.Info.Clusters)) - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - -} - -func TestNewVolumeEntryMarshal(t *testing.T) { - - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.Clusters = []string{"abc", "def"} - req.Snapshot.Enable = true - req.Snapshot.Factor = 1.3 - req.Name = "myvol" - - v := NewVolumeEntryFromRequest(req) - v.BrickAdd("abc") - v.BrickAdd("def") - - buffer, err := v.Marshal() - tests.Assert(t, err == nil) - tests.Assert(t, buffer != nil) - tests.Assert(t, len(buffer) > 0) - - um := &VolumeEntry{} - err = um.Unmarshal(buffer) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(v, um)) - -} - -func TestVolumeEntryAddDeleteDevices(t *testing.T) { - - v := NewVolumeEntry() - tests.Assert(t, len(v.Bricks) == 0) - - v.BrickAdd("123") - tests.Assert(t, utils.SortedStringHas(v.Bricks, "123")) - tests.Assert(t, len(v.Bricks) == 1) - v.BrickAdd("abc") - tests.Assert(t, utils.SortedStringHas(v.Bricks, "123")) - tests.Assert(t, utils.SortedStringHas(v.Bricks, "abc")) - tests.Assert(t, len(v.Bricks) == 2) - - v.BrickDelete("123") - tests.Assert(t, !utils.SortedStringHas(v.Bricks, "123")) - tests.Assert(t, utils.SortedStringHas(v.Bricks, "abc")) - tests.Assert(t, len(v.Bricks) == 1) - - v.BrickDelete("ccc") - tests.Assert(t, !utils.SortedStringHas(v.Bricks, "123")) - tests.Assert(t, utils.SortedStringHas(v.Bricks, "abc")) - tests.Assert(t, len(v.Bricks) == 1) -} - -func TestVolumeEntryFromIdNotFound(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Test for ID not found - err := app.db.View(func(tx *bolt.Tx) error { - _, err := NewVolumeEntryFromId(tx, "123") - return err - }) - tests.Assert(t, err == ErrNotFound) - -} - -func TestVolumeEntryFromId(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a volume entry - v := createSampleVolumeEntry(1024) - - // Save in database - err := app.db.Update(func(tx *bolt.Tx) error { - return v.Save(tx) - }) - tests.Assert(t, err == nil) - - // Load from database - var entry *VolumeEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - entry, err = NewVolumeEntryFromId(tx, v.Info.Id) - return err - }) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(entry, v)) - -} - -func TestVolumeEntrySaveDelete(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a volume entry - v := createSampleVolumeEntry(1024) - - // Save in database - err := app.db.Update(func(tx *bolt.Tx) error { - return v.Save(tx) - }) - tests.Assert(t, err == nil) - - // Delete entry which has devices - var entry *VolumeEntry - err = app.db.Update(func(tx *bolt.Tx) error { - var err error - entry, err = NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - - err = entry.Delete(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Check volume has been deleted and is not in db - err = app.db.View(func(tx *bolt.Tx) error { - var err error - entry, err = NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - return nil - - }) - tests.Assert(t, err == ErrNotFound) -} - -func TestNewVolumeEntryNewInfoResponse(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a volume entry - v := createSampleVolumeEntry(1024) - - // Save in database - err := app.db.Update(func(tx *bolt.Tx) error { - return v.Save(tx) - }) - tests.Assert(t, err == nil) - - // Retrieve info response - var info *api.VolumeInfoResponse - err = app.db.View(func(tx *bolt.Tx) error { - volume, err := NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - - info, err = volume.NewInfoResponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil, err) - - tests.Assert(t, info.Cluster == v.Info.Cluster) - tests.Assert(t, reflect.DeepEqual(info.Snapshot, v.Info.Snapshot)) - tests.Assert(t, info.Name == v.Info.Name) - tests.Assert(t, info.Id == v.Info.Id) - tests.Assert(t, info.Size == v.Info.Size) - tests.Assert(t, len(info.Bricks) == 0) -} - -func TestVolumeEntryCreateMissingCluster(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a volume entry - v := createSampleVolumeEntry(1024) - v.Info.Clusters = []string{} - - // Save in database - err := app.db.Update(func(tx *bolt.Tx) error { - return v.Save(tx) - }) - tests.Assert(t, err == nil) - - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == ErrNoSpace) - -} - -func TestVolumeEntryCreateRunOutOfSpaceMinBrickSizeLimit(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Total 80GB - err := setupSampleDbWithTopology(app, - 1, // clusters - 2, // nodes_per_cluster - 4, // devices_per_node, - 10*GB, // disksize, 10G) - ) - tests.Assert(t, err == nil) - - // Create a 100 GB volume - // Shouldn't be able to break it down enough to allocate volume - v := createSampleVolumeEntry(100) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == ErrNoSpace) - tests.Assert(t, v.Info.Cluster == "") - - // Check database volume does not exist - err = app.db.View(func(tx *bolt.Tx) error { - _, err := NewVolumeEntryFromId(tx, v.Info.Id) - return err - }) - tests.Assert(t, err == ErrNotFound) - - // Check no bricks or volumes exist - var bricks []string - var volumes []string - err = app.db.View(func(tx *bolt.Tx) error { - bricks = EntryKeys(tx, BOLTDB_BUCKET_BRICK) - volumes = EntryKeys(tx, BOLTDB_BUCKET_VOLUME) - - return nil - }) - tests.Assert(t, err == nil) - tests.Assert(t, len(bricks) == 0, bricks) - tests.Assert(t, len(volumes) == 0) - -} - -func TestVolumeEntryCreateRunOutOfSpaceMaxBrickLimit(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Lots of nodes with little drives - err := setupSampleDbWithTopology(app, - 1, // clusters - 20, // nodes_per_cluster - 40, // devices_per_node, - - // Must be larger than the brick min size - BrickMinSize*2, // disksize - ) - tests.Assert(t, err == nil) - - // Create a volume who will be broken down to - // Shouldn't be able to break it down enough to allocate volume - v := createSampleVolumeEntry(BrickMaxNum * 2 * int(BrickMinSize/GB)) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == ErrNoSpace) - - // Check database volume does not exist - err = app.db.View(func(tx *bolt.Tx) error { - _, err := NewVolumeEntryFromId(tx, v.Info.Id) - return err - }) - tests.Assert(t, err == ErrNotFound) - - // Check no bricks or volumes exist - var bricks []string - var volumes []string - err = app.db.View(func(tx *bolt.Tx) error { - bricks = EntryKeys(tx, BOLTDB_BUCKET_BRICK) - - volumes = EntryKeys(tx, BOLTDB_BUCKET_VOLUME) - return nil - }) - tests.Assert(t, len(bricks) == 0) - tests.Assert(t, len(volumes) == 0) - -} - -func TestVolumeEntryCreateTwoBricks(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a cluster in the database - err := setupSampleDbWithTopology(app, - 1, // clusters - 4, // nodes_per_cluster - 4, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Mock Brick creation and check it was called - brickCreateCount := 0 - gid := int64(1000) - var mutex sync.Mutex - app.xo.MockBrickCreate = func(host string, - brick *executors.BrickRequest) (*executors.BrickInfo, error) { - - mutex.Lock() - brickCreateCount++ - mutex.Unlock() - - bInfo := &executors.BrickInfo{ - Path: "/mockpath", - } - - tests.Assert(t, brick.Gid == gid) - return bInfo, nil - } - - // Create a volume who will be broken down to - v := createSampleVolumeEntry(250) - - // Set a GID - v.Info.Gid = gid - - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil, err) - tests.Assert(t, brickCreateCount == 2) - - // Check database - var info *api.VolumeInfoResponse - var nodelist sort.StringSlice - err = app.db.View(func(tx *bolt.Tx) error { - entry, err := NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - - info, err = entry.NewInfoResponse(tx) - if err != nil { - return err - } - - cluster, err := NewClusterEntryFromId(tx, v.Info.Cluster) - if err != nil { - return err - } - nodelist = make(sort.StringSlice, len(cluster.Info.Nodes)) - - for i, id := range cluster.Info.Nodes { - node, err := NewNodeEntryFromId(tx, id) - if err != nil { - return err - } - nodelist[i] = node.StorageHostName() - } - nodelist.Sort() - - return nil - - }) - tests.Assert(t, err == nil) - - // Check that it used only two bricks each with only two replicas - tests.Assert(t, len(info.Bricks) == 2) - tests.Assert(t, info.Bricks[0].Size == info.Bricks[1].Size) - tests.Assert(t, info.Cluster == v.Info.Cluster) - - // Check information on the bricks - for _, brick := range info.Bricks { - tests.Assert(t, brick.DeviceId != "") - tests.Assert(t, brick.NodeId != "") - tests.Assert(t, brick.Path != "") - } - - // Check mount information - host := strings.Split(info.Mount.GlusterFS.MountPoint, ":")[0] - tests.Assert(t, utils.SortedStringHas(nodelist, host), host, nodelist) - volfileServers := strings.Split(info.Mount.GlusterFS.Options["backup-volfile-servers"], ",") - for index, node := range volfileServers { - tests.Assert(t, node != host, index, node, host) - } - - // Should have at least the number nodes as replicas - tests.Assert(t, len(info.Mount.GlusterFS.Hosts) >= info.Durability.Replicate.Replica, - info.Mount.GlusterFS.Hosts, - info) - - // Check all hosts are in the list - err = app.db.View(func(tx *bolt.Tx) error { - for _, brick := range info.Bricks { - found := false - - node, err := NewNodeEntryFromId(tx, brick.NodeId) - tests.Assert(t, err == nil) - - for _, host := range info.Mount.GlusterFS.Hosts { - if host == node.StorageHostName() { - found = true - break - } - } - tests.Assert(t, found, node.StorageHostName(), - info.Mount.GlusterFS.Hosts) - } - - return nil - }) - -} - -func TestVolumeEntryCreateBrickDivision(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create 50TB of storage - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a volume which is so big that it does - // not fit into a single replica set - v := createSampleVolumeEntry(2000) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - var info *api.VolumeInfoResponse - var nodelist sort.StringSlice - err = app.db.View(func(tx *bolt.Tx) error { - entry, err := NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - - info, err = entry.NewInfoResponse(tx) - if err != nil { - return err - } - - cluster, err := NewClusterEntryFromId(tx, v.Info.Cluster) - if err != nil { - return err - } - nodelist = make(sort.StringSlice, len(cluster.Info.Nodes)) - - for i, id := range cluster.Info.Nodes { - node, err := NewNodeEntryFromId(tx, id) - if err != nil { - return err - } - nodelist[i] = node.StorageHostName() - } - nodelist.Sort() - - return nil - - }) - tests.Assert(t, err == nil) - - // Will need 3 splits for a total of 8 bricks + replicas - // - // NOTE: Why 8 bricksets of 250GB instead of 4 bricksets - // of 500GB each? Because the disk needed for hosting - // a brick if size X is a little larger than X, because - // we additionally allocate at least some space for metadata. - // Hence we will end up using 250GB bricks, and no two bricks - // will be on the same disk. Requesting slightly less than - // 2000GB, e.g. 1940GB yields a four 485GB bricksets. - // - tests.Assert(t, len(info.Bricks) == 16) - for b := 1; b < 16; b++ { - tests.Assert(t, 250*GB == info.Bricks[b].Size, b) - } - tests.Assert(t, info.Cluster == v.Info.Cluster) - - // Check mount information - host := strings.Split(info.Mount.GlusterFS.MountPoint, ":")[0] - tests.Assert(t, utils.SortedStringHas(nodelist, host), host, nodelist) - volfileServers := strings.Split(info.Mount.GlusterFS.Options["backup-volfile-servers"], ",") - for index, node := range volfileServers { - tests.Assert(t, node != host, index, node, host) - } - -} - -func TestVolumeEntryCreateMaxBrickSize(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create 500TB of storage - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a volume whose bricks must be at most BrickMaxSize - v := createSampleVolumeEntry(int(BrickMaxSize / GB * 4)) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Get volume information - var info *api.VolumeInfoResponse - err = app.db.View(func(tx *bolt.Tx) error { - entry, err := NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - - info, err = entry.NewInfoResponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Check the size of the bricks are not bigger than BrickMaxSize - tests.Assert(t, len(info.Bricks) == 8) - for b := 1; b < len(info.Bricks); b++ { - tests.Assert(t, info.Bricks[b].Size <= BrickMaxSize) - } - tests.Assert(t, info.Cluster == v.Info.Cluster) - -} - -func TestVolumeEntryCreateOnClustersRequested(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create 50TB of storage - err := setupSampleDbWithTopology(app, - 10, // clusters - 10, // nodes_per_cluster - 10, // devices_per_node, - 5*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Get a cluster list - var clusters sort.StringSlice - err = app.db.View(func(tx *bolt.Tx) error { - var err error - clusters, err = ClusterList(tx) - return err - }) - tests.Assert(t, err == nil) - clusters.Sort() - - // Create a 1TB volume - v := createSampleVolumeEntry(1024) - - // Set the clusters to the first two cluster ids - v.Info.Clusters = []string{clusters[0]} - - // Create volume - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Check database volume does not exist - var info *api.VolumeInfoResponse - err = app.db.View(func(tx *bolt.Tx) error { - entry, err := NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - - info, err = entry.NewInfoResponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - tests.Assert(t, info.Cluster == clusters[0]) - - // Create a new volume on either of three clusters - clusterset := clusters[2:5] - v = createSampleVolumeEntry(1024) - v.Info.Clusters = clusterset - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Check database volume exists - err = app.db.View(func(tx *bolt.Tx) error { - entry, err := NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - - info, err = entry.NewInfoResponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - tests.Assert(t, utils.SortedStringHas(clusterset, info.Cluster)) - -} - -func TestVolumeEntryCreateCheckingClustersForSpace(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create 100 small clusters - err := setupSampleDbWithTopology(app, - 10, // clusters - 1, // nodes_per_cluster - 1, // devices_per_node, - 10*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create one large cluster - cluster := createSampleClusterEntry() - err = app.db.Update(func(tx *bolt.Tx) error { - for n := 0; n < 100; n++ { - node := createSampleNodeEntry() - node.Info.ClusterId = cluster.Info.Id - node.Info.Zone = n % 2 - - cluster.NodeAdd(node.Info.Id) - - for d := 0; d < 10; d++ { - device := createSampleDeviceEntry(node.Info.Id, 4*TB) - node.DeviceAdd(device.Id()) - - // update allocator - err := app.allocator.AddDevice(cluster, node, device) - if err != nil { - return nil - } - - // Save - err = device.Save(tx) - if err != nil { - return err - } - } - err := node.Save(tx) - if err != nil { - return err - } - } - err := cluster.Save(tx) - if err != nil { - return err - } - - return nil - }) - - // Create a 1TB volume - v := createSampleVolumeEntry(1024) - - // Create volume - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Check database volume exists - var info *api.VolumeInfoResponse - err = app.db.View(func(tx *bolt.Tx) error { - entry, err := NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - - info, err = entry.NewInfoResponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - tests.Assert(t, info.Cluster == cluster.Info.Id) -} - -func TestVolumeEntryCreateWithSnapshot(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Lots of nodes with little drives - err := setupSampleDbWithTopology(app, - 1, // clusters - 4, // nodes_per_cluster - 4, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a volume with a snapshot factor of 1.5 - // For a 200G vol, it would get a brick size of 100G, with a thin pool - // size of 100G * 1.5 = 150GB. - v := createSampleVolumeEntry(200) - v.Info.Snapshot.Enable = true - v.Info.Snapshot.Factor = 1.5 - - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Check database volume exists - var info *api.VolumeInfoResponse - err = app.db.View(func(tx *bolt.Tx) error { - entry, err := NewVolumeEntryFromId(tx, v.Info.Id) - if err != nil { - return err - } - - info, err = entry.NewInfoResponse(tx) - if err != nil { - return err - } - - return nil - - }) - tests.Assert(t, err == nil) - - // Check that it used only two bricks each with only two replicas - tests.Assert(t, len(info.Bricks) == 2) - err = app.db.View(func(tx *bolt.Tx) error { - for _, b := range info.Bricks { - device, err := NewDeviceEntryFromId(tx, b.DeviceId) - if err != nil { - return err - } - - tests.Assert(t, device.Info.Storage.Used >= uint64(1.5*float32(b.Size))) - } - - return nil - }) - tests.Assert(t, err == nil) -} - -func TestVolumeEntryCreateBrickCreationFailure(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Lots of nodes with little drives - err := setupSampleDbWithTopology(app, - 1, // clusters - 4, // nodes_per_cluster - 4, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Cause a brick creation failure - mockerror := errors.New("MOCK") - app.xo.MockBrickCreate = func(host string, brick *executors.BrickRequest) (*executors.BrickInfo, error) { - return nil, mockerror - } - - // Create a volume with a snapshot factor of 1.5 - // For a 200G vol, it would get a brick size of 100G, with a thin pool - // size of 100G * 1.5 = 150GB. - v := createSampleVolumeEntry(200) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == mockerror, err, mockerror) - - // Check database is still clean. No bricks and No volumes - err = app.db.View(func(tx *bolt.Tx) error { - volumes, err := VolumeList(tx) - tests.Assert(t, err == nil) - tests.Assert(t, len(volumes) == 0) - - bricks, err := BrickList(tx) - tests.Assert(t, err == nil) - tests.Assert(t, len(bricks) == 0) - - clusters, err := ClusterList(tx) - tests.Assert(t, err == nil) - tests.Assert(t, len(clusters) == 1) - - cluster, err := NewClusterEntryFromId(tx, clusters[0]) - tests.Assert(t, err == nil) - tests.Assert(t, len(cluster.Info.Volumes) == 0) - - return nil - - }) - tests.Assert(t, err == nil) -} - -func TestVolumeEntryCreateVolumeCreationFailure(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Lots of nodes with little drives - err := setupSampleDbWithTopology(app, - 1, // clusters - 4, // nodes_per_cluster - 4, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Cause a brick creation failure - mockerror := errors.New("MOCK") - app.xo.MockVolumeCreate = func(host string, volume *executors.VolumeRequest) (*executors.Volume, error) { - return nil, mockerror - } - - // Create a volume with a snapshot factor of 1.5 - // For a 200G vol, it would get a brick size of 100G, with a thin pool - // size of 100G * 1.5 = 150GB. - v := createSampleVolumeEntry(200) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == mockerror) - - // Check database is still clean. No bricks and No volumes - err = app.db.View(func(tx *bolt.Tx) error { - volumes, err := VolumeList(tx) - tests.Assert(t, err == nil) - tests.Assert(t, len(volumes) == 0) - - bricks, err := BrickList(tx) - tests.Assert(t, err == nil) - tests.Assert(t, len(bricks) == 0) - - clusters, err := ClusterList(tx) - tests.Assert(t, err == nil) - tests.Assert(t, len(clusters) == 1) - - cluster, err := NewClusterEntryFromId(tx, clusters[0]) - tests.Assert(t, err == nil) - tests.Assert(t, len(cluster.Info.Volumes) == 0) - - return nil - - }) - tests.Assert(t, err == nil) -} - -func TestVolumeEntryDestroy(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Lots of nodes with little drives - err := setupSampleDbWithTopology(app, - 1, // clusters - 4, // nodes_per_cluster - 4, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a volume with a snapshot factor of 1.5 - // For a 200G vol, it would get a brick size of 100G, with a thin pool - // size of 100G * 1.5 = 150GB. - v := createSampleVolumeEntry(200) - v.Info.Snapshot.Enable = true - v.Info.Snapshot.Factor = 1.5 - - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Destroy the volume - err = v.Destroy(app.db, app.executor) - tests.Assert(t, err == nil) - - // Check database volume does not exist - err = app.db.View(func(tx *bolt.Tx) error { - - // Check that all devices have no used data - devices, err := DeviceList(tx) - tests.Assert(t, err == nil) - for _, id := range devices { - device, err := NewDeviceEntryFromId(tx, id) - tests.Assert(t, err == nil) - tests.Assert(t, device.Info.Storage.Used == 0) - tests.Assert(t, device.Info.Storage.Total == device.Info.Storage.Free) - } - - // Check there are no bricks - bricks, err := BrickList(tx) - tests.Assert(t, len(bricks) == 0) - - return nil - - }) - tests.Assert(t, err == nil) - - // Check that the devices have no bricks - err = app.db.View(func(tx *bolt.Tx) error { - devices, err := DeviceList(tx) - if err != nil { - return err - } - - for _, id := range devices { - device, err := NewDeviceEntryFromId(tx, id) - if err != nil { - return err - } - tests.Assert(t, len(device.Bricks) == 0, id, device) - } - - return err - }) - tests.Assert(t, err == nil) - - // Check that the cluster has no volumes - err = app.db.View(func(tx *bolt.Tx) error { - clusters, err := ClusterList(tx) - if err != nil { - return err - } - - tests.Assert(t, len(clusters) == 1) - cluster, err := NewClusterEntryFromId(tx, clusters[0]) - tests.Assert(t, err == nil) - tests.Assert(t, len(cluster.Info.Volumes) == 0) - - return nil - }) - tests.Assert(t, err == nil) - -} - -func TestVolumeEntryExpandNoSpace(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create cluster - err := setupSampleDbWithTopology(app, - 10, // clusters - 2, // nodes_per_cluster - 2, // devices_per_node, - 600*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create large volume - v := createSampleVolumeEntry(1190) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Save a copy of the volume before expansion - vcopy := &VolumeEntry{} - *vcopy = *v - - // Asking for a large amount will require too many little bricks - err = v.Expand(app.db, app.executor, app.allocator, 5000) - tests.Assert(t, err == ErrMaxBricks, err) - - // Asking for a small amount will set the bricks too small - err = v.Expand(app.db, app.executor, app.allocator, 10) - tests.Assert(t, err == ErrMinimumBrickSize, err) - - // Check db is the same as before expansion - var entry *VolumeEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - entry, err = NewVolumeEntryFromId(tx, v.Info.Id) - - return err - }) - tests.Assert(t, err == nil, err) - tests.Assert(t, reflect.DeepEqual(vcopy, entry)) -} - -func TestVolumeEntryExpandMaxBrickLimit(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a large cluster - err := setupSampleDbWithTopology(app, - 10, // clusters - 4, // nodes_per_cluster - 24, // devices_per_node, - 600*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create large volume - v := createSampleVolumeEntry(100) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Add a bunch of bricks until the limit - fakebricks := make(sort.StringSlice, BrickMaxNum-len(v.Bricks)) - v.Bricks = append(v.Bricks, fakebricks...) - - // Try to expand the volume, but it will return that the max number - // of bricks has been reached - err = v.Expand(app.db, app.executor, app.allocator, 100) - tests.Assert(t, err == ErrMaxBricks, err) -} - -func TestVolumeEntryExpandCreateBricksFailure(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create large cluster - err := setupSampleDbWithTopology(app, - 10, // clusters - 10, // nodes_per_cluster - 20, // devices_per_node, - 600*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create volume - v := createSampleVolumeEntry(100) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Save a copy of the volume before expansion - vcopy := &VolumeEntry{} - *vcopy = *v - - // Mock create bricks to fail - ErrMock := errors.New("MOCK") - app.xo.MockBrickCreate = func(host string, brick *executors.BrickRequest) (*executors.BrickInfo, error) { - return nil, ErrMock - } - - // Expand volume - err = v.Expand(app.db, app.executor, app.allocator, 500) - tests.Assert(t, err == ErrMock) - - // Check db is the same as before expansion - var entry *VolumeEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - entry, err = NewVolumeEntryFromId(tx, v.Info.Id) - - return err - }) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(vcopy, entry)) -} - -func TestVolumeEntryExpand(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create large cluster - err := setupSampleDbWithTopology(app, - 1, // clusters - 10, // nodes_per_cluster - 20, // devices_per_node, - 6*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create volume - v := createSampleVolumeEntry(1024) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, len(v.Bricks) == 2) - - // Expand volume - err = v.Expand(app.db, app.executor, app.allocator, 1234) - tests.Assert(t, err == nil) - tests.Assert(t, v.Info.Size == 1024+1234) - tests.Assert(t, len(v.Bricks) == 4) - - // Check db - var entry *VolumeEntry - err = app.db.View(func(tx *bolt.Tx) error { - var err error - entry, err = NewVolumeEntryFromId(tx, v.Info.Id) - - return err - }) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(entry, v)) -} - -func TestVolumeEntryDoNotAllowDeviceOnSameNode(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create cluster with plenty of space, but - // it will not have enough nodes - err := setupSampleDbWithTopology(app, - 1, // clusters - 1, // nodes_per_cluster - 200, // devices_per_node, - 6*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create volume - v := createSampleVolumeEntry(100) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err != nil, err) - tests.Assert(t, err == ErrNoSpace) - - v = createSampleVolumeEntry(10000) - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err != nil, err) - tests.Assert(t, err == ErrNoSpace) -} - -func TestVolumeEntryDestroyCheck(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Lots of nodes with little drives - err := setupSampleDbWithTopology(app, - 1, // clusters - 4, // nodes_per_cluster - 4, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create a volume with a snapshot factor of 1.5 - // For a 200G vol, it would get a brick size of 100G, with a thin pool - // size of 100G * 1.5 = 150GB. - v := createSampleVolumeEntry(200) - v.Info.Snapshot.Enable = true - v.Info.Snapshot.Factor = 1.5 - - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Test that a volume that is sharing space in a thin pool - // with either a clone or a snapshot cannot be deleted - app.xo.MockBrickDestroyCheck = func(host string, brick *executors.BrickRequest) error { - return fmt.Errorf("BRICKMOCK") - } - err = v.Destroy(app.db, app.executor) - tests.Assert(t, err != nil) - tests.Assert(t, err.Error() == "BRICKMOCK") - app.xo.MockBrickDestroyCheck = func(host string, brick *executors.BrickRequest) error { - return nil - } - - // Check that a volume with snapshots cannot be deleted - app.xo.MockVolumeDestroyCheck = func(host, volume string) error { - return fmt.Errorf("VOLMOCK") - } - err = v.Destroy(app.db, app.executor) - tests.Assert(t, err != nil) - tests.Assert(t, err.Error() == "VOLMOCK") - app.xo.MockVolumeDestroyCheck = func(host, volume string) error { - return nil - } - - // Now it should be able to be deleted - err = v.Destroy(app.db, app.executor) - tests.Assert(t, err == nil) - -} - -func TestVolumeEntryNameConflictSingleCluster(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - err := setupSampleDbWithTopology(app, - 1, // clusters - 3, // nodes_per_cluster - 6, // devices_per_node, - 6*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create volume - v := createSampleVolumeEntry(1024) - v.Info.Name = "myvol" - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil) - - // Create another volume same name - v = createSampleVolumeEntry(10000) - v.Info.Name = "myvol" - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err != nil, err) -} - -func TestVolumeEntryNameConflictMultiCluster(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create the app - app := NewTestApp(tmpfile) - defer app.Close() - - // Create 10 clusters - err := setupSampleDbWithTopology(app, - 10, // clusters - 3, // nodes_per_cluster - 6, // devices_per_node, - 6*TB, // disksize) - ) - tests.Assert(t, err == nil) - - // Create 10 volumes - for i := 0; i < 10; i++ { - v := createSampleVolumeEntry(1024) - v.Info.Name = "myvol" - err = v.Create(app.db, app.executor, app.allocator) - logger.Info("%v", v.Info.Cluster) - tests.Assert(t, err == nil, err) - } - - // Create another volume same name - v := createSampleVolumeEntry(10000) - v.Info.Name = "myvol" - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err != nil, err) -} - -func TestReplaceBrickInVolume(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a cluster in the database - err := setupSampleDbWithTopology(app, - 1, // clusters - 3, // nodes_per_cluster - 1, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - - v := createSampleVolumeEntry(100) - - err = v.Create(app.db, app.executor, app.allocator) - tests.Assert(t, err == nil, err) - var brickNames []string - var be *BrickEntry - err = app.db.View(func(tx *bolt.Tx) error { - - for _, brick := range v.Bricks { - be, err = NewBrickEntryFromId(tx, brick) - if err != nil { - return err - } - ne, err := NewNodeEntryFromId(tx, be.Info.NodeId) - if err != nil { - return err - } - brickName := fmt.Sprintf("%v:%v", ne.Info.Hostnames.Storage[0], be.Info.Path) - brickNames = append(brickNames, brickName) - } - return nil - }) - app.xo.MockVolumeInfo = func(host string, volume string) (*executors.Volume, error) { - var bricks []executors.Brick - brick := executors.Brick{Name: brickNames[0]} - bricks = append(bricks, brick) - brick = executors.Brick{Name: brickNames[1]} - bricks = append(bricks, brick) - Bricks := executors.Bricks{ - BrickList: bricks, - } - b := &executors.Volume{ - Bricks: Bricks, - } - return b, nil - } - brickId := be.Id() - err = v.replaceBrickInVolume(app.db, app.executor, app.allocator, brickId) - tests.Assert(t, err == nil) - - oldNode := be.Info.NodeId - brickOnOldNode := false - oldBrickIdExists := false - - err = app.db.View(func(tx *bolt.Tx) error { - - for _, brick := range v.Bricks { - be, err = NewBrickEntryFromId(tx, brick) - if err != nil { - return err - } - ne, err := NewNodeEntryFromId(tx, be.Info.NodeId) - if err != nil { - return err - } - if ne.Info.Id == oldNode { - brickOnOldNode = true - } - if be.Info.Id == brickId { - oldBrickIdExists = true - } - } - return nil - }) - - tests.Assert(t, !brickOnOldNode, "brick found on oldNode") - tests.Assert(t, !oldBrickIdExists, "old Brick not deleted") -} - -func TestNewVolumeEntryWithVolumeOptions(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - app := NewTestApp(tmpfile) - defer app.Close() - - // Create a cluster in the database - err := setupSampleDbWithTopology(app, - 1, // clusters - 3, // nodes_per_cluster - 1, // devices_per_node, - 500*GB, // disksize) - ) - tests.Assert(t, err == nil) - req := &api.VolumeCreateRequest{} - req.Size = 1024 - req.GlusterVolumeOptions = []string{"test-option"} - - v := NewVolumeEntryFromRequest(req) - tests.Assert(t, v.Info.Name == "vol_"+v.Info.Id) - tests.Assert(t, v.Info.Snapshot.Enable == false) - tests.Assert(t, v.Info.Snapshot.Factor == 1) - tests.Assert(t, v.Info.Size == 1024) - tests.Assert(t, len(v.Info.Id) != 0) - tests.Assert(t, len(v.Bricks) == 0) - tests.Assert(t, v.GlusterVolumeOptions[0] == "test-option") - - err = v.Create(app.db, app.executor, app.allocator) - logger.Info("%v", v.Info.Cluster) - tests.Assert(t, err == nil, err) - - // Check that the data on the database is recorded correctly - var entry VolumeEntry - err = app.db.View(func(tx *bolt.Tx) error { - return entry.Unmarshal( - tx.Bucket([]byte(BOLTDB_BUCKET_VOLUME)). - Get([]byte(v.Info.Id))) - }) - tests.Assert(t, err == nil) - tests.Assert(t, entry.GlusterVolumeOptions[0] == "test-option") - -} diff --git a/client/api/go-client/backup.go b/client/api/go-client/backup.go deleted file mode 100644 index 4b04d12de6..0000000000 --- a/client/api/go-client/backup.go +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), as published by the Free Software Foundation, -// or under the Apache License, Version 2.0 . -// -// You may not use this file except in compliance with those terms. -// - -package client - -import ( - "io" - "net/http" - - "github.com/heketi/heketi/pkg/utils" -) - -func (c *Client) BackupDb(w io.Writer) error { - // Create a request - req, err := http.NewRequest("GET", c.host+"/backup/db", nil) - if err != nil { - return err - } - - // Set token - err = c.setToken(req) - if err != nil { - return err - } - - // Send request - r, err := c.do(req) - if err != nil { - return err - } - if r.StatusCode != http.StatusOK { - return utils.GetErrorFromResponse(r) - } - - // Read data from response - defer r.Body.Close() - _, err = io.Copy(w, r.Body) - - return err -} diff --git a/client/api/go-client/client.go b/client/api/go-client/client.go index e29abfbfb8..cd03c62b5f 100644 --- a/client/api/go-client/client.go +++ b/client/api/go-client/client.go @@ -19,7 +19,7 @@ import ( "time" jwt "github.com/dgrijalva/jwt-go" - "github.com/heketi/heketi/pkg/utils" + "github.com/sigma/heketi/pkg/utils" ) const ( diff --git a/client/api/go-client/client_test.go b/client/api/go-client/client_test.go deleted file mode 100644 index 7909f7bed8..0000000000 --- a/client/api/go-client/client_test.go +++ /dev/null @@ -1,584 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), as published by the Free Software Foundation, -// or under the Apache License, Version 2.0 . -// -// You may not use this file except in compliance with those terms. -// - -package client - -import ( - "fmt" - "net/http/httptest" - "os" - "reflect" - "testing" - - "github.com/gorilla/mux" - "github.com/heketi/heketi/apps/glusterfs" - "github.com/heketi/heketi/middleware" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" - "github.com/urfave/negroni" -) - -const ( - TEST_ADMIN_KEY = "adminkey" -) - -func setupHeketiServer(app *glusterfs.App) *httptest.Server { - router := mux.NewRouter() - app.SetRoutes(router) - n := negroni.New() - - jwtconfig := &middleware.JwtAuthConfig{} - jwtconfig.Admin.PrivateKey = TEST_ADMIN_KEY - jwtconfig.User.PrivateKey = "userkey" - - // Setup middleware - n.Use(middleware.NewJwtAuth(jwtconfig)) - n.UseFunc(app.Auth) - n.UseHandler(router) - - // Create server - return httptest.NewServer(n) -} - -func TestTopology(t *testing.T) { - db := tests.Tempfile() - defer os.Remove(db) - - // Create the app - app := glusterfs.NewTestApp(db) - defer app.Close() - - // Setup the server - ts := setupHeketiServer(app) - defer ts.Close() - - // Create cluster correctly - c := NewClient(ts.URL, "admin", TEST_ADMIN_KEY) - tests.Assert(t, c != nil) - - //Create multiple clusters - clusteridlist := make([]api.ClusterInfoResponse, 0) - for m := 0; m < 4; m++ { - cluster, err := c.ClusterCreate() - tests.Assert(t, err == nil) - tests.Assert(t, cluster.Id != "") - clusteridlist = append(clusteridlist, *cluster) - } - tests.Assert(t, len(clusteridlist) == 4) - - //Verify the topology info and then delete the clusters - topology, err := c.TopologyInfo() - tests.Assert(t, err == nil) - for _, cid := range topology.ClusterList { - clusterid := cid.Id - err = c.ClusterDelete(clusterid) - tests.Assert(t, err == nil) - } - - //Create a cluster and add multiple nodes,devices and volumes - cluster, err := c.ClusterCreate() - tests.Assert(t, err == nil) - tests.Assert(t, cluster.Id != "") - tests.Assert(t, len(cluster.Nodes) == 0) - tests.Assert(t, len(cluster.Volumes) == 0) - - // Get information about the client - clusterinfo, err := c.ClusterInfo(cluster.Id) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(clusterinfo, cluster)) - - // Get information about the Topology and verify the cluster creation - topology, err = c.TopologyInfo() - tests.Assert(t, err == nil) - tests.Assert(t, topology.ClusterList[0].Id == cluster.Id) - - // Create multiple nodes and add devices to the nodes - nodeinfos := make([]api.NodeInfoResponse, 0) - for n := 0; n < 4; n++ { - nodeReq := &api.NodeAddRequest{} - nodeReq.ClusterId = cluster.Id - nodeReq.Hostnames.Manage = []string{"manage" + fmt.Sprintf("%v", n)} - nodeReq.Hostnames.Storage = []string{"storage" + fmt.Sprintf("%v", n)} - nodeReq.Zone = n + 1 - - // Create node - node, err := c.NodeAdd(nodeReq) - nodeinfos = append(nodeinfos, *node) - tests.Assert(t, err == nil) - - // Create a device request - sg := utils.NewStatusGroup() - for i := 0; i < 50; i++ { - sg.Add(1) - go func() { - defer sg.Done() - - deviceReq := &api.DeviceAddRequest{} - deviceReq.Name = "sd" + utils.GenUUID()[:8] - deviceReq.NodeId = node.Id - - // Create device - err := c.DeviceAdd(deviceReq) - sg.Err(err) - }() - } - tests.Assert(t, sg.Result() == nil) - } - tests.Assert(t, len(nodeinfos) != 0) - - // Get list of volumes - list, err := c.VolumeList() - tests.Assert(t, err == nil) - tests.Assert(t, len(list.Volumes) == 0) - - //Create multiple volumes to the cluster - volumeinfos := make([]api.VolumeInfoResponse, 0) - for n := 0; n < 4; n++ { - volumeReq := &api.VolumeCreateRequest{} - volumeReq.Size = 10 - volume, err := c.VolumeCreate(volumeReq) - tests.Assert(t, err == nil) - tests.Assert(t, volume.Id != "") - tests.Assert(t, volume.Size == volumeReq.Size) - volumeinfos = append(volumeinfos, *volume) - } - topology, err = c.TopologyInfo() - tests.Assert(t, err == nil) - - // Test topology have all the existing volumes - var volumefound int - for _, volumeid := range topology.ClusterList[0].Volumes { - volumeInfo := volumeid - for _, singlevolumei := range volumeinfos { - if singlevolumei.Id == volumeInfo.Id { - volumefound++ - break - } - } - } - tests.Assert(t, volumefound == 4) - - // Delete all the volumes - for _, volumeid := range topology.ClusterList[0].Volumes { - volumeInfo := volumeid - err = c.VolumeDelete(volumeInfo.Id) - tests.Assert(t, err == nil) - - } - - // Verify the nodes and devices info from topology info and delete the entries - for _, nodeid := range topology.ClusterList[0].Nodes { - nodeInfo := nodeid - var found bool - for _, singlenodei := range nodeinfos { - found = false - if singlenodei.Id == nodeInfo.Id { - found = true - break - } - } - tests.Assert(t, found == true) - - // Delete all devices - sg := utils.NewStatusGroup() - for index := range nodeInfo.DevicesInfo { - sg.Add(1) - go func(i int) { - defer sg.Done() - sg.Err(c.DeviceDelete(nodeInfo.DevicesInfo[i].Id)) - }(index) - } - err = sg.Result() - tests.Assert(t, err == nil, err) - - // Delete node - err = c.NodeDelete(nodeInfo.Id) - tests.Assert(t, err == nil) - - } - - // Delete cluster - err = c.ClusterDelete(cluster.Id) - tests.Assert(t, err == nil) - -} - -func TestClientCluster(t *testing.T) { - db := tests.Tempfile() - defer os.Remove(db) - - // Create the app - app := glusterfs.NewTestApp(db) - defer app.Close() - - // Setup the server - ts := setupHeketiServer(app) - defer ts.Close() - - // Create cluster with unknown user - c := NewClient(ts.URL, "asdf", "") - tests.Assert(t, c != nil) - cluster, err := c.ClusterCreate() - tests.Assert(t, err != nil) - tests.Assert(t, cluster == nil) - - // Create cluster with bad password - c = NewClient(ts.URL, "admin", "badpassword") - tests.Assert(t, c != nil) - cluster, err = c.ClusterCreate() - tests.Assert(t, err != nil) - tests.Assert(t, cluster == nil) - - // Create cluster correctly - c = NewClient(ts.URL, "admin", TEST_ADMIN_KEY) - tests.Assert(t, c != nil) - cluster, err = c.ClusterCreate() - tests.Assert(t, err == nil) - tests.Assert(t, cluster.Id != "") - tests.Assert(t, len(cluster.Nodes) == 0) - tests.Assert(t, len(cluster.Volumes) == 0) - - // Request bad id - info, err := c.ClusterInfo("bad") - tests.Assert(t, err != nil) - tests.Assert(t, info == nil) - - // Get information about the client - info, err = c.ClusterInfo(cluster.Id) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(info, cluster)) - - // Get a list of clusters - list, err := c.ClusterList() - tests.Assert(t, err == nil) - tests.Assert(t, len(list.Clusters) == 1) - tests.Assert(t, list.Clusters[0] == info.Id) - - // Delete non-existent cluster - err = c.ClusterDelete("badid") - tests.Assert(t, err != nil) - - // Delete current cluster - err = c.ClusterDelete(info.Id) - tests.Assert(t, err == nil) -} - -func TestClientNode(t *testing.T) { - db := tests.Tempfile() - defer os.Remove(db) - - // Create the app - app := glusterfs.NewTestApp(db) - defer app.Close() - - // Setup the server - ts := setupHeketiServer(app) - defer ts.Close() - - // Create cluster - c := NewClient(ts.URL, "admin", TEST_ADMIN_KEY) - tests.Assert(t, c != nil) - cluster, err := c.ClusterCreate() - tests.Assert(t, err == nil) - tests.Assert(t, cluster.Id != "") - tests.Assert(t, len(cluster.Nodes) == 0) - tests.Assert(t, len(cluster.Volumes) == 0) - - // Add node to unknown cluster - nodeReq := &api.NodeAddRequest{} - nodeReq.ClusterId = "badid" - nodeReq.Hostnames.Manage = []string{"manage"} - nodeReq.Hostnames.Storage = []string{"storage"} - nodeReq.Zone = 10 - _, err = c.NodeAdd(nodeReq) - tests.Assert(t, err != nil) - - // Create node request packet - nodeReq.ClusterId = cluster.Id - node, err := c.NodeAdd(nodeReq) - tests.Assert(t, err == nil) - tests.Assert(t, node.Zone == nodeReq.Zone) - tests.Assert(t, node.State == api.EntryStateOnline) - tests.Assert(t, node.Id != "") - tests.Assert(t, reflect.DeepEqual(nodeReq.Hostnames, node.Hostnames)) - tests.Assert(t, len(node.DevicesInfo) == 0) - - // Info on invalid id - info, err := c.NodeInfo("badid") - tests.Assert(t, err != nil) - tests.Assert(t, info == nil) - - // Set offline - err = c.NodeState(node.Id, &api.StateRequest{ - State: api.EntryStateOffline, - }) - tests.Assert(t, err == nil) - - // Get node info - info, err = c.NodeInfo(node.Id) - tests.Assert(t, err == nil) - tests.Assert(t, info.State == api.EntryStateOffline) - - // Set online - err = c.NodeState(node.Id, &api.StateRequest{ - State: api.EntryStateOnline, - }) - tests.Assert(t, err == nil) - - // Get node info - info, err = c.NodeInfo(node.Id) - tests.Assert(t, err == nil) - tests.Assert(t, info.State == api.EntryStateOnline) - tests.Assert(t, reflect.DeepEqual(info, node)) - - // Delete invalid node - err = c.NodeDelete("badid") - tests.Assert(t, err != nil) - - // Can't delete cluster with a node - err = c.ClusterDelete(cluster.Id) - tests.Assert(t, err != nil) - - // Delete node - err = c.NodeDelete(node.Id) - tests.Assert(t, err == nil) - - // Delete cluster - err = c.ClusterDelete(cluster.Id) - tests.Assert(t, err == nil) - -} - -func TestClientDevice(t *testing.T) { - db := tests.Tempfile() - defer os.Remove(db) - - // Create the app - app := glusterfs.NewTestApp(db) - defer app.Close() - - // Setup the server - ts := setupHeketiServer(app) - defer ts.Close() - - // Create cluster - c := NewClient(ts.URL, "admin", TEST_ADMIN_KEY) - tests.Assert(t, c != nil) - cluster, err := c.ClusterCreate() - tests.Assert(t, err == nil) - - // Create node request packet - nodeReq := &api.NodeAddRequest{} - nodeReq.ClusterId = cluster.Id - nodeReq.Hostnames.Manage = []string{"manage"} - nodeReq.Hostnames.Storage = []string{"storage"} - nodeReq.Zone = 10 - - // Create node - node, err := c.NodeAdd(nodeReq) - tests.Assert(t, err == nil) - - // Create a device request - deviceReq := &api.DeviceAddRequest{} - deviceReq.Name = "sda" - deviceReq.NodeId = node.Id - - // Create device - err = c.DeviceAdd(deviceReq) - tests.Assert(t, err == nil) - - // Get node information - info, err := c.NodeInfo(node.Id) - tests.Assert(t, err == nil) - tests.Assert(t, len(info.DevicesInfo) == 1) - tests.Assert(t, len(info.DevicesInfo[0].Bricks) == 0) - tests.Assert(t, info.DevicesInfo[0].Name == deviceReq.Name) - tests.Assert(t, info.DevicesInfo[0].Id != "") - - // Get info from an unknown id - _, err = c.DeviceInfo("badid") - tests.Assert(t, err != nil) - - // Get device information - deviceId := info.DevicesInfo[0].Id - deviceInfo, err := c.DeviceInfo(deviceId) - tests.Assert(t, err == nil) - tests.Assert(t, deviceInfo.State == api.EntryStateOnline) - tests.Assert(t, reflect.DeepEqual(*deviceInfo, info.DevicesInfo[0])) - - // Set offline - err = c.DeviceState(deviceId, &api.StateRequest{ - State: api.EntryStateOffline, - }) - tests.Assert(t, err == nil) - deviceInfo, err = c.DeviceInfo(deviceId) - tests.Assert(t, err == nil) - tests.Assert(t, deviceInfo.State == api.EntryStateOffline) - - // Set online - err = c.DeviceState(deviceId, &api.StateRequest{ - State: api.EntryStateOnline, - }) - tests.Assert(t, err == nil) - deviceInfo, err = c.DeviceInfo(deviceId) - tests.Assert(t, err == nil) - tests.Assert(t, deviceInfo.State == api.EntryStateOnline) - - // Try to delete node, and will not until we delete the device - err = c.NodeDelete(node.Id) - tests.Assert(t, err != nil) - - // Delete unknown device - err = c.DeviceDelete("badid") - tests.Assert(t, err != nil) - - // Delete device - err = c.DeviceDelete(deviceInfo.Id) - tests.Assert(t, err == nil) - - // Delete node - err = c.NodeDelete(node.Id) - tests.Assert(t, err == nil) - - // Delete cluster - err = c.ClusterDelete(cluster.Id) - tests.Assert(t, err == nil) - -} - -func TestClientVolume(t *testing.T) { - db := tests.Tempfile() - defer os.Remove(db) - - // Create the app - app := glusterfs.NewTestApp(db) - defer app.Close() - - // Setup the server - ts := setupHeketiServer(app) - defer ts.Close() - - // Create cluster - c := NewClient(ts.URL, "admin", TEST_ADMIN_KEY) - tests.Assert(t, c != nil) - cluster, err := c.ClusterCreate() - tests.Assert(t, err == nil) - - // Create node request packet - for n := 0; n < 4; n++ { - nodeReq := &api.NodeAddRequest{} - nodeReq.ClusterId = cluster.Id - nodeReq.Hostnames.Manage = []string{"manage" + fmt.Sprintf("%v", n)} - nodeReq.Hostnames.Storage = []string{"storage" + fmt.Sprintf("%v", n)} - nodeReq.Zone = n + 1 - - // Create node - node, err := c.NodeAdd(nodeReq) - tests.Assert(t, err == nil) - - // Create a device request - sg := utils.NewStatusGroup() - for i := 0; i < 50; i++ { - sg.Add(1) - go func() { - defer sg.Done() - - deviceReq := &api.DeviceAddRequest{} - deviceReq.Name = "sd" + utils.GenUUID()[:8] - deviceReq.NodeId = node.Id - - // Create device - err := c.DeviceAdd(deviceReq) - sg.Err(err) - - }() - } - tests.Assert(t, sg.Result() == nil) - } - - // Get list of volumes - list, err := c.VolumeList() - tests.Assert(t, err == nil) - tests.Assert(t, len(list.Volumes) == 0) - - // Create a volume - volumeReq := &api.VolumeCreateRequest{} - volumeReq.Size = 10 - volume, err := c.VolumeCreate(volumeReq) - tests.Assert(t, err == nil) - tests.Assert(t, volume.Id != "") - tests.Assert(t, volume.Size == volumeReq.Size) - - // Get list of volumes - list, err = c.VolumeList() - tests.Assert(t, err == nil) - tests.Assert(t, len(list.Volumes) == 1) - tests.Assert(t, list.Volumes[0] == volume.Id) - - // Get info on incorrect id - info, err := c.VolumeInfo("badid") - tests.Assert(t, err != nil) - - // Get info - info, err = c.VolumeInfo(volume.Id) - tests.Assert(t, err == nil) - tests.Assert(t, reflect.DeepEqual(info, volume)) - - // Expand volume with a bad id - expandReq := &api.VolumeExpandRequest{} - expandReq.Size = 10 - volumeInfo, err := c.VolumeExpand("badid", expandReq) - tests.Assert(t, err != nil) - - // Expand volume - volumeInfo, err = c.VolumeExpand(volume.Id, expandReq) - tests.Assert(t, err == nil) - tests.Assert(t, volumeInfo.Size == 20) - - // Delete bad id - err = c.VolumeDelete("badid") - tests.Assert(t, err != nil) - - // Delete volume - err = c.VolumeDelete(volume.Id) - tests.Assert(t, err == nil) - - clusterInfo, err := c.ClusterInfo(cluster.Id) - for _, nodeid := range clusterInfo.Nodes { - // Get node information - nodeInfo, err := c.NodeInfo(nodeid) - tests.Assert(t, err == nil) - - // Delete all devices - sg := utils.NewStatusGroup() - for index := range nodeInfo.DevicesInfo { - sg.Add(1) - go func(i int) { - defer sg.Done() - sg.Err(c.DeviceDelete(nodeInfo.DevicesInfo[i].Id)) - }(index) - } - err = sg.Result() - tests.Assert(t, err == nil, err) - - // Delete node - err = c.NodeDelete(nodeid) - tests.Assert(t, err == nil) - - } - - // Delete cluster - err = c.ClusterDelete(cluster.Id) - tests.Assert(t, err == nil) - -} diff --git a/client/api/go-client/cluster.go b/client/api/go-client/cluster.go index ddab70dac5..38799e5eab 100644 --- a/client/api/go-client/cluster.go +++ b/client/api/go-client/cluster.go @@ -16,8 +16,8 @@ import ( "bytes" "net/http" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" + "github.com/sigma/heketi/pkg/glusterfs/api" + "github.com/sigma/heketi/pkg/utils" ) func (c *Client) ClusterCreate() (*api.ClusterInfoResponse, error) { diff --git a/client/api/go-client/device.go b/client/api/go-client/device.go deleted file mode 100644 index 50412521a5..0000000000 --- a/client/api/go-client/device.go +++ /dev/null @@ -1,178 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), as published by the Free Software Foundation, -// or under the Apache License, Version 2.0 . -// -// You may not use this file except in compliance with those terms. -// - -package client - -import ( - "bytes" - "encoding/json" - "net/http" - "time" - - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" -) - -func (c *Client) DeviceAdd(request *api.DeviceAddRequest) error { - // Marshal request to JSON - buffer, err := json.Marshal(request) - if err != nil { - return err - } - - // Create a request - req, err := http.NewRequest("POST", c.host+"/devices", bytes.NewBuffer(buffer)) - if err != nil { - return err - } - req.Header.Set("Content-Type", "application/json") - - // Set token - err = c.setToken(req) - if err != nil { - return err - } - - // Send request - r, err := c.do(req) - if err != nil { - return err - } - if r.StatusCode != http.StatusAccepted { - return utils.GetErrorFromResponse(r) - } - - // Wait for response - r, err = c.waitForResponseWithTimer(r, time.Second) - if err != nil { - return err - } - if r.StatusCode != http.StatusNoContent { - return utils.GetErrorFromResponse(r) - } - - return nil -} - -func (c *Client) DeviceInfo(id string) (*api.DeviceInfoResponse, error) { - - // Create request - req, err := http.NewRequest("GET", c.host+"/devices/"+id, nil) - if err != nil { - return nil, err - } - - // Set token - err = c.setToken(req) - if err != nil { - return nil, err - } - - // Get info - r, err := c.do(req) - if err != nil { - return nil, err - } - if r.StatusCode != http.StatusOK { - return nil, utils.GetErrorFromResponse(r) - } - - // Read JSON response - var device api.DeviceInfoResponse - err = utils.GetJsonFromResponse(r, &device) - r.Body.Close() - if err != nil { - return nil, err - } - - return &device, nil -} - -func (c *Client) DeviceDelete(id string) error { - - // Create a request - req, err := http.NewRequest("DELETE", c.host+"/devices/"+id, nil) - if err != nil { - return err - } - - // Set token - err = c.setToken(req) - if err != nil { - return err - } - - // Send request - r, err := c.do(req) - if err != nil { - return err - } - if r.StatusCode != http.StatusAccepted { - return utils.GetErrorFromResponse(r) - } - - // Wait for response - r, err = c.waitForResponseWithTimer(r, time.Second) - if err != nil { - return err - } - if r.StatusCode != http.StatusNoContent { - return utils.GetErrorFromResponse(r) - } - - return nil -} - -func (c *Client) DeviceState(id string, - request *api.StateRequest) error { - - // Marshal request to JSON - buffer, err := json.Marshal(request) - if err != nil { - return err - } - - // Create a request - req, err := http.NewRequest("POST", - c.host+"/devices/"+id+"/state", - bytes.NewBuffer(buffer)) - if err != nil { - return err - } - req.Header.Set("Content-Type", "application/json") - - // Set token - err = c.setToken(req) - if err != nil { - return err - } - - // Get info - r, err := c.do(req) - if err != nil { - return err - } - if r.StatusCode != http.StatusAccepted { - return utils.GetErrorFromResponse(r) - } - - // Wait for response - r, err = c.waitForResponseWithTimer(r, time.Second) - if err != nil { - return err - } - if r.StatusCode != http.StatusNoContent { - return utils.GetErrorFromResponse(r) - } - - return nil -} diff --git a/client/api/go-client/node.go b/client/api/go-client/node.go index 20039c1ef4..304cca1e8a 100644 --- a/client/api/go-client/node.go +++ b/client/api/go-client/node.go @@ -18,8 +18,8 @@ import ( "net/http" "time" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" + "github.com/sigma/heketi/pkg/glusterfs/api" + "github.com/sigma/heketi/pkg/utils" ) func (c *Client) NodeAdd(request *api.NodeAddRequest) (*api.NodeInfoResponse, error) { diff --git a/client/api/go-client/topology.go b/client/api/go-client/topology.go deleted file mode 100644 index 6c9ffe28c8..0000000000 --- a/client/api/go-client/topology.go +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), as published by the Free Software Foundation, -// or under the Apache License, Version 2.0 . -// -// You may not use this file except in compliance with those terms. -// - -package client - -import ( - "github.com/heketi/heketi/pkg/glusterfs/api" -) - -func (c *Client) TopologyInfo() (*api.TopologyInfoResponse, error) { - topo := &api.TopologyInfoResponse{ - ClusterList: make([]api.Cluster, 0), - } - clusterlist, err := c.ClusterList() - if err != nil { - return nil, err - } - for _, cluster := range clusterlist.Clusters { - clusteri, err := c.ClusterInfo(cluster) - if err != nil { - return nil, err - } - cluster := api.Cluster{ - Id: clusteri.Id, - Volumes: make([]api.VolumeInfoResponse, 0), - Nodes: make([]api.NodeInfoResponse, 0), - } - cluster.Id = clusteri.Id - - // Iterate over the volume list in the cluster - for _, volumes := range clusteri.Volumes { - volumesi, err := c.VolumeInfo(volumes) - if err != nil { - return nil, err - } - if volumesi.Cluster == cluster.Id { - cluster.Volumes = append(cluster.Volumes, *volumesi) - } - } - - // Iterate over the nodes in the cluster - for _, node := range clusteri.Nodes { - nodei, err := c.NodeInfo(string(node)) - if err != nil { - return nil, err - } - cluster.Nodes = append(cluster.Nodes, *nodei) - } - topo.ClusterList = append(topo.ClusterList, cluster) - } - return topo, nil - -} diff --git a/client/api/go-client/volume.go b/client/api/go-client/volume.go index 821cfa8083..71139f330e 100644 --- a/client/api/go-client/volume.go +++ b/client/api/go-client/volume.go @@ -18,8 +18,8 @@ import ( "net/http" "time" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" + "github.com/sigma/heketi/pkg/glusterfs/api" + "github.com/sigma/heketi/pkg/utils" ) func (c *Client) VolumeCreate(request *api.VolumeCreateRequest) ( diff --git a/client/api/python/heketi/__init__.py b/client/api/python/heketi/__init__.py deleted file mode 100644 index 96c60e15fb..0000000000 --- a/client/api/python/heketi/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# flake8: noqa - -__title__ = 'heketi' -__version__ = '1.0.2' -__author__ = 'Heketi authors' -__license__ = 'Apache License (2.0) or LGPLv3+' -__copyright__ = 'Copyright 2016 Heketi authors' - - -from .heketi import HeketiClient diff --git a/client/api/python/heketi/heketi.py b/client/api/python/heketi/heketi.py deleted file mode 100644 index 373547ce97..0000000000 --- a/client/api/python/heketi/heketi.py +++ /dev/null @@ -1,211 +0,0 @@ -# -# Copyright (c) 2015 The heketi Authors -# -# This file is licensed to you under your choice of the GNU Lesser -# General Public License, version 3 or any later version (LGPLv3 or -# later), as published by the Free Software Foundation, -# or under the Apache License, Version 2.0 . -# -# You may not use this file except in compliance with those terms. -# -# -# Usage: -# # from heketi import HeketiClient -# # client = HeketiClient(server, user, key) -# # Eg.: Cluster creation: client.cluster_create() - -import jwt -import datetime -import hashlib -import requests -import time -import json - - -class HeketiClient(object): - - def __init__(self, host, user, key): - self.host = host - self.user = user - self.key = key - - def _set_token_in_header(self, method, uri, headers={}): - claims = {} - claims['iss'] = self.user - - # Issued at time - claims['iat'] = datetime.datetime.utcnow() - - # Expiration time - claims['exp'] = datetime.datetime.utcnow() \ - + datetime.timedelta(seconds=1) - - # URI tampering protection - claims['qsh'] = hashlib.sha256(method + '&' + uri).hexdigest() - - token = jwt.encode(claims, self.key, algorithm='HS256') - headers['Authorization'] = 'bearer ' + token - - return headers - - def hello(self): - method = 'GET' - uri = '/hello' - - headers = self._set_token_in_header(method, uri) - r = requests.get(self.host + uri, headers=headers) - return r.status_code == requests.codes.ok - - def _make_request(self, method, uri, data={}, headers={}): - ''' - Ref: - http://docs.python-requests.org - /en/master/_modules/requests/api/#request - ''' - headers.update(self._set_token_in_header(method, uri)) - r = requests.request(method, - self.host + uri, - headers=headers, - data=json.dumps(data)) - - r.raise_for_status() - - if r.status_code == requests.codes.accepted: - return self._get_queued_response(r.headers['location']) - else: - return r - - def _get_queued_response(self, queue_uri): - queue_uri = queue_uri - response_ready = False - - while response_ready is False: - headers = self._set_token_in_header('GET', queue_uri) - q = requests.get(self.host + queue_uri, - headers=headers, - allow_redirects=False) - - # Raise an exception when the request fails - q.raise_for_status() - - if 'X-Pending' in q.headers: - time.sleep(2) - else: - if q.status_code == requests.codes.see_other: - return self._make_request('GET', q.headers['location']) - else: - return q - - def cluster_create(self): - req = self._make_request('POST', '/clusters') - if req.status_code == requests.codes.created: - return req.json() - - def cluster_info(self, cluster_id): - uri = "/clusters/" + cluster_id - req = self._make_request('GET', uri) - if req.status_code == requests.codes.ok: - return req.json() - - def cluster_list(self): - uri = "/clusters" - req = self._make_request('GET', uri) - if req.status_code == requests.codes.ok: - return req.json() - - def cluster_delete(self, cluster_id): - uri = "/clusters/" + cluster_id - req = self._make_request('DELETE', uri) - return req.status_code == requests.codes.ok - - def node_add(self, node_options={}): - ''' - node_options is a dict consisting of paramters for - adding a node: https://github.com/heketi/heketi/wiki/API#add-node - ''' - uri = "/nodes" - req = self._make_request('POST', uri, node_options) - if req.status_code == requests.codes.ok: - return req.json() - - def node_info(self, node_id): - uri = '/nodes/' + node_id - req = self._make_request('GET', uri) - if req.status_code == requests.codes.ok: - return req.json() - - def node_delete(self, node_id): - uri = '/nodes/' + node_id - req = self._make_request('DELETE', uri) - return req.status_code == requests.codes.NO_CONTENT - - def node_state(self, node_id, state_request={}): - uri = '/nodes/' + node_id + '/state' - req = self._make_request('POST', uri, state_request) - return req.status_code == requests.codes.NO_CONTENT - '''if req.status_code == requests.codes.ok: - return req.json() - return req.status_code == requests.codes.ok - ''' - - def device_add(self, device_options={}): - ''' device_options is a dict with parameters to be passed \ - in the json request: \ - https://github.com/heketi/heketi/wiki/API#add-device - ''' - uri = '/devices' - req = self._make_request('POST', uri, device_options) - return req.status_code == requests.codes.NO_CONTENT - - def device_info(self, device_id): - uri = '/devices/' + device_id - req = self._make_request('GET', uri) - if req.status_code == requests.codes.ok: - return req.json() - - def device_delete(self, device_id): - uri = '/devices/' + device_id - req = self._make_request('DELETE', uri) - return req.status_code == requests.codes.NO_CONTENT - - def device_state(self, device_id, state_request={}): - uri = "/devices/" + device_id + "/state" - req = self._make_request('POST', uri, state_request) - return req.status_code == requests.codes.NO_CONTENT - '''if req.status_code == requests.codes.ok: - return req.json() - return req.status_code == requests.codes.ok - ''' - - def volume_create(self, volume_options={}): - ''' volume_options is a dict with volume creation options: - https://github.com/heketi/heketi/wiki/API#create-a-volume - ''' - uri = '/volumes' - req = self._make_request('POST', uri, volume_options) - if req.status_code == requests.codes.ok: - return req.json() - - def volume_list(self): - uri = '/volumes' - req = self._make_request('GET', uri) - if req.status_code == requests.codes.ok: - return req.json() - - def volume_info(self, volume_id): - uri = '/volumes/' + volume_id - req = self._make_request('GET', uri) - if req.status_code == requests.codes.ok: - return req.json() - - def volume_expand(self, volume_id, expand_size={}): - uri = '/volumes/' + volume_id + '/expand' - req = self._make_request('POST', uri, expand_size) - if req.status_code == requests.codes.ok: - return req.json() - - def volume_delete(self, volume_id): - uri = '/volumes/' + volume_id - req = self._make_request('DELETE', uri) - return req.status_code == requests.codes.NO_CONTENT diff --git a/client/api/python/requirements.txt b/client/api/python/requirements.txt deleted file mode 100644 index ec3a20c836..0000000000 --- a/client/api/python/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pyjwt >= 1.4.0 -requests >= 2.9.0 \ No newline at end of file diff --git a/client/api/python/setup.cfg b/client/api/python/setup.cfg deleted file mode 100644 index 3480374bc2..0000000000 --- a/client/api/python/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal=1 \ No newline at end of file diff --git a/client/api/python/setup.py b/client/api/python/setup.py deleted file mode 100644 index bee18a5238..0000000000 --- a/client/api/python/setup.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2016 heketi authors -# -# This file is licensed to you under your choice of the GNU Lesser -# General Public License, version 3 or any later version (LGPLv3 or -# later), as published by the Free Software Foundation, -# or under the Apache License, Version 2.0 . -# -# You may not use this file except in compliance with those terms. - -from setuptools import setup, find_packages - -setup( - name='heketi', - version='3.0.0', - description='Python client library for Heketi', - license='Apache License (2.0) or LGPLv3+', - author='Luis Pabon', - author_email='lpabon@redhat.com', - url='https://github.com/heketi/heketi/tree/master/client/api/python', - packages=find_packages(exclude=['test', 'bin']), - test_suite='nose.collector', - install_requires=['pyjwt', 'requests'], - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Information Technology', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', # noqa - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Topic :: System :: Filesystems', - 'Topic :: System :: Distributed Computing', - ], -) diff --git a/client/api/python/test-requirements.txt b/client/api/python/test-requirements.txt deleted file mode 100644 index 026a465cf9..0000000000 --- a/client/api/python/test-requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Hacking already pins down pep8, pyflakes and flake8 -hacking>=0.5.6,<0.6 -coverage -nose -nosexcover -nosehtmloutput -mock>=0.8.0 diff --git a/client/api/python/test/unit/heketi.json b/client/api/python/test/unit/heketi.json deleted file mode 100644 index 78f2d9090e..0000000000 --- a/client/api/python/test/unit/heketi.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "_port_comment": "Heketi Server Port Number", - "port": "8080", - - "_use_auth": "Enable JWT authorization. Please enable for deployment", - "use_auth": true, - - "_jwt": "Private keys for access", - "jwt": { - "_admin": "Admin has access to all APIs", - "admin": { - "key": "My Secret" - }, - "_user": "User only has access to /volumes endpoint", - "user": { - "key": "My Secret" - } - }, - - "_glusterfs_comment": "GlusterFS Configuration", - "glusterfs": { - "_executor_comment": [ - "Execute plugin. Possible choices: mock, ssh", - "mock: This setting is used for testing and development.", - " It will not send commands to any node.", - "ssh: This setting will notify Heketi to ssh to the nodes.", - " It will need the values in sshexec to be configured." - ], - "executor": "mock", - - "_sshexec_comment": "SSH username and private key file information", - "sshexec": { - "keyfile": "path/to/private_key", - "user": "sshuser" - }, - - "_db_comment": "Database file name", - "db": "heketi.db" - } -} diff --git a/client/api/python/test/unit/test_client.py b/client/api/python/test/unit/test_client.py deleted file mode 100644 index 741f8da903..0000000000 --- a/client/api/python/test/unit/test_client.py +++ /dev/null @@ -1,301 +0,0 @@ -# -# Copyright (c) 2015 The heketi Authors -# -# This file is licensed to you under your choice of the GNU Lesser -# General Public License, version 3 or any later version (LGPLv3 or -# later), as published by the Free Software Foundation, -# or under the Apache License, Version 2.0 . -# -# You may not use this file except in compliance with those terms. -# - -import unittest -import requests -from heketi import HeketiClient - - -TEST_ADMIN_KEY = "My Secret" -TEST_SERVER = "http://localhost:8080" - - -class test_heketi(unittest.TestCase): - - def test_cluster(self): - c = HeketiClient(TEST_SERVER, "admin", TEST_ADMIN_KEY) - - cluster = c.cluster_create() - self.assertEqual(True, cluster['id'] != "") - self.assertEqual(True, len(cluster['nodes']) == 0) - self.assertEqual(True, len(cluster['volumes']) == 0) - - # Request bad id - with self.assertRaises(requests.exceptions.HTTPError): - c.cluster_info("bad") - - # Get info about the cluster - info = c.cluster_info(cluster['id']) - self.assertEqual(True, info == cluster) - - # Get a list of clusters - list = c.cluster_list() - self.assertEqual(True, len(list['clusters']) == 1) - self.assertEqual(True, list['clusters'][0] == cluster['id']) - - # Delete non-existent cluster - with self.assertRaises(requests.exceptions.HTTPError): - c.cluster_delete("badid") - - # Delete current cluster - self.assertEqual(True, c.cluster_delete(info['id'])) - - def test_node(self): - node_req = {} - - c = HeketiClient(TEST_SERVER, "admin", TEST_ADMIN_KEY) - self.assertEqual(True, c != '') - - # Create cluster - cluster = c.cluster_create() - self.assertEqual(True, cluster['id'] != "") - self.assertEqual(True, len(cluster['nodes']) == 0) - self.assertEqual(True, len(cluster['volumes']) == 0) - - # Add node to unknown cluster - node_req['cluster'] = "bad_id" - node_req['zone'] = 10 - node_req['hostnames'] = { - "manage": ["node1-manage.gluster.lab.com"], - "storage": ["node1-storage.gluster.lab.com"] - } - - with self.assertRaises(requests.exceptions.HTTPError): - c.node_add(node_req) - - # Create node request packet - node_req['cluster'] = cluster['id'] - node = c.node_add(node_req) - self.assertEqual(True, node['zone'] == node_req['zone']) - self.assertEqual(True, node['id'] != "") - self.assertEqual(True, node_req['hostnames'] == node['hostnames']) - self.assertEqual(True, len(node['devices']) == 0) - - # Info on invalid id - with self.assertRaises(requests.exceptions.HTTPError): - c.node_info("badid") - - # Get node info - info = c.node_info(node['id']) - self.assertEqual(True, info == node) - self.assertEqual(info['state'], 'online') - - # Set offline - state = {} - state['state'] = 'offline' - self.assertEqual(True, c.node_state(node['id'], state)) - - # Get node info - info = c.node_info(node['id']) - self.assertEqual(info['state'], 'offline') - - state['state'] = 'online' - self.assertEqual(True, c.node_state(node['id'], state)) - - info = c.node_info(node['id']) - self.assertEqual(info['state'], 'online') - - # Delete invalid node - with self.assertRaises(requests.exceptions.HTTPError): - c.node_delete("badid") - - # Can't delete cluster with a node - with self.assertRaises(requests.exceptions.HTTPError): - c.cluster_delete(cluster['id']) - - # Delete node - del_node = c.node_delete(node['id']) - self.assertEqual(True, del_node) - - # Delete cluster - del_cluster = c.cluster_delete(cluster['id']) - self.assertEqual(True, del_cluster) - - def test_device(self): - # Create app - c = HeketiClient(TEST_SERVER, "admin", TEST_ADMIN_KEY) - - # Create cluster - cluster = c.cluster_create() - self.assertEqual(True, cluster['id'] != '') - - # Create node - node_req = {} - node_req['cluster'] = cluster['id'] - node_req['zone'] = 10 - node_req['hostnames'] = { - "manage": ["node1-manage.gluster.lab.com"], - "storage": ["node1-storage.gluster.lab.com"] - } - - node = c.node_add(node_req) - self.assertEqual(True, node['id'] != '') - - # Create a device request - device_req = {} - device_req['name'] = "sda" - device_req['node'] = node['id'] - - device = c.device_add(device_req) - self.assertEqual(True, device) - - # Get node information - info = c.node_info(node['id']) - self.assertEqual(True, len(info['devices']) == 1) - self.assertEqual(True, len(info['devices'][0]['bricks']) == 0) - self.assertEqual( - True, info['devices'][0]['name'] == device_req['name']) - self.assertEqual(True, info['devices'][0]['id'] != '') - - # Get info from an unknown id - with self.assertRaises(requests.exceptions.HTTPError): - c.device_info("badid") - - # Get device information - device_id = info['devices'][0]['id'] - device_info = c.device_info(device_id) - self.assertEqual(True, device_info == info['devices'][0]) - - # Set offline - state = {} - state['state'] = 'offline' - self.assertEqual(True, c.device_state(device_id, state)) - - # Get device info - info = c.device_info(device_id) - self.assertEqual(info['state'], 'offline') - - state['state'] = 'online' - self.assertEqual(True, c.device_state(device_id, state)) - - info = c.device_info(device_id) - self.assertEqual(info['state'], 'online') - - # Try to delete node, and will not until we delete the device - with self.assertRaises(requests.exceptions.HTTPError): - c.node_delete(node['id']) - - # Delete unknown device - with self.assertRaises(requests.exceptions.HTTPError): - c.node_delete("badid") - - # Delete device - device_delete = c.device_delete(device_info['id']) - self.assertEqual(True, device_delete) - - # Delete node - node_delete = c.node_delete(node['id']) - self.assertEqual(True, node_delete) - - # Delete cluster - cluster_delete = c.cluster_delete(cluster['id']) - self.assertEqual(True, cluster_delete) - - def test_volume(self): - # Create cluster - c = HeketiClient(TEST_SERVER, "admin", TEST_ADMIN_KEY) - self.assertEqual(True, c != '') - - cluster = c.cluster_create() - self.assertEqual(True, cluster['id'] != '') - - # Create node request packet - print "Creating Cluster" - for i in range(3): - node_req = {} - node_req['cluster'] = cluster['id'] - node_req['hostnames'] = { - "manage": ["node%s-manage.gluster.lab.com" % (i)], - "storage": ["node%s-storage.gluster.lab.com" % (i)]} - node_req['zone'] = i + 1 - - # Create node - node = c.node_add(node_req) - self.assertEqual(True, node['id'] != '') - - # Create and add devices - for i in range(1, 4): - device_req = {} - device_req['name'] = "sda%s" % (i) - device_req['node'] = node['id'] - - device = c.device_add(device_req) - self.assertEqual(True, device) - - # Get list of volumes - list = c.volume_list() - self.assertEqual(True, len(list['volumes']) == 0) - - # Create a volume - print "Creating a volume" - volume_req = {} - volume_req['size'] = 10 - volume = c.volume_create(volume_req) - self.assertEqual(True, volume['id'] != "") - self.assertEqual(True, volume['size'] == volume_req['size']) - - # Get list of volumes - list = c.volume_list() - self.assertEqual(True, len(list['volumes']) == 1) - self.assertEqual(True, list['volumes'][0] == volume['id']) - - # Get info on incorrect id - with self.assertRaises(requests.exceptions.HTTPError): - c.volume_info("badid") - - # Get info - info = c.volume_info(volume['id']) - self.assertEqual(True, info == volume) - - # Expand volume with a bad id - volume_ex_params = {} - volume_ex_params['expand_size'] = 10 - - with self.assertRaises(requests.exceptions.HTTPError): - c.volume_expand("badid", volume_ex_params) - - # Expand volume - print "Expanding volume" - volumeInfo = c.volume_expand(volume['id'], volume_ex_params) - self.assertEqual(True, volumeInfo['size'] == 20) - - # Delete bad id - with self.assertRaises(requests.exceptions.HTTPError): - c.volume_delete("badid") - - # Delete volume - print "Deleting volume" - volume_delete = c.volume_delete(volume['id']) - self.assertEqual(True, volume_delete) - - print "Deleting Cluster" - clusterInfo = c.cluster_info(cluster['id']) - for node_id in clusterInfo['nodes']: - # Get node information - nodeInfo = c.node_info(node_id) - - # Delete all devices - for device in nodeInfo['devices']: - device_delete = c.device_delete(device['id']) - self.assertEqual(True, device_delete) - - # Delete node - node_delete = c.node_delete(node_id) - self.assertEqual(True, node_delete) - - # Delete cluster - cluster_delete = c.cluster_delete(cluster['id']) - self.assertEqual(True, cluster_delete) - - -if __name__ == '__main__': - unittest.main() diff --git a/client/api/python/tox.ini b/client/api/python/tox.ini deleted file mode 100644 index 22f3dc12cf..0000000000 --- a/client/api/python/tox.ini +++ /dev/null @@ -1,35 +0,0 @@ -[tox] -envlist = py27,pep8 - -[testenv] -install_command = pip install -U {opts} {packages} -setenv = VIRTUAL_ENV={envdir} - NOSE_WITH_COVERAGE=1 - NOSE_COVER_BRANCHES=1 - NOSE_COVER_PACKAGE=heketi -deps = - -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt -changedir = {toxinidir}/test/unit -commands = nosetests -v {posargs} - -[testenv:cover] -setenv = VIRTUAL_ENV={envdir} - NOSE_WITH_COVERAGE=1 - NOSE_COVER_BRANCHES=1 - NOSE_COVER_HTML=1 - NOSE_COVER_HTML_DIR={toxinidir}/cover - -[tox:jenkins] -downloadcache = ~/cache/pip - -[testenv:pep8] -changedir = {toxinidir} -commands = - flake8 heketi test setup.py - -[flake8] -ignore = H -builtins = _ -exclude = .venv,.tox,dist,doc,test,*egg -show-source = True diff --git a/client/api/python/unittests.sh b/client/api/python/unittests.sh deleted file mode 100755 index 2769fa64a8..0000000000 --- a/client/api/python/unittests.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -CURRENT_DIR=`pwd` - -# Build server if we need to -if [ ! -x heketi-server ] ; then - ( cd ../../../ ; make ; cp heketi $CURRENT_DIR/heketi-server ) -fi - -# Start server -rm -f heketi.db > /dev/null 2>&1 -./heketi-server --config=test/unit/heketi.json > heketi.log 2>&1 & -pid=$! -sleep 2 - -# Start unit tests -tox -e py27 -results=$? - - -# kill server -kill $pid - -if [ $results -ne 0 ] ; then - exit $results -fi - -tox -e pep8 -results=$? - -exit $results diff --git a/client/cli/go/Makefile b/client/cli/go/Makefile deleted file mode 100644 index facdcd10fd..0000000000 --- a/client/cli/go/Makefile +++ /dev/null @@ -1,77 +0,0 @@ -# -# Based on http://chrismckenzie.io/post/deploying-with-golang/ -# - -.PHONY: version all run dist clean - -APP_NAME := heketi-cli -SHA := $(shell git rev-parse --short HEAD) -BRANCH := $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) -VER := $(shell git describe) -ARCH := $(shell go env GOARCH) -GOOS := $(shell go env GOOS) -DIR=. - -ifdef APP_SUFFIX - VERSION = $(VER)-$(subst /,-,$(APP_SUFFIX)) -else -ifeq (master,$(BRANCH)) - VERSION = $(VER) -else - VERSION = $(VER)-$(BRANCH) -endif -endif - -# Go setup -GO=go -TEST="go test" - -# Sources and Targets -EXECUTABLES :=$(APP_NAME) -# Build Binaries setting main.version and main.build vars -LDFLAGS :=-ldflags "-X main.HEKETI_CLI_VERSION=$(VERSION) -extldflags '-z relro -z now'" -# Package target -PACKAGE :=$(DIR)/dist/$(APP_NAME)-$(VERSION).$(GOOS).$(ARCH).tar.gz - -.DEFAULT: all - -all: build - -# print the version -version: - @echo $(VERSION) - -# print the name of the app -name: - @echo $(APP_NAME) - -# print the package path -package: - @echo $(PACKAGE) - -build: - go build $(LDFLAGS) -o $(APP_NAME) - -run: build - ./$(APP_NAME) - -test: - go test ./... - -clean: - @echo Cleaning Workspace... - rm -rf $(APP_NAME) - rm -rf dist - -$(PACKAGE): all - @echo Packaging Binaries... - @mkdir -p tmp/$(APP_NAME) - @cp $(APP_NAME) tmp/$(APP_NAME)/ - @cp etc/heketi.json tmp/$(APP_NAME)/ - @mkdir -p $(DIR)/dist/ - tar -czf $@ -C tmp $(APP_NAME); - @rm -rf tmp - @echo - @echo Package $@ saved in dist directory - -dist: $(PACKAGE) diff --git a/client/cli/go/cmds/cluster.go b/client/cli/go/cmds/cluster.go deleted file mode 100644 index a39c87dec0..0000000000 --- a/client/cli/go/cmds/cluster.go +++ /dev/null @@ -1,168 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package cmds - -import ( - "encoding/json" - "errors" - "fmt" - "strings" - - "github.com/heketi/heketi/client/api/go-client" - "github.com/spf13/cobra" -) - -func init() { - RootCmd.AddCommand(clusterCommand) - clusterCommand.AddCommand(clusterCreateCommand) - clusterCommand.AddCommand(clusterDeleteCommand) - clusterCommand.AddCommand(clusterListCommand) - clusterCommand.AddCommand(clusterInfoCommand) - clusterCreateCommand.SilenceUsage = true - clusterDeleteCommand.SilenceUsage = true - clusterInfoCommand.SilenceUsage = true - clusterListCommand.SilenceUsage = true -} - -var clusterCommand = &cobra.Command{ - Use: "cluster", - Short: "Heketi cluster management", - Long: "Heketi Cluster Management", -} - -var clusterCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a cluster", - Long: "Create a cluster", - Example: " $ heketi-cli cluster create", - RunE: func(cmd *cobra.Command, args []string) error { - // Create a client to talk to Heketi - heketi := client.NewClient(options.Url, options.User, options.Key) - // Create cluster - cluster, err := heketi.ClusterCreate() - if err != nil { - return err - } - - // Check if JSON should be printed - if options.Json { - data, err := json.Marshal(cluster) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - fmt.Fprintf(stdout, "Cluster id: %v\n", cluster.Id) - } - - return nil - }, -} - -var clusterDeleteCommand = &cobra.Command{ - Use: "delete [cluster_id]", - Short: "Delete the cluster", - Long: "Delete the cluster", - Example: " $ heketi-cli cluster delete 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("Cluster id missing") - } - - //set clusterId - clusterId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - err := heketi.ClusterDelete(clusterId) - if err == nil { - fmt.Fprintf(stdout, "Cluster %v deleted\n", clusterId) - } - - return err - }, -} - -var clusterInfoCommand = &cobra.Command{ - Use: "info [cluster_id]", - Short: "Retrieves information about cluster", - Long: "Retrieves information about cluster", - Example: " $ heketi-cli cluster info 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - if len(s) < 1 { - return errors.New("Cluster id missing") - } - - //set clusterId - clusterId := cmd.Flags().Arg(0) - - // Create a client to talk to Heketi - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Create cluster - info, err := heketi.ClusterInfo(clusterId) - if err != nil { - return err - } - - // Check if JSON should be printed - if options.Json { - data, err := json.Marshal(info) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - fmt.Fprintf(stdout, "Cluster id: %v\n", info.Id) - fmt.Fprintf(stdout, "Nodes:\n%v", strings.Join(info.Nodes, "\n")) - fmt.Fprintf(stdout, "\nVolumes:\n%v", strings.Join(info.Volumes, "\n")) - } - - return nil - }, -} - -var clusterListCommand = &cobra.Command{ - Use: "list", - Short: "Lists the clusters managed by Heketi", - Long: "Lists the clusters managed by Heketi", - Example: " $ heketi-cli cluster list", - RunE: func(cmd *cobra.Command, args []string) error { - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - // List clusters - list, err := heketi.ClusterList() - if err != nil { - return err - } - - if options.Json { - data, err := json.Marshal(list) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - fmt.Fprintf(stdout, "Clusters:\n") - for _, clusterid := range list.Clusters { - fmt.Fprintf(stdout, "Id:%v\n", clusterid) - } - } - - return nil - }, -} diff --git a/client/cli/go/cmds/device.go b/client/cli/go/cmds/device.go deleted file mode 100644 index 7b47c0a40c..0000000000 --- a/client/cli/go/cmds/device.go +++ /dev/null @@ -1,268 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package cmds - -import ( - "encoding/json" - "errors" - "fmt" - - "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/spf13/cobra" -) - -var ( - device, nodeId string -) - -func init() { - RootCmd.AddCommand(deviceCommand) - deviceCommand.AddCommand(deviceAddCommand) - deviceCommand.AddCommand(deviceDeleteCommand) - deviceCommand.AddCommand(deviceRemoveCommand) - deviceCommand.AddCommand(deviceInfoCommand) - deviceCommand.AddCommand(deviceEnableCommand) - deviceCommand.AddCommand(deviceDisableCommand) - deviceAddCommand.Flags().StringVar(&device, "name", "", - "Name of device to add") - deviceAddCommand.Flags().StringVar(&nodeId, "node", "", - "Id of the node which has this device") - deviceAddCommand.SilenceUsage = true - deviceDeleteCommand.SilenceUsage = true - deviceRemoveCommand.SilenceUsage = true - deviceInfoCommand.SilenceUsage = true -} - -var deviceCommand = &cobra.Command{ - Use: "device", - Short: "Heketi device management", - Long: "Heketi Device Management", -} - -var deviceAddCommand = &cobra.Command{ - Use: "add", - Short: "Add new device to node to be managed by Heketi", - Long: "Add new device to node to be managed by Heketi", - Example: ` $ heketi-cli device add \ - --name=/dev/sdb - --node=3e098cb4407d7109806bb196d9e8f095 `, - RunE: func(cmd *cobra.Command, args []string) error { - // Check arguments - if device == "" { - return errors.New("Missing device name") - } - if nodeId == "" { - return errors.New("Missing node id") - } - - // Create request blob - req := &api.DeviceAddRequest{} - req.Name = device - req.NodeId = nodeId - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Add node - err := heketi.DeviceAdd(req) - if err != nil { - return err - } else { - fmt.Fprintf(stdout, "Device added successfully\n") - } - - return nil - }, -} - -var deviceDeleteCommand = &cobra.Command{ - Use: "delete [device_id]", - Short: "Deletes a device from Heketi node", - Long: "Deletes a device from Heketi node", - Example: " $ heketi-cli device delete 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("Device id missing") - } - - //set clusterId - deviceId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - err := heketi.DeviceDelete(deviceId) - if err == nil { - fmt.Fprintf(stdout, "Device %v deleted\n", deviceId) - } - - return err - }, -} - -var deviceRemoveCommand = &cobra.Command{ - Use: "remove [device_id]", - Short: "Removes a device from Heketi node", - Long: "Removes a device from Heketi node", - Example: " $ heketi-cli device remove 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("Device id missing") - } - - //set clusterId - deviceId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - req := &api.StateRequest{ - State: "failed", - } - err := heketi.DeviceState(deviceId, req) - if err == nil { - fmt.Fprintf(stdout, "Device %v is now removed\n", deviceId) - } - - return err - }, -} - -var deviceInfoCommand = &cobra.Command{ - Use: "info [device_id]", - Short: "Retreives information about the device", - Long: "Retreives information about the device", - Example: " $ heketi-cli node info 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - //ensure proper number of args - s := cmd.Flags().Args() - if len(s) < 1 { - return errors.New("Device id missing") - } - - // Set node id - deviceId := cmd.Flags().Arg(0) - - // Create a client to talk to Heketi - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Create cluster - info, err := heketi.DeviceInfo(deviceId) - if err != nil { - return err - } - - if options.Json { - data, err := json.Marshal(info) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - fmt.Fprintf(stdout, "Device Id: %v\n"+ - "Name: %v\n"+ - "State: %v\n"+ - "Size (GiB): %v\n"+ - "Used (GiB): %v\n"+ - "Free (GiB): %v\n", - info.Id, - info.Name, - info.State, - info.Storage.Total/(1024*1024), - info.Storage.Used/(1024*1024), - info.Storage.Free/(1024*1024)) - - fmt.Fprintf(stdout, "Bricks:\n") - for _, d := range info.Bricks { - fmt.Fprintf(stdout, "Id:%-35v"+ - "Size (GiB):%-8v"+ - "Path: %v\n", - d.Id, - d.Size/(1024*1024), - d.Path) - } - } - return nil - - }, -} - -var deviceEnableCommand = &cobra.Command{ - Use: "enable [device_id]", - Short: "Allows device to go online", - Long: "Allows device to go online", - Example: " $ heketi-cli device enable 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("device id missing") - } - - //set clusterId - deviceId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - req := &api.StateRequest{ - State: "online", - } - err := heketi.DeviceState(deviceId, req) - if err == nil { - fmt.Fprintf(stdout, "Device %v is now online\n", deviceId) - } - - return err - }, -} - -var deviceDisableCommand = &cobra.Command{ - Use: "disable [device_id]", - Short: "Disallow usage of a device by placing it offline", - Long: "Disallow usage of a device by placing it offline", - Example: " $ heketi-cli device disable 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("device id missing") - } - - //set clusterId - deviceId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - req := &api.StateRequest{ - State: "offline", - } - err := heketi.DeviceState(deviceId, req) - if err == nil { - fmt.Fprintf(stdout, "Device %v is now offline\n", deviceId) - } - - return err - }, -} diff --git a/client/cli/go/cmds/heketi_storage.go b/client/cli/go/cmds/heketi_storage.go deleted file mode 100644 index 7b7897075e..0000000000 --- a/client/cli/go/cmds/heketi_storage.go +++ /dev/null @@ -1,321 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package cmds - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/db" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/spf13/cobra" - - kubeapi "k8s.io/kubernetes/pkg/api/v1" - batch "k8s.io/kubernetes/pkg/apis/batch/v1" -) - -type KubeList struct { - APIVersion string `json:"apiVersion"` - Kind string `json:"kind"` - Items []interface{} `json:"items"` -} - -const ( - HeketiStorageJobName = "heketi-storage-copy-job" - HeketiStorageEndpointName = "heketi-storage-endpoints" - HeketiStorageSecretName = "heketi-storage-secret" - HeketiStorageVolTagName = "heketi-storage" - - HeketiStorageVolumeSize = 2 - HeketiStorageVolumeSizeStr = "2Gi" -) - -var ( - HeketiStorageJobContainer string - heketiStorageListFilename string -) - -func init() { - RootCmd.AddCommand(setupHeketiStorageCommand) - setupHeketiStorageCommand.Flags().StringVar(&heketiStorageListFilename, - "listfile", - "heketi-storage.json", - "Filename to contain list of objects") - setupHeketiStorageCommand.Flags().StringVar(&HeketiStorageJobContainer, - "image", - "heketi/heketi:dev", - "container image to run this job") -} - -func saveJson(i interface{}, filename string) error { - - // Open File - f, err := os.Create(filename) - if err != nil { - return err - } - defer f.Close() - - // Marshal struct to JSON - data, err := json.MarshalIndent(i, "", " ") - if err != nil { - return err - } - - // Save data to file - _, err = f.Write(data) - if err != nil { - return err - } - - return nil -} - -func createHeketiStorageVolume(c *client.Client) (*api.VolumeInfoResponse, error) { - - // Make sure the volume does not already exist on any cluster - clusters, err := c.ClusterList() - if err != nil { - return nil, err - } - - // Go through all the clusters checking volumes - for _, clusterId := range clusters.Clusters { - cluster, err := c.ClusterInfo(clusterId) - if err != nil { - return nil, err - } - - // Go through all the volumes checking the names - for _, volumeId := range cluster.Volumes { - volume, err := c.VolumeInfo(volumeId) - if err != nil { - return nil, err - } - - // Check volume name - if volume.Name == db.HeketiStorageVolumeName { - return nil, fmt.Errorf("Volume %v alreay exists", db.HeketiStorageVolumeName) - } - } - } - - // Create request - req := &api.VolumeCreateRequest{} - req.Size = HeketiStorageVolumeSize - req.Durability.Type = api.DurabilityReplicate - req.Durability.Replicate.Replica = 3 - req.Name = db.HeketiStorageVolumeName - - // Create volume - volume, err := c.VolumeCreate(req) - if err != nil { - return nil, err - } - - return volume, nil -} - -func createHeketiSecretFromDb(c *client.Client) (*kubeapi.Secret, error) { - var dbfile bytes.Buffer - - // Save db - err := c.BackupDb(&dbfile) - if err != nil { - return nil, fmt.Errorf("ERROR: %v\nUnable to get database from Heketi server", err.Error()) - } - - // Create Secret - secret := &kubeapi.Secret{} - secret.Kind = "Secret" - secret.APIVersion = "v1" - secret.ObjectMeta.Name = HeketiStorageSecretName - secret.ObjectMeta.Labels = map[string]string{ - "deploy-heketi": "support", - } - secret.Data = make(map[string][]byte) - secret.Data["heketi.db"] = dbfile.Bytes() - - return secret, nil -} - -func createHeketiEndpointService() *kubeapi.Service { - - service := &kubeapi.Service{} - service.Kind = "Service" - service.APIVersion = "v1" - service.ObjectMeta.Name = HeketiStorageEndpointName - service.Spec.Ports = []kubeapi.ServicePort{ - kubeapi.ServicePort{ - Port: 1, - }, - } - - return service -} - -func createHeketiStorageEndpoints(c *client.Client, - volume *api.VolumeInfoResponse) *kubeapi.Endpoints { - - endpoint := &kubeapi.Endpoints{} - endpoint.Kind = "Endpoints" - endpoint.APIVersion = "v1" - endpoint.ObjectMeta.Name = HeketiStorageEndpointName - - // Initialize slices - endpoint.Subsets = make([]kubeapi.EndpointSubset, - len(volume.Mount.GlusterFS.Hosts)) - - // Save all nodes in the endpoints - for n, host := range volume.Mount.GlusterFS.Hosts { - - // Set Hostname/IP - endpoint.Subsets[n].Addresses = []kubeapi.EndpointAddress{ - kubeapi.EndpointAddress{ - IP: host, - }, - } - - // Set to port 1 - endpoint.Subsets[n].Ports = []kubeapi.EndpointPort{ - kubeapi.EndpointPort{ - Port: 1, - }, - } - } - - return endpoint -} - -func createHeketiCopyJob(volume *api.VolumeInfoResponse) *batch.Job { - job := &batch.Job{} - job.Kind = "Job" - job.APIVersion = "batch/v1" - job.ObjectMeta.Name = HeketiStorageJobName - job.ObjectMeta.Labels = map[string]string{ - "deploy-heketi": "support", - } - - var ( - p int32 = 1 - c int32 = 1 - ) - job.Spec.Parallelism = &p - job.Spec.Completions = &c - job.Spec.Template.ObjectMeta.Name = HeketiStorageJobName - job.Spec.Template.Spec.Volumes = []kubeapi.Volume{ - kubeapi.Volume{ - Name: HeketiStorageVolTagName, - VolumeSource: kubeapi.VolumeSource{ - Glusterfs: &kubeapi.GlusterfsVolumeSource{ - EndpointsName: HeketiStorageEndpointName, - Path: volume.Name, - }, - }, - }, - kubeapi.Volume{ - Name: HeketiStorageSecretName, - VolumeSource: kubeapi.VolumeSource{ - Secret: &kubeapi.SecretVolumeSource{ - SecretName: HeketiStorageSecretName, - }, - }, - }, - } - - job.Spec.Template.Spec.Containers = []kubeapi.Container{ - kubeapi.Container{ - Name: "heketi", - Image: HeketiStorageJobContainer, - Command: []string{ - "cp", - "/db/heketi.db", - "/heketi", - }, - VolumeMounts: []kubeapi.VolumeMount{ - kubeapi.VolumeMount{ - Name: HeketiStorageVolTagName, - MountPath: "/heketi", - }, - kubeapi.VolumeMount{ - Name: HeketiStorageSecretName, - MountPath: "/db", - }, - }, - }, - } - job.Spec.Template.Spec.RestartPolicy = kubeapi.RestartPolicyNever - - return job -} - -var setupHeketiStorageCommand = &cobra.Command{ - Use: "setup-openshift-heketi-storage", - Short: "Setup OpenShift/Kubernetes persistent storage for Heketi", - Long: "Creates a dedicated GlusterFS volume for Heketi.\n" + - "Once the volume is created, a Kubernetes/OpenShift\n" + - "list object is created to configure the volume.\n", - RunE: func(cmd *cobra.Command, args []string) (e error) { - - // Initialize Kubernetes List object - list := &KubeList{} - list.APIVersion = "v1" - list.Kind = "List" - list.Items = make([]interface{}, 0) - - // Create client - c := client.NewClient(options.Url, options.User, options.Key) - - // Create volume - volume, err := createHeketiStorageVolume(c) - if err != nil { - return err - } - - // Cleanup volume on error - defer func() { - if e != nil { - fmt.Fprintln(stderr, "Cleaning up") - c.VolumeDelete(volume.Id) - } - }() - - // Create secret - secret, err := createHeketiSecretFromDb(c) - if err != nil { - return err - } - list.Items = append(list.Items, secret) - - // Create endpoints - endpoints := createHeketiStorageEndpoints(c, volume) - list.Items = append(list.Items, endpoints) - - // Create service for the endpoints - service := createHeketiEndpointService() - list.Items = append(list.Items, service) - - // Create Job which copies db - job := createHeketiCopyJob(volume) - list.Items = append(list.Items, job) - - // Save list - fmt.Fprintf(stdout, "Saving %v\n", heketiStorageListFilename) - err = saveJson(list, heketiStorageListFilename) - if err != nil { - return err - } - - return nil - }, -} diff --git a/client/cli/go/cmds/node.go b/client/cli/go/cmds/node.go deleted file mode 100644 index 725d850d9c..0000000000 --- a/client/cli/go/cmds/node.go +++ /dev/null @@ -1,338 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package cmds - -import ( - "encoding/json" - "errors" - "fmt" - - "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/spf13/cobra" -) - -var ( - zone int - managmentHostNames string - storageHostNames string - clusterId string -) - -func init() { - RootCmd.AddCommand(nodeCommand) - nodeCommand.AddCommand(nodeAddCommand) - nodeCommand.AddCommand(nodeDeleteCommand) - nodeCommand.AddCommand(nodeInfoCommand) - nodeCommand.AddCommand(nodeEnableCommand) - nodeCommand.AddCommand(nodeDisableCommand) - nodeCommand.AddCommand(nodeListCommand) - nodeCommand.AddCommand(nodeRemoveCommand) - nodeAddCommand.Flags().IntVar(&zone, "zone", -1, "The zone in which the node should reside") - nodeAddCommand.Flags().StringVar(&clusterId, "cluster", "", "The cluster in which the node should reside") - nodeAddCommand.Flags().StringVar(&managmentHostNames, "management-host-name", "", "Management host name") - nodeAddCommand.Flags().StringVar(&storageHostNames, "storage-host-name", "", "Storage host name") - nodeAddCommand.SilenceUsage = true - nodeDeleteCommand.SilenceUsage = true - nodeInfoCommand.SilenceUsage = true - nodeListCommand.SilenceUsage = true - nodeRemoveCommand.SilenceUsage = true -} - -var nodeCommand = &cobra.Command{ - Use: "node", - Short: "Heketi Node Management", - Long: "Heketi Node Management", -} - -var nodeAddCommand = &cobra.Command{ - Use: "add", - Short: "Add new node to be managed by Heketi", - Long: "Add new node to be managed by Heketi", - Example: ` $ heketi-cli node add \ - --zone=3 \ - --cluster=3e098cb4407d7109806bb196d9e8f095 \ - --management-host-name=node1-manage.gluster.lab.com \ - --storage-host-name=node1-storage.gluster.lab.com -`, - RunE: func(cmd *cobra.Command, args []string) error { - // Check arguments - if zone == -1 { - return errors.New("Missing zone") - } - if managmentHostNames == "" { - return errors.New("Missing management hostname") - } - if storageHostNames == "" { - return errors.New("Missing storage hostname") - } - if clusterId == "" { - return errors.New("Missing cluster id") - } - - // Create request blob - req := &api.NodeAddRequest{} - req.ClusterId = clusterId - req.Hostnames.Manage = []string{managmentHostNames} - req.Hostnames.Storage = []string{storageHostNames} - req.Zone = zone - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Add node - node, err := heketi.NodeAdd(req) - if err != nil { - return err - } - - if options.Json { - data, err := json.Marshal(node) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - fmt.Fprintf(stdout, "Node information:\n"+ - "Id: %v\n"+ - "State: %v\n"+ - "Cluster Id: %v\n"+ - "Zone: %v\n"+ - "Management Hostname %v\n"+ - "Storage Hostname %v\n", - node.Id, - node.State, - node.ClusterId, - node.Zone, - node.Hostnames.Manage[0], - node.Hostnames.Storage[0]) - } - return nil - }, -} - -var nodeDeleteCommand = &cobra.Command{ - Use: "delete [node_id]", - Short: "Deletes a node from Heketi management", - Long: "Deletes a node from Heketi management", - Example: " $ heketi-cli node delete 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("Node id missing") - } - - //set clusterId - nodeId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - err := heketi.NodeDelete(nodeId) - if err == nil { - fmt.Fprintf(stdout, "Node %v deleted\n", nodeId) - } - - return err - }, -} - -var nodeEnableCommand = &cobra.Command{ - Use: "enable [node_id]", - Short: "Allows node to go online", - Long: "Allows node to go online", - Example: " $ heketi-cli node enable 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("Node id missing") - } - - //set clusterId - nodeId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - req := &api.StateRequest{ - State: "online", - } - err := heketi.NodeState(nodeId, req) - if err == nil { - fmt.Fprintf(stdout, "Node %v is now online\n", nodeId) - } - - return err - }, -} - -var nodeDisableCommand = &cobra.Command{ - Use: "disable [node_id]", - Short: "Disallow usage of a node by placing it offline", - Long: "Disallow usage of a node by placing it offline", - Example: " $ heketi-cli node disable 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("Node id missing") - } - - //set clusterId - nodeId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - req := &api.StateRequest{ - State: "offline", - } - err := heketi.NodeState(nodeId, req) - if err == nil { - fmt.Fprintf(stdout, "Node %v is now offline\n", nodeId) - } - - return err - }, -} - -var nodeListCommand = &cobra.Command{ - Use: "list all nodes", - Short: "List all nodes in cluster", - Long: "List all nodes in cluster", - Example: " $ heketi-cli node list", - RunE: func(cmd *cobra.Command, args []string) error { - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - clusters, err := heketi.ClusterList() - if err != nil { - return err - } - - for _, clusterid := range clusters.Clusters { - clusterinfo, err := heketi.ClusterInfo(clusterid) - if err != nil { - return err - } - for _, nodeid := range clusterinfo.Nodes { - fmt.Fprintf(stdout, - "Id:%v\tCluster:%v\n", - nodeid, - clusterid) - } - } - - return err - }, -} - -var nodeInfoCommand = &cobra.Command{ - Use: "info [node_id]", - Short: "Retreives information about the node", - Long: "Retreives information about the node", - Example: " $ heketi-cli node info 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - //ensure proper number of args - s := cmd.Flags().Args() - if len(s) < 1 { - return errors.New("Node id missing") - } - - // Set node id - nodeId := cmd.Flags().Arg(0) - - // Create a client to talk to Heketi - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Create cluster - info, err := heketi.NodeInfo(nodeId) - if err != nil { - return err - } - - if options.Json { - data, err := json.Marshal(info) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - fmt.Fprintf(stdout, "Node Id: %v\n"+ - "State: %v\n"+ - "Cluster Id: %v\n"+ - "Zone: %v\n"+ - "Management Hostname: %v\n"+ - "Storage Hostname: %v\n", - info.Id, - info.State, - info.ClusterId, - info.Zone, - info.Hostnames.Manage[0], - info.Hostnames.Storage[0]) - fmt.Fprintf(stdout, "Devices:\n") - for _, d := range info.DevicesInfo { - fmt.Fprintf(stdout, "Id:%-35v"+ - "Name:%-20v"+ - "State:%-10v"+ - "Size (GiB):%-8v"+ - "Used (GiB):%-8v"+ - "Free (GiB):%-8v\n", - d.Id, - d.Name, - d.State, - d.Storage.Total/(1024*1024), - d.Storage.Used/(1024*1024), - d.Storage.Free/(1024*1024)) - } - } - return nil - }, -} - -var nodeRemoveCommand = &cobra.Command{ - Use: "remove [node_id]", - Short: "Removes a node and all its associated devices from Heketi", - Long: "Removes a node and all its associated devices from Heketi", - Example: " $ heketi-cli node remove 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("Node id missing") - } - - //set clusterId - nodeId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - req := &api.StateRequest{ - State: "failed", - } - err := heketi.NodeState(nodeId, req) - if err == nil { - fmt.Fprintf(stdout, "Node %v is now removed\n", nodeId) - } - - return err - }, -} diff --git a/client/cli/go/cmds/root.go b/client/cli/go/cmds/root.go deleted file mode 100644 index c237456ae7..0000000000 --- a/client/cli/go/cmds/root.go +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package cmds - -import ( - "fmt" - "io" - "os" - - "github.com/spf13/cobra" -) - -var ( - HEKETI_CLI_VERSION = "(dev)" - stderr io.Writer - stdout io.Writer - options Options - version bool -) - -// Main arguments -type Options struct { - Url, Key, User string - Json bool -} - -var RootCmd = &cobra.Command{ - Use: "heketi-cli", - Short: "Command line program for Heketi", - Long: "Command line program for Heketi", - Example: ` $ export HEKETI_CLI_SERVER=http://localhost:8080 - $ heketi-cli volume list`, - Run: func(cmd *cobra.Command, args []string) { - if version { - fmt.Printf("heketi-cli %v\n", HEKETI_CLI_VERSION) - } else { - cmd.Usage() - } - }, -} - -func init() { - cobra.OnInitialize(initConfig) - RootCmd.PersistentFlags().StringVarP(&options.Url, "server", "s", "", - "\n\tHeketi server. Can also be set using the"+ - "\n\tenvironment variable HEKETI_CLI_SERVER") - RootCmd.PersistentFlags().StringVar(&options.Key, "secret", "", - "\n\tSecret key for specified user. Can also be"+ - "\n\tset using the environment variable HEKETI_CLI_KEY") - RootCmd.PersistentFlags().StringVar(&options.User, "user", "", - "\n\tHeketi user. Can also be set using the"+ - "\n\tenvironment variable HEKETI_CLI_USER") - RootCmd.PersistentFlags().BoolVar(&options.Json, "json", false, - "\n\tPrint response as JSON") - RootCmd.Flags().BoolVarP(&version, "version", "v", false, - "\n\tPrint version") - RootCmd.SilenceUsage = true -} - -func initConfig() { - // Check server - if options.Url == "" { - options.Url = os.Getenv("HEKETI_CLI_SERVER") - args := os.Args[1:] - if options.Url == "" && !version && len(args) > 0 { - fmt.Fprintf(stderr, "Server must be provided\n") - os.Exit(3) - } - } - - // Check user - if options.Key == "" { - options.Key = os.Getenv("HEKETI_CLI_KEY") - } - - // Check key - if options.User == "" { - options.User = os.Getenv("HEKETI_CLI_USER") - } -} - -func NewHeketiCli(heketiVersion string, mstderr io.Writer, mstdout io.Writer) *cobra.Command { - stderr = mstderr - stdout = mstdout - HEKETI_CLI_VERSION = heketiVersion - return RootCmd -} diff --git a/client/cli/go/cmds/topology.go b/client/cli/go/cmds/topology.go deleted file mode 100644 index 554f4f5f9d..0000000000 --- a/client/cli/go/cmds/topology.go +++ /dev/null @@ -1,342 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package cmds - -import ( - "encoding/json" - "errors" - "fmt" - "os" - - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/spf13/cobra" -) - -const ( - DURABILITY_STRING_REPLICATE = "replicate" - DURABILITY_STRING_DISTRIBUTE_ONLY = "none" - DURABILITY_STRING_EC = "disperse" -) - -var jsonConfigFile string - -// Config file -type ConfigFileNode struct { - Devices []string `json:"devices"` - Node api.NodeAddRequest `json:"node"` -} -type ConfigFileCluster struct { - Nodes []ConfigFileNode `json:"nodes"` -} -type ConfigFile struct { - Clusters []ConfigFileCluster `json:"clusters"` -} - -func init() { - RootCmd.AddCommand(topologyCommand) - topologyCommand.AddCommand(topologyLoadCommand) - topologyCommand.AddCommand(topologyInfoCommand) - topologyLoadCommand.Flags().StringVarP(&jsonConfigFile, "json", "j", "", - "\n\tConfiguration containing devices, nodes, and clusters, in"+ - "\n\tJSON format.") - topologyLoadCommand.SilenceUsage = true - topologyInfoCommand.SilenceUsage = true -} - -var topologyCommand = &cobra.Command{ - Use: "topology", - Short: "Heketi Topology Management", - Long: "Heketi Topology management", -} - -func getNodeIdFromHeketiTopology(t *api.TopologyInfoResponse, - managmentHostName string) *api.NodeInfoResponse { - - for _, c := range t.ClusterList { - for _, n := range c.Nodes { - if n.Hostnames.Manage[0] == managmentHostName { - return &n - } - } - } - - return nil -} - -func getDeviceIdFromHeketiTopology(t *api.TopologyInfoResponse, - managmentHostName string, - deviceName string) *api.DeviceInfoResponse { - - for _, c := range t.ClusterList { - for _, n := range c.Nodes { - if n.Hostnames.Manage[0] == managmentHostName { - for _, d := range n.DevicesInfo { - if d.Name == deviceName { - return &d - } - } - } - } - } - - return nil -} - -var topologyLoadCommand = &cobra.Command{ - Use: "load", - Short: "Add devices to Heketi from a configuration file", - Long: "Add devices to Heketi from a configuration file", - Example: " $ heketi-cli topology load --json=topo.json", - RunE: func(cmd *cobra.Command, args []string) error { - - // Check arguments - if jsonConfigFile == "" { - return errors.New("Missing configuration file") - } - - // Load config file - fp, err := os.Open(jsonConfigFile) - if err != nil { - return errors.New("Unable to open config file") - } - defer fp.Close() - configParser := json.NewDecoder(fp) - var topology ConfigFile - if err = configParser.Decode(&topology); err != nil { - return errors.New("Unable to parse config file") - } - - // Create client - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Load current topolgy - heketiTopology, err := heketi.TopologyInfo() - if err != nil { - return fmt.Errorf("Unable to get topology information: %v", err) - } - - // Register topology - for _, cluster := range topology.Clusters { - - // Register Nodes - var clusterInfo *api.ClusterInfoResponse - for _, node := range cluster.Nodes { - // Check node already exists - nodeInfo := getNodeIdFromHeketiTopology(heketiTopology, node.Node.Hostnames.Manage[0]) - - if nodeInfo != nil { - var err error - fmt.Fprintf(stdout, "\tFound node %v on cluster %v\n", - node.Node.Hostnames.Manage[0], nodeInfo.ClusterId) - clusterInfo, err = heketi.ClusterInfo(nodeInfo.ClusterId) - if err != nil { - fmt.Fprintf(stdout, "Unable to get cluster information\n") - return fmt.Errorf("Unable to get cluster information") - } - } else { - var err error - - // See if we need to create a cluster - if clusterInfo == nil { - fmt.Fprintf(stdout, "Creating cluster ... ") - clusterInfo, err = heketi.ClusterCreate() - if err != nil { - return err - } - fmt.Fprintf(stdout, "ID: %v\n", clusterInfo.Id) - - // Create a cleanup function in case no - // nodes or devices are created - defer func() { - // Get cluster information - info, err := heketi.ClusterInfo(clusterInfo.Id) - - // Delete empty cluster - if err == nil && len(info.Nodes) == 0 && len(info.Volumes) == 0 { - heketi.ClusterDelete(clusterInfo.Id) - } - }() - } - - // Create node - fmt.Fprintf(stdout, "\tCreating node %v ... ", node.Node.Hostnames.Manage[0]) - node.Node.ClusterId = clusterInfo.Id - nodeInfo, err = heketi.NodeAdd(&node.Node) - if err != nil { - fmt.Fprintf(stdout, "Unable to create node: %v\n", err) - - // Go to next node - continue - } else { - fmt.Fprintf(stdout, "ID: %v\n", nodeInfo.Id) - } - } - - // Add devices - for _, device := range node.Devices { - deviceInfo := getDeviceIdFromHeketiTopology(heketiTopology, - nodeInfo.Hostnames.Manage[0], - device) - if deviceInfo != nil { - fmt.Fprintf(stdout, "\t\tFound device %v\n", device) - } else { - fmt.Fprintf(stdout, "\t\tAdding device %v ... ", device) - - req := &api.DeviceAddRequest{} - req.Name = device - req.NodeId = nodeInfo.Id - err := heketi.DeviceAdd(req) - if err != nil { - fmt.Fprintf(stdout, "Unable to add device: %v\n", err) - } else { - fmt.Fprintf(stdout, "OK\n") - } - } - } - } - } - return nil - }, -} - -var topologyInfoCommand = &cobra.Command{ - Use: "info", - Short: "Retreives information about the current Topology", - Long: "Retreives information about the current Topology", - Example: " $ heketi-cli topology info", - RunE: func(cmd *cobra.Command, args []string) error { - - // Create a client to talk to Heketi - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Create Topology - topoinfo, err := heketi.TopologyInfo() - if err != nil { - return err - } - - // Check if JSON should be printed - if options.Json { - data, err := json.Marshal(topoinfo) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - - // Get the cluster list and iterate over - for i, _ := range topoinfo.ClusterList { - fmt.Fprintf(stdout, "\nCluster Id: %v\n", topoinfo.ClusterList[i].Id) - fmt.Fprintf(stdout, "\n %s\n", "Volumes:") - for k, _ := range topoinfo.ClusterList[i].Volumes { - - // Format and print volumeinfo on this cluster - v := topoinfo.ClusterList[i].Volumes[k] - s := fmt.Sprintf("\n\tName: %v\n"+ - "\tSize: %v\n"+ - "\tId: %v\n"+ - "\tCluster Id: %v\n"+ - "\tMount: %v\n"+ - "\tMount Options: backup-volfile-servers=%v\n"+ - "\tDurability Type: %v\n", - v.Name, - v.Size, - v.Id, - v.Cluster, - v.Mount.GlusterFS.MountPoint, - v.Mount.GlusterFS.Options["backup-volfile-servers"], - v.Durability.Type) - - switch v.Durability.Type { - case api.DurabilityEC: - s += fmt.Sprintf("\tDisperse Data: %v\n"+ - "\tDisperse Redundancy: %v\n", - v.Durability.Disperse.Data, - v.Durability.Disperse.Redundancy) - case api.DurabilityReplicate: - s += fmt.Sprintf("\tReplica: %v\n", - v.Durability.Replicate.Replica) - } - if v.Snapshot.Enable { - s += fmt.Sprintf("\tSnapshot: Enabled\n"+ - "\tSnapshot Factor: %.2f\n", - v.Snapshot.Factor) - } else { - s += "\tSnapshot: Disabled\n" - } - s += "\n\t\tBricks:\n" - for _, b := range v.Bricks { - s += fmt.Sprintf("\t\t\tId: %v\n"+ - "\t\t\tPath: %v\n"+ - "\t\t\tSize (GiB): %v\n"+ - "\t\t\tNode: %v\n"+ - "\t\t\tDevice: %v\n\n", - b.Id, - b.Path, - b.Size/(1024*1024), - b.NodeId, - b.DeviceId) - } - fmt.Fprintf(stdout, "%s", s) - } - - // format and print each Node information on this cluster - fmt.Fprintf(stdout, "\n %s\n", "Nodes:") - for j, _ := range topoinfo.ClusterList[i].Nodes { - info := topoinfo.ClusterList[i].Nodes[j] - fmt.Fprintf(stdout, "\n\tNode Id: %v\n"+ - "\tState: %v\n"+ - "\tCluster Id: %v\n"+ - "\tZone: %v\n"+ - "\tManagement Hostname: %v\n"+ - "\tStorage Hostname: %v\n", - info.Id, - info.State, - info.ClusterId, - info.Zone, - info.Hostnames.Manage[0], - info.Hostnames.Storage[0]) - fmt.Fprintf(stdout, "\tDevices:\n") - - // format and print the device info - for j, d := range info.DevicesInfo { - fmt.Fprintf(stdout, "\t\tId:%-35v"+ - "Name:%-20v"+ - "State:%-10v"+ - "Size (GiB):%-8v"+ - "Used (GiB):%-8v"+ - "Free (GiB):%-8v\n", - d.Id, - d.Name, - d.State, - d.Storage.Total/(1024*1024), - d.Storage.Used/(1024*1024), - d.Storage.Free/(1024*1024)) - - // format and print the brick information - fmt.Fprintf(stdout, "\t\t\tBricks:\n") - for _, d := range info.DevicesInfo[j].Bricks { - fmt.Fprintf(stdout, "\t\t\t\tId:%-35v"+ - "Size (GiB):%-8v"+ - "Path: %v\n", - d.Id, - d.Size/(1024*1024), - d.Path) - } - } - } - } - - } - - return nil - }, -} diff --git a/client/cli/go/cmds/volume.go b/client/cli/go/cmds/volume.go deleted file mode 100644 index fdfb4105ff..0000000000 --- a/client/cli/go/cmds/volume.go +++ /dev/null @@ -1,378 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package cmds - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "strings" - - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/kubernetes" - "github.com/spf13/cobra" -) - -var ( - size int - volname string - durability string - replica int - disperseData int - redundancy int - gid int64 - snapshotFactor float64 - clusters string - expandSize int - id string - kubePvFile string - kubePvEndpoint string - kubePv bool - glusterVolumeOptions string -) - -func init() { - RootCmd.AddCommand(volumeCommand) - volumeCommand.AddCommand(volumeCreateCommand) - volumeCommand.AddCommand(volumeDeleteCommand) - volumeCommand.AddCommand(volumeExpandCommand) - volumeCommand.AddCommand(volumeInfoCommand) - volumeCommand.AddCommand(volumeListCommand) - - volumeCreateCommand.Flags().IntVar(&size, "size", -1, - "\n\tSize of volume in GB") - volumeCreateCommand.Flags().Int64Var(&gid, "gid", 0, - "\n\tOptional: Initialize volume with the specified group id") - volumeCreateCommand.Flags().StringVar(&volname, "name", "", - "\n\tOptional: Name of volume. Only set if really necessary") - volumeCreateCommand.Flags().StringVar(&durability, "durability", "replicate", - "\n\tOptional: Durability type. Values are:"+ - "\n\t\tnone: No durability. Distributed volume only."+ - "\n\t\treplicate: (Default) Distributed-Replica volume."+ - "\n\t\tdisperse: Distributed-Erasure Coded volume.") - volumeCreateCommand.Flags().IntVar(&replica, "replica", 3, - "\n\tReplica value for durability type 'replicate'."+ - "\n\tDefault is 3") - volumeCreateCommand.Flags().IntVar(&disperseData, "disperse-data", 4, - "\n\tOptional: Dispersion value for durability type 'disperse'."+ - "\n\tDefault is 4") - volumeCreateCommand.Flags().IntVar(&redundancy, "redundancy", 2, - "\n\tOptional: Redundancy value for durability type 'disperse'."+ - "\n\tDefault is 2") - volumeCreateCommand.Flags().Float64Var(&snapshotFactor, "snapshot-factor", 1.0, - "\n\tOptional: Amount of storage to allocate for snapshot support."+ - "\n\tMust be greater 1.0. For example if a 10TiB volume requires 5TiB of"+ - "\n\tsnapshot storage, then snapshot-factor would be set to 1.5. If the"+ - "\n\tvalue is set to 1, then snapshots will consume the storage allocated") - volumeCreateCommand.Flags().StringVar(&clusters, "clusters", "", - "\n\tOptional: Comma separated list of cluster ids where this volume"+ - "\n\tmust be allocated. If omitted, Heketi will allocate the volume"+ - "\n\ton any of the configured clusters which have the available space."+ - "\n\tProviding a set of clusters will ensure Heketi allocates storage"+ - "\n\tfor this volume only in the clusters specified.") - volumeCreateCommand.Flags().StringVar(&glusterVolumeOptions, "gluster-volume-options", "", - "\n\tOptional: Comma separated list of volume options which can be set on the volume."+ - "\n\tIf omitted, Heketi will set no volume option for the volume.") - volumeCreateCommand.Flags().BoolVar(&kubePv, "persistent-volume", false, - "\n\tOptional: Output to standard out a persistent volume JSON file for OpenShift or"+ - "\n\tKubernetes with the name provided.") - volumeCreateCommand.Flags().StringVar(&kubePvFile, "persistent-volume-file", "", - "\n\tOptional: Create a persistent volume JSON file for OpenShift or"+ - "\n\tKubernetes with the name provided.") - volumeCreateCommand.Flags().StringVar(&kubePvEndpoint, "persistent-volume-endpoint", "", - "\n\tOptional: Endpoint name for the persistent volume") - volumeExpandCommand.Flags().IntVar(&expandSize, "expand-size", -1, - "\n\tAmount in GB to add to the volume") - volumeExpandCommand.Flags().StringVar(&id, "volume", "", - "\n\tId of volume to expand") - volumeCreateCommand.SilenceUsage = true - volumeDeleteCommand.SilenceUsage = true - volumeExpandCommand.SilenceUsage = true - volumeInfoCommand.SilenceUsage = true - volumeListCommand.SilenceUsage = true -} - -var volumeCommand = &cobra.Command{ - Use: "volume", - Short: "Heketi Volume Management", - Long: "Heketi Volume Management", -} - -var volumeCreateCommand = &cobra.Command{ - Use: "create", - Short: "Create a GlusterFS volume", - Long: "Create a GlusterFS volume", - Example: ` * Create a 100GB replica 3 volume: - $ heketi-cli volume create --size=100 - - * Create a 100GB replica 3 volume specifying two specific clusters: - $ heketi-cli volume create --size=100 \ - --clusters=0995098e1284ddccb46c7752d142c832,60d46d518074b13a04ce1022c8c7193c - - * Create a 100GB replica 2 volume with 50GB of snapshot storage: - $ heketi-cli volume create --size=100 --snapshot-factor=1.5 --replica=2 - - * Create a 100GB distributed volume - $ heketi-cli volume create --size=100 --durability=none - - * Create a 100GB erasure coded 4+2 volume with 25GB snapshot storage: - $ heketi-cli volume create --size=100 --durability=disperse --snapshot-factor=1.25 - - * Create a 100GB erasure coded 8+3 volume with 25GB snapshot storage: - $ heketi-cli volume create --size=100 --durability=disperse --snapshot-factor=1.25 \ - --disperse-data=8 --redundancy=3 - - * Create a 100GB distributed volume which supports performance related volume options. - $ heketi-cli volume create --size=100 --durability=none --gluster-volume-options="performance.rda-cache-limit 10MB","performance.nl-cache-positive-entry no" -`, - RunE: func(cmd *cobra.Command, args []string) error { - // Check volume size - if size == -1 { - return errors.New("Missing volume size") - } - - if kubePv && kubePvEndpoint == "" { - fmt.Fprintf(stderr, "--persistent-volume-endpoint must be provided "+ - "when using --persistent-volume\n") - return fmt.Errorf("Missing endpoint") - } - - // Create request blob - req := &api.VolumeCreateRequest{} - req.Size = size - req.Durability.Type = api.DurabilityType(durability) - req.Durability.Replicate.Replica = replica - req.Durability.Disperse.Data = disperseData - req.Durability.Disperse.Redundancy = redundancy - - // Check clusters - if clusters != "" { - req.Clusters = strings.Split(clusters, ",") - } - - // Check volume options - if glusterVolumeOptions != "" { - req.GlusterVolumeOptions = strings.Split(glusterVolumeOptions, ",") - } - - // Set group id if specified - if gid != 0 { - req.Gid = gid - } - - if volname != "" { - req.Name = volname - } - - if snapshotFactor > 1.0 { - req.Snapshot.Factor = float32(snapshotFactor) - req.Snapshot.Enable = true - } - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Add volume - volume, err := heketi.VolumeCreate(req) - if err != nil { - return err - } - - // Check if we need to print out a PV - if kubePvFile != "" || kubePv { - - // Create PV - pv := kubernetes.VolumeToPv(volume, "", kubePvEndpoint) - - // Convert to JSON - data, err := json.MarshalIndent(pv, "", " ") - if err != nil { - return err - } - - if kubePv { - fmt.Fprintln(stdout, string(data)) - } else { - - f, err := os.Create(kubePvFile) - if err != nil { - fmt.Fprintf(stderr, "Unable to write to file %v\n", kubePvFile) - return err - } - f.Write(data) - f.Close() - } - - } else { - if options.Json { - data, err := json.Marshal(volume) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - fmt.Fprintf(stdout, "%v", volume) - } - } - - return nil - }, -} - -var volumeDeleteCommand = &cobra.Command{ - Use: "delete", - Short: "Deletes the volume", - Long: "Deletes the volume", - Example: " $ heketi-cli volume delete 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - s := cmd.Flags().Args() - - //ensure proper number of args - if len(s) < 1 { - return errors.New("Volume id missing") - } - - //set volumeId - volumeId := cmd.Flags().Arg(0) - - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - //set url - err := heketi.VolumeDelete(volumeId) - if err == nil { - fmt.Fprintf(stdout, "Volume %v deleted\n", volumeId) - } - - return err - }, -} - -var volumeExpandCommand = &cobra.Command{ - Use: "expand", - Short: "Expand a volume", - Long: "Expand a volume", - Example: ` * Add 10GB to a volume - $ heketi-cli volume expand --volume=60d46d518074b13a04ce1022c8c7193c --expand-size=10 -`, - RunE: func(cmd *cobra.Command, args []string) error { - // Check volume size - if expandSize == -1 { - return errors.New("Missing volume amount to expand") - } - - if id == "" { - return errors.New("Missing volume id") - } - - // Create request - req := &api.VolumeExpandRequest{} - req.Size = expandSize - - // Create client - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Expand volume - volume, err := heketi.VolumeExpand(id, req) - if err != nil { - return err - } - - if options.Json { - data, err := json.Marshal(volume) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - fmt.Fprintf(stdout, "%v", volume) - } - return nil - }, -} - -var volumeInfoCommand = &cobra.Command{ - Use: "info", - Short: "Retreives information about the volume", - Long: "Retreives information about the volume", - Example: " $ heketi-cli volume info 886a86a868711bef83001", - RunE: func(cmd *cobra.Command, args []string) error { - //ensure proper number of args - s := cmd.Flags().Args() - if len(s) < 1 { - return errors.New("Volume id missing") - } - - // Set volume id - volumeId := cmd.Flags().Arg(0) - - // Create a client to talk to Heketi - heketi := client.NewClient(options.Url, options.User, options.Key) - - // Create cluster - info, err := heketi.VolumeInfo(volumeId) - if err != nil { - return err - } - - if options.Json { - data, err := json.Marshal(info) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - fmt.Fprintf(stdout, "%v", info) - } - return nil - - }, -} - -var volumeListCommand = &cobra.Command{ - Use: "list", - Short: "Lists the volumes managed by Heketi", - Long: "Lists the volumes managed by Heketi", - Example: " $ heketi-cli volume list", - RunE: func(cmd *cobra.Command, args []string) error { - // Create a client - heketi := client.NewClient(options.Url, options.User, options.Key) - - // List volumes - list, err := heketi.VolumeList() - if err != nil { - return err - } - - if options.Json { - data, err := json.Marshal(list) - if err != nil { - return err - } - fmt.Fprintf(stdout, string(data)) - } else { - for _, id := range list.Volumes { - volume, err := heketi.VolumeInfo(id) - if err != nil { - return err - } - - fmt.Fprintf(stdout, "Id:%-35v Cluster:%-35v Name:%v\n", - id, - volume.Cluster, - volume.Name) - } - } - - return nil - }, -} diff --git a/client/cli/go/heketi-cli.sh b/client/cli/go/heketi-cli.sh deleted file mode 100644 index fd91f4186b..0000000000 --- a/client/cli/go/heketi-cli.sh +++ /dev/null @@ -1,973 +0,0 @@ -# bash completion for heketi-cli -*- shell-script -*- - -__debug() -{ - if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then - echo "$*" >> "${BASH_COMP_DEBUG_FILE}" - fi -} - -# Homebrew on Macs have version 1.3 of bash-completion which doesn't include -# _init_completion. This is a very minimal version of that function. -__my_init_completion() -{ - COMPREPLY=() - _get_comp_words_by_ref "$@" cur prev words cword -} - -__index_of_word() -{ - local w word=$1 - shift - index=0 - for w in "$@"; do - [[ $w = "$word" ]] && return - index=$((index+1)) - done - index=-1 -} - -__contains_word() -{ - local w word=$1; shift - for w in "$@"; do - [[ $w = "$word" ]] && return - done - return 1 -} - -__handle_reply() -{ - __debug "${FUNCNAME[0]}" - case $cur in - -*) - if [[ $(type -t compopt) = "builtin" ]]; then - compopt -o nospace - fi - local allflags - if [ ${#must_have_one_flag[@]} -ne 0 ]; then - allflags=("${must_have_one_flag[@]}") - else - allflags=("${flags[*]} ${two_word_flags[*]}") - fi - COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) - if [[ $(type -t compopt) = "builtin" ]]; then - [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace - fi - - # complete after --flag=abc - if [[ $cur == *=* ]]; then - if [[ $(type -t compopt) = "builtin" ]]; then - compopt +o nospace - fi - - local index flag - flag="${cur%%=*}" - __index_of_word "${flag}" "${flags_with_completion[@]}" - if [[ ${index} -ge 0 ]]; then - COMPREPLY=() - PREFIX="" - cur="${cur#*=}" - ${flags_completion[${index}]} - if [ -n "${ZSH_VERSION}" ]; then - # zfs completion needs --flag= prefix - eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" - fi - fi - fi - return 0; - ;; - esac - - # check if we are handling a flag with special work handling - local index - __index_of_word "${prev}" "${flags_with_completion[@]}" - if [[ ${index} -ge 0 ]]; then - ${flags_completion[${index}]} - return - fi - - # we are parsing a flag and don't have a special handler, no completion - if [[ ${cur} != "${words[cword]}" ]]; then - return - fi - - local completions - completions=("${commands[@]}") - if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then - completions=("${must_have_one_noun[@]}") - fi - if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then - completions+=("${must_have_one_flag[@]}") - fi - COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) - - if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then - COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) - fi - - if [[ ${#COMPREPLY[@]} -eq 0 ]]; then - declare -F __custom_func >/dev/null && __custom_func - fi - - __ltrim_colon_completions "$cur" -} - -# The arguments should be in the form "ext1|ext2|extn" -__handle_filename_extension_flag() -{ - local ext="$1" - _filedir "@(${ext})" -} - -__handle_subdirs_in_dir_flag() -{ - local dir="$1" - pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 -} - -__handle_flag() -{ - __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - # if a command required a flag, and we found it, unset must_have_one_flag() - local flagname=${words[c]} - local flagvalue - # if the word contained an = - if [[ ${words[c]} == *"="* ]]; then - flagvalue=${flagname#*=} # take in as flagvalue after the = - flagname=${flagname%%=*} # strip everything after the = - flagname="${flagname}=" # but put the = back - fi - __debug "${FUNCNAME[0]}: looking for ${flagname}" - if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then - must_have_one_flag=() - fi - - # if you set a flag which only applies to this command, don't show subcommands - if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then - commands=() - fi - - # keep flag value with flagname as flaghash - if [ -n "${flagvalue}" ] ; then - flaghash[${flagname}]=${flagvalue} - elif [ -n "${words[ $((c+1)) ]}" ] ; then - flaghash[${flagname}]=${words[ $((c+1)) ]} - else - flaghash[${flagname}]="true" # pad "true" for bool flag - fi - - # skip the argument to a two word flag - if __contains_word "${words[c]}" "${two_word_flags[@]}"; then - c=$((c+1)) - # if we are looking for a flags value, don't show commands - if [[ $c -eq $cword ]]; then - commands=() - fi - fi - - c=$((c+1)) - -} - -__handle_noun() -{ - __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then - must_have_one_noun=() - elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then - must_have_one_noun=() - fi - - nouns+=("${words[c]}") - c=$((c+1)) -} - -__handle_command() -{ - __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - - local next_command - if [[ -n ${last_command} ]]; then - next_command="_${last_command}_${words[c]//:/__}" - else - if [[ $c -eq 0 ]]; then - next_command="_$(basename "${words[c]//:/__}")" - else - next_command="_${words[c]//:/__}" - fi - fi - c=$((c+1)) - __debug "${FUNCNAME[0]}: looking for ${next_command}" - declare -F $next_command >/dev/null && $next_command -} - -__handle_word() -{ - if [[ $c -ge $cword ]]; then - __handle_reply - return - fi - __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" - if [[ "${words[c]}" == -* ]]; then - __handle_flag - elif __contains_word "${words[c]}" "${commands[@]}"; then - __handle_command - elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then - __handle_command - else - __handle_noun - fi - __handle_word -} - -_heketi-cli_cluster_create() -{ - last_command="heketi-cli_cluster_create" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_cluster_delete() -{ - last_command="heketi-cli_cluster_delete" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_cluster_info() -{ - last_command="heketi-cli_cluster_info" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_cluster_list() -{ - last_command="heketi-cli_cluster_list" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_cluster() -{ - last_command="heketi-cli_cluster" - commands=() - commands+=("create") - commands+=("delete") - commands+=("info") - commands+=("list") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_device_add() -{ - last_command="heketi-cli_device_add" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--name=") - local_nonpersistent_flags+=("--name=") - flags+=("--node=") - local_nonpersistent_flags+=("--node=") - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_device_delete() -{ - last_command="heketi-cli_device_delete" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_device_disable() -{ - last_command="heketi-cli_device_disable" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_device_enable() -{ - last_command="heketi-cli_device_enable" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_device_info() -{ - last_command="heketi-cli_device_info" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_device() -{ - last_command="heketi-cli_device" - commands=() - commands+=("add") - commands+=("delete") - commands+=("disable") - commands+=("enable") - commands+=("info") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_node_add() -{ - last_command="heketi-cli_node_add" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--cluster=") - local_nonpersistent_flags+=("--cluster=") - flags+=("--management-host-name=") - local_nonpersistent_flags+=("--management-host-name=") - flags+=("--storage-host-name=") - local_nonpersistent_flags+=("--storage-host-name=") - flags+=("--zone=") - local_nonpersistent_flags+=("--zone=") - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_node_delete() -{ - last_command="heketi-cli_node_delete" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_node_disable() -{ - last_command="heketi-cli_node_disable" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_node_enable() -{ - last_command="heketi-cli_node_enable" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_node_info() -{ - last_command="heketi-cli_node_info" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_node() -{ - last_command="heketi-cli_node" - commands=() - commands+=("add") - commands+=("delete") - commands+=("disable") - commands+=("enable") - commands+=("info") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_setup-openshift-heketi-storage() -{ - last_command="heketi-cli_setup-openshift-heketi-storage" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--listfile=") - local_nonpersistent_flags+=("--listfile=") - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_topology_info() -{ - last_command="heketi-cli_topology_info" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_topology_load() -{ - last_command="heketi-cli_topology_load" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json=") - two_word_flags+=("-j") - local_nonpersistent_flags+=("--json=") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_topology() -{ - last_command="heketi-cli_topology" - commands=() - commands+=("info") - commands+=("load") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_volume_create() -{ - last_command="heketi-cli_volume_create" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--clusters=") - local_nonpersistent_flags+=("--clusters=") - flags+=("--disperse-data=") - local_nonpersistent_flags+=("--disperse-data=") - flags+=("--durability=") - local_nonpersistent_flags+=("--durability=") - flags+=("--name=") - local_nonpersistent_flags+=("--name=") - flags+=("--persistent-volume") - local_nonpersistent_flags+=("--persistent-volume") - flags+=("--persistent-volume-endpoint=") - local_nonpersistent_flags+=("--persistent-volume-endpoint=") - flags+=("--persistent-volume-file=") - local_nonpersistent_flags+=("--persistent-volume-file=") - flags+=("--redundancy=") - local_nonpersistent_flags+=("--redundancy=") - flags+=("--replica=") - local_nonpersistent_flags+=("--replica=") - flags+=("--size=") - local_nonpersistent_flags+=("--size=") - flags+=("--snapshot-factor=") - local_nonpersistent_flags+=("--snapshot-factor=") - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_volume_delete() -{ - last_command="heketi-cli_volume_delete" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_volume_expand() -{ - last_command="heketi-cli_volume_expand" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--expand-size=") - local_nonpersistent_flags+=("--expand-size=") - flags+=("--volume=") - local_nonpersistent_flags+=("--volume=") - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_volume_info() -{ - last_command="heketi-cli_volume_info" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_volume_list() -{ - last_command="heketi-cli_volume_list" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli_volume() -{ - last_command="heketi-cli_volume" - commands=() - commands+=("create") - commands+=("delete") - commands+=("expand") - commands+=("info") - commands+=("list") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_heketi-cli() -{ - last_command="heketi-cli" - commands=() - commands+=("cluster") - commands+=("device") - commands+=("node") - commands+=("setup-openshift-heketi-storage") - commands+=("topology") - commands+=("volume") - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--json") - flags+=("--log-flush-frequency=") - flags+=("--secret=") - flags+=("--server=") - two_word_flags+=("-s") - flags+=("--user=") - flags+=("--version") - flags+=("-v") - local_nonpersistent_flags+=("--version") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -__start_heketi-cli() -{ - local cur prev words cword - declare -A flaghash 2>/dev/null || : - if declare -F _init_completion >/dev/null 2>&1; then - _init_completion -s || return - else - __my_init_completion -n "=" || return - fi - - local c=0 - local flags=() - local two_word_flags=() - local local_nonpersistent_flags=() - local flags_with_completion=() - local flags_completion=() - local commands=("heketi-cli") - local must_have_one_flag=() - local must_have_one_noun=() - local last_command - local nouns=() - - __handle_word -} - -if [[ $(type -t compopt) = "builtin" ]]; then - complete -o default -F __start_heketi-cli heketi-cli -else - complete -o default -o nospace -F __start_heketi-cli heketi-cli -fi - -# ex: ts=4 sw=4 et filetype=sh diff --git a/client/cli/go/main.go b/client/cli/go/main.go deleted file mode 100644 index 1d67466518..0000000000 --- a/client/cli/go/main.go +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package main - -import ( - "io" - "os" - - "github.com/heketi/heketi/client/cli/go/cmds" -) - -var ( - HEKETI_CLI_VERSION = "(dev)" - stdout io.Writer = os.Stdout - stderr io.Writer = os.Stderr - version bool -) - -func main() { - cmd := cmds.NewHeketiCli(HEKETI_CLI_VERSION, stderr, stdout) - if err := cmd.Execute(); err != nil { - //fmt.Println(err) //Should be used for logging - os.Exit(-1) - } -} diff --git a/client/cli/go/topology-sample.json b/client/cli/go/topology-sample.json deleted file mode 100644 index e48152844d..0000000000 --- a/client/cli/go/topology-sample.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "clusters": [ - { - "nodes": [ - { - "node": { - "hostnames": { - "manage": [ - "192.168.10.100" - ], - "storage": [ - "192.168.10.100" - ] - }, - "zone": 1 - }, - "devices": [ - "/dev/sdb", - "/dev/sdc", - "/dev/sdd", - "/dev/sde", - "/dev/sdf", - "/dev/sdg", - "/dev/sdh", - "/dev/sdi" - ] - }, - { - "node": { - "hostnames": { - "manage": [ - "192.168.10.101" - ], - "storage": [ - "192.168.10.101" - ] - }, - "zone": 2 - }, - "devices": [ - "/dev/sdb", - "/dev/sdc", - "/dev/sdd", - "/dev/sde", - "/dev/sdf", - "/dev/sdg", - "/dev/sdh", - "/dev/sdi" - ] - }, - { - "node": { - "hostnames": { - "manage": [ - "192.168.10.102" - ], - "storage": [ - "192.168.10.102" - ] - }, - "zone": 1 - }, - "devices": [ - "/dev/sdb", - "/dev/sdc", - "/dev/sdd", - "/dev/sde", - "/dev/sdf", - "/dev/sdg", - "/dev/sdh", - "/dev/sdi" - ] - }, - { - "node": { - "hostnames": { - "manage": [ - "192.168.10.103" - ], - "storage": [ - "192.168.10.103" - ] - }, - "zone": 2 - }, - "devices": [ - "/dev/sdb", - "/dev/sdc", - "/dev/sdd", - "/dev/sde", - "/dev/sdf", - "/dev/sdg", - "/dev/sdh", - "/dev/sdi" - ] - } - ] - } - ] -} diff --git a/doc/design/README.md b/doc/design/README.md deleted file mode 100644 index d0c8d125c2..0000000000 --- a/doc/design/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Heketi Design Documents and Proposals - -This directory contains Heketi design documents and accepted design proposals. diff --git a/doc/man/heketi-cli.8 b/doc/man/heketi-cli.8 deleted file mode 100644 index 638d5cd649..0000000000 --- a/doc/man/heketi-cli.8 +++ /dev/null @@ -1,468 +0,0 @@ -.\" -.\" Copyright (c) 2015 The heketi Authors -.\" -.\" This file is licensed to you under your choice of the GNU Lesser -.\" General Public License, version 3 or any later version (LGPLv3 or -.\" later), or the GNU General Public License, version 2 (GPLv2), in all -.\" cases as published by the Free Software Foundation. -.\" - -.TH heketi-cli 8 "Heketi command line program" "Apr 2016" "The heketi Authors" -.nh -.ad l -.SH NAME -.PP -heketi\-cli \- Command line program for Heketi -.SH SYNOPSIS -.PP -\fBheketi\-cli\fP [commands] [options] -.SH DESCRIPTION -.PP -Command line program for Heketi -.SH COMMANDS - - -.SS "Cluster Commands" -.PP -.TP - -\fBheketi\-cli cluster create\fP -Create a cluster - \fBExample\fP - $ heketi-cli cluster create -.PP -.TP - -\fBheketi\-cli cluster delete \fP -Delete a cluster - \fBExample\fP - $ heketi-cli cluster delete 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli cluster info \fP -Retrieves information about cluster - - \fBExample\fP - $ heketi-cli cluster info 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli cluster list\fP -Lists the clusters managed by Heketi - - \fBExample\fP - $ heketi-cli cluster list - - -.SS "Device Commands" -.PP -.TP - -\fBheketi\-cli device add \-\-name= \-\-node=\fP -Add new device to node to be managed by Heketi -.TP -\fB Options\fP -.PP -\fB \-\-name\fP="" - Name of device to add -.PP -\fB \-\-node\fP="" - Id of the node which has this device - - \fB Example\fP - $ heketi\-cli device add \\ - \-\-name=/dev/sdb - \-\-node=3e098cb4407d7109806bb196d9e8f095 - -.PP -.TP - -\fBheketi\-cli device delete \fP -Deletes a device from Heketi node - - \fBExample\fP - $ heketi-cli device delete 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli device disable \fP -Disallow usage of a device by placing it offline - - \fBExample\fP - $ heketi\-cli device disable 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli device enable \fP -Allows device to go online - - \fBExample\fP - $ heketi\-cli device enable 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli device info \fP -Retrieves information about device - - \fBExample\fP - $ heketi-cli device info 886a86a868711bef83001 - - -.SS "Node Commands" -.PP -.TP - -\fBheketi\-cli node add \-\-zone= \-\-cluster= \-\-management\-host\-name= \-\-storage-host-name=\fP -Add new node to be managed by Heketi -.TP -\fB Options\fP -.PP -\fB \-\-cluster\fP="" - The cluster in which the node should reside -.PP -\fB \-\-management\-host\-name\fP="" - Management host name -.PP -\fB \-\-storage\-host\-name\fP="" - Storage host name -.PP -\fB \-\-zone\fP=\-1 - The zone in which the node should reside - - \fB Example\fP - $ heketi\-cli node add \\ - \-\-zone=3 \\ - \-\-cluster=3e098cb4407d7109806bb196d9e8f095 \\ - \-\-management\-host\-name=node1\-manage.gluster.lab.com \\ - \-\-storage\-host\-name=node1\-storage.gluster.lab.com - -.PP -.TP - -\fBheketi\-cli node delete \fP -Deletes a node from Heketi management - - \fBExample\fP - $ heketi-cli node delete 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli node disable \fP -Disallow usage of a node by placing it offline - - \fBExample\fP - $ heketi\-cli node disable 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli node enable \fP -Allows node to go online - - \fBExample\fP - $ heketi\-cli node enable 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli node info \fP -Retrieves information about node - - \fBExample\fP - $ heketi-cli node info 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli node list\fP -List all nodes in cluster - - \fBExample\fP - $ heketi\-cli node list - - - -.SS "Setup OpenShift/Kubernetes persistent storage for Heketi" -.PP -.TP - -\fBheketi\-cli setup\-openshift\-heketi\-storage\fP -Creates a dedicated GlusterFS volume for Heketi. -Once the volume is created, a Kubernetes/OpenShift -list object is created to configure the volume. -.TP -\fB Options\fP -.PP -\fB \-\-listfile\fP="heketi\-storage.json" - Filename to contain list of objects - - \fB Example\fP - $ heketi\-cli setup\-openshift\-heketi\-storage - -.PP - - - -.SS "Topology Commands" -.PP -.TP - -\fBheketi\-cli topology load \-\-json=\fP -Add devices to Heketi from a configuration file -.TP -\fB Options\fP -.PP -\fB \-j, \-\-json\fP="" - Configuration containing devices, nodes, and clusters, in JSON format - - \fB Example\fP - $ heketi-cli topology load --json=topo.json - -.PP -.TP - -\fBheketi\-cli topology info \fP -Retreives information about the current Topology - - \fBExample\fP - $ heketi-cli topology info - - -.SS "Volume Commands" -.PP -.TP - -\fBheketi\-cli volume create \-\-cluster= \-\-disperse-data= \-\-durability= \-\-name= \-\-redundancy= \-\-replica= \-\-size= \-\-snapshot-factor=\fP -Create a GlusterFS volume -.TP -\fB Options\fP -.PP -\fB \-\-clusters\fP="" -.PP -.RS -.nf - Optional: Comma separated list of cluster ids where this volume - must be allocated. If omitted, Heketi will allocate the volume - on any of the configured clusters which have the available space. - Providing a set of clusters will ensure Heketi allocates storage - for this volume only in the clusters specified. -.fi -.RE -.PP -\fB \-\-disperse\-data\fP=4 -.PP -.RS -.nf - Optional: Dispersion value for durability type 'disperse'. - Default is 4 -.fi -.RE -.PP -\fB \-\-durability\fP="replicate" -.PP -.RS -.nf - Optional: Durability type. - Values are: - none: No durability. Distributed volume only. - replicate: (Default) Distributed\-Replica volume. - disperse: Distributed\-Erasure Coded volume. -.fi -.RE -.PP -\fB \-\-gid\fP=0 -.PP -.RS -.nf - Optional: Initialize volume with the specified group id - Default is 0 -.fi -.RE -.PP -\fB \-\-name\fP="" -.PP -.RS -.nf - Optional: Name of volume. Only set if really necessary -.fi -.RE -.PP -\fB \-\-persistent\-volume\fP[=false] -.PP -.RS -.nf - Optional: Output to standard out a persistent volume JSON file for OpenShift or - Kubernetes with the name provided. -.fi -.RE -.PP -\fB \-\-persistent\-volume\-endpoint\fP="" -.PP -.RS -.nf - Optional: Endpoint name for the persistent volume -.fi -.RE -.PP -\fB \-\-persistent\-volume\-file\fP="" -.PP -.RS -.nf - Optional: Create a persistent volume JSON file for OpenShift or - Kubernetes with the name provided. -.fi -.RE -.PP -\fB \-\-redundancy\fP=2 -.PP -.RS -.nf - Optional: Redundancy value for durability type 'disperse'. - Default is 2 -.fi -.RE -.PP -\fB \-\-replica\fP=3 -.PP -.RS -.nf - Replica value for durability type 'replicate'. - Default is 3 -.fi -.RE -.PP -\fB \-\-size\fP=\-1 -.PP -.RS -.nf - Size of volume in GB -.fi -.RE -.PP -\fB \-\-snapshot\-factor\fP=1 -.PP -.RS -.nf - Optional: Amount of storage to allocate for snapshot support. - Must be greater 1.0. For example if a 10TiB volume requires 5TiB of - snapshot storage, then snapshot\-factor would be set to 1.5. If the - value is set to 1, then snapshots will not be enabled for this volume -.fi -.RE - - - \fB Example\fP - * Create a 100GB replica 3 volume: - $ heketi\-cli volume create \-\-size=100 - * Create a 100GB replica 3 volume specifying two specific clusters: - $ heketi\-cli volume create \-\-size=100 \\ - \-\-clusters=0995098e1284ddccb46c7752d142c832,60d46d518074b13a04ce1022c8c7193c - * Create a 100GB replica 2 volume with 50GB of snapshot storage: - $ heketi\-cli volume create \-\-size=100 \-\-snapshot\-factor=1.5 \-\-replica=2 - * Create a 100GB distributed volume - $ heketi\-cli volume create \-\-size=100 \-\-durability=none - * Create a 100GB erasure coded 4+2 volume with 25GB snapshot storage: - $ heketi\-cli volume create \-\-size=100 \-\-durability=disperse \-\-snapshot\-factor=1.25 - * Create a 100GB erasure coded 8+3 volume with 25GB snapshot storage: - $ heketi\-cli volume create \-\-size=100 \-\-durability=disperse \-\-snapshot\-factor=1.25 \\ - \-\-disperse\-data=8 \-\-redundancy=3 - - -.PP -.TP - -\fBheketi\-cli volume delete \fP -Deletes the volume - - \fBExample\fP - $ heketi-cli volume delete 886a86a868711bef83001 -.PP -.TP - -\fBheketi\-cli volume expand --expand-size= --volume=\fP -Expand a volume -.TP -\fB Options\fP -.PP -\fB \-\-expand\fP="" - Amount in GB to add to the volume -.PP -\fB \-\-volume\fP="" - Id of volume to expand - - -\fB Example\fP - * Add 10GB to a volume - $ heketi\-cli volume expand \-\-volume=60d46d518074b13a04ce1022c8c7193c \-\-expand\-size=10 - -.PP -.TP - -\fBheketi\-cli volume info \fP -Retrieves information about volume - - \fBExample\fP - $ heketi-cli volume info 886a86a868711bef83001 - -.PP -.TP - -\fBheketi\-cli volume list\fP -Lists the volumes managed by Heketi - - \fBExample\fP - $ heketi-cli volume list - -.SH GLOBAL OPTIONS -.PP -\fB\-\-json\fP[=false] -.PP -.RS -.nf -Print response as JSON -.fi -.RE -.PP -\fB\-\-secret\fP="" -.PP -.RS -.nf -Secret key for specified user. Can also be -set using the environment variable HEKETI\_CLI\_KEY -.fi -.RE -.PP -\fB\-s\fP, \fB\-\-server\fP="" -.PP -.RS -.nf -Heketi server. Can also be set using the -environment variable HEKETI\_CLI\_SERVER -.fi -.RE -.PP -\fB\-\-user\fP="" -.PP -.RS -.nf -Heketi user. Can also be set using the -environment variable HEKETI\_CLI\_USER -.fi -.RE -.PP -\fB\-v\fP, \fB\-\-version\fP[=false] -.PP -.RS -.nf -Print version -.fi -.RE -.SH EXAMPLE -.PP -.RS -.nf - $ export HEKETI\_CLI\_SERVER=http://localhost:8080 - $ heketi\-cli volume list -.fi -.RE -.SH COPYRIGHT -.nf -Copyright (c) 2015 The heketi Authors diff --git a/etc/heketi.json b/etc/heketi.json deleted file mode 100644 index af73cb80e1..0000000000 --- a/etc/heketi.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "_port_comment": "Heketi Server Port Number", - "port": "8080", - - "_use_auth": "Enable JWT authorization. Please enable for deployment", - "use_auth": false, - - "_jwt": "Private keys for access", - "jwt": { - "_admin": "Admin has access to all APIs", - "admin": { - "key": "My Secret" - }, - "_user": "User only has access to /volumes endpoint", - "user": { - "key": "My Secret" - } - }, - - "_backup_db_to_kube_secret": "Backup the heketi database to a Kubernetes secret when running in Kubernetes. Default is off.", - "backup_db_to_kube_secret": false, - - "_glusterfs_comment": "GlusterFS Configuration", - "glusterfs": { - "_executor_comment": [ - "Execute plugin. Possible choices: mock, ssh", - "mock: This setting is used for testing and development.", - " It will not send commands to any node.", - "ssh: This setting will notify Heketi to ssh to the nodes.", - " It will need the values in sshexec to be configured.", - "kubernetes: Communicate with GlusterFS containers over", - " Kubernetes exec api." - ], - "executor": "mock", - - "_sshexec_comment": "SSH username and private key file information", - "sshexec": { - "keyfile": "path/to/private_key", - "user": "sshuser", - "port": "Optional: ssh port. Default is 22", - "fstab": "Optional: Specify fstab file on node. Default is /etc/fstab" - }, - - "_kubeexec_comment": "Kubernetes configuration", - "kubeexec": { - "host" :"https://kubernetes.host:8443", - "cert" : "/path/to/crt.file", - "insecure": false, - "user": "kubernetes username", - "password": "password for kubernetes user", - "namespace": "OpenShift project or Kubernetes namespace", - "fstab": "Optional: Specify fstab file on node. Default is /etc/fstab" - }, - - "_db_comment": "Database file name", - "db": "/var/lib/heketi/heketi.db", - - "_loglevel_comment": [ - "Set log level. Choices are:", - " none, critical, error, warning, info, debug", - "Default is warning" - ], - "loglevel" : "debug" - } -} diff --git a/executors/executor.go b/executors/executor.go deleted file mode 100644 index d149731ebb..0000000000 --- a/executors/executor.go +++ /dev/null @@ -1,130 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package executors - -import "encoding/xml" - -type Executor interface { - GlusterdCheck(host string) error - PeerProbe(exec_host, newnode string) error - PeerDetach(exec_host, detachnode string) error - DeviceSetup(host, device, vgid string) (*DeviceInfo, error) - DeviceTeardown(host, device, vgid string) error - BrickCreate(host string, brick *BrickRequest) (*BrickInfo, error) - BrickDestroy(host string, brick *BrickRequest) error - BrickDestroyCheck(host string, brick *BrickRequest) error - VolumeCreate(host string, volume *VolumeRequest) (*Volume, error) - VolumeDestroy(host string, volume string) error - VolumeDestroyCheck(host, volume string) error - VolumeExpand(host string, volume *VolumeRequest) (*Volume, error) - VolumeReplaceBrick(host string, volume string, oldBrick *BrickInfo, newBrick *BrickInfo) error - VolumeInfo(host string, volume string) (*Volume, error) - SetLogLevel(level string) -} - -// Enumerate durability types -type DurabilityType int - -const ( - DurabilityNone DurabilityType = iota - DurabilityReplica - DurabilityDispersion -) - -// Returns the size of the device -type DeviceInfo struct { - // Size in KB - Size uint64 - ExtentSize uint64 -} - -// Brick description -type BrickRequest struct { - VgId string - Name string - TpSize uint64 - Size uint64 - PoolMetadataSize uint64 - Gid int64 -} - -// Returns information about the location of the brick -type BrickInfo struct { - Path string - Host string -} - -type VolumeRequest struct { - Bricks []BrickInfo - Name string - Type DurabilityType - GlusterVolumeOptions []string - - // Dispersion - Data int - Redundancy int - - // Replica - Replica int -} - -type Brick struct { - UUID string `xml:"uuid,attr"` - Name string `xml:"name"` - HostUUID string `xml:"hostUuid"` - IsArbiter int `xml:"isArbiter"` -} - -type Bricks struct { - XMLName xml.Name `xml:"bricks"` - BrickList []Brick `xml:"brick"` -} - -type Option struct { - Name string `xml:"name"` - Value string `xml:"value"` -} - -type Options struct { - XMLName xml.Name `xml:"options"` - OptionList []Option `xml:"option"` -} - -type Volume struct { - XMLName xml.Name `xml:"volume"` - VolumeName string `xml:"name"` - ID string `xml:"id"` - Status int `xml:"status"` - StatusStr string `xml:"statusStr"` - BrickCount int `xml:"brickCount"` - DistCount int `xml:"distCount"` - StripeCount int `xml:"stripeCount"` - ReplicaCount int `xml:"replicaCount"` - ArbiterCount int `xml:"arbiterCount"` - DisperseCount int `xml:"disperseCount"` - RedundancyCount int `xml:"redundancyCount"` - Type int `xml:"type"` - TypeStr string `xml:"typeStr"` - Transport int `xml:"transport"` - Bricks Bricks - OptCount int `xml:"optCount"` - Options Options -} - -type Volumes struct { - XMLName xml.Name `xml:"volumes"` - Count int `xml:"count"` - VolumeList []Volume `xml:"volume"` -} - -type VolInfo struct { - XMLName xml.Name `xml:"volInfo"` - Volumes Volumes `xml:"volumes"` -} diff --git a/executors/kubeexec/config.go b/executors/kubeexec/config.go deleted file mode 100644 index 8c5c3bdeec..0000000000 --- a/executors/kubeexec/config.go +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package kubeexec - -import ( - "github.com/heketi/heketi/executors/sshexec" -) - -type KubeConfig struct { - sshexec.CLICommandConfig - Namespace string `json:"namespace"` - GlusterDaemonSet bool `json:"gluster_daemonset"` - - // Use POD name instead of using label - // to access POD - UsePodNames bool `json:"use_pod_names"` -} diff --git a/executors/kubeexec/kubeexec.go b/executors/kubeexec/kubeexec.go deleted file mode 100644 index 7c17ed92b3..0000000000 --- a/executors/kubeexec/kubeexec.go +++ /dev/null @@ -1,320 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package kubeexec - -import ( - "bytes" - "fmt" - "os" - "strconv" - "strings" - - "k8s.io/apimachinery/pkg/apis/meta/v1" - restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api" - client "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" - coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1" - "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" - kubeletcmd "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" - - "github.com/heketi/heketi/executors/sshexec" - "github.com/heketi/heketi/pkg/kubernetes" - "github.com/heketi/heketi/pkg/utils" - "github.com/lpabon/godbc" -) - -const ( - KubeGlusterFSPodLabelKey = "glusterfs-node" -) - -type KubeExecutor struct { - // Embed all sshexecutor functions - sshexec.SshExecutor - - // save kube configuration - config *KubeConfig - namespace string - kube *client.Clientset - rest restclient.Interface - kubeConfig *restclient.Config -} - -var ( - logger = utils.NewLogger("[kubeexec]", utils.LEVEL_DEBUG) - inClusterConfig = func() (*restclient.Config, error) { - return restclient.InClusterConfig() - } -) - -func setWithEnvVariables(config *KubeConfig) { - var env string - - // Namespace / Project - env = os.Getenv("HEKETI_KUBE_NAMESPACE") - if "" != env { - config.Namespace = env - } - - // FSTAB - env = os.Getenv("HEKETI_FSTAB") - if "" != env { - config.Fstab = env - } - - // Snapshot Limit - env = os.Getenv("HEKETI_SNAPSHOT_LIMIT") - if "" != env { - i, err := strconv.Atoi(env) - if err == nil { - config.SnapShotLimit = i - } - } - - // Determine if Heketi should communicate with Gluster - // pods deployed by a DaemonSet - env = os.Getenv("HEKETI_KUBE_GLUSTER_DAEMONSET") - if "" != env { - env = strings.ToLower(env) - if env[0] == 'y' || env[0] == '1' { - config.GlusterDaemonSet = true - } else if env[0] == 'n' || env[0] == '0' { - config.GlusterDaemonSet = false - } - } - - // Use POD names - env = os.Getenv("HEKETI_KUBE_USE_POD_NAMES") - if "" != env { - env = strings.ToLower(env) - if env[0] == 'y' || env[0] == '1' { - config.UsePodNames = true - } else if env[0] == 'n' || env[0] == '0' { - config.UsePodNames = false - } - } -} - -func NewKubeExecutor(config *KubeConfig) (*KubeExecutor, error) { - // Override configuration - setWithEnvVariables(config) - - // Initialize - k := &KubeExecutor{} - k.config = config - k.Throttlemap = make(map[string]chan bool) - k.RemoteExecutor = k - - if k.config.Fstab == "" { - k.Fstab = "/etc/fstab" - } else { - k.Fstab = config.Fstab - } - - // Get namespace - var err error - if k.config.Namespace == "" { - k.config.Namespace, err = kubernetes.GetNamespace() - if err != nil { - return nil, logger.LogError("Namespace must be provided in configuration: %v", err) - } - } - k.namespace = k.config.Namespace - - // Create a Kube client configuration - k.kubeConfig, err = inClusterConfig() - if err != nil { - return nil, logger.LogError("Unable to create configuration for Kubernetes: %v", err) - } - - // Get a raw REST client. This is still needed for kube-exec - restCore, err := coreclient.NewForConfig(k.kubeConfig) - if err != nil { - return nil, logger.LogError("Unable to create a client connection: %v", err) - } - k.rest = restCore.RESTClient() - - // Get a Go-client for Kubernetes - k.kube, err = client.NewForConfig(k.kubeConfig) - if err != nil { - logger.Err(err) - return nil, fmt.Errorf("Unable to create a client set") - } - - // Show experimental settings - if k.config.RebalanceOnExpansion { - logger.Warning("Rebalance on volume expansion has been enabled. This is an EXPERIMENTAL feature") - } - - godbc.Ensure(k != nil) - godbc.Ensure(k.Fstab != "") - - return k, nil -} - -func (k *KubeExecutor) RemoteCommandExecute(host string, - commands []string, - timeoutMinutes int) ([]string, error) { - - // Throttle - k.AccessConnection(host) - defer k.FreeConnection(host) - - // Execute - return k.ConnectAndExec(host, - "pods", - commands, - timeoutMinutes) -} - -func (k *KubeExecutor) ConnectAndExec(host, resource string, - commands []string, - timeoutMinutes int) ([]string, error) { - - // Used to return command output - buffers := make([]string, len(commands)) - - // Get pod name - var ( - podName string - err error - ) - if k.config.UsePodNames { - podName = host - } else if k.config.GlusterDaemonSet { - podName, err = k.getPodNameFromDaemonSet(host) - } else { - podName, err = k.getPodNameByLabel(host) - } - if err != nil { - return nil, err - } - - // Get container name - podSpec, err := k.kube.Core().Pods(k.namespace).Get(podName, v1.GetOptions{}) - if err != nil { - return nil, logger.LogError("Unable to get pod spec for %v: %v", - podName, err) - } - containerName := podSpec.Spec.Containers[0].Name - - for index, command := range commands { - - // Remove any whitespace - command = strings.Trim(command, " ") - - // SUDO is *not* supported - - // Create REST command - req := k.rest.Post(). - Resource(resource). - Name(podName). - Namespace(k.namespace). - SubResource("exec"). - Param("container", containerName) - req.VersionedParams(&api.PodExecOptions{ - Container: containerName, - Command: []string{"/bin/bash", "-c", command}, - Stdout: true, - Stderr: true, - }, api.ParameterCodec) - - // Create SPDY connection - exec, err := remotecommand.NewExecutor(k.kubeConfig, "POST", req.URL()) - if err != nil { - logger.Err(err) - return nil, fmt.Errorf("Unable to setup a session with %v", podName) - } - - // Create a buffer to trap session output - var b bytes.Buffer - var berr bytes.Buffer - - // Excute command - err = exec.Stream(remotecommand.StreamOptions{ - SupportedProtocols: kubeletcmd.SupportedStreamingProtocols, - Stdout: &b, - Stderr: &berr, - }) - if err != nil { - logger.LogError("Failed to run command [%v] on %v: Err[%v]: Stdout [%v]: Stderr [%v]", - command, podName, err, b.String(), berr.String()) - return nil, fmt.Errorf("Unable to execute command on %v: %v", podName, berr.String()) - } - logger.Debug("Host: %v Pod: %v Command: %v\nResult: %v", host, podName, command, b.String()) - buffers[index] = b.String() - - } - - return buffers, nil -} - -func (k *KubeExecutor) RebalanceOnExpansion() bool { - return k.config.RebalanceOnExpansion -} - -func (k *KubeExecutor) SnapShotLimit() int { - return k.config.SnapShotLimit -} - -func (k *KubeExecutor) getPodNameByLabel(host string) (string, error) { - // Get a list of pods - pods, err := k.kube.Core().Pods(k.config.Namespace).List(v1.ListOptions{ - LabelSelector: KubeGlusterFSPodLabelKey + "==" + host, - }) - if err != nil { - logger.Err(err) - return "", fmt.Errorf("Failed to get list of pods") - } - - numPods := len(pods.Items) - if numPods == 0 { - // No pods found with that label - err := fmt.Errorf("No pods with the label '%v=%v' were found", - KubeGlusterFSPodLabelKey, host) - logger.Critical(err.Error()) - return "", err - - } else if numPods > 1 { - // There are more than one pod with the same label - err := fmt.Errorf("Found %v pods with the sharing the same label '%v=%v'", - numPods, KubeGlusterFSPodLabelKey, host) - logger.Critical(err.Error()) - return "", err - } - - // Get pod name - return pods.Items[0].ObjectMeta.Name, nil -} - -func (k *KubeExecutor) getPodNameFromDaemonSet(host string) (string, error) { - // Get a list of pods - pods, err := k.kube.Core().Pods(k.config.Namespace).List(v1.ListOptions{ - LabelSelector: KubeGlusterFSPodLabelKey, - }) - if err != nil { - logger.Err(err) - return "", logger.LogError("Failed to get list of pods") - } - - // Go through the pods looking for the node - var glusterPod string - for _, pod := range pods.Items { - if pod.Spec.NodeName == host { - glusterPod = pod.ObjectMeta.Name - } - } - if glusterPod == "" { - return "", logger.LogError("Unable to find a GlusterFS pod on host %v "+ - "with a label key %v", host, KubeGlusterFSPodLabelKey) - } - - // Get pod name - return glusterPod, nil -} diff --git a/executors/kubeexec/kubeexec_test.go b/executors/kubeexec/kubeexec_test.go deleted file mode 100644 index 01c4ff3370..0000000000 --- a/executors/kubeexec/kubeexec_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package kubeexec - -import ( - "os" - "testing" - - restclient "k8s.io/client-go/rest" - - "github.com/heketi/heketi/executors/sshexec" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func init() { - inClusterConfig = func() (*restclient.Config, error) { - return &restclient.Config{}, nil - } - logger.SetLevel(utils.LEVEL_NOLOG) -} - -func TestNewKubeExecutor(t *testing.T) { - config := &KubeConfig{ - CLICommandConfig: sshexec.CLICommandConfig{ - Fstab: "myfstab", - }, - Namespace: "mynamespace", - } - - k, err := NewKubeExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, k.Fstab == "myfstab") - tests.Assert(t, k.Throttlemap != nil) - tests.Assert(t, k.config != nil) -} - -func TestNewKubeExecutorNoNamespace(t *testing.T) { - config := &KubeConfig{ - CLICommandConfig: sshexec.CLICommandConfig{ - Fstab: "myfstab", - }, - } - - k, err := NewKubeExecutor(config) - tests.Assert(t, err != nil) - tests.Assert(t, k == nil) -} - -func TestNewKubeExecutorRebalanceOnExpansion(t *testing.T) { - - // This tests access to configurations - // from the sshconfig exector - - config := &KubeConfig{ - CLICommandConfig: sshexec.CLICommandConfig{ - Fstab: "myfstab", - }, - Namespace: "mynamespace", - } - - k, err := NewKubeExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, k.Fstab == "myfstab") - tests.Assert(t, k.Throttlemap != nil) - tests.Assert(t, k.config != nil) - tests.Assert(t, k.RebalanceOnExpansion() == false) - - config = &KubeConfig{ - CLICommandConfig: sshexec.CLICommandConfig{ - Fstab: "myfstab", - RebalanceOnExpansion: true, - }, - Namespace: "mynamespace", - } - - k, err = NewKubeExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, k.Fstab == "myfstab") - tests.Assert(t, k.Throttlemap != nil) - tests.Assert(t, k.config != nil) - tests.Assert(t, k.RebalanceOnExpansion() == true) -} - -func TestKubeExecutorEnvVariables(t *testing.T) { - - // set environment - err := os.Setenv("HEKETI_SNAPSHOT_LIMIT", "999") - tests.Assert(t, err == nil) - defer os.Unsetenv("HEKETI_SNAPSHOT_LIMIT") - - err = os.Setenv("HEKETI_FSTAB", "anotherfstab") - tests.Assert(t, err == nil) - defer os.Unsetenv("HEKETI_FSTAB") - - config := &KubeConfig{ - CLICommandConfig: sshexec.CLICommandConfig{ - Fstab: "myfstab", - }, - Namespace: "mynamespace", - } - - k, err := NewKubeExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, k.Throttlemap != nil) - tests.Assert(t, k.config != nil) - tests.Assert(t, k.Fstab == "anotherfstab") - tests.Assert(t, k.SnapShotLimit() == 999) - -} diff --git a/executors/mockexec/mock.go b/executors/mockexec/mock.go deleted file mode 100644 index 7da055d689..0000000000 --- a/executors/mockexec/mock.go +++ /dev/null @@ -1,173 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package mockexec - -import ( - "github.com/heketi/heketi/executors" -) - -type MockExecutor struct { - // These functions can be overwritten for testing - MockGlusterdCheck func(host string) error - MockPeerProbe func(exec_host, newnode string) error - MockPeerDetach func(exec_host, newnode string) error - MockDeviceSetup func(host, device, vgid string) (*executors.DeviceInfo, error) - MockDeviceTeardown func(host, device, vgid string) error - MockBrickCreate func(host string, brick *executors.BrickRequest) (*executors.BrickInfo, error) - MockBrickDestroy func(host string, brick *executors.BrickRequest) error - MockBrickDestroyCheck func(host string, brick *executors.BrickRequest) error - MockVolumeCreate func(host string, volume *executors.VolumeRequest) (*executors.Volume, error) - MockVolumeExpand func(host string, volume *executors.VolumeRequest) (*executors.Volume, error) - MockVolumeDestroy func(host string, volume string) error - MockVolumeDestroyCheck func(host, volume string) error - MockVolumeReplaceBrick func(host string, volume string, oldBrick *executors.BrickInfo, newBrick *executors.BrickInfo) error - MockVolumeInfo func(host string, volume string) (*executors.Volume, error) -} - -func NewMockExecutor() (*MockExecutor, error) { - m := &MockExecutor{} - - m.MockGlusterdCheck = func(host string) error { - return nil - } - - m.MockPeerProbe = func(exec_host, newnode string) error { - return nil - } - - m.MockPeerDetach = func(exec_host, newnode string) error { - return nil - } - - m.MockDeviceSetup = func(host, device, vgid string) (*executors.DeviceInfo, error) { - d := &executors.DeviceInfo{} - d.Size = 500 * 1024 * 1024 // Size in KB - d.ExtentSize = 4096 - return d, nil - } - - m.MockDeviceTeardown = func(host, device, vgid string) error { - return nil - } - - m.MockBrickCreate = func(host string, brick *executors.BrickRequest) (*executors.BrickInfo, error) { - b := &executors.BrickInfo{ - Path: "/mockpath", - } - return b, nil - } - - m.MockBrickDestroy = func(host string, brick *executors.BrickRequest) error { - return nil - } - - m.MockBrickDestroyCheck = func(host string, brick *executors.BrickRequest) error { - return nil - } - - m.MockVolumeCreate = func(host string, volume *executors.VolumeRequest) (*executors.Volume, error) { - return &executors.Volume{}, nil - } - - m.MockVolumeExpand = func(host string, volume *executors.VolumeRequest) (*executors.Volume, error) { - return &executors.Volume{}, nil - } - - m.MockVolumeDestroy = func(host string, volume string) error { - return nil - } - - m.MockVolumeDestroyCheck = func(host, volume string) error { - return nil - } - - m.MockVolumeReplaceBrick = func(host string, volume string, oldBrick *executors.BrickInfo, newBrick *executors.BrickInfo) error { - return nil - } - - m.MockVolumeInfo = func(host string, volume string) (*executors.Volume, error) { - var bricks []executors.Brick - brick := executors.Brick{Name: host + ":/mockpath"} - bricks = append(bricks, brick) - brick = executors.Brick{Name: host + ":/mockpath"} - bricks = append(bricks, brick) - brick = executors.Brick{Name: host + ":/mockpath"} - bricks = append(bricks, brick) - Bricks := executors.Bricks{ - BrickList: bricks, - } - vinfo := &executors.Volume{ - Bricks: Bricks, - } - return vinfo, nil - } - - return m, nil -} - -func (m *MockExecutor) SetLogLevel(level string) { - -} - -func (m *MockExecutor) GlusterdCheck(host string) error { - return m.MockGlusterdCheck(host) -} - -func (m *MockExecutor) PeerProbe(exec_host, newnode string) error { - return m.MockPeerProbe(exec_host, newnode) -} - -func (m *MockExecutor) PeerDetach(exec_host, newnode string) error { - return m.MockPeerDetach(exec_host, newnode) -} - -func (m *MockExecutor) DeviceSetup(host, device, vgid string) (*executors.DeviceInfo, error) { - return m.MockDeviceSetup(host, device, vgid) -} - -func (m *MockExecutor) DeviceTeardown(host, device, vgid string) error { - return m.MockDeviceTeardown(host, device, vgid) -} - -func (m *MockExecutor) BrickCreate(host string, brick *executors.BrickRequest) (*executors.BrickInfo, error) { - return m.MockBrickCreate(host, brick) -} - -func (m *MockExecutor) BrickDestroy(host string, brick *executors.BrickRequest) error { - return m.MockBrickDestroy(host, brick) -} - -func (m *MockExecutor) BrickDestroyCheck(host string, brick *executors.BrickRequest) error { - return m.MockBrickDestroyCheck(host, brick) -} - -func (m *MockExecutor) VolumeCreate(host string, volume *executors.VolumeRequest) (*executors.Volume, error) { - return m.MockVolumeCreate(host, volume) -} - -func (m *MockExecutor) VolumeExpand(host string, volume *executors.VolumeRequest) (*executors.Volume, error) { - return m.MockVolumeExpand(host, volume) -} - -func (m *MockExecutor) VolumeDestroy(host string, volume string) error { - return m.MockVolumeDestroy(host, volume) -} - -func (m *MockExecutor) VolumeDestroyCheck(host string, volume string) error { - return m.MockVolumeDestroyCheck(host, volume) -} - -func (m *MockExecutor) VolumeReplaceBrick(host string, volume string, oldBrick *executors.BrickInfo, newBrick *executors.BrickInfo) error { - return m.MockVolumeReplaceBrick(host, volume, oldBrick, newBrick) -} - -func (m *MockExecutor) VolumeInfo(host string, volume string) (*executors.Volume, error) { - return m.MockVolumeInfo(host, volume) -} diff --git a/executors/sshexec/brick.go b/executors/sshexec/brick.go deleted file mode 100644 index 14b385abe0..0000000000 --- a/executors/sshexec/brick.go +++ /dev/null @@ -1,221 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package sshexec - -import ( - "fmt" - "strings" - - "github.com/heketi/heketi/executors" - "github.com/lpabon/godbc" -) - -const ( - rootMountPoint = "/var/lib/heketi/mounts" -) - -// Return the mount point for the brick -func (s *SshExecutor) brickMountPoint(brick *executors.BrickRequest) string { - return rootMountPoint + "/" + - s.vgName(brick.VgId) + "/" + - s.brickName(brick.Name) -} - -// Device node for the lvm volume -func (s *SshExecutor) devnode(brick *executors.BrickRequest) string { - return "/dev/mapper/" + s.vgName(brick.VgId) + - "-" + s.brickName(brick.Name) -} - -func (s *SshExecutor) BrickCreate(host string, - brick *executors.BrickRequest) (*executors.BrickInfo, error) { - - godbc.Require(brick != nil) - godbc.Require(host != "") - godbc.Require(brick.Name != "") - godbc.Require(brick.Size > 0) - godbc.Require(brick.TpSize >= brick.Size) - godbc.Require(brick.VgId != "") - godbc.Require(s.Fstab != "") - - // Create mountpoint name - mountpoint := s.brickMountPoint(brick) - - // Create command set to execute on the node - commands := []string{ - - // Create a directory - fmt.Sprintf("mkdir -p %v", mountpoint), - - // Setup the LV - fmt.Sprintf("lvcreate --poolmetadatasize %vK -c 256K -L %vK -T %v/%v -V %vK -n %v", - // MetadataSize - brick.PoolMetadataSize, - - //Thin Pool Size - brick.TpSize, - - // volume group - s.vgName(brick.VgId), - - // ThinP name - s.tpName(brick.Name), - - // Allocation size - brick.Size, - - // Logical Vol name - s.brickName(brick.Name)), - - // Format - fmt.Sprintf("mkfs.xfs -i size=512 -n size=8192 %v", s.devnode(brick)), - - // Fstab - fmt.Sprintf("echo \"%v %v xfs rw,inode64,noatime,nouuid 1 2\" | tee -a %v > /dev/null ", - s.devnode(brick), - mountpoint, - s.Fstab), - - // Mount - fmt.Sprintf("mount -o rw,inode64,noatime,nouuid %v %v", s.devnode(brick), mountpoint), - - // Create a directory inside the formated volume for GlusterFS - fmt.Sprintf("mkdir %v/brick", mountpoint), - } - - // Only set the GID if the value is other than root(gid 0). - // When no gid is set, root is the only one that can write to the volume - if 0 != brick.Gid { - commands = append(commands, []string{ - // Set GID on brick - fmt.Sprintf("chown :%v %v/brick", brick.Gid, mountpoint), - - // Set writable by GID and UID - fmt.Sprintf("chmod 2775 %v/brick", mountpoint), - }...) - } - - // Execute commands - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - // Cleanup - s.BrickDestroy(host, brick) - return nil, err - } - - // Save brick location - b := &executors.BrickInfo{ - Path: fmt.Sprintf("%v/brick", mountpoint), - } - return b, nil -} - -func (s *SshExecutor) BrickDestroy(host string, - brick *executors.BrickRequest) error { - - godbc.Require(brick != nil) - godbc.Require(host != "") - godbc.Require(brick.Name != "") - godbc.Require(brick.VgId != "") - - // Try to unmount first - commands := []string{ - fmt.Sprintf("umount %v", s.brickMountPoint(brick)), - } - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - logger.Err(err) - } - - // Now try to remove the LV - commands = []string{ - fmt.Sprintf("lvremove -f %v/%v", s.vgName(brick.VgId), s.tpName(brick.Name)), - } - _, err = s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - logger.Err(err) - } - - // Now cleanup the mount point - commands = []string{ - fmt.Sprintf("rmdir %v", s.brickMountPoint(brick)), - } - _, err = s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - logger.Err(err) - } - - // Remove from fstab - commands = []string{ - fmt.Sprintf("sed -i.save \"/%v/d\" %v", - s.brickName(brick.Name), - s.Fstab), - } - _, err = s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - logger.Err(err) - } - - return nil -} - -func (s *SshExecutor) BrickDestroyCheck(host string, - brick *executors.BrickRequest) error { - godbc.Require(brick != nil) - godbc.Require(host != "") - godbc.Require(brick.Name != "") - godbc.Require(brick.VgId != "") - - err := s.checkThinPoolUsage(host, brick) - if err != nil { - return err - } - - return nil -} - -// Determine if any other logical volumes are using the thin pool. -// If they are, then either a clone volume or a snapshot is using that storage, -// and we cannot delete the brick. -func (s *SshExecutor) checkThinPoolUsage(host string, - brick *executors.BrickRequest) error { - - // Sample output: - // # lvs --options=lv_name,thin_count --separator=: | grep "tp_" - // tp_a17c621ade79017b48cc0042bea86510:2 - // tp_8d4e0849a5c90608a543928961bd2387:1 - // tp_3b9b3e07f06b93d94006ef272d3c10eb:2 - - tp := s.tpName(brick.Name) - commands := []string{ - fmt.Sprintf("lvs --options=lv_name,thin_count --separator=:"), - } - - // Send command - output, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - logger.Err(err) - return fmt.Errorf("Unable to determine number of logical volumes in "+ - "thin pool %v on host %v", tp, host) - } - - // Determine if do not have only one LV in the thin pool, - // we cannot delete the brick - lvs := strings.Index(output[0], tp+":1") - if lvs == -1 { - return fmt.Errorf("Cannot delete thin pool %v on %v because it "+ - "is used by [%v] snapshot(s) or cloned volume(s)", - tp, - host, - lvs) - } - - return nil -} diff --git a/executors/sshexec/brick_test.go b/executors/sshexec/brick_test.go deleted file mode 100644 index 85f42383a0..0000000000 --- a/executors/sshexec/brick_test.go +++ /dev/null @@ -1,357 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package sshexec - -import ( - "strings" - "testing" - - "github.com/heketi/heketi/executors" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func TestSshExecBrickCreate(t *testing.T) { - - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - Port: "100", - CLICommandConfig: CLICommandConfig{ - Fstab: "/my/fstab", - }, - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - - // Create a Brick - b := &executors.BrickRequest{ - VgId: "xvgid", - Name: "id", - TpSize: 100, - Size: 10, - PoolMetadataSize: 5, - } - - // Mock ssh function - f.FakeConnectAndExec = func(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) { - - tests.Assert(t, host == "myhost:100", host) - tests.Assert(t, len(commands) == 6) - - for i, cmd := range commands { - cmd = strings.Trim(cmd, " ") - switch i { - case 0: - tests.Assert(t, - cmd == "mkdir -p /var/lib/heketi/mounts/vg_xvgid/brick_id", cmd) - - case 1: - tests.Assert(t, - cmd == "lvcreate --poolmetadatasize 5K "+ - "-c 256K -L 100K -T vg_xvgid/tp_id -V 10K -n brick_id", cmd) - - case 2: - tests.Assert(t, - cmd == "mkfs.xfs -i size=512 "+ - "-n size=8192 /dev/mapper/vg_xvgid-brick_id", cmd) - - case 3: - tests.Assert(t, - cmd == "echo \"/dev/mapper/vg_xvgid-brick_id "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id "+ - "xfs rw,inode64,noatime,nouuid 1 2\" | "+ - "tee -a /my/fstab > /dev/null", cmd) - - case 4: - tests.Assert(t, - cmd == "mount -o rw,inode64,noatime,nouuid "+ - "/dev/mapper/vg_xvgid-brick_id "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id", cmd) - - case 5: - tests.Assert(t, - cmd == "mkdir "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id/brick", cmd) - } - } - - return nil, nil - } - - // Create Brick - _, err = s.BrickCreate("myhost", b) - tests.Assert(t, err == nil, err) - -} - -func TestSshExecBrickCreateWithGid(t *testing.T) { - - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - Port: "100", - CLICommandConfig: CLICommandConfig{ - Fstab: "/my/fstab", - }, - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - - // Create a Brick - b := &executors.BrickRequest{ - VgId: "xvgid", - Name: "id", - TpSize: 100, - Size: 10, - PoolMetadataSize: 5, - Gid: 1234, - } - - // Mock ssh function - f.FakeConnectAndExec = func(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) { - - tests.Assert(t, host == "myhost:100", host) - tests.Assert(t, len(commands) == 8) - - for i, cmd := range commands { - cmd = strings.Trim(cmd, " ") - switch i { - case 0: - tests.Assert(t, - cmd == "mkdir -p /var/lib/heketi/mounts/vg_xvgid/brick_id", cmd) - - case 1: - tests.Assert(t, - cmd == "lvcreate --poolmetadatasize 5K "+ - "-c 256K -L 100K -T vg_xvgid/tp_id -V 10K -n brick_id", cmd) - - case 2: - tests.Assert(t, - cmd == "mkfs.xfs -i size=512 "+ - "-n size=8192 /dev/mapper/vg_xvgid-brick_id", cmd) - - case 3: - tests.Assert(t, - cmd == "echo \"/dev/mapper/vg_xvgid-brick_id "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id "+ - "xfs rw,inode64,noatime,nouuid 1 2\" | "+ - "tee -a /my/fstab > /dev/null", cmd) - - case 4: - tests.Assert(t, - cmd == "mount -o rw,inode64,noatime,nouuid "+ - "/dev/mapper/vg_xvgid-brick_id "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id", cmd) - - case 5: - tests.Assert(t, - cmd == "mkdir "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id/brick", cmd) - - case 6: - tests.Assert(t, - cmd == "chown :1234 "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id/brick", cmd) - - case 7: - tests.Assert(t, - cmd == "chmod 2775 "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id/brick", cmd) - } - } - - return nil, nil - } - - // Create Brick - _, err = s.BrickCreate("myhost", b) - tests.Assert(t, err == nil, err) - -} - -func TestSshExecBrickCreateSudo(t *testing.T) { - - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - Port: "100", - CLICommandConfig: CLICommandConfig{ - Fstab: "/my/fstab", - Sudo: true, - }, - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - - // Create a Brick - b := &executors.BrickRequest{ - VgId: "xvgid", - Name: "id", - TpSize: 100, - Size: 10, - PoolMetadataSize: 5, - } - - // Mock ssh function - f.FakeConnectAndExec = func(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) { - - tests.Assert(t, host == "myhost:100", host) - tests.Assert(t, len(commands) == 6) - tests.Assert(t, useSudo == true) - - for i, cmd := range commands { - cmd = strings.Trim(cmd, " ") - switch i { - case 0: - tests.Assert(t, - cmd == "mkdir -p /var/lib/heketi/mounts/vg_xvgid/brick_id", cmd) - - case 1: - tests.Assert(t, - cmd == "lvcreate --poolmetadatasize 5K "+ - "-c 256K -L 100K -T vg_xvgid/tp_id -V 10K -n brick_id", cmd) - - case 2: - tests.Assert(t, - cmd == "mkfs.xfs -i size=512 "+ - "-n size=8192 /dev/mapper/vg_xvgid-brick_id", cmd) - - case 3: - tests.Assert(t, - cmd == "echo \"/dev/mapper/vg_xvgid-brick_id "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id "+ - "xfs rw,inode64,noatime,nouuid 1 2\" | "+ - "tee -a /my/fstab > /dev/null", cmd) - - case 4: - tests.Assert(t, - cmd == "mount -o rw,inode64,noatime,nouuid "+ - "/dev/mapper/vg_xvgid-brick_id "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id", cmd) - - case 5: - tests.Assert(t, - cmd == "mkdir "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id/brick", cmd) - } - } - - return nil, nil - } - - // Create Brick - _, err = s.BrickCreate("myhost", b) - tests.Assert(t, err == nil, err) - -} - -func TestSshExecBrickDestroy(t *testing.T) { - - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - Port: "100", - CLICommandConfig: CLICommandConfig{ - Fstab: "/my/fstab", - }, - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - - // Create a Brick - b := &executors.BrickRequest{ - VgId: "xvgid", - Name: "id", - TpSize: 100, - Size: 10, - PoolMetadataSize: 5, - } - - // Mock ssh function - f.FakeConnectAndExec = func(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) { - - tests.Assert(t, host == "myhost:100", host) - - for _, cmd := range commands { - cmd = strings.Trim(cmd, " ") - switch { - case strings.Contains(cmd, "umount"): - tests.Assert(t, - cmd == "umount "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id", cmd) - - case strings.Contains(cmd, "lvremove"): - tests.Assert(t, - cmd == "lvremove -f vg_xvgid/tp_id", cmd) - - case strings.Contains(cmd, "rmdir"): - tests.Assert(t, - cmd == "rmdir "+ - "/var/lib/heketi/mounts/vg_xvgid/brick_id", cmd) - - case strings.Contains(cmd, "sed"): - tests.Assert(t, - cmd == "sed -i.save "+ - "\"/brick_id/d\" /my/fstab", cmd) - } - } - - return nil, nil - } - - // Create Brick - err = s.BrickDestroy("myhost", b) - tests.Assert(t, err == nil, err) -} diff --git a/executors/sshexec/config.go b/executors/sshexec/config.go deleted file mode 100644 index 602f7335ca..0000000000 --- a/executors/sshexec/config.go +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package sshexec - -type CLICommandConfig struct { - Fstab string `json:"fstab"` - Sudo bool `json:"sudo"` - SnapShotLimit int `json:"snapshot_limit"` - - // Experimental Settings - RebalanceOnExpansion bool `json:"rebalance_on_expansion"` -} - -type SshConfig struct { - CLICommandConfig - PrivateKeyFile string `json:"keyfile"` - User string `json:"user"` - Port string `json:"port"` -} diff --git a/executors/sshexec/device.go b/executors/sshexec/device.go deleted file mode 100644 index f0a44229f0..0000000000 --- a/executors/sshexec/device.go +++ /dev/null @@ -1,139 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package sshexec - -import ( - "errors" - "fmt" - "github.com/heketi/heketi/executors" - "strconv" - "strings" -) - -const ( - VGDISPLAY_SIZE_KB = 11 - VGDISPLAY_PHYSICAL_EXTENT_SIZE = 12 - VGDISPLAY_TOTAL_NUMBER_EXTENTS = 13 - VGDISPLAY_ALLOCATED_NUMBER_EXTENTS = 14 - VGDISPLAY_FREE_NUMBER_EXTENTS = 15 -) - -// Read: -// https://access.redhat.com/documentation/en-US/Red_Hat_Storage/3.1/html/Administration_Guide/Brick_Configuration.html -// - -func (s *SshExecutor) DeviceSetup(host, device, vgid string) (d *executors.DeviceInfo, e error) { - - // Setup commands - commands := []string{ - fmt.Sprintf("pvcreate --metadatasize=128M --dataalignment=256K %v", device), - fmt.Sprintf("vgcreate %v %v", s.vgName(vgid), device), - } - - // Execute command - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - return nil, err - } - - // Create a cleanup function if anything fails - defer func() { - if e != nil { - s.DeviceTeardown(host, device, vgid) - } - }() - - // Vg info - d = &executors.DeviceInfo{} - err = s.getVgSizeFromNode(d, host, device, vgid) - if err != nil { - return nil, err - } - - return d, nil -} - -func (s *SshExecutor) DeviceTeardown(host, device, vgid string) error { - - // Setup commands - commands := []string{ - fmt.Sprintf("vgremove %v", s.vgName(vgid)), - fmt.Sprintf("pvremove %v", device), - } - - // Execute command - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - logger.LogError("Error while deleting device %v on %v with id %v", - device, host, vgid) - } - - commands = []string{ - fmt.Sprintf("ls %v/%v", rootMountPoint, s.vgName(vgid)), - } - _, err = s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - return nil - } - - commands = []string{ - fmt.Sprintf("rmdir %v/%v", rootMountPoint, s.vgName(vgid)), - } - - _, err = s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - logger.LogError("Error while removing the VG directory") - return nil - } - - return nil -} - -func (s *SshExecutor) getVgSizeFromNode( - d *executors.DeviceInfo, - host, device, vgid string) error { - - // Setup command - commands := []string{ - fmt.Sprintf("vgdisplay -c %v", s.vgName(vgid)), - } - - // Execute command - b, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 5) - if err != nil { - return err - } - - // Example: - // sampleVg:r/w:772:-1:0:0:0:-1:0:4:4:2097135616:4096:511996:0:511996:rJ0bIG-3XNc-NoS0-fkKm-batK-dFyX-xbxHym - vginfo := strings.Split(b[0], ":") - - // See vgdisplay manpage - if len(vginfo) < 17 { - return errors.New("vgdisplay returned an invalid string") - } - - extent_size, err := - strconv.ParseUint(vginfo[VGDISPLAY_PHYSICAL_EXTENT_SIZE], 10, 64) - if err != nil { - return err - } - - free_extents, err := - strconv.ParseUint(vginfo[VGDISPLAY_FREE_NUMBER_EXTENTS], 10, 64) - if err != nil { - return err - } - - d.Size = free_extents * extent_size - d.ExtentSize = extent_size - logger.Debug("Size of %v in %v is %v", device, host, d.Size) - return nil -} diff --git a/executors/sshexec/peer.go b/executors/sshexec/peer.go deleted file mode 100644 index fc161b5436..0000000000 --- a/executors/sshexec/peer.go +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package sshexec - -import ( - "fmt" - - "github.com/lpabon/godbc" -) - -// :TODO: Rename this function to NodeInit or something -func (s *SshExecutor) PeerProbe(host, newnode string) error { - - godbc.Require(host != "") - godbc.Require(newnode != "") - - logger.Info("Probing: %v -> %v", host, newnode) - // create the commands - commands := []string{ - fmt.Sprintf("gluster peer probe %v", newnode), - } - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - return err - } - - // Determine if there is a snapshot limit configuration setting - if s.RemoteExecutor.SnapShotLimit() > 0 { - logger.Info("Setting snapshot limit") - commands = []string{ - fmt.Sprintf("gluster --mode=script snapshot config snap-max-hard-limit %v", - s.RemoteExecutor.SnapShotLimit()), - } - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - return err - } - } - - return nil -} - -func (s *SshExecutor) PeerDetach(host, detachnode string) error { - godbc.Require(host != "") - godbc.Require(detachnode != "") - - // create the commands - logger.Info("Detaching node %v", detachnode) - commands := []string{ - fmt.Sprintf("gluster peer detach %v", detachnode), - } - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - logger.Err(err) - } - - return nil -} - -func (s *SshExecutor) GlusterdCheck(host string) error { - godbc.Require(host != "") - - logger.Info("Check Glusterd service status in node %v", host) - commands := []string{ - fmt.Sprintf("systemctl status glusterd"), - } - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - logger.Err(err) - return err - } - - return nil -} diff --git a/executors/sshexec/peer_test.go b/executors/sshexec/peer_test.go deleted file mode 100644 index 67e891ee28..0000000000 --- a/executors/sshexec/peer_test.go +++ /dev/null @@ -1,138 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package sshexec - -import ( - "testing" - - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -func TestSshExecPeerProbe(t *testing.T) { - - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - CLICommandConfig: CLICommandConfig{ - Fstab: "/my/fstab", - }, - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - - // Mock ssh function - f.FakeConnectAndExec = func(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) { - - tests.Assert(t, host == "host:22", host) - tests.Assert(t, len(commands) == 1) - tests.Assert(t, commands[0] == "gluster peer probe newnode", commands) - - return nil, nil - } - - // Call function - err = s.PeerProbe("host", "newnode") - tests.Assert(t, err == nil, err) - - // Now set the snapshot limit - config = &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - CLICommandConfig: CLICommandConfig{ - Fstab: "/my/fstab", - SnapShotLimit: 14, - }, - } - - s, err = NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - - // Mock ssh function - count := 0 - f.FakeConnectAndExec = func(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) { - - switch count { - case 0: - tests.Assert(t, host == "host:22", host) - tests.Assert(t, len(commands) == 1) - tests.Assert(t, commands[0] == "gluster peer probe newnode", commands) - - case 1: - tests.Assert(t, host == "host:22", host) - tests.Assert(t, len(commands) == 1) - tests.Assert(t, commands[0] == "gluster --mode=script snapshot config snap-max-hard-limit 14", commands) - - default: - tests.Assert(t, false, "Should not be reached") - } - count++ - - return nil, nil - } - - // Call function - err = s.PeerProbe("host", "newnode") - tests.Assert(t, err == nil, err) - tests.Assert(t, count == 2) - -} - -func TestSshExecGlusterdCheck(t *testing.T) { - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - CLICommandConfig: CLICommandConfig{ - Fstab: "/my/fstab", - }, - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - - // Mock ssh function - f.FakeConnectAndExec = func(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) { - - tests.Assert(t, host == "newhost:22", host) - tests.Assert(t, len(commands) == 1) - tests.Assert(t, commands[0] == "systemctl status glusterd", commands) - - return nil, nil - } - - // Call function - err = s.GlusterdCheck("newhost") - tests.Assert(t, err == nil, err) -} diff --git a/executors/sshexec/sshexec.go b/executors/sshexec/sshexec.go deleted file mode 100644 index 7eb452edb6..0000000000 --- a/executors/sshexec/sshexec.go +++ /dev/null @@ -1,224 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package sshexec - -import ( - "errors" - "fmt" - "os" - "strconv" - "sync" - - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/heketi/pkg/utils/ssh" - "github.com/lpabon/godbc" -) - -type RemoteCommandTransport interface { - RemoteCommandExecute(host string, commands []string, timeoutMinutes int) ([]string, error) - RebalanceOnExpansion() bool - SnapShotLimit() int -} - -type Ssher interface { - ConnectAndExec(host string, commands []string, timeoutMinutes int, useSudo bool) ([]string, error) -} - -type SshExecutor struct { - // "Public" - Throttlemap map[string]chan bool - Lock sync.Mutex - RemoteExecutor RemoteCommandTransport - Fstab string - - // Private - private_keyfile string - user string - exec Ssher - config *SshConfig - port string -} - -var ( - logger = utils.NewLogger("[sshexec]", utils.LEVEL_DEBUG) - ErrSshPrivateKey = errors.New("Unable to read private key file") - sshNew = func(logger *utils.Logger, user string, file string) (Ssher, error) { - s := ssh.NewSshExecWithKeyFile(logger, user, file) - if s == nil { - return nil, ErrSshPrivateKey - } - return s, nil - } -) - -func setWithEnvVariables(config *SshConfig) { - var env string - - env = os.Getenv("HEKETI_SSH_KEYFILE") - if "" != env { - config.PrivateKeyFile = env - } - - env = os.Getenv("HEKETI_SSH_USER") - if "" != env { - config.User = env - } - - env = os.Getenv("HEKETI_SSH_PORT") - if "" != env { - config.Port = env - } - - env = os.Getenv("HEKETI_FSTAB") - if "" != env { - config.Fstab = env - } - - env = os.Getenv("HEKETI_SNAPSHOT_LIMIT") - if "" != env { - i, err := strconv.Atoi(env) - if err == nil { - config.SnapShotLimit = i - } - } - -} - -func NewSshExecutor(config *SshConfig) (*SshExecutor, error) { - // Override configuration - setWithEnvVariables(config) - - s := &SshExecutor{} - s.RemoteExecutor = s - s.Throttlemap = make(map[string]chan bool) - - // Set configuration - if config.PrivateKeyFile == "" { - return nil, fmt.Errorf("Missing ssh private key file in configuration") - } - s.private_keyfile = config.PrivateKeyFile - - if config.User == "" { - s.user = "heketi" - } else { - s.user = config.User - } - - if config.Port == "" { - s.port = "22" - } else { - s.port = config.Port - } - - if config.Fstab == "" { - s.Fstab = "/etc/fstab" - } else { - s.Fstab = config.Fstab - } - - // Save the configuration - s.config = config - - // Show experimental settings - if s.config.RebalanceOnExpansion { - logger.Warning("Rebalance on volume expansion has been enabled. This is an EXPERIMENTAL feature") - } - - // Setup key - var err error - s.exec, err = sshNew(logger, s.user, s.private_keyfile) - if err != nil { - logger.Err(err) - return nil, err - } - - godbc.Ensure(s != nil) - godbc.Ensure(s.config == config) - godbc.Ensure(s.user != "") - godbc.Ensure(s.private_keyfile != "") - godbc.Ensure(s.port != "") - godbc.Ensure(s.Fstab != "") - - return s, nil -} - -func (s *SshExecutor) SetLogLevel(level string) { - switch level { - case "none": - logger.SetLevel(utils.LEVEL_NOLOG) - case "critical": - logger.SetLevel(utils.LEVEL_CRITICAL) - case "error": - logger.SetLevel(utils.LEVEL_ERROR) - case "warning": - logger.SetLevel(utils.LEVEL_WARNING) - case "info": - logger.SetLevel(utils.LEVEL_INFO) - case "debug": - logger.SetLevel(utils.LEVEL_DEBUG) - } -} - -func (s *SshExecutor) AccessConnection(host string) { - - var ( - c chan bool - ok bool - ) - - s.Lock.Lock() - if c, ok = s.Throttlemap[host]; !ok { - c = make(chan bool, 1) - s.Throttlemap[host] = c - } - s.Lock.Unlock() - - c <- true -} - -func (s *SshExecutor) FreeConnection(host string) { - s.Lock.Lock() - c := s.Throttlemap[host] - s.Lock.Unlock() - - <-c -} - -func (s *SshExecutor) RemoteCommandExecute(host string, - commands []string, - timeoutMinutes int) ([]string, error) { - - // Throttle - s.AccessConnection(host) - defer s.FreeConnection(host) - - // Execute - return s.exec.ConnectAndExec(host+":"+s.port, commands, timeoutMinutes, s.config.Sudo) -} - -func (s *SshExecutor) vgName(vgId string) string { - return "vg_" + vgId -} - -func (s *SshExecutor) brickName(brickId string) string { - return "brick_" + brickId -} - -func (s *SshExecutor) tpName(brickId string) string { - return "tp_" + brickId -} - -func (s *SshExecutor) RebalanceOnExpansion() bool { - return s.config.RebalanceOnExpansion -} - -func (s *SshExecutor) SnapShotLimit() int { - return s.config.SnapShotLimit -} diff --git a/executors/sshexec/sshexec_test.go b/executors/sshexec/sshexec_test.go deleted file mode 100644 index 7a079c6234..0000000000 --- a/executors/sshexec/sshexec_test.go +++ /dev/null @@ -1,205 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package sshexec - -import ( - "os" - "testing" - - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" -) - -// Mock SSH calls -type FakeSsh struct { - FakeConnectAndExec func(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) -} - -func NewFakeSsh() *FakeSsh { - f := &FakeSsh{} - - f.FakeConnectAndExec = func(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) { - return []string{""}, nil - } - - return f -} - -func (f *FakeSsh) ConnectAndExec(host string, - commands []string, - timeoutMinutes int, - useSudo bool) ([]string, error) { - return f.FakeConnectAndExec(host, commands, timeoutMinutes, useSudo) - -} - -func TestNewSshExec(t *testing.T) { - - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - Port: "100", - CLICommandConfig: CLICommandConfig{ - Fstab: "xfstab", - }, - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - tests.Assert(t, s.private_keyfile == config.PrivateKeyFile) - tests.Assert(t, s.user == config.User) - tests.Assert(t, s.port == config.Port) - tests.Assert(t, s.Fstab == config.Fstab) - tests.Assert(t, s.exec != nil) -} - -func TestSshExecRebalanceOnExpansion(t *testing.T) { - - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - Port: "100", - CLICommandConfig: CLICommandConfig{ - Fstab: "xfstab", - }, - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - tests.Assert(t, s.private_keyfile == config.PrivateKeyFile) - tests.Assert(t, s.user == config.User) - tests.Assert(t, s.port == config.Port) - tests.Assert(t, s.Fstab == config.Fstab) - tests.Assert(t, s.exec != nil) - tests.Assert(t, s.RebalanceOnExpansion() == false) - - config = &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - Port: "100", - CLICommandConfig: CLICommandConfig{ - Fstab: "xfstab", - RebalanceOnExpansion: true, - }, - } - - s, err = NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - tests.Assert(t, s.private_keyfile == config.PrivateKeyFile) - tests.Assert(t, s.user == config.User) - tests.Assert(t, s.port == config.Port) - tests.Assert(t, s.Fstab == config.Fstab) - tests.Assert(t, s.exec != nil) - tests.Assert(t, s.RebalanceOnExpansion() == true) - -} - -func TestNewSshExecDefaults(t *testing.T) { - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - tests.Assert(t, s.private_keyfile == "xkeyfile") - tests.Assert(t, s.user == "heketi") - tests.Assert(t, s.port == "22") - tests.Assert(t, s.Fstab == "/etc/fstab") - tests.Assert(t, s.exec != nil) - -} - -func TestNewSshExecBadPrivateKeyLocation(t *testing.T) { - config := &SshConfig{} - - s, err := NewSshExecutor(config) - tests.Assert(t, s == nil) - tests.Assert(t, err != nil) -} - -func TestSshExecutorEnvVariables(t *testing.T) { - - f := NewFakeSsh() - defer tests.Patch(&sshNew, - func(logger *utils.Logger, user string, file string) (Ssher, error) { - return f, nil - }).Restore() - - // set environment - err := os.Setenv("HEKETI_SNAPSHOT_LIMIT", "999") - tests.Assert(t, err == nil) - defer os.Unsetenv("HEKETI_SNAPSHOT_LIMIT") - - err = os.Setenv("HEKETI_FSTAB", "anotherfstab") - tests.Assert(t, err == nil) - defer os.Unsetenv("HEKETI_FSTAB") - - err = os.Setenv("HEKETI_SSH_KEYFILE", "ykeyfile") - tests.Assert(t, err == nil) - defer os.Unsetenv("HEKETI_SSH_KEYFILE") - - err = os.Setenv("HEKETI_SSH_USER", "yuser") - tests.Assert(t, err == nil) - defer os.Unsetenv("HEKETI_SSH_USER") - - err = os.Setenv("HEKETI_SSH_PORT", "33") - tests.Assert(t, err == nil) - defer os.Unsetenv("HEKETI_SSH_PORT") - - config := &SshConfig{ - PrivateKeyFile: "xkeyfile", - User: "xuser", - Port: "100", - CLICommandConfig: CLICommandConfig{ - Fstab: "xfstab", - }, - } - - s, err := NewSshExecutor(config) - tests.Assert(t, err == nil) - tests.Assert(t, s != nil) - tests.Assert(t, s.Throttlemap != nil) - tests.Assert(t, s.config != nil) - tests.Assert(t, s.Fstab == "anotherfstab") - tests.Assert(t, s.SnapShotLimit() == 999) - tests.Assert(t, s.private_keyfile == "ykeyfile") - tests.Assert(t, s.user == "yuser") - tests.Assert(t, s.port == "33") - tests.Assert(t, s.exec != nil) - -} diff --git a/executors/sshexec/volume.go b/executors/sshexec/volume.go deleted file mode 100644 index 19d85ef7c0..0000000000 --- a/executors/sshexec/volume.go +++ /dev/null @@ -1,284 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package sshexec - -import ( - "encoding/xml" - "fmt" - - "github.com/heketi/heketi/executors" - "github.com/lpabon/godbc" -) - -func (s *SshExecutor) VolumeCreate(host string, - volume *executors.VolumeRequest) (*executors.Volume, error) { - - godbc.Require(volume != nil) - godbc.Require(host != "") - godbc.Require(len(volume.Bricks) > 0) - godbc.Require(volume.Name != "") - - cmd := fmt.Sprintf("gluster --mode=script volume create %v ", volume.Name) - - var ( - inSet int - maxPerSet int - ) - switch volume.Type { - case executors.DurabilityNone: - logger.Info("Creating volume %v with no durability", volume.Name) - inSet = 1 - maxPerSet = 15 - case executors.DurabilityReplica: - logger.Info("Creating volume %v replica %v", volume.Name, volume.Replica) - cmd += fmt.Sprintf("replica %v ", volume.Replica) - inSet = volume.Replica - maxPerSet = 5 - case executors.DurabilityDispersion: - logger.Info("Creating volume %v dispersion %v+%v", - volume.Name, volume.Data, volume.Redundancy) - cmd += fmt.Sprintf("disperse-data %v redundancy %v ", volume.Data, volume.Redundancy) - inSet = volume.Data + volume.Redundancy - maxPerSet = 1 - } - - // There could many, many bricks, which could render a single command - // line that creates the volume with all the bricks too long. - // Therefore, we initially create the volume with the first brick set - // only, and then add each brick set in one subsequent command. - - for _, brick := range volume.Bricks[:inSet] { - cmd += fmt.Sprintf("%v:%v ", brick.Host, brick.Path) - } - - commands := []string{cmd} - - commands = append(commands, s.createAddBrickCommands(volume, inSet, inSet, maxPerSet)...) - - commands = append(commands, s.createVolumeOptionsCommand(volume)...) - - commands = append(commands, fmt.Sprintf("gluster --mode=script volume start %v", volume.Name)) - - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - s.VolumeDestroy(host, volume.Name) - return nil, err - } - - return &executors.Volume{}, nil -} - -func (s *SshExecutor) VolumeExpand(host string, - volume *executors.VolumeRequest) (*executors.Volume, error) { - - godbc.Require(volume != nil) - godbc.Require(host != "") - godbc.Require(len(volume.Bricks) > 0) - godbc.Require(volume.Name != "") - - var ( - inSet int - maxPerSet int - ) - switch volume.Type { - case executors.DurabilityNone: - inSet = 1 - maxPerSet = 15 - case executors.DurabilityReplica: - inSet = volume.Replica - maxPerSet = 5 - case executors.DurabilityDispersion: - inSet = volume.Data + volume.Redundancy - maxPerSet = 1 - } - - commands := s.createAddBrickCommands(volume, - 0, // start at the beginning of the brick list - inSet, - maxPerSet) - - if s.RemoteExecutor.RebalanceOnExpansion() { - commands = append(commands, - fmt.Sprintf("gluster --mode=script volume rebalance %v start", volume.Name)) - } - - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - return nil, err - } - - return &executors.Volume{}, nil -} - -func (s *SshExecutor) VolumeDestroy(host string, volume string) error { - godbc.Require(host != "") - godbc.Require(volume != "") - - // First stop the volume, then delete it - - commands := []string{ - fmt.Sprintf("gluster --mode=script volume stop %v force", volume), - } - - _, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - logger.LogError("Unable to stop volume %v: %v", volume, err) - } - - commands = []string{ - fmt.Sprintf("gluster --mode=script volume delete %v", volume), - } - - _, err = s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - return logger.Err(fmt.Errorf("Unable to delete volume %v: %v", volume, err)) - } - - return nil -} - -func (s *SshExecutor) VolumeDestroyCheck(host, volume string) error { - godbc.Require(host != "") - godbc.Require(volume != "") - - // Determine if the volume is able to be deleted - err := s.checkForSnapshots(host, volume) - if err != nil { - return err - } - - return nil -} - -func (s *SshExecutor) createVolumeOptionsCommand(volume *executors.VolumeRequest) []string { - godbc.Require(len(volume.GlusterVolumeOptions) > 0) - - commands := []string{} - var cmd string - - // Go through all the Options and create volume set command - for _, volOption := range volume.GlusterVolumeOptions { - if volOption != "" { - cmd = fmt.Sprintf("gluster --mode=script volume set %v %v", volume.Name, volOption) - commands = append(commands, cmd) - } - - } - return commands -} - -func (s *SshExecutor) createAddBrickCommands(volume *executors.VolumeRequest, - start, inSet, maxPerSet int) []string { - - commands := []string{} - var cmd string - - // Go through all the bricks and create add-brick commands - for index, brick := range volume.Bricks[start:] { - if index%(inSet*maxPerSet) == 0 { - if cmd != "" { - // Add add-brick command to the command list - commands = append(commands, cmd) - } - - // Create a new add-brick command - cmd = fmt.Sprintf("gluster --mode=script volume add-brick %v ", volume.Name) - } - - // Add this brick to the add-brick command - cmd += fmt.Sprintf("%v:%v ", brick.Host, brick.Path) - } - - // Add the last add-brick command to the command list - commands = append(commands, cmd) - - return commands -} - -func (s *SshExecutor) checkForSnapshots(host, volume string) error { - - // Structure used to unmarshal XML from snapshot gluster cli - type CliOutput struct { - SnapList struct { - Count int `xml:"count"` - } `xml:"snapList"` - } - - commands := []string{ - fmt.Sprintf("gluster --mode=script snapshot list %v --xml", volume), - } - - output, err := s.RemoteExecutor.RemoteCommandExecute(host, commands, 10) - if err != nil { - return fmt.Errorf("Unable to get snapshot information from volume %v: %v", volume, err) - } - - var snapInfo CliOutput - err = xml.Unmarshal([]byte(output[0]), &snapInfo) - if err != nil { - return fmt.Errorf("Unable to determine snapshot information from volume %v: %v", volume, err) - } - - if snapInfo.SnapList.Count > 0 { - return fmt.Errorf("Unable to delete volume %v because it contains %v snapshots", - volume, snapInfo.SnapList.Count) - } - - return nil -} - -func (s *SshExecutor) VolumeInfo(host string, volume string) (*executors.Volume, error) { - - godbc.Require(volume != "") - godbc.Require(host != "") - - type CliOutput struct { - OpRet int `xml:"opRet"` - OpErrno int `xml:"opErrno"` - OpErrStr string `xml:"opErrstr"` - VolInfo executors.VolInfo `xml:"volInfo"` - } - - command := []string{ - fmt.Sprintf("gluster --mode=script volume info %v --xml", volume), - } - - //Get the xml output of volume info - output, err := s.RemoteExecutor.RemoteCommandExecute(host, command, 10) - if err != nil { - return nil, fmt.Errorf("Unable to get volume info of volume name: %v", volume) - } - var volumeInfo CliOutput - err = xml.Unmarshal([]byte(output[0]), &volumeInfo) - if err != nil { - return nil, fmt.Errorf("Unable to determine volume info of volume name: %v", volume) - } - logger.Debug("%+v\n", volumeInfo) - return &volumeInfo.VolInfo.Volumes.VolumeList[0], nil -} - -func (s *SshExecutor) VolumeReplaceBrick(host string, volume string, oldBrick *executors.BrickInfo, newBrick *executors.BrickInfo) error { - godbc.Require(volume != "") - godbc.Require(host != "") - godbc.Require(oldBrick != nil) - godbc.Require(newBrick != nil) - - // Replace the brick - command := []string{ - fmt.Sprintf("gluster --mode=script volume replace-brick %v %v:%v %v:%v commit force", volume, oldBrick.Host, oldBrick.Path, newBrick.Host, newBrick.Path), - } - _, err := s.RemoteExecutor.RemoteCommandExecute(host, command, 10) - if err != nil { - return logger.Err(fmt.Errorf("Unable to replace brick %v:%v with %v:%v for volume %v", oldBrick.Host, oldBrick.Path, newBrick.Host, newBrick.Path, volume)) - } - - return nil - -} diff --git a/extras/docker/ci/Dockerfile b/extras/docker/ci/Dockerfile deleted file mode 100644 index bd6e5fc9a3..0000000000 --- a/extras/docker/ci/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -# set author and base -FROM centos -MAINTAINER Luis Pabón - -LABEL version="1.0" -LABEL description="CI Development build" - -# post install config and volume setup -ADD ./heketi /usr/bin/heketi -ADD ./heketi-start.sh /usr/bin/heketi-start.sh -ADD ./heketi.json /etc/heketi/heketi.json - -RUN mkdir /var/lib/heketi -VOLUME /var/lib/heketi - -# expose port, set user and set entrypoint with config option -ENTRYPOINT ["/usr/bin/heketi-start.sh"] -EXPOSE 8080 diff --git a/extras/docker/ci/README.md b/extras/docker/ci/README.md deleted file mode 100644 index 3861680f9f..0000000000 --- a/extras/docker/ci/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Overview -Dockerfile to build using code from this repo and test in a CI system - -# Build -First build binaries and copy to this directory - - # docker build --rm --tag heketi/heketi:ci . - diff --git a/extras/docker/ci/heketi.json b/extras/docker/ci/heketi.json deleted file mode 100644 index fcc7ad81ec..0000000000 --- a/extras/docker/ci/heketi.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "_port_comment": "Heketi Server Port Number", - "port" : "8080", - - "_use_auth": "Enable JWT authorization. Please enable for deployment", - "use_auth" : false, - - "_jwt" : "Private keys for access", - "jwt" : { - "_admin" : "Admin has access to all APIs", - "admin" : { - "key" : "My Secret" - }, - "_user" : "User only has access to /volumes endpoint", - "user" : { - "key" : "My Secret" - } - }, - - "_glusterfs_comment": "GlusterFS Configuration", - "glusterfs" : { - - "_executor_comment": "Execute plugin. Possible choices: mock, ssh", - "executor" : "mock", - - "_db_comment": "Database file name", - "db" : "/var/lib/heketi/heketi.db" - } -} diff --git a/extras/docker/fromsource/Dockerfile b/extras/docker/fromsource/Dockerfile deleted file mode 100644 index bf819d399b..0000000000 --- a/extras/docker/fromsource/Dockerfile +++ /dev/null @@ -1,43 +0,0 @@ -# set author and base -FROM fedora -MAINTAINER Luis Pabón - -LABEL version="1.3.1" -LABEL description="Development build" - -# let's setup all the necessary environment variables -ENV BUILD_HOME=/build -ENV GOPATH=$BUILD_HOME/golang -ENV PATH=$GOPATH/bin:$PATH -ENV HEKETI_BRANCH="master" - -RUN curl https://glide.sh/get | sh - -# install dependencies, build and cleanup -RUN mkdir $BUILD_HOME $GOPATH && \ - dnf -q -y install golang git && \ - dnf -q -y install make && \ - dnf -q -y clean all && \ - mkdir -p $GOPATH/src/github.com/heketi && \ - cd $GOPATH/src/github.com/heketi && \ - git clone -b $HEKETI_BRANCH https://github.com/heketi/heketi.git && \ - cd $GOPATH/src/github.com/heketi/heketi && \ - glide install -v && make && \ - cp heketi /usr/bin/heketi && \ - cp client/cli/go/heketi-cli /usr/bin/heketi-cli && \ - cd && rm -rf $BUILD_HOME && \ - dnf -q -y remove git golang make && \ - dnf -q -y autoremove && \ - dnf -q -y clean all - -# post install config and volume setup -ADD ./heketi.json /etc/heketi/heketi.json -ADD ./heketi-start.sh /usr/bin/heketi-start.sh -VOLUME /etc/heketi - -RUN mkdir /var/lib/heketi -VOLUME /var/lib/heketi - -# expose port, set user and set entrypoint with config option -ENTRYPOINT ["/usr/bin/heketi-start.sh"] -EXPOSE 8080 diff --git a/extras/docker/fromsource/README.md b/extras/docker/fromsource/README.md deleted file mode 100644 index 72b9bcdf1c..0000000000 --- a/extras/docker/fromsource/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Overview -The main purpose of this container is to be used for testing -and verification of the unstable master builds. - -# How to use for testing - -## Downloading -First you will need to download the latest development container: - - # docker pull heketi/heketi:dev - -> NOTE: Most likely you will always need to do a new pull before staring your tests since the container changes so often. - -## Server Setup -You will need to create a directory which has a directory containing configuraiton and any private key if necessary, and an empty directory used for storing the database. Directory and files must be read/write by user with id 1000 and if an ssh private key is used, it must also have a mod of 0600. - -Here is an example: - - $ mkdir -p heketi/config - $ mkdir -p heketi/db - $ cp heketi.json heketi/config - $ cp myprivate_key heketi/config - $ chmod 600 heketi/config/myprivate_key - $ chown 1000:1000 -R heketi - -To run: - - # docker run -d -p 8080:8080 \ - -v $PWD/heketi/config:/etc/heketi \ - -v $PWD/heketi/db:/var/lib/heketi \ - heketi/heketi:dev - -Now you can see the container running. Here is an example: - -``` -$ sudo docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -6e3ed5c59f87 heketidev "/usr/bin/heketi -con" 32 minutes ago Up 32 minutes 0.0.0.0:8080->8080/tcp goofy_kowalevski -``` - -Now we can check the logs - -``` -$ sudo docker logs 6e3ed5c59f87 | head -Heketi 1.0.0-81-g0c78700 -[heketi] INFO 2016/04/12 18:57:13 Loaded ssh executor -[heketi] INFO 2016/04/12 18:57:13 Loaded simple allocator -[heketi] INFO 2016/04/12 18:57:13 GlusterFS Application Loaded -[negroni] Started GET /hello -[negroni] Completed 200 OK in 79.951µs -[negroni] Started GET /clusters -[negroni] Completed 200 OK in 91.658µs -[negroni] Started POST /clusters -[negroni] Completed 201 Created in 6.046309ms -``` - -## Using heketi-cli -Using our example above, to use the heketi-cli, you can type: - -``` -$ sudo docker exec 6e3ed5c59f87 \ - heketi-cli -h -$ sudo docker exec 6e3ed5c59f87 \ - heketi-cli --server http://localhost:8080/ cluster list -``` - -# Build -If you need to build it: - - # docker build --rm --tag /heketi:dev . - diff --git a/extras/docker/fromsource/heketi-start.sh b/extras/docker/fromsource/heketi-start.sh deleted file mode 100755 index bf1586fa3e..0000000000 --- a/extras/docker/fromsource/heketi-start.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -if [ -f /backupdb/heketi.db.gz ] ; then - gunzip -c /backupdb/heketi.db.gz > /var/lib/heketi/heketi.db - if [ $? -ne 0 ] ; then - echo "Unable to copy database" - exit 1 - fi - echo "Copied backup db to /var/lib/heketi/heketi.db" -elif [ -f /backupdb/heketi.db ] ; then - cp /backupdb/heketi.db /var/lib/heketi/heketi.db - if [ $? -ne 0 ] ; then - echo "Unable to copy database" - exit 1 - fi - echo "Copied backup db to /var/lib/heketi/heketi.db" -fi - -/usr/bin/heketi --config=/etc/heketi/heketi.json diff --git a/extras/docker/fromsource/heketi.json b/extras/docker/fromsource/heketi.json deleted file mode 100644 index fcc7ad81ec..0000000000 --- a/extras/docker/fromsource/heketi.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "_port_comment": "Heketi Server Port Number", - "port" : "8080", - - "_use_auth": "Enable JWT authorization. Please enable for deployment", - "use_auth" : false, - - "_jwt" : "Private keys for access", - "jwt" : { - "_admin" : "Admin has access to all APIs", - "admin" : { - "key" : "My Secret" - }, - "_user" : "User only has access to /volumes endpoint", - "user" : { - "key" : "My Secret" - } - }, - - "_glusterfs_comment": "GlusterFS Configuration", - "glusterfs" : { - - "_executor_comment": "Execute plugin. Possible choices: mock, ssh", - "executor" : "mock", - - "_db_comment": "Database file name", - "db" : "/var/lib/heketi/heketi.db" - } -} diff --git a/extras/docker/gluster/Dockerfile b/extras/docker/gluster/Dockerfile deleted file mode 100644 index e6c57f832d..0000000000 --- a/extras/docker/gluster/Dockerfile +++ /dev/null @@ -1,80 +0,0 @@ -FROM centos - -MAINTAINER Humble Chirammal hchiramm@redhat.com -LABEL version="0.5" -LABEL description="GlusterFS container based on CentOS 7" - -ENV container docker - -RUN yum --setopt=tsflags=nodocs -y update; yum clean all; - -RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \ -rm -f /lib/systemd/system/multi-user.target.wants/*;\ -rm -f /etc/systemd/system/*.wants/*;\ -rm -f /lib/systemd/system/local-fs.target.wants/*; \ -rm -f /lib/systemd/system/sockets.target.wants/*udev*; \ -rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \ -rm -f /lib/systemd/system/basic.target.wants/*;\ -rm -f /lib/systemd/system/anaconda.target.wants/*; - -RUN yum --setopt=tsflags=nodocs -q -y install \ - wget \ - nfs-utils \ - attr \ - iputils \ - iproute \ - sudo \ - xfsprogs \ - centos-release-gluster \ - ntp \ - epel-release \ - openssh-clients \ - cronie \ - tar \ - rsync \ - sos ; yum clean all - -RUN yum --setopt=tsflags=nodocs -y install \ - glusterfs \ - glusterfs-server \ - glusterfs-geo-replication ; yum clean all - -# Backing up gluster config as it overlaps when bind mounting. -RUN mkdir -p /etc/glusterfs_bkp /var/lib/glusterd_bkp /var/log/glusterfs_bkp;\ -cp -r /etc/glusterfs/* /etc/glusterfs_bkp;\ -cp -r /var/lib/glusterd/* /var/lib/glusterd_bkp;\ -cp -r /var/log/glusterfs/* /var/log/glusterfs_bkp; - -# Adding script to move the glusterfs config file to location -ADD gluster-setup.service /etc/systemd/system/gluster-setup.service -RUN chmod 644 /etc/systemd/system/gluster-setup.service - -# Adding script to move the glusterfs config file to location -ADD gluster-setup.sh /usr/sbin/gluster-setup.sh -RUN chmod 500 /usr/sbin/gluster-setup.sh - -# To avoid the warnings while accessing the container -RUN sed -i "s/LANG/\#LANG/g" /etc/locale.conf - -# Configure LVM so that we can create LVs and snapshots -RUN sed -i.save -e "s#udev_sync = 1#udev_sync = 0#" \ - -e "s#udev_rules = 1#udev_rules = 0#" \ - -e "s#use_lvmetad = 1#use_lvmetad = 0#" /etc/lvm/lvm.conf - -# Set password -RUN echo 'root:password' | chpasswd - -# Set SSH public key -USER root - -VOLUME [ "/sys/fs/cgroup", "/dev", "/run/lvm" , "/var/lib/heketi" ] - -EXPOSE 111 245 443 24007 2049 8080 6010 6011 6012 38465 38466 38468 38469 49152 49153 49154 49156 49157 49158 49159 49160 49161 49162 - -RUN systemctl disable nfs-server.service -RUN systemctl enable rpcbind.service -RUN systemctl enable ntpd.service -RUN systemctl enable gluster-setup.service -RUN systemctl enable glusterd.service - -CMD ["/usr/sbin/init"] diff --git a/extras/docker/gluster/README.md b/extras/docker/gluster/README.md deleted file mode 100644 index a4793315a7..0000000000 --- a/extras/docker/gluster/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Dockerfile for GlusterFS -Forked from https://github.com/gluster/docker - -# Running - -```bash -$ sudo docker run -d \ - -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ - -v /run/lvm:/run/lvm \ - -v /dev:/dev \ - -v /var/lib/heketi:/var/lib/heketi \ - --privileged \ - heketi/gluster -``` - -# Build - -``` -# docker build --rm --tag heketi/gluster:dev . -``` - - diff --git a/extras/docker/gluster/gluster-setup.service b/extras/docker/gluster/gluster-setup.service deleted file mode 100644 index 804c281e41..0000000000 --- a/extras/docker/gluster/gluster-setup.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Configuring GlusterFS in container -Before=rpcbind.service - -[Service] -Type=oneshot -ExecStart=/usr/sbin/gluster-setup.sh - -[Install] -WantedBy=multi-user.target - diff --git a/extras/docker/gluster/gluster-setup.sh b/extras/docker/gluster/gluster-setup.sh deleted file mode 100644 index 522e9933c9..0000000000 --- a/extras/docker/gluster/gluster-setup.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -### -# Description: Script to move the glusterfs initial setup to bind mounted directories of Atomic Host. -# Copyright (c) 2016 Red Hat, Inc. -# -# This file is part of GlusterFS. -# -# This file is licensed to you under your choice of the GNU Lesser -# General Public License, version 3 or any later version (LGPLv3 or -# later), or the GNU General Public License, version 2 (GPLv2), in all -# cases as published by the Free Software Foundation. -### - -main () { - if test "$(ls /var/lib/heketi/fstab)" - then - mount -a --fstab /var/lib/heketi/fstab - if [ $? -eq 1 ] - then - echo "mount failed" - exit 1 - fi - echo "Mount Successful" - else - echo "heketi-fstab not found" - fi - DIR_1="/etc/glusterfs" - DIR_2="/var/log/glusterfs" - DIR_3="/var/lib/glusterd" - var=0 - for i in $DIR_1 $DIR_2 $DIR_3 - do - if test "$(ls $i)" - then - echo "$i is not empty" - var=$((var+1)) - fi - done - - if [ $var -eq 3 ] - then - exit 1 - fi - - cp -r /etc/glusterfs_bkp/* /etc/glusterfs - if [ $? -eq 1 ] - then - echo "Failed to copy $DIR_1" - exit 1 - fi - - cp -r /var/log/glusterfs_bkp/* /var/log/glusterfs - if [ $? -eq 1 ] - then - echo "Failed to copy $DIR_2" - exit 1 - fi - - cp -r /var/lib/glusterd_bkp/* /var/lib/glusterd - if [ $? -eq 1 ] - then - echo "Failed to copy $DIR_3" - exit 1 - fi - - echo "Script Ran Successfully" -} -main - diff --git a/extras/docker/rpi/Dockerfile b/extras/docker/rpi/Dockerfile deleted file mode 100644 index 2379790cd3..0000000000 --- a/extras/docker/rpi/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -# set author and base -FROM resin/rpi-raspbian -MAINTAINER Luis Pabón - -LABEL version="1.0.0" -LABEL description="Heketi Container for Raspberry Pi" - -ADD ./heketi /usr/bin/heketi -ADD ./heketi-cli /usr/bin/heketi-cli -ADD ./heketi.json /etc/heketi/heketi.json -ADD ./heketi-start.sh /usr/bin/heketi-start.sh -VOLUME /etc/heketi - -VOLUME /var/lib/heketi - -# expose port, set user and set entrypoint with config option -ENTRYPOINT ["/usr/bin/heketi-start.sh"] -EXPOSE 8080 diff --git a/extras/docker/rpi/README.md b/extras/docker/rpi/README.md deleted file mode 100644 index 9555adbdbd..0000000000 --- a/extras/docker/rpi/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Overview -Dockerfile and helper build script to build a Heketi container -for Raspberry Pi from an x86_64 machine - -# Build - -``` -$ ./build-rpi-dockerfile.sh -$ sudo docker push heketi/heketi-rpi -``` - diff --git a/extras/docker/rpi/build-rpi-dockerfile.sh b/extras/docker/rpi/build-rpi-dockerfile.sh deleted file mode 100755 index e85af47dbd..0000000000 --- a/extras/docker/rpi/build-rpi-dockerfile.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -# This is needed to build the executable on an x86 machine for RPi - -fail() { - echo "$1" - exit 1 -} - -DOCKERFILEDIR=`pwd` -compile() { - cd ../../.. - env GOOS=linux GOARCH=arm make || fail "Unable to create build" - cp heketi $DOCKERFILEDIR - cp client/cli/go/heketi-cli $DOCKERFILEDIR - make clean - cd $DOCKERFILEDIR -} - -build() { - sudo docker build --rm --tag heketi/heketi-rpi:latest . -} - -compile -build diff --git a/extras/docker/rpi/heketi-start.sh b/extras/docker/rpi/heketi-start.sh deleted file mode 100755 index bf1586fa3e..0000000000 --- a/extras/docker/rpi/heketi-start.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -if [ -f /backupdb/heketi.db.gz ] ; then - gunzip -c /backupdb/heketi.db.gz > /var/lib/heketi/heketi.db - if [ $? -ne 0 ] ; then - echo "Unable to copy database" - exit 1 - fi - echo "Copied backup db to /var/lib/heketi/heketi.db" -elif [ -f /backupdb/heketi.db ] ; then - cp /backupdb/heketi.db /var/lib/heketi/heketi.db - if [ $? -ne 0 ] ; then - echo "Unable to copy database" - exit 1 - fi - echo "Copied backup db to /var/lib/heketi/heketi.db" -fi - -/usr/bin/heketi --config=/etc/heketi/heketi.json diff --git a/extras/docker/rpi/heketi.json b/extras/docker/rpi/heketi.json deleted file mode 100644 index fcc7ad81ec..0000000000 --- a/extras/docker/rpi/heketi.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "_port_comment": "Heketi Server Port Number", - "port" : "8080", - - "_use_auth": "Enable JWT authorization. Please enable for deployment", - "use_auth" : false, - - "_jwt" : "Private keys for access", - "jwt" : { - "_admin" : "Admin has access to all APIs", - "admin" : { - "key" : "My Secret" - }, - "_user" : "User only has access to /volumes endpoint", - "user" : { - "key" : "My Secret" - } - }, - - "_glusterfs_comment": "GlusterFS Configuration", - "glusterfs" : { - - "_executor_comment": "Execute plugin. Possible choices: mock, ssh", - "executor" : "mock", - - "_db_comment": "Database file name", - "db" : "/var/lib/heketi/heketi.db" - } -} diff --git a/extras/etc/init/heketi.initd b/extras/etc/init/heketi.initd deleted file mode 100644 index ed6689d7db..0000000000 --- a/extras/etc/init/heketi.initd +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/sh -# -# heketi Startup script for the heketi server -# -# chkconfig: - 95 95 -# description: Heketi service - -### BEGIN INIT INFO -# Provides: heketi -# Required-Start: $local_fs $network -# Required-Stop: $local_fs $network -# Should-Start: -# Should-Stop: -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: start and stop heketi server -# Description: Heketi server -### END INIT INFO - -# Source function library. -. /etc/rc.d/init.d/functions - -exec="/usr/bin/heketi" -prog="heketi" -user="heketi" -config="/etc/heketi/heketi.json" -pidfile="/var/run/$prog.pid" -lockfile="/var/lock/subsys/$prog" -logfile="/var/log/$prog" - -[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog - -start() { - - [ -x $exec ] || exit 5 - [ -f $config ] || exit 6 - - if [ ! -f $logfile ] ; then - touch $logfile - chown ${user}:${user} $logfile - chmod 600 $logfile - fi - - if ! [ -f $pidfile ]; then - if [ ! -f $pidfile ] ; then - touch $pidfile - chown ${user}:${user} $pidfile - fi - echo -n "Starting $prog: " - daemon --user heketi "$exec --config=${config} &>> $logfile & echo \$! > $pidfile" - retval=$? - if [ $retval -eq 0 ] ; then - touch $lockfile - echo - fi - return $retval - else - failure - echo - printf "$pidfile still exists...\n" - exit 7 - fi - -} - -stop() { - echo -n $"Stopping $prog: " - # stop it here, often "killproc $prog" - killproc -p $pidfile $prog - retval=$? - echo - [ $retval -eq 0 ] && rm -f $lockfile - return $retval -} - -restart() { - stop - start -} - -reload() { - restart -} - -force_reload() { - restart -} - -rh_status() { - # run checks to determine if the service is running or use generic status - status $prog -} - -rh_status_q() { - rh_status >/dev/null 2>&1 -} - - -case "$1" in - start) - rh_status_q && exit 0 - $1 - ;; - stop) - rh_status_q || exit 0 - $1 - ;; - restart) - $1 - ;; - reload) - rh_status_q || exit 7 - $1 - ;; - force-reload) - force_reload - ;; - status) - rh_status - ;; - condrestart|try-restart) - rh_status_q || exit 0 - restart - ;; - *) - echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" - exit 2 -esac -exit $? diff --git a/extras/kubernetes/README.md b/extras/kubernetes/README.md deleted file mode 100644 index 739748ee1d..0000000000 --- a/extras/kubernetes/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Overview -Kubernetes templates for Heketi and Gluster. The following documentation is setup -to deploy the containers in Kubernetes. It is not a full setup. For full -documentation, please visit the Heketi wiki page. - -# Usage - -## Deploy Gluster - -* Get node name by running: - -``` -$ kubectl get nodes -``` - -* Deploy the GlusterFS DaemonSet - -``` -$ kubectl create -f gluster-daemonset.json -``` - -* Deploy gluster container onto specified node by setting the label -`storagenode=glusterfs` on that node: - -``` -$ kubectl label node <...node...> storagenode=glusterfs -``` - -Repeat as needed. - -## Deploy Heketi - -First you will need to deploy the bootstrap Heketi container: - -``` -$ kubectl create -f deploy-heketi-deployment.json -``` - -This will deploy the a Heketi container used to bootstrap the Heketi -database. Please refer to the wiki Kubernetes Deployment page for -more information - diff --git a/extras/kubernetes/glusterfs-daemonset.json b/extras/kubernetes/glusterfs-daemonset.json deleted file mode 100644 index 41b349d81f..0000000000 --- a/extras/kubernetes/glusterfs-daemonset.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "kind": "DaemonSet", - "apiVersion": "extensions/v1beta1", - "metadata": { - "name": "glusterfs", - "labels": { - "glusterfs": "deployment" - }, - "annotations": { - "description": "GlusterFS Daemon Set", - "tags": "glusterfs" - } - }, - "spec": { - "template": { - "metadata": { - "name": "glusterfs", - "labels": { - "glusterfs-node": "daemonset" - } - }, - "spec": { - "nodeSelector": { - "storagenode" : "glusterfs" - }, - "hostNetwork": true, - "containers": [ - { - "image": "heketi/gluster:latest", - "imagePullPolicy": "Always", - "name": "glusterfs", - "volumeMounts": [ - { - "name": "glusterfs-heketi", - "mountPath": "/var/lib/heketi" - }, - { - "name": "glusterfs-run", - "mountPath": "/run" - }, - { - "name": "glusterfs-lvm", - "mountPath": "/run/lvm" - }, - { - "name": "glusterfs-etc", - "mountPath": "/etc/glusterfs" - }, - { - "name": "glusterfs-logs", - "mountPath": "/var/log/glusterfs" - }, - { - "name": "glusterfs-config", - "mountPath": "/var/lib/glusterd" - }, - { - "name": "glusterfs-dev", - "mountPath": "/dev" - }, - { - "name": "glusterfs-cgroup", - "mountPath": "/sys/fs/cgroup" - } - ], - "securityContext": { - "capabilities": {}, - "privileged": true - }, - "readinessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 60, - "exec": { - "command": [ - "/bin/bash", - "-c", - "systemctl status glusterd.service" - ] - } - }, - "livenessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 60, - "exec": { - "command": [ - "/bin/bash", - "-c", - "systemctl status glusterd.service" - ] - } - } - } - ], - "volumes": [ - { - "name": "glusterfs-heketi", - "hostPath": { - "path": "/var/lib/heketi" - } - }, - { - "name": "glusterfs-run" - }, - { - "name": "glusterfs-lvm", - "hostPath": { - "path": "/run/lvm" - } - }, - { - "name": "glusterfs-etc", - "hostPath": { - "path": "/etc/glusterfs" - } - }, - { - "name": "glusterfs-logs", - "hostPath": { - "path": "/var/log/glusterfs" - } - }, - { - "name": "glusterfs-config", - "hostPath": { - "path": "/var/lib/glusterd" - } - }, - { - "name": "glusterfs-dev", - "hostPath": { - "path": "/dev" - } - }, - { - "name": "glusterfs-cgroup", - "hostPath": { - "path": "/sys/fs/cgroup" - } - } - ] - } - } - } -} diff --git a/extras/kubernetes/heketi-deployment.json b/extras/kubernetes/heketi-deployment.json deleted file mode 100644 index 2e83f7cf48..0000000000 --- a/extras/kubernetes/heketi-deployment.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "kind": "List", - "apiVersion": "v1", - "items": [ - { - "kind": "Secret", - "apiVersion": "v1", - "metadata": { - "name": "heketi-db-backup", - "labels": { - "glusterfs": "heketi-db", - "heketi": "db" - } - }, - "data": { - "heketi.db": "" - }, - "type": "Opaque" - }, - { - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "heketi", - "labels": { - "glusterfs": "heketi-service", - "deploy-heketi": "support" - }, - "annotations": { - "description": "Exposes Heketi Service" - } - }, - "spec": { - "selector": { - "name": "heketi" - }, - "ports": [ - { - "name": "heketi", - "port": 8080, - "targetPort": 8080 - } - ] - } - }, - { - "kind": "Deployment", - "apiVersion": "extensions/v1beta1", - "metadata": { - "name": "heketi", - "labels": { - "glusterfs": "heketi-deployment" - }, - "annotations": { - "description": "Defines how to deploy Heketi" - } - }, - "spec": { - "replicas": 1, - "template": { - "metadata": { - "name": "heketi", - "labels": { - "name": "heketi", - "glusterfs": "heketi-pod" - } - }, - "spec": { - "serviceAccountName": "heketi-service-account", - "containers": [ - { - "image": "heketi/heketi:dev", - "imagePullPolicy": "Always", - "name": "heketi", - "env": [ - { - "name": "HEKETI_EXECUTOR", - "value": "kubernetes" - }, - { - "name": "HEKETI_FSTAB", - "value": "/var/lib/heketi/fstab" - }, - { - "name": "HEKETI_SNAPSHOT_LIMIT", - "value": "14" - }, - { - "name": "HEKETI_KUBE_GLUSTER_DAEMONSET", - "value": "y" - } - ], - "ports": [ - { - "containerPort": 8080 - } - ], - "volumeMounts": [ - { - "mountPath": "/backupdb", - "name": "heketi-db-secret" - }, - { - "name": "db", - "mountPath": "/var/lib/heketi" - } - ], - "readinessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 3, - "httpGet": { - "path": "/hello", - "port": 8080 - } - }, - "livenessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 30, - "httpGet": { - "path": "/hello", - "port": 8080 - } - } - } - ], - "volumes": [ - { - "name": "db" - }, - { - "name": "heketi-db-secret", - "secret": { - "secretName": "heketi-db-backup" - } - } - ] - } - } - } - } - ] -} diff --git a/extras/kubernetes/heketi-start.sh b/extras/kubernetes/heketi-start.sh deleted file mode 100755 index bf1586fa3e..0000000000 --- a/extras/kubernetes/heketi-start.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -if [ -f /backupdb/heketi.db.gz ] ; then - gunzip -c /backupdb/heketi.db.gz > /var/lib/heketi/heketi.db - if [ $? -ne 0 ] ; then - echo "Unable to copy database" - exit 1 - fi - echo "Copied backup db to /var/lib/heketi/heketi.db" -elif [ -f /backupdb/heketi.db ] ; then - cp /backupdb/heketi.db /var/lib/heketi/heketi.db - if [ $? -ne 0 ] ; then - echo "Unable to copy database" - exit 1 - fi - echo "Copied backup db to /var/lib/heketi/heketi.db" -fi - -/usr/bin/heketi --config=/etc/heketi/heketi.json diff --git a/extras/openshift/endpoint/sample-gluster-endpoint.json b/extras/openshift/endpoint/sample-gluster-endpoint.json deleted file mode 100644 index 36d68e0ccf..0000000000 --- a/extras/openshift/endpoint/sample-gluster-endpoint.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "kind": "Endpoints", - "apiVersion": "v1", - "metadata": { - "name": "glusterfs-cluster" - }, - "subsets": [ - { - "addresses": [ - { - "ip": "10.36.6.105" - } - ], - "ports": [ - { - "port": 1 - } - ] - }, - { - "addresses": [ - { - "ip": "10.36.6.105" - } - ], - "ports": [ - { - "port": 1 - } - ] - } - ] -} - - diff --git a/extras/openshift/service/sample-gluster-service.json b/extras/openshift/service/sample-gluster-service.json deleted file mode 100644 index b409103a4f..0000000000 --- a/extras/openshift/service/sample-gluster-service.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "glusterfs-cluster" - }, - "spec": { - "ports": [ - {"port": 1} - ] - } -} - diff --git a/extras/openshift/templates/README.md b/extras/openshift/templates/README.md deleted file mode 100644 index 18b36fd708..0000000000 --- a/extras/openshift/templates/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# Create a Heketi service in OpenShift -> NOTE: This template file places the database in an _EmptyDir_ volume. You will need to adjust accordingly if you would like the database to be on reliable persistent storage. - -* Register template with OpenShift - -``` -oc create -f heketi.json -``` - -* Note the number of parameters which need to be set. Currently only _NAME_ - needs to be set. - -``` -oc process --parameters heketi -``` - -* Deploy a Heketi service - -Here is an example of how to deploy Heketi - -``` -oc process heketi -v NAME=myheketiservice \ - HEKETI_KUBE_NAMESPACE=test \ - HEKETI_KUBE_APIHOST='https://192.168.10.90:8443' \ - HEKETI_KUBE_INSECURE=y \ - HEKETI_KUBE_USER=test-admin \ - HEKETI_KUBE_PASSWORD=admin | oc create -f - -``` - -* Note service - -``` -oc status -``` - -* Send a _hello_ command to service - -``` -curl http://:/hello -``` - -* For example - -``` -$ oc project -Using project "gluster" on server "https://192.168.10.90:8443". - -$ oc create -f heketi-template.json -template "heketi" created - -$ oc process heketi -v NAME=ams \ -> HEKETI_KUBE_NAMESPACE=gluster \ -> HEKETI_KUBE_APIHOST='https://192.168.10.90:8443' \ -> HEKETI_KUBE_INSECURE=y \ -> HEKETI_KUBE_USER=test-admin \ -> HEKETI_KUBE_PASSWORD=admin | oc create -f - -service "ams" created -deploymentconfig "ams" created - -$ oc status -In project gluster on server https://192.168.10.90:8443 - -svc/ams - 172.30.244.79:8080 - dc/ams deploys docker.io/heketi/heketi:dev - deployment #1 pending 5 seconds ago - -View details with 'oc describe /' or list everything with 'oc get all'. - -$ oc get pods -o wide -NAME READY STATUS RESTARTS AGE NODE -ams-1-bed48 0/1 ContainerCreating 0 8s openshift-node-1 -ams-1-deploy 1/1 Running 0 1m openshift-node-2 - -<< Wait until the container is running, then... >> - -$ curl http://172.30.244.79:8080/hello -HelloWorld from GlusterFS Application -``` - - - diff --git a/extras/openshift/templates/deploy-heketi-template.json b/extras/openshift/templates/deploy-heketi-template.json deleted file mode 100644 index 4397831126..0000000000 --- a/extras/openshift/templates/deploy-heketi-template.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "kind": "Template", - "apiVersion": "v1", - "metadata": { - "name": "deploy-heketi", - "labels": { - "glusterfs": "heketi-template", - "deploy-heketi": "support" - }, - "annotations": { - "description": "Bootstrap Heketi installation", - "tags": "glusterfs,heketi,installation" - } - }, - "labels": { - "template": "deploy-heketi" - }, - "objects": [ - { - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "deploy-heketi", - "labels": { - "glusterfs": "heketi-service", - "deploy-heketi": "support" - }, - "annotations": { - "description": "Exposes Heketi service" - } - }, - "spec": { - "ports": [ - { - "name": "deploy-heketi", - "port": 8080, - "targetPort": 8080 - } - ], - "selector": { - "name": "deploy-heketi" - } - } - }, - { - "kind": "Route", - "apiVersion": "v1", - "metadata": { - "name": "deploy-heketi", - "labels": { - "glusterfs": "heketi-route", - "deploy-heketi": "support" - } - }, - "spec": { - "to": { - "kind": "Service", - "name": "deploy-heketi" - } - } - }, - { - "kind": "DeploymentConfig", - "apiVersion": "v1", - "metadata": { - "name": "deploy-heketi", - "labels": { - "glusterfs": "heketi-dc", - "deploy-heketi": "support" - }, - "annotations": { - "description": "Defines how to deploy Heketi" - } - }, - "spec": { - "replicas": 1, - "selector": { - "name": "deploy-heketi" - }, - "triggers": [ - { - "type": "ConfigChange" - } - ], - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "name": "deploy-heketi", - "labels": { - "name": "deploy-heketi", - "glusterfs": "heketi-pod", - "deploy-heketi": "support" - } - }, - "spec": { - "containers": [ - { - "name": "deploy-heketi", - "image": "heketi/heketi:dev", - "env": [ - { - "name": "HEKETI_USER_KEY", - "value": "${HEKETI_USER_KEY}" - }, - { - "name": "HEKETI_ADMIN_KEY", - "value": "${HEKETI_ADMIN_KEY}" - }, - { - "name": "HEKETI_EXECUTOR", - "value": "kubernetes" - }, - { - "name": "HEKETI_FSTAB", - "value": "/var/lib/heketi/fstab" - }, - { - "name": "HEKETI_SNAPSHOT_LIMIT", - "value": "14" - }, - { - "name": "HEKETI_KUBE_CERTFILE", - "value": "${HEKETI_KUBE_CERTFILE}" - }, - { - "name": "HEKETI_KUBE_INSECURE", - "value": "${HEKETI_KUBE_INSECURE}" - }, - { - "name": "HEKETI_KUBE_USER", - "value": "${HEKETI_KUBE_USER}" - }, - { - "name": "HEKETI_KUBE_PASSWORD", - "value": "${HEKETI_KUBE_PASSWORD}" - }, - { - "name": "HEKETI_KUBE_NAMESPACE", - "value": "${HEKETI_KUBE_NAMESPACE}" - }, - { - "name": "HEKETI_KUBE_APIHOST", - "value": "${HEKETI_KUBE_APIHOST}" - } - ], - "ports": [ - { - "containerPort": 8080 - } - ], - "volumeMounts": [ - { - "name": "db", - "mountPath": "/var/lib/heketi" - } - ], - "readinessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 3, - "httpGet": { - "path": "/hello", - "port": 8080 - } - }, - "livenessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 30, - "httpGet": { - "path": "/hello", - "port": 8080 - } - } - } - ], - "volumes": [ - { - "name": "db" - } - ] - } - } - } - } - ], - "parameters": [ - { - "name": "HEKETI_USER_KEY", - "displayName": "Heketi User Secret", - "description": "Set secret for those creating volumes as type _user_" - }, - { - "name": "HEKETI_ADMIN_KEY", - "displayName": "Heketi Administrator Secret", - "description": "Set secret for administration of the Heketi service as user _admin_" - }, - { - "name": "HEKETI_KUBE_CERTFILE", - "displayName": "Certificate file", - "description": "Container path to Kubernetes certificate file" - }, - { - "name": "HEKETI_KUBE_INSECURE", - "displayName": "Insecure access", - "description": "Allow insecure SSL/HTTPS access", - "value": "n" - }, - { - "name": "HEKETI_KUBE_USER", - "displayName": "User", - "description": "OpenShift/Kubernetes username to access Kubernetes API", - "required": true - }, - { - "name": "HEKETI_KUBE_PASSWORD", - "displayName": "Password", - "description": "Password for OpenShift user", - "required": true - }, - { - "name": "HEKETI_KUBE_NAMESPACE", - "displayName": "Project/Namespace", - "description": "OpenShift project or Kubernetes namespace containing GlusterFS", - "required": true - }, - { - "name": "HEKETI_KUBE_APIHOST", - "displayName": "API Host Address", - "description": "Kubernetes API host, for example: https://ip:port", - "required": true - } - ] -} diff --git a/extras/openshift/templates/glusterfs-template.json b/extras/openshift/templates/glusterfs-template.json deleted file mode 100644 index c3bd98e681..0000000000 --- a/extras/openshift/templates/glusterfs-template.json +++ /dev/null @@ -1,196 +0,0 @@ -{ - "kind": "Template", - "apiVersion": "v1", - "metadata": { - "name": "glusterfs", - "labels": { - "glusterfs": "template" - }, - "annotations": { - "description": "GlusterFS container deployment template", - "tags": "glusterfs" - } - }, - "labels": { - "template": "glusterfs" - }, - "objects": [ - { - "kind": "DeploymentConfig", - "apiVersion": "v1", - "metadata": { - "name": "glusterfs-dc-${GLUSTERFS_NODE}", - "labels": { - "glusterfs": "dc", - "glusterfs-node": "${GLUSTERFS_NODE}" - }, - "annotations": { - "description": "Deploys the GlusterFS container" - } - }, - "spec": { - "replicas": 1, - "selector": { - "name": "glusterfs" - }, - "triggers": [ - { - "type": "ConfigChange" - } - ], - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "name": "glusterfs", - "labels": { - "name": "glusterfs", - "glusterfs": "pod", - "glusterfs-node": "${GLUSTERFS_NODE}" - } - }, - "spec": { - "nodeSelector": { - "kubernetes.io/hostname": "${GLUSTERFS_NODE}" - }, - "hostNetwork": true, - "containers": [ - { - "image": "heketi/gluster:latest", - "imagePullPolicy": "Always", - "name": "glusterfs", - "volumeMounts": [ - { - "name": "glusterfs-heketi", - "mountPath": "/var/lib/heketi" - }, - { - "name": "glusterfs-run", - "mountPath": "/run" - }, - { - "name": "glusterfs-lvm", - "mountPath": "/run/lvm" - }, - { - "name": "glusterfs-etc", - "mountPath": "/etc/glusterfs" - }, - { - "name": "glusterfs-logs", - "mountPath": "/var/log/glusterfs" - }, - { - "name": "glusterfs-config", - "mountPath": "/var/lib/glusterd" - }, - { - "name": "glusterfs-dev", - "mountPath": "/dev" - }, - { - "name": "glusterfs-misc", - "mountPath": "/var/lib/misc/glusterfsd" - }, - { - "name": "glusterfs-cgroup", - "mountPath": "/sys/fs/cgroup", - "readOnly": true - } - ], - "securityContext": { - "capabilities": {}, - "privileged": true - }, - "readinessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 100, - "exec": { - "command": [ - "/bin/bash", - "-c", - "systemctl status glusterd.service" - ] - } - }, - "livenessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 100, - "exec": { - "command": [ - "/bin/bash", - "-c", - "systemctl status glusterd.service" - ] - } - } - } - ], - "volumes": [ - { - "name": "glusterfs-heketi", - "hostPath": { - "path": "/var/lib/heketi" - } - }, - { - "name": "glusterfs-run" - }, - { - "name": "glusterfs-lvm", - "hostPath": { - "path": "/run/lvm" - } - }, - { - "name": "glusterfs-etc", - "hostPath": { - "path": "/etc/glusterfs" - } - }, - { - "name": "glusterfs-logs", - "hostPath": { - "path": "/var/log/glusterfs" - } - }, - { - "name": "glusterfs-config", - "hostPath": { - "path": "/var/lib/glusterd" - } - }, - { - "name": "glusterfs-dev", - "hostPath": { - "path": "/dev" - } - }, - { - "name": "glusterfs-misc", - "hostPath": { - "path": "/var/lib/misc/glusterfsd" - } - }, - { - "name": "glusterfs-cgroup", - "hostPath": { - "path": "/sys/fs/cgroup" - } - } - ] - } - } - } - } - ], - "parameters": [ - { - "name": "GLUSTERFS_NODE", - "displayName": "Storage node name", - "description": "Node name to run GlusterFS container", - "required": true - } - ] -} diff --git a/extras/openshift/templates/heketi-template.json b/extras/openshift/templates/heketi-template.json deleted file mode 100644 index 103a452cea..0000000000 --- a/extras/openshift/templates/heketi-template.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "kind": "Template", - "apiVersion": "v1", - "metadata": { - "name": "heketi", - "labels": { - "glusterfs": "heketi-template" - }, - "annotations": { - "description": "Heketi service deployment template", - "tags": "glusterfs,heketi" - } - }, - "labels": { - "template": "heketi" - }, - "objects": [ - { - "kind": "Service", - "apiVersion": "v1", - "metadata": { - "name": "heketi", - "labels": { - "glusterfs": "heketi-service" - }, - "annotations": { - "description": "Exposes Heketi service" - } - }, - "spec": { - "ports": [ - { - "name": "heketi", - "port": 8080, - "targetPort": 8080 - } - ], - "selector": { - "name": "heketi" - } - } - }, - { - "kind": "Route", - "apiVersion": "v1", - "metadata": { - "name": "heketi", - "labels": { - "glusterfs": "heketi-route" - } - }, - "spec": { - "to": { - "kind": "Service", - "name": "heketi" - } - } - }, - { - "kind": "DeploymentConfig", - "apiVersion": "v1", - "metadata": { - "name": "heketi", - "labels": { - "glusterfs": "heketi-dc" - }, - "annotations": { - "description": "Defines how to deploy Heketi" - } - }, - "spec": { - "replicas": 1, - "selector": { - "name": "heketi" - }, - "triggers": [ - { - "type": "ConfigChange" - } - ], - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "name": "heketi", - "labels": { - "name": "heketi", - "glusterfs": "heketi-pod" - } - }, - "spec": { - "containers": [ - { - "name": "heketi", - "image": "heketi/heketi:dev", - "imagePullPolicy": "Always", - "env": [ - { - "name": "HEKETI_USER_KEY", - "value": "${HEKETI_USER_KEY}" - }, - { - "name": "HEKETI_ADMIN_KEY", - "value": "${HEKETI_ADMIN_KEY}" - }, - { - "name": "HEKETI_EXECUTOR", - "value": "kubernetes" - }, - { - "name": "HEKETI_FSTAB", - "value": "/var/lib/heketi/fstab" - }, - { - "name": "HEKETI_SNAPSHOT_LIMIT", - "value": "14" - }, - { - "name": "HEKETI_KUBE_CERTFILE", - "value": "${HEKETI_KUBE_CERTFILE}" - }, - { - "name": "HEKETI_KUBE_INSECURE", - "value": "${HEKETI_KUBE_INSECURE}" - }, - { - "name": "HEKETI_KUBE_USER", - "value": "${HEKETI_KUBE_USER}" - }, - { - "name": "HEKETI_KUBE_PASSWORD", - "value": "${HEKETI_KUBE_PASSWORD}" - }, - { - "name": "HEKETI_KUBE_NAMESPACE", - "value": "${HEKETI_KUBE_NAMESPACE}" - }, - { - "name": "HEKETI_KUBE_APIHOST", - "value": "${HEKETI_KUBE_APIHOST}" - } - ], - "ports": [ - { - "containerPort": 8080 - } - ], - "volumeMounts": [ - { - "name": "db", - "mountPath": "/var/lib/heketi" - } - ], - "readinessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 3, - "httpGet": { - "path": "/hello", - "port": 8080 - } - }, - "livenessProbe": { - "timeoutSeconds": 3, - "initialDelaySeconds": 30, - "httpGet": { - "path": "/hello", - "port": 8080 - } - } - } - ], - "volumes": [ - { - "name": "db", - "glusterfs": { - "endpoints" : "heketi-storage-endpoints", - "path" : "heketidbstorage" - } - } - ] - } - } - } - } - ], - "parameters": [ - { - "name": "HEKETI_USER_KEY", - "displayName": "Heketi User Secret", - "description": "Set secret for those creating volumes as type _user_" - }, - { - "name": "HEKETI_ADMIN_KEY", - "displayName": "Heketi Administrator Secret", - "description": "Set secret for administration of the Heketi service as user _admin_" - }, - { - "name": "HEKETI_KUBE_CERTFILE", - "displayName": "Certificate file", - "description": "Container path to Kubernetes certificate file" - }, - { - "name": "HEKETI_KUBE_INSECURE", - "displayName": "Insecure access", - "description": "Allow insecure SSL/HTTPS access", - "value": "n" - }, - { - "name": "HEKETI_KUBE_USER", - "displayName": "User", - "description": "OpenShift/Kubernetes username to access Kubernetes API", - "required": true - }, - { - "name": "HEKETI_KUBE_PASSWORD", - "displayName": "Password", - "description": "Password for OpenShift user", - "required": true - }, - { - "name": "HEKETI_KUBE_NAMESPACE", - "displayName": "Project/Namespace", - "description": "OpenShift project or Kubernetes namespace containing GlusterFS", - "required": true - }, - { - "name": "HEKETI_KUBE_APIHOST", - "displayName": "API Host Address", - "description": "Kubernetes API host, for example: https://ip:port", - "required": true - } - ] -} diff --git a/extras/systemd/heketi.service b/extras/systemd/heketi.service deleted file mode 100644 index 7b0b2d2d04..0000000000 --- a/extras/systemd/heketi.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Heketi Server - -[Service] -Type=simple -WorkingDirectory=/var/lib/heketi -EnvironmentFile=-/etc/heketi/heketi.env -User=heketi -ExecStart=/usr/bin/heketi --config=/etc/heketi/heketi.json -Restart=on-failure -StandardOutput=syslog -StandardError=syslog - -[Install] -WantedBy=multi-user.target diff --git a/glide.lock b/glide.lock deleted file mode 100644 index 2a3e30261b..0000000000 --- a/glide.lock +++ /dev/null @@ -1,311 +0,0 @@ -hash: 59834cf573ea7a48f90686852f6c02428d2ffee8ecfcfeafac4ca2e52638f00a -updated: 2017-05-22T14:00:49.364236742-04:00 -imports: -- name: github.com/auth0/go-jwt-middleware - version: f3f7de3b9e394e3af3b88e1b9457f6f71d1ae0ac -- name: github.com/Azure/go-ansiterm - version: 70b2c90b260171e829f1ebd7c17f600c11858dbe - subpackages: - - winterm -- name: github.com/boltdb/bolt - version: 583e8937c61f1af6513608ccc75c97b6abdf4ff9 -- name: github.com/davecgh/go-spew - version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d - subpackages: - - spew -- name: github.com/dgrijalva/jwt-go - version: d2709f9f1f31ebcda9651b03077758c1f3a0018c -- name: github.com/docker/distribution - version: cd27f179f2c10c5d300e6d09025b538c475b0d51 - subpackages: - - digest - - reference -- name: github.com/docker/docker - version: b9f10c951893f9a00865890a5232e85d770c1087 - subpackages: - - pkg/jsonlog - - pkg/jsonmessage - - pkg/longpath - - pkg/mount - - pkg/stdcopy - - pkg/symlink - - pkg/system - - pkg/term - - pkg/term/windows -- name: github.com/docker/go-units - version: e30f1e79f3cd72542f2026ceec18d3bd67ab859c -- name: github.com/docker/spdystream - version: 449fdfce4d962303d702fec724ef0ad181c92528 - subpackages: - - spdy -- name: github.com/emicklei/go-restful - version: 09691a3b6378b740595c1002f40c34dd5f218a22 - subpackages: - - log - - swagger -- name: github.com/ghodss/yaml - version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee -- name: github.com/go-openapi/jsonpointer - version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 -- name: github.com/go-openapi/jsonreference - version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 -- name: github.com/go-openapi/spec - version: 6aced65f8501fe1217321abf0749d354824ba2ff -- name: github.com/go-openapi/swag - version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 -- name: github.com/gogo/protobuf - version: e18d7aa8f8c624c915db340349aad4c49b10d173 - subpackages: - - proto - - sortkeys -- name: github.com/golang/glog - version: 44145f04b68cf362d9c4df2182967c2275eaefed -- name: github.com/google/gofuzz - version: 44d81051d367757e1c7c6a5a86423ece9afcf63c -- name: github.com/gorilla/context - version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a -- name: github.com/gorilla/mux - version: bcd8bc72b08df0f70df986b97f95590779502d31 -- name: github.com/heketi/rest - version: 738570ea73f1cc2a0888c7c49fe80991c6050f34 -- name: github.com/heketi/tests - version: f3775cbcefd6822086c729e3ce4b70ca85a5bd21 -- name: github.com/heketi/utils - version: 435bc5bdfa64550e867cd7ef0b9181adc02b88d2 -- name: github.com/inconshreveable/mousetrap - version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 -- name: github.com/juju/ratelimit - version: 77ed1c8a01217656d2080ad51981f6e99adaa177 -- name: github.com/lpabon/godbc - version: 9577782540c1398b710ddae1b86268ba03a19b0c -- name: github.com/mailru/easyjson - version: d5b7844b561a7bc640052f1b935f7b800330d7e0 - subpackages: - - buffer - - jlexer - - jwriter -- name: github.com/mitchellh/go-wordwrap - version: ad45545899c7b13c020ea92b2072220eefad42b8 -- name: github.com/PuerkitoBio/purell - version: 8a290539e2e8629dbc4e6bad948158f790ec31f4 -- name: github.com/PuerkitoBio/urlesc - version: 5bd2802263f21d8788851d5305584c82a5c75d7e -- name: github.com/Sirupsen/logrus - version: 51fe59aca108dc5680109e7b2051cbdcfa5a253c -- name: github.com/spf13/cobra - version: ca57f0f5dba473a8a58765d16d7e811fb8027add -- name: github.com/spf13/pflag - version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 -- name: github.com/ugorji/go - version: ded73eae5db7e7a0ef6f55aace87a2873c5d2b74 - subpackages: - - codec -- name: github.com/urfave/negroni - version: fde5e16d32adc7ad637e9cd9ad21d4ebc6192535 -- name: golang.org/x/crypto - version: d172538b2cfce0c13cee31e647d0367aa8cd2486 - subpackages: - - curve25519 - - ed25519 - - ed25519/internal/edwards25519 - - ssh - - ssh/agent -- name: golang.org/x/net - version: e90d6d0afc4c315a0d87a568ae68577cc15149a0 - subpackages: - - context - - context/ctxhttp - - http2 - - http2/hpack - - idna - - lex/httplex - - websocket -- name: golang.org/x/sys - version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9 - subpackages: - - unix -- name: golang.org/x/text - version: 2910a502d2bf9e43193af9d68ca516529614eed3 - subpackages: - - cases - - internal/tag - - language - - runes - - secure/bidirule - - secure/precis - - transform - - unicode/bidi - - unicode/norm - - width -- name: gopkg.in/inf.v0 - version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 -- name: gopkg.in/yaml.v2 - version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 -- name: k8s.io/apimachinery - version: 75b8dd260ef0469d96d578705a87cffd0e09dab8 - subpackages: - - pkg/api/equality - - pkg/api/errors - - pkg/api/meta - - pkg/api/resource - - pkg/apimachinery - - pkg/apimachinery/announced - - pkg/apimachinery/registered - - pkg/apis/meta/v1 - - pkg/apis/meta/v1/unstructured - - pkg/conversion - - pkg/conversion/queryparams - - pkg/fields - - pkg/labels - - pkg/openapi - - pkg/runtime - - pkg/runtime/schema - - pkg/runtime/serializer - - pkg/runtime/serializer/json - - pkg/runtime/serializer/protobuf - - pkg/runtime/serializer/recognizer - - pkg/runtime/serializer/streaming - - pkg/runtime/serializer/versioning - - pkg/selection - - pkg/types - - pkg/util/diff - - pkg/util/errors - - pkg/util/framer - - pkg/util/httpstream - - pkg/util/httpstream/spdy - - pkg/util/intstr - - pkg/util/json - - pkg/util/mergepatch - - pkg/util/net - - pkg/util/rand - - pkg/util/runtime - - pkg/util/sets - - pkg/util/strategicpatch - - pkg/util/validation - - pkg/util/validation/field - - pkg/util/wait - - pkg/util/yaml - - pkg/version - - pkg/watch - - third_party/forked/golang/json - - third_party/forked/golang/netutil - - third_party/forked/golang/reflect -- name: k8s.io/apiserver - version: c809cf8581e1e44c6174bf5ab4415e6ee39965ca - subpackages: - - pkg/server/httplog - - pkg/util/wsstream -- name: k8s.io/client-go - version: 3627aeb7d4f6ade38f995d2c923e459146493c7e - subpackages: - - discovery - - discovery/fake - - pkg/api - - pkg/api/v1 - - pkg/apis/extensions - - pkg/util - - pkg/util/parsers - - pkg/version - - rest - - rest/watch - - testing - - tools/clientcmd/api - - tools/metrics - - transport - - util/cert - - util/clock - - util/flowcontrol - - util/integer -- name: k8s.io/kubernetes - version: d6f433224538d4f9ca2f7ae19b252e6fcb66a3ae - subpackages: - - pkg/api - - pkg/api/install - - pkg/api/v1 - - pkg/apis/apps - - pkg/apis/apps/install - - pkg/apis/apps/v1beta1 - - pkg/apis/authentication - - pkg/apis/authentication/install - - pkg/apis/authentication/v1 - - pkg/apis/authentication/v1beta1 - - pkg/apis/authorization - - pkg/apis/authorization/install - - pkg/apis/authorization/v1 - - pkg/apis/authorization/v1beta1 - - pkg/apis/autoscaling - - pkg/apis/autoscaling/install - - pkg/apis/autoscaling/v1 - - pkg/apis/autoscaling/v2alpha1 - - pkg/apis/batch - - pkg/apis/batch/install - - pkg/apis/batch/v1 - - pkg/apis/batch/v2alpha1 - - pkg/apis/certificates - - pkg/apis/certificates/install - - pkg/apis/certificates/v1beta1 - - pkg/apis/extensions - - pkg/apis/extensions/install - - pkg/apis/extensions/v1beta1 - - pkg/apis/policy - - pkg/apis/policy/install - - pkg/apis/policy/v1beta1 - - pkg/apis/rbac - - pkg/apis/rbac/install - - pkg/apis/rbac/v1alpha1 - - pkg/apis/rbac/v1beta1 - - pkg/apis/settings - - pkg/apis/settings/install - - pkg/apis/settings/v1alpha1 - - pkg/apis/storage - - pkg/apis/storage/install - - pkg/apis/storage/v1 - - pkg/apis/storage/v1beta1 - - pkg/client/clientset_generated - - pkg/client/clientset_generated/clientset - - pkg/client/clientset_generated/clientset/fake - - pkg/client/clientset_generated/clientset/scheme - - pkg/client/clientset_generated/clientset/typed/apps/v1beta1 - - pkg/client/clientset_generated/clientset/typed/apps/v1beta1/fake - - pkg/client/clientset_generated/clientset/typed/authentication/v1 - - pkg/client/clientset_generated/clientset/typed/authentication/v1/fake - - pkg/client/clientset_generated/clientset/typed/authentication/v1beta1 - - pkg/client/clientset_generated/clientset/typed/authentication/v1beta1/fake - - pkg/client/clientset_generated/clientset/typed/authorization/v1 - - pkg/client/clientset_generated/clientset/typed/authorization/v1/fake - - pkg/client/clientset_generated/clientset/typed/authorization/v1beta1 - - pkg/client/clientset_generated/clientset/typed/authorization/v1beta1/fake - - pkg/client/clientset_generated/clientset/typed/autoscaling/v1 - - pkg/client/clientset_generated/clientset/typed/autoscaling/v1/fake - - pkg/client/clientset_generated/clientset/typed/autoscaling/v2alpha1 - - pkg/client/clientset_generated/clientset/typed/autoscaling/v2alpha1/fake - - pkg/client/clientset_generated/clientset/typed/batch/v1 - - pkg/client/clientset_generated/clientset/typed/batch/v1/fake - - pkg/client/clientset_generated/clientset/typed/batch/v2alpha1 - - pkg/client/clientset_generated/clientset/typed/batch/v2alpha1/fake - - pkg/client/clientset_generated/clientset/typed/certificates/v1beta1 - - pkg/client/clientset_generated/clientset/typed/certificates/v1beta1/fake - - pkg/client/clientset_generated/clientset/typed/core/v1 - - pkg/client/clientset_generated/clientset/typed/core/v1/fake - - pkg/client/clientset_generated/clientset/typed/extensions/v1beta1 - - pkg/client/clientset_generated/clientset/typed/extensions/v1beta1/fake - - pkg/client/clientset_generated/clientset/typed/policy/v1beta1 - - pkg/client/clientset_generated/clientset/typed/policy/v1beta1/fake - - pkg/client/clientset_generated/clientset/typed/rbac/v1alpha1 - - pkg/client/clientset_generated/clientset/typed/rbac/v1alpha1/fake - - pkg/client/clientset_generated/clientset/typed/rbac/v1beta1 - - pkg/client/clientset_generated/clientset/typed/rbac/v1beta1/fake - - pkg/client/clientset_generated/clientset/typed/settings/v1alpha1 - - pkg/client/clientset_generated/clientset/typed/settings/v1alpha1/fake - - pkg/client/clientset_generated/clientset/typed/storage/v1 - - pkg/client/clientset_generated/clientset/typed/storage/v1/fake - - pkg/client/clientset_generated/clientset/typed/storage/v1beta1 - - pkg/client/clientset_generated/clientset/typed/storage/v1beta1/fake - - pkg/client/unversioned/remotecommand - - pkg/kubelet/server/remotecommand - - pkg/util - - pkg/util/exec - - pkg/util/interrupt - - pkg/util/parsers - - pkg/util/term -testImports: [] diff --git a/glide.yaml b/glide.yaml deleted file mode 100644 index 8dfb1c4fd3..0000000000 --- a/glide.yaml +++ /dev/null @@ -1,24 +0,0 @@ -package: github.com/heketi/heketi -import: -- package: github.com/auth0/go-jwt-middleware -- package: github.com/boltdb/bolt - version: ^1.3.0 -- package: github.com/dgrijalva/jwt-go - version: ^3.0.0 -- package: github.com/gorilla/context -- package: github.com/gorilla/mux -- package: github.com/heketi/rest -- package: github.com/heketi/tests -- package: github.com/lpabon/godbc -- package: github.com/spf13/cobra - version: ca57f0f5dba473a8a58765d16d7e811fb8027add -- package: github.com/spf13/pflag - version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 -- package: github.com/urfave/negroni - version: ^0.2.0 -- package: golang.org/x/crypto - subpackages: - - ssh - - ssh/agent -- package: k8s.io/client-go - version: v3.0.0-beta.0 diff --git a/main.go b/main.go deleted file mode 100644 index e2b3469853..0000000000 --- a/main.go +++ /dev/null @@ -1,224 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package main - -import ( - "encoding/json" - "fmt" - "github.com/gorilla/mux" - "github.com/heketi/heketi/apps" - "github.com/heketi/heketi/apps/glusterfs" - "github.com/heketi/heketi/middleware" - "github.com/spf13/cobra" - "github.com/urfave/negroni" - "net/http" - "os" - "os/signal" - "syscall" - - restclient "k8s.io/client-go/rest" -) - -type Config struct { - Port string `json:"port"` - AuthEnabled bool `json:"use_auth"` - JwtConfig middleware.JwtAuthConfig `json:"jwt"` - BackupDbToKubeSecret bool `json:"backup_db_to_kube_secret"` -} - -var ( - HEKETI_VERSION = "(dev)" - configfile string - showVersion bool -) - -var RootCmd = &cobra.Command{ - Use: "heketi", - Short: "Heketi is a restful volume management server", - Long: "Heketi is a restful volume management server", - Example: "heketi --config=/config/file/path/", - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Heketi %v\n", HEKETI_VERSION) - if !showVersion { - // Check configuration file was given - if configfile == "" { - fmt.Fprintln(os.Stderr, "Please provide configuration file") - os.Exit(1) - } - } else { - // Quit here if all we needed to do was show version - os.Exit(0) - } - }, -} - -func init() { - RootCmd.Flags().StringVar(&configfile, "config", "", "Configuration file") - RootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "Show version") - RootCmd.SilenceUsage = true -} - -func setWithEnvVariables(options *Config) { - // Check for user key - env := os.Getenv("HEKETI_USER_KEY") - if "" != env { - options.AuthEnabled = true - options.JwtConfig.User.PrivateKey = env - } - - // Check for user key - env = os.Getenv("HEKETI_ADMIN_KEY") - if "" != env { - options.AuthEnabled = true - options.JwtConfig.Admin.PrivateKey = env - } - - // Check for user key - env = os.Getenv("HEKETI_HTTP_PORT") - if "" != env { - options.Port = env - } - - env = os.Getenv("HEKETI_BACKUP_DB_TO_KUBE_SECRET") - if "" != env { - options.BackupDbToKubeSecret = true - } -} - -func main() { - if err := RootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(-1) - } - - // Quit here if all we needed to do was show usage/help - if configfile == "" { - return - } - - // Read configuration - fp, err := os.Open(configfile) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: Unable to open config file %v: %v\n", - configfile, - err.Error()) - os.Exit(1) - } - defer fp.Close() - - configParser := json.NewDecoder(fp) - var options Config - if err = configParser.Decode(&options); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: Unable to parse %v: %v\n", - configfile, - err.Error()) - os.Exit(1) - } - - // Substitue values using any set environment variables - setWithEnvVariables(&options) - - // Use negroni to add middleware. Here we add two - // middlewares: Recovery and Logger, which come with - // Negroni - n := negroni.New(negroni.NewRecovery(), negroni.NewLogger()) - - // Go to the beginning of the file when we pass it - // to the application - fp.Seek(0, os.SEEK_SET) - - // Setup a new GlusterFS application - var app apps.Application - glusterfsApp := glusterfs.NewApp(fp) - if glusterfsApp == nil { - fmt.Fprintln(os.Stderr, "ERROR: Unable to start application") - os.Exit(1) - } - app = glusterfsApp - - // Add /hello router - router := mux.NewRouter() - router.Methods("GET").Path("/hello").Name("Hello").HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain; charset=UTF-8") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, "Hello from Heketi") - }) - - // Create a router and do not allow any routes - // unless defined. - heketiRouter := mux.NewRouter().StrictSlash(true) - err = app.SetRoutes(heketiRouter) - if err != nil { - fmt.Fprintln(os.Stderr, "ERROR: Unable to create http server endpoints") - os.Exit(1) - } - - // Load authorization JWT middleware - if options.AuthEnabled { - jwtauth := middleware.NewJwtAuth(&options.JwtConfig) - if jwtauth == nil { - fmt.Fprintln(os.Stderr, "ERROR: Missing JWT information in config file") - os.Exit(1) - } - - // Add Token parser - n.Use(jwtauth) - - // Add application middleware check - n.UseFunc(app.Auth) - - fmt.Println("Authorization loaded") - } - - if options.BackupDbToKubeSecret { - // Check if running in a Kubernetes environment - _, err = restclient.InClusterConfig() - if err == nil { - // Load middleware to backup database - n.UseFunc(glusterfsApp.BackupToKubernetesSecret) - } - } - - // Add all endpoints after the middleware was added - n.UseHandler(heketiRouter) - - // Setup complete routing - router.NewRoute().Handler(n) - - // Shutdown on CTRL-C signal - // For a better cleanup, we should shutdown the server and - signalch := make(chan os.Signal, 1) - signal.Notify(signalch, os.Interrupt, os.Kill, syscall.SIGINT, syscall.SIGTERM) - - // Create a channel to know if the server was unable to start - done := make(chan bool) - go func() { - // Start the server. - fmt.Printf("Listening on port %v\n", options.Port) - err = http.ListenAndServe(":"+options.Port, router) - if err != nil { - fmt.Printf("ERROR: HTTP Server error: %v\n", err) - } - done <- true - }() - - // Block here for signals and errors from the HTTP server - select { - case <-signalch: - case <-done: - } - fmt.Printf("Shutting down...\n") - - // Shutdown the application - // :TODO: Need to shutdown the server - app.Close() - -} diff --git a/middleware/jwt.go b/middleware/jwt.go deleted file mode 100644 index 9c95342777..0000000000 --- a/middleware/jwt.go +++ /dev/null @@ -1,136 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package middleware - -import ( - "crypto/sha256" - "encoding/hex" - "errors" - "fmt" - "github.com/auth0/go-jwt-middleware" - jwt "github.com/dgrijalva/jwt-go" - "github.com/gorilla/context" - "net/http" -) - -var ( - required_claims = []string{"iss", "iat", "exp"} -) - -type JwtAuth struct { - adminKey []byte - userKey []byte -} - -type Issuer struct { - PrivateKey string `json:"key"` -} - -type JwtAuthConfig struct { - Admin Issuer `json:"admin"` - User Issuer `json:"user"` -} - -func generate_qsh(r *http.Request) string { - // Please see Heketi REST API for more information - claim := r.Method + "&" + r.URL.Path - hash := sha256.New() - hash.Write([]byte(claim)) - return hex.EncodeToString(hash.Sum(nil)) -} - -func NewJwtAuth(config *JwtAuthConfig) *JwtAuth { - - if config.Admin.PrivateKey == "" || - config.User.PrivateKey == "" { - return nil - } - - j := &JwtAuth{} - j.adminKey = []byte(config.Admin.PrivateKey) - j.userKey = []byte(config.User.PrivateKey) - - return j -} - -func (j *JwtAuth) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - - // Access token from header - rawtoken, err := jwtmiddleware.FromAuthHeader(r) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Determine if we have the token - if rawtoken == "" { - http.Error(w, "Required authorization token not found", http.StatusUnauthorized) - return - } - - // Parse token - var claims jwt.MapClaims - token, err := jwt.Parse(rawtoken, func(token *jwt.Token) (interface{}, error) { - - // Verify Method - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) - } - - claims = token.Claims.(jwt.MapClaims) - if claims == nil { - return nil, fmt.Errorf("No claims found in token") - } - - // Get claims - if issuer, ok := claims["iss"]; ok { - switch issuer { - case "admin": - return j.adminKey, nil - case "user": - return j.userKey, nil - default: - return nil, errors.New("Unknown user") - } - } - - return nil, errors.New("Token missing iss claim") - }) - if err != nil { - http.Error(w, err.Error(), http.StatusUnauthorized) - return - } - - if !token.Valid { - http.Error(w, "Invalid token", http.StatusUnauthorized) - return - } - - // Check for required claims - for _, required_claim := range required_claims { - if _, ok := claims[required_claim]; !ok { - // Claim missing - http.Error(w, fmt.Sprintf("Required claim %v missing from token", required_claim), http.StatusBadRequest) - return - } - } - - // Check qsh claim - if claims["qsh"] != generate_qsh(r) { - http.Error(w, "Invalid qsh claim in token", http.StatusUnauthorized) - return - } - - // Store token in request for other middleware to access - context.Set(r, "jwt", token) - - // Everything passes call next middleware - next(w, r) -} diff --git a/middleware/jwt_test.go b/middleware/jwt_test.go deleted file mode 100644 index c6f8920897..0000000000 --- a/middleware/jwt_test.go +++ /dev/null @@ -1,528 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package middleware - -import ( - "crypto/sha256" - "encoding/hex" - jwt "github.com/dgrijalva/jwt-go" - "github.com/gorilla/context" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" - "github.com/urfave/negroni" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" -) - -func TestNewJwtAuth(t *testing.T) { - c := &JwtAuthConfig{} - c.Admin.PrivateKey = "Key" - c.User.PrivateKey = "UserKey" - - j := NewJwtAuth(c) - tests.Assert(t, string(j.adminKey) == c.Admin.PrivateKey) - tests.Assert(t, string(j.userKey) == c.User.PrivateKey) - tests.Assert(t, j != nil) -} - -func TestNewJwtAuthFailure(t *testing.T) { - c := &JwtAuthConfig{} - j := NewJwtAuth(c) - tests.Assert(t, j == nil) -} - -func TestJwtNoToken(t *testing.T) { - c := &JwtAuthConfig{} - c.Admin.PrivateKey = "Key" - c.User.PrivateKey = "UserKey" - j := NewJwtAuth(c) - tests.Assert(t, j != nil) - - n := negroni.New(j) - tests.Assert(t, n != nil) - - called := false - mw := func(rw http.ResponseWriter, r *http.Request) { - called = true - } - n.UseHandlerFunc(mw) - - ts := httptest.NewServer(n) - r, err := http.Get(ts.URL) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusUnauthorized) - tests.Assert(t, called == false) -} - -func TestJwtGarbageToken(t *testing.T) { - - // Setup jwt - c := &JwtAuthConfig{} - c.Admin.PrivateKey = "Key" - c.User.PrivateKey = "UserKey" - j := NewJwtAuth(c) - tests.Assert(t, j != nil) - - // Setup middleware framework - n := negroni.New(j) - tests.Assert(t, n != nil) - - // Create a simple middleware to check if it was called - called := false - mw := func(rw http.ResponseWriter, r *http.Request) { - called = true - } - n.UseHandlerFunc(mw) - - // Create test server - ts := httptest.NewServer(n) - - // Setup header - req, err := http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Miss 'bearer' string - req.Header.Set("Authorization", "123456770309238402938402398409234") - - // Call - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - tests.Assert(t, called == false) - - s, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "Authorization header format must be Bearer")) - - // Setup header - req, err = http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - req.Header.Set("Authorization", "bearer") - - // Call - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - tests.Assert(t, called == false) - - s, err = utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "Authorization header format must be Bearer")) - - // Setup header - req, err = http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - req.Header.Set("Authorization", "bearer 123456770309238402938402398409234") - - // Call - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusUnauthorized) - tests.Assert(t, called == false) - - s, err = utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "token contains an invalid number of segments")) - -} - -func TestJwtMissingClaims(t *testing.T) { - // Setup jwt - c := &JwtAuthConfig{} - c.Admin.PrivateKey = "Key" - c.User.PrivateKey = "UserKey" - j := NewJwtAuth(c) - tests.Assert(t, j != nil) - - // Setup middleware framework - n := negroni.New(j) - tests.Assert(t, n != nil) - - // Create a simple middleware to check if it was called - called := false - mw := func(rw http.ResponseWriter, r *http.Request) { - called = true - } - n.UseHandlerFunc(mw) - - // Create test server - ts := httptest.NewServer(n) - - // Create token with missing 'iss' claim - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "iss": "admin", - }) - tokenString, err := token.SignedString([]byte("Key")) - tests.Assert(t, err == nil) - - // Setup header - req, err := http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Miss 'bearer' string - req.Header.Set("Authorization", "bearer "+tokenString) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusBadRequest) - tests.Assert(t, called == false) - - s, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "missing from token")) - -} - -func TestJwtInvalidToken(t *testing.T) { - - // Setup jwt - c := &JwtAuthConfig{} - c.Admin.PrivateKey = "Key" - c.User.PrivateKey = "UserKey" - j := NewJwtAuth(c) - tests.Assert(t, j != nil) - - // Setup middleware framework - n := negroni.New(j) - tests.Assert(t, n != nil) - - // Create a simple middleware to check if it was called - called := false - mw := func(rw http.ResponseWriter, r *http.Request) { - called = true - } - n.UseHandlerFunc(mw) - - // Create test server - ts := httptest.NewServer(n) - - // Create token with missing 'iss' claim - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - // Set issued at time - "iat": time.Now().Unix(), - - // Set expiration - "exp": time.Now().Add(time.Second * 5).Unix(), - }) - tokenString, err := token.SignedString([]byte("Key")) - tests.Assert(t, err == nil) - - // Setup header - req, err := http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Miss 'bearer' string - req.Header.Set("Authorization", "bearer "+tokenString) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusUnauthorized) - tests.Assert(t, called == false) - - s, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "Token missing iss claim")) - - // Create an expired token - token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - // Set issuer - "iss": "admin", - - // Set issued at time - "iat": time.Now().Unix(), - - // Set expiration - "exp": time.Now().Add(time.Millisecond).Unix(), - }) - tokenString, err = token.SignedString([]byte("Key")) - tests.Assert(t, err == nil) - - // Wait a bit - time.Sleep(time.Second) - - // Setup header - req, err = http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Send request - req.Header.Set("Authorization", "bearer "+tokenString) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusUnauthorized) - tests.Assert(t, called == false) - - s, err = utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "Token is expired"), s) - - // Create missing 'qsh' claim - token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - // Set issuer - "iss": "admin", - - // Set issued at time - "iat": time.Now().Unix(), - - // Set expiration - "exp": time.Now().Add(time.Second * 10).Unix(), - }) - tokenString, err = token.SignedString([]byte("Key")) - tests.Assert(t, err == nil) - - // Setup header - req, err = http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Send request - req.Header.Set("Authorization", "bearer "+tokenString) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusUnauthorized) - tests.Assert(t, called == false) - - s, err = utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "Invalid qsh claim in token")) - - // Create an invalid 'qsh' claim - token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - // Set issuer - "iss": "admin", - - // Set issued at time - "iat": time.Now().Unix(), - - // Set expiration - "exp": time.Now().Add(time.Second * 10).Unix(), - - // Set qsh - "qsh": "12343345678945678a", - }) - tokenString, err = token.SignedString([]byte("Key")) - tests.Assert(t, err == nil) - - // Setup header - req, err = http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Send request - req.Header.Set("Authorization", "bearer "+tokenString) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusUnauthorized) - tests.Assert(t, called == false) - - s, err = utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "Invalid qsh claim in token")) - -} - -func TestJwt(t *testing.T) { - // Setup jwt - c := &JwtAuthConfig{} - c.Admin.PrivateKey = "Key" - c.User.PrivateKey = "UserKey" - j := NewJwtAuth(c) - tests.Assert(t, j != nil) - - // Setup middleware framework - n := negroni.New(j) - tests.Assert(t, n != nil) - - // Create a simple middleware to check if it was called - called := false - mw := func(rw http.ResponseWriter, r *http.Request) { - data := context.Get(r, "jwt") - tests.Assert(t, data != nil) - - token := data.(*jwt.Token) - claims := token.Claims.(jwt.MapClaims) - tests.Assert(t, claims["iss"] == "admin") - - called = true - - rw.WriteHeader(http.StatusOK) - } - n.UseHandlerFunc(mw) - - // Create test server - ts := httptest.NewServer(n) - - // Generate qsh - qshstring := "GET&/" - hash := sha256.New() - hash.Write([]byte(qshstring)) - - // Create token - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - // Set issuer - "iss": "admin", - - // Set issued at time - "iat": time.Now().Unix(), - - // Set expiration - "exp": time.Now().Add(time.Second * 10).Unix(), - - // Set qsh - "qsh": hex.EncodeToString(hash.Sum(nil)), - }) - - tokenString, err := token.SignedString([]byte("Key")) - tests.Assert(t, err == nil) - - // Setup header - req, err := http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Miss 'bearer' string - req.Header.Set("Authorization", "bearer "+tokenString) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) - tests.Assert(t, called == true) -} - -func TestJwtUnknownUser(t *testing.T) { - - // Setup jwt - c := &JwtAuthConfig{} - c.Admin.PrivateKey = "Key" - c.User.PrivateKey = "UserKey" - j := NewJwtAuth(c) - tests.Assert(t, j != nil) - - // Setup middleware framework - n := negroni.New(j) - tests.Assert(t, n != nil) - - // Create a simple middleware to check if it was called - called := false - mw := func(rw http.ResponseWriter, r *http.Request) { - called = true - } - n.UseHandlerFunc(mw) - - // Create test server - ts := httptest.NewServer(n) - - // Create token with invalid user - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - // Set issuer - "iss": "someotheruser", - - // Set issued at time - "iat": time.Now().Unix(), - - // Set expiration - "exp": time.Now().Add(time.Second * 10).Unix(), - }) - tokenString, err := token.SignedString([]byte("Key")) - tests.Assert(t, err == nil) - - // Setup header - req, err := http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Miss 'bearer' string - req.Header.Set("Authorization", "bearer "+tokenString) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusUnauthorized) - tests.Assert(t, called == false) - - s, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "Unknown user")) -} - -func TestJwtInvalidKeys(t *testing.T) { - - // Setup jwt - c := &JwtAuthConfig{} - c.Admin.PrivateKey = "Key" - c.User.PrivateKey = "UserKey" - j := NewJwtAuth(c) - tests.Assert(t, j != nil) - - // Setup middleware framework - n := negroni.New(j) - tests.Assert(t, n != nil) - - // Create a simple middleware to check if it was called - called := false - mw := func(rw http.ResponseWriter, r *http.Request) { - called = true - } - n.UseHandlerFunc(mw) - - // Create test server - ts := httptest.NewServer(n) - - // Invalid user key - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - // Set issuer - "iss": "user", - - // Set issued at time - "iat": time.Now().Unix(), - - // Set expiration - "exp": time.Now().Add(time.Second * 10).Unix(), - }) - tokenString, err := token.SignedString([]byte("Badkey")) - tests.Assert(t, err == nil) - - // Setup header - req, err := http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Miss 'bearer' string - req.Header.Set("Authorization", "bearer "+tokenString) - r, err := http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusUnauthorized) - tests.Assert(t, called == false) - - s, err := utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "signature is invalid")) - - // Send invalid admin key - token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - // Set issuer - "iss": "admin", - - // Set issued at time - "iat": time.Now().Unix(), - - // Set expiration - "exp": time.Now().Add(time.Second * 10).Unix(), - }) - tokenString, err = token.SignedString([]byte("Badkey")) - tests.Assert(t, err == nil) - - // Setup header - req, err = http.NewRequest("GET", ts.URL, nil) - tests.Assert(t, err == nil) - - // Miss 'bearer' string - req.Header.Set("Authorization", "bearer "+tokenString) - r, err = http.DefaultClient.Do(req) - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusUnauthorized) - tests.Assert(t, called == false) - - s, err = utils.GetStringFromResponse(r) - tests.Assert(t, err == nil) - tests.Assert(t, strings.Contains(s, "signature is invalid")) -} diff --git a/pkg/db/dbvolume.go b/pkg/db/dbvolume.go deleted file mode 100644 index 13f15dc4fb..0000000000 --- a/pkg/db/dbvolume.go +++ /dev/null @@ -1,14 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package db - -const ( - HeketiStorageVolumeName = "heketidbstorage" -) diff --git a/pkg/glusterfs/api/types.go b/pkg/glusterfs/api/types.go index c244f5af9b..895b920a25 100644 --- a/pkg/glusterfs/api/types.go +++ b/pkg/glusterfs/api/types.go @@ -11,7 +11,7 @@ // // -// Please see https://github.com/heketi/heketi/wiki/API +// Please see https://github.com/sigma/heketi/wiki/API // for documentation // package api diff --git a/pkg/heketitest/heketitest.go b/pkg/heketitest/heketitest.go deleted file mode 100644 index c8b1a86e7f..0000000000 --- a/pkg/heketitest/heketitest.go +++ /dev/null @@ -1,146 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package heketitest - -import ( - "bytes" - "net/http/httptest" - "os" - - "github.com/gorilla/mux" - "github.com/heketi/heketi/apps/glusterfs" - "github.com/heketi/heketi/middleware" - "github.com/heketi/tests" - "github.com/lpabon/godbc" - "github.com/urfave/negroni" -) - -// Heketi test server configuration -type HeketiMockTestServerConfig struct { - Auth bool - AdminKey string - UserKey string - Logging bool -} - -// Heketi test service metadata -type HeketiMockTestServer struct { - DbFile string - Ts *httptest.Server - App *glusterfs.App -} - -// Create a simple Heketi mock server -// -// Example: -// h := heketitest.NewHeketiMockTestServerDefault() -// defer h.Close() -// -func NewHeketiMockTestServerDefault() *HeketiMockTestServer { - return NewHeketiMockTestServer(nil) -} - -// Create a Heketi mock server -// -// Example: -// c := &heketitest.HeketiMockTestServerConfig{ -// Auth: true, -// AdminKey: "admin", -// UserKey: "user", -// Logging: false, -// } -// -// h := heketitest.NewHeketiMockTestServer(c) -// defer h.Close() -// -func NewHeketiMockTestServer( - config *HeketiMockTestServerConfig) *HeketiMockTestServer { - - if config == nil { - config = &HeketiMockTestServerConfig{} - } - - h := &HeketiMockTestServer{} - h.DbFile = tests.Tempfile() - - // Set loglevel - var loglevel string - if config.Logging { - loglevel = "debug" - } else { - loglevel = "none" - } - - // Create simple configuration for unit tests - appConfig := bytes.NewBuffer([]byte(`{ - "glusterfs" : { - "executor" : "mock", - "allocator" : "simple", - "loglevel" : "` + loglevel + `", - "db" : "` + h.DbFile + `" - } - }`)) - h.App = glusterfs.NewApp(appConfig) - if h.App == nil { - return nil - } - - // Initialize REST service - h.Ts = h.setupHeketiServer(config) - if h.Ts == nil { - return nil - } - - return h -} - -// Get http test service struct -func (h *HeketiMockTestServer) HttpServer() *httptest.Server { - return h.Ts -} - -// Get URL to test server -func (h *HeketiMockTestServer) URL() string { - return h.Ts.URL -} - -// Close database and other services -func (h *HeketiMockTestServer) Close() { - os.Remove(h.DbFile) - h.App.Close() - h.Ts.Close() -} - -func (h *HeketiMockTestServer) setupHeketiServer( - config *HeketiMockTestServerConfig) *httptest.Server { - - godbc.Require(h.App != nil) - - router := mux.NewRouter() - h.App.SetRoutes(router) - n := negroni.New() - - // Add authentication - if config.Auth { - jwtconfig := &middleware.JwtAuthConfig{} - jwtconfig.Admin.PrivateKey = config.AdminKey - jwtconfig.User.PrivateKey = config.UserKey - - // Setup middleware - n.Use(middleware.NewJwtAuth(jwtconfig)) - n.UseFunc(h.App.Auth) - } - - // Add App - n.UseHandler(router) - - // Create server - return httptest.NewServer(n) -} diff --git a/pkg/heketitest/heketitest_test.go b/pkg/heketitest/heketitest_test.go deleted file mode 100644 index 704702fa1b..0000000000 --- a/pkg/heketitest/heketitest_test.go +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package heketitest - -import ( - "testing" - - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/tests" -) - -func TestNewHeketiMockTestServer(t *testing.T) { - c := &HeketiMockTestServerConfig{ - Auth: true, - AdminKey: "admin", - UserKey: "user", - Logging: true, - } - - h := NewHeketiMockTestServer(c) - tests.Assert(t, h != nil) - tests.Assert(t, h.Ts != nil) - tests.Assert(t, h.DbFile != "") - tests.Assert(t, h.App != nil) - h.Close() - - h = NewHeketiMockTestServerDefault() - tests.Assert(t, h != nil) - tests.Assert(t, h.Ts != nil) - tests.Assert(t, h.DbFile != "") - tests.Assert(t, h.App != nil) -} - -func TestHeketiMockTestServer(t *testing.T) { - c := &HeketiMockTestServerConfig{ - Auth: true, - AdminKey: "admin", - UserKey: "user", - } - - h := NewHeketiMockTestServer(c) - defer h.Close() - - api := client.NewClient(h.URL(), "admin", "admin") - tests.Assert(t, api != nil) - - cluster, err := api.ClusterCreate() - tests.Assert(t, err == nil) - tests.Assert(t, cluster != nil) - tests.Assert(t, len(cluster.Nodes) == 0) - tests.Assert(t, len(cluster.Volumes) == 0) - - info, err := api.ClusterInfo(cluster.Id) - tests.Assert(t, err == nil) - tests.Assert(t, info.Id == cluster.Id) - tests.Assert(t, len(info.Nodes) == 0) - tests.Assert(t, len(info.Volumes) == 0) -} diff --git a/pkg/kubernetes/backupdb.go b/pkg/kubernetes/backupdb.go deleted file mode 100644 index 74956b2a6d..0000000000 --- a/pkg/kubernetes/backupdb.go +++ /dev/null @@ -1,110 +0,0 @@ -// -// Copyright (c) 2017 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package kubernetes - -import ( - "bytes" - "compress/gzip" - "fmt" - "os" - - "github.com/boltdb/bolt" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/api/v1" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" -) - -var ( - inClusterConfig = restclient.InClusterConfig - newForConfig = func(c *restclient.Config) (clientset.Interface, error) { - return clientset.NewForConfig(c) - } - getNamespace = GetNamespace - dbSecretName = "heketi-db-backup" -) - -func KubeBackupDbToSecret(db *bolt.DB) error { - - // Check if we should use another name for the heketi backup secret - env := os.Getenv("HEKETI_KUBE_DB_SECRET_NAME") - if len(env) != 0 { - dbSecretName = env - } - - // Get Kubernetes configuration - kubeConfig, err := inClusterConfig() - if err != nil { - return fmt.Errorf("Unable to get kubernetes configuration: %v", err) - } - - // Get clientset - c, err := newForConfig(kubeConfig) - if err != nil { - return fmt.Errorf("Unable to get kubernetes clientset: %v", err) - } - - // Get namespace - ns, err := getNamespace() - if err != nil { - return fmt.Errorf("Unable to get namespace: %v", err) - } - - // Create client for secrets - secrets := c.CoreV1().Secrets(ns) - if err != nil { - return fmt.Errorf("Unable to get a client to kubernetes secrets: %v", err) - } - - // Get a backup - err = db.View(func(tx *bolt.Tx) error { - var backup bytes.Buffer - - gz := gzip.NewWriter(&backup) - _, err := tx.WriteTo(gz) - if err != nil { - return fmt.Errorf("Unable to access database: %v", err) - } - if err := gz.Close(); err != nil { - return fmt.Errorf("Unable to close gzipped database: %v", err) - } - - // Create a secret with backup - secret := &v1.Secret{} - secret.Kind = "Secret" - secret.Namespace = ns - secret.APIVersion = "v1" - secret.ObjectMeta.Name = dbSecretName - secret.Data = map[string][]byte{ - "heketi.db.gz": backup.Bytes(), - } - - // Submit secret - _, err = secrets.Create(secret) - if apierrors.IsAlreadyExists(err) { - // It already exists, so just update it instead - _, err = secrets.Update(secret) - if err != nil { - return fmt.Errorf("Unable to update database to secret: %v", err) - } - } else if err != nil { - return fmt.Errorf("Unable to create database secret: %v", err) - } - - return nil - - }) - if err != nil { - return fmt.Errorf("Unable to backup database to kubernetes secret: %v", err) - } - - return nil -} diff --git a/pkg/kubernetes/backupdb_test.go b/pkg/kubernetes/backupdb_test.go deleted file mode 100644 index 348daba2f1..0000000000 --- a/pkg/kubernetes/backupdb_test.go +++ /dev/null @@ -1,341 +0,0 @@ -// -// Copyright (c) 2017 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package kubernetes - -import ( - "bytes" - "compress/gzip" - "fmt" - "io/ioutil" - "os" - "testing" - "time" - - "github.com/boltdb/bolt" - - "github.com/heketi/tests" - "k8s.io/apimachinery/pkg/apis/meta/v1" - restclient "k8s.io/client-go/rest" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" - fakeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake" -) - -func TestBackupToKubeSecretFailedClusterConfig(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create a db - db, err := bolt.Open(tmpfile, 0600, &bolt.Options{Timeout: 3 * time.Second}) - tests.Assert(t, err == nil) - defer db.Close() - - incluster_count := 0 - defer tests.Patch(&inClusterConfig, func() (*restclient.Config, error) { - incluster_count++ - return nil, fmt.Errorf("TEST") - }).Restore() - - config_count := 0 - defer tests.Patch(&newForConfig, func(c *restclient.Config) (clientset.Interface, error) { - config_count++ - return nil, nil - }).Restore() - - ns := "default" - ns_count := 0 - defer tests.Patch(&getNamespace, func() (string, error) { - ns_count++ - return ns, nil - }).Restore() - - // Try to backup - err = KubeBackupDbToSecret(db) - tests.Assert(t, incluster_count == 1) - tests.Assert(t, config_count == 0) - tests.Assert(t, ns_count == 0) - tests.Assert(t, err != nil) -} - -func TestBackupToKubeSecretFailedNewConfig(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create a db - db, err := bolt.Open(tmpfile, 0600, &bolt.Options{Timeout: 3 * time.Second}) - tests.Assert(t, err == nil) - defer db.Close() - - incluster_count := 0 - defer tests.Patch(&inClusterConfig, func() (*restclient.Config, error) { - incluster_count++ - return nil, nil - }).Restore() - - config_count := 0 - defer tests.Patch(&newForConfig, func(c *restclient.Config) (clientset.Interface, error) { - config_count++ - return nil, fmt.Errorf("TEST") - }).Restore() - - ns := "default" - ns_count := 0 - defer tests.Patch(&getNamespace, func() (string, error) { - ns_count++ - return ns, nil - }).Restore() - - // Try to backup - err = KubeBackupDbToSecret(db) - tests.Assert(t, incluster_count == 1) - tests.Assert(t, config_count == 1) - tests.Assert(t, ns_count == 0) - tests.Assert(t, err != nil) -} - -func TestBackupToKubeSecretFailedNamespace(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create a db - db, err := bolt.Open(tmpfile, 0600, &bolt.Options{Timeout: 3 * time.Second}) - tests.Assert(t, err == nil) - defer db.Close() - - incluster_count := 0 - defer tests.Patch(&inClusterConfig, func() (*restclient.Config, error) { - incluster_count++ - return nil, nil - }).Restore() - - config_count := 0 - defer tests.Patch(&newForConfig, func(c *restclient.Config) (clientset.Interface, error) { - config_count++ - return nil, nil - }).Restore() - - ns_count := 0 - defer tests.Patch(&getNamespace, func() (string, error) { - ns_count++ - return "", fmt.Errorf("TEST") - }).Restore() - - // Try to backup - err = KubeBackupDbToSecret(db) - tests.Assert(t, incluster_count == 1) - tests.Assert(t, config_count == 1) - tests.Assert(t, ns_count == 1) - tests.Assert(t, err != nil) -} - -func TestBackupToKubeSecretGoodBackup(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create a db - db, err := bolt.Open(tmpfile, 0600, &bolt.Options{Timeout: 3 * time.Second}) - tests.Assert(t, err == nil) - defer db.Close() - - incluster_count := 0 - defer tests.Patch(&inClusterConfig, func() (*restclient.Config, error) { - incluster_count++ - return nil, nil - }).Restore() - - config_count := 0 - defer tests.Patch(&newForConfig, func(c *restclient.Config) (clientset.Interface, error) { - config_count++ - return fakeclientset.NewSimpleClientset(), nil - }).Restore() - - ns := "default" - ns_count := 0 - defer tests.Patch(&getNamespace, func() (string, error) { - ns_count++ - return ns, nil - }).Restore() - - err = KubeBackupDbToSecret(db) - tests.Assert(t, incluster_count == 1) - tests.Assert(t, config_count == 1) - tests.Assert(t, ns_count == 1) - tests.Assert(t, err == nil) -} - -func TestBackupToKubeSecretVerifyBackup(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create a db - db, err := bolt.Open(tmpfile, 0600, &bolt.Options{Timeout: 3 * time.Second}) - tests.Assert(t, err == nil) - - incluster_count := 0 - defer tests.Patch(&inClusterConfig, func() (*restclient.Config, error) { - incluster_count++ - return nil, nil - }).Restore() - - config_count := 0 - fakeclient := fakeclientset.NewSimpleClientset() - defer tests.Patch(&newForConfig, func(c *restclient.Config) (clientset.Interface, error) { - config_count++ - return fakeclient, nil - }).Restore() - - ns := "default" - ns_count := 0 - defer tests.Patch(&getNamespace, func() (string, error) { - ns_count++ - return ns, nil - }).Restore() - - // Add some content to the db - err = db.Update(func(tx *bolt.Tx) error { - bucket, err := tx.CreateBucketIfNotExists([]byte("bucket")) - tests.Assert(t, err == nil) - - err = bucket.Put([]byte("key1"), []byte("value1")) - tests.Assert(t, err == nil) - - return nil - }) - tests.Assert(t, err == nil) - - // Save to a secret - err = KubeBackupDbToSecret(db) - tests.Assert(t, incluster_count == 1) - tests.Assert(t, config_count == 1) - tests.Assert(t, ns_count == 1) - tests.Assert(t, err == nil) - - // Get the secret - secret, err := fakeclient.CoreV1().Secrets(ns).Get("heketi-db-backup", v1.GetOptions{}) - tests.Assert(t, err == nil) - - // Gunzip - b := bytes.NewReader(secret.Data["heketi.db.gz"]) - gzr, err := gzip.NewReader(b) - tests.Assert(t, err == nil) - newdbData, err := ioutil.ReadAll(gzr) - tests.Assert(t, err == nil) - - // Verify - newdb := tests.Tempfile() - defer os.Remove(newdb) - err = ioutil.WriteFile(newdb, newdbData, 0644) - tests.Assert(t, err == nil) - - // Load new app with backup - db.Close() - db, err = bolt.Open(newdb, 0600, &bolt.Options{Timeout: 3 * time.Second}) - tests.Assert(t, err == nil) - defer db.Close() - - err = db.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte("bucket")) - tests.Assert(t, bucket != nil) - - val := bucket.Get([]byte("key1")) - tests.Assert(t, val != nil) - tests.Assert(t, string(val) == "value1") - - return nil - }) - tests.Assert(t, err == nil) -} - -func TestBackupToKubeSecretVerifyBackupWithName(t *testing.T) { - tmpfile := tests.Tempfile() - defer os.Remove(tmpfile) - - // Create a name in the envrionment - secretName := "mysecret" - os.Setenv("HEKETI_KUBE_DB_SECRET_NAME", secretName) - defer os.Unsetenv("HEKETI_KUBE_DB_SECRET_NAME") - - // Create a db - db, err := bolt.Open(tmpfile, 0600, &bolt.Options{Timeout: 3 * time.Second}) - tests.Assert(t, err == nil) - - incluster_count := 0 - defer tests.Patch(&inClusterConfig, func() (*restclient.Config, error) { - incluster_count++ - return nil, nil - }).Restore() - - config_count := 0 - fakeclient := fakeclientset.NewSimpleClientset() - defer tests.Patch(&newForConfig, func(c *restclient.Config) (clientset.Interface, error) { - config_count++ - return fakeclient, nil - }).Restore() - - ns := "default" - ns_count := 0 - defer tests.Patch(&getNamespace, func() (string, error) { - ns_count++ - return ns, nil - }).Restore() - - // Add some content to the db - err = db.Update(func(tx *bolt.Tx) error { - bucket, err := tx.CreateBucketIfNotExists([]byte("bucket")) - tests.Assert(t, err == nil) - - err = bucket.Put([]byte("key1"), []byte("value1")) - tests.Assert(t, err == nil) - - return nil - }) - tests.Assert(t, err == nil) - - // Save to a secret - err = KubeBackupDbToSecret(db) - tests.Assert(t, incluster_count == 1) - tests.Assert(t, config_count == 1) - tests.Assert(t, ns_count == 1) - tests.Assert(t, err == nil) - - // Get the secret - secret, err := fakeclient.CoreV1().Secrets(ns).Get(secretName, v1.GetOptions{}) - tests.Assert(t, err == nil) - - // Gunzip - b := bytes.NewReader(secret.Data["heketi.db.gz"]) - gzr, err := gzip.NewReader(b) - tests.Assert(t, err == nil) - newdbData, err := ioutil.ReadAll(gzr) - tests.Assert(t, err == nil) - - // Verify - newdb := tests.Tempfile() - defer os.Remove(newdb) - err = ioutil.WriteFile(newdb, newdbData, 0644) - tests.Assert(t, err == nil) - - // Load new app with backup - db.Close() - db, err = bolt.Open(newdb, 0600, &bolt.Options{Timeout: 3 * time.Second}) - tests.Assert(t, err == nil) - defer db.Close() - - err = db.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte("bucket")) - tests.Assert(t, bucket != nil) - - val := bucket.Get([]byte("key1")) - tests.Assert(t, val != nil) - tests.Assert(t, string(val) == "value1") - - return nil - }) - tests.Assert(t, err == nil) -} diff --git a/pkg/kubernetes/namespace.go b/pkg/kubernetes/namespace.go deleted file mode 100644 index da178e668c..0000000000 --- a/pkg/kubernetes/namespace.go +++ /dev/null @@ -1,35 +0,0 @@ -// -// Copyright (c) 2017 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package kubernetes - -import ( - "fmt" - "io/ioutil" - "strings" - - "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/v1" -) - -const ( - KubeServiceAccountDir = "/var/run/secrets/kubernetes.io/serviceaccount/" - KubeNameSpaceFile = KubeServiceAccountDir + v1.ServiceAccountNamespaceKey -) - -func GetNamespace() (string, error) { - data, err := ioutil.ReadFile(KubeNameSpaceFile) - if err != nil { - return "", fmt.Errorf("File %v not found", KubeNameSpaceFile) - } - if ns := strings.TrimSpace(string(data)); len(ns) > 0 { - return ns, nil - } - return api.NamespaceDefault, nil -} diff --git a/pkg/kubernetes/pv.go b/pkg/kubernetes/pv.go deleted file mode 100644 index b94a78fcdd..0000000000 --- a/pkg/kubernetes/pv.go +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) 2016 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package kubernetes - -import ( - "fmt" - - "github.com/heketi/heketi/pkg/glusterfs/api" - - "k8s.io/apimachinery/pkg/api/resource" - kubeapi "k8s.io/kubernetes/pkg/api/v1" -) - -func VolumeToPv(volume *api.VolumeInfoResponse, - name, endpoint string) *kubeapi.PersistentVolume { - // Initialize object - pv := &kubeapi.PersistentVolume{} - pv.Kind = "PersistentVolume" - pv.APIVersion = "v1" - pv.Spec.PersistentVolumeReclaimPolicy = kubeapi.PersistentVolumeReclaimRetain - pv.Spec.AccessModes = []kubeapi.PersistentVolumeAccessMode{ - kubeapi.ReadWriteMany, - } - pv.Spec.Capacity = make(kubeapi.ResourceList) - pv.Spec.Glusterfs = &kubeapi.GlusterfsVolumeSource{} - - // Set path - pv.Spec.Capacity[kubeapi.ResourceStorage] = - resource.MustParse(fmt.Sprintf("%vGi", volume.Size)) - pv.Spec.Glusterfs.Path = volume.Name - - // Set name - if name == "" { - pv.ObjectMeta.Name = "glusterfs-" + volume.Id[:8] - } else { - pv.ObjectMeta.Name = name - - } - - // Set endpoint - if endpoint == "" { - pv.Spec.Glusterfs.EndpointsName = "TYPE ENDPOINT HERE" - } else { - pv.Spec.Glusterfs.EndpointsName = endpoint - } - - return pv -} diff --git a/pkg/utils/bodystring.go b/pkg/utils/bodystring.go index 468db04e45..5eff7be69f 100644 --- a/pkg/utils/bodystring.go +++ b/pkg/utils/bodystring.go @@ -1,10 +1,17 @@ // // Copyright (c) 2015 The heketi Authors // -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. +// 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 utils @@ -14,7 +21,6 @@ import ( "io" "io/ioutil" "net/http" - "strings" ) // Return the body from a response as a string @@ -33,5 +39,5 @@ func GetErrorFromResponse(r *http.Response) error { if err != nil { return err } - return errors.New(strings.TrimSpace(s)) + return errors.New(s) } diff --git a/pkg/utils/jsonutils.go b/pkg/utils/jsonutils.go index 77781d9c93..060409b047 100644 --- a/pkg/utils/jsonutils.go +++ b/pkg/utils/jsonutils.go @@ -1,10 +1,17 @@ // // Copyright (c) 2015 The heketi Authors // -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. +// 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 utils diff --git a/pkg/utils/log.go b/pkg/utils/log.go deleted file mode 100644 index a9451ed7e5..0000000000 --- a/pkg/utils/log.go +++ /dev/null @@ -1,155 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package utils - -import ( - "fmt" - "io" - "log" - "os" - "runtime" - "strings" - - "github.com/lpabon/godbc" -) - -type LogLevel int - -// Log levels -const ( - LEVEL_NOLOG LogLevel = iota - LEVEL_CRITICAL - LEVEL_ERROR - LEVEL_WARNING - LEVEL_INFO - LEVEL_DEBUG -) - -var ( - stderr io.Writer = os.Stderr - stdout io.Writer = os.Stdout -) - -type Logger struct { - critlog, errorlog, infolog *log.Logger - debuglog, warninglog *log.Logger - - level LogLevel -} - -func logWithLongFile(l *log.Logger, format string, v ...interface{}) { - _, file, line, _ := runtime.Caller(2) - - // Shorten the path. - // From - // /builddir/build/BUILD/heketi-3f4a5b1b6edff87232e8b24533c53b4151ebd9c7/src/github.com/heketi/heketi/apps/glusterfs/volume_entry.go - // to - // src/github.com/heketi/heketi/apps/glusterfs/volume_entry.go - i := strings.Index(file, "/src/") - if i == -1 { - i = 0 - } - - l.Print(fmt.Sprintf("%v:%v: ", file[i:], line) + - fmt.Sprintf(format, v...)) -} - -// Create a new logger -func NewLogger(prefix string, level LogLevel) *Logger { - godbc.Require(level >= 0, level) - godbc.Require(level <= LEVEL_DEBUG, level) - - l := &Logger{} - - if level == LEVEL_NOLOG { - l.level = LEVEL_DEBUG - } else { - l.level = level - } - - l.critlog = log.New(stderr, prefix+" CRITICAL ", log.LstdFlags) - l.errorlog = log.New(stderr, prefix+" ERROR ", log.LstdFlags) - l.warninglog = log.New(stdout, prefix+" WARNING ", log.LstdFlags) - l.infolog = log.New(stdout, prefix+" INFO ", log.LstdFlags) - l.debuglog = log.New(stdout, prefix+" DEBUG ", log.LstdFlags) - - godbc.Ensure(l.critlog != nil) - godbc.Ensure(l.errorlog != nil) - godbc.Ensure(l.warninglog != nil) - godbc.Ensure(l.infolog != nil) - godbc.Ensure(l.debuglog != nil) - - return l -} - -// Return current level -func (l *Logger) Level() LogLevel { - return l.level -} - -// Set level -func (l *Logger) SetLevel(level LogLevel) { - l.level = level -} - -// Log critical information -func (l *Logger) Critical(format string, v ...interface{}) { - if l.level >= LEVEL_CRITICAL { - logWithLongFile(l.critlog, format, v...) - } -} - -// Log error string -func (l *Logger) LogError(format string, v ...interface{}) error { - if l.level >= LEVEL_ERROR { - logWithLongFile(l.errorlog, format, v...) - } - - return fmt.Errorf(format, v...) -} - -// Log error variable -func (l *Logger) Err(err error) error { - if l.level >= LEVEL_ERROR { - logWithLongFile(l.errorlog, "%v", err) - } - - return err -} - -// Log warning information -func (l *Logger) Warning(format string, v ...interface{}) { - if l.level >= LEVEL_WARNING { - l.warninglog.Printf(format, v...) - } -} - -// Log error variable as a warning -func (l *Logger) WarnErr(err error) error { - if l.level >= LEVEL_WARNING { - logWithLongFile(l.warninglog, "%v", err) - } - - return err -} - -// Log string -func (l *Logger) Info(format string, v ...interface{}) { - if l.level >= LEVEL_INFO { - l.infolog.Printf(format, v...) - } -} - -// Log string as debug -func (l *Logger) Debug(format string, v ...interface{}) { - if l.level >= LEVEL_DEBUG { - logWithLongFile(l.debuglog, format, v...) - } -} diff --git a/pkg/utils/log_test.go b/pkg/utils/log_test.go deleted file mode 100644 index 508335e8d4..0000000000 --- a/pkg/utils/log_test.go +++ /dev/null @@ -1,202 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package utils - -import ( - "bytes" - "errors" - "fmt" - "github.com/heketi/tests" - "strings" - "testing" -) - -func TestLogLevel(t *testing.T) { - var testbuffer bytes.Buffer - - defer tests.Patch(&stdout, &testbuffer).Restore() - - l := NewLogger("[testing]", LEVEL_INFO) - tests.Assert(t, LEVEL_INFO == l.level) - tests.Assert(t, LEVEL_INFO == l.Level()) - - l.SetLevel(LEVEL_CRITICAL) - tests.Assert(t, LEVEL_CRITICAL == l.level) - tests.Assert(t, LEVEL_CRITICAL == l.Level()) - -} - -func TestLogInfo(t *testing.T) { - var testbuffer bytes.Buffer - - defer tests.Patch(&stdout, &testbuffer).Restore() - - l := NewLogger("[testing]", LEVEL_INFO) - - l.Info("Hello %v", "World") - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] INFO "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "Hello World"), testbuffer.String()) - testbuffer.Reset() - - l.SetLevel(LEVEL_WARNING) - l.Info("TEXT") - tests.Assert(t, testbuffer.Len() == 0) -} - -func TestLogDebug(t *testing.T) { - var testbuffer bytes.Buffer - - defer tests.Patch(&stdout, &testbuffer).Restore() - - l := NewLogger("[testing]", LEVEL_DEBUG) - - l.Debug("Hello %v", "World") - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] DEBUG "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "Hello World"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "log_test.go"), testbuffer.String()) - - // [testing] DEBUG 2016/04/28 15:25:08 /src/github.com/heketi/heketi/pkg/utils/log_test.go:66: Hello World - fileinfo := strings.Split(testbuffer.String(), " ")[4] - filename := strings.Split(fileinfo, ":")[0] - - // Need to check that it starts with /src/github.com - tests.Assert(t, strings.HasPrefix(filename, "/src/github.com/")) - tests.Assert(t, strings.HasSuffix(filename, "/pkg/utils/log_test.go")) - testbuffer.Reset() - - l.SetLevel(LEVEL_INFO) - l.Debug("TEXT") - tests.Assert(t, testbuffer.Len() == 0) -} - -func TestLogWarning(t *testing.T) { - var testbuffer bytes.Buffer - - defer tests.Patch(&stdout, &testbuffer).Restore() - - l := NewLogger("[testing]", LEVEL_DEBUG) - - l.Warning("Hello %v", "World") - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] WARNING "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "Hello World"), testbuffer.String()) - testbuffer.Reset() - - l.SetLevel(LEVEL_ERROR) - l.Warning("TEXT") - tests.Assert(t, testbuffer.Len() == 0) -} - -func TestLogWarnErr(t *testing.T) { - var testbuffer bytes.Buffer - - defer tests.Patch(&stdout, &testbuffer).Restore() - - l := NewLogger("[testing]", LEVEL_DEBUG) - - ErrSample := errors.New("TEST ERROR") - err := l.WarnErr(ErrSample) - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] WARNING "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "TEST ERROR"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "log_test.go"), testbuffer.String()) - tests.Assert(t, err == ErrSample) - testbuffer.Reset() - - err = l.WarnErr(fmt.Errorf("GOT %v", err)) - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] WARNING "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "TEST ERROR"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "log_test.go"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "GOT"), testbuffer.String()) - tests.Assert(t, err != ErrSample) - tests.Assert(t, err != nil) - tests.Assert(t, strings.Contains(err.Error(), "GOT TEST ERROR"), err) - testbuffer.Reset() - - l.SetLevel(LEVEL_ERROR) - l.WarnErr(ErrSample) - tests.Assert(t, testbuffer.Len() == 0) -} - -func TestLogError(t *testing.T) { - var testbuffer bytes.Buffer - - defer tests.Patch(&stderr, &testbuffer).Restore() - - l := NewLogger("[testing]", LEVEL_DEBUG) - - err := l.LogError("Hello %v", "World") - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] ERROR "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "Hello World"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "log_test.go"), testbuffer.String()) - testbuffer.Reset() - testbuffer.Reset() - tests.Assert(t, err != nil) - tests.Assert(t, strings.Contains(err.Error(), "Hello World"), err) - - err = errors.New("BAD") - l.Err(err) - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] ERROR "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "BAD"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "log_test.go"), testbuffer.String()) - testbuffer.Reset() - - l.SetLevel(LEVEL_CRITICAL) - l.LogError("TEXT") - tests.Assert(t, testbuffer.Len() == 0) - -} - -func TestLogCritical(t *testing.T) { - var testbuffer bytes.Buffer - - defer tests.Patch(&stderr, &testbuffer).Restore() - - l := NewLogger("[testing]", LEVEL_DEBUG) - - l.LogError("Hello %v", "World") - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] ERROR "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "Hello World"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "log_test.go"), testbuffer.String()) - testbuffer.Reset() - - l.SetLevel(LEVEL_NOLOG) - l.LogError("TEXT") - tests.Assert(t, testbuffer.Len() == 0) - -} - -func TestLogErr(t *testing.T) { - var testbuffer bytes.Buffer - - defer tests.Patch(&stderr, &testbuffer).Restore() - - l := NewLogger("[testing]", LEVEL_DEBUG) - - ErrSample := errors.New("TEST ERROR") - err := l.Err(ErrSample) - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] ERROR "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "TEST ERROR"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "log_test.go"), testbuffer.String()) - tests.Assert(t, err == ErrSample) - testbuffer.Reset() - - err = l.Err(fmt.Errorf("GOT %v", err)) - tests.Assert(t, strings.Contains(testbuffer.String(), "[testing] ERROR "), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "TEST ERROR"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "log_test.go"), testbuffer.String()) - tests.Assert(t, strings.Contains(testbuffer.String(), "GOT"), testbuffer.String()) - tests.Assert(t, err != ErrSample) - tests.Assert(t, err != nil) - tests.Assert(t, strings.Contains(err.Error(), "GOT TEST ERROR"), err) - testbuffer.Reset() - - l.SetLevel(LEVEL_NOLOG) - l.Err(ErrSample) - tests.Assert(t, testbuffer.Len() == 0) -} diff --git a/pkg/utils/sortedstrings.go b/pkg/utils/sortedstrings.go deleted file mode 100644 index 985230d9f3..0000000000 --- a/pkg/utils/sortedstrings.go +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package utils - -import ( - "sort" -) - -// Check if a sorted string list has a string -func SortedStringHas(s sort.StringSlice, x string) bool { - index := s.Search(x) - if index == len(s) { - return false - } - return s[s.Search(x)] == x -} - -// Delete a string from a sorted string list -func SortedStringsDelete(s sort.StringSlice, x string) sort.StringSlice { - index := s.Search(x) - if len(s) != index && s[index] == x { - s = append(s[:index], s[index+1:]...) - } - - return s -} diff --git a/pkg/utils/sortedstrings_test.go b/pkg/utils/sortedstrings_test.go deleted file mode 100644 index a5d5b1d569..0000000000 --- a/pkg/utils/sortedstrings_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package utils - -import ( - "github.com/heketi/tests" - "sort" - "testing" -) - -func TestSortedStringsHas(t *testing.T) { - s := sort.StringSlice{"z", "b", "a"} - s.Sort() - tests.Assert(t, len(s) == 3) - tests.Assert(t, s[0] == "a") - tests.Assert(t, s[1] == "b") - tests.Assert(t, s[2] == "z") - - tests.Assert(t, SortedStringHas(s, "a")) - tests.Assert(t, SortedStringHas(s, "b")) - tests.Assert(t, SortedStringHas(s, "z")) - tests.Assert(t, !SortedStringHas(s, "c")) - tests.Assert(t, !SortedStringHas(s, "zz")) -} - -func TestSortedStringsDelete(t *testing.T) { - s := sort.StringSlice{"z", "b", "a"} - s.Sort() - tests.Assert(t, len(s) == 3) - tests.Assert(t, s[0] == "a") - tests.Assert(t, s[1] == "b") - tests.Assert(t, s[2] == "z") - - tests.Assert(t, SortedStringHas(s, "a")) - tests.Assert(t, SortedStringHas(s, "b")) - tests.Assert(t, SortedStringHas(s, "z")) - tests.Assert(t, !SortedStringHas(s, "c")) - tests.Assert(t, !SortedStringHas(s, "zz")) - - s = SortedStringsDelete(s, "notthere") - tests.Assert(t, len(s) == 3) - s = SortedStringsDelete(s, "zzzznotthere") - tests.Assert(t, len(s) == 3) - s = SortedStringsDelete(s, "1azzzznotthere") - tests.Assert(t, len(s) == 3) - tests.Assert(t, SortedStringHas(s, "a")) - tests.Assert(t, SortedStringHas(s, "b")) - tests.Assert(t, SortedStringHas(s, "z")) - tests.Assert(t, !SortedStringHas(s, "c")) - tests.Assert(t, !SortedStringHas(s, "zz")) - - s = SortedStringsDelete(s, "z") - tests.Assert(t, len(s) == 2) - tests.Assert(t, SortedStringHas(s, "a")) - tests.Assert(t, SortedStringHas(s, "b")) - tests.Assert(t, !SortedStringHas(s, "z")) - tests.Assert(t, !SortedStringHas(s, "c")) - tests.Assert(t, !SortedStringHas(s, "zz")) - - s = SortedStringsDelete(s, "a") - tests.Assert(t, len(s) == 1) - tests.Assert(t, !SortedStringHas(s, "a")) - tests.Assert(t, SortedStringHas(s, "b")) - tests.Assert(t, !SortedStringHas(s, "z")) - tests.Assert(t, !SortedStringHas(s, "c")) - tests.Assert(t, !SortedStringHas(s, "zz")) - - s = SortedStringsDelete(s, "b") - tests.Assert(t, len(s) == 0) - tests.Assert(t, !SortedStringHas(s, "a")) - tests.Assert(t, !SortedStringHas(s, "b")) - tests.Assert(t, !SortedStringHas(s, "z")) - tests.Assert(t, !SortedStringHas(s, "c")) - tests.Assert(t, !SortedStringHas(s, "zz")) - -} diff --git a/pkg/utils/ssh/ssh.go b/pkg/utils/ssh/ssh.go deleted file mode 100644 index 8b8772957c..0000000000 --- a/pkg/utils/ssh/ssh.go +++ /dev/null @@ -1,192 +0,0 @@ -// -// Copyright (c) 2014 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package ssh - -import ( - "bytes" - "errors" - "fmt" - "io/ioutil" - "log" - "net" - "os" - "time" - - "github.com/heketi/heketi/pkg/utils" - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/agent" -) - -type SshExec struct { - clientConfig *ssh.ClientConfig - logger *utils.Logger -} - -func getKeyFile(file string) (key ssh.Signer, err error) { - buf, err := ioutil.ReadFile(file) - if err != nil { - return - } - key, err = ssh.ParsePrivateKey(buf) - if err != nil { - fmt.Print(err) - return - } - return -} - -func NewSshExecWithAuth(logger *utils.Logger, user string) *SshExec { - - sshexec := &SshExec{} - sshexec.logger = logger - - authSocket := os.Getenv("SSH_AUTH_SOCK") - if authSocket == "" { - log.Fatal("SSH_AUTH_SOCK required, check that your ssh agent is running") - return nil - } - - agentUnixSock, err := net.Dial("unix", authSocket) - if err != nil { - log.Fatal(err) - return nil - } - - agent := agent.NewClient(agentUnixSock) - signers, err := agent.Signers() - if err != nil { - log.Fatal(err) - return nil - } - - sshexec.clientConfig = &ssh.ClientConfig{ - User: user, - Auth: []ssh.AuthMethod{ssh.PublicKeys(signers...)}, - } - - return sshexec -} - -func NewSshExecWithKeyFile(logger *utils.Logger, user string, file string) *SshExec { - - var key ssh.Signer - var err error - - sshexec := &SshExec{} - sshexec.logger = logger - - // Now in the main function DO: - if key, err = getKeyFile(file); err != nil { - fmt.Println("Unable to get keyfile") - return nil - } - // Define the Client Config as : - sshexec.clientConfig = &ssh.ClientConfig{ - User: user, - Auth: []ssh.AuthMethod{ - ssh.PublicKeys(key), - }, - } - - return sshexec -} - -// This function requires the password string to be crypt encrypted -func NewSshExecWithPassword(logger *utils.Logger, user string, password string) *SshExec { - - sshexec := &SshExec{} - sshexec.logger = logger - - // Define the Client Config as : - sshexec.clientConfig = &ssh.ClientConfig{ - User: user, - Auth: []ssh.AuthMethod{ssh.Password(password)}, - } - - return sshexec -} - -// This function was based from https://github.com/coreos/etcd-manager/blob/master/main.go -func (s *SshExec) ConnectAndExec(host string, commands []string, timeoutMinutes int, useSudo bool) ([]string, error) { - - buffers := make([]string, len(commands)) - - // :TODO: Will need a timeout here in case the server does not respond - client, err := ssh.Dial("tcp", host, s.clientConfig) - if err != nil { - s.logger.Warning("Failed to create SSH connection to %v: %v", host, err) - return nil, err - } - defer client.Close() - - // Execute each command - for index, command := range commands { - - session, err := client.NewSession() - if err != nil { - s.logger.LogError("Unable to create SSH session: %v", err) - return nil, err - } - defer session.Close() - - // Create a buffer to trap session output - var b bytes.Buffer - var berr bytes.Buffer - session.Stdout = &b - session.Stderr = &berr - - // Execute command in a shell - command = "/bin/bash -c '" + command + "'" - - // Check if we need to use sudo for the entire command - if useSudo { - command = "sudo " + command - } - - // Execute command - err = session.Start(command) - if err != nil { - return nil, err - } - - // Spawn function to wait for results - errch := make(chan error) - go func() { - errch <- session.Wait() - }() - - // Set the timeout - timeout := time.After(time.Minute * time.Duration(timeoutMinutes)) - - // Wait for either the command completion or timeout - select { - case err := <-errch: - if err != nil { - s.logger.LogError("Failed to run command [%v] on %v: Err[%v]: Stdout [%v]: Stderr [%v]", - command, host, err, b.String(), berr.String()) - return nil, fmt.Errorf("%s", berr.String()) - } - s.logger.Debug("Host: %v Command: %v\nResult: %v", host, command, b.String()) - buffers[index] = b.String() - - case <-timeout: - s.logger.LogError("Timeout on command [%v] on %v: Err[%v]: Stdout [%v]: Stderr [%v]", - command, host, err, b.String(), berr.String()) - err := session.Signal(ssh.SIGKILL) - if err != nil { - s.logger.LogError("Unable to send kill signal to command [%v] on host [%v]: %v", - command, host, err) - } - return nil, errors.New("SSH command timeout") - } - } - - return buffers, nil -} diff --git a/pkg/utils/statusgroup.go b/pkg/utils/statusgroup.go deleted file mode 100644 index 7c678a5031..0000000000 --- a/pkg/utils/statusgroup.go +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package utils - -import ( - "sync" -) - -type StatusGroup struct { - wg sync.WaitGroup - results chan error - err error -} - -// Create a new goroutine error status collector -func NewStatusGroup() *StatusGroup { - s := &StatusGroup{} - s.results = make(chan error, 1) - - return s -} - -// Adds to the number of goroutines it should wait -func (s *StatusGroup) Add(delta int) { - s.wg.Add(delta) -} - -// Removes the number of pending goroutines by one -func (s *StatusGroup) Done() { - s.wg.Done() -} - -// Goroutine can return an error back to caller -func (s *StatusGroup) Err(err error) { - s.results <- err -} - -// Returns an error if any of the spawned goroutines -// return an error. Only the last error is saved. -// This function must be called last after the last -// s.Register() function -func (s *StatusGroup) Result() error { - - // This goroutine will wait until all - // other privously spawned goroutines finish. - // Once they finish, it will close the channel - go func() { - s.wg.Wait() - close(s.results) - }() - - // Read from the channel until close - for err := range s.results { - // Only save the last one - if err != nil { - s.err = err - } - } - - return s.err -} diff --git a/pkg/utils/statusgroup_test.go b/pkg/utils/statusgroup_test.go deleted file mode 100644 index f213051210..0000000000 --- a/pkg/utils/statusgroup_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package utils - -import ( - "errors" - "fmt" - "github.com/heketi/tests" - "testing" - "time" -) - -func TestNewStatusGroup(t *testing.T) { - s := NewStatusGroup() - tests.Assert(t, s != nil) - tests.Assert(t, s.results != nil) - tests.Assert(t, len(s.results) == 0) - tests.Assert(t, s.err == nil) -} - -func TestStatusGroupSuccess(t *testing.T) { - - s := NewStatusGroup() - - max := 100 - s.Add(max) - - for i := 0; i < max; i++ { - go func(value int) { - defer s.Done() - time.Sleep(time.Millisecond * 1 * time.Duration(value)) - }(i) - } - - err := s.Result() - tests.Assert(t, err == nil) - -} - -func TestStatusGroupFailure(t *testing.T) { - s := NewStatusGroup() - - for i := 0; i < 100; i++ { - - s.Add(1) - go func(value int) { - defer s.Done() - time.Sleep(time.Millisecond * 1 * time.Duration(value)) - if value%10 == 0 { - s.Err(errors.New(fmt.Sprintf("Err: %v", value))) - } - - }(i) - - } - - err := s.Result() - - tests.Assert(t, err != nil) - tests.Assert(t, err.Error() == "Err: 90", err) - -} diff --git a/pkg/utils/stringset.go b/pkg/utils/stringset.go deleted file mode 100644 index 8d7f3ca780..0000000000 --- a/pkg/utils/stringset.go +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package utils - -import ( - "sort" -) - -type StringSet struct { - Set sort.StringSlice -} - -// Create a string set. -// -// A string set is a list where each element appears only once -func NewStringSet() *StringSet { - return &StringSet{ - Set: make(sort.StringSlice, 0), - } -} - -// Add a string to the string set -func (s *StringSet) Add(v string) { - if !SortedStringHas(s.Set, v) { - s.Set = append(s.Set, v) - s.Set.Sort() - } -} - -// Return string list -func (s *StringSet) Strings() []string { - return s.Set -} - -func (s *StringSet) Len() int { - return len(s.Set) -} diff --git a/pkg/utils/stringset_test.go b/pkg/utils/stringset_test.go deleted file mode 100644 index f2761a029f..0000000000 --- a/pkg/utils/stringset_test.go +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package utils - -import ( - "github.com/heketi/tests" - "testing" -) - -func TestNewStringSet(t *testing.T) { - s := NewStringSet() - tests.Assert(t, s.Set != nil) - tests.Assert(t, len(s.Set) == 0) -} - -func TestStringSet(t *testing.T) { - s := NewStringSet() - - s.Add("one") - s.Add("two") - s.Add("three") - tests.Assert(t, s.Len() == 3) - tests.Assert(t, SortedStringHas(s.Set, "one")) - tests.Assert(t, SortedStringHas(s.Set, "two")) - tests.Assert(t, SortedStringHas(s.Set, "three")) - - s.Add("one") - tests.Assert(t, s.Len() == 3) - tests.Assert(t, SortedStringHas(s.Set, "one")) - tests.Assert(t, SortedStringHas(s.Set, "two")) - tests.Assert(t, SortedStringHas(s.Set, "three")) - - s.Add("three") - tests.Assert(t, s.Len() == 3) - tests.Assert(t, SortedStringHas(s.Set, "one")) - tests.Assert(t, SortedStringHas(s.Set, "two")) - tests.Assert(t, SortedStringHas(s.Set, "three")) - - s.Add("four") - tests.Assert(t, s.Len() == 4) - tests.Assert(t, SortedStringHas(s.Set, "one")) - tests.Assert(t, SortedStringHas(s.Set, "two")) - tests.Assert(t, SortedStringHas(s.Set, "three")) - tests.Assert(t, SortedStringHas(s.Set, "four")) -} diff --git a/pkg/utils/stringstack.go b/pkg/utils/stringstack.go deleted file mode 100644 index c1b48e219a..0000000000 --- a/pkg/utils/stringstack.go +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// - -package utils - -type StringStack struct { - list []string -} - -func NewStringStack() *StringStack { - a := &StringStack{} - a.list = make([]string, 0) - return a -} - -func (a *StringStack) IsEmpty() bool { - return len(a.list) == 0 -} - -func (a *StringStack) Pop() (x string) { - x, a.list = a.list[0], a.list[1:len(a.list)] - return -} - -func (a *StringStack) Push(x string) { - a.list = append(a.list, x) -} diff --git a/pkg/utils/uuid.go b/pkg/utils/uuid.go deleted file mode 100644 index 39e743d4da..0000000000 --- a/pkg/utils/uuid.go +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. - -package utils - -// From http://www.ashishbanerjee.com/home/go/go-generate-uuid - -import ( - "crypto/rand" - "encoding/hex" - "github.com/lpabon/godbc" -) - -// Return a 16-byte uuid -func GenUUID() string { - uuid := make([]byte, 16) - n, err := rand.Read(uuid) - godbc.Check(n == len(uuid), n, len(uuid)) - godbc.Check(err == nil, err) - - return hex.EncodeToString(uuid) -} diff --git a/tests/functional/README.md b/tests/functional/README.md deleted file mode 100644 index fe2e3a7c2e..0000000000 --- a/tests/functional/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Automated Functional Test - -## Requirements -You will need a system with at least 16G of ram. If you only have 8G, you can at least run the TestSmokeTest. - -### Packages - -``` -# dnf -y install docker libvirt qemu-kvm \ - ansible vagrant vagrant-libvirt go git make -``` - -Make sure docker and libvirt deamons are running - -### User - -The user running the tests must have password-less sudo access - -## Setup - -``` -$ mkdir go -$ cd go -$ export GOPATH=$PWD -$ export PATH=$PATH:$GOPATH/bin -$ curl https://glide.sh/get | sh -$ mkdir -p src/github.com/heketi -$ cd src/github.com/heketi -$ git clone https://github.com/heketi/heketi.git -``` - -## Running - -Run the entire suite: - -``` -$ cd $GOPATH/src/github.com/heketi/heketi/tests/functional -$ ./run.sh -``` - -To run a specific functional test, go into that functionl test's directory and type: - -``` -$ ./run.sh -``` - -## Adding new tests - -Create a new directory under tests/functional matching the style of -the current ones. Create a shell script called `run.sh` in that directory -which will run that test. diff --git a/tests/functional/TestKubeSmokeTest/md5sums b/tests/functional/TestKubeSmokeTest/md5sums deleted file mode 100644 index 54b3885866..0000000000 --- a/tests/functional/TestKubeSmokeTest/md5sums +++ /dev/null @@ -1,4 +0,0 @@ -8f5de36e01e8f160e91681cbc8355a64 /usr/local/bin/docker-machine -519d29300df740a73c90d048e5df43fb /usr/local/bin/docker-machine-driver-kvm -eab2ea4df73dd3cbbe147954a161d10f /usr/local/bin/kubectl -b7490e41854ef77bbd89f46ce4bc2edd /usr/local/bin/minikube diff --git a/tests/functional/TestKubeSmokeTest/mock-endpoints.json b/tests/functional/TestKubeSmokeTest/mock-endpoints.json deleted file mode 100644 index 265bbf79de..0000000000 --- a/tests/functional/TestKubeSmokeTest/mock-endpoints.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "kind": "Endpoints", - "apiVersion": "v1", - "metadata": { - "name": "glusterfs-cluster" - }, - "subsets": [{ - "addresses": [{ - "ip": "192.168.10.32" - }], - "ports": [{ - "port": 1 - }] - }] -} diff --git a/tests/functional/TestKubeSmokeTest/mock-topology.json b/tests/functional/TestKubeSmokeTest/mock-topology.json deleted file mode 100644 index f517d6f328..0000000000 --- a/tests/functional/TestKubeSmokeTest/mock-topology.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "clusters": [ - { - "nodes": [ - { - "node": { - "hostnames": { - "manage": [ - "192.168.10.100" - ], - "storage": [ - "192.168.10.100" - ] - }, - "zone": 1 - }, - "devices": [ - "/dev/sdb", - "/dev/sdc", - "/dev/sdd" - ] - }, - { - "node": { - "hostnames": { - "manage": [ - "192.168.10.101" - ], - "storage": [ - "192.168.10.101" - ] - }, - "zone": 2 - }, - "devices": [ - "/dev/sdb", - "/dev/sdc", - "/dev/sdd" - ] - }, - { - "node": { - "hostnames": { - "manage": [ - "192.168.10.102" - ], - "storage": [ - "192.168.10.102" - ] - }, - "zone": 1 - }, - "devices": [ - "/dev/sdb", - "/dev/sdc", - "/dev/sdd" - ] - }, - { - "node": { - "hostnames": { - "manage": [ - "192.168.10.103" - ], - "storage": [ - "192.168.10.103" - ] - }, - "zone": 2 - }, - "devices": [ - "/dev/sdb", - "/dev/sdc", - "/dev/sdd" - ] - } - ] - } - ] -} diff --git a/tests/functional/TestKubeSmokeTest/pvc.json b/tests/functional/TestKubeSmokeTest/pvc.json deleted file mode 100644 index efd5a6fe8e..0000000000 --- a/tests/functional/TestKubeSmokeTest/pvc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "kind" : "PersistentVolumeClaim", - "apiVersion": "v1", - "metadata": { - "name": "claim1", - "annotations": { - "volume.beta.kubernetes.io/storage-class": "slow" - } - }, - "spec": { - "accessModes": [ - "ReadWriteOnce" - ], - "resources": { - "requests": { - "storage": "100Gi" - } - } - } -} diff --git a/tests/functional/TestKubeSmokeTest/run.sh b/tests/functional/TestKubeSmokeTest/run.sh deleted file mode 100755 index 197f2bdb02..0000000000 --- a/tests/functional/TestKubeSmokeTest/run.sh +++ /dev/null @@ -1,162 +0,0 @@ -#!/bin/sh - -TOP=../../.. -CURRENT_DIR=`pwd` -RESOURCES_DIR=$CURRENT_DIR/resources -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -HEKETI_DOCKER_IMG=heketi-docker-ci.img -DOCKERDIR=$TOP/extras/docker -CLIENTDIR=$TOP/client/cli/go - -source ${FUNCTIONAL_DIR}/lib.sh - -### VERSIONS ### -KUBEVERSION=v1.4.3 - -docker_set_env() { - - ### - ### CENTOS WORKAROUND ### - ### Suffering from same bug as https://github.com/getcarina/carina/issues/112 - ### - if grep -q -s "CentOS\|Fedora" /etc/redhat-release ; then - echo "CentOS/Fedora DOCKER WORKAROUND" - curl -sL https://download.getcarina.com/dvm/latest/install.sh | sh - eval $(minikube docker-env) - source ~/.dvm/dvm.sh - dvm install 1.10.3 - dvm use 1.10.3 - else - eval $(minikube docker-env) - fi -} - -copy_docker_files() { - docker_set_env - docker load -i $heketi_docker || fail "Unable to load Heketi docker image" -} - -build_docker_file(){ - echo "Create Heketi Docker image" - heketi_docker=$RESOURCES_DIR/$HEKETI_DOCKER_IMG - if [ ! -f "$heketi_docker" ] ; then - cd $DOCKERDIR/ci - cp $TOP/heketi $DOCKERDIR/ci || fail "Unable to copy $TOP/heketi to $DOCKERDIR/ci" - _sudo docker build --rm --tag heketi/heketi:ci . || fail "Unable to create docker container" - _sudo docker save -o $HEKETI_DOCKER_IMG heketi/heketi:ci || fail "Unable to save docker image" - _sudo chmod 0666 $HEKETI_DOCKER_IMG || fail "Unable to chmod docker image" - cp $HEKETI_DOCKER_IMG $heketi_docker || fail "Unable to copy image" - _sudo docker rmi heketi/heketi:ci - cd $CURRENT_DIR - fi - copy_docker_files -} - -build_heketi() { - cd $TOP - make || fail "Unable to build heketi" - cd $CURRENT_DIR -} - -copy_client_files() { - cp $CLIENTDIR/heketi-cli $RESOURCES_DIR || fail "Unable to copy client files" - cp $TOP/extras/kubernetes/* $RESOURCES_DIR || fail "Unable to copy kubernetes deployment files" -} - -teardown() { - if [ -x /usr/local/bin/minikube ] ; then - minikube stop > /dev/null - minikube delete > /dev/null - fi - rm -rf $RESOURCES_DIR > /dev/null -} - -setup_minikube() { - if [ ! -d $RESOURCES_DIR ] ; then - mkdir $RESOURCES_DIR - fi - - if ! md5sum -c md5sums > /dev/null 2>&1 ; then - echo -e "\nGet docker-machine" - curl -Lo docker-machine https://github.com/docker/machine/releases/download/v0.8.2/docker-machine-Linux-x86_64 || fail "Unable to get docker-machine" - chmod +x docker-machine - _sudo mv docker-machine /usr/local/bin - - echo -e "\nGet docker-machine-driver-kvm" - curl -Lo docker-machine-driver-kvm \ - https://github.com/dhiltgen/docker-machine-kvm/releases/download/v0.7.0/docker-machine-driver-kvm || fail "Unable to get docker-machine-driver-kvm" - chmod +x docker-machine-driver-kvm - _sudo mv docker-machine-driver-kvm /usr/local/bin - - _sudo usermod -a -G libvirt $(whoami) - #newgrp libvirt - - echo -e "\nGet minikube" - curl -Lo minikube \ - https://storage.googleapis.com/minikube/releases/v0.12.2/minikube-linux-amd64 || fail "Unable to get minikube" - chmod +x minikube - _sudo mv minikube /usr/local/bin - - echo -e "\nGet kubectl $KUBEVERSION" - curl -Lo kubectl \ - http://storage.googleapis.com/kubernetes-release/release/${KUBEVERSION}/bin/linux/amd64/kubectl || fail "Unable to get kubectl" - chmod +x kubectl - _sudo mv kubectl /usr/local/bin - fi - - echo -e "\nOpt-out of errors" - minikube config set WantReportErrorPrompt false -} - -start_minikube() { - minikube start \ - --cpus=2 \ - --memory=2048 \ - --vm-driver=kvm \ - --kubernetes-version="${KUBEVERSION}" || fail "Unable to start minikube" - - # wait until it is ready - echo -e "\nWait until kubernetes containers are running and ready" - while [ 3 -ne $(kubectl get pods --all-namespaces | grep Running | wc -l) ] ; do - echo -n "." - sleep 1 - done -} - -setup() { - setup_minikube - build_heketi - copy_client_files -} - -test_teardown() { - minikube stop - minikube delete -} - -test_setup() { - start_minikube - build_docker_file -} - - -### MAIN ### -teardown -setup - -### TESTS ### -for kubetest in test*.sh ; do - test_setup - println "TEST $kubetest" - bash $kubetest; result=$? - - if [ $result -ne 0 ] ; then - println "FAILED $kubetest" - else - println "PASSED $kubetest" - fi - test_teardown -done -teardown -exit $result - diff --git a/tests/functional/TestKubeSmokeTest/storageclass.yaml.sed b/tests/functional/TestKubeSmokeTest/storageclass.yaml.sed deleted file mode 100644 index 410c68591f..0000000000 --- a/tests/functional/TestKubeSmokeTest/storageclass.yaml.sed +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: storage.k8s.io/v1beta1 -kind: StorageClass -metadata: - name: slow -provisioner: kubernetes.io/glusterfs -parameters: - endpoint: "glusterfs-cluster" - resturl: "%%URL%%" diff --git a/tests/functional/TestKubeSmokeTest/teardown.sh b/tests/functional/TestKubeSmokeTest/teardown.sh deleted file mode 100755 index 38c3361a4e..0000000000 --- a/tests/functional/TestKubeSmokeTest/teardown.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -TOP=../../.. -CURRENT_DIR=`pwd` -RESOURCES_DIR=$CURRENT_DIR/resources -FUNCTIONAL_DIR=${CURRENT_DIR}/.. - -source ${FUNCTIONAL_DIR}/lib.sh - - -if [ -x /usr/local/bin/minikube ] ; then - minikube stop - minikube delete -fi -rm -rf $RESOURCES_DIR > /dev/null diff --git a/tests/functional/TestKubeSmokeTest/testHeketiMock.sh b/tests/functional/TestKubeSmokeTest/testHeketiMock.sh deleted file mode 100755 index 0f0dccc8f6..0000000000 --- a/tests/functional/TestKubeSmokeTest/testHeketiMock.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/sh - -TOP=../../.. -CURRENT_DIR=`pwd` -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -RESOURCES_DIR=$CURRENT_DIR/resources -PATH=$PATH:$RESOURCES_DIR - -source ${FUNCTIONAL_DIR}/lib.sh - -# Setup Docker environment -eval $(minikube docker-env) - -display_information() { - # Display information - echo -e "\nVersions" - kubectl version - - echo -e "\nDocker containers running" - docker ps - - echo -e "\nDocker images" - docker images - - echo -e "\nShow nodes" - kubectl get nodes -} - -setup_heketi() { - # Start Heketi - echo -e "\nStart Heketi container" - kubectl run heketi --image=heketi/heketi:ci --port=8080 || fail "Unable to start heketi container" - sleep 2 - - # This blocks until ready - kubectl expose deployment heketi --type=NodePort || fail "Unable to expose heketi service" - - echo -e "\nShow Topology" - export HEKETI_CLI_SERVER=$(minikube service heketi --url) - heketi-cli topology info - - echo -e "\nLoad mock topology" - heketi-cli topology load --json=mock-topology.json || fail "Unable to load topology" - - echo -e "\nShow Topology" - export HEKETI_CLI_SERVER=$(minikube service heketi --url) - heketi-cli topology info - - echo -e "\nRegister mock endpoints" - kubectl create -f mock-endpoints.json || fail "Unable to submit mock-endpoints" - - echo -e "\nRegister storage class" - sed -e \ - "s#%%URL%%#${HEKETI_CLI_SERVER}#" \ - storageclass.yaml.sed > ${RESOURCES_DIR}/sc.yaml - kubectl create -f ${RESOURCES_DIR}/sc.yaml || fail "Unable to register storage class" -} - -test_create() { - echo "Assert no volumes available" - if heketi-cli volume list | grep Id ; then - heketi-cli volume list - fail "Incorrect number of volumes in Heketi" - fi - - echo "Submit PVC for 100GiB" - kubectl create -f pvc.json || fail "Unable to submit PVC" - - sleep 2 - echo "Assert PVC Bound" - if ! kubectl get pvc | grep claim1 | grep Bound ; then - fail "PVC is not Bound" - fi - - echo "Assert only one volume created in Heketi" - if ! heketi-cli volume list | grep Id | wc -l | grep 1 ; then - fail "Incorrect number of volumes in Heketi" - fi - - echo "Assert volume size is 100GiB" - id=`heketi-cli volume list | grep Id | awk '{print $1}' | cut -d: -f2` - if ! heketi-cli volume info ${id} | grep Size | cut -d: -f2 | grep 100 ; then - fail "Invalid size" - fi -} - -test_delete() { - echo "Delete PVC" - kubectl delete pvc claim1 || fail "Unable to delete claim1" - - sleep 30 - echo "Assert no volumes available" - if heketi-cli volume list | grep Id ; then - heketi-cli volume list - fail "Incorrect number of volumes in Heketi" - fi -} - -display_information -setup_heketi - -echo -e "\n*** Start tests ***" -test_create -test_delete - -# Ok now start test - - - - diff --git a/tests/functional/TestKubeSmokeTest/testHeketiRpc.sh b/tests/functional/TestKubeSmokeTest/testHeketiRpc.sh deleted file mode 100755 index b98630dec2..0000000000 --- a/tests/functional/TestKubeSmokeTest/testHeketiRpc.sh +++ /dev/null @@ -1,146 +0,0 @@ -#!/bin/sh - -TOP=../../.. -CURRENT_DIR=`pwd` -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -RESOURCES_DIR=$CURRENT_DIR/resources -PATH=$PATH:$RESOURCES_DIR - -source ${FUNCTIONAL_DIR}/lib.sh - -# Setup Docker environment -eval $(minikube docker-env) - -display_information() { - # Display information - echo -e "\nVersions" - kubectl version - - echo -e "\nDocker containers running" - docker ps - - echo -e "\nDocker images" - docker images - - echo -e "\nShow nodes" - kubectl get nodes -} - -create_fake_application() { - pod=$1 - app=$2 - kubectl exec $pod -- sh -c "echo '#!/bin/sh' > /bin/${app}" || fail "Unable to create /bin/${app}" - kubectl exec $pod -- chmod +x /bin/${app} || fail "Unable to chmod +x /bin/${app}" -} - -create_bash() { - pod=$1 - app="bash" - kubectl exec $pod -- sh -c "echo '#!/bin/sh' > /bin/${app}" || fail "Unable to create /bin/${app}" - kubectl exec $pod -- sh -c "echo 'sh \$@' >> /bin/${app}" || fail "Unable to add to /bin/${app}" - kubectl exec $pod -- chmod +x /bin/${app} || fail "Unable to chmod +x /bin/${app}" -} - -create_fake_vgdisplay() { - pod=$1 - app=vgdisplay - kubectl exec $pod -- sh -c "echo '#!/bin/sh' > /bin/${app}" || fail "Unable to create /bin/${app}" - - ## This pretends that there is a disk with 99G of free space and 0 used. - kubectl exec $pod -- sh -c "echo 'echo mock:r/w:772:-1:0:0:0:-1:0:1:1:104722432:4096:25567:0:25567:3xn8HX-x6cB-CJJy-Sj6Q-I0CY-gJLY-qlipv5' >> /bin/${app}" || fail "Unable to add to /bin/${app}" - kubectl exec $pod -- chmod +x /bin/${app} || fail "Unable to chmod +x /bin/${app}" -} - -start_mock_gluster_container() { - # Use a busybox container - kubectl run gluster$1 \ - --restart=Never \ - --image=busybox \ - --labels=glusterfs-node=daemonset \ - --command -- sleep 10000 || fail "Unable to start gluster$1" - - # Wait until it is running - while ! kubectl get pods | grep gluster$1 | grep "1/1" > /dev/null ; do - sleep 1 - done - - # Create fake gluster file - create_fake_application gluster$1 "gluster" - create_fake_application gluster$1 "pvcreate" - create_fake_application gluster$1 "vgcreate" - create_fake_application gluster$1 "pvremove" - create_fake_application gluster$1 "vgremove" - create_fake_vgdisplay gluster$1 - create_bash gluster$1 -} - -setup_all_pods() { - - kubectl get nodes --show-labels - - # Start Heketi - echo -e "\nStart Heketi container" - sed -e "s#heketi/heketi:dev#heketi/heketi:ci#" \ - -e "s#Always#IfNotPresent#" \ - $RESOURCES_DIR/deploy-heketi-deployment.json | kubectl create -f - - - # Wait until it is running - echo "Wait until deploy-heketi is ready" - while ! kubectl get pods | grep heketi | grep "1/1" > /dev/null ; do - echo -n "." - sleep 1 - done - - echo "Delete the cluster service because it cannot be used in minikube" - kubectl delete service deploy-heketi - - echo "Create a service for deploy-heketi" - kubectl expose deployment deploy-heketi --port=8080 --type=NodePort || fail "Unable to expose heketi service" - - echo -e "\nShow Topology" - export HEKETI_CLI_SERVER=$(minikube service deploy-heketi --url) - heketi-cli topology info - - echo -e "\nStart gluster mock container" - start_mock_gluster_container 1 -} - -test_add_devices() { - echo -e "\nGet the Heketi server connection" - heketi-cli cluster create || fail "Unable to create cluster" - - CLUSTERID=$(heketi-cli cluster list | sed -e '$!d') - - echo -e "\nAdd Node" - heketi-cli node add --zone=1 --cluster=$CLUSTERID \ - --management-host-name=minikube --storage-host-name=minikube || fail "Unable to add gluster1" - - echo -e "\nAdd device" - nodeid=$(heketi-cli node list | awk '{print $1}' | awk -F: '{print $2}') - heketi-cli device add --name=/dev/fakedevice --node=$nodeid || fail "Unable to add device" - - # Check sizes - device_size=$(heketi-cli topology info | grep fakedevice | awk '{print $5}' | cut -d: -f2) - device_used=$(heketi-cli topology info | grep fakedevice | awk '{print $7}' | cut -d: -f2) - device_free=$(heketi-cli topology info | grep fakedevice | awk '{print $9}' | cut -d: -f2) - if [ 99 -ne $device_size ] ; then - fail "Expected size of 99 instead got $device_size" - fi - if [ 0 -ne $device_used ] ; then - fail "Expected used of 0 instead got $device_used" - fi - if [ 99 -ne $device_free ] ; then - fail "Expected free of 99 instead got $device_free" - fi - - echo -e "\nShow Topology" - heketi-cli topology info -} - -display_information -setup_all_pods - -echo -e "\n*** Start tests ***" -test_add_devices - -# Ok now start test diff --git a/tests/functional/TestManyBricksVolume/README.md b/tests/functional/TestManyBricksVolume/README.md deleted file mode 100644 index 0a12f47521..0000000000 --- a/tests/functional/TestManyBricksVolume/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Large Functional Test -This functional test can be used on a system with at least 128GB of RAM. - -## Requirements - -* Vagrant -* Andible -* Hypervisor: Only Libvirt/KVM - -For simplicity you can use the ansible script in https://github.com/heketi/setup-vagrant-libvirt -to setup your system for functional tests. - -## Setup - -Type: - -``` -$ ./up -``` - -Note, this could take up to one hour. - -## Running the tests - -* Go to the top of the source tree build and run a new Heketi server: - -``` -$ rm heketi.db -$ make -$ ./heketi --config=tests/functional/large/config/heketi.json 2>&1 | tee log - -``` - -* Then start running the tests - -``` -$ cd tests/functional/large/tests -$ go test -tags ftlarge -``` - -Output will be shows by the logs on the heketi server. diff --git a/tests/functional/TestManyBricksVolume/config/heketi.json b/tests/functional/TestManyBricksVolume/config/heketi.json deleted file mode 100644 index 6bdb238949..0000000000 --- a/tests/functional/TestManyBricksVolume/config/heketi.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "_port_comment": "Heketi Server Port Number", - "port" : "8080", - - "_use_auth": "Enable JWT authorization. Please enable for deployment", - "use_auth" : true, - - "_jwt" : "Private keys for access", - "jwt" : { - "_admin" : "Admin has access to all APIs", - "admin" : { - "key" : "adminkey" - }, - "_user" : "User only has access to /volumes endpoint", - "user" : { - "key" : "userkey" - } - }, - - "_glusterfs_comment": "GlusterFS Configuration", - "glusterfs" : { - - "_executor_comment": "Execute plugin. Possible choices: mock, ssh", - "executor" : "ssh", - - "_db_comment": "Database file name", - "db" : "heketi.db", - - "sshexec" : { - "keyfile" : "config/insecure_private_key", - "sudo" : true, - "user" : "vagrant" - } - } -} diff --git a/tests/functional/TestManyBricksVolume/config/insecure_private_key b/tests/functional/TestManyBricksVolume/config/insecure_private_key deleted file mode 100644 index 7d6a083909..0000000000 --- a/tests/functional/TestManyBricksVolume/config/insecure_private_key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI -w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP -kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 -hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO -Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW -yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd -ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 -Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf -TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK -iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A -sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf -4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP -cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk -EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN -CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX -3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG -YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj -3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ -dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz -6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC -P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF -llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ -kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH -+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ -NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= ------END RSA PRIVATE KEY----- diff --git a/tests/functional/TestManyBricksVolume/run.sh b/tests/functional/TestManyBricksVolume/run.sh deleted file mode 100755 index 5270bd664e..0000000000 --- a/tests/functional/TestManyBricksVolume/run.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -CURRENT_DIR=`pwd` -HEKETI_SERVER_BUILD_DIR=../../.. -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server - -source ${FUNCTIONAL_DIR}/lib.sh - -functional_tests - diff --git a/tests/functional/TestManyBricksVolume/teardown.sh b/tests/functional/TestManyBricksVolume/teardown.sh deleted file mode 100755 index 6f066fa432..0000000000 --- a/tests/functional/TestManyBricksVolume/teardown.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -CURRENT_DIR=`pwd` -HEKETI_SERVER_BUILD_DIR=../../.. -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server - -source ${FUNCTIONAL_DIR}/lib.sh - -teardown - diff --git a/tests/functional/TestManyBricksVolume/tests/heketi_test.go b/tests/functional/TestManyBricksVolume/tests/heketi_test.go deleted file mode 100644 index 5cffef07ea..0000000000 --- a/tests/functional/TestManyBricksVolume/tests/heketi_test.go +++ /dev/null @@ -1,234 +0,0 @@ -// +build functional - -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// -package functional - -import ( - "fmt" - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/tests" - "testing" -) - -// These are the settings for the vagrant file -const ( - - // The heketi server must be running on the host - heketiUrl = "http://127.0.0.1:8080" - - // VMs - DISKS = 10 - NODES = 3 - ZONES = 3 - CLUSTERS = 1 -) - -var ( - // Heketi client - heketi = client.NewClient(heketiUrl, "admin", "adminkey") - logger = utils.NewLogger("[test]", utils.LEVEL_DEBUG) -) - -func getdisks() []string { - - diskletters := make([]string, DISKS) - for index, i := 0, []byte("b")[0]; index < DISKS; index, i = index+1, i+1 { - diskletters[index] = "/dev/vd" + string(i) - } - - return diskletters -} - -func getnodes() []string { - nodelist := make([]string, NODES) - - for index, ip := 0, 100; index < NODES; index, ip = index+1, ip+1 { - nodelist[index] = "192.168.10." + fmt.Sprintf("%v", ip) - } - - return nodelist -} - -func setupCluster(t *testing.T) { - tests.Assert(t, heketi != nil) - - nodespercluster := NODES / CLUSTERS - nodes := getnodes() - sg := utils.NewStatusGroup() - for cluster := 0; cluster < CLUSTERS; cluster++ { - sg.Add(1) - go func(nodes_in_cluster []string) { - defer sg.Done() - // Create a cluster - cluster, err := heketi.ClusterCreate() - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Add nodes sequentially due to probes - for index, hostname := range nodes_in_cluster { - nodeReq := &api.NodeAddRequest{} - nodeReq.ClusterId = cluster.Id - nodeReq.Hostnames.Manage = []string{hostname} - nodeReq.Hostnames.Storage = []string{hostname} - nodeReq.Zone = index%ZONES + 1 - - node, err := heketi.NodeAdd(nodeReq) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Add devices all concurrently - for _, disk := range getdisks() { - sg.Add(1) - go func(d string) { - defer sg.Done() - - driveReq := &api.DeviceAddRequest{} - driveReq.Name = d - driveReq.NodeId = node.Id - - err := heketi.DeviceAdd(driveReq) - if err != nil { - logger.Err(err) - sg.Err(err) - } - }(disk) - } - } - }(nodes[cluster*nodespercluster : (cluster+1)*nodespercluster]) - } - - // Wait here for results - err := sg.Result() - tests.Assert(t, err == nil) - -} - -func teardownCluster(t *testing.T) { - clusters, err := heketi.ClusterList() - tests.Assert(t, err == nil) - - sg := utils.NewStatusGroup() - for _, cluster := range clusters.Clusters { - - sg.Add(1) - go func(clusterId string) { - defer sg.Done() - - clusterInfo, err := heketi.ClusterInfo(clusterId) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Delete volumes in this cluster - for _, volume := range clusterInfo.Volumes { - err := heketi.VolumeDelete(volume) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - } - - // Delete all devices in the cluster concurrently - deviceSg := utils.NewStatusGroup() - for _, node := range clusterInfo.Nodes { - - // Get node information - nodeInfo, err := heketi.NodeInfo(node) - if err != nil { - logger.Err(err) - deviceSg.Err(err) - return - } - - // Delete each device - for _, device := range nodeInfo.DevicesInfo { - deviceSg.Add(1) - go func(id string) { - defer deviceSg.Done() - - err := heketi.DeviceDelete(id) - if err != nil { - logger.Err(err) - deviceSg.Err(err) - return - } - - }(device.Id) - } - } - err = deviceSg.Result() - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Delete nodes - for _, node := range clusterInfo.Nodes { - err = heketi.NodeDelete(node) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - } - - // Delete cluster - err = heketi.ClusterDelete(clusterId) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - }(cluster) - - } - - err = sg.Result() - tests.Assert(t, err == nil) -} - -func TestConnection(t *testing.T) { - err := heketi.Hello() - tests.Assert(t, err == nil) -} - -func TestHeketiManyBricksVolume(t *testing.T) { - - // Setup the VM storage topology - setupCluster(t) - defer teardownCluster(t) - - // Create a volume with replica 3 - volReq := &api.VolumeCreateRequest{} - volReq.Size = 500 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - - volInfo, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - tests.Assert(t, volInfo.Size == 500) - tests.Assert(t, volInfo.Mount.GlusterFS.MountPoint != "") - tests.Assert(t, volInfo.Durability.Type == api.DurabilityReplicate) - tests.Assert(t, volInfo.Durability.Replicate.Replica == 3) - tests.Assert(t, volInfo.Name != "") -} diff --git a/tests/functional/TestManyBricksVolume/vagrant/Vagrantfile b/tests/functional/TestManyBricksVolume/vagrant/Vagrantfile deleted file mode 100644 index 59c635c1d9..0000000000 --- a/tests/functional/TestManyBricksVolume/vagrant/Vagrantfile +++ /dev/null @@ -1,43 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : -# - -NODES = 3 -DISKS = 10 -DRIVE_SIZE = "100G" - -Vagrant.configure("2") do |config| - config.ssh.insert_key = false - config.vm.box = "centos/7" - config.vm.synced_folder '.', '/home/vagrant/sync', disabled: true - - # Make the glusterfs cluster, each with DISKS number of drives - (0..NODES-1).each do |i| - config.vm.define "storage#{i}" do |storage| - storage.vm.hostname = "storage#{i}" - localip = 100+i - storage.vm.network :private_network, ip: "192.168.10.#{localip}" - (0..DISKS-1).each do |d| - driverletters = ('b'..'z').to_a - storage.vm.provider :libvirt do |lv| - lv.storage :file, :device => "vd#{driverletters[d]}", :path => "test_many_bricks_disk-#{i}-#{d}.disk", :size => "#{DRIVE_SIZE}" - lv.memory = 2048 - lv.cpus =2 - end - end - - if i == (NODES-1) - # View the documentation for the provider you're using for more - # information on available options. - storage.vm.provision :ansible do |ansible| - ansible.limit = "all" - ansible.playbook = "site.yml" - ansible.groups = { - "gluster" => (0..NODES-1).map {|j| "storage#{j}"}, - } - end - end - end - end -end - diff --git a/tests/functional/TestManyBricksVolume/vagrant/roles/common/files/insecure_private_key b/tests/functional/TestManyBricksVolume/vagrant/roles/common/files/insecure_private_key deleted file mode 100644 index 7d6a083909..0000000000 --- a/tests/functional/TestManyBricksVolume/vagrant/roles/common/files/insecure_private_key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI -w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP -kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 -hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO -Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW -yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd -ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 -Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf -TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK -iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A -sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf -4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP -cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk -EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN -CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX -3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG -YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj -3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ -dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz -6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC -P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF -llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ -kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH -+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ -NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= ------END RSA PRIVATE KEY----- diff --git a/tests/functional/TestManyBricksVolume/vagrant/roles/common/tasks/main.yml b/tests/functional/TestManyBricksVolume/vagrant/roles/common/tasks/main.yml deleted file mode 100644 index a47e932ec2..0000000000 --- a/tests/functional/TestManyBricksVolume/vagrant/roles/common/tasks/main.yml +++ /dev/null @@ -1,29 +0,0 @@ -- name: clean disks - command: dd if=/dev/zero of={{ item }} count=100 bs=1024k - with_items: - - /dev/vdb - - /dev/vdc - - /dev/vdd - - /dev/vde - - /dev/vdf - - /dev/vdg - - /dev/vdh - - /dev/vdi - - /dev/vdj - - /dev/vdk - -- name: install epel and centos-gluster - yum: name={{ item }} state=present - with_items: - - epel-release - - centos-release-gluster - -- name: copy private key - copy: src=insecure_private_key owner=vagrant group=vagrant dest=/home/vagrant/.ssh/id_rsa force=no mode=0600 - -- name: clean iptables - command: iptables -F - -- name: disable selinux - selinux: state=disabled - diff --git a/tests/functional/TestManyBricksVolume/vagrant/roles/gluster/tasks/main.yml b/tests/functional/TestManyBricksVolume/vagrant/roles/gluster/tasks/main.yml deleted file mode 100644 index 9dfeea91d4..0000000000 --- a/tests/functional/TestManyBricksVolume/vagrant/roles/gluster/tasks/main.yml +++ /dev/null @@ -1,7 +0,0 @@ -- name: install glusterfs - yum: name={{ item }} state=present - with_items: - - glusterfs-server - -- name: start glusterd - service: name=glusterd state=started enabled=yes diff --git a/tests/functional/TestManyBricksVolume/vagrant/site.yml b/tests/functional/TestManyBricksVolume/vagrant/site.yml deleted file mode 100644 index f973bb9ee6..0000000000 --- a/tests/functional/TestManyBricksVolume/vagrant/site.yml +++ /dev/null @@ -1,11 +0,0 @@ -- hosts: all - become: yes - become_method: sudo - roles: - - common - -- hosts: gluster - become: yes - become_method: sudo - roles: - - gluster diff --git a/tests/functional/TestManyBricksVolume/vagrant/up.sh b/tests/functional/TestManyBricksVolume/vagrant/up.sh deleted file mode 100755 index 35cbe79d83..0000000000 --- a/tests/functional/TestManyBricksVolume/vagrant/up.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -vagrant up --no-provision $@ -vagrant provision diff --git a/tests/functional/TestSmokeTest/README.md b/tests/functional/TestSmokeTest/README.md deleted file mode 100644 index c042941358..0000000000 --- a/tests/functional/TestSmokeTest/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Small Functional Test -This functional test can be used on a system with at least 8GB of RAM. - -## Requirements - -* Vagrant -* Ansible -* Hypervisor: VirtualBox or Libvirt/KVM - -## Setup - -* Go to `tests/functional/TestSmokeTest/vagrant` -Type: -``` -$ ./up.sh --provider=PROVIDER -``` -where PROVIDER is virtualbox or libvirt. - -## Running the Tests - -* Go to the top of the source tree build and run a new Heketi server: - -``` -$ rm heketi.db -$ make -$ ./heketi --config=tests/functional/TestSmokeTest/config/heketi.json | tee log - -``` - -* Once it is ready, then start running the tests in another terminal - -``` -$ cd tests/functional/TestSmokeTest/tests -$ go test -tags ftsmall -``` - -Output will be shown by the logs on the heketi server. diff --git a/tests/functional/TestSmokeTest/config/heketi.json b/tests/functional/TestSmokeTest/config/heketi.json deleted file mode 100644 index 52f9190fb7..0000000000 --- a/tests/functional/TestSmokeTest/config/heketi.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "_port_comment": "Heketi Server Port Number", - "port" : "8080", - - "_glusterfs_comment": "GlusterFS Configuration", - "glusterfs" : { - - "_executor_comment": "Execute plugin. Possible choices: mock, ssh", - "executor" : "ssh", - - "_db_comment": "Database file name", - "db" : "heketi.db", - - "sshexec" : { - "keyfile" : "config/insecure_private_key", - "user" : "vagrant", - "sudo" : true - } - } -} diff --git a/tests/functional/TestSmokeTest/config/insecure_private_key b/tests/functional/TestSmokeTest/config/insecure_private_key deleted file mode 100644 index 7d6a083909..0000000000 --- a/tests/functional/TestSmokeTest/config/insecure_private_key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI -w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP -kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 -hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO -Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW -yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd -ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 -Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf -TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK -iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A -sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf -4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP -cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk -EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN -CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX -3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG -YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj -3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ -dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz -6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC -P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF -llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ -kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH -+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ -NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= ------END RSA PRIVATE KEY----- diff --git a/tests/functional/TestSmokeTest/run.sh b/tests/functional/TestSmokeTest/run.sh deleted file mode 100755 index 5270bd664e..0000000000 --- a/tests/functional/TestSmokeTest/run.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -CURRENT_DIR=`pwd` -HEKETI_SERVER_BUILD_DIR=../../.. -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server - -source ${FUNCTIONAL_DIR}/lib.sh - -functional_tests - diff --git a/tests/functional/TestSmokeTest/teardown.sh b/tests/functional/TestSmokeTest/teardown.sh deleted file mode 100755 index 6f066fa432..0000000000 --- a/tests/functional/TestSmokeTest/teardown.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -CURRENT_DIR=`pwd` -HEKETI_SERVER_BUILD_DIR=../../.. -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server - -source ${FUNCTIONAL_DIR}/lib.sh - -teardown - diff --git a/tests/functional/TestSmokeTest/tests/heketi_test.go b/tests/functional/TestSmokeTest/tests/heketi_test.go deleted file mode 100644 index 1db16be222..0000000000 --- a/tests/functional/TestSmokeTest/tests/heketi_test.go +++ /dev/null @@ -1,558 +0,0 @@ -// +build functional - -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// -package functional - -import ( - "fmt" - "net/http" - "testing" - - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/heketi/pkg/utils/ssh" - "github.com/heketi/tests" -) - -// These are the settings for the vagrant file -const ( - - // The heketi server must be running on the host - heketiUrl = "http://localhost:8080" - - // VMs - storage0 = "192.168.10.100" - storage1 = "192.168.10.101" - storage2 = "192.168.10.102" - storage3 = "192.168.10.103" -) - -var ( - // Heketi client - heketi = client.NewClientNoAuth(heketiUrl) - logger = utils.NewLogger("[test]", utils.LEVEL_DEBUG) - - // Storage systems - storagevms = []string{ - storage0, - storage1, - storage2, - storage3, - } - - // Disks on each system - disks = []string{ - "/dev/vdb", - "/dev/vdc", - "/dev/vdd", - "/dev/vde", - - "/dev/vdf", - "/dev/vdg", - "/dev/vdh", - "/dev/vdi", - } -) - -func setupCluster(t *testing.T, numNodes int, numDisks int) { - tests.Assert(t, heketi != nil) - - // Create a cluster - cluster, err := heketi.ClusterCreate() - tests.Assert(t, err == nil) - - // hardcoded limits from the lists above - // possible TODO: generalize - tests.Assert(t, numNodes <= 4) - tests.Assert(t, numDisks <= 8) - - // Add nodes - for index, hostname := range storagevms[:numNodes] { - nodeReq := &api.NodeAddRequest{} - nodeReq.ClusterId = cluster.Id - nodeReq.Hostnames.Manage = []string{hostname} - nodeReq.Hostnames.Storage = []string{hostname} - nodeReq.Zone = index%2 + 1 - - node, err := heketi.NodeAdd(nodeReq) - tests.Assert(t, err == nil) - - // Add devices - sg := utils.NewStatusGroup() - for _, disk := range disks[:numDisks] { - sg.Add(1) - go func(d string) { - defer sg.Done() - - driveReq := &api.DeviceAddRequest{} - driveReq.Name = d - driveReq.NodeId = node.Id - - err := heketi.DeviceAdd(driveReq) - sg.Err(err) - }(disk) - } - - err = sg.Result() - tests.Assert(t, err == nil) - } -} - -func teardownCluster(t *testing.T) { - clusters, err := heketi.ClusterList() - tests.Assert(t, err == nil, err) - - for _, cluster := range clusters.Clusters { - - clusterInfo, err := heketi.ClusterInfo(cluster) - tests.Assert(t, err == nil) - - // Delete volumes in this cluster - for _, volume := range clusterInfo.Volumes { - err := heketi.VolumeDelete(volume) - tests.Assert(t, err == nil) - } - - // Delete nodes - for _, node := range clusterInfo.Nodes { - - // Get node information - nodeInfo, err := heketi.NodeInfo(node) - tests.Assert(t, err == nil) - - // Delete each device - sg := utils.NewStatusGroup() - for _, device := range nodeInfo.DevicesInfo { - sg.Add(1) - go func(id string) { - defer sg.Done() - - err := heketi.DeviceDelete(id) - sg.Err(err) - - }(device.Id) - } - err = sg.Result() - tests.Assert(t, err == nil) - - // Delete node - err = heketi.NodeDelete(node) - tests.Assert(t, err == nil) - } - - // Delete cluster - err = heketi.ClusterDelete(cluster) - tests.Assert(t, err == nil) - } -} - -func TestConnection(t *testing.T) { - r, err := http.Get(heketiUrl + "/hello") - tests.Assert(t, err == nil) - tests.Assert(t, r.StatusCode == http.StatusOK) -} - -func TestHeketiSmokeTest(t *testing.T) { - - // Setup the VM storage topology - teardownCluster(t) - setupCluster(t, 4, 8) - defer teardownCluster(t) - - // Create a volume and delete a few time to test garbage collection - for i := 0; i < 2; i++ { - - volReq := &api.VolumeCreateRequest{} - volReq.Size = 4000 - volReq.Snapshot.Enable = true - volReq.Snapshot.Factor = 1.5 - volReq.Durability.Type = api.DurabilityReplicate - - volInfo, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - tests.Assert(t, volInfo.Size == 4000) - tests.Assert(t, volInfo.Mount.GlusterFS.MountPoint != "") - tests.Assert(t, volInfo.Name != "") - - volumes, err := heketi.VolumeList() - tests.Assert(t, err == nil) - tests.Assert(t, len(volumes.Volumes) == 1) - tests.Assert(t, volumes.Volumes[0] == volInfo.Id) - - err = heketi.VolumeDelete(volInfo.Id) - tests.Assert(t, err == nil) - } - - // Create a 1TB volume - volReq := &api.VolumeCreateRequest{} - volReq.Size = 1024 - volReq.Snapshot.Enable = true - volReq.Snapshot.Factor = 1.5 - volReq.Durability.Type = api.DurabilityReplicate - - simplevol, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - - // Create a 12TB volume with 6TB of snapshot space - // There should be no space - volReq = &api.VolumeCreateRequest{} - volReq.Size = 12 * 1024 - volReq.Snapshot.Enable = true - volReq.Snapshot.Factor = 1.5 - volReq.Durability.Type = api.DurabilityReplicate - - _, err = heketi.VolumeCreate(volReq) - tests.Assert(t, err != nil) - - // Check there is only one - volumes, err := heketi.VolumeList() - tests.Assert(t, err == nil) - tests.Assert(t, len(volumes.Volumes) == 1) - - // Create a 100G volume with replica 3 - volReq = &api.VolumeCreateRequest{} - volReq.Size = 100 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - - volInfo, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - tests.Assert(t, volInfo.Size == 100) - tests.Assert(t, volInfo.Mount.GlusterFS.MountPoint != "") - tests.Assert(t, volInfo.Name != "") - tests.Assert(t, len(volInfo.Bricks) == 3, len(volInfo.Bricks)) - - // Check there are two volumes - volumes, err = heketi.VolumeList() - tests.Assert(t, err == nil) - tests.Assert(t, len(volumes.Volumes) == 2) - - // Expand volume - volExpReq := &api.VolumeExpandRequest{} - volExpReq.Size = 2000 - - volInfo, err = heketi.VolumeExpand(simplevol.Id, volExpReq) - tests.Assert(t, err == nil) - tests.Assert(t, volInfo.Size == simplevol.Size+2000) - - // Delete volume - err = heketi.VolumeDelete(volInfo.Id) - tests.Assert(t, err == nil) -} - -func TestHeketiCreateVolumeWithGid(t *testing.T) { - // Setup the VM storage topology - teardownCluster(t) - setupCluster(t, 4, 8) - defer teardownCluster(t) - - // Create a volume - volReq := &api.VolumeCreateRequest{} - volReq.Size = 1024 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - volReq.Snapshot.Enable = true - volReq.Snapshot.Factor = 1.5 - - // Set to the vagrant gid - volReq.Gid = 1000 - - // Create the volume - volInfo, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - - // SSH into system and create two writers belonging to writegroup gid - vagrantexec := ssh.NewSshExecWithKeyFile(logger, "vagrant", "../config/insecure_private_key") - cmd := []string{ - "sudo groupadd writegroup", - "sudo useradd writer1 -G writegroup -p$6$WBG5yf03$3DvyE41cicXEZDW.HDeJg3S4oEoELqKWoS/n6l28vorNxhIlcBe2SLQFDhqq6.Pq", - "sudo useradd writer2 -G writegroup -p$6$WBG5yf03$3DvyE41cicXEZDW.HDeJg3S4oEoELqKWoS/n6l28vorNxhIlcBe2SLQFDhqq6.Pq", - fmt.Sprintf("sudo mount -t glusterfs %v /mnt", volInfo.Mount.GlusterFS.MountPoint), - } - _, err = vagrantexec.ConnectAndExec("192.168.10.100:22", cmd, 10, true) - tests.Assert(t, err == nil, err) - - writer1exec := ssh.NewSshExecWithPassword(logger, "writer1", "$6$WBG5yf03$3DvyE41cicXEZDW.HDeJg3S4oEoELqKWoS/n6l28vorNxhIlcBe2SLQFDhqq6.Pq") - cmd = []string{ - "touch /mnt/writer1testfile", - "mkdir /mnt/writer1dir", - "touch /mnt/writer1dir/testfile", - } - _, err = writer1exec.ConnectAndExec("192.168.10.100:22", cmd, 10, false) - tests.Assert(t, err == nil, err) - - writer2exec := ssh.NewSshExecWithPassword(logger, "writer2", "$6$WBG5yf03$3DvyE41cicXEZDW.HDeJg3S4oEoELqKWoS/n6l28vorNxhIlcBe2SLQFDhqq6.Pq") - cmd = []string{ - "touch /mnt/writer2testfile", - "mkdir /mnt/writer2dir", - "touch /mnt/writer2dir/testfile", - } - _, err = writer2exec.ConnectAndExec("192.168.10.100:22", cmd, 10, false) - tests.Assert(t, err == nil, err) - cmd = []string{ - "mkdir /mnt/writer1dir/writer2subdir", - "touch /mnt/writer1dir/writer2testfile", - } - _, err = writer2exec.ConnectAndExec("192.168.10.100:22", cmd, 10, false) - tests.Assert(t, err == nil, err) - -} - -func TestRemoveDevice(t *testing.T) { - - // Setup the VM storage topology - teardownCluster(t) - setupCluster(t, 3, 2) - defer teardownCluster(t) - - // We have 2 disks of 500GB on every node - // Total space per node is 1TB - // We have 3 Nodes, so total space is 3TB - - // vol1: 300 ==> 1 replica set - // vol2: 600 ==> 4 replica sets of 150 each - // on each node: - // 1 brick on the already used disk - // 3 bricks on the previously unused disk - // - // n1d1 n2d1 n3d1 - // ------------------------- - // r1: [ r1b1 , r1b2, r1b3 ] - // - // n1d2 n2d2 n3d2 - // ------------------------- - // r2 [ r2b1, r2b2, r2b3 ] - // r3 [ r3b1, r3b2 r3b4 ] - // r4 [ r4b1 r4b2 r4b3 ] - - volReq := &api.VolumeCreateRequest{} - volReq.Size = 300 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - vol1, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - - // Check there is only one - volumes, err := heketi.VolumeList() - tests.Assert(t, err == nil) - tests.Assert(t, len(volumes.Volumes) == 1) - - volReq = &api.VolumeCreateRequest{} - volReq.Size = 600 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - vol2, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - - deviceOccurence := make(map[string]int) - maxBricksPerDevice := 0 - var deviceToRemove string - var diskNode string - for _, brick := range vol2.Bricks { - deviceOccurence[brick.DeviceId]++ - if deviceOccurence[brick.DeviceId] > maxBricksPerDevice { - maxBricksPerDevice = deviceOccurence[brick.DeviceId] - deviceToRemove = brick.DeviceId - diskNode = brick.NodeId - } - } - - for device, _ := range deviceOccurence { - logger.Info("Key: %v , Value: %v", device, deviceOccurence[device]) - } - - // if this fails, it's a problem with the test ... - tests.Assert(t, maxBricksPerDevice > 1, "Problem: failed to produce a disk with multiple bricks from one volume!") - - // Add a replacement disk - driveReq := &api.DeviceAddRequest{} - driveReq.Name = disks[2] - driveReq.NodeId = diskNode - err = heketi.DeviceAdd(driveReq) - tests.Assert(t, err == nil, err) - - stateReq := &api.StateRequest{} - stateReq.State = api.EntryStateOffline - err = heketi.DeviceState(deviceToRemove, stateReq) - tests.Assert(t, err == nil) - - stateReq = &api.StateRequest{} - stateReq.State = api.EntryStateFailed - err = heketi.DeviceState(deviceToRemove, stateReq) - tests.Assert(t, err == nil) - - logger.Info("%v %v", vol1, vol2) - // Delete volumes - err = heketi.VolumeDelete(vol1.Id) - tests.Assert(t, err == nil) - err = heketi.VolumeDelete(vol2.Id) - tests.Assert(t, err == nil) -} - -func TestRemoveDeviceVsVolumeCreate(t *testing.T) { - - // Setup the VM storage topology - teardownCluster(t) - setupCluster(t, 4, 1) - defer teardownCluster(t) - - var newDevice string - var deviceToRemove string - - volReq := &api.VolumeCreateRequest{} - volReq.Size = 300 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - _, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - // Check there is only one - volumes, err := heketi.VolumeList() - tests.Assert(t, err == nil) - tests.Assert(t, len(volumes.Volumes) == 1) - - clusters, err := heketi.ClusterList() - tests.Assert(t, err == nil, err) - for _, cluster := range clusters.Clusters { - clusterInfo, err := heketi.ClusterInfo(cluster) - tests.Assert(t, err == nil) - - for _, node := range clusterInfo.Nodes { - - // Get node information - nodeInfo, err := heketi.NodeInfo(node) - tests.Assert(t, err == nil) - for _, device := range nodeInfo.DevicesInfo { - if len(device.Bricks) == 0 { - newDevice = device.Id - } else { - deviceToRemove = device.Id - } - } - } - } - - stateReq := &api.StateRequest{} - stateReq.State = api.EntryStateOffline - err = heketi.DeviceState(deviceToRemove, stateReq) - tests.Assert(t, err == nil) - - sgDeviceRemove := utils.NewStatusGroup() - sgDeviceRemove.Add(1) - go func() { - defer sgDeviceRemove.Done() - stateReq = &api.StateRequest{} - stateReq.State = api.EntryStateFailed - err = heketi.DeviceState(deviceToRemove, stateReq) - sgDeviceRemove.Err(err) - }() - - sgVolumeCreate := utils.NewStatusGroup() - for i := 0; i < 15; i++ { - sgVolumeCreate.Add(1) - go func() { - defer sgVolumeCreate.Done() - volReq = &api.VolumeCreateRequest{} - volReq.Size = 10 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - _, err := heketi.VolumeCreate(volReq) - sgVolumeCreate.Err(err) - }() - } - - err = sgVolumeCreate.Result() - tests.Assert(t, err == nil) - err = sgDeviceRemove.Result() - tests.Assert(t, err == nil) - // At this point, we should have one brick moved to new device as a result of remove device - // and 15 bricks created on new device as a result of 15 volume creates - newDeviceResponse, err := heketi.DeviceInfo(newDevice) - tests.Assert(t, len(newDeviceResponse.Bricks) == 16, "device entry not consistent") - -} - -func TestHeketiVolumeExpandWithGid(t *testing.T) { - // Setup the VM storage topology - teardownCluster(t) - setupCluster(t, 4, 8) - defer teardownCluster(t) - - // Create a volume - volReq := &api.VolumeCreateRequest{} - volReq.Size = 300 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - volReq.Snapshot.Enable = true - volReq.Snapshot.Factor = 1.5 - - // Set to the vagrant gid - volReq.Gid = 2333 - - // Create the volume - volInfo, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - - // Expand volume - volExpReq := &api.VolumeExpandRequest{} - volExpReq.Size = 300 - - newVolInfo, err := heketi.VolumeExpand(volInfo.Id, volExpReq) - tests.Assert(t, err == nil) - tests.Assert(t, newVolInfo.Size == volInfo.Size+300) - - // SSH into system and check gid of bricks - vagrantexec := ssh.NewSshExecWithKeyFile(logger, "vagrant", "../config/insecure_private_key") - cmd := []string{ - fmt.Sprintf("sudo ls -l /var/lib/heketi/mounts/vg_*/brick_*/ | grep -e \"^d\" | cut -d\" \" -f4 | grep -q %v", volReq.Gid), - } - _, err = vagrantexec.ConnectAndExec("192.168.10.100:22", cmd, 10, true) - tests.Assert(t, err == nil, "Brick found with different Gid") - _, err = vagrantexec.ConnectAndExec("192.168.10.101:22", cmd, 10, true) - tests.Assert(t, err == nil, "Brick found with different Gid") - _, err = vagrantexec.ConnectAndExec("192.168.10.102:22", cmd, 10, true) - tests.Assert(t, err == nil, "Brick found with different Gid") - _, err = vagrantexec.ConnectAndExec("192.168.10.103:22", cmd, 10, true) - tests.Assert(t, err == nil, "Brick found with different Gid") - -} - -func TestHeketiVolumeCreateWithOptions(t *testing.T) { - // Setup the VM storage topology - teardownCluster(t) - setupCluster(t, 2, 2) - defer teardownCluster(t) - - // Create a volume - volReq := &api.VolumeCreateRequest{} - volReq.Size = 10 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 2 - volReq.Snapshot.Enable = true - volReq.Snapshot.Factor = 1.5 - volReq.GlusterVolumeOptions = []string{"performance.rda-cache-limit 10MB","performance.nl-cache-positive-entry no"} - - // Set to the vagrant gid - volReq.Gid = 2333 - - // Create the volume - volInfo, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - tests.Assert(t, len(volInfo.GlusterVolumeOptions) > 0) - - // SSH into system and check volume options. - vagrantexec := ssh.NewSshExecWithKeyFile(logger, "vagrant", "../config/insecure_private_key") - cmd := []string{ - fmt.Sprintf("sudo gluster v info %v | grep performance.rda-cache-limit | grep 10MB", volInfo.Name), - fmt.Sprintf("sudo gluster v info %v | grep performance.nl-cache-positive-entry | grep no", volInfo.Name), - } - _, err = vagrantexec.ConnectAndExec("192.168.10.100:22", cmd, 10, true) - tests.Assert(t, err == nil, "Volume Created with specified options") -} diff --git a/tests/functional/TestSmokeTest/vagrant/Vagrantfile b/tests/functional/TestSmokeTest/vagrant/Vagrantfile deleted file mode 100644 index ae70cb54b6..0000000000 --- a/tests/functional/TestSmokeTest/vagrant/Vagrantfile +++ /dev/null @@ -1,56 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : -# - -NODES = 4 -DISKS = 8 - -Vagrant.configure("2") do |config| - config.ssh.insert_key = false - - config.vm.provider :libvirt do |v,override| - override.vm.box = "centos/7" - override.vm.synced_folder '.', '/home/vagrant/sync', disabled: true - end - config.vm.provider :virtualbox do |v| - config.vm.box = "bento/centos-7.1" - end - - # Make the glusterfs cluster, each with DISKS number of drives - (0..NODES-1).each do |i| - config.vm.define "storage#{i}" do |storage| - storage.vm.hostname = "storage#{i}" - storage.vm.network :private_network, ip: "192.168.10.10#{i}" - (0..DISKS-1).each do |d| - storage.vm.provider :virtualbox do |vb| - vb.customize [ "createhd", "--filename", "disk-#{i}-#{d}.vdi", "--size", 500*1024 ] - vb.customize [ "storageattach", :id, "--storagectl", "SATA Controller", "--port", 3+d, "--device", 0, "--type", "hdd", "--medium", "disk-#{i}-#{d}.vdi" ] - vb.memory = 1024 - vb.cpus = 2 - end - driverletters = ('b'..'z').to_a - storage.vm.provider :libvirt do |lv| - lv.storage :file, :device => "vd#{driverletters[d]}", :path => "test_smoke_disk-#{i}-#{d}.disk", :size => '500G' - lv.memory = 1024 - lv.cpus =2 - end - end - - if i == (NODES-1) - # View the documentation for the provider you're using for more - # information on available options. - storage.vm.provision :ansible do |ansible| - ansible.limit = "all" - ansible.playbook = "site.yml" - ansible.groups = { - "client" => ["client"], - "heketi" => ["storage0"], - "gluster" => (0..NODES-1).map {|j| "storage#{j}"}, - } - - end - end - end - end -end - diff --git a/tests/functional/TestSmokeTest/vagrant/roles/common/files/insecure_private_key b/tests/functional/TestSmokeTest/vagrant/roles/common/files/insecure_private_key deleted file mode 100644 index 7d6a083909..0000000000 --- a/tests/functional/TestSmokeTest/vagrant/roles/common/files/insecure_private_key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI -w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP -kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 -hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO -Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW -yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd -ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 -Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf -TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK -iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A -sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf -4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP -cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk -EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN -CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX -3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG -YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj -3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ -dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz -6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC -P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF -llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ -kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH -+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ -NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= ------END RSA PRIVATE KEY----- diff --git a/tests/functional/TestSmokeTest/vagrant/roles/common/tasks/main.yml b/tests/functional/TestSmokeTest/vagrant/roles/common/tasks/main.yml deleted file mode 100644 index 6c34987f42..0000000000 --- a/tests/functional/TestSmokeTest/vagrant/roles/common/tasks/main.yml +++ /dev/null @@ -1,26 +0,0 @@ -- name: clean disks - command: dd if=/dev/zero of={{ item }} count=100 bs=1024k - with_items: - - /dev/vdb - - /dev/vdc - - /dev/vdd - - /dev/vde - - /dev/vdf - - /dev/vdg - - /dev/vdh - - /dev/vdi - -- name: install packages - yum: name={{ item }} state=present - with_items: - - epel-release - - centos-release-gluster - -- name: copy private key - copy: src=insecure_private_key owner=vagrant group=vagrant dest=/home/vagrant/.ssh/id_rsa force=no mode=0600 - -- name: clean iptables - command: iptables -F - -- name: disable selinux - selinux: state=disabled diff --git a/tests/functional/TestSmokeTest/vagrant/roles/gluster/tasks/main.yml b/tests/functional/TestSmokeTest/vagrant/roles/gluster/tasks/main.yml deleted file mode 100644 index 6499fb296c..0000000000 --- a/tests/functional/TestSmokeTest/vagrant/roles/gluster/tasks/main.yml +++ /dev/null @@ -1,8 +0,0 @@ -- name: install glusterfs - yum: name={{ item }} state=present - with_items: - - glusterfs-server - - glusterfs-client - -- name: start glusterd - service: name=glusterd state=started enabled=yes diff --git a/tests/functional/TestSmokeTest/vagrant/site.yml b/tests/functional/TestSmokeTest/vagrant/site.yml deleted file mode 100644 index f973bb9ee6..0000000000 --- a/tests/functional/TestSmokeTest/vagrant/site.yml +++ /dev/null @@ -1,11 +0,0 @@ -- hosts: all - become: yes - become_method: sudo - roles: - - common - -- hosts: gluster - become: yes - become_method: sudo - roles: - - gluster diff --git a/tests/functional/TestSmokeTest/vagrant/up.sh b/tests/functional/TestSmokeTest/vagrant/up.sh deleted file mode 100755 index 35cbe79d83..0000000000 --- a/tests/functional/TestSmokeTest/vagrant/up.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -vagrant up --no-provision $@ -vagrant provision diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/config/heketi.json b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/config/heketi.json deleted file mode 100644 index 6bdb238949..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/config/heketi.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "_port_comment": "Heketi Server Port Number", - "port" : "8080", - - "_use_auth": "Enable JWT authorization. Please enable for deployment", - "use_auth" : true, - - "_jwt" : "Private keys for access", - "jwt" : { - "_admin" : "Admin has access to all APIs", - "admin" : { - "key" : "adminkey" - }, - "_user" : "User only has access to /volumes endpoint", - "user" : { - "key" : "userkey" - } - }, - - "_glusterfs_comment": "GlusterFS Configuration", - "glusterfs" : { - - "_executor_comment": "Execute plugin. Possible choices: mock, ssh", - "executor" : "ssh", - - "_db_comment": "Database file name", - "db" : "heketi.db", - - "sshexec" : { - "keyfile" : "config/insecure_private_key", - "sudo" : true, - "user" : "vagrant" - } - } -} diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/config/insecure_private_key b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/config/insecure_private_key deleted file mode 100644 index 7d6a083909..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/config/insecure_private_key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI -w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP -kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 -hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO -Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW -yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd -ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 -Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf -TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK -iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A -sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf -4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP -cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk -EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN -CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX -3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG -YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj -3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ -dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz -6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC -P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF -llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ -kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH -+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ -NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= ------END RSA PRIVATE KEY----- diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/run.sh b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/run.sh deleted file mode 100755 index 5270bd664e..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/run.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -CURRENT_DIR=`pwd` -HEKETI_SERVER_BUILD_DIR=../../.. -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server - -source ${FUNCTIONAL_DIR}/lib.sh - -functional_tests - diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/teardown.sh b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/teardown.sh deleted file mode 100755 index 6f066fa432..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/teardown.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -CURRENT_DIR=`pwd` -HEKETI_SERVER_BUILD_DIR=../../.. -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server - -source ${FUNCTIONAL_DIR}/lib.sh - -teardown - diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/tests/heketi_test.go b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/tests/heketi_test.go deleted file mode 100644 index 77336fd489..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/tests/heketi_test.go +++ /dev/null @@ -1,269 +0,0 @@ -// +build functional - -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// -package functional - -import ( - "fmt" - "testing" - "time" - - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/heketi/pkg/utils/ssh" - "github.com/heketi/tests" -) - -// These are the settings for the vagrant file -const ( - - // The heketi server must be running on the host - heketiUrl = "http://127.0.0.1:8080" - - // VM Information - DISKS = 3 - NODES = 3 - ZONES = 2 - CLUSTERS = 1 -) - -var ( - // Heketi client - heketi = client.NewClient(heketiUrl, "admin", "adminkey") - logger = utils.NewLogger("[test]", utils.LEVEL_DEBUG) -) - -func getdisks() []string { - - diskletters := make([]string, DISKS) - for index, i := 0, []byte("b")[0]; index < DISKS; index, i = index+1, i+1 { - diskletters[index] = "/dev/vd" + string(i) - } - - return diskletters -} - -func getnodes() []string { - nodelist := make([]string, NODES) - - for index, ip := 0, 100; index < NODES; index, ip = index+1, ip+1 { - nodelist[index] = "192.168.10." + fmt.Sprintf("%v", ip) - } - - return nodelist -} - -func setupCluster(t *testing.T) { - tests.Assert(t, heketi != nil) - - nodespercluster := NODES / CLUSTERS - nodes := getnodes() - sg := utils.NewStatusGroup() - for cluster := 0; cluster < CLUSTERS; cluster++ { - sg.Add(1) - go func(nodes_in_cluster []string) { - defer sg.Done() - // Create a cluster - cluster, err := heketi.ClusterCreate() - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Add nodes sequentially due to probes - for index, hostname := range nodes_in_cluster { - nodeReq := &api.NodeAddRequest{} - nodeReq.ClusterId = cluster.Id - nodeReq.Hostnames.Manage = []string{hostname} - nodeReq.Hostnames.Storage = []string{hostname} - nodeReq.Zone = index%ZONES + 1 - - node, err := heketi.NodeAdd(nodeReq) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Add devices all concurrently - for _, disk := range getdisks() { - sg.Add(1) - go func(d string) { - defer sg.Done() - - driveReq := &api.DeviceAddRequest{} - driveReq.Name = d - driveReq.NodeId = node.Id - - err := heketi.DeviceAdd(driveReq) - if err != nil { - logger.Err(err) - sg.Err(err) - } - }(disk) - } - } - }(nodes[cluster*nodespercluster : (cluster+1)*nodespercluster]) - } - - // Wait here for results - err := sg.Result() - tests.Assert(t, err == nil) - -} - -func teardownCluster(t *testing.T) { - clusters, err := heketi.ClusterList() - tests.Assert(t, err == nil) - - sg := utils.NewStatusGroup() - for _, cluster := range clusters.Clusters { - - sg.Add(1) - go func(clusterId string) { - defer sg.Done() - - clusterInfo, err := heketi.ClusterInfo(clusterId) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Delete volumes in this cluster - for _, volume := range clusterInfo.Volumes { - err := heketi.VolumeDelete(volume) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - } - - // Delete all devices in the cluster concurrently - deviceSg := utils.NewStatusGroup() - for _, node := range clusterInfo.Nodes { - - // Get node information - nodeInfo, err := heketi.NodeInfo(node) - if err != nil { - logger.Err(err) - deviceSg.Err(err) - return - } - - // Delete each device - for _, device := range nodeInfo.DevicesInfo { - deviceSg.Add(1) - go func(id string) { - defer deviceSg.Done() - - err := heketi.DeviceDelete(id) - if err != nil { - logger.Err(err) - deviceSg.Err(err) - return - } - - }(device.Id) - } - } - err = deviceSg.Result() - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Delete nodes - for _, node := range clusterInfo.Nodes { - err = heketi.NodeDelete(node) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - } - - // Delete cluster - err = heketi.ClusterDelete(clusterId) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - }(cluster) - - } - - err = sg.Result() - tests.Assert(t, err == nil) -} - -func TestConnection(t *testing.T) { - err := heketi.Hello() - tests.Assert(t, err == nil) -} - -func TestVolumeNotDeletedWhenNodeIsDown(t *testing.T) { - - // Setup the VM storage topology - teardownCluster(t) - setupCluster(t) - - // Create a volume - volReq := &api.VolumeCreateRequest{} - volReq.Size = 100 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - - volInfo, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - - // SSH into one system and power it off - exec := ssh.NewSshExecWithKeyFile(logger, "vagrant", "../config/insecure_private_key") - - // Turn off glusterd - cmd := []string{"service glusterd stop"} - exec.ConnectAndExec("192.168.10.100:22", cmd, 10, true) - time.Sleep(2 * time.Second) - - // Try to delete the volume - err = heketi.VolumeDelete(volInfo.Id) - tests.Assert(t, err != nil, err) - logger.Info("Error Returned:\n%v", err) - - // Check that the volume is still there - info, err := heketi.VolumeInfo(volInfo.Id) - tests.Assert(t, err == nil) - tests.Assert(t, info.Id == volInfo.Id) - - // Now poweroff node - // Don't check for error, since it the connection will be disconnected - // by powering off, it will return an error - cmd = []string{"poweroff"} - exec.ConnectAndExec("192.168.10.100:22", cmd, 10, true) - - // Wait some time for the system to come down - time.Sleep(5 * time.Second) - - // Try to delete the volume - err = heketi.VolumeDelete(volInfo.Id) - tests.Assert(t, err != nil, err) - logger.Info("Error Returned:\n%v", err) - - // Check that the volume is still there - info, err = heketi.VolumeInfo(volInfo.Id) - tests.Assert(t, err == nil) - tests.Assert(t, info.Id == volInfo.Id) -} diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/Vagrantfile b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/Vagrantfile deleted file mode 100644 index b6dd54ed60..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/Vagrantfile +++ /dev/null @@ -1,46 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : -# - -NODES = 3 -DISKS = 3 - -Vagrant.configure("2") do |config| - config.ssh.insert_key = false - - config.vm.provider :libvirt do |v,override| - override.vm.box = "centos/7" - override.vm.synced_folder '.', '/home/vagrant/sync', disabled: true - end - - # Make the glusterfs cluster, each with DISKS number of drives - (0..NODES-1).each do |i| - config.vm.define "storage#{i}" do |storage| - storage.vm.hostname = "storage#{i}" - storage.vm.network :private_network, ip: "192.168.10.10#{i}" - driverletters = ('b'..'z').to_a - (0..DISKS-1).each do |d| - storage.vm.provider :libvirt do |lv| - lv.storage :file, :device => "vd#{driverletters[d]}", :path => "test_volume_delete_disk-#{i}-#{d}.disk", :size => '500G' - lv.memory = 1024 - lv.cpus =2 - end - end - - if i == (NODES-1) - # View the documentation for the provider you're using for more - # information on available options. - storage.vm.provision :ansible do |ansible| - ansible.limit = "all" - ansible.playbook = "site.yml" - ansible.groups = { - "heketi" => ["storage0"], - "gluster" => (0..NODES-1).map {|j| "storage#{j}"}, - } - - end - end - end - end -end - diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/common/files/insecure_private_key b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/common/files/insecure_private_key deleted file mode 100644 index 7d6a083909..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/common/files/insecure_private_key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI -w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP -kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 -hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO -Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW -yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd -ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 -Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf -TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK -iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A -sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf -4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP -cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk -EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN -CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX -3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG -YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj -3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ -dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz -6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC -P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF -llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ -kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH -+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ -NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= ------END RSA PRIVATE KEY----- diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/common/tasks/main.yml b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/common/tasks/main.yml deleted file mode 100644 index 1c419e1056..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/common/tasks/main.yml +++ /dev/null @@ -1,21 +0,0 @@ -- name: clean disks - command: dd if=/dev/zero of={{ item }} count=100 bs=1024k - with_items: - - /dev/vdb - - /dev/vdc - - /dev/vdd - -- name: install packages - yum: name={{ item }} state=present - with_items: - - epel-release - - centos-release-gluster - -- name: copy private key - copy: src=insecure_private_key owner=vagrant group=vagrant dest=/home/vagrant/.ssh/id_rsa force=no mode=0600 - -- name: clean iptables - command: iptables -F - -- name: disable selinux - selinux: state=disabled diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/gluster/tasks/main.yml b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/gluster/tasks/main.yml deleted file mode 100644 index 9dfeea91d4..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/roles/gluster/tasks/main.yml +++ /dev/null @@ -1,7 +0,0 @@ -- name: install glusterfs - yum: name={{ item }} state=present - with_items: - - glusterfs-server - -- name: start glusterd - service: name=glusterd state=started enabled=yes diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/site.yml b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/site.yml deleted file mode 100644 index f973bb9ee6..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/site.yml +++ /dev/null @@ -1,11 +0,0 @@ -- hosts: all - become: yes - become_method: sudo - roles: - - common - -- hosts: gluster - become: yes - become_method: sudo - roles: - - gluster diff --git a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/up.sh b/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/up.sh deleted file mode 100755 index 35cbe79d83..0000000000 --- a/tests/functional/TestVolumeNotDeletedWhenNodeIsDown/vagrant/up.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -vagrant up --no-provision $@ -vagrant provision diff --git a/tests/functional/TestVolumeSnapshotBehavior/README.md b/tests/functional/TestVolumeSnapshotBehavior/README.md deleted file mode 100644 index fd218f1980..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# TestVolumeSnapshotBehavior -Test that Heketi manages volumes with snapshots correctly - -## Requirements - -* Vagrant -* Andible -* Hypervisor: Only Libvirt/KVM - -For simplicity you can use the ansible script in https://github.com/heketi/setup-vagrant-libvirt -to setup your system for functional tests. - -## Setup - -Type: - -``` -$ ./up -``` - -## Running the tests - -* Go to the top of the source tree build and run a new Heketi server: - -``` -$ rm heketi.db -$ make -$ ./heketi --config=tests/functional/TestVolumeSnapshotBehavior/config/heketi.json 2>&1 | tee log - -``` - -* Then start running the tests - -``` -$ cd tests/functional/large/tests -$ go test -timeout=1h -tags ftlarge -``` - -Output will be shows by the logs on the heketi server. diff --git a/tests/functional/TestVolumeSnapshotBehavior/config/heketi.json b/tests/functional/TestVolumeSnapshotBehavior/config/heketi.json deleted file mode 100644 index 6bdb238949..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/config/heketi.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "_port_comment": "Heketi Server Port Number", - "port" : "8080", - - "_use_auth": "Enable JWT authorization. Please enable for deployment", - "use_auth" : true, - - "_jwt" : "Private keys for access", - "jwt" : { - "_admin" : "Admin has access to all APIs", - "admin" : { - "key" : "adminkey" - }, - "_user" : "User only has access to /volumes endpoint", - "user" : { - "key" : "userkey" - } - }, - - "_glusterfs_comment": "GlusterFS Configuration", - "glusterfs" : { - - "_executor_comment": "Execute plugin. Possible choices: mock, ssh", - "executor" : "ssh", - - "_db_comment": "Database file name", - "db" : "heketi.db", - - "sshexec" : { - "keyfile" : "config/insecure_private_key", - "sudo" : true, - "user" : "vagrant" - } - } -} diff --git a/tests/functional/TestVolumeSnapshotBehavior/config/insecure_private_key b/tests/functional/TestVolumeSnapshotBehavior/config/insecure_private_key deleted file mode 100644 index 7d6a083909..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/config/insecure_private_key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI -w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP -kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 -hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO -Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW -yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd -ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 -Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf -TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK -iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A -sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf -4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP -cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk -EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN -CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX -3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG -YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj -3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ -dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz -6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC -P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF -llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ -kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH -+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ -NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= ------END RSA PRIVATE KEY----- diff --git a/tests/functional/TestVolumeSnapshotBehavior/run.sh b/tests/functional/TestVolumeSnapshotBehavior/run.sh deleted file mode 100755 index 5270bd664e..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/run.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -CURRENT_DIR=`pwd` -HEKETI_SERVER_BUILD_DIR=../../.. -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server - -source ${FUNCTIONAL_DIR}/lib.sh - -functional_tests - diff --git a/tests/functional/TestVolumeSnapshotBehavior/teardown.sh b/tests/functional/TestVolumeSnapshotBehavior/teardown.sh deleted file mode 100755 index 6f066fa432..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/teardown.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -CURRENT_DIR=`pwd` -HEKETI_SERVER_BUILD_DIR=../../.. -FUNCTIONAL_DIR=${CURRENT_DIR}/.. -HEKETI_SERVER=${FUNCTIONAL_DIR}/heketi-server - -source ${FUNCTIONAL_DIR}/lib.sh - -teardown - diff --git a/tests/functional/TestVolumeSnapshotBehavior/tests/heketi_test.go b/tests/functional/TestVolumeSnapshotBehavior/tests/heketi_test.go deleted file mode 100644 index 2641d5c831..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/tests/heketi_test.go +++ /dev/null @@ -1,259 +0,0 @@ -// +build functional - -// -// Copyright (c) 2015 The heketi Authors -// -// This file is licensed to you under your choice of the GNU Lesser -// General Public License, version 3 or any later version (LGPLv3 or -// later), or the GNU General Public License, version 2 (GPLv2), in all -// cases as published by the Free Software Foundation. -// -package functional - -import ( - "fmt" - "testing" - - client "github.com/heketi/heketi/client/api/go-client" - "github.com/heketi/heketi/pkg/glusterfs/api" - "github.com/heketi/heketi/pkg/utils" - "github.com/heketi/heketi/pkg/utils/ssh" - "github.com/heketi/tests" -) - -// These are the settings for the vagrant file -const ( - - // The heketi server must be running on the host - heketiUrl = "http://127.0.0.1:8080" - - // VM Information - DISKS = 8 - NODES = 4 - ZONES = 4 - CLUSTERS = 1 -) - -var ( - // Heketi client - heketi = client.NewClient(heketiUrl, "admin", "adminkey") - logger = utils.NewLogger("[test]", utils.LEVEL_DEBUG) -) - -func getdisks() []string { - - diskletters := make([]string, DISKS) - for index, i := 0, []byte("b")[0]; index < DISKS; index, i = index+1, i+1 { - diskletters[index] = "/dev/vd" + string(i) - } - - return diskletters -} - -func getnodes() []string { - nodelist := make([]string, NODES) - - for index, ip := 0, 100; index < NODES; index, ip = index+1, ip+1 { - nodelist[index] = "192.168.10." + fmt.Sprintf("%v", ip) - } - - return nodelist -} - -func setupCluster(t *testing.T) { - tests.Assert(t, heketi != nil) - - nodespercluster := NODES / CLUSTERS - nodes := getnodes() - sg := utils.NewStatusGroup() - for cluster := 0; cluster < CLUSTERS; cluster++ { - sg.Add(1) - go func(nodes_in_cluster []string) { - defer sg.Done() - // Create a cluster - cluster, err := heketi.ClusterCreate() - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Add nodes sequentially due to probes - for index, hostname := range nodes_in_cluster { - nodeReq := &api.NodeAddRequest{} - nodeReq.ClusterId = cluster.Id - nodeReq.Hostnames.Manage = []string{hostname} - nodeReq.Hostnames.Storage = []string{hostname} - nodeReq.Zone = index%ZONES + 1 - - node, err := heketi.NodeAdd(nodeReq) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Add devices all concurrently - for _, disk := range getdisks() { - sg.Add(1) - go func(d string) { - defer sg.Done() - - driveReq := &api.DeviceAddRequest{} - driveReq.Name = d - driveReq.NodeId = node.Id - - err := heketi.DeviceAdd(driveReq) - if err != nil { - logger.Err(err) - sg.Err(err) - } - }(disk) - } - } - }(nodes[cluster*nodespercluster : (cluster+1)*nodespercluster]) - } - - // Wait here for results - err := sg.Result() - tests.Assert(t, err == nil) - -} - -func teardownCluster(t *testing.T) { - clusters, err := heketi.ClusterList() - tests.Assert(t, err == nil) - - sg := utils.NewStatusGroup() - for _, cluster := range clusters.Clusters { - - sg.Add(1) - go func(clusterId string) { - defer sg.Done() - - clusterInfo, err := heketi.ClusterInfo(clusterId) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Delete volumes in this cluster - for _, volume := range clusterInfo.Volumes { - err := heketi.VolumeDelete(volume) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - } - - // Delete all devices in the cluster concurrently - deviceSg := utils.NewStatusGroup() - for _, node := range clusterInfo.Nodes { - - // Get node information - nodeInfo, err := heketi.NodeInfo(node) - if err != nil { - logger.Err(err) - deviceSg.Err(err) - return - } - - // Delete each device - for _, device := range nodeInfo.DevicesInfo { - deviceSg.Add(1) - go func(id string) { - defer deviceSg.Done() - - err := heketi.DeviceDelete(id) - if err != nil { - logger.Err(err) - deviceSg.Err(err) - return - } - - }(device.Id) - } - } - err = deviceSg.Result() - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - // Delete nodes - for _, node := range clusterInfo.Nodes { - err = heketi.NodeDelete(node) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - } - - // Delete cluster - err = heketi.ClusterDelete(clusterId) - if err != nil { - logger.Err(err) - sg.Err(err) - return - } - - }(cluster) - - } - - err = sg.Result() - tests.Assert(t, err == nil) -} - -func TestConnection(t *testing.T) { - err := heketi.Hello() - tests.Assert(t, err == nil) -} - -func TestHeketiVolumeSnapshotBehavior(t *testing.T) { - - // Setup the VM storage topology - teardownCluster(t) - setupCluster(t) - defer teardownCluster(t) - - // Create a volume - volReq := &api.VolumeCreateRequest{} - volReq.Size = 1024 - volReq.Durability.Type = api.DurabilityReplicate - volReq.Durability.Replicate.Replica = 3 - volReq.Snapshot.Enable = true - volReq.Snapshot.Factor = 1.5 - - volInfo, err := heketi.VolumeCreate(volReq) - tests.Assert(t, err == nil) - - // SSH into system and execute gluster command to create a snapshot - exec := ssh.NewSshExecWithKeyFile(logger, "vagrant", "../config/insecure_private_key") - cmd := []string{ - fmt.Sprintf("sudo gluster --mode=script snapshot create mysnap %v no-timestamp", volInfo.Name), - "sudo gluster --mode=script snapshot activate mysnap", - } - _, err = exec.ConnectAndExec("192.168.10.100:22", cmd, 10, true) - tests.Assert(t, err == nil, err) - - // Try to delete the volume - err = heketi.VolumeDelete(volInfo.Id) - tests.Assert(t, err != nil, err) - - // Now clean up the snapshot - cmd = []string{ - "sudo gluster --mode=script snapshot deactivate mysnap", - "sudo gluster --mode=script snapshot delete mysnap", - } - _, err = exec.ConnectAndExec("192.168.10.100:22", cmd, 10, true) - tests.Assert(t, err == nil, err) - - // Try to delete the volume - err = heketi.VolumeDelete(volInfo.Id) - tests.Assert(t, err == nil, err) -} diff --git a/tests/functional/TestVolumeSnapshotBehavior/vagrant/Vagrantfile b/tests/functional/TestVolumeSnapshotBehavior/vagrant/Vagrantfile deleted file mode 100644 index af7e40009e..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/vagrant/Vagrantfile +++ /dev/null @@ -1,46 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : -# - -NODES = 4 -DISKS = 8 - -Vagrant.configure("2") do |config| - config.ssh.insert_key = false - - config.vm.provider :libvirt do |v,override| - override.vm.box = "centos/7" - override.vm.synced_folder '.', '/home/vagrant/sync', disabled: true - end - - # Make the glusterfs cluster, each with DISKS number of drives - (0..NODES-1).each do |i| - config.vm.define "storage#{i}" do |storage| - storage.vm.hostname = "storage#{i}" - storage.vm.network :private_network, ip: "192.168.10.10#{i}" - driverletters = ('b'..'z').to_a - (0..DISKS-1).each do |d| - storage.vm.provider :libvirt do |lv| - lv.storage :file, :device => "vd#{driverletters[d]}", :path => "test_volume_snapshot_disk-#{i}-#{d}.disk", :size => '500G' - lv.memory = 1024 - lv.cpus =2 - end - end - - if i == (NODES-1) - # View the documentation for the provider you're using for more - # information on available options. - storage.vm.provision :ansible do |ansible| - ansible.limit = "all" - ansible.playbook = "site.yml" - ansible.groups = { - "heketi" => ["storage0"], - "gluster" => (0..NODES-1).map {|j| "storage#{j}"}, - } - - end - end - end - end -end - diff --git a/tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/common/files/insecure_private_key b/tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/common/files/insecure_private_key deleted file mode 100644 index 7d6a083909..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/common/files/insecure_private_key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI -w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP -kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 -hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO -Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW -yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd -ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 -Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf -TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK -iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A -sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf -4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP -cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk -EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN -CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX -3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG -YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj -3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ -dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz -6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC -P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF -llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ -kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH -+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ -NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= ------END RSA PRIVATE KEY----- diff --git a/tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/common/tasks/main.yml b/tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/common/tasks/main.yml deleted file mode 100644 index 48a1d3d538..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/common/tasks/main.yml +++ /dev/null @@ -1,22 +0,0 @@ -- name: clean disks - command: dd if=/dev/zero of={{ item }} count=100 bs=1024k - with_items: - - /dev/vdb - - /dev/vdc - - /dev/vdd - - /dev/vde - -- name: install packages - yum: name={{ item }} state=present - with_items: - - epel-release - - centos-release-gluster - -- name: copy private key - copy: src=insecure_private_key owner=vagrant group=vagrant dest=/home/vagrant/.ssh/id_rsa force=no mode=0600 - -- name: clean iptables - command: iptables -F - -- name: disable selinux - selinux: state=disabled diff --git a/tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/gluster/tasks/main.yml b/tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/gluster/tasks/main.yml deleted file mode 100644 index 9dfeea91d4..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/vagrant/roles/gluster/tasks/main.yml +++ /dev/null @@ -1,7 +0,0 @@ -- name: install glusterfs - yum: name={{ item }} state=present - with_items: - - glusterfs-server - -- name: start glusterd - service: name=glusterd state=started enabled=yes diff --git a/tests/functional/TestVolumeSnapshotBehavior/vagrant/site.yml b/tests/functional/TestVolumeSnapshotBehavior/vagrant/site.yml deleted file mode 100644 index f973bb9ee6..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/vagrant/site.yml +++ /dev/null @@ -1,11 +0,0 @@ -- hosts: all - become: yes - become_method: sudo - roles: - - common - -- hosts: gluster - become: yes - become_method: sudo - roles: - - gluster diff --git a/tests/functional/TestVolumeSnapshotBehavior/vagrant/up.sh b/tests/functional/TestVolumeSnapshotBehavior/vagrant/up.sh deleted file mode 100755 index 35cbe79d83..0000000000 --- a/tests/functional/TestVolumeSnapshotBehavior/vagrant/up.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -vagrant up --no-provision $@ -vagrant provision diff --git a/tests/functional/lib.sh b/tests/functional/lib.sh deleted file mode 100644 index 7b67518f4d..0000000000 --- a/tests/functional/lib.sh +++ /dev/null @@ -1,76 +0,0 @@ - -fail() { - echo "==> ERROR: $@" - exit 1 -} - -println() { - echo "==> $1" -} - -_sudo() { - if [ ${UID} = 0 ] ; then - ${@} - else - sudo -E ${@} - fi -} - -HEKETI_PID= -start_heketi() { - ( cd $HEKETI_SERVER_BUILD_DIR && make && cp heketi $HEKETI_SERVER ) - if [ $? -ne 0 ] ; then - fail "Unable to build Heketi" - fi - - # Start server - rm -f heketi.db > /dev/null 2>&1 - $HEKETI_SERVER --config=config/heketi.json & - HEKETI_PID=$! - sleep 2 -} - -start_vagrant() { - cd vagrant - _sudo ./up.sh || fail "unable to start vagrant virtual machines" - cd .. -} - -teardown_vagrant() { - cd vagrant - _sudo vagrant destroy -f - cd .. -} - -run_tests() { - cd tests - go test -timeout=1h -tags functional - gotest_result=$? - cd .. -} - -force_cleanup_libvirt_disks() { - # Sometimes disks are not deleted - for i in `_sudo virsh vol-list default | grep "*.disk" | awk '{print $1}'` ; do - _sudo virsh vol-delete --pool default "${i}" || fail "Unable to delete disk $i" - done -} - -teardown() { - teardown_vagrant - force_cleanup_libvirt_disks - rm -f heketi.db > /dev/null 2>&1 -} - -functional_tests() { - start_vagrant - start_heketi - - run_tests - - kill $HEKETI_PID - teardown - - exit $gotest_result -} - diff --git a/tests/functional/run.sh b/tests/functional/run.sh deleted file mode 100755 index fbf4fcaaef..0000000000 --- a/tests/functional/run.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh - -source ./lib.sh - -teardown_all() { - results=0 - for testDir in * ; do - if [ -x $testDir/teardown.sh ] ; then - println "TEARDOWN $testDir" - cd $testDir - teardown.sh - cd .. - fi - done -} - -### MAIN ### - -# See https://bugzilla.redhat.com/show_bug.cgi?id=1327740 -_sudo setenforce 0 - -starttime=`date` -export PATH=$PATH:. - -# Check go can build -if [ -z $GOPATH ] ; then - fail "GOPATH must be specified" -fi - -# Clean up -rm -f heketi-server > /dev/null 2>&1 -teardown_all - -# Check each dir for tests -results=0 -for testDir in * ; do - if [ -x $testDir/run.sh ] ; then - println "TEST $testDir" - cd $testDir - - # Run the command with a large timeout. - # Just large enough so that it doesn't run forever. - timeout 1h run.sh ; result=$? - - if [ $result -ne 0 ] ; then - println "FAILED $testDir" - println "TEARDOWN $testDir" - teardown.sh - results=1 - else - println "PASSED $testDir" - fi - - cd .. - fi -done - -# Summary -println "Started $starttime" -println "Ended `date`" -if [ $results -eq 0 ] ; then - println "PASSED" -else - println "FAILED" -fi - -exit $results