├── LICENSE ├── README.rdoc ├── Rakefile ├── TODO ├── examples └── virtual_machines.rb ├── lib ├── spherical.rb └── spherical │ ├── .DS_Store │ ├── base.rb │ ├── constants.rb │ ├── host.rb │ ├── interface.rb │ ├── mixins │ ├── callable_content.rb │ ├── searchable.rb │ └── traversable.rb │ ├── types │ ├── datacenter.rb │ ├── folder.rb │ ├── properties.rb │ ├── property_filter.rb │ ├── service.rb │ ├── task.rb │ └── virtual_machine.rb │ ├── util.rb │ └── vim.wsdl └── spec ├── host_spec.rb ├── managedref_spec.rb └── spec_helper.rb /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Spherical 2 | 3 | Spherical provides access to VMware vSphere servers and their resources via a self-contained Ruby gem. It provides the following features/benefits: 4 | 5 | * Small number of dependencies; only relies on savon and xmlsimple 6 | * Utilizes meta-programming wherever possible to introspect server-side operations and data structures, enabling simple adaptation to changes in the vSphere API. 7 | * Abstracts away complex traversals of vSphere tree structures, as well as access to properties, etc. 8 | * Covers almost the entire vSphere API -- if you can do it directly with the vSphere SOAP API, you can do it with Spherical. 9 | 10 | == Requirements 11 | 12 | When installing Spherical, the following gems will be installed automatically if they are not already: 13 | 14 | * savon -- a library providing simplified access to WSDL endpoints 15 | * xmlsimple -- a robust XML parsing library that converts even complex XML to simple native data structures. 16 | * rspec -- spec testing support 17 | 18 | == RDoc Documentation 19 | 20 | See the auto-generated documentation[http://rubydoc.info/github/kenkeiter/spherical/] -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | require 'rspec/core/rake_task' 24 | 25 | RSpec::Core::RakeTask.new(:spec) 26 | 27 | desc "Open an irb session preloaded with this library" 28 | task :console do 29 | sh "irb -rubygems -I lib -r spherical.rb -I spec -r spec_helper.rb" 30 | end -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | 4 | * Convert to gem 5 | * Rewrite tests that were removed when changing to raw XML generation. 6 | savon-spec doesn't support mocks for raw XML requests. 7 | * Write patch for savon-spec library to handle mocks for objects that build 8 | their own XML, or get rid of savon altogether. 9 | * Finish/improve documentation 10 | * Add more examples 11 | * Improve test coverage 12 | * Improve traversal efficiency -------------------------------------------------------------------------------- /examples/virtual_machines.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | require 'spherical' 24 | 25 | # Create a new host, providing a :username and :password so we don't have 26 | # to manually call login() 27 | host = Spherical::Host.new :api => 'https://vsphere.example.com/sdk', 28 | :username => 'username', 29 | :password => 'password' 30 | 31 | # Recurse over all of the datacenters in the root folder on host. 32 | host.datacenters.each do |datacenter| 33 | 34 | # Recurse over VMs in the datacenter's inventory and print the MAC 35 | # addresses of each NIC interface in relation to the device name. 36 | datacenter.inventory.each do |vm| 37 | puts "Found VM #{vm.name} in #{datacenter.id}:" 38 | vm.mac_addresses.each do |if_name, address| 39 | puts "\t#{if_name} => #{address}" 40 | end 41 | end 42 | 43 | end 44 | 45 | # Now, find a VM within the first datacenter by MAC address 46 | datacenter = host.datacenters.first 47 | vm_instance = datacenter.find_by_uuid('564d5e5866f52d0403310abd6fa71988') 48 | 49 | # Halt the instance abruptly, and wait for that task to complete. The 50 | # complete! call on the resulting Task object will poll the server (thus, 51 | # blocking further execution) until the request has been successfully completed. 52 | vm_instance.halt!.complete! 53 | 54 | # Now, mark the instance as a template 55 | vm_instance.mark_as_template 56 | 57 | # And deploy a clone of that instance into the root folder, powering it on 58 | # upon completion by default. 59 | clone_task = vm_instance.clone('new_instance_name', datacenter.vm_folder, datacenter.datastore.first) 60 | clone_task.complete! 61 | new_vm = clone_task['info.result'] 62 | -------------------------------------------------------------------------------- /lib/spherical.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | BASEPATH = File.dirname(__FILE__) 24 | $: << File.join(BASEPATH, 'spherical') 25 | 26 | require 'savon' 27 | require 'xmlsimple' 28 | require 'builder' 29 | require 'ostruct' 30 | 31 | require 'spherical/util' 32 | require 'spherical/constants' 33 | require 'spherical/interface' 34 | require 'spherical/base' 35 | require 'spherical/host' 36 | 37 | Spherical.require_all File.join(BASEPATH, 'spherical/mixins/*.rb') 38 | Spherical.require_all File.join(BASEPATH, 'spherical/types/*.rb') 39 | 40 | Savon.configure do |config| 41 | config.log = false 42 | end -------------------------------------------------------------------------------- /lib/spherical/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenkeiter/spherical/07004d33c649c6be269fd337e2a09f455663c8c5/lib/spherical/.DS_Store -------------------------------------------------------------------------------- /lib/spherical/base.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | # ManagedReference provides an abstraction layer for managing VMware 26 | # ManagedObjectReferences. The VMware SOAP API provides client-side access 27 | # to "object-oriented" server-side resources (within the context of the 28 | # session) by returning references to those objects, upon which SOAP 29 | # methods can be called, properties can be retrieved, etc. 30 | # 31 | # ManagedReferences are identified by an ID string that is unique per 32 | # type and vSphere installation. ManagedReference tracks all 33 | # ManagedObjectReferences, and provides common mechanisms for access to 34 | # properties, structure traversal, and dynamic typing. In addition, 35 | # ManagedReference can be subclassed to extend Ruby functionality for 36 | # specific ManagedObjectReference types. 37 | # 38 | # Ruby classes are dynamically defined for each ManagedObjectReference type 39 | # as they are instantiated and returned to the client. This means that, 40 | # even if ManagedReference hasn't been subclassed for a specific type (i.e. 41 | # that type has been defined), returned ManagedObjectReferences will still 42 | # behave as objects in Ruby -- methods can be called on them, properties 43 | # can be accessed, etc. 44 | 45 | class ManagedReference 46 | 47 | @@types = {} # type registry 48 | 49 | class << self 50 | 51 | # Clear all existing types (primarily for testing purposes) 52 | def clear_all! 53 | @@types = {} 54 | end 55 | 56 | # Instantiate a new instance of a given type with: 57 | # +host+:: Host instance against which requests can be made 58 | # +type_sym+:: a Symbol of the name of the type, such as +:VirtualMachine+ 59 | # +id+:: the server-side ID of the object 60 | # +attrs+:: a hash of any attributes that the instance should provide 61 | # accessors for upon instantiation 62 | # 63 | # Will return a new instance of the equivalent Ruby class for +type_sym+. 64 | def build(host, type, id, attrs = {}) 65 | raise 'Type must be Symbol.' unless type.kind_of?(Symbol) 66 | klass = type_defined?(type) ? @@types[type] : Class.new(self).represent_managed(type) 67 | klass.new(host, id).apply_attributes(attrs) # instantiate instance 68 | end 69 | 70 | # When defining a subclass of ManagedReference, specifiy the type that 71 | # the new ManagedReference suclass should represent (DSL style). 72 | # When called, this method will define a +type+ method in the type's 73 | # Ruby class equivalent. 74 | def represent_managed(type) 75 | raise 'Type must be Symbol.' unless type.kind_of?(Symbol) 76 | meta = class << self; self; end 77 | meta.send(:define_method, :type){ type.to_sym } 78 | @@types[type] = self 79 | return self 80 | end 81 | 82 | # property_reader is a DSL-style method that can be called upon any 83 | # type's class to define optionally-memoizing server-side property 84 | # reader methods. This is the equivalent of +attr_reader+, with the 85 | # exception that it provides optional memoization of the results. 86 | # 87 | # If property_reader is called with an array of property names, those 88 | # properties will be fetched (and optionally memoized) *simultaneously* 89 | # in a single call when any of their accessor methods are called. 90 | # 91 | # class RandomManagedObject < ManagedReference 92 | # represent_managed :RandomManagedObject 93 | # property_reader ['someRandomProperty', 'summary'], :memoize => true 94 | # end 95 | # 96 | # In the above example, two accessor methods ()+some_random_property+ 97 | # and +summary+) are defined which provide access to the values of 98 | # those properties. When either of those accessors are called for the 99 | # first time, both properties are fetched from the server in the same 100 | # request, and memoized (cached) for that object. 101 | # 102 | # Note that +property_names+ must be provided as strings. The accessor 103 | # method names will be the underscore version of the +property_names+, 104 | # such that 'somePropertyName' => obj.some_property_name. 105 | # 106 | # If memoization is not enabled, the property will be requested from 107 | # the server each time the method is called! 108 | def property_reader(props, opts = {}) 109 | opts = {:memoize => false}.merge!(opts) 110 | props = props.kind_of?(Array) ? props : [props] # coerce to array 111 | props.each do |prop| 112 | accessor_name = prop.to_s.underscore.to_sym # camelcase to underscore 113 | define_method(accessor_name) do 114 | if !opts[:memoize] or !@attributes.has_key?(accessor_name) 115 | collect(*props).each{ |k, v| @attributes[k.to_s.underscore.to_sym] = v } 116 | end 117 | @attributes[accessor_name] 118 | end 119 | end 120 | end 121 | 122 | # Return bool indicating if a corresponding Ruby class has been created 123 | # (either by subclassing, or anonymously/dynamically) for the given 124 | # type name. 125 | def type_defined?(type) 126 | raise 'Type must be Symbol.' unless type.kind_of?(Symbol) 127 | @@types.include? type 128 | end 129 | 130 | end 131 | 132 | attr_accessor :id 133 | attr_reader :attributes 134 | 135 | # Initialize a new ManagedReference instance. This should only be called 136 | # on ManagedReference subclasses. Note that if a ManagedReference subclass 137 | # overloads this method, it should still call super(*opts), 138 | # otherwise it will not be properly initialized and fail miserably. 139 | def initialize(host, id, *opts) 140 | @host, @id, @opts = host, id, opts 141 | @attributes = {} 142 | end 143 | 144 | # Make a SOAP request to this instance's Host, against this 145 | # ManagedReference's server-side object. Allows for request parameters to 146 | # be provided as a complex hash, or the request may be fine-tuned with 147 | # specific headers or security parameters, and custom-built XML using 148 | # Builder. Accepts the following arguments: 149 | # 150 | # +sym+:: The underscore version of the camelCase method name to be called 151 | # +params+:: (optional when block is given) accepts a complex hash of 152 | # parameters encoded by Interface#coerce_to_xml. Note that 153 | # the managed object reference (<_this>) is always 154 | # automatically added to requests, regardless of whether 155 | # params or a block are given. 156 | # +&block+:: A block accepting the following parameters may be provided 157 | # so that the request may be built manually: 158 | # |xml, soap, wsdl, http, wsse| See Interface#request 159 | # for more detail. 160 | def request(sym, params = {}, &block) 161 | final_params = {:this! => self}.merge!(params) # this! must be first 162 | @host.api.request(sym.to_s.camelize.to_sym, final_params, &block) 163 | end 164 | 165 | # If the Host supports a SOAP method of the name called, that method will 166 | # called accepting the second and third arguments to 167 | # ManagedReference#request. This means that you can call remote methods 168 | # on any object simply by calling the method on the object itself. Local 169 | # methods will override remote methods. 170 | def method_missing(sym, *opts, &block) 171 | return super(sym, *opts, &block) unless @host 172 | if @host.api.supports_request?(sym) 173 | request(sym, *opts, &block) 174 | else 175 | raise NoMethodError.new "undefined method '#{sym}' for #{to_s}" 176 | end 177 | end 178 | 179 | # Create attribute readers for a hash of attributes. 180 | def apply_attributes(attrs) 181 | attrs.each do |name, value| 182 | fixed_name = name.to_s.underscore.to_sym 183 | @attributes[fixed_name] = value 184 | self.class.send(:define_method, fixed_name) do 185 | value 186 | end 187 | end 188 | return self 189 | end 190 | 191 | # Poll the server for updates to the given property +paths+, feeding their 192 | # current values to +&block+ each time until the block yields true. 193 | def wait_for_update(*paths, &block) 194 | begin 195 | filter = @host.service.property_collector.create_filter do |xml| 196 | xml.vim25(:spec, 'vim25:type' => 'PropertyFilterSpec'){ 197 | xml.propSet{ 198 | xml.type self.type 199 | paths.empty? ? xml.all(true) : paths.each{|p| xml.pathSet p } 200 | } 201 | xml.objectSet{ @host.api.coerce_to_xml(xml, :obj => self) } # target obj 202 | } 203 | xml.partialUpdates false 204 | end 205 | 206 | version = '' 207 | loop do 208 | result = @host.service.property_collector.wait_for_updates(:version => version) 209 | version = result.version 210 | if x = block.call 211 | return x 212 | end 213 | end 214 | rescue => e 215 | raise e 216 | ensure 217 | filter.destroy 218 | end 219 | end 220 | 221 | # Get the value of a single property from the server. 222 | def [](key) 223 | @host.service.property_collector.get_object_properties(self, [key])[key] 224 | end 225 | 226 | # Get the value of multiple properties from the server. 227 | def collect(*props) 228 | @host.service.property_collector.get_object_properties(self, props) 229 | end 230 | 231 | # Get the type of ManagedObjectReference this instance represents. Returns 232 | # a Symbol. 233 | def type 234 | self.class.type 235 | end 236 | 237 | def to_s # :nodoc: 238 | "<(remote)#{type}:#{id}>" 239 | end 240 | 241 | end 242 | 243 | end -------------------------------------------------------------------------------- /lib/spherical/constants.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | # Absolute path to the vim.wsdl file, which Savon uses to determine 26 | # valid requests (to some extent). 27 | WSDL_PATH = File.join(File.dirname(__FILE__), '/vim.wsdl') 28 | 29 | # Hash of coercion functions for converting XSD types to native Ruby types 30 | XSD_NATIVE_COERCIONS = {'xsd:string' => lambda{|id| id.to_s }, 31 | 'xsd:boolean' => lambda{|id| !!(id =~ /^true$/i) }, 32 | 'xsd:datetime' => lambda{|id| Time.parse(id) }, 33 | 'xsd:decimal' => lambda{|id| id.to_f }} 34 | 35 | # SOAP namespace descriptor 36 | SOAP_NS = {'xmlns:xsd' => "http://www.w3.org/2001/XMLSchema", 37 | 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance", 38 | 'xmlns:env' => "http://schemas.xmlsoap.org/soap/envelope/", 39 | 'xmlns:vim25' => "urn:vim25"} 40 | 41 | # Hash describing a full traversal across all traversable objects 42 | FULL_PROP_TRAVERSAL = { 43 | 'visitFolders' => {:type => 'Folder', 44 | :path => 'childEntity', 45 | :skip => false, 46 | :select => ['visitFolders', 'dcToHf', 'dcToVmf', 47 | 'crToH', 'crToRp', 'HToVm', 'rpToVm']}, 48 | 'dcToVmf' => {:type => 'Datacenter', 49 | :path => 'vmFolder', 50 | :skip => false, 51 | :select => ['visitFolders']}, 52 | 'dcToHf' => {:type => 'Datacenter', 53 | :path => 'hostFolder', 54 | :skip => false, 55 | :select => ['visitFolders']}, 56 | 'crToH' => {:type => 'ComputeResource', 57 | :path => 'host', 58 | :skip => false}, 59 | 'crToRp' => {:type => 'ComputeResource', 60 | :path => 'resourcePool', 61 | :skip => false, 62 | :select => ['rpToRp', 'rpToVm']}, 63 | 'rpToRp' => {:type => 'ResourcePool', 64 | :path => 'resourcePool', 65 | :skip => false, 66 | :select => ['rpToRp', 'rpToVm']}, 67 | 'HToVm' => {:type => 'HostSystem', 68 | :path => 'vm', 69 | :skip => false, 70 | :select => ['visitFolders']}, 71 | 'rpToVm' => {:type => 'ResourcePool', 72 | :path => 'vm', 73 | :skip => false}} 74 | 75 | end -------------------------------------------------------------------------------- /lib/spherical/host.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | class Host 26 | 27 | attr_reader :api, :service, :user 28 | 29 | DEFAULT_OPTS = {} # None, at the moment 30 | 31 | # Initialize a new host instance. Accepts a hash of options requiring: 32 | # +:api+:: The URL of your vSphere or ESXi endpoint (i.e. 'http://your-vsphere-host-fqdn/sdk'). 33 | # Optionally: 34 | # +:username+:: A username to log into the server with. 35 | # +:password+:: The password for +:username+'s account. If both a 36 | # +:username+ and +:password+ is provided, you do not need 37 | # to explicitly call login(), as it will be done for you 38 | # automatically. 39 | def initialize(opts = {}) 40 | @options = DEFAULT_OPTS.merge(opts) 41 | @api = Spherical::Interface.new(self, @options[:api]) 42 | @service = ManagedReference.build(self, :ServiceInstance, 'ServiceInstance') 43 | login if @options.include?(:username) and @options.include?(:password) 44 | end 45 | 46 | # Login to the vSphere server. Not required if you provided a +:username+ 47 | # and +:password+ when initializing the host. 48 | def login(username = @options[:username], password = @options[:password]) 49 | @user = @service.session_manager.login(:userName => username, :password => password) 50 | end 51 | 52 | # Get information about the vSphere server. This call typically provides 53 | # a hash with the following keys (at a minimum): 54 | # +apiType+:: Indicates whether or not the service is a standalone host 55 | # +apiVersion+:: The version of the API as a dot-separated string 56 | # +build+:: The build string for the server 57 | # +fullName+:: The complete product name, including version information 58 | # +name+:: A short version of the product name 59 | # +osType+:: The server OS/architecture (for ex: 'win32-x86' or 'linux-x86') 60 | # +productLineId+:: Product ID of the product line (for ex: 'esx' or 'vpx') 61 | # +vendor+:: Name of the vendor of the product 62 | # +version+:: Dot-separated version string 63 | def about 64 | @service.about 65 | end 66 | 67 | # Retrieve an array of Datacenter instances that exist in the root folder 68 | # of the vSphere endpoint. 69 | def datacenters 70 | @service.root_folder.children_of_type(:Datacenter) 71 | end 72 | 73 | def to_s # :nodoc: 74 | "" 75 | end 76 | 77 | end 78 | 79 | end -------------------------------------------------------------------------------- /lib/spherical/interface.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | class Interface 26 | 27 | # Create a new Interface instance which will attach to a vSphere or 28 | # ESX/i endpoint on behalf of a Host instance. Accepts a +host_inst+ 29 | # instance which will be passed as a root object to all instances 30 | # instantiated as the result of requests, and an endpoint which to 31 | # connect to. 32 | # 33 | # my_interface = Interface.new(host, 'https://vsphere.example.com/sdk') 34 | def initialize(host_inst, endpoint) 35 | @host = host_inst 36 | @client = Savon::Client.new do 37 | wsdl.document = Spherical::WSDL_PATH 38 | wsdl.endpoint = endpoint 39 | wsdl.namespace = 'urn:vim25' 40 | http.auth.ssl.verify_mode = :none # no local issuer cert support 41 | end 42 | end 43 | 44 | # Determine if +sym+ can be invoked on the remote interface. Returns boolean. 45 | def supports_request?(sym) 46 | @client.wsdl.soap_actions.include?(sym) 47 | end 48 | 49 | # Coerce a structured hash to XML via a provided Builder instance, with 50 | # native support for coercions of Managed Object References. 51 | # 52 | # The following rules apply when converting hashes to XML: 53 | # * If any hash value is a managed reference of any type, it will be 54 | # automatically converted to XML as a +ManagedObjectReference+. The key 55 | # that it is associated with will become the name of the tag. 56 | # 57 | # {:obj => my_folder} becomes 58 | # Folder-0 59 | # 60 | # * Hashes can optionally contain two special keys: +content!+ and 61 | # +attributes!+. These allow finer grained control of the output by 62 | # allowing you to specify tag attributes, and when content should be 63 | # taken literally rather than being subject to coercion. If no 64 | # +:content!+ key is provided, keys will be represented as tags, and 65 | # coerced recursively. 66 | # 67 | # {:name => 'ken', :age => 10} becomes 68 | # ken10 69 | # 70 | # * Hash values for which an array is provided will be converted to a 71 | # set of tags whose names match the key pointing to the array. 72 | # 73 | # {:name => ['ken', 'tigger']} becomes 74 | # kentigger 75 | def coerce_to_xml(xml, params) 76 | return nil if params.empty? 77 | params.each do |key, val| 78 | 79 | case 80 | when val.kind_of?(ManagedReference) 81 | tag = [val.id, {'type' => val.type, 'xsi:type' => 'vim25:ManagedObjectReference'}] 82 | key.eql?(:this!) ? tag.insert(0, :_this) : tag.insert(0, key) 83 | xml.vim25(*tag) 84 | 85 | when val.kind_of?(Hash) 86 | content = val.pull(:content!, nil) 87 | attributes = val.pull(:attributes!, nil) 88 | if content.nil? # val *is* the content 89 | xml.vim25(key.to_sym, *attributes) do 90 | coerce_to_xml(xml, val) 91 | end 92 | else 93 | xml.vim25(key.to_sym, content, *attributes) 94 | end 95 | 96 | when val.kind_of?(Array) 97 | xml.vim25(key.to_sym) do 98 | val.each do |item| 99 | coerce_to_xml(xml, item) 100 | end 101 | end 102 | 103 | else # any normal type 104 | xml.vim25(key.to_sym, *val) 105 | end 106 | 107 | end 108 | end 109 | 110 | # Perform a request on the API endpoint using SOAP protocol. Accepts the 111 | # following arguments: 112 | # 113 | # +sym+:: Remote method name as a symbol converted to underscore format 114 | # rather than camelCase. 115 | # +params+:: Request parameters which will be coerced to XML 116 | # using the coerce_to_xml method. 117 | # +&block+:: An optional block which allows you to build requests by 118 | # directly accessing the Builder instance and the remainder of 119 | # the Savon API. 120 | # 121 | # The first tag typically contains the response from 122 | # the server, so it is eliminated, and its contents are converted to 123 | # native objects (by way of XmlSimple) and are further coerced and 124 | # simplified by the response_to_native method. If a single result is 125 | # returned, it is converted to an OpenStruct object with a special method 126 | # called _hash that allows direct access to the original data 127 | # structure. If multiple results are returned, they are converted to an 128 | # array of OpenStructs, each with a _hash method. 129 | # 130 | # SoapFault exceptions will be raised automatically when making requests. 131 | def request(sym, params = {}, &block) 132 | response = @client.request(:vim25, sym) do |soap, wsdl, http, wsse| 133 | soap.xml do |xml| 134 | xml.env(:Envelope, Spherical::SOAP_NS){ 135 | xml.env(:Body){ 136 | xml.vim25(sym){ 137 | coerce_to_xml(xml, params) 138 | block.call(xml, soap, wsdl, http, wsse) if block_given? 139 | } # method call 140 | } # env :Body 141 | } # envelope 142 | end 143 | end 144 | raw_hash = XmlSimple.xml_in(response.to_xml, { 'KeyAttr' => ['key', 'id'], 'ForceArray' => false }) 145 | result = raw_hash.value_at_first('returnval') # hack to find deeply nested response. 146 | native = response_to_native(result) 147 | case 148 | when native.kind_of?(Hash) 149 | return native.to_ostruct 150 | when native.kind_of?(Array) 151 | return native.map!{|o| o.to_ostruct } 152 | end 153 | end 154 | 155 | ####### 156 | private 157 | ####### 158 | 159 | # Coerce a response to a native data structure, recursively. 160 | def response_to_native(structure) 161 | case 162 | when structure.kind_of?(Hash) 163 | if structure.has_key?('type') or structure.has_key?('xsi:type') # it's an object 164 | type = structure['type'] || structure['xsi:type'] 165 | if type =~ /^ArrayOf/ 166 | type.sub!(/^ArrayOf/, '') # get class name. 167 | return structure.has_key?(type) ? response_to_native(structure[type]) : [] 168 | else 169 | structure.delete('type'); structure.delete('xsi:type') 170 | return coerce_to_native(type, response_to_native(structure)) 171 | end 172 | else # it's a hash, possibly containing objects. 173 | out = {} 174 | structure.each do |key, value| 175 | out[key] = response_to_native(value) # Value at key is not an object. 176 | end 177 | return out 178 | end 179 | when structure.kind_of?(Array) 180 | structure.map do |item| 181 | response_to_native(item) 182 | end 183 | else 184 | structure 185 | end 186 | end 187 | 188 | # Coerce a single value to a native object, or a ManagedReference. 189 | def coerce_to_native(type, options = {}) 190 | xsd_coercion = Spherical::XSD_NATIVE_COERCIONS[type] 191 | return xsd_coercion.call(options['content']) unless xsd_coercion.nil? 192 | ManagedReference.build(@host, type.to_sym, options['content'], options) 193 | end 194 | 195 | end 196 | 197 | end -------------------------------------------------------------------------------- /lib/spherical/mixins/callable_content.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | module ContentMethodInheritance 26 | 27 | def self.included(base) 28 | base.send :extend, ClassMethods 29 | base.send :include, InstanceMethods 30 | end 31 | 32 | module ClassMethods 33 | 34 | end 35 | 36 | module InstanceMethods 37 | 38 | def method_missing(sym, *opts, &block) 39 | return @content.send(sym) if @content.respond_to? sym 40 | super(sym, *opts, &block) 41 | end 42 | 43 | end 44 | 45 | end 46 | 47 | end -------------------------------------------------------------------------------- /lib/spherical/mixins/searchable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | module Searchable 26 | 27 | def self.included(base) 28 | base.send :extend, ClassMethods 29 | base.send :include, InstanceMethods 30 | end 31 | 32 | module ClassMethods 33 | 34 | end 35 | 36 | module InstanceMethods 37 | 38 | def find_by_name(name, type = Object) 39 | @host.service.search_index.find_child(:entity => self, :name => name) 40 | end 41 | 42 | def find_by_hostname(hostname, filters = {}, type = Spherical::VirtualMachine) 43 | @host.service.search_index.find_by_dns_name({ 44 | :entity => self, 45 | :dnsName => hostname, 46 | :vmSearch => type == Spherical::VirtualMachine 47 | }.merge(filters)) 48 | end 49 | 50 | def find_by_ip(address, filters = {}, type = Spherical::VirtualMachine) 51 | @host.service.search_index.find_by_dns_name({ 52 | :entity => self, 53 | :ip => address, 54 | :vmSearch => type == Spherical::VirtualMachine 55 | }.merge(filters)) 56 | end 57 | 58 | def find_by_uuid(uuid, filters = {}, type = Spherical::VirtualMachine) 59 | @host.service.search_index.find_by_uuid({ 60 | :entity => self, 61 | :uuid => uuid, :instanceUuid => false, 62 | :vmSearch => type == Spherical::VirtualMachine 63 | }.merge(filters)) 64 | end 65 | 66 | end 67 | 68 | end 69 | 70 | end -------------------------------------------------------------------------------- /lib/spherical/mixins/traversable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | module Traversable 26 | 27 | def self.included(base) 28 | base.send :extend, ClassMethods 29 | base.send :include, InstanceMethods 30 | end 31 | 32 | module ClassMethods 33 | 34 | end 35 | 36 | module InstanceMethods 37 | 38 | def each(type, &block) 39 | @host.service.property_collector.children_for_object(self, type) 40 | end 41 | 42 | # Find all children of the baseclass. The baseclass's server-side 43 | # managed object must have a childEntity property for this to work. 44 | def children_of_type(*args) 45 | @host.service.property_collector.children_for_object(self, *args) 46 | end 47 | 48 | end 49 | 50 | end 51 | 52 | end -------------------------------------------------------------------------------- /lib/spherical/types/datacenter.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | class Datacenter < ManagedReference 26 | 27 | represent_managed :Datacenter 28 | property_reader 'vmFolder' 29 | property_reader 'datastore' 30 | 31 | include Searchable # allow Datacenters to be searched 32 | 33 | def inventory 34 | vm_folder.children_of_type(:VirtualMachine) 35 | end 36 | 37 | end 38 | 39 | end -------------------------------------------------------------------------------- /lib/spherical/types/folder.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | class Folder < ManagedReference 26 | 27 | represent_managed :Folder 28 | include Searchable # allow Folders to be searched 29 | include Traversable # this entity has children 30 | 31 | # Create a new folder beneath this one. Return a ManagedReference to 32 | # the new folder. 33 | def create_subfolder(name) 34 | create_folder(:name => name) 35 | end 36 | 37 | def traverse(path, type = Object, create = false) 38 | elements = path.kind_of?(String) ? path.split('/').reject(&:empty?) : path 39 | return self if elements.empty? 40 | target = elements.pop 41 | path = elements.inject(self) do |folder, ele| 42 | folder.find(ele, Spherical::Folder) || (create && folder.create_subfolder(ele)) || return 43 | end 44 | if result = path.find(target, type) 45 | result 46 | elsif create and type == Spherical::Folder 47 | path.create_subfolder(target) 48 | else 49 | nil 50 | end 51 | end 52 | 53 | end 54 | 55 | end -------------------------------------------------------------------------------- /lib/spherical/types/properties.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | # The ServiceInstance class is a root/top-level class representitive of the 26 | # details of a connection with a client. 27 | 28 | class PropertyCollector < ManagedReference 29 | 30 | represent_managed :PropertyCollector 31 | 32 | def children_for_object(obj, match_type, attrs = ['name', 'parent'], &block) 33 | type = match_type.kind_of?(ManagedReference) ? match_type.type.to_s : match_type.to_s 34 | results = retrieve_properties do |xml| 35 | xml.vim25(:specSet, 'xsi:type' => 'PropertyFilterSpec'){ 36 | # specify properties to select for each object returned 37 | xml.propSet{ 38 | xml.type type 39 | if attrs == :all 40 | xml.all true 41 | else 42 | attrs.each{|attr| xml.pathSet attr } 43 | end 44 | } 45 | # specify objects to filter 46 | xml.objectSet{ 47 | @host.api.coerce_to_xml(xml, {:obj => obj}) 48 | xml.skip false 49 | Spherical::FULL_PROP_TRAVERSAL.each do |name, params| 50 | xml.vim25(:selectSet, 'xsi:type' => 'TraversalSpec') { 51 | xml.name name 52 | xml.type params[:type] 53 | xml.path params[:path] 54 | if params.key?(:select) 55 | params[:select].each do |sel| 56 | xml.vim25(:selectSet, 'xsi:type' => 'SelectionSpec') { xml.name sel } 57 | end 58 | end 59 | } 60 | end 61 | } 62 | } 63 | end 64 | 65 | structure = [] 66 | obj_results = results.kind_of?(Array) ? results : [results] # if multiple results 67 | obj_results.each do |obj_result| 68 | prop_set = obj_result.prop_set.kind_of?(Array) ? obj_result.prop_set : [obj_result.prop_set._hash] 69 | prop_set = Hash[prop_set.map{ |p| [p['name'], p['val']] }] 70 | final_obj = obj_result.obj.apply_attributes(prop_set) 71 | yield final_obj if block_given? 72 | structure << final_obj 73 | end 74 | 75 | return structure 76 | 77 | end 78 | 79 | def get_object_properties(obj, props) 80 | unless obj.kind_of?(ManagedReference) 81 | raise TypeError.new("Invalid object #{obj}. Must be a ManagedReference.") 82 | end 83 | results = retrieve_properties do |xml| 84 | xml.vim25(:specSet, 'vim25:type' => 'PropertyFilterSpec'){ 85 | # specify properties to select for each object returned 86 | xml.propSet{ 87 | xml.type obj.type.to_s 88 | (props == :all) ? xml.all(true) : props.each{|p| xml.pathSet p } 89 | } 90 | xml.objectSet{ @host.api.coerce_to_xml(xml, :obj => obj) } # target obj 91 | } 92 | end 93 | 94 | raise "Unable to retrieve #{props} for #{obj}." unless results._hash 95 | 96 | structure = {} 97 | obj_results = results._hash.kind_of?(Array) ? results : [results] # if multiple results 98 | obj_results.each do |obj_result| 99 | prop_set = obj_result.prop_set.kind_of?(Array) ? obj_result.prop_set : [obj_result.prop_set._hash] 100 | prop_set = Hash[prop_set.map{ |p| [p['name'], p['val']] }] 101 | structure.merge!(prop_set) 102 | end 103 | 104 | return structure 105 | 106 | end 107 | 108 | end 109 | 110 | end -------------------------------------------------------------------------------- /lib/spherical/types/property_filter.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | # The ServiceInstance class is a root/top-level class representitive of the 26 | # details of a connection with a client. 27 | 28 | class PropertyFilter < ManagedReference 29 | 30 | represent_managed :PropertyFilter # represent MOR ServiceInstance 31 | 32 | def destroy 33 | destory_property_filter 34 | end 35 | 36 | end 37 | 38 | end -------------------------------------------------------------------------------- /lib/spherical/types/service.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | # The ServiceInstance class is a root/top-level class representitive of the 26 | # details of a connection with a client. 27 | 28 | class ServiceInstance < ManagedReference 29 | 30 | represent_managed :ServiceInstance # represent MOR ServiceInstance 31 | include ContentMethodInheritance # inherit methods from @content 32 | 33 | attr_reader :about 34 | 35 | def initialize(*opts) 36 | super(*opts) 37 | @content = retrieve_service_content 38 | @about = @content.about._hash 39 | end 40 | 41 | def find_datacenter(path = nil) 42 | unless path.nil? 43 | root_folder.traverse path, Spherical::Datacenter 44 | else 45 | root_folder.children.grep(Spherical::Datacenter).first 46 | end 47 | end 48 | 49 | end 50 | 51 | end -------------------------------------------------------------------------------- /lib/spherical/types/task.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | # The ServiceInstance class is a root/top-level class representitive of the 26 | # details of a connection with a client. 27 | 28 | class Task < ManagedReference 29 | 30 | represent_managed :Task # represent MOR ServiceInstance 31 | 32 | attr_reader :about 33 | 34 | # Block until task is complete. Note that this will function will 35 | # poll the server continuously awaiting completion. 36 | def complete! 37 | wait_for_update('info.state'){ %w(success error).member? info.state } 38 | end 39 | 40 | end 41 | 42 | end -------------------------------------------------------------------------------- /lib/spherical/types/virtual_machine.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | class VirtualMachine < ManagedReference 26 | 27 | represent_managed :VirtualMachine # represent MOR VirtualMachine 28 | property_reader ['config', 'summary'], :memoize => true # lazy-load these 29 | 30 | def stats 31 | summary.quick_stats 32 | end 33 | 34 | def guest_full_name; summary.config['guestFullName'] end 35 | def uuid; summary.config['instanceUuid'] end 36 | def name; summary.config['name'] end 37 | def num_cpus; summary.config['numCpu'] end 38 | def reserved_memory; summary.config['memoryReservation'] end 39 | 40 | def devices 41 | config.hardware['device'] 42 | end 43 | 44 | def mac_addresses 45 | nics = [] 46 | devices.each do |dev_id, dev| 47 | nics << dev if [:VirtualEthernetCard, :VirtualE1000].include? dev.type 48 | end 49 | Hash[nics.map{|nic| [nic.device_info['label'], nic.mac_address]}] 50 | end 51 | 52 | # Create a snapshot of the current virtual machine, and update the 53 | # current snapshot. A name for the new snapshot is required, and you may 54 | # optionally capture memory, or (if VMware Tools is installed) quiesce 55 | # the guest file system (ensuring that the snapshot represents a constant 56 | # system state). 57 | # 58 | # create_snapshot('my snapshot', :memory => true, :quiesce => true) 59 | # 60 | # Returns a Task instance which can be used to track the progress of 61 | # snapshot creation. 62 | def create_snapshot(name, opts = {}) 63 | opts = {:name => name, :description => name, 64 | :memory => false, :quiesce => false}.merge!(opts) 65 | create_snapshot_task(opts) 66 | end 67 | 68 | # Clone this VM to the given folder and location, and optionally 69 | # power it on. Accepts the following arguments: 70 | # 71 | # +name+:: Unique name for the new VM. Percent character (%) must be 72 | # escaped. 73 | # +dest+:: A Folder instance in which to place the new VM 74 | # +store+:: A hash accepting a +:datastore+ (and, optionally, +:host+) 75 | # key specifying the location to store the files associated 76 | # with the new VM. Both should be managed reference instances 77 | # of the types their names suggest. 78 | # +power_on+:: Boolean indicating if the cloned VM should be automatically 79 | # powered on upon completion. 80 | # +config+:: Optionally allows reconfiguration of VM parameters upon 81 | # completion. If you don't know what you're doing, don't use it. 82 | def clone(name, dest, store, power_on = true, config = {}) 83 | clone_vm_task(:folder => dest, 84 | :name => name, 85 | :spec => {:location => store}) 86 | end 87 | 88 | # Rename the virtual machine to the provided new_name. new_name should be 89 | # escaped by the user. 90 | def rename(new_name) 91 | rename_task(:new_name => new_name) 92 | end 93 | 94 | # Power on the virtual machine. Returns a Task instance which can be 95 | # polled or blocked upon for completion. 96 | def start 97 | power_on_vm_task 98 | end 99 | 100 | # Attempt to gracefully shutdown the guest operating system, and power 101 | # off the virtual machine. Returns immediately. Success is unknown. 102 | def stop 103 | shutdown_guest 104 | end 105 | 106 | # Perform a hard restart on the virtual machine, regardless of the guest 107 | # OS's status. Returns a new Task representing the restart request. 108 | def restart! 109 | reset_vm_task 110 | end 111 | 112 | # Attempt to perform a graceful reboot by signaling the guest operating 113 | # system. Returns immediately. Success is unknown. 114 | def restart 115 | reboot_guest 116 | end 117 | 118 | # Attempt to suspend the guest OS. Returns immediately, success is unknwon. 119 | def suspend 120 | standby_guest 121 | end 122 | 123 | # Suspend a virtual machine to disk without waiting for the guest OS. 124 | def suspend! 125 | suspend_vm_task 126 | end 127 | 128 | # Perform a hard shutdown on the virtual machine, ignoring guest OS 129 | # status. Returns a Task representing the shutdown request. 130 | def halt! 131 | power_off_vm_task 132 | end 133 | 134 | ############# 135 | # existential 136 | ############# 137 | 138 | # Destroy a virtual machine, removing its disks and configuration from 139 | # its current datastore. Returns a Task instance representing the request. 140 | def destroy! 141 | destroy_task 142 | end 143 | 144 | end 145 | 146 | end -------------------------------------------------------------------------------- /lib/spherical/util.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Red Hat, Inc. 2 | # Written by Ken Keiter 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; version 2 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | # MA 02110-1301, USA. A copy of the GNU General Public License is 17 | # also available at http://www.gnu.org/copyleft/gpl.html. 18 | # 19 | # Author:: Kenneth Keiter (mailto:ken@kenkeiter.com) 20 | # Copyright:: Copyright (C) 2011 Red Hat, Inc. 21 | # License:: Distributed under GPLv2 22 | 23 | module Spherical 24 | 25 | # Require all files according to +matcher+ (a glob-matching pattern). See 26 | # http://ruby-doc.org/core/classes/Dir.html#M000629 for more information.6 27 | def require_all(matcher) 28 | Dir.glob(matcher).each{|file| require file } 29 | end 30 | 31 | module_function :require_all 32 | 33 | end 34 | 35 | class String 36 | # Some methods imported from ActiveSupport gem, inflector.rb 37 | 38 | def underscore 39 | to_s.gsub(/::/, '/'). 40 | gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). 41 | gsub(/([a-z\d])([A-Z])/,'\1_\2'). 42 | tr("-", "_"). 43 | downcase 44 | end 45 | 46 | def camelize(first_letter_in_uppercase = true) 47 | if first_letter_in_uppercase 48 | to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } 49 | else 50 | to_s.first + camelize(to_s)[1..-1] 51 | end 52 | end 53 | 54 | end 55 | 56 | class OpenStruct 57 | 58 | # Hack to allow us to read the table directly. 59 | attr_reader :table 60 | 61 | end 62 | 63 | class Hash 64 | 65 | # Recursively merge with other_hash in place. 66 | # source:: http://gist.github.com/gists/6391/ 67 | def rmerge!(other_hash) 68 | merge!(other_hash) do |key, oldval, newval| 69 | oldval.class == self.class ? oldval.rmerge!(newval) : newval 70 | end 71 | end 72 | 73 | # Recursively merge with another hash, resulting in a new hash. 74 | # source:: http://gist.github.com/gists/6391/ 75 | def rmerge(other_hash) 76 | r = {} 77 | merge(other_hash) do |key, oldval, newval| 78 | r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval 79 | end 80 | end 81 | 82 | # Peform breadth-first recursive map of hash. 83 | def rmap(&block) 84 | r = {} 85 | each do |k, v| 86 | test_val = v.class == self.class ? v.rmap(&block) : v 87 | item = block.call(k, test_val) 88 | item ? r[item.first] = item.last : r[k] = test_val # no changes made. 89 | end 90 | return r 91 | end 92 | 93 | def path_exists?(*path) 94 | path.inject(self) do |location, key| 95 | location.respond_to?(:keys) ? location[key] : nil 96 | end 97 | end 98 | 99 | # Perform a deep recursion through the hash, finding the first +key+. 100 | # For example: 101 | # 102 | # {'fish' => {'halibut' => 1, 'trout' => 2}}.value_at_first('halibut') == 1 103 | def value_at_first(key) 104 | return fetch(key) if include? key 105 | each do |k, v| 106 | if v.class == self.class 107 | return v.has_key?(key) ? v[key] : v.value_at_first(key) 108 | end 109 | end 110 | end 111 | 112 | # Remove the key and value from the hash, and return the value. If the key 113 | # does not exist, optionally return +value+. Return nil if +value+ is 114 | # unspecified. 115 | def pull(key, value = nil) 116 | if include? key 117 | value = fetch(key) 118 | delete(key) 119 | end 120 | return value 121 | end 122 | 123 | # Convert the hash to an OpenStruct instance. Adds a method to the 124 | # resulting OpenStruct called +_hash+ that provides access to the original 125 | # hash that was converted. 126 | def to_ostruct(klass = OpenStruct, cch = {}) 127 | cch[self] = (os = klass.new) 128 | os.__send__("_hash=", to_hash) 129 | each do |k, v| 130 | native_key = k.underscore 131 | raise "Invalid key: #{native_key}" unless native_key =~ /[a-z_][a-zA-Z0-9_]*/ 132 | os.__send__("#{native_key}=", v.is_a?(Hash)? cch[v] || v.to_ostruct(klass, cch) : v) 133 | end 134 | os 135 | end 136 | 137 | end 138 | -------------------------------------------------------------------------------- /spec/host_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe Spherical::Host do 4 | 5 | context 'upon connecting' do 6 | 7 | before(:all) do 8 | @host = create_test_host 9 | end 10 | 11 | it "should provide service information" do 12 | @host.about.should be_instance_of(Hash) 13 | end 14 | 15 | it "should list datacenters" do 16 | @host.datacenters.should be_instance_of(Array) 17 | end 18 | 19 | it 'should list inventory' do 20 | @host.datacenters.each do |dc| 21 | dc.instances.each do |child| 22 | puts "-----> #{child.guest_full_name}" 23 | puts " #{child.mac_addresses}" 24 | end 25 | end 26 | end 27 | 28 | end 29 | 30 | end -------------------------------------------------------------------------------- /spec/managedref_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe Spherical::ManagedReference do 4 | 5 | context 'when managing types' do 6 | 7 | before(:all) do 8 | Spherical::ManagedReference.clear_all! 9 | end 10 | 11 | it "should allow definition of new types" do 12 | klass = Class.new(Spherical::ManagedReference) 13 | klass.represent_managed(:Halibut) 14 | klass.type.should == :Halibut 15 | Spherical::ManagedReference.type_defined?(:Halibut).should == true 16 | end 17 | 18 | it "should allow the instantiation of new instances of defined types" do 19 | klass = Class.new(Spherical::ManagedReference) 20 | klass.represent_managed(:Halibut) 21 | 22 | inst = Spherical::ManagedReference.build(nil, :Halibut, 'thing-1') 23 | inst.class.type.should == :Halibut 24 | inst.id.should == 'thing-1' 25 | end 26 | 27 | # Managed reference classes that are undefined are still valid. This 28 | # allows us to support server-side MORs generically without defining a 29 | # wrapping class in the Spherical library. 30 | it "should allow the instantation of arbitrary types" do 31 | inst = Spherical::ManagedReference.build(nil, :Halibut, 'thing-1') 32 | inst.class.type.should == :Halibut 33 | inst.id.should == 'thing-1' 34 | end 35 | 36 | it "should associate unique Class instances with arbitrary types" do 37 | first_inst = Spherical::ManagedReference.build(nil, :Halibut, 'thing-1') 38 | second_inst = Spherical::ManagedReference.build(nil, :Halibut, 'thing-2') 39 | # Should be of the same class, different instances 40 | first_inst.class.should == second_inst.class 41 | first_inst.class.type.should == :Halibut 42 | # IDs should be set individually 43 | first_inst.id.should == 'thing-1' 44 | second_inst.id.should == 'thing-2' 45 | end 46 | 47 | end 48 | 49 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.join(File.dirname(__FILE__), '../lib') 2 | 3 | require 'spherical' 4 | 5 | def create_test_host 6 | Spherical::Host.new :api => 'https://vsphere.example.com/sdk', 7 | :username => 'username', 8 | :password => 'password' 9 | end --------------------------------------------------------------------------------